Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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."
);
});
});
13 changes: 9 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,12 @@ 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; flip back to
// KNOWN_MODELS.SONNET.id once Sonnet 5.1 is live.
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 +41,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 +61,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 +81,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