From b05bdd13255efbbe5eb5a33d7d6548d1f2b15b04 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Mon, 13 Jul 2026 16:40:56 -0700 Subject: [PATCH] Default Claude context window to 1M The 200k default dates to the era when 1M context was entitlement-gated and carried a long-context pricing premium. Anthropic has since made 1M the default for every model that supports it, with no beta header, tier gate, or premium. Most users run Opus 4.8 or Fable 5, which are already 1M-default upstream, so the conservative default only added a stale mismatch for the toggle-capable models. - Flip the contextWindow default to 1m for Fable 5, Opus 4.6, Sonnet 5, and Sonnet 4.6; 200k remains selectable. - Add resolveClaudeContextWindow so option-less selections resolve to the descriptor default instead of implicitly meaning 200k; both resolveClaudeApiModelId ([1m] suffix) and the adapter's token accounting now share it. Co-Authored-By: Claude Fable 5 --- .../src/provider/Layers/ClaudeAdapter.test.ts | 11 ++++--- .../src/provider/Layers/ClaudeAdapter.ts | 23 ++++++------- .../src/provider/Layers/ClaudeProvider.ts | 32 +++++++++++++------ 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index 191bf8e27db..c3c4dfb2dbe 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -3042,7 +3042,7 @@ describe("ClaudeAdapterLive", () => { attachments: [], }); - assert.deepEqual(harness.query.setModelCalls, ["claude-opus-4-6"]); + assert.deepEqual(harness.query.setModelCalls, ["claude-opus-4-6[1m]"]); }).pipe( Effect.provideService(Random.Random, makeDeterministicRandomService()), Effect.provide(harness.layer), @@ -3140,10 +3140,11 @@ describe("ClaudeAdapterLive", () => { yield* adapter.sendTurn({ threadId: session.threadId, input: "hello again", - modelSelection: { - instanceId: ProviderInstanceId.make("claudeAgent"), - model: "claude-opus-4-6", - }, + modelSelection: createModelSelection( + ProviderInstanceId.make("claudeAgent"), + "claude-opus-4-6", + [{ id: "contextWindow", value: "200k" }], + ), attachments: [], }); diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index 97a93f85829..a8e6cfda230 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -76,6 +76,7 @@ import { isClaudeUltracodeEffort, normalizeClaudeCliEffort, resolveClaudeApiModelId, + resolveClaudeContextWindow, resolveClaudeEffort, } from "./ClaudeProvider.ts"; import { @@ -340,24 +341,18 @@ function selectedClaudeContextWindow( switch (modelSelection?.model) { case "claude-opus-4-8": case "claude-opus-4-7": + // Always 1M at the API; these models expose no contextWindow option. return 1_000_000; } - const optionValue = getModelSelectionStringOptionValue(modelSelection, "contextWindow"); - if (optionValue === "1m") { - return 1_000_000; - } - if (optionValue === "200k") { - return 200_000; - } - const caps = getClaudeModelCapabilities(modelSelection?.model); - const hasContextWindowOption = getProviderOptionDescriptors({ caps }).some( - (descriptor) => descriptor.type === "select" && descriptor.id === "contextWindow", - ); - if (hasContextWindowOption) { - return 200_000; + switch (resolveClaudeContextWindow(modelSelection)) { + case "1m": + return 1_000_000; + case "200k": + return 200_000; + default: + return undefined; } - return undefined; } function finiteNonNegativeInteger(value: unknown): number | undefined { diff --git a/apps/server/src/provider/Layers/ClaudeProvider.ts b/apps/server/src/provider/Layers/ClaudeProvider.ts index 59b7d0464de..5471c710543 100644 --- a/apps/server/src/provider/Layers/ClaudeProvider.ts +++ b/apps/server/src/provider/Layers/ClaudeProvider.ts @@ -77,8 +77,8 @@ const BUILT_IN_MODELS: ReadonlyArray = [ id: "contextWindow", label: "Context Window", options: [ - { value: "200k", label: "200k", isDefault: true }, - { value: "1m", label: "1M" }, + { value: "200k", label: "200k" }, + { value: "1m", label: "1M", isDefault: true }, ], }), ], @@ -163,8 +163,8 @@ const BUILT_IN_MODELS: ReadonlyArray = [ id: "contextWindow", label: "Context Window", options: [ - { value: "200k", label: "200k", isDefault: true }, - { value: "1m", label: "1M" }, + { value: "200k", label: "200k" }, + { value: "1m", label: "1M", isDefault: true }, ], }), ], @@ -216,8 +216,8 @@ const BUILT_IN_MODELS: ReadonlyArray = [ id: "contextWindow", label: "Context Window", options: [ - { value: "200k", label: "200k", isDefault: true }, - { value: "1m", label: "1M" }, + { value: "200k", label: "200k" }, + { value: "1m", label: "1M", isDefault: true }, ], }), ], @@ -245,8 +245,8 @@ const BUILT_IN_MODELS: ReadonlyArray = [ id: "contextWindow", label: "Context Window", options: [ - { value: "200k", label: "200k", isDefault: true }, - { value: "1m", label: "1M" }, + { value: "200k", label: "200k" }, + { value: "1m", label: "1M", isDefault: true }, ], }), ], @@ -370,8 +370,22 @@ export function isClaudeUltracodeEffort(effort: string | null | undefined): bool return effort === "ultracode"; } +export function resolveClaudeContextWindow( + modelSelection: ModelSelection | undefined, +): string | undefined { + const caps = getClaudeModelCapabilities(modelSelection?.model); + const raw = getModelSelectionStringOptionValue(modelSelection, "contextWindow"); + const descriptors = getProviderOptionDescriptors({ + caps, + ...(raw ? { selections: [{ id: "contextWindow", value: raw }] } : {}), + }); + const descriptor = descriptors.find((candidate) => candidate.id === "contextWindow"); + const value = getProviderOptionCurrentValue(descriptor); + return typeof value === "string" ? value : undefined; +} + export function resolveClaudeApiModelId(modelSelection: ModelSelection): string { - switch (getModelSelectionStringOptionValue(modelSelection, "contextWindow")) { + switch (resolveClaudeContextWindow(modelSelection)) { case "1m": return `${modelSelection.model}[1m]`; default: