Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 11 additions & 20 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
"electron"
],
"overrides": {
"@ai-sdk/anthropic": "3.0.111",
"@opentui/core": "catalog:",
"@opentui/keymap": "catalog:",
"@opentui/solid": "catalog:",
Expand All @@ -160,6 +161,8 @@
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch",
"@ai-sdk/openai-compatible@2.0.41": "patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch",
"@ai-sdk/groq@3.0.31": "patches/@ai-sdk%2Fgroq@3.0.31.patch"
"@ai-sdk/groq@3.0.31": "patches/@ai-sdk%2Fgroq@3.0.31.patch",
"@ai-sdk/anthropic@3.0.111": "patches/@ai-sdk%2Fanthropic@3.0.111.patch",
"@ai-sdk/amazon-bedrock@4.0.166": "patches/@ai-sdk%2Famazon-bedrock@4.0.166.patch"
}
}
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"dependencies": {
"@ai-sdk/alibaba": "1.0.17",
"@ai-sdk/amazon-bedrock": "4.0.166",
"@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/anthropic": "3.0.111",
"@ai-sdk/azure": "3.0.88",
"@ai-sdk/cerebras": "2.0.41",
"@ai-sdk/cohere": "3.0.27",
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"@agentclientprotocol/sdk": "0.21.0",
"@ai-sdk/alibaba": "1.0.17",
"@ai-sdk/amazon-bedrock": "4.0.166",
"@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/anthropic": "3.0.111",
"@ai-sdk/azure": "3.0.88",
"@ai-sdk/cerebras": "2.0.60",
"@ai-sdk/cohere": "3.0.27",
Expand Down
37 changes: 36 additions & 1 deletion packages/opencode/src/provider/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,41 @@ function anthropicOmitsThinking(apiId: string) {
return anthropicUsesModernAdaptiveThinking(apiId)
}

// Opus 5, Sonnet 5, Fable 5.x, and Mythos 5.x think without a `thinking` parameter.
function anthropicThinksByDefault(apiId: string) {
const version = /claude-(?:[a-z]+-)?(\d+)(?:[.-](\d{1,2}))?(?:[.@-]|$)/i.exec(apiId)
if (!version) return false
return Number(version[1]) >= 5
}

// Fable 5.1 binds each thinking signature to the system prompt, tool list, and
// messages above it, and rejects the request when any of that changes. opencode
// re-renders parts of that prefix between turns (system prompt, tools, compaction),
// so ask the API to drop the affected blocks instead of failing the request.
// Models that do not run the check accept the field, so it is safe on every Claude.
// The patched AI SDK adds the thinking-binding-controls beta whenever it is set.
const ANTHROPIC_BLOCK_BINDING = { prefixMismatchBehavior: "drop_block" }

function anthropicBlockBinding(model: Provider.Model, options: { [x: string]: any }) {
if (!model.api.id.toLowerCase().includes("claude")) return options
const byDefault = anthropicThinksByDefault(model.api.id)
switch (model.api.npm) {
case "@ai-sdk/anthropic":
case "@ai-sdk/google-vertex/anthropic": {
const thinking = options.thinking ?? (byDefault ? { type: "adaptive" } : undefined)
if (!thinking || (thinking.type !== "adaptive" && thinking.type !== "enabled")) return options
return { ...options, thinking: { ...thinking, blockBinding: ANTHROPIC_BLOCK_BINDING } }
}
case "@ai-sdk/amazon-bedrock": {
const reasoningConfig = options.reasoningConfig ?? (byDefault ? { type: "adaptive" } : undefined)
if (!reasoningConfig || (reasoningConfig.type !== "adaptive" && reasoningConfig.type !== "enabled"))
return options
return { ...options, reasoningConfig: { ...reasoningConfig, blockBinding: ANTHROPIC_BLOCK_BINDING } }
}
}
return options
}

function googleThinkingLevelEfforts(apiId: string) {
const id = apiId.toLowerCase()
if (!id.includes("gemini-3")) return ["low", "high"]
Expand Down Expand Up @@ -1363,7 +1398,7 @@ export function providerOptions(model: Provider.Model, options: { [x: string]: a
usesOpenAIReasoningGate &&
(model.capabilities.reasoning || options.reasoningEffort !== undefined || options.reasoningSummary !== undefined)
? { ...options, forceReasoning: true }
: options
: anthropicBlockBinding(model, options)

if (model.api.npm === "@ai-sdk/gateway") {
// Gateway providerOptions are split across two namespaces:
Expand Down
14 changes: 14 additions & 0 deletions packages/opencode/src/session/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,20 @@ const layer = Layer.effect(
case "step-finish": {
const completedSnapshot = yield* snapshot.track()
yield* Effect.forEach(Object.keys(ctx.reasoningMap), finishReasoning)
// Anthropic reports thinking blocks it removed before the model saw the
// prompt. Prefix mismatches mean opencode changed history behind a signed
// block; log them so the churn can be tracked down.
const dropped = isRecord(value.providerMetadata?.anthropic)
? value.providerMetadata.anthropic.inputTransformations
: undefined
if (Array.isArray(dropped) && dropped.length > 0) {
yield* Effect.logWarning("thinking blocks dropped by provider", {
sessionID: ctx.sessionID,
messageID: ctx.assistantMessage.id,
model: ctx.model.id,
transformations: JSON.stringify(dropped),
})
}
const usage = Session.getUsage({
model: ctx.model,
usage: value.usage ?? new Usage({}),
Expand Down
Loading
Loading