mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-06 05:55:28 +08:00
fix(code-tools): support new-api providers and endpoint type routing for OpenCode (#14511)
### What this PR does Before this PR: Users with NEWAPI backends could use models for normal conversations, but the OpenCode tool's model dropdown did not show any NEWAPI models. Additionally, NEWAPI Claude models (with `endpoint_type='anthropic'`) were forced through `@ai-sdk/openai-compatible` instead of `@ai-sdk/anthropic`, losing Anthropic-specific features like thinking/reasoning support. After this PR: - NEWAPI provider models appear in the OpenCode model dropdown - NEWAPI Claude models use `@ai-sdk/anthropic` with correct base URL formatting (no double `/v1`) - NEWAPI OpenAI models continue using `@ai-sdk/openai-compatible` with `/v1` appended - Native Anthropic/OpenAI/OpenAI-Response providers work without regression Fixes #14468 ### Why we need it and why it was done in this way The root cause was twofold: 1. OpenCode's provider filter (`CLI_TOOL_PROVIDER_MAP`) and model filter (`modelPredicate`) used hardcoded type lists that excluded `new-api` 2. `generateOpenCodeConfig` only checked `provider.type` to select npm packages, ignoring the model's actual `endpoint_type` The following tradeoffs were made: - Used `endpoint_type` (model-level) as primary signal with `provider.type` as fallback, matching the pattern used by Agent functionality (`OpenClawService.determineApiType`) - Added `!endpointType` guard to provider type fallback conditions, preventing conflicts when a model has a different endpoint type than its provider The following alternatives were considered: - Adding `new-api` to filter lists only (minimal fix) — rejected because it wouldn't route Anthropic models correctly - Using `isOpenAICompatibleProvider()` for filtering — rejected because it only checks provider type, not individual model capabilities ### Breaking changes None. ### Special notes for your reviewer Key files to review: - `src/renderer/src/pages/code/index.ts` — Provider filter + env var generation with endpoint-type-aware base URL - `src/renderer/src/pages/code/CodeToolsPage.tsx` — Model filter fallback - `src/main/services/CodeToolsService.ts` — Config generation with endpoint-type-aware npm package selection The `endpoint_type` field is set when models are added to a NEWAPI provider (`useProvider.ts:91-98`), converted from `supported_endpoint_types[0]` returned by the NEWAPI `/models` endpoint. ### Checklist - [x] PR: The PR description is expressive enough and will help future contributors - [x] Code: [Write code that humans can understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans) and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle) - [ ] Refactor: You have [left the code cleaner than you found it (Boy Scout Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html) - [ ] Upgrade: Impact of this change on upgrade flows was considered and addressed if required - [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com) was considered and is present (link) or not required. Check this only when the PR introduces or changes a user-facing feature or behavior. - [x] Self-review: I have reviewed my own code (e.g., via [`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`, or GitHub UI) before requesting review from others ### Release note ```release-note fix(code-tools): OpenCode now shows NEWAPI provider models in the dropdown and correctly routes Anthropic models through @ai-sdk/anthropic ``` --------- Signed-off-by: George·Dong <GeorgeDong32@qq.com>
This commit is contained in:
@@ -175,15 +175,16 @@ class CodeToolsService {
|
||||
supportsReasoningEffort: boolean,
|
||||
budgetTokens: number | undefined,
|
||||
providerType: string,
|
||||
providerName: string
|
||||
providerName: string,
|
||||
endpointType: string
|
||||
): Promise<string> {
|
||||
const configPath = path.join(directory, 'opencode.json')
|
||||
|
||||
// Determine npm package based on provider type
|
||||
// Determine npm package based on endpoint type (model-level) then provider type (fallback)
|
||||
let npmPackage = '@ai-sdk/openai-compatible'
|
||||
if (providerType === 'anthropic') {
|
||||
if (endpointType === 'anthropic' || (!endpointType && providerType === 'anthropic')) {
|
||||
npmPackage = '@ai-sdk/anthropic'
|
||||
} else if (providerType === 'openai-response') {
|
||||
} else if (endpointType === 'openai-response' || (!endpointType && providerType === 'openai-response')) {
|
||||
npmPackage = '@ai-sdk/openai'
|
||||
}
|
||||
|
||||
@@ -192,10 +193,10 @@ class CodeToolsService {
|
||||
name: model.name
|
||||
}
|
||||
|
||||
// Add reasoning config based on provider type
|
||||
// Add reasoning config based on endpoint type and provider type
|
||||
if (isReasoning) {
|
||||
modelConfig.reasoning = true
|
||||
if (providerType === 'anthropic') {
|
||||
if (endpointType === 'anthropic' || (!endpointType && providerType === 'anthropic')) {
|
||||
// Anthropic style: thinking with budgetTokens
|
||||
modelConfig.options = {
|
||||
thinking: {
|
||||
@@ -1036,6 +1037,7 @@ class CodeToolsService {
|
||||
const budgetTokens = env.OPENCODE_MODEL_BUDGET_TOKENS ? Number(env.OPENCODE_MODEL_BUDGET_TOKENS) : undefined
|
||||
const providerType = env.OPENCODE_PROVIDER_TYPE || 'openai-compatible'
|
||||
const providerName = env.OPENCODE_PROVIDER_NAME || 'Studio'
|
||||
const endpointType = env.OPENCODE_MODEL_ENDPOINT_TYPE || ''
|
||||
|
||||
const configPath = await this.generateOpenCodeConfig(
|
||||
directory,
|
||||
@@ -1045,7 +1047,8 @@ class CodeToolsService {
|
||||
supportsReasoningEffort,
|
||||
budgetTokens,
|
||||
providerType,
|
||||
providerName
|
||||
providerName,
|
||||
endpointType
|
||||
)
|
||||
this.scheduleOpenCodeConfigCleanup(configPath)
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ const CodeToolsPage: FC = () => {
|
||||
}
|
||||
// Check if model belongs to openai, openai-response, or anthropic type provider
|
||||
const provider = providers.find((p) => p.id === m.provider)
|
||||
return !!['openai', 'openai-response', 'anthropic'].includes(provider?.type ?? '')
|
||||
return !!['openai', 'openai-response', 'anthropic', 'new-api'].includes(provider?.type ?? '')
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@@ -58,7 +58,7 @@ export const CLI_TOOL_PROVIDER_MAP: Record<string, (providers: Provider[]) => Pr
|
||||
[codeTools.githubCopilotCli]: () => [],
|
||||
[codeTools.kimiCli]: (providers) => providers.filter((p) => p.type.includes('openai')),
|
||||
[codeTools.openCode]: (providers) =>
|
||||
providers.filter((p) => ['openai', 'openai-response', 'anthropic'].includes(p.type))
|
||||
providers.filter((p) => ['openai', 'openai-response', 'anthropic', 'new-api'].includes(p.type))
|
||||
}
|
||||
|
||||
export const getCodeToolsApiBaseUrl = (model: Model, type: EndpointType) => {
|
||||
@@ -206,8 +206,18 @@ export const generateToolEnvironment = ({
|
||||
case codeTools.openCode:
|
||||
// Set environment variable with provider-specific suffix for security
|
||||
{
|
||||
env.OPENCODE_BASE_URL = formattedBaseUrl
|
||||
// Determine base URL format based on model's endpoint type and provider type
|
||||
// anthropic: use formatApiHost(url, false) to preserve existing /v1 from provider config
|
||||
// @ai-sdk/anthropic appends /messages to the baseURL (not /v1/messages)
|
||||
// others: append /v1 (standard OpenAI-compatible endpoint)
|
||||
const endpointType = model.endpoint_type
|
||||
const isAnthropicEndpoint =
|
||||
endpointType === 'anthropic' || (!endpointType && modelProvider.type === 'anthropic')
|
||||
const openCodeBaseUrl = isAnthropicEndpoint ? formatApiHost(baseUrl, false) : formattedBaseUrl
|
||||
|
||||
env.OPENCODE_BASE_URL = openCodeBaseUrl
|
||||
env.OPENCODE_MODEL_NAME = model.name
|
||||
env.OPENCODE_MODEL_ENDPOINT_TYPE = endpointType || ''
|
||||
// Calculate OpenCode-specific config internally
|
||||
const isReasoning = isReasoningModel(model)
|
||||
const supportsReasoningEffort = isSupportedReasoningEffortModel(model)
|
||||
|
||||
Reference in New Issue
Block a user