Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/config/models.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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` | |
Expand Down
2 changes: 1 addition & 1 deletion src/browser/utils/slashCommands/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
20 changes: 11 additions & 9 deletions src/common/constants/knownModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Comment thread
ThomasK33 marked this conversation as resolved.
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: {
Expand Down Expand Up @@ -276,11 +277,12 @@ export const MODEL_ABBREVIATIONS: Record<string, string> = 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<string, string> = {
"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<string, string> = {
Expand Down
1 change: 1 addition & 0 deletions src/common/utils/ai/modelDisplay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
8 changes: 7 additions & 1 deletion src/common/utils/ai/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
1 change: 1 addition & 0 deletions src/common/utils/ai/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
10 changes: 9 additions & 1 deletion src/common/utils/ai/providerOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
{
Expand Down Expand Up @@ -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", () => {
Expand Down
13 changes: 12 additions & 1 deletion src/common/utils/thinking/policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand All @@ -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",
Expand Down
20 changes: 20 additions & 0 deletions src/common/utils/tokens/models-extra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,26 @@ export const modelsExtra: Record<string, ModelData> = {
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
Expand Down
1 change: 1 addition & 0 deletions src/common/utils/tools/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3853,7 +3853,7 @@ export const BUILTIN_SKILL_FILES: Record<string, Record<string, string>> = {
"| 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` | |",
Expand Down
2 changes: 1 addition & 1 deletion src/node/services/mock/mockAiRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/node/services/providerService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]);
Expand Down
2 changes: 1 addition & 1 deletion src/node/services/providerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
];
Expand Down
2 changes: 1 addition & 1 deletion src/node/services/tools/task.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
8 changes: 4 additions & 4 deletions tests/e2e/scenarios/slashCommands.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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."
);
});
});
18 changes: 14 additions & 4 deletions tests/ipc/streaming/sendMessage.reasoning.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Comment thread
ThomasK33 marked this conversation as resolved.

// Skip all tests if TEST_INTEGRATION is not set
const describeIntegration = shouldRunIntegrationTests() ? describe : describe.skip;
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Loading