Uh oh!
There was an error while loading. Please reload this page.
feat(opencode-go): native model params, anthropic-format routing, and context-token fix - #652
Conversation
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughAdds a curated ChangesOpencode Go Anthropic-format routing and dual-client support
Sequence Diagram(s)sequenceDiagram
participant Caller
participant createMessage
participant resolveModel
participant isOpencodeGoAnthropicFormatModel
participant streamAnthropicMessage
participant AnthropicClient as anthropicClient<br/>(/v1/messages)
participant OpenAIClient as openaiClient<br/>(/v1/chat/completions)
Caller->>createMessage: createMessage(params)
createMessage->>resolveModel: fetchModel() + getModelParams()
resolveModel->>isOpencodeGoAnthropicFormatModel: check modelId
isOpencodeGoAnthropicFormatModel-->>resolveModel: true / false
resolveModel-->>createMessage: format, maxTokens, temperature, reasoningEffort
alt format === "anthropic"
createMessage->>streamAnthropicMessage: model, messages, tools
streamAnthropicMessage->>streamAnthropicMessage: addAnthropicCacheControl if supportsPromptCache
streamAnthropicMessage->>AnthropicClient: messages.stream(params)
AnthropicClient-->>streamAnthropicMessage: message_start, content_block_delta, message_delta
streamAnthropicMessage->>streamAnthropicMessage: accumulate usage + cache tokens
streamAnthropicMessage-->>createMessage: text, tool, usage, cost chunks
else format === "openai"
createMessage->>createMessage: convertToR1Format if preserveReasoning
createMessage->>OpenAIClient: chat.completions.create(stream=true)
OpenAIClient-->>createMessage: OpenAI stream chunks
end
createMessage-->>Caller: typed provider chunks
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/api/providers/__tests__/opencode-go.spec.ts (1)
462-477: ⚡ Quick winAdd regression assertions for Anthropic request-construction parity.
Consider asserting Anthropic
max_tokensoverride behavior (includeMaxTokens/modelMaxTokens) and thetool_choice: "none"disable-tools path to lock in the intended contract.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/api/providers/__tests__/opencode-go.spec.ts` around lines 462 - 477, Add additional regression assertions to the test for the OpencodeGoHandler completePrompt method to verify both the max_tokens override behavior and tool disabling functionality are working correctly. Expand the expect.objectContaining() assertion on mockAnthropicCreate to include checks for max_tokens with the correct values based on includeMaxTokens and modelMaxTokens properties, and add an assertion to verify that tool_choice is set to "none" in the Anthropic request to ensure the disable-tools path is functioning as intended.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/api/providers/opencode-go.ts`:
- Around line 321-327: In the message_delta case handler, the output tokens from
chunk.usage.output_tokens are being yielded but not accumulated into a running
total. You need to maintain a variable that accumulates the outputTokens across
all message_delta events, and then use this accumulated value when calculating
the final totalCost. Ensure this accumulation logic is applied consistently both
at the first location around line 321-327 (in the message_delta case) and the
second location mentioned at lines 392-400, so that the final cost calculation
includes all streamed output tokens rather than just the last batch.
- Around line 463-464: The non-streaming branch in the Anthropic provider's
completePrompt method does not honor includeMaxTokens and modelMaxTokens
overrides when setting the max_tokens value, causing it to behave inconsistently
with the streaming request construction. Update the max_tokens assignment
(currently using maxTokens ?? 16_384) to apply the same override logic as the
streaming implementation, ensuring that includeMaxTokens and modelMaxTokens
parameters take precedence over the default fallback value.
- Around line 274-286: The request payload unconditionally includes the tools
and tool_choice fields even when no tools are provided or tool use is disabled.
To fix this, conditionally include the tools and tool_choice properties in the
request object only when there are actual tools to send. Check if
metadata?.tools exists and has content before adding the
convertOpenAIToolsToAnthropic result and the corresponding tool_choice field to
the payload. This ensures that when tools are disabled or empty, these fields
are omitted from the request entirely rather than being sent with empty/none
values.
---
Nitpick comments:
In `@src/api/providers/__tests__/opencode-go.spec.ts`:
- Around line 462-477: Add additional regression assertions to the test for the
OpencodeGoHandler completePrompt method to verify both the max_tokens override
behavior and tool disabling functionality are working correctly. Expand the
expect.objectContaining() assertion on mockAnthropicCreate to include checks for
max_tokens with the correct values based on includeMaxTokens and modelMaxTokens
properties, and add an assertion to verify that tool_choice is set to "none" in
the Anthropic request to ensure the disable-tools path is functioning as
intended.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 726fe535-ceea-42f5-b13b-9bfa3e878088
📒 Files selected for processing (7)
packages/types/src/__tests__/provider-settings.test.tspackages/types/src/provider-settings.tspackages/types/src/providers/opencode-go.tssrc/api/providers/__tests__/opencode-go.spec.tssrc/api/providers/fetchers/__tests__/opencode-go.spec.tssrc/api/providers/fetchers/opencode-go.tssrc/api/providers/opencode-go.ts
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| : getModelParams({ | ||
| format: "openai", | ||
| modelId: id, | ||
| model: info, | ||
| settings: this.options, | ||
| defaultTemperature: OPENCODE_GO_DEFAULT_TEMPERATURE, | ||
| }) |
There was a problem hiding this comment.
For non-GPT-5 models with an explicit maxTokens, getModelMaxOutputTokens clamps to Math.min(maxTokens, ceil(contextWindow × 0.2)). For GLM-5.1 that resolves to Math.min(131072, 40960) = 40,960 — a significant reduction from the old code which used info.maxTokens directly. Is the 20% clamp the intended default when no user slider override is set, or should models that advertise supportsMaxTokens: true skip the clamp?
| */ | ||
| private async *streamAnthropicMessage( | ||
| modelId: string, | ||
| info: { supportsPromptCache?: boolean }, |
There was a problem hiding this comment.
Should this be ModelInfo rather than { supportsPromptCache?: boolean }? The calculateApiCostAnthropic call below force-casts info — a caller passing only { supportsPromptCache: false } would silently get totalCost: 0 because inputPrice/outputPrice would be undefined.
| tool_choice: convertOpenAIToolChoiceToAnthropic(metadata?.tool_choice, metadata?.parallelToolCalls), | ||
| } | ||
| const stream = await this.anthropicClient.messages.create(requestParams) |
There was a problem hiding this comment.
Pre-stream errors (401, 429, network) propagate unwrapped here, but the completePrompt Anthropic branch wraps them with "Opencode Go completion error: ...". Should this also have a try/catch for consistency?
| supportsPromptCache: false, | ||
| preserveReasoning: true, | ||
| inputPrice: 0.14, | ||
| outputPrice: 0.28, | ||
| cacheReadsPrice: 0.0028, |
There was a problem hiding this comment.
supportsPromptCache: false means no cache-control breakpoints are ever injected for MiMo, so cacheReadsPrice here can never be applied — the extension won't report cache reads for a model that doesn't advertise prompt cache support. Does the Go gateway actually bill and report cached_tokens for MiMo on the OA-compat path without client-side breakpoints?
| preserveReasoning: true, | ||
| inputPrice: 0.3, | ||
| outputPrice: 1.2, | ||
| cacheReadsPrice: 0.06, |
There was a problem hiding this comment.
M2.5 and M2.7 both declare cacheWritesPrice: 0.375, and M3 has supportsPromptCache: true with cache injection active. Is M3 exempt from write charges, or is this a gap?
| "deepseek-v4-pro": { | ||
| maxTokens: 384_000, | ||
| contextWindow: 1_000_000, |
There was a problem hiding this comment.
Without supportsMaxTokens: true, getModelMaxOutputTokens clamps the effective default to Math.min(384000, ceil(1000000 × 0.2)) = 200,000 and the UI slider is hidden. Should DeepSeek expose the slider like GLM does?
| export const OPENCODE_GO_ANTHROPIC_FORMAT_MODELS = new Set<string>([ | ||
| // --- Alibaba Qwen --- | ||
| "qwen3.7-max", | ||
| "qwen3.7-plus", | ||
| "qwen3.6-plus", | ||
| // --- MiniMax --- | ||
| "minimax-m3", | ||
| "minimax-m2.7", | ||
| "minimax-m2.5", | ||
| ]) |
There was a problem hiding this comment.
Is there a test asserting that every ID in this set also exists as a key in opencodeGoModels? A model added to the registry but omitted here will silently hit the OA-compat endpoint and get a 401 at runtime.
| // ... message_delta output tokens ... | ||
| expect(chunks).toContainEqual({ type: "usage", inputTokens: 0, outputTokens: 5 }) | ||
| // ... and a final cost chunk. | ||
| expect(chunks.some((c) => c.type === "usage" && c.totalCost !== undefined)).toBe(true) |
There was a problem hiding this comment.
This passes even when totalCost is computed from outputTokens: 0 (the accumulation bug CodeRabbit flagged). Could this assert c.totalCost > 0 so CI would catch that regression?
- Type streamAnthropicMessage's info param as ModelInfo and drop the force-cast so calculateApiCostAnthropic can no longer silently return /bin/sh when pricing fields are absent. - Wrap pre-stream Anthropic-format errors (401/429/network) with the 'Opencode Go completion error:' prefix for consistency with completePrompt. - Clarify the registry doc: supportsPromptCache controls client-side cache_control injection (Anthropic path) only; OA-compat models price server-side cached_tokens via cacheReadsPrice regardless of the flag (MiMo stays false, matching the dedicated mimo provider). - Add cacheWritesPrice (0.375) to minimax-m3 so its cache writes are billed, matching M2.5/M2.7. - Add supportsMaxTokens to DeepSeek V4 models so the max-output slider is exposed like GLM. - Strengthen the streaming cost test to assert totalCost > 0, and add registry invariants for MiniMax cache-write pricing and DeepSeek supportsMaxTokens plus a streaming error-wrapping test.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Related GitHub Issue
Closes: #646
Description
The Opencode Go provider previously treated every model as a generic OpenAI-compatible chat-completions endpoint. The Go gateway, however, routes a subset of its curated models (Qwen, MiniMax) exclusively through the Anthropic Messages wire format (
/v1/messages), and the/v1/modelsendpoint only reliably returnsidplus optionalcontext_window/max_tokens— it advertises none of the capability flags or pricing the extension needs. This PR closes those gaps so Go-plan models behave correctly in the model picker, reasoning controls, max-output slider, and cost reporting.Key implementation details:
packages/types/src/providers/opencode-go.ts): AddedopencodeGoModels— a curatedRecord<string, ModelInfo>encoding per-model context windows, max tokens, capability flags (supportsReasoningEffort,preserveReasoning,supportsMaxTokens,supportsPromptCache), tool include/exclude lists, and pricing sourced from vendor specs and the Go pricing table. AddedgetOpencodeGoModelInfo()for lookup with fallback toopencodeGoDefaultModelInfo.packages/types/src/providers/opencode-go.ts): AddedOPENCODE_GO_ANTHROPIC_FORMAT_MODELSandisOpencodeGoAnthropicFormatModel()to identify models that must use/v1/messages. Sending these to the OpenAI endpoint is rejected with401 Model <id> is not supported for format oa-compat.packages/types/src/provider-settings.ts):getApiProtocol()now returns"anthropic"for Anthropic-format Go models so token/cost aggregation re-adds cache tokens intocontextTokens(Anthropic usage reportsinput_tokensexcluding cache, with separatecache_creation_input_tokens/cache_read_input_tokensfields — without this the cached prefix is dropped and context-window usage is undercounted).src/api/providers/opencode-go.ts): Added a dedicated Anthropic SDK client andresolveModel()that branchesgetModelParamson a literalformat("anthropic" vs "openai"). Implemented an Anthropic streaming path that mapsmessage_start/content_block_*events to the internal stream chunk shape, plusaddAnthropicCacheControl()for server-side prompt caching.completePromptand the OpenAI path now honourincludeMaxTokens,modelMaxTokens, andreasoning_effort.src/api/providers/fetchers/opencode-go.ts):parseOpencodeGoModel()now starts from the native registry, then overridescontextWindow/maxTokens/supportsImageswith live/modelsvalues when present (gateway stays source of truth for volatile fields), falling back to defaults for unknown models.opencodeGoDefaultModelIdfromglm-5.1toglm-5.2.Reviewers should pay attention to: the format-routing set must stay in sync with the Go model table, and the Anthropic usage/cost aggregation path (the
contextTokensfix is the core correctness change).Test Procedure
packages/types/src/__tests__/provider-settings.test.ts— assertsgetApiProtocolreturns"anthropic"for the Qwen/MiniMax Go models and"openai"for the rest.src/api/providers/__tests__/opencode-go.spec.ts— coversresolveModelformat branching, the Anthropic streaming event mapping, cache-control injection,completePromptdual-path, andreasoning_effort/max_completion_tokenshandling.src/api/providers/fetchers/__tests__/opencode-go.spec.ts— covers registry-merge precedence (live values override registry; registry supplies capabilities/pricing; unknown models fall back to defaults).cd packages/types && npx vitest run src/__tests__/provider-settings.test.tscd src && npx vitest run api/providers/__tests__/opencode-go.spec.ts api/providers/fetchers/__tests__/opencode-go.spec.ts/v1/messages(no401 ... not supported for format oa-compat); confirmcontextTokensreflects the cached prefix and cost is reported.Pre-Submission Checklist
Screenshots / Videos
N/A — no UI changes; behaviour is in provider routing, model metadata, and cost/context calculation.
Documentation Updates
Get in Touch
Summary by CodeRabbit
New Features
/v1/messages(including cache-control behavior and improved token/cost handling).Bug Fixes
Tests