-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Feat: Opencode Go Subcription as Provider #8179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
VonLan233
wants to merge
7
commits into
AstrBotDevs:master
Choose a base branch
from
VonLan233:feat/issue-8158-opencode-go-provider
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
5ff796e
feat(provider): add OpenCode Go chat provider
VonLan233 af88dcb
feat(dashboard): add OpenCode Go provider icon
VonLan233 a5a74da
feat(dashboard): update OpenCode Go provider icon to local asset
VonLan233 b6a5ade
fix(dashboard): render OpenCode Go provider icon
VonLan233 45a5e3e
fix(provider): preserve Kimi tool-call reasoning history
VonLan233 340d4e6
fix(provider): prefix OpenCode Go model identifiers
VonLan233 7fbfa80
refactor(provider): make tool-call reasoning configurable
VonLan233 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| from collections.abc import AsyncGenerator | ||
| from typing import Literal | ||
|
|
||
| from astrbot.api.provider import Provider | ||
| from astrbot.core.agent.message import ContentPart, Message | ||
| from astrbot.core.agent.tool import ToolSet | ||
| from astrbot.core.provider.entities import LLMResponse, ToolCallsResult | ||
|
|
||
| from ..register import register_provider_adapter | ||
| from .openai_source import ProviderOpenAIOfficial | ||
|
|
||
| OPENCODE_GO_API_BASE = "https://opencode.ai/zen/go/v1" | ||
| OPENCODE_GO_MODEL_PREFIX = "opencode-go/" | ||
| OPENCODE_GO_DEFAULT_MODEL = "kimi-k2.6" | ||
| OPENCODE_GO_MESSAGES_ONLY_MODELS = {"minimax-m2.5", "minimax-m2.7"} | ||
|
|
||
|
|
||
| @register_provider_adapter( | ||
| "opencode_go_chat_completion", | ||
| "OpenCode Go Subscription Provider Adapter", | ||
| ) | ||
| class ProviderOpenCodeGo(Provider): | ||
| def __init__(self, provider_config: dict, provider_settings: dict) -> None: | ||
| super().__init__(provider_config, provider_settings) | ||
| self.api_base = provider_config.get("api_base", OPENCODE_GO_API_BASE).rstrip( | ||
| "/" | ||
| ) | ||
| self.timeout = provider_config.get("timeout", 120) | ||
| if isinstance(self.timeout, str): | ||
| self.timeout = int(self.timeout) | ||
|
|
||
| model = self._to_api_model( | ||
| provider_config.get("model", OPENCODE_GO_DEFAULT_MODEL) | ||
| ) | ||
| self.set_model(model) | ||
|
|
||
| self.openai_provider = ProviderOpenAIOfficial( | ||
| self._build_delegate_config(model=model), | ||
| provider_settings, | ||
| ) | ||
|
|
||
| def _build_delegate_config(self, *, model: str) -> dict: | ||
| config = dict(self.provider_config) | ||
| config["api_base"] = self.api_base | ||
| config["model"] = model | ||
| config["force_tool_call_reasoning_content"] = True | ||
| return config | ||
|
|
||
| @classmethod | ||
| def _to_api_model(cls, model: str | None) -> str: | ||
| resolved_model = (model or OPENCODE_GO_DEFAULT_MODEL).strip() | ||
| if resolved_model.startswith(OPENCODE_GO_MODEL_PREFIX): | ||
| return resolved_model.removeprefix(OPENCODE_GO_MODEL_PREFIX) | ||
| return resolved_model | ||
|
|
||
| @classmethod | ||
| def _to_provider_model(cls, model: str) -> str: | ||
| api_model = cls._to_api_model(model) | ||
| return f"{OPENCODE_GO_MODEL_PREFIX}{api_model}" | ||
|
|
||
| @classmethod | ||
| def _ensure_chat_completions_model(cls, model: str | None) -> str: | ||
| api_model = cls._to_api_model(model) | ||
| if api_model in OPENCODE_GO_MESSAGES_ONLY_MODELS: | ||
| raise ValueError( | ||
| f"OpenCode Go model {OPENCODE_GO_MODEL_PREFIX}{api_model} uses " | ||
| "/v1/messages. This adapter currently supports " | ||
| "/v1/chat/completions models only." | ||
| ) | ||
| return api_model | ||
|
|
||
| def _resolve_model(self, model: str | None = None) -> str: | ||
| return self._ensure_chat_completions_model(model or self.get_model()) | ||
|
|
||
| def get_current_key(self) -> str: | ||
| return self.openai_provider.get_current_key() | ||
|
|
||
| def get_keys(self) -> list[str]: | ||
| return self.openai_provider.get_keys() | ||
|
|
||
| def set_key(self, key: str) -> None: | ||
| self.openai_provider.set_key(key) | ||
|
|
||
| async def get_models(self) -> list[str]: | ||
| models = await self.openai_provider.get_models() | ||
| provider_models: list[str] = [] | ||
| for model in models: | ||
| api_model = self._to_api_model(model) | ||
| if not api_model or api_model in OPENCODE_GO_MESSAGES_ONLY_MODELS: | ||
| continue | ||
| provider_models.append(f"{OPENCODE_GO_MODEL_PREFIX}{api_model}") | ||
| return sorted(provider_models) | ||
|
|
||
| async def text_chat( | ||
| self, | ||
| prompt: str | None = None, | ||
| session_id: str | None = None, | ||
| image_urls: list[str] | None = None, | ||
| audio_urls: list[str] | None = None, | ||
| func_tool: ToolSet | None = None, | ||
| contexts: list[Message] | list[dict] | None = None, | ||
| system_prompt: str | None = None, | ||
| tool_calls_result: ToolCallsResult | list[ToolCallsResult] | None = None, | ||
| model: str | None = None, | ||
| extra_user_content_parts: list[ContentPart] | None = None, | ||
| tool_choice: Literal["auto", "required"] = "auto", | ||
| **kwargs, | ||
| ) -> LLMResponse: | ||
| return await self.openai_provider.text_chat( | ||
| prompt=prompt, | ||
| session_id=session_id, | ||
| image_urls=image_urls, | ||
| audio_urls=audio_urls, | ||
| func_tool=func_tool, | ||
| contexts=contexts, | ||
| system_prompt=system_prompt, | ||
| tool_calls_result=tool_calls_result, | ||
| model=self._resolve_model(model), | ||
| extra_user_content_parts=extra_user_content_parts, | ||
| tool_choice=tool_choice, | ||
| **kwargs, | ||
| ) | ||
|
|
||
| async def text_chat_stream( | ||
| self, | ||
| prompt: str | None = None, | ||
| session_id: str | None = None, | ||
| image_urls: list[str] | None = None, | ||
| audio_urls: list[str] | None = None, | ||
| func_tool: ToolSet | None = None, | ||
| contexts: list[Message] | list[dict] | None = None, | ||
| system_prompt: str | None = None, | ||
| tool_calls_result: ToolCallsResult | list[ToolCallsResult] | None = None, | ||
| model: str | None = None, | ||
| tool_choice: Literal["auto", "required"] = "auto", | ||
| **kwargs, | ||
| ) -> AsyncGenerator[LLMResponse, None]: | ||
| async for response in self.openai_provider.text_chat_stream( | ||
| prompt=prompt, | ||
| session_id=session_id, | ||
| image_urls=image_urls, | ||
| audio_urls=audio_urls, | ||
| func_tool=func_tool, | ||
| contexts=contexts, | ||
| system_prompt=system_prompt, | ||
| tool_calls_result=tool_calls_result, | ||
| model=self._resolve_model(model), | ||
| tool_choice=tool_choice, | ||
| **kwargs, | ||
| ): | ||
| yield response | ||
|
|
||
| async def terminate(self) -> None: | ||
| await self.openai_provider.terminate() | ||
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
text_chat和text_chat_stream方法有重复的逻辑来解析和验证模型名称。为了遵循 DRY (Don't Repeat Yourself) 原则并提高代码可维护性,建议将这部分逻辑提取到一个辅助方法中。例如,可以创建一个
_resolve_model方法:此外,新功能的实现(如该 Provider 的核心逻辑)应当伴随相应的单元测试以确保稳定性。
References