feat(providers): add Claude Sonnet 5

This commit is contained in:
Mohamed Boudra
2026-07-01 19:47:27 +02:00
parent e32c50f2d7
commit ab7543ae1d
4 changed files with 34 additions and 12 deletions
+6 -1
View File
@@ -60,6 +60,11 @@ const EXPECTED_CLAUDE_MODELS = [
model: "Opus 4.8",
descriptionFragment: "Latest release",
},
{
id: "claude-sonnet-5",
model: "Sonnet 5",
descriptionFragment: "Efficient for routine tasks",
},
{
id: "claude-opus-4-7[1m]",
model: "Opus 4.7 1M",
@@ -392,7 +397,7 @@ try {
"--quiet should print the current Claude catalog IDs",
);
assert(
claudeModelsFromJson.some((m) => m.id === "claude-sonnet-4-6"),
claudeModelsFromJson.some((m) => m.id === "claude-sonnet-5"),
"captured --json output should include the current Claude everyday model id",
);
console.log("✓ provider models --quiet outputs model IDs only\n");
@@ -416,6 +416,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
"claude-fable-5",
"claude-opus-4-8[1m]",
"claude-opus-4-8",
"claude-sonnet-5",
"claude-opus-4-7[1m]",
"claude-opus-4-7",
"claude-opus-4-6[1m]",
@@ -457,6 +458,8 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
expect(getThinkingIds("claude-fable-5")).toContain("ultracode");
expect(getThinkingIds("claude-opus-4-8[1m]")).toContain("ultracode");
expect(getThinkingIds("claude-opus-4-8")).toContain("ultracode");
expect(getThinkingIds("claude-sonnet-5")).toContain("xhigh");
expect(getThinkingIds("claude-sonnet-5")).not.toContain("ultracode");
expect(getThinkingIds("claude-opus-4-7")).not.toContain("ultracode");
expect(getThinkingIds("claude-sonnet-4-6")).not.toContain("ultracode");
} finally {
@@ -38,6 +38,7 @@ describe("getClaudeModels", () => {
"claude-fable-5",
"claude-opus-4-8[1m]",
"claude-opus-4-8",
"claude-sonnet-5",
"claude-opus-4-7[1m]",
"claude-opus-4-7",
"claude-opus-4-6[1m]",
@@ -203,12 +204,14 @@ describe("normalizeClaudeRuntimeModelId", () => {
expect(normalizeClaudeRuntimeModelId("claude-fable-5")).toBe("claude-fable-5");
expect(normalizeClaudeRuntimeModelId("claude-opus-4-6")).toBe("claude-opus-4-6");
expect(normalizeClaudeRuntimeModelId("claude-opus-4-6[1m]")).toBe("claude-opus-4-6[1m]");
expect(normalizeClaudeRuntimeModelId("claude-sonnet-5")).toBe("claude-sonnet-5");
expect(normalizeClaudeRuntimeModelId("claude-sonnet-4-6")).toBe("claude-sonnet-4-6");
expect(normalizeClaudeRuntimeModelId("claude-haiku-4-5")).toBe("claude-haiku-4-5");
});
it("normalizes dated model IDs to base model", () => {
expect(normalizeClaudeRuntimeModelId("claude-fable-5-20260301")).toBe("claude-fable-5");
expect(normalizeClaudeRuntimeModelId("claude-sonnet-5-20260101")).toBe("claude-sonnet-5");
expect(normalizeClaudeRuntimeModelId("claude-opus-4-6-20260101")).toBe("claude-opus-4-6");
expect(normalizeClaudeRuntimeModelId("claude-sonnet-4-6-20260101")).toBe("claude-sonnet-4-6");
expect(normalizeClaudeRuntimeModelId("claude-haiku-4-5-20251001")).toBe("claude-haiku-4-5");
@@ -216,6 +219,7 @@ describe("normalizeClaudeRuntimeModelId", () => {
it("preserves [1m] suffix from runtime model strings", () => {
expect(normalizeClaudeRuntimeModelId("claude-opus-4-6[1m]")).toBe("claude-opus-4-6[1m]");
expect(normalizeClaudeRuntimeModelId("claude-sonnet-5[1m]")).toBe("claude-sonnet-5");
});
it("returns null for empty/null/undefined", () => {
@@ -12,7 +12,7 @@ const CLAUDE_THINKING_OPTIONS = [
{ id: "max", label: "Max" },
] as const;
const CLAUDE_OPUS_EXTENDED_THINKING_OPTIONS = [
const CLAUDE_EXTENDED_THINKING_OPTIONS = [
{ id: "low", label: "Low" },
{ id: "medium", label: "Medium" },
{ id: "high", label: "High" },
@@ -21,7 +21,7 @@ const CLAUDE_OPUS_EXTENDED_THINKING_OPTIONS = [
] as const;
const CLAUDE_ULTRACODE_THINKING_OPTIONS = [
...CLAUDE_OPUS_EXTENDED_THINKING_OPTIONS,
...CLAUDE_EXTENDED_THINKING_OPTIONS,
{ id: "ultracode", label: "Ultracode" },
] as const;
@@ -48,19 +48,26 @@ const CLAUDE_MODELS: AgentModelDefinition[] = [
isDefault: true,
thinkingOptions: [...CLAUDE_ULTRACODE_THINKING_OPTIONS],
},
{
provider: "claude",
id: "claude-sonnet-5",
label: "Sonnet 5",
description: "Sonnet 5 · Efficient for routine tasks",
thinkingOptions: [...CLAUDE_EXTENDED_THINKING_OPTIONS],
},
{
provider: "claude",
id: "claude-opus-4-7[1m]",
label: "Opus 4.7 1M",
description: "Opus 4.7 with 1M context window",
thinkingOptions: [...CLAUDE_OPUS_EXTENDED_THINKING_OPTIONS],
thinkingOptions: [...CLAUDE_EXTENDED_THINKING_OPTIONS],
},
{
provider: "claude",
id: "claude-opus-4-7",
label: "Opus 4.7",
description: "Opus 4.7 · Previous release",
thinkingOptions: [...CLAUDE_OPUS_EXTENDED_THINKING_OPTIONS],
thinkingOptions: [...CLAUDE_EXTENDED_THINKING_OPTIONS],
},
{
provider: "claude",
@@ -218,13 +225,16 @@ export function normalizeClaudeRuntimeModelId(value: string | null | undefined):
return trimmed;
}
// Fable uses a single-segment version (claude-fable-5), not the {major}-{minor}
// scheme of opus/sonnet/haiku, so match it separately. This maps dated runtime
// strings (e.g. claude-fable-5-20260301) back to the catalog ID. No [1m] variant:
// Fable 5 is natively 1M, so there is no 200K-default model to opt into 1M.
const fableMatch = trimmed.match(/(?:claude-)?fable[-_ ]+(\d+)/i);
if (fableMatch) {
return `claude-fable-${fableMatch[1]}`;
// Fable and Sonnet 5 use single-segment versions, not the {major}-{minor}
// scheme of older opus/sonnet/haiku models. They are natively 1M, so map
// dated or suffixed runtime strings back to the one catalog ID.
const singleSegmentMatch = trimmed.match(/(?:claude-)?(fable|sonnet)[-_ ]+(\d+)/i);
if (singleSegmentMatch) {
const family = singleSegmentMatch[1].toLowerCase();
const major = singleSegmentMatch[2];
if (family === "fable" || (family === "sonnet" && major === "5")) {
return `claude-${family}-${major}`;
}
}
// Match: claude-{family}-{major}-{minor}[1m]? possibly followed by a date suffix