mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-10 02:15:02 +00:00
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.
This commit is contained in:
@@ -62,10 +62,15 @@ const EXPECTED_CLAUDE_MODELS = [
|
||||
model: "Opus 5",
|
||||
descriptionFragment: "Previous release",
|
||||
},
|
||||
{
|
||||
id: "claude-fable-5-2",
|
||||
model: "Fable 5.2",
|
||||
descriptionFragment: "Most powerful",
|
||||
},
|
||||
{
|
||||
id: "claude-fable-5-1",
|
||||
model: "Fable 5.1",
|
||||
descriptionFragment: "Most powerful",
|
||||
descriptionFragment: "Previous release",
|
||||
},
|
||||
{
|
||||
id: "claude-fable-5",
|
||||
@@ -199,11 +204,10 @@ function assertClaudeModels(data: ProviderModel[]): void {
|
||||
);
|
||||
}
|
||||
|
||||
const fable51Index = data.findIndex((model) => model.id === "claude-fable-5-1");
|
||||
const fable5Index = data.findIndex((model) => model.id === "claude-fable-5");
|
||||
assert.strictEqual(
|
||||
fable5Index,
|
||||
fable51Index + 1,
|
||||
const fable52Index = data.findIndex((model) => model.id === "claude-fable-5-2");
|
||||
assert.deepStrictEqual(
|
||||
data.slice(fable52Index, fable52Index + 3).map((model) => model.id),
|
||||
["claude-fable-5-2", "claude-fable-5-1", "claude-fable-5"],
|
||||
"Fable models should stay adjacent and newest-first",
|
||||
);
|
||||
assert(
|
||||
|
||||
@@ -1614,6 +1614,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),
|
||||
);
|
||||
});
|
||||
@@ -785,6 +828,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()
|
||||
@@ -920,7 +1001,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",
|
||||
@@ -965,6 +1046,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;
|
||||
|
||||
Reference in New Issue
Block a user