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:
George·Dong
2026-04-27 00:22:05 +08:00
committed by GitHub
parent 9a213eaa4d
commit 8b9f8f7b03
3 changed files with 23 additions and 10 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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)