chore(release): sync Sonnet 5 stable release to main

This commit is contained in:
Mohamed Boudra
2026-07-01 22:36:37 +02:00
parent d3e8f77914
commit f41dde8c72
22 changed files with 170 additions and 74 deletions
@@ -79,6 +79,7 @@ export interface AgentModelDefinition {
description?: string;
isDefault?: boolean;
metadata?: AgentMetadata;
contextWindowMaxTokens?: number;
thinkingOptions?: AgentSelectOption[];
defaultThinkingOptionId?: string;
}
@@ -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 {
@@ -1889,6 +1892,30 @@ describe("ClaudeAgentSession context window usage", () => {
}
});
test("native 1M Claude models seed active context window usage from the catalog", async () => {
const session = await createSessionForTurns(
[[createInitMessage(), createMessageStartEvent(), createSuccessResult()]],
{ model: "claude-sonnet-5" },
);
try {
const events = await collectStreamEvents(session);
expect(events).toContainEqual(
expect.objectContaining({
type: "usage_updated",
provider: "claude",
usage: {
contextWindowMaxTokens: 1_000_000,
contextWindowUsedTokens: 150,
},
}),
);
} finally {
await session.close();
}
});
test("message_delta stream events update per-request usage", async () => {
const session = await createSessionForTurns([
[
@@ -29,7 +29,11 @@ import {
mapTaskNotificationSystemRecordToToolCall,
mapTaskNotificationUserContentToToolCall,
} from "./task-notification-tool-call.js";
import { getClaudeModelsWithSettings, normalizeClaudeRuntimeModelId } from "./models.js";
import {
findClaudeModel,
getClaudeModelsWithSettings,
normalizeClaudeRuntimeModelId,
} from "./models.js";
import { parsePartialJsonObject } from "./partial-json.js";
import { ClaudeSidechainTracker } from "./sidechain-tracker.js";
import { buildClaudeFeatures, claudeModelSupportsFastMode } from "./feature-definitions.js";
@@ -1626,23 +1630,6 @@ function extractContextWindowSize(modelUsage: unknown): number | undefined {
return maxContextWindow;
}
function resolveInitialContextWindowSize(modelId: string | null | undefined): number | undefined {
const normalized = typeof modelId === "string" ? modelId.trim().toLowerCase() : "";
if (!normalized) {
return undefined;
}
if (normalized.includes("[1m]") || normalized.includes("context-1m")) {
return 1_000_000;
}
if (normalized.includes("claude-fable-5")) {
return 1_000_000;
}
if (/(?:^|[~/_-])(?:claude[-_ ]*)?(opus|sonnet|haiku)(?:$|[-_ ./])/.test(normalized)) {
return 200_000;
}
return undefined;
}
function readStreamRequestInputTokens(event: Record<string, unknown>): number | undefined {
const messageUsage = toObjectRecord(toObjectRecord(event.message)?.usage);
if (!messageUsage) {
@@ -1941,7 +1928,7 @@ class ClaudeAgentSession implements AgentSession {
this.queryFactory = options.queryFactory;
this.resolveBinary = options.resolveBinary;
this.contextUsage = new ClaudeContextUsageState(
resolveInitialContextWindowSize(this.config.model),
findClaudeModel(this.config.model)?.contextWindowMaxTokens,
);
const handle = options.handle;
@@ -2187,7 +2174,7 @@ class ClaudeAgentSession implements AgentSession {
await this.applyFastModeFeature(false, activeQuery);
}
this.contextUsage.setInitialContextWindowMaxTokens(
resolveInitialContextWindowSize(this.config.model),
findClaudeModel(this.config.model)?.contextWindowMaxTokens,
);
this.lastOptionsModel = normalizedModelId ?? this.lastOptionsModel;
this.lastRuntimeModel = null;
@@ -5,7 +5,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { createTestLogger } from "../../../../test-utils/test-logger.js";
import { ClaudeAgentClient } from "./agent.js";
import { getClaudeModels, normalizeClaudeRuntimeModelId } from "./models.js";
import { findClaudeModel, getClaudeModels, normalizeClaudeRuntimeModelId } from "./models.js";
const createdClaudeConfigDirs: string[] = [];
@@ -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]",
@@ -55,6 +56,28 @@ describe("getClaudeModels", () => {
expect(defaults[0].id).toBe("claude-opus-4-8");
});
it("defines context window sizes in the catalog", () => {
const contextWindows = new Map(
getClaudeModels().map((model) => [model.id, model.contextWindowMaxTokens]),
);
expect(contextWindows).toEqual(
new Map([
["claude-fable-5", 1_000_000],
["claude-opus-4-8[1m]", 1_000_000],
["claude-opus-4-8", 200_000],
["claude-sonnet-5", 1_000_000],
["claude-opus-4-7[1m]", 1_000_000],
["claude-opus-4-7", 200_000],
["claude-opus-4-6[1m]", 1_000_000],
["claude-opus-4-6", 200_000],
["claude-sonnet-4-6[1m]", 1_000_000],
["claude-sonnet-4-6", 200_000],
["claude-haiku-4-5", 200_000],
]),
);
});
it("returns fresh copies each call", () => {
const a = getClaudeModels();
const b = getClaudeModels();
@@ -201,6 +224,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
describe("normalizeClaudeRuntimeModelId", () => {
it("returns exact match for known model IDs", () => {
expect(normalizeClaudeRuntimeModelId("claude-fable-5")).toBe("claude-fable-5");
expect(normalizeClaudeRuntimeModelId("claude-sonnet-5")).toBe("claude-sonnet-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-4-6")).toBe("claude-sonnet-4-6");
@@ -209,6 +233,7 @@ describe("normalizeClaudeRuntimeModelId", () => {
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");
@@ -230,3 +255,10 @@ describe("normalizeClaudeRuntimeModelId", () => {
expect(normalizeClaudeRuntimeModelId("random")).toBeNull();
});
});
describe("findClaudeModel", () => {
it("resolves runtime model IDs to catalog entries", () => {
expect(findClaudeModel("claude-sonnet-5-20260101")?.id).toBe("claude-sonnet-5");
expect(findClaudeModel("claude-sonnet-5[1m]")?.contextWindowMaxTokens).toBe(1_000_000);
});
});
@@ -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;
@@ -31,6 +31,7 @@ const CLAUDE_MODELS: AgentModelDefinition[] = [
id: "claude-fable-5",
label: "Fable 5",
description: "Fable 5 · Most powerful model",
contextWindowMaxTokens: 1_000_000,
thinkingOptions: [...CLAUDE_ULTRACODE_THINKING_OPTIONS],
},
{
@@ -38,6 +39,7 @@ const CLAUDE_MODELS: AgentModelDefinition[] = [
id: "claude-opus-4-8[1m]",
label: "Opus 4.8 1M",
description: "Opus 4.8 with 1M context window",
contextWindowMaxTokens: 1_000_000,
thinkingOptions: [...CLAUDE_ULTRACODE_THINKING_OPTIONS],
},
{
@@ -46,27 +48,39 @@ const CLAUDE_MODELS: AgentModelDefinition[] = [
label: "Opus 4.8",
description: "Opus 4.8 · Latest release",
isDefault: true,
contextWindowMaxTokens: 200_000,
thinkingOptions: [...CLAUDE_ULTRACODE_THINKING_OPTIONS],
},
{
provider: "claude",
id: "claude-sonnet-5",
label: "Sonnet 5",
description: "Sonnet 5 · Best for everyday tasks",
contextWindowMaxTokens: 1_000_000,
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],
contextWindowMaxTokens: 1_000_000,
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],
contextWindowMaxTokens: 200_000,
thinkingOptions: [...CLAUDE_EXTENDED_THINKING_OPTIONS],
},
{
provider: "claude",
id: "claude-opus-4-6[1m]",
label: "Opus 4.6 1M",
description: "Opus 4.6 with 1M context window",
contextWindowMaxTokens: 1_000_000,
thinkingOptions: [...CLAUDE_THINKING_OPTIONS],
},
{
@@ -74,6 +88,7 @@ const CLAUDE_MODELS: AgentModelDefinition[] = [
id: "claude-opus-4-6",
label: "Opus 4.6",
description: "Opus 4.6 · Most capable for complex work",
contextWindowMaxTokens: 200_000,
thinkingOptions: [...CLAUDE_THINKING_OPTIONS],
},
{
@@ -81,6 +96,7 @@ const CLAUDE_MODELS: AgentModelDefinition[] = [
id: "claude-sonnet-4-6[1m]",
label: "Sonnet 4.6 1M",
description: "Sonnet 4.6 with 1M context window",
contextWindowMaxTokens: 1_000_000,
thinkingOptions: [...CLAUDE_THINKING_OPTIONS],
},
{
@@ -88,6 +104,7 @@ const CLAUDE_MODELS: AgentModelDefinition[] = [
id: "claude-sonnet-4-6",
label: "Sonnet 4.6",
description: "Sonnet 4.6 · Best for everyday tasks",
contextWindowMaxTokens: 200_000,
thinkingOptions: [...CLAUDE_THINKING_OPTIONS],
},
{
@@ -95,6 +112,7 @@ const CLAUDE_MODELS: AgentModelDefinition[] = [
id: "claude-haiku-4-5",
label: "Haiku 4.5",
description: "Haiku 4.5 · Fastest for quick answers",
contextWindowMaxTokens: 200_000,
},
];
@@ -110,6 +128,16 @@ export function getClaudeModels(): AgentModelDefinition[] {
return CLAUDE_MODELS.map((model) => ({ ...model }));
}
export function findClaudeModel(
modelId: string | null | undefined,
): AgentModelDefinition | undefined {
const normalizedModelId = normalizeClaudeRuntimeModelId(modelId);
if (!normalizedModelId) {
return undefined;
}
return CLAUDE_MODELS.find((model) => model.id === normalizedModelId);
}
export async function getClaudeModelsWithSettings(
logger: Logger,
configDir?: string,
@@ -218,13 +246,19 @@ 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]}`;
// Match single-segment major-version model names such as claude-fable-5
// and claude-sonnet-5, including dated runtime strings from the SDK.
const singleSegmentMatch = trimmed.match(/(?:claude-)?(fable|opus|sonnet|haiku)[-_ ]+(\d+)/i);
if (singleSegmentMatch) {
const family = singleSegmentMatch[1].toLowerCase();
const major = singleSegmentMatch[2];
const suffix = trimmed.toLowerCase().includes("[1m]") ? "[1m]" : "";
const candidates = [`claude-${family}-${major}${suffix}`, `claude-${family}-${major}`];
for (const candidate of candidates) {
if (CLAUDE_MODELS.some((model) => model.id === candidate)) {
return candidate;
}
}
}
// Match: claude-{family}-{major}-{minor}[1m]? possibly followed by a date suffix