From b0f90c00edf07c3d0dfa1f86cfa6b04d7ba5fd40 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Fri, 3 Jul 2026 11:50:48 +0200 Subject: [PATCH] Add Fable and Sonnet 5 as adaptive reasoning models --- .../src/plugin/github-copilot/models.ts | 7 +- packages/opencode/src/provider/transform.ts | 17 ++- .../test/kilocode/transform-opus-4.7.test.ts | 100 ++++++++++++++++++ 3 files changed, 117 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/plugin/github-copilot/models.ts b/packages/opencode/src/plugin/github-copilot/models.ts index 5a24c6f4db..bf89f1c0dc 100644 --- a/packages/opencode/src/plugin/github-copilot/models.ts +++ b/packages/opencode/src/plugin/github-copilot/models.ts @@ -126,8 +126,11 @@ function build(key: string, remote: Item, url: string, prev?: Model): Model { variants[effort] = { thinking: { type: "adaptive", - // kilocode_change start - treat opus-4.8 like opus-4.7 - ...(model.api.id.includes("opus-4.7") || model.api.id.includes("opus-4.8") + // kilocode_change start - treat opus-4.8, fable, and sonnet-5 like opus-4.7 + ...(model.api.id.includes("opus-4.7") || + model.api.id.includes("opus-4.8") || + model.api.id.toLowerCase().includes("fable") || + /sonnet[.-]5/.test(model.api.id.toLowerCase()) ? { display: "summarized" } : {}), // kilocode_change end diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index f661d9d633..18b94c5adf 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -614,9 +614,16 @@ function anthropicOpus47OrLater(apiId: string) { return major > 4 || (major === 4 && minor >= 7) } +// kilocode_change start - fable and sonnet-5 models are adaptive thinking models like opus-4.7/4.8 +function anthropicClaude5(apiId: string) { + const id = apiId.toLowerCase() + return id.includes("fable") || /sonnet[.-]5/.test(id) +} +// kilocode_change end + function anthropicAdaptiveEfforts(apiId: string): string[] | null { - // kilocode_change start - treat opus-4.8 like opus-4.7 - if (anthropicOpus47OrLater(apiId)) { + // kilocode_change start - treat opus-4.8 and fable like opus-4.7 + if (anthropicOpus47OrLater(apiId) || anthropicClaude5(apiId)) { return ["low", "medium", "high", "xhigh", "max"] } // kilocode_change end @@ -667,7 +674,7 @@ export function variants(model: Provider.Model): Record { }) }) + test("fable returns adaptive thinking variants including xhigh (native anthropic)", () => { + const model = mockModel({ + api: { + id: "claude-fable-5", + url: "https://api.anthropic.com", + npm: "@ai-sdk/anthropic", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"]) + expect(result.xhigh).toEqual({ + thinking: { type: "adaptive", display: "summarized" }, + effort: "xhigh", + }) + }) + + test("fable returns adaptive thinking variants via @ai-sdk/gateway", () => { + const model = mockModel({ + id: "anthropic/claude-fable-5", + api: { + id: "anthropic/claude-fable-5", + url: "https://gateway.ai", + npm: "@ai-sdk/gateway", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"]) + }) + + test("fable on bedrock returns adaptive reasoningConfig with xhigh", () => { + const model = mockModel({ + api: { + id: "anthropic.claude-fable-5", + url: "https://bedrock.amazonaws.com", + npm: "@ai-sdk/amazon-bedrock", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"]) + expect(result.xhigh).toEqual({ + reasoningConfig: { type: "adaptive", maxReasoningEffort: "xhigh", display: "summarized" }, + }) + }) + + test("sonnet-5 returns adaptive thinking variants including xhigh (native anthropic)", () => { + const model = mockModel({ + api: { + id: "claude-sonnet-5", + url: "https://api.anthropic.com", + npm: "@ai-sdk/anthropic", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"]) + expect(result.xhigh).toEqual({ + thinking: { type: "adaptive", display: "summarized" }, + effort: "xhigh", + }) + }) + + test("sonnet-5 returns adaptive thinking variants via @ai-sdk/gateway", () => { + const model = mockModel({ + id: "anthropic/claude-sonnet-5", + api: { + id: "anthropic/claude-sonnet-5", + url: "https://gateway.ai", + npm: "@ai-sdk/gateway", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"]) + }) + + test("sonnet-5 on bedrock returns adaptive reasoningConfig with xhigh", () => { + const model = mockModel({ + api: { + id: "anthropic.claude-sonnet-5", + url: "https://bedrock.amazonaws.com", + npm: "@ai-sdk/amazon-bedrock", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"]) + expect(result.xhigh).toEqual({ + reasoningConfig: { type: "adaptive", maxReasoningEffort: "xhigh", display: "summarized" }, + }) + }) + + test("sonnet-4.6 keeps original adaptive efforts without xhigh", () => { + const model = mockModel({ + api: { + id: "claude-sonnet-4.6", + url: "https://api.anthropic.com", + npm: "@ai-sdk/anthropic", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"]) + }) + test("opus-4-6 keeps original adaptive efforts without xhigh", () => { const model = mockModel({ api: {