Skip to content
Merged
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 @@ -15,7 +15,7 @@ Mux ships with curated models kept up to date with the frontier. Use any custom
| ---------------------- | ----------------------------- | ------------------------------------------------------------ | ------- |
| Fable 5 | anthropic:claude-fable-5 | `fable` | |
| Opus 4.8 | anthropic:claude-opus-4-8 | `opus` | ✓ |
| Sonnet 4.6 | anthropic:claude-sonnet-4-6 | `sonnet` | |
| Sonnet 5 | anthropic:claude-sonnet-5 | `sonnet` | |
| Haiku 4.5 | anthropic:claude-haiku-4-5 | `haiku` | |
| GPT-5.5 | openai:gpt-5.5 | `gpt`, `gpt-5.5` | |
| GPT-5.5 Pro | openai:gpt-5.5-pro | `gpt-pro`, `gpt-5.5-pro` | |
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 @@ -62,7 +62,7 @@ describe("commandParser", () => {
});

it("should parse /model with full provider:model format", () => {
expectModelSet("/model anthropic:claude-sonnet-4-6", KNOWN_MODELS.SONNET.id);
expectModelSet("/model anthropic:claude-sonnet-5", KNOWN_MODELS.SONNET.id);
});

it("should parse /compact -m with alias", () => {
Expand Down
10 changes: 8 additions & 2 deletions src/common/constants/knownModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,18 @@ const MODEL_DEFINITIONS = {
// (Opus 4.6/4.7 also reused 4.5 — tokenization is unchanged across the 4.x line).
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.
SONNET: {
provider: "anthropic",
providerModelId: "claude-sonnet-4-6",
providerModelId: "claude-sonnet-5",
Comment thread
ammar-agent marked this conversation as resolved.
Comment thread
ammar-agent marked this conversation as resolved.
aliases: ["sonnet"],
warm: true,
// Sonnet 4.6 tokenizer not yet available upstream; reuse 4.5 for approximate counting
// 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.
tokenizerOverride: "anthropic/claude-sonnet-4.5",
},
HAIKU: {
Expand Down
30 changes: 17 additions & 13 deletions src/common/types/thinking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ export const THINKING_DISPLAY_LABELS: Record<ThinkingLevel, string> = {
/**
* Display label for thinking levels, with provider-aware xhigh labeling.
*
* Models with a native "xhigh" effort level (OpenAI, Anthropic Opus 4.7+) show
* "XHIGH" for the xhigh ThinkingLevel; on those providers xhigh and max are
* distinct. On Opus 4.6 (where xhigh maps to max effort), it shows "MAX".
* Models with a native "xhigh" effort level (OpenAI, Anthropic Opus 4.7+,
* Anthropic Sonnet 5+) show "XHIGH" for the xhigh ThinkingLevel; on those
* providers xhigh and max are distinct. On Opus/Sonnet 4.6 (where xhigh maps
* to max effort), it shows "MAX".
* Medium always displays as "MED".
*/
export function getThinkingDisplayLabel(level: ThinkingLevel, modelString?: string): string {
Expand Down Expand Up @@ -164,10 +165,10 @@ export const ANTHROPIC_THINKING_BUDGETS: Record<ThinkingLevel, number> = {
/**
* Anthropic effort type - matches SDK's AnthropicProviderOptions["effort"].
*
* Note: Opus 4.7 introduced a native "xhigh" effort level in the API, but the
* SDK's Zod validator still rejects "xhigh". Mux handles this by sending "max"
* through the SDK and rewriting `output_config.effort` to "xhigh" in a fetch
* wrapper for Opus 4.7+ when the user selected the xhigh ThinkingLevel.
* Note: Opus 4.7 and Sonnet 5 introduced a native "xhigh" effort level in the API,
* but the SDK's Zod validator still rejects "xhigh". Mux handles this by sending
* "max" through the SDK and rewriting `output_config.effort` to "xhigh" in a fetch
* wrapper for native-xhigh Anthropic models when the user selected the xhigh ThinkingLevel.
* See `wrapFetchWithAnthropicCacheControl` and `buildRequestHeaders`.
*/
export type AnthropicEffortLevel = "low" | "medium" | "high" | "max";
Expand All @@ -178,18 +179,18 @@ export type AnthropicEffortLevel = "low" | "medium" | "high" | "max";
* The effort parameter controls how much computational work the model applies.
* - Opus 4.5 supports: low, medium, high (policy clamps xhigh → high)
* - Opus 4.6 supports: low, medium, high, max (xhigh maps to "max" effort)
* - Opus 4.7 supports: low, medium, high, xhigh, max (xhigh requires wire override)
* - Opus 4.7+ and Sonnet 5+ support: low, medium, high, xhigh, max (xhigh requires wire override)
*
* Because the @ai-sdk/anthropic Zod schema doesn't accept "xhigh" yet, we send
* "max" through the SDK for xhigh-on-4.7 and rewrite `output_config.effort` to
* "xhigh" in the Anthropic fetch wrapper.
* "max" through the SDK for native-xhigh Anthropic models and rewrite
* `output_config.effort` to "xhigh" in the Anthropic fetch wrapper.
*/
const ANTHROPIC_EFFORT: Record<ThinkingLevel, AnthropicEffortLevel> = {
off: "low",
low: "low",
medium: "medium",
high: "high",
xhigh: "max", // SDK placeholder; fetch wrapper rewrites to "xhigh" on Opus 4.7+
xhigh: "max", // SDK placeholder; fetch wrapper rewrites to "xhigh" on native-xhigh models
max: "max",
};

Expand All @@ -204,6 +205,8 @@ export function getAnthropicEffort(level: ThinkingLevel): AnthropicEffortLevel {
* Matches:
* - `claude-opus-4-7`, `claude-opus-4-8`, ... `claude-opus-4-99`, and any future Opus 5+
* (which we assume preserves or exceeds 4.7's capabilities).
* - `claude-sonnet-5` and any future Sonnet 5+ (Sonnet 5 introduced native xhigh effort for
* the Sonnet tier; Sonnet 4.6 and earlier did not).
* - Mythos-class models (`claude-fable-*`, `claude-mythos-*`), the tier above Opus.
*/
export function anthropicSupportsNativeXhigh(modelString: string): boolean {
Expand All @@ -212,10 +215,11 @@ export function anthropicSupportsNativeXhigh(modelString: string): boolean {
.toLowerCase()
.replace(/^[a-z0-9_-]+:\s*/, "")
.replace(/^[a-z0-9_-]+\//, "");
// Opus 4.7+ (4-7, 4-8, 4-9, 4-10, 4-11, ...) or any Opus 5+, plus the Mythos-class
// Fable / Mythos models that sit above Opus.
// Opus 4.7+ (4-7, 4-8, 4-9, 4-10, 4-11, ...) or any Opus 5+, Sonnet 5+ (5, 6, ... 10+),
// plus the Mythos-class Fable / Mythos models that sit above Opus.
return (
/claude-opus-(?:4-(?:[7-9]|\d{2,})|[5-9]|\d{2,})/.test(withoutPrefix) ||
/claude-sonnet-(?:[5-9]|\d{2,})/.test(withoutPrefix) ||
/claude-(?:fable|mythos)-/.test(withoutPrefix)
);
}
Expand Down
8 changes: 8 additions & 0 deletions src/common/utils/ai/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ describe("Anthropic 1M context classification", () => {
expect(hasNative1MContext("anthropic:claude-sonnet-4-6")).toBe(true);
});

it("treats Sonnet 5 as a native 1M model", () => {
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);
});

it("treats Mythos-class Fable 5 / Mythos 5 as native 1M models", () => {
expect(getAnthropic1MContextMode("anthropic:claude-fable-5")).toBe("native");
expect(getAnthropic1MContextMode("anthropic:claude-mythos-5")).toBe("native");
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 @@ -137,6 +137,7 @@ const ANTHROPIC_NATIVE_1M_PATTERNS = [
new RegExp(`^claude-opus-4-8${OPTIONAL_VERSION_SUFFIX}$`, "i"),
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-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 @@ -108,7 +108,7 @@ describe("buildProviderOptions - Anthropic", () => {
});
});

for (const model of ["claude-opus-4-6", "claude-sonnet-4-6"] as const) {
for (const model of ["claude-opus-4-6", "claude-sonnet-4-6", "claude-sonnet-5"] as const) {
describe(`${model} (adaptive thinking + effort)`, () => {
for (const { thinking, expectedThinking, effort } of [
{ thinking: "medium", expectedThinking: { type: "adaptive" }, effort: "medium" },
Expand Down Expand Up @@ -985,6 +985,14 @@ describe("buildRequestHeaders", () => {
thinkingLevel: "xhigh",
expected: { [MUX_ANTHROPIC_EFFORT_OVERRIDE_HEADER]: "xhigh" },
},
{
// Sonnet 5 added native xhigh for the Sonnet tier, so it needs the wire rewrite too.
name: "emits override header for Sonnet 5",
model: "anthropic:claude-sonnet-5",
routeProvider: undefined,
thinkingLevel: "xhigh",
expected: { [MUX_ANTHROPIC_EFFORT_OVERRIDE_HEADER]: "xhigh" },
},
{
name: "does not emit override header for Opus 4.7 with thinkingLevel=max",
model: "anthropic:claude-opus-4-7",
Expand Down
21 changes: 11 additions & 10 deletions src/common/utils/ai/providerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,23 +273,24 @@ export function buildProviderOptions(
// serializes it to a top-level cache_control block, which adds an extra
// breakpoint on direct Anthropic requests.

// Opus 4.5+ and Sonnet 4.6 use the effort parameter for reasoning control.
// Opus 4.6+ / Sonnet 4.6 use adaptive thinking (model decides when/how much to think).
// Opus 4.5+, Sonnet 4.6, and Sonnet 5 use the effort parameter for reasoning control.
// Opus 4.6+ / Sonnet 4.6 / Sonnet 5 use adaptive thinking (model decides when/how much).
// Opus 4.5 uses enabled thinking with a budgetTokens ceiling.
const isOpus45 = capModelName?.includes("opus-4-5") ?? false;
const isOpus46 = capModelName?.includes("opus-4-6") ?? false;
const isOpus47Plus = anthropicSupportsNativeXhigh(capabilityModel);
// Opus 4.7+ and Sonnet 5+ — the native-xhigh tier, which also uses adaptive thinking.
const supportsNativeXhigh = anthropicSupportsNativeXhigh(capabilityModel);
const isSonnet46 = capModelName?.includes("sonnet-4-6") ?? false;
const usesAdaptiveThinking = isOpus46 || isOpus47Plus || isSonnet46;
const usesAdaptiveThinking = isOpus46 || supportsNativeXhigh || isSonnet46;

if (isOpus45 || usesAdaptiveThinking) {
// Map to SDK-accepted effort. For Opus 4.7+ with xhigh ThinkingLevel, the SDK
// gets "max" as a placeholder and the Anthropic fetch wrapper rewrites
// Map to SDK-accepted effort. For Opus 4.7+ / Sonnet 5+ with xhigh ThinkingLevel, the
// SDK gets "max" as a placeholder and the Anthropic fetch wrapper rewrites
// `output_config.effort` to "xhigh" via the X-Mux-Anthropic-Effort header
// (added in buildRequestHeaders).
const effortLevel = getAnthropicEffort(effectiveThinking);
const budgetTokens = ANTHROPIC_THINKING_BUDGETS[effectiveThinking];
// Opus 4.6+ / Sonnet 4.6: adaptive thinking when on, disabled when off
// Opus 4.6+ / Sonnet 4.6 / Sonnet 5: adaptive thinking when on, disabled when off
// Opus 4.5: enabled thinking with budgetTokens ceiling (only when not "off")
const thinking: AnthropicProviderOptions["thinking"] = usesAdaptiveThinking
? effectiveThinking === "off"
Expand Down Expand Up @@ -595,9 +596,9 @@ export function buildRequestHeaders(
headers["anthropic-beta"] = ANTHROPIC_1M_CONTEXT_HEADER;
}

// Opus 4.7 introduced a native "xhigh" effort level that the @ai-sdk/anthropic
// Zod schema doesn't accept yet. Emit a Mux-internal header so the Anthropic
// fetch wrapper can rewrite `output_config.effort` to "xhigh" on the wire.
// Native-xhigh Anthropic models use an "xhigh" effort level that the
// @ai-sdk/anthropic Zod schema doesn't accept yet. Emit a Mux-internal header
// so the Anthropic fetch wrapper can rewrite `output_config.effort` to "xhigh" on the wire.
//
// Only emit when the route will pass through our Anthropic fetch wrapper
// (direct Anthropic or passthrough gateways like mux-gateway). Non-passthrough
Expand Down
30 changes: 30 additions & 0 deletions src/common/utils/thinking/policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,36 @@ describe("getThinkingPolicyForModel", () => {
]);
});

test("returns all 6 levels for Sonnet 5 (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([
"off",
"low",
"medium",
"high",
"xhigh",
"max",
]);
expect(getThinkingPolicyForModel("anthropic:claude-sonnet-5-20260630")).toEqual([
"off",
"low",
"medium",
"high",
"xhigh",
"max",
]);
// Behind gateway
expect(getThinkingPolicyForModel("mux-gateway:anthropic/claude-sonnet-5")).toEqual([
"off",
"low",
"medium",
"high",
"xhigh",
"max",
]);
});

test("returns 5 levels including xhigh for Sonnet 4.6", () => {
expect(getThinkingPolicyForModel("anthropic:claude-sonnet-4-6")).toEqual([
"off",
Expand Down
21 changes: 21 additions & 0 deletions src/common/utils/tokens/models-extra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,27 @@ export const modelsExtra: Record<string, ModelData> = {
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
// standard rate so cost estimates stay correct once the promo ends.
// 128K max output (up from Sonnet 4.6's 64K); supports adaptive thinking + effort (incl. xhigh).
"claude-sonnet-5": {
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 4.6 - Released February 2026
// Native 1M context at standard pricing: $3/M input, $15/M output.
// 64K max output tokens, supports adaptive thinking + effort parameter.
Expand Down
32 changes: 31 additions & 1 deletion src/common/utils/tools/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { Ok } from "@/common/types/result";
import type { InitStateManager } from "@/node/services/initStateManager";
import type { DesktopSessionManager } from "@/node/services/desktop/DesktopSessionManager";
import { LocalRuntime } from "@/node/runtime/LocalRuntime";
import { getToolsForModel, type WorkspaceHeartbeatToolService } from "./tools";
import {
getToolsForModel,
supportsAnthropicNativeWebFetch,
type WorkspaceHeartbeatToolService,
} from "./tools";

const DESKTOP_TOOL_NAMES = [
"desktop_screenshot",
Expand Down Expand Up @@ -59,6 +63,32 @@ function createDesktopSessionManager(options: { available: boolean }) {
};
}

describe("supportsAnthropicNativeWebFetch", () => {
test.each([
// Major-only "5 generation" IDs (dateless naming) — Sonnet 5, Fable 5, Mythos 5.
["claude-sonnet-5", true],
["claude-fable-5", true],
["claude-mythos-5", true],
// Two-segment IDs at/after the 4.6 cutoff.
["claude-sonnet-4-6", true],
["claude-opus-4-6", true],
["claude-opus-4-8", true],
["claude-opus-4-6-20260201", true],
// Below the 4.6 cutoff.
["claude-sonnet-4-5", false],
["claude-haiku-4-5", false],
// Date-based pre-4.6 ID must parse as major=4 / no minor and stay unsupported,
// not misread the 8-digit date as a minor version.
["claude-sonnet-4-20250514", false],
// Older Claude 3.x IDs encode the family before the variant; do not misread
// `3-5` or `3-7` as variant=3, major=5/7.
["claude-3-5-sonnet-20241022", false],
["claude-3-7-sonnet-20250219", false],
] as const)("%s -> %s", (modelId, expected) => {
expect(supportsAnthropicNativeWebFetch(modelId)).toBe(expected);
});
});

describe("getToolsForModel", () => {
test("only includes agent_report when enableAgentReport=true", async () => {
const runtime = new LocalRuntime(process.cwd());
Expand Down
24 changes: 15 additions & 9 deletions src/common/utils/tools/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,19 +489,25 @@ async function getDesktopTools(config: ToolConfiguration): Promise<Record<string
/**
* Returns true when an Anthropic model supports webFetch_20250910 (Claude 4.6+).
*
* Generation-based IDs: claude-{variant}-{major}-{minor} (e.g. claude-sonnet-4-6)
* Pinned generation IDs: claude-{variant}-{major}-{minor}-{date} (e.g. claude-opus-4-6-20260201)
* Date-based pre-4.6 IDs: claude-{variant}-{major}-{date} (e.g. claude-sonnet-4-20250514)
* Two-segment IDs: claude-{variant}-{major}-{minor} (e.g. claude-sonnet-4-6, claude-opus-4-8)
* Pinned two-segment: claude-{variant}-{major}-{minor}-{date} (e.g. claude-opus-4-6-20260201)
* Date-based pre-4.6: claude-{variant}-{major}-{date} (e.g. claude-sonnet-4-20250514)
* Major-only IDs: claude-{variant}-{major} (e.g. claude-sonnet-5, claude-fable-5,
* claude-mythos-5) — the dateless naming adopted for the 5 generation.
*
* The \d{1,2} constraint on the minor segment accepts 1-2 digit version numbers (1–99) while
* rejecting 8-digit date suffixes. The (?:-|$) lookahead allows an optional pinned date to follow.
* The minor segment is optional so major-only IDs (Sonnet 5, Fable 5, Mythos 5, future Opus 5+)
* are recognized; those are all > 4 and qualify. The variant segment must be alphabetic so older
* third-generation IDs like claude-3-5-sonnet-20241022 do not get misread as major=5. The \d{1,2}
* constraint accepts 1-2 digit version numbers (1–99) while rejecting 8-digit date suffixes, so
* date-based pre-4.6 IDs like claude-sonnet-4-20250514 parse as major=4 / no minor and correctly
* stay unsupported. The (?:-|$) lookahead allows an optional pinned date to follow.
*/
function supportsAnthropicNativeWebFetch(modelId: string): boolean {
const match = /^claude-\w+-(\d+)-(\d{1,2})(?:-|$)/.exec(modelId);
export function supportsAnthropicNativeWebFetch(modelId: string): boolean {
const match = /^claude-[a-z]+-(\d+)(?:-(\d{1,2}))?(?:-|$)/.exec(modelId);
if (!match) return false;
const major = parseInt(match[1], 10);
const minor = parseInt(match[2], 10);
return major > 4 || (major === 4 && minor >= 6);
const minor = match[2] != null ? parseInt(match[2], 10) : undefined;
return major > 4 || (major === 4 && minor !== undefined && minor >= 6);
}

export async function getToolsForModel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3101,7 +3101,7 @@ export const BUILTIN_SKILL_FILES: Record<string, Record<string, string>> = {
"| ---------------------- | ----------------------------- | ------------------------------------------------------------ | ------- |",
"| Fable 5 | anthropic:claude-fable-5 | `fable` | |",
"| Opus 4.8 | anthropic:claude-opus-4-8 | `opus` | ✓ |",
"| Sonnet 4.6 | anthropic:claude-sonnet-4-6 | `sonnet` | |",
"| Sonnet 5 | anthropic:claude-sonnet-5 | `sonnet` | |",
"| Haiku 4.5 | anthropic:claude-haiku-4-5 | `haiku` | |",
"| GPT-5.5 | openai:gpt-5.5 | `gpt`, `gpt-5.5` | |",
"| GPT-5.5 Pro | openai:gpt-5.5-pro | `gpt-pro`, `gpt-5.5-pro` | |",
Expand Down
Loading
Loading