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 @@ -13,7 +13,7 @@ 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` | |
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
32 changes: 22 additions & 10 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",
Comment thread
ibetitsmike marked this conversation as resolved.
aliases: ["fable"],
warm: true,
// Fable/Mythos use the newer Opus 4.7+ tokenizer, which isn't published upstream;
Expand Down Expand Up @@ -275,11 +275,23 @@ 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 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",
};

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
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 @@ -27,6 +27,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
8 changes: 5 additions & 3 deletions src/common/utils/ai/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,14 @@ describe("Anthropic 1M context classification", () => {
expect(hasNative1MContext("anthropic:claude-sonnet-5")).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
1 change: 1 addition & 0 deletions src/common/utils/ai/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ 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"),
Expand Down
9 changes: 9 additions & 0 deletions src/common/utils/ai/providerOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,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 +195,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
8 changes: 8 additions & 0 deletions 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
19 changes: 19 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
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 @@ -72,6 +72,7 @@ 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-4-6", true],
["claude-opus-4-6", true],
["claude-opus-4-8", true],
Expand Down
Loading
Loading