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
2 changes: 1 addition & 1 deletion .github/workflows/auto-cleanup-fixup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,6 @@ jobs:
FAILED_RUN_ID: ${{ github.event.workflow_run.id }}
run: |
bunx mux@next run \
--model anthropic:claude-opus-5 \
--model anthropic:claude-opus-5-1 \
--thinking xhigh \
< .github/prompts/auto-cleanup-fixup.md
2 changes: 1 addition & 1 deletion .github/workflows/auto-cleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,6 @@ jobs:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
bunx @coder/xum@next run \
--model anthropic:claude-opus-5 \
--model anthropic:claude-opus-5-1 \
--thinking xhigh \
< .github/prompts/auto-cleanup.md
4 changes: 2 additions & 2 deletions .github/workflows/nightly-terminal-bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
workflow_dispatch:
inputs:
models:
description: 'Models to test (comma-separated, or "all" for opus-5 + gpt-5.6-sol + google/gemini-3-pro-preview + google/gemini-3-flash-preview + google/gemini-3.7-flash)'
description: 'Models to test (comma-separated, or "all" for opus-5-1 + gpt-5.6-sol + google/gemini-3-pro-preview + google/gemini-3-flash-preview + google/gemini-3.7-flash)'
required: false
default: "all"
type: string
Expand Down Expand Up @@ -129,7 +129,7 @@ jobs:
INPUT_MODELS: ${{ inputs.models }}
run: |
if [ "$INPUT_MODELS" = "all" ] || [ -z "$INPUT_MODELS" ]; then
echo 'models=["anthropic/claude-opus-5","openai/gpt-5.6-sol","google/gemini-3-pro-preview","google/gemini-3-flash-preview","google/gemini-3.7-flash"]' >> "$GITHUB_OUTPUT"
echo 'models=["anthropic/claude-opus-5-1","openai/gpt-5.6-sol","google/gemini-3-pro-preview","google/gemini-3-flash-preview","google/gemini-3.7-flash"]' >> "$GITHUB_OUTPUT"
Comment thread
ThomasK33 marked this conversation as resolved.
else
# Convert comma-separated to JSON array
models_json=$(echo "$INPUT_MODELS" | jq -R -s -c 'split(",") | map(gsub("^\\s+|\\s+$"; ""))')
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/terminal-bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_call:
inputs:
model_name:
description: "Model to use (e.g., anthropic/claude-opus-5, openai/gpt-5.6-sol)"
description: "Model to use (e.g., anthropic/claude-opus-5-1, openai/gpt-5.6-sol)"
required: false
type: string
dataset:
Expand Down Expand Up @@ -98,7 +98,7 @@ on:
required: false
type: string
model_name:
description: "Model to use (e.g., anthropic/claude-opus-5, openai/gpt-5.6-sol)"
description: "Model to use (e.g., anthropic/claude-opus-5-1, openai/gpt-5.6-sol)"
required: false
type: string
mux_run_args:
Expand Down
7 changes: 7 additions & 0 deletions benchmarks/terminal_bench/prepare_leaderboard_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@
"model_org_display_name": "Anthropic",
"folder_name": "Claude-Opus-5",
},
"anthropic/claude-opus-5-1": {
"model_name": "claude-opus-5-1",
"model_provider": "anthropic",
"model_display_name": "Claude Opus 5.1",
"model_org_display_name": "Anthropic",
"folder_name": "Claude-Opus-5.1",
},
# Keep historical GPT metadata alongside the current GPT-5.6 Sol bench target
# so mixed or older artifact sets still map to the canonical leaderboard names.
"openai/gpt-5.2": {
Expand Down
4 changes: 2 additions & 2 deletions docs/config/models.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ 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` | βœ“ |
| Opus 5.1 | anthropic:claude-opus-5-1 | `opus` | βœ“ |
| Sonnet 5 | anthropic:claude-sonnet-5 | `sonnet` | |
| Haiku 4.5 | anthropic:claude-haiku-4-5 | `haiku` | |
| GPT-5.6 Sol | openai:gpt-5.6-sol | `gpt`, `sol` | |
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/github-actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ jobs:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
bunx @coder/xum@next run \
--model anthropic:claude-opus-5 \
--model anthropic:claude-opus-5-1 \
--thinking xhigh \
< .github/prompts/auto-cleanup.md
```
Expand Down
4 changes: 2 additions & 2 deletions src/browser/features/ChatInput/useCreationWorkspace.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,7 @@ describe("useCreationWorkspace", () => {
});
expect(onWorkspaceCreated.mock.calls[0][1]).toEqual({
autoNavigate: true,
pendingStreamModel: "anthropic:claude-opus-5",
pendingStreamModel: "anthropic:claude-opus-5-1",
markPendingInitialSend: false,
});
});
Expand Down Expand Up @@ -1838,7 +1838,7 @@ describe("useCreationWorkspace", () => {
expect(onWorkspaceCreated.mock.calls.length).toBe(1);
expect(onWorkspaceCreated.mock.calls[0][1]).toEqual({
autoNavigate: true,
pendingStreamModel: "anthropic:claude-opus-5",
pendingStreamModel: "anthropic:claude-opus-5-1",
markPendingInitialSend: true,
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/cli/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ describe("xum CLI", () => {
test("shows default model as opus", async () => {
const result = await runCli(["run", "--help"]);
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("anthropic:claude-opus-5");
expect(result.stdout).toContain("anthropic:claude-opus-5-1");
});

test("--service-tier has no default auto", async () => {
Expand Down
13 changes: 13 additions & 0 deletions src/common/config/schemas/appConfigOnDisk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ 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 chain-target migration after the opus alias moved to Opus 5.1:
* chains still exactly equal to the superseded shipped default
* (Fable 5.1 β†’ Opus 5) move to the current default (Fable 5.1 β†’ Opus 5.1);
* any user-edited chain is left untouched.
*/
defaultModelFallbacksSeededOpus51: z.boolean().optional(),
/** One-time migration from the legacy auto-delete default to persistent sub-agents. */
persistentSubagentsDefaulted: z.boolean().optional(),
})
Expand Down
16 changes: 15 additions & 1 deletion src/common/constants/knownModels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
*/

import { describe, test, expect } from "@jest/globals";
import { KNOWN_MODELS, MODEL_ABBREVIATIONS } from "@/common/constants/knownModels";
import {
KNOWN_MODELS,
MODEL_ABBREVIATIONS,
TOKENIZER_MODEL_OVERRIDES,
} from "@/common/constants/knownModels";
import modelsJson from "@/common/utils/tokens/models.json";
import { findMissingKnownModels } from "@/common/utils/tokens/updateModelsData";

Expand All @@ -19,6 +23,16 @@ describe("Known Models Integration", () => {
}
});

test("opus alias tracks Opus 5.1 and retired ids keep tokenizer overrides", () => {
expect(MODEL_ABBREVIATIONS.opus).toBe("anthropic:claude-opus-5-1");
expect(KNOWN_MODELS.OPUS.id).toBe("anthropic:claude-opus-5-1");
// Exact-id lookup for retired-but-documented custom model strings must keep
// resolving to the Opus 4.5 approximation instead of falling back (with a
// warning) to the generic per-provider tokenizer.
expect(TOKENIZER_MODEL_OVERRIDES["anthropic:claude-opus-5"]).toBe("anthropic/claude-opus-4.5");
expect(TOKENIZER_MODEL_OVERRIDES[KNOWN_MODELS.OPUS.id]).toBe("anthropic/claude-opus-4.5");
});

test("gemini-flash resolves to the stable Gemini 3.7 Flash model", () => {
expect(MODEL_ABBREVIATIONS["gemini-flash"]).toBe("google:gemini-3.7-flash");
});
Expand Down
43 changes: 28 additions & 15 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 All @@ -56,15 +56,15 @@ const MODEL_DEFINITIONS = {
// approximate counting; real usage can run ~1.0-1.3x higher.
tokenizerOverride: "anthropic/claude-opus-4.5",
},
// Claude Opus 5 - released July 24, 2026. Successor to Opus 4.8 at the same pricing
// ($5/M input, $25/M output). API id `claude-opus-5`; Opus 4.8 stays usable as the
// custom model string `anthropic:claude-opus-4-8`.
// Claude Opus 5.1 - successor to Opus 5 (released July 24, 2026) at unchanged pricing
// ($5/M input, $25/M output). API id `claude-opus-5-1`; Opus 5 stays usable as the
// custom model string `anthropic:claude-opus-5`.
OPUS: {
provider: "anthropic",
providerModelId: "claude-opus-5",
providerModelId: "claude-opus-5-1",
aliases: ["opus"],
warm: true,
// Opus 5 uses the newer Opus 4.7+ tokenizer (~30% more tokens for the same text),
// Opus 5.1 uses the newer Opus 4.7+ tokenizer (~30% more tokens for the same text),
// which isn't published upstream; reuse Opus 4.5 for approximate counting. Real
// usage can run ~1.0-1.3x higher than this estimate (same situation as FABLE above).
tokenizerOverride: "anthropic/claude-opus-4.5",
Expand Down Expand Up @@ -275,11 +275,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 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-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
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 @@ -10,6 +10,7 @@ describe("formatCompactModelDisplayName", () => {

test("falls back to the full display name when no shorter label is clearer", () => {
expect(formatCompactModelDisplayName("claude-opus-5")).toBe("Opus 5");
expect(formatCompactModelDisplayName("claude-opus-5-1")).toBe("Opus 5.1");
expect(formatCompactModelDisplayName("gpt-5.3-codex-spark")).toBe("Spark 5.3");
});
});
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
41 changes: 39 additions & 2 deletions src/common/utils/ai/modelFallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ 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,
* and the one-shot defaultModelFallbacksSeededOpus51 target migration for the
* Opus 5.1 promotion, see SUPERSEDED_DEFAULT_MODEL_FALLBACKS)
* β€” 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 +29,43 @@ 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 = {
// Target pinned to the literal Opus 5 id (not KNOWN_MODELS.OPUS.id): this
// chain exists for downgraded pre-5.1 builds, whose own shipped default was
// Fable 5 β†’ Opus 5. Pinning keeps the seeded bytes identical no matter which
// build ran the original seed pass.
"anthropic:claude-fable-5": { models: ["anthropic:claude-opus-5"] },
};

/**
* Shipped defaults superseded by the Opus 5.1 promotion, keyed like
* DEFAULT_MODEL_FALLBACKS. Guarded by migrations.defaultModelFallbacksSeededOpus51,
* config load rewrites a chain to the current default exactly once IF the stored
* entry still deep-equals the superseded default (same single-model chain, no
* enabled/triggers customization). Any deviation is user intent and is never
* touched; the legacy `anthropic:claude-fable-5` chain is deliberately not
* migrated so downgraded builds keep their expected bytes on disk.
*/
export const SUPERSEDED_DEFAULT_MODEL_FALLBACKS: ModelFallbacks = {
[KNOWN_MODELS.FABLE.id]: { models: ["anthropic:claude-opus-5"] },
};
// 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.values(Object.freeze(SUPERSEDED_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 @@ -143,9 +143,15 @@ describe("Anthropic 1M context classification", () => {
expect(hasNative1MContext("anthropic:claude-sonnet-4-5")).toBe(false);
});

it("treats Opus 4.6 to 4.8, Opus 5, and Sonnet 4.6 as native 1M models", () => {
it("treats Opus 4.6 to 4.8, Opus 5, Opus 5.1, and Sonnet 4.6 as native 1M models", () => {
expect(getAnthropic1MContextMode("anthropic:claude-opus-5")).toBe("native");
expect(getAnthropic1MContextMode("anthropic:claude-opus-5-20260724")).toBe("native");
// The claude-opus-5 pattern only tolerates date suffixes, so the 5.1 id
// needs (and has) its own pattern.
expect(getAnthropic1MContextMode("anthropic:claude-opus-5-1")).toBe("native");
expect(getAnthropic1MContextMode("mux-gateway:anthropic/claude-opus-5-1")).toBe("native");
expect(hasNative1MContext("anthropic:claude-opus-5-1")).toBe(true);
expect(supports1MContext("anthropic:claude-opus-5-1")).toBe(false);
expect(getAnthropic1MContextMode("anthropic:claude-opus-4-8")).toBe("native");
expect(getAnthropic1MContextMode("anthropic:claude-opus-4-8-20260528")).toBe("native");
expect(getAnthropic1MContextMode("anthropic:claude-opus-4-6")).toBe("native");
Expand All @@ -167,12 +173,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
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,8 +179,10 @@ 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-5-1${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"),
Expand Down
Loading
Loading