diff --git a/docs/config/models.mdx b/docs/config/models.mdx index 79f56e53876..7741775e98a 100644 --- a/docs/config/models.mdx +++ b/docs/config/models.mdx @@ -16,7 +16,7 @@ Xum ships with curated models kept up to date with the frontier. Use any custom | Fable 5.1 | anthropic:claude-fable-5-1 | `fable` | | | Mythos 5 | anthropic:claude-mythos-5 | `mythos` | | | Opus 5 | anthropic:claude-opus-5 | `opus` | ✓ | -| Sonnet 5 | anthropic:claude-sonnet-5 | `sonnet` | | +| Sonnet 5.1 | anthropic:claude-sonnet-5-1 | `sonnet` | | | Haiku 4.5 | anthropic:claude-haiku-4-5 | `haiku` | | | GPT-5.6 Sol | openai:gpt-5.6-sol | `gpt`, `sol` | | | GPT-5.6 Terra | openai:gpt-5.6-terra | `terra` | | diff --git a/src/browser/utils/slashCommands/parser.test.ts b/src/browser/utils/slashCommands/parser.test.ts index d74c9fba1d5..a69c5dc611b 100644 --- a/src/browser/utils/slashCommands/parser.test.ts +++ b/src/browser/utils/slashCommands/parser.test.ts @@ -80,7 +80,7 @@ describe("commandParser", () => { }); it("should parse /model with full provider:model format", () => { - expectModelSet("/model anthropic:claude-sonnet-5", KNOWN_MODELS.SONNET.id); + expectModelSet("/model anthropic:claude-sonnet-5-1", KNOWN_MODELS.SONNET.id); }); it("should parse /compact -m with alias", () => { diff --git a/src/common/constants/knownModels.ts b/src/common/constants/knownModels.ts index 5a4a3f90c9d..a5e34815b20 100644 --- a/src/common/constants/knownModels.ts +++ b/src/common/constants/knownModels.ts @@ -69,18 +69,19 @@ const MODEL_DEFINITIONS = { // usage can run ~1.0-1.3x higher than this estimate (same situation as FABLE above). tokenizerOverride: "anthropic/claude-opus-4.5", }, - // Claude Sonnet 5 - released June 30, 2026. The most agentic Sonnet yet (native 1M context, - // 128K max output, adaptive thinking + effort including native xhigh). Standard pricing matches - // Sonnet 4.6 ($3/M in, $15/M out); introductory $2/$10 applies through Aug 31, 2026. API id - // `claude-sonnet-5`. The bare `sonnet` alias tracks the latest Sonnet tier. + // Claude Sonnet 5.1 - successor to Sonnet 5 (released June 30, 2026) with the same + // envelope (native 1M context, 128K max output, adaptive thinking + effort including + // native xhigh) at unchanged standard pricing ($3/M in, $15/M out). API id + // `claude-sonnet-5-1`; Sonnet 5 stays usable as the custom model string + // `anthropic:claude-sonnet-5`. The bare `sonnet` alias tracks the latest Sonnet tier. SONNET: { provider: "anthropic", - providerModelId: "claude-sonnet-5", + providerModelId: "claude-sonnet-5-1", aliases: ["sonnet"], warm: true, - // Sonnet 5 ships an updated tokenizer (same kind of change introduced with Opus 4.7) that - // isn't published upstream yet; reuse Sonnet 4.5 for approximate counting. Real usage can run - // ~1.0-1.35x higher than this estimate depending on content type. + // Sonnet 5/5.1 ship an updated tokenizer (same kind of change introduced with Opus 4.7) + // that isn't published upstream yet; reuse Sonnet 4.5 for approximate counting. Real usage + // can run ~1.0-1.35x higher than this estimate depending on content type. tokenizerOverride: "anthropic/claude-sonnet-4.5", }, HAIKU: { @@ -276,11 +277,12 @@ export const MODEL_ABBREVIATIONS: Record = Object.fromEntries( ); // Retired first-class models stay documented as custom model strings (see the -// FABLE/OPUS comments); keep their approximate-tokenizer overrides so exact-id +// FABLE/OPUS/SONNET comments); keep their approximate-tokenizer overrides so exact-id // lookup does not fall back to the generic per-provider tokenizer. const LEGACY_TOKENIZER_MODEL_OVERRIDES: Record = { "anthropic:claude-fable-5": "anthropic/claude-opus-4.5", "anthropic:claude-opus-4-8": "anthropic/claude-opus-4.5", + "anthropic:claude-sonnet-5": "anthropic/claude-sonnet-4.5", }; export const TOKENIZER_MODEL_OVERRIDES: Record = { diff --git a/src/common/utils/ai/modelDisplay.test.ts b/src/common/utils/ai/modelDisplay.test.ts index f5e4cc59287..4115a483d7c 100644 --- a/src/common/utils/ai/modelDisplay.test.ts +++ b/src/common/utils/ai/modelDisplay.test.ts @@ -19,6 +19,7 @@ describe("formatModelDisplayName", () => { test("formats Sonnet models", () => { expect(formatModelDisplayName("claude-sonnet-4-5")).toBe("Sonnet 4.5"); expect(formatModelDisplayName("claude-sonnet-4")).toBe("Sonnet 4"); + expect(formatModelDisplayName("claude-sonnet-5-1")).toBe("Sonnet 5.1"); }); test("formats Opus models", () => { diff --git a/src/common/utils/ai/models.test.ts b/src/common/utils/ai/models.test.ts index 8abea904a54..9d250586648 100644 --- a/src/common/utils/ai/models.test.ts +++ b/src/common/utils/ai/models.test.ts @@ -159,12 +159,18 @@ describe("Anthropic 1M context classification", () => { expect(hasNative1MContext("anthropic:claude-sonnet-4-6")).toBe(true); }); - it("treats Sonnet 5 as a native 1M model", () => { + it("treats Sonnet 5 / Sonnet 5.1 as native 1M models", () => { expect(getAnthropic1MContextMode("anthropic:claude-sonnet-5")).toBe("native"); expect(getAnthropic1MContextMode("anthropic:claude-sonnet-5-20260630")).toBe("native"); expect(getAnthropic1MContextMode("mux-gateway:anthropic/claude-sonnet-5")).toBe("native"); expect(supports1MContext("anthropic:claude-sonnet-5")).toBe(false); expect(hasNative1MContext("anthropic:claude-sonnet-5")).toBe(true); + // The claude-sonnet-5 pattern only tolerates date suffixes, so 5.1 needs + // its own pattern (same trap as claude-fable-5-1). + expect(getAnthropic1MContextMode("anthropic:claude-sonnet-5-1")).toBe("native"); + expect(getAnthropic1MContextMode("mux-gateway:anthropic/claude-sonnet-5-1")).toBe("native"); + expect(supports1MContext("anthropic:claude-sonnet-5-1")).toBe(false); + expect(hasNative1MContext("anthropic:claude-sonnet-5-1")).toBe(true); }); it("treats Mythos-class Fable 5 / Fable 5.1 / Mythos 5 as native 1M models", () => { diff --git a/src/common/utils/ai/models.ts b/src/common/utils/ai/models.ts index a58d7432b98..a03dfd2d1f5 100644 --- a/src/common/utils/ai/models.ts +++ b/src/common/utils/ai/models.ts @@ -186,6 +186,7 @@ const ANTHROPIC_NATIVE_1M_PATTERNS = [ new RegExp(`^claude-opus-4-7${OPTIONAL_VERSION_SUFFIX}$`, "i"), new RegExp(`^claude-opus-4-6${OPTIONAL_VERSION_SUFFIX}$`, "i"), new RegExp(`^claude-sonnet-5${OPTIONAL_VERSION_SUFFIX}$`, "i"), + new RegExp(`^claude-sonnet-5-1${OPTIONAL_VERSION_SUFFIX}$`, "i"), new RegExp(`^claude-sonnet-4-6${OPTIONAL_VERSION_SUFFIX}$`, "i"), ]; const ANTHROPIC_BETA_1M_PATTERNS = [ diff --git a/src/common/utils/ai/providerOptions.test.ts b/src/common/utils/ai/providerOptions.test.ts index 6fe8c7658b3..348c1bb0c76 100644 --- a/src/common/utils/ai/providerOptions.test.ts +++ b/src/common/utils/ai/providerOptions.test.ts @@ -139,7 +139,14 @@ describe("buildProviderOptions - Anthropic", () => { // Native-xhigh models (Opus 4.7+ / Sonnet 5+): xhigh is a distinct native // effort and adaptive thinking requires `display: "summarized"` to return // thinking content. - for (const model of ["claude-opus-4-7", "claude-opus-5", "claude-sonnet-5"] as const) { + // Sonnet 5.1 rides the same Sonnet 5+ wildcard matcher; unlike Mythos-class + // models it still sends `thinking: { type: "disabled" }` when off. + for (const model of [ + "claude-opus-4-7", + "claude-opus-5", + "claude-sonnet-5", + "claude-sonnet-5-1", + ] as const) { describe(`${model} (native xhigh effort + summarized display)`, () => { for (const { thinking, expectedThinking, effort } of [ { @@ -2056,6 +2063,7 @@ describe("buildRequestHeaders", () => { test("does not emit any Xum-internal effort header for native-xhigh models", () => { expect(buildRequestHeaders("anthropic:claude-opus-4-7")).toBeUndefined(); expect(buildRequestHeaders("anthropic:claude-sonnet-5")).toBeUndefined(); + expect(buildRequestHeaders("anthropic:claude-sonnet-5-1")).toBeUndefined(); }); describe("openaiDirectProviderOptionsAvailable", () => { diff --git a/src/common/utils/thinking/policy.test.ts b/src/common/utils/thinking/policy.test.ts index 6c58576bf13..5995b1d3777 100644 --- a/src/common/utils/thinking/policy.test.ts +++ b/src/common/utils/thinking/policy.test.ts @@ -532,7 +532,7 @@ describe("getThinkingPolicyForModel", () => { expect(getDefaultMinimumThinkingLevel("openai:team-sol")).toBe("off"); }); - test("returns all 6 levels for Sonnet 5 (native xhigh)", () => { + test("returns all 6 levels for Sonnet 5 / Sonnet 5.1 (native xhigh)", () => { // Sonnet 5 introduced the native xhigh effort level for the Sonnet tier, so it exposes // all 6 levels (unlike Sonnet 4.6, which maps xhigh -> "max" and stops at 5). expect(getThinkingPolicyForModel("anthropic:claude-sonnet-5")).toEqual([ @@ -543,6 +543,17 @@ describe("getThinkingPolicyForModel", () => { "xhigh", "max", ]); + // Sonnet 5.1 rides the same Sonnet 5+ wildcard matcher. Unlike Mythos-class + // models, the Sonnet tier keeps "off" (disabled thinking stays accepted). + expect(getThinkingPolicyForModel("anthropic:claude-sonnet-5-1")).toEqual([ + "off", + "low", + "medium", + "high", + "xhigh", + "max", + ]); + expect(enforceThinkingPolicy("anthropic:claude-sonnet-5-1", "off")).toBe("off"); expect(getThinkingPolicyForModel("anthropic:claude-sonnet-5-20260630")).toEqual([ "off", "low", diff --git a/src/common/utils/tokens/models-extra.ts b/src/common/utils/tokens/models-extra.ts index 81beb9a31b3..60d46153766 100644 --- a/src/common/utils/tokens/models-extra.ts +++ b/src/common/utils/tokens/models-extra.ts @@ -276,6 +276,26 @@ export const modelsExtra: Record = { supports_response_schema: true, }, + // Claude Sonnet 5.1 - successor to Sonnet 5 with the same envelope and standard + // pricing: $3/M input, $15/M output, cache write 1.25x input / cache read 0.1x + // input, native 1M context, 128K max output, adaptive thinking + effort (incl. + // native xhigh). + "claude-sonnet-5-1": { + max_input_tokens: 1000000, + max_output_tokens: 128000, + input_cost_per_token: 0.000003, // $3 per million input tokens + output_cost_per_token: 0.000015, // $15 per million output tokens + cache_creation_input_token_cost: 0.00000375, // $3.75 per million tokens + cache_read_input_token_cost: 0.0000003, // $0.30 per million tokens + litellm_provider: "anthropic", + mode: "chat", + supports_function_calling: true, + supports_vision: true, + supports_pdf_input: true, + supports_reasoning: true, + supports_response_schema: true, + }, + // Claude Sonnet 5 - Released June 30, 2026 // Native 1M context. Standard pricing $3/M input, $15/M output (same as Sonnet 4.6). // Introductory pricing of $2/$10 per MTok applies through Aug 31, 2026, but we list the diff --git a/src/common/utils/tools/tools.test.ts b/src/common/utils/tools/tools.test.ts index 98f281c733f..cb6413a04c7 100644 --- a/src/common/utils/tools/tools.test.ts +++ b/src/common/utils/tools/tools.test.ts @@ -73,6 +73,7 @@ describe("supportsAnthropicNativeWebFetch", () => { ["claude-mythos-5", true], // Two-segment IDs at/after the 4.6 cutoff. ["claude-fable-5-1", true], + ["claude-sonnet-5-1", true], ["claude-sonnet-4-6", true], ["claude-opus-4-6", true], ["claude-opus-4-8", true], diff --git a/src/node/services/agentSkills/builtInSkillContent.generated.ts b/src/node/services/agentSkills/builtInSkillContent.generated.ts index 9148b88adf5..2f6a799921c 100644 --- a/src/node/services/agentSkills/builtInSkillContent.generated.ts +++ b/src/node/services/agentSkills/builtInSkillContent.generated.ts @@ -3853,7 +3853,7 @@ export const BUILTIN_SKILL_FILES: Record> = { "| Fable 5.1 | anthropic:claude-fable-5-1 | `fable` | |", "| Mythos 5 | anthropic:claude-mythos-5 | `mythos` | |", "| Opus 5 | anthropic:claude-opus-5 | `opus` | ✓ |", - "| Sonnet 5 | anthropic:claude-sonnet-5 | `sonnet` | |", + "| Sonnet 5.1 | anthropic:claude-sonnet-5-1 | `sonnet` | |", "| Haiku 4.5 | anthropic:claude-haiku-4-5 | `haiku` | |", "| GPT-5.6 Sol | openai:gpt-5.6-sol | `gpt`, `sol` | |", "| GPT-5.6 Terra | openai:gpt-5.6-terra | `terra` | |", diff --git a/src/node/services/mock/mockAiRouter.ts b/src/node/services/mock/mockAiRouter.ts index 02aa1f8ddf3..7e021899f14 100644 --- a/src/node/services/mock/mockAiRouter.ts +++ b/src/node/services/mock/mockAiRouter.ts @@ -396,7 +396,7 @@ function buildReviewShowDocReply(): MockAiRouterReply { function buildModelStatusReply(): MockAiRouterReply { return { - assistantText: "Claude Sonnet 5 is now responding with standard reasoning capacity.", + assistantText: "Claude Sonnet 5.1 is now responding with standard reasoning capacity.", }; } diff --git a/src/node/services/providerService.test.ts b/src/node/services/providerService.test.ts index 81a5b3df74f..73a8de086e7 100644 --- a/src/node/services/providerService.test.ts +++ b/src/node/services/providerService.test.ts @@ -1649,7 +1649,7 @@ describe("ProviderService.setConfig", () => { const providersConfig = config.loadProvidersConfig(); expect(providersConfig?.["mux-gateway"]?.models).toEqual([ - "anthropic/claude-sonnet-5", + "anthropic/claude-sonnet-5-1", "anthropic/claude-opus-5", "openai/gpt-5.5", ]); diff --git a/src/node/services/providerService.ts b/src/node/services/providerService.ts index c2528c9a5f6..67ab1640c46 100644 --- a/src/node/services/providerService.ts +++ b/src/node/services/providerService.ts @@ -1514,7 +1514,7 @@ export class ProviderService { const existingModels = normalizeProviderModelEntries(providerConfig.models); if (existingModels.length === 0) { providerConfig.models = [ - "anthropic/claude-sonnet-5", + "anthropic/claude-sonnet-5-1", "anthropic/claude-opus-5", "openai/gpt-5.5", ]; diff --git a/src/node/services/tools/task.test.ts b/src/node/services/tools/task.test.ts index f32ead99e13..8ec60118ec5 100644 --- a/src/node/services/tools/task.test.ts +++ b/src/node/services/tools/task.test.ts @@ -477,7 +477,7 @@ describe("task tool", () => { expect(create).toHaveBeenCalledTimes(1); const createArgs = create.mock.calls[0]?.[0]; - expect(createArgs?.modelString).toBe("anthropic:claude-sonnet-5"); + expect(createArgs?.modelString).toBe("anthropic:claude-sonnet-5-1"); expect(createArgs?.thinkingLevel).toBe("high"); // Parent runtime hint is still forwarded so unspecified fields keep inheriting. expect(createArgs?.parentRuntimeAiSettings).toEqual({ diff --git a/tests/e2e/scenarios/slashCommands.spec.ts b/tests/e2e/scenarios/slashCommands.spec.ts index 38b13579f0d..10684180ce6 100644 --- a/tests/e2e/scenarios/slashCommands.spec.ts +++ b/tests/e2e/scenarios/slashCommands.spec.ts @@ -80,18 +80,18 @@ test.describe("slash command flows", () => { await expect(modeToggles.getByText("Opus 5", { exact: true })).toBeVisible(); await ui.chat.sendMessage("/model sonnet"); - await ui.chat.expectStatusMessageContains("Model changed to anthropic:claude-sonnet-5"); + await ui.chat.expectStatusMessageContains("Model changed to anthropic:claude-sonnet-5-1"); // Model is displayed as formatted name - await expect(modeToggles.getByText("Sonnet 5", { exact: true })).toBeVisible(); + await expect(modeToggles.getByText("Sonnet 5.1", { exact: true })).toBeVisible(); const timeline = await ui.chat.captureStreamTimeline(async () => { await ui.chat.sendMessage(MOCK_SLASH_COMMAND_PROMPTS.MODEL_STATUS); }); const streamStart = timeline.events.find((event) => event.type === "stream-start"); - expect(streamStart?.model).toBe("anthropic:claude-sonnet-5"); + expect(streamStart?.model).toBe("anthropic:claude-sonnet-5-1"); await ui.chat.expectTranscriptContains( - "Claude Sonnet 5 is now responding with standard reasoning capacity." + "Claude Sonnet 5.1 is now responding with standard reasoning capacity." ); }); }); diff --git a/tests/ipc/streaming/sendMessage.reasoning.test.ts b/tests/ipc/streaming/sendMessage.reasoning.test.ts index 2e150d55f66..a921ae915d0 100644 --- a/tests/ipc/streaming/sendMessage.reasoning.test.ts +++ b/tests/ipc/streaming/sendMessage.reasoning.test.ts @@ -15,7 +15,17 @@ import { withSharedWorkspace, configureTestRetries, } from "../sendMessageTestHelpers"; -import { KNOWN_MODELS } from "../../../src/common/constants/knownModels"; +// These tests hit the live Anthropic API, and KNOWN_MODELS.SONNET.id now points at +// claude-sonnet-5-1, which does not exist at the API until Anthropic announces it +// (this PR is prepared ahead of the drop). Pin the last live Sonnet id so the tests +// keep exercising real Sonnet-tier reasoning behavior in the meantime. +// +// TODO(sonnet-5-1-release): switch back to KNOWN_MODELS.SONNET.id as part of the +// gated merge, once the id is live — otherwise the promoted production path +// (adaptive thinking + effort + summarized display on claude-sonnet-5-1) is never +// exercised by live integration coverage. This is an explicit release-day item in +// the PR's merge gate. +const LIVE_SONNET_MODEL = "anthropic:claude-sonnet-5"; // Skip all tests if TEST_INTEGRATION is not set const describeIntegration = shouldRunIntegrationTests() ? describe : describe.skip; @@ -36,7 +46,7 @@ describeIntegration("Anthropic reasoning parameter tests", () => { async () => { await withSharedWorkspace("anthropic", async ({ env, workspaceId, collector }) => { const result = await sendMessage(env, workspaceId, "What is 2+2? Answer in one word.", { - model: KNOWN_MODELS.SONNET.id, + model: LIVE_SONNET_MODEL, thinkingLevel: "low", }); expect(result.success).toBe(true); @@ -56,7 +66,7 @@ describeIntegration("Anthropic reasoning parameter tests", () => { async () => { await withSharedWorkspace("anthropic", async ({ env, workspaceId, collector }) => { const result = await sendMessage(env, workspaceId, "What is 4+4? Answer in one word.", { - model: KNOWN_MODELS.SONNET.id, + model: LIVE_SONNET_MODEL, thinkingLevel: "low", }); expect(result.success).toBe(true); @@ -76,7 +86,7 @@ describeIntegration("Anthropic reasoning parameter tests", () => { async () => { await withSharedWorkspace("anthropic", async ({ env, workspaceId, collector }) => { const result = await sendMessage(env, workspaceId, "Explain briefly why 2+2=4", { - model: KNOWN_MODELS.SONNET.id, + model: LIVE_SONNET_MODEL, thinkingLevel: "medium", }); expect(result.success).toBe(true);