feat(claude): add Fable 5.2 and Mythos 5.2 to the model catalog

Claude Code 2.1.321 ships Fable 5.2 as the default Fable model and names
Mythos 5.2 as its current Mythos model. Without manifest entries Paseo
listed neither, and normalized a runtime claude-fable-5-2 to
claude-fable-5, so a session on Fable 5.2 was recorded and labeled as
Fable 5.

Fable 5.2 is gated on Claude Code 2.1.321. Mythos 5.2 stays behind
discovery like Mythos 5.1. Both get a 1M context window, always-on
thinking with high default effort, and fast mode, matching what Claude
Code advertises. Fable 5.1 becomes the previous release.

(cherry picked from commit a79b0fa3d8)
This commit is contained in:
gengjiawen
2026-11-10 01:53:49 +01:00
committed by Mohamed Boudra
parent d91ae138cb
commit 23e954099b
4 changed files with 147 additions and 13 deletions
@@ -1364,6 +1364,7 @@ describe("ClaudeAgentSession features", () => {
test.each([
["supported model", "claude-opus-4-8", { type: "disabled" }, undefined],
["Mythos 5.1", "claude-mythos-5-1", { type: "adaptive", display: "summarized" }, "high"],
["Fable 5.2", "claude-fable-5-2", { type: "adaptive", display: "summarized" }, "high"],
["Haiku 5.5", "claude-haiku-5-5", { type: "disabled" }, undefined],
["unsupported model", "claude-fable-5", { type: "adaptive", display: "summarized" }, "high"],
["custom model", "openrouter/anthropic/claude-opus-4-8", undefined, undefined],
@@ -58,6 +58,15 @@ export const CLAUDE_MODEL_MANIFEST = [
supportsThinkingDisabled: true,
supportsFastMode: true,
},
{
id: "claude-mythos-5-2",
label: "Mythos 5.2",
requiresDiscovery: true,
description: "Mythos 5.2 · Requires verified access",
contextWindowMaxTokens: 1_000_000,
effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
supportsFastMode: true,
},
{
id: "claude-mythos-5-1",
label: "Mythos 5.1",
@@ -66,10 +75,19 @@ export const CLAUDE_MODEL_MANIFEST = [
contextWindowMaxTokens: 1_000_000,
effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
},
{
id: "claude-fable-5-2",
label: "Fable 5.2",
description: "Fable 5.2 · Most powerful model",
minimumClaudeCodeVersion: "2.1.321",
contextWindowMaxTokens: 1_000_000,
effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
supportsFastMode: true,
},
{
id: "claude-fable-5-1",
label: "Fable 5.1",
description: "Fable 5.1 · Most powerful model",
description: "Fable 5.1 · Previous release",
contextWindowMaxTokens: 1_000_000,
effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
},
@@ -61,17 +61,23 @@ async function createClaudeConfigDirWithRawSettings(settings: string): Promise<s
return configDir;
}
function createCatalogClient(claudeCodeVersion = "2.1.293"): ClaudeAgentClient {
function createCatalogClient(claudeCodeVersion = "2.1.321"): ClaudeAgentClient {
return new ClaudeAgentClient({
logger: createTestLogger(),
resolveVersion: async () => claudeCodeVersion,
discoverModels: async () => [
{
value: "mythos",
resolvedModel: "claude-mythos-5-1",
resolvedModel: "claude-mythos-5-2",
displayName: "Mythos",
description: "",
},
{
value: "claude-mythos-5-1",
resolvedModel: "claude-mythos-5-1",
displayName: "Mythos 5.1",
description: "",
},
],
});
}
@@ -82,7 +88,9 @@ describe("getClaudeModels", () => {
expect(models.map((m) => m.id)).toEqual([
"claude-opus-5-5",
"claude-opus-5",
"claude-mythos-5-2",
"claude-mythos-5-1",
"claude-fable-5-2",
"claude-fable-5-1",
"claude-fable-5",
"claude-fable-5[1m]",
@@ -118,7 +126,9 @@ describe("getClaudeModels", () => {
new Map([
["claude-opus-5-5", 1_000_000],
["claude-opus-5", 1_000_000],
["claude-mythos-5-2", 1_000_000],
["claude-mythos-5-1", 1_000_000],
["claude-fable-5-2", 1_000_000],
["claude-fable-5-1", 1_000_000],
["claude-fable-5", 1_000_000],
["claude-fable-5[1m]", 1_000_000],
@@ -163,6 +173,12 @@ describe("getClaudeModels", () => {
expect(getClaudeModels("2.1.293").find((model) => model.isDefault)?.id).toBe("claude-opus-5-5");
});
it("gates Fable 5.2 on Claude Code 2.1.321 without changing the default", () => {
expect(getClaudeModels("2.1.320").map((model) => model.id)).not.toContain("claude-fable-5-2");
expect(getClaudeModels("2.1.321").map((model) => model.id)).toContain("claude-fable-5-2");
expect(getClaudeModels("2.1.321").find((model) => model.isDefault)?.id).toBe("claude-opus-5-5");
});
it("derives thinking options from model effort capabilities", () => {
const models = new Map(getClaudeModels().map((model) => [model.id, model]));
@@ -232,6 +248,8 @@ describe("getClaudeModels", () => {
["claude-sonnet-5-20260101", true, "high"],
["claude-fable-5", false, "high"],
["claude-fable-5-1", false, "high"],
["claude-fable-5-2", false, "high"],
["claude-mythos-5-2", false, "high"],
["claude-haiku-5-5", true, "medium"],
["claude-haiku-5-5-20261007", true, "medium"],
["claude-haiku-4-5", false, undefined],
@@ -284,6 +302,31 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
},
);
it("offers Mythos 5.2 in place of Mythos 5.1 when Claude Code resolves mythos to 5.2", async () => {
const configDir = await createClaudeConfigDir({});
const client = new ClaudeAgentClient({
logger: createTestLogger(),
resolveVersion: async () => "2.1.321",
runtimeSettings: { env: { CLAUDE_CONFIG_DIR: configDir } },
discoverModels: async () => [
{
value: "mythos",
resolvedModel: "claude-mythos-5-2",
displayName: "Mythos",
description: "",
},
],
});
const { models } = await client.fetchCatalog({
scope: "workspace",
cwd: configDir,
force: false,
});
expect(models.filter((model) => model.id.startsWith("claude-mythos-"))).toEqual([
findClaudeModel("claude-mythos-5-2"),
]);
});
it("preserves the curated catalog and explicitly configured models after discovery failure", async () => {
const configDir = await createClaudeConfigDir({ model: "claude-mythos-5-1" });
const client = new ClaudeAgentClient({
@@ -299,8 +342,8 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
cwd: configDir,
force: false,
});
expect(models.filter((model) => model.id !== "claude-mythos-5-1")).toEqual(
getClaudeModels("2.1.293").filter((model) => model.id !== "claude-mythos-5-1"),
expect(models.filter((model) => !model.id.startsWith("claude-mythos-"))).toEqual(
getClaudeModels("2.1.293").filter((model) => !model.id.startsWith("claude-mythos-")),
);
expect(models.find((model) => model.id === "claude-mythos-5-1")).toEqual({
...findClaudeModel("claude-mythos-5-1"),
@@ -360,7 +403,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
});
expect(models.map((model) => model.id)).toEqual(
getClaudeModels("2.1.293")
.filter((model) => model.id !== "claude-mythos-5-1")
.filter((model) => !model.id.startsWith("claude-mythos-"))
.map((model) => model.id),
);
});
@@ -764,6 +807,44 @@ describe("Claude Fable 5.1 catalog", () => {
});
});
describe("Claude Fable 5.2 catalog", () => {
it("offers one 1M model with high default effort, always-on thinking, and fast mode", () => {
const models = getClaudeModels().filter((model) => model.id.startsWith("claude-fable-5-2"));
expect(models).toHaveLength(1);
expect(models[0]).toMatchObject({
id: "claude-fable-5-2",
label: "Fable 5.2",
contextWindowMaxTokens: 1_000_000,
defaultThinkingOptionId: "high",
});
expect(models[0].thinkingOptions?.map((option) => option.id)).toEqual([
"low",
"medium",
"high",
"xhigh",
"max",
CLAUDE_ULTRACODE_THINKING_OPTION_ID,
]);
expect(claudeManifestModelSupportsFastMode("claude-fable-5-2")).toBe(true);
});
it.each([
"claude-fable-5-2[1m]",
"claude-fable-5-2-20261110",
"claude-fable-5-2-20261110[1m]",
"Fable 5.2",
])("resolves %s to the single catalog entry", (id) => {
expect(findClaudeModel(id)?.id).toBe("claude-fable-5-2");
});
it.each(["claude-fable-5-2", "anthropic/claude-fable-5-2", "us.anthropic.claude-fable-5-2-v1:0"])(
"keeps runtime model %s from collapsing onto Fable 5",
(id) => {
expect(normalizeClaudeRuntimeModelId(id)).toBe("claude-fable-5-2");
},
);
});
describe("Claude Opus 5.5 catalog", () => {
it("offers one Opus 5.5 entry with a 1M context window", () => {
const opus55Models = getClaudeModels()
@@ -899,7 +980,7 @@ describe("claudeManifestModelSupportsFastMode", () => {
describe("Claude Mythos 5.1 catalog", () => {
it("offers one 1M model with high default effort and always-on thinking", () => {
const models = getClaudeModels().filter((model) => model.id.startsWith("claude-mythos"));
const models = getClaudeModels().filter((model) => model.id.startsWith("claude-mythos-5-1"));
expect(models).toHaveLength(1);
expect(models[0]).toMatchObject({
id: "claude-mythos-5-1",
@@ -944,6 +1025,36 @@ describe("Claude Mythos 5.1 catalog", () => {
);
});
describe("Claude Mythos 5.2 catalog", () => {
it("offers one 1M model with high default effort, always-on thinking, and fast mode", () => {
const models = getClaudeModels().filter((model) => model.id.startsWith("claude-mythos-5-2"));
expect(models).toHaveLength(1);
expect(models[0]).toMatchObject({
id: "claude-mythos-5-2",
label: "Mythos 5.2",
contextWindowMaxTokens: 1_000_000,
defaultThinkingOptionId: "high",
});
expect(models[0].thinkingOptions?.map((option) => option.id)).toEqual([
"low",
"medium",
"high",
"xhigh",
"max",
CLAUDE_ULTRACODE_THINKING_OPTION_ID,
]);
expect(claudeManifestModelSupportsFastMode("claude-mythos-5-2")).toBe(true);
});
it.each(["anthropic/claude-mythos-5-2", "us.anthropic.claude-mythos-5-2-v1:0"])(
"normalizes runtime identity without granting first-party capabilities: %s",
(id) => {
expect(normalizeClaudeRuntimeModelId(id)).toBe("claude-mythos-5-2");
expect(normalizeClaudeManifestModelId(id)).toBeNull();
},
);
});
describe("Claude model discovery process", () => {
it("does not send a prompt or persist a session and closes after discovery", async () => {
let closed = false;