Skip to content
Draft
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
4 changes: 2 additions & 2 deletions docs/config/models.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ Xum ships with curated models kept up to date with the frontier. Use any custom

| Model | ID | Aliases | Default |
| ---------------------- | ----------------------------- | ------------------------------------------------------------ | ------- |
| Fable 5 | anthropic:claude-fable-5 | `fable` | |
| 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
6 changes: 6 additions & 0 deletions src/common/config/schemas/appConfigOnDisk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ export const AppConfigMigrationsSchema = z
userPreferencesInitialized: z.boolean().optional(),
/** One-time seed of DEFAULT_MODEL_FALLBACKS; not re-applied while true. */
defaultModelFallbacksSeeded: z.boolean().optional(),
/**
* One-time re-run of the fallback seed after the fable alias moved to
* Fable 5.1: configs seeded before the promotion lack a chain for the new
* source key.
*/
defaultModelFallbacksSeededFable51: z.boolean().optional(),
/** One-time migration from the legacy auto-delete default to persistent sub-agents. */
persistentSubagentsDefaulted: z.boolean().optional(),
})
Expand Down
50 changes: 32 additions & 18 deletions src/common/constants/knownModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ interface KnownModel extends KnownModelDefinition {
// Model definitions. Note we avoid listing legacy models here. These represent the focal models
// of the community.
const MODEL_DEFINITIONS = {
// Claude Fable 5 - Mythos-class model (a tier above Opus) released June 9, 2026.
// It is the generally-available variant of the Mythos 5 model, shipped with safeguards
// enabled (a small fraction of flagged requests fall back to Opus 4.8 server-side, which
// is transparent to API clients). API id `claude-fable-5`; $10/M input, $50/M output.
// Claude Fable 5.1 - Mythos-class model (a tier above Opus), successor to Fable 5
// (released June 9, 2026) as the generally-available safeguarded variant, at unchanged
// pricing ($10/M input, $50/M output). API id `claude-fable-5-1`; Fable 5 stays usable
// as the custom model string `anthropic:claude-fable-5`.
FABLE: {
provider: "anthropic",
providerModelId: "claude-fable-5",
providerModelId: "claude-fable-5-1",
aliases: ["fable"],
warm: true,
// Fable/Mythos use the newer Opus 4.7+ tokenizer, which isn't published upstream;
Expand Down 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 @@ -275,11 +276,24 @@ export const MODEL_ABBREVIATIONS: Record<string, string> = Object.fromEntries(
.sort(([a], [b]) => a.localeCompare(b))
);

export const TOKENIZER_MODEL_OVERRIDES: Record<string, string> = Object.fromEntries(
Object.values(KNOWN_MODELS)
.filter((model) => Boolean(model.tokenizerOverride))
.map((model) => [model.id, model.tokenizerOverride!])
);
// Retired first-class models stay documented as custom model strings (see the
// 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> = {
...LEGACY_TOKENIZER_MODEL_OVERRIDES,
// Spread current models last so a returning id always wins over its legacy entry.
...Object.fromEntries(
Object.values(KNOWN_MODELS)
.filter((model) => Boolean(model.tokenizerOverride))
.map((model) => [model.id, model.tokenizerOverride!])
),
};

/** Tooltip-friendly abbreviation examples: show representative shortcuts */
export const MODEL_ABBREVIATION_EXAMPLES = (["opus", "sonnet"] as const).map((abbrev) => ({
Expand Down
2 changes: 2 additions & 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 All @@ -27,6 +28,7 @@ describe("formatModelDisplayName", () => {

test("formats Mythos-class Fable / Mythos models", () => {
expect(formatModelDisplayName("claude-fable-5")).toBe("Fable 5");
expect(formatModelDisplayName("claude-fable-5-1")).toBe("Fable 5.1");
expect(formatModelDisplayName("claude-mythos-5")).toBe("Mythos 5");
});
});
Expand Down
21 changes: 19 additions & 2 deletions src/common/utils/ai/modelFallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export const MODEL_FALLBACK_CHAIN_LIMIT = 3;
* the sensible out-of-the-box behavior.
*
* Seeded into the config exactly once, guarded by
* migrations.defaultModelFallbacksSeeded — on versions that know the flag,
* migrations.defaultModelFallbacksSeeded (plus the one-shot
* defaultModelFallbacksSeededFable51 re-seed for the key move to Fable 5.1)
* — on versions that know the flag,
* user edits or deletions of these chains are never overridden by updates.
* (Versions predating the flag strip it on save, so a downgrade→save→
* re-upgrade round-trip re-seeds a deleted chain; bounded to re-adding this
Expand All @@ -25,10 +27,25 @@ export const MODEL_FALLBACK_CHAIN_LIMIT = 3;
export const DEFAULT_MODEL_FALLBACKS: ModelFallbacks = {
[KNOWN_MODELS.FABLE.id]: { models: [KNOWN_MODELS.OPUS.id] },
};

/**
* Legacy default chain for the pre-5.1 fable id, seeded alongside
* DEFAULT_MODEL_FALLBACKS whenever the original seed pass runs (fresh installs
* and configs that never completed it). Those configs mark
* defaultModelFallbacksSeeded, so a downgrade to a build whose FABLE is
* Fable 5 would skip its own seed; carrying this chain keeps the old build's
* refusal fallback intact.
*/
export const LEGACY_DEFAULT_MODEL_FALLBACKS: ModelFallbacks = {
"anthropic:claude-fable-5": { models: [KNOWN_MODELS.OPUS.id] },
};
// Deep-freeze: entries are spread by reference into live configs (fresh-install
// defaults, seed merge). Accidental in-place mutation must crash fast instead
// of silently corrupting the process-wide default.
for (const entry of Object.values(Object.freeze(DEFAULT_MODEL_FALLBACKS))) {
for (const entry of [
...Object.values(Object.freeze(DEFAULT_MODEL_FALLBACKS)),
...Object.values(Object.freeze(LEGACY_DEFAULT_MODEL_FALLBACKS)),
]) {
Object.freeze(entry);
Object.freeze(entry.models);
}
Expand Down
16 changes: 12 additions & 4 deletions src/common/utils/ai/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,20 +159,28 @@ 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 / Mythos 5 as native 1M models", () => {
it("treats Mythos-class Fable 5 / Fable 5.1 / Mythos 5 as native 1M models", () => {
expect(getAnthropic1MContextMode("anthropic:claude-fable-5")).toBe("native");
expect(getAnthropic1MContextMode("anthropic:claude-fable-5-1")).toBe("native");
expect(getAnthropic1MContextMode("anthropic:claude-mythos-5")).toBe("native");
expect(getAnthropic1MContextMode("mux-gateway:anthropic/claude-fable-5")).toBe("native");
expect(getAnthropic1MContextMode("mux-gateway:anthropic/claude-fable-5-1")).toBe("native");
expect(hasNative1MContext("anthropic:claude-fable-5")).toBe(true);
expect(supports1MContext("anthropic:claude-fable-5")).toBe(false);
expect(hasNative1MContext("anthropic:claude-fable-5-1")).toBe(true);
expect(supports1MContext("anthropic:claude-fable-5-1")).toBe(false);
});

it("returns none for models without Anthropic 1M support", () => {
Expand Down
2 changes: 2 additions & 0 deletions src/common/utils/ai/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,14 @@ const OPTIONAL_VERSION_SUFFIX = String.raw`(?:-(?:\d{8}|\d{4}-\d{2}-\d{2}))?`;
const ANTHROPIC_NATIVE_1M_PATTERNS = [
// Mythos-class models (Fable 5 / Mythos 5) ship 1M context as standard metadata.
new RegExp(`^claude-fable-5${OPTIONAL_VERSION_SUFFIX}$`, "i"),
new RegExp(`^claude-fable-5-1${OPTIONAL_VERSION_SUFFIX}$`, "i"),
new RegExp(`^claude-mythos-5${OPTIONAL_VERSION_SUFFIX}$`, "i"),
new RegExp(`^claude-opus-5${OPTIONAL_VERSION_SUFFIX}$`, "i"),
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-5-1${OPTIONAL_VERSION_SUFFIX}$`, "i"),
new RegExp(`^claude-sonnet-4-6${OPTIONAL_VERSION_SUFFIX}$`, "i"),
];
const ANTHROPIC_BETA_1M_PATTERNS = [
Expand Down
19 changes: 18 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 @@ -180,6 +187,12 @@ describe("buildProviderOptions - Anthropic", () => {
// the summarized display flag.
expect(anthropic.thinking).toEqual({ type: "adaptive", display: "summarized" });
expect(anthropic.effort).toBe("medium");
// Fable 5.1 rides the same Mythos-class wildcard matcher.
const anthropic51 = anthropicProviderOptions(
buildProviderOptions("anthropic:claude-fable-5-1", "medium")
);
expect(anthropic51.thinking).toEqual({ type: "adaptive", display: "summarized" });
expect(anthropic51.effort).toBe("medium");
});

test("omits thinking instead of sending disabled when off", () => {
Expand All @@ -189,6 +202,9 @@ describe("buildProviderOptions - Anthropic", () => {
expect(buildProviderOptions("anthropic:claude-fable-5", "off")).toEqual({
anthropic: { ...baseAnthropicOptions, effort: "low" },
});
expect(buildProviderOptions("anthropic:claude-fable-5-1", "off")).toEqual({
anthropic: { ...baseAnthropicOptions, effort: "low" },
});
});
});

Expand Down Expand Up @@ -2047,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
21 changes: 20 additions & 1 deletion src/common/utils/thinking/policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,13 @@ describe("getThinkingPolicyForModel", () => {
"xhigh",
"max",
]);
expect(getThinkingPolicyForModel("anthropic:claude-fable-5-1")).toEqual([
"low",
"medium",
"high",
"xhigh",
"max",
]);
expect(getThinkingPolicyForModel("anthropic:claude-mythos-5")).toEqual([
"low",
"medium",
Expand All @@ -455,6 +462,7 @@ describe("getThinkingPolicyForModel", () => {
test("clamps 'off' up to 'low' for Mythos-class models", () => {
// A stored/legacy "off" selection must not reach the wire as disabled thinking.
expect(enforceThinkingPolicy("anthropic:claude-fable-5", "off")).toBe("low");
expect(enforceThinkingPolicy("anthropic:claude-fable-5-1", "off")).toBe("low");
expect(enforceThinkingPolicy("anthropic:claude-mythos-5", "off")).toBe("low");
});

Expand Down Expand Up @@ -524,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 @@ -535,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
39 changes: 39 additions & 0 deletions src/common/utils/tokens/models-extra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,25 @@ export const modelsExtra: Record<string, ModelData> = {
supports_pdf_input: true,
},

// Claude Fable 5.1 - successor to Fable 5 in the Mythos-class tier at the same
// pricing/shape: $10/M input, $50/M output, cache write 1.25x input / cache read
// 0.1x input, native 1M context, 128K max output, native xhigh effort level.
"claude-fable-5-1": {
max_input_tokens: 1000000,
max_output_tokens: 128000,
input_cost_per_token: 0.00001, // $10 per million input tokens
output_cost_per_token: 0.00005, // $50 per million output tokens
cache_creation_input_token_cost: 0.0000125, // $12.50 per million tokens (1.25× input)
cache_read_input_token_cost: 0.000001, // $1.00 per million tokens (0.1× input)
litellm_provider: "anthropic",
mode: "chat",
supports_function_calling: true,
supports_vision: true,
supports_pdf_input: true,
supports_reasoning: true,
supports_response_schema: true,
},

// Claude Fable 5 / Mythos 5 - Released June 9, 2026
// Mythos-class model (a tier above Opus). Fable 5 (`claude-fable-5`) is the
// generally-available variant with safeguards; Mythos 5 (`claude-mythos-5`) is the same
Expand Down Expand Up @@ -257,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
2 changes: 2 additions & 0 deletions src/common/utils/tools/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ describe("supportsAnthropicNativeWebFetch", () => {
["claude-fable-5", true],
["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
Loading
Loading