From ed4128c3c653a733c42c2206d22ae0f5ed13c89d Mon Sep 17 00:00:00 2001 From: Karl Kurzer Date: Thu, 12 Feb 2026 15:06:06 +0100 Subject: [PATCH 1/2] fix(provider): split Bedrock 1M context models into explicit 200K and 1M variants Bedrock Claude Opus 4.6 and Sonnet 4.5 support 1M token context, but only when the anthropic_beta flag context-1m-2025-08-07 is sent in the request body. Without it, Bedrock enforces a 200K hard limit. Split these models at models.dev ingestion time into a safe 200K default and an opt-in 1M Experimental variant that sends the required beta flag via the AI SDK anthropicBeta provider option. Custom user-configured models are not affected. Closes #13199 --- packages/opencode/src/provider/provider.ts | 38 +++- .../opencode/test/provider/provider.test.ts | 169 ++++++++++++++++++ 2 files changed, 206 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 44bcf8adb3d..6e62afc7772 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -623,6 +623,39 @@ export namespace Provider { }) export type Info = z.infer + const BEDROCK_1M_MODELS = ["claude-opus-4-6", "claude-sonnet-4-5"] + const BEDROCK_1M_BETA = "context-1m-2025-08-07" + + function splitBedrock1m(providerID: string, models: Record) { + if (providerID !== "amazon-bedrock") return + + for (const [id, model] of Object.entries(models)) { + if (!BEDROCK_1M_MODELS.some((m) => model.api.id.includes(m))) continue + if (id.endsWith("-1m")) continue + + const name = model.name.replace(/\s+\((200K|1M Experimental)\)$/i, "") + const opts = { ...model.options } + const raw = opts["anthropicBeta"] + const existing = (Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : []).filter( + (item): item is string => typeof item === "string", + ) + delete opts["anthropicBeta"] + + model.name = `${name} (200K)` + model.options = opts + model.limit = { ...model.limit, context: Math.min(model.limit.context, 200_000) } + + models[`${id}-1m`] = { + ...model, + id: `${id}-1m`, + name: `${name} (1M Experimental)`, + status: "beta", + limit: { ...model.limit, context: 1_000_000 }, + options: { ...model.options, anthropicBeta: [...new Set([...existing, BEDROCK_1M_BETA])] }, + } + } + } + function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model { const m: Model = { id: model.id, @@ -691,13 +724,16 @@ export namespace Provider { } export function fromModelsDevProvider(provider: ModelsDev.Provider): Info { + const models = mapValues(provider.models, (model) => fromModelsDevModel(provider, model)) + splitBedrock1m(provider.id, models) + return { id: provider.id, source: "custom", name: provider.name, env: provider.env ?? [], options: {}, - models: mapValues(provider.models, (model) => fromModelsDevModel(provider, model)), + models, } } diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 98cd49c02fd..2c3b79b2a41 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1407,6 +1407,175 @@ test("model headers are preserved", async () => { }) }) +test("fromModelsDevProvider splits bedrock opus 4.6 into 200K and 1M variants", () => { + const provider = Provider.fromModelsDevProvider({ + id: "amazon-bedrock", + name: "Amazon Bedrock", + env: [], + models: { + "us.anthropic.claude-opus-4-6-v1:0": { + id: "us.anthropic.claude-opus-4-6-v1:0", + name: "Claude Opus 4.6", + attachment: true, + reasoning: true, + tool_call: true, + temperature: true, + release_date: "2026-02-05", + limit: { context: 1_000_000, output: 128_000 }, + options: { + anthropicBeta: ["existing-beta"], + }, + }, + }, + }) + + const base = provider.models["us.anthropic.claude-opus-4-6-v1:0"] + const extended = provider.models["us.anthropic.claude-opus-4-6-v1:0-1m"] + + expect(base).toBeDefined() + expect(extended).toBeDefined() + + expect(base.name).toContain("(200K)") + expect(extended.name).toContain("(1M Experimental)") + + expect(base.limit.context).toBe(200_000) + expect(extended.limit.context).toBe(1_000_000) + + expect(base.options.anthropicBeta).toBeUndefined() + expect(extended.options.anthropicBeta).toEqual(expect.arrayContaining(["existing-beta", "context-1m-2025-08-07"])) + + expect(extended.api.id).toBe(base.api.id) + expect(extended.status).toBe("beta") +}) + +test("fromModelsDevProvider splits bedrock sonnet 4.5 into 200K and 1M variants", () => { + const provider = Provider.fromModelsDevProvider({ + id: "amazon-bedrock", + name: "Amazon Bedrock", + env: [], + models: { + "us.anthropic.claude-sonnet-4-5-20250929-v1:0": { + id: "us.anthropic.claude-sonnet-4-5-20250929-v1:0", + name: "Claude Sonnet 4.5", + attachment: true, + reasoning: true, + tool_call: true, + temperature: true, + release_date: "2025-09-29", + limit: { context: 1_000_000, output: 64_000 }, + options: {}, + }, + }, + }) + + const base = provider.models["us.anthropic.claude-sonnet-4-5-20250929-v1:0"] + const extended = provider.models["us.anthropic.claude-sonnet-4-5-20250929-v1:0-1m"] + + expect(base).toBeDefined() + expect(extended).toBeDefined() + + expect(base.name).toContain("(200K)") + expect(extended.name).toContain("(1M Experimental)") + + expect(base.limit.context).toBe(200_000) + expect(extended.limit.context).toBe(1_000_000) + + expect(extended.options.anthropicBeta).toEqual(["context-1m-2025-08-07"]) + expect(extended.status).toBe("beta") +}) + +test("bedrock custom opus 4.6 model is not auto-split", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "amazon-bedrock": { + models: { + "anthropic.claude-opus-4-6-v1:0": { + name: "Claude Opus 4.6", + attachment: true, + reasoning: true, + tool_call: true, + temperature: true, + limit: { context: 1_000_000, output: 64_000 }, + }, + }, + }, + }, + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const providers = await Provider.list() + const base = providers["amazon-bedrock"].models["anthropic.claude-opus-4-6-v1:0"] + const extended = providers["amazon-bedrock"].models["anthropic.claude-opus-4-6-v1:0-1m"] + + expect(base).toBeDefined() + expect(extended).toBeUndefined() + expect(base.name).toBe("Claude Opus 4.6") + expect(base.limit.context).toBe(1_000_000) + expect(base.options.anthropicBeta).toBeUndefined() + }, + }) +}) + +test("fromModelsDevProvider does not split non-1m bedrock models", () => { + const provider = Provider.fromModelsDevProvider({ + id: "amazon-bedrock", + name: "Amazon Bedrock", + env: [], + models: { + "us.anthropic.claude-haiku-4-20250414-v1:0": { + id: "us.anthropic.claude-haiku-4-20250414-v1:0", + name: "Claude Haiku 4", + attachment: true, + reasoning: false, + tool_call: true, + temperature: true, + release_date: "2025-04-14", + limit: { context: 200_000, output: 64_000 }, + options: {}, + }, + }, + }) + + const models = Object.keys(provider.models) + expect(models).toEqual(["us.anthropic.claude-haiku-4-20250414-v1:0"]) + expect(provider.models["us.anthropic.claude-haiku-4-20250414-v1:0-1m"]).toBeUndefined() +}) + +test("fromModelsDevProvider does not split non-bedrock providers", () => { + const provider = Provider.fromModelsDevProvider({ + id: "anthropic", + name: "Anthropic", + env: ["ANTHROPIC_API_KEY"], + models: { + "claude-opus-4-6": { + id: "claude-opus-4-6", + name: "Claude Opus 4.6", + attachment: true, + reasoning: true, + tool_call: true, + temperature: true, + release_date: "2026-02-05", + limit: { context: 200_000, output: 128_000 }, + options: {}, + }, + }, + }) + + const models = Object.keys(provider.models) + expect(models).toEqual(["claude-opus-4-6"]) + expect(provider.models["claude-opus-4-6-1m"]).toBeUndefined() +}) + test("provider env fallback - second env var used if first missing", async () => { await using tmp = await tmpdir({ init: async (dir) => { From 4ce19a552ddba8e625ed0c02b9230440c89c8af8 Mon Sep 17 00:00:00 2001 From: Karl Kurzer Date: Wed, 18 Feb 2026 09:39:39 +0100 Subject: [PATCH 2/2] fix(provider): add Bedrock Sonnet 4.6 to 1M context split Sonnet 4.6 (anthropic.claude-sonnet-4-6) also supports 1M context on Bedrock with the same anthropic_beta flag. Add it to the allowlist so it gets split into 200K default and 1M Experimental variants. --- packages/opencode/src/provider/provider.ts | 2 +- .../opencode/test/provider/provider.test.ts | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 2eb65d5fd7c..ec61251b43d 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -666,7 +666,7 @@ export namespace Provider { }) export type Info = z.infer - const BEDROCK_1M_MODELS = ["claude-opus-4-6", "claude-sonnet-4-5"] + const BEDROCK_1M_MODELS = ["claude-opus-4-6", "claude-sonnet-4-5", "claude-sonnet-4-6"] const BEDROCK_1M_BETA = "context-1m-2025-08-07" function splitBedrock1m(providerID: string, models: Record) { diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index c0fac91cc90..148ae313e72 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1484,6 +1484,42 @@ test("fromModelsDevProvider splits bedrock sonnet 4.5 into 200K and 1M variants" expect(extended.status).toBe("beta") }) +test("fromModelsDevProvider splits bedrock sonnet 4.6 into 200K and 1M variants", () => { + const provider = Provider.fromModelsDevProvider({ + id: "amazon-bedrock", + name: "Amazon Bedrock", + env: [], + models: { + "us.anthropic.claude-sonnet-4-6-v1:0": { + id: "us.anthropic.claude-sonnet-4-6-v1:0", + name: "Claude Sonnet 4.6", + attachment: true, + reasoning: true, + tool_call: true, + temperature: true, + release_date: "2026-02-17", + limit: { context: 1_000_000, output: 64_000 }, + options: {}, + }, + }, + }) + + const base = provider.models["us.anthropic.claude-sonnet-4-6-v1:0"] + const extended = provider.models["us.anthropic.claude-sonnet-4-6-v1:0-1m"] + + expect(base).toBeDefined() + expect(extended).toBeDefined() + + expect(base.name).toContain("(200K)") + expect(extended.name).toContain("(1M Experimental)") + + expect(base.limit.context).toBe(200_000) + expect(extended.limit.context).toBe(1_000_000) + + expect(extended.options.anthropicBeta).toEqual(["context-1m-2025-08-07"]) + expect(extended.status).toBe("beta") +}) + test("bedrock custom opus 4.6 model is not auto-split", async () => { await using tmp = await tmpdir({ init: async (dir) => {