-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
13
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 7 commits
Commits
Show all changes
13 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 296427c
fix(provider): preserve OpenCode Go reasoning config
VonLan233 d500f1b
fix(provider): forward extra user content in streams
VonLan233 23e6788
fix(dashboard): add reasoning config metadata translations
VonLan233 8f869d8
Merge branch 'master' into feat/issue-8158-opencode-go-provider
VonLan233 b4032a3
Merge remote-tracking branch 'origin/master' into codex/pr-8179-resol…
Soulter fa51f3e
Merge remote-tracking branch 'origin/master' into codex/pr-8179-resol…
Soulter 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 | ||
|
Comment on lines
+94
to
+153
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
例如,可以创建一个 def _resolve_model(self, model: str | None) -> str:
requested_model = model or self.get_model()
return self._ensure_chat_completions_model(requested_model)此外,新功能的实现(如该 Provider 的核心逻辑)应当伴随相应的单元测试以确保稳定性。 References
|
||
|
|
||
| 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.
OpenCode 所有的模型都支持传入 reasoning_content 吗?
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.
minimax的不支持,但是opencode的后端会为不支持的模型忽略reasoning_content
除了deepseek会要求reasoning_content,其他模型都作为可选项 可传可不传