diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 87e9f22c..948bcb32 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -5,10 +5,12 @@ ### Added - Added OpenAI Codex subscription device-code login as a selectable headless alternative while keeping browser login as the default. +- Added Claude Opus 4.8 model metadata for Anthropic and updated Opus adaptive-thinking coverage to use it. ### Fixed - Fixed Anthropic-compatible replay for providers that return empty thinking signatures by adding an opt-in `allowEmptySignature` compatibility flag ([#4464](https://github.com/earendil-works/pi/issues/4464)). +- Fixed OpenAI and OpenRouter GPT-5.5 Pro thinking level metadata to expose only supported medium, high, and xhigh efforts. ## [0.76.0] - 2026-05-27 diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 791dff0f..927401f7 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -184,6 +184,8 @@ function isAnthropicAdaptiveThinkingModel(modelId: string): boolean { modelId.includes("opus-4.6") || modelId.includes("opus-4-7") || modelId.includes("opus-4.7") || + modelId.includes("opus-4-8") || + modelId.includes("opus-4.8") || modelId.includes("sonnet-4-6") || modelId.includes("sonnet-4.6") ); @@ -225,10 +227,18 @@ function applyThinkingLevelMetadata(model: Model): void { if (supportsOpenAiXhigh(model.id)) { mergeThinkingLevelMap(model, { xhigh: "xhigh" }); } + if (model.id.endsWith("gpt-5.5-pro")) { + mergeThinkingLevelMap(model, { off: null, minimal: null, low: null }); + } if (model.id.includes("opus-4-6") || model.id.includes("opus-4.6")) { mergeThinkingLevelMap(model, { xhigh: "max" }); } - if (model.id.includes("opus-4-7") || model.id.includes("opus-4.7")) { + if ( + model.id.includes("opus-4-7") || + model.id.includes("opus-4.7") || + model.id.includes("opus-4-8") || + model.id.includes("opus-4.8") + ) { mergeThinkingLevelMap(model, { xhigh: "xhigh" }); } if (model.api === "anthropic-messages" && isAnthropicAdaptiveThinkingModel(model.id)) { @@ -1273,6 +1283,27 @@ async function generateModels() { }); } + // Add missing Claude Opus 4.8 + if (!allModels.some(m => m.provider === "anthropic" && m.id === "claude-opus-4-8")) { + allModels.push({ + id: "claude-opus-4-8", + name: "Claude Opus 4.8", + api: "anthropic-messages", + baseUrl: "https://api.anthropic.com", + provider: "anthropic", + reasoning: true, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + }); + } + // Add missing Claude Sonnet 4.6 if (!allModels.some(m => m.provider === "anthropic" && m.id === "claude-sonnet-4-6")) { allModels.push({ diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index e881ae0e..1996cb02 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -160,6 +160,24 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "anthropic.claude-opus-4-8": { + id: "anthropic.claude-opus-4-8", + name: "Claude Opus 4.8", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "anthropic.claude-sonnet-4-5-20250929-v1:0": { id: "anthropic.claude-sonnet-4-5-20250929-v1:0", name: "Claude Sonnet 4.5", @@ -229,6 +247,24 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "au.anthropic.claude-opus-4-8": { + id: "au.anthropic.claude-opus-4-8", + name: "Claude Opus 4.8 (AU)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "au.anthropic.claude-sonnet-4-5-20250929-v1:0": { id: "au.anthropic.claude-sonnet-4-5-20250929-v1:0", name: "Claude Sonnet 4.5 (AU)", @@ -384,6 +420,24 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "eu.anthropic.claude-opus-4-8": { + id: "eu.anthropic.claude-opus-4-8", + name: "Claude Opus 4.8 (EU)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.eu-central-1.amazonaws.com", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "eu.anthropic.claude-sonnet-4-5-20250929-v1:0": { id: "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", name: "Claude Sonnet 4.5 (EU)", @@ -488,6 +542,24 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "global.anthropic.claude-opus-4-8": { + id: "global.anthropic.claude-opus-4-8", + name: "Claude Opus 4.8 (Global)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "global.anthropic.claude-sonnet-4-5-20250929-v1:0": { id: "global.anthropic.claude-sonnet-4-5-20250929-v1:0", name: "Claude Sonnet 4.5 (Global)", @@ -574,6 +646,24 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "jp.anthropic.claude-opus-4-8": { + id: "jp.anthropic.claude-opus-4-8", + name: "Claude Opus 4.8 (JP)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "jp.anthropic.claude-sonnet-4-5-20250929-v1:0": { id: "jp.anthropic.claude-sonnet-4-5-20250929-v1:0", name: "Claude Sonnet 4.5 (JP)", @@ -1273,6 +1363,24 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "us.anthropic.claude-opus-4-8": { + id: "us.anthropic.claude-opus-4-8", + name: "Claude Opus 4.8 (US)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "us.anthropic.claude-sonnet-4-5-20250929-v1:0": { id: "us.anthropic.claude-sonnet-4-5-20250929-v1:0", name: "Claude Sonnet 4.5 (US)", @@ -1755,6 +1863,25 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"anthropic-messages">, + "claude-opus-4-8": { + id: "claude-opus-4-8", + name: "Claude Opus 4.8", + api: "anthropic-messages", + provider: "anthropic", + baseUrl: "https://api.anthropic.com", + compat: {"forceAdaptiveThinking":true}, + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, "claude-sonnet-4-0": { id: "claude-sonnet-4-0", name: "Claude Sonnet 4 (latest)", @@ -2434,7 +2561,7 @@ export const MODELS = { provider: "azure-openai-responses", baseUrl: "", reasoning: true, - thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, + thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"low":null}, input: ["text", "image"], cost: { input: 30, @@ -6760,7 +6887,7 @@ export const MODELS = { provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, - thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, + thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"low":null}, input: ["text", "image"], cost: { input: 30, @@ -7125,6 +7252,25 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"anthropic-messages">, + "claude-opus-4-8": { + id: "claude-opus-4-8", + name: "Claude Opus 4.8", + api: "anthropic-messages", + provider: "opencode", + baseUrl: "https://opencode.ai/zen", + compat: {"forceAdaptiveThinking":true}, + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, "claude-sonnet-4": { id: "claude-sonnet-4", name: "Claude Sonnet 4", @@ -7561,7 +7707,7 @@ export const MODELS = { provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", reasoning: true, - thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, + thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"low":null}, input: ["text", "image"], cost: { input: 30, @@ -8232,6 +8378,42 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"openai-completions">, + "anthropic/claude-opus-4.8": { + id: "anthropic/claude-opus-4.8", + name: "Anthropic: Claude Opus 4.8", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"openai-completions">, + "anthropic/claude-opus-4.8-fast": { + id: "anthropic/claude-opus-4.8-fast", + name: "Anthropic: Claude Opus 4.8 (Fast)", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 10, + output: 50, + cacheRead: 1, + cacheWrite: 12.5, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"openai-completions">, "anthropic/claude-sonnet-4": { id: "anthropic/claude-sonnet-4", name: "Anthropic: Claude Sonnet 4", @@ -10637,7 +10819,7 @@ export const MODELS = { provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", reasoning: true, - thinkingLevelMap: {"xhigh":"xhigh"}, + thinkingLevelMap: {"xhigh":"xhigh","off":null,"minimal":null,"low":null}, input: ["text", "image"], cost: { input: 30, @@ -12154,8 +12336,8 @@ export const MODELS = { input: ["text"], cost: { input: 0.125, - output: 0.84, - cacheRead: 0, + output: 0.85, + cacheRead: 0.06, cacheWrite: 0, }, contextWindow: 131072, @@ -13273,6 +13455,25 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"anthropic-messages">, + "anthropic/claude-opus-4.8": { + id: "anthropic/claude-opus-4.8", + name: "Claude Opus 4.8", + api: "anthropic-messages", + provider: "vercel-ai-gateway", + baseUrl: "https://ai-gateway.vercel.sh", + compat: {"forceAdaptiveThinking":true}, + reasoning: true, + thinkingLevelMap: {"xhigh":"xhigh"}, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, "anthropic/claude-sonnet-4": { id: "anthropic/claude-sonnet-4", name: "Claude Sonnet 4", @@ -14873,7 +15074,7 @@ export const MODELS = { provider: "vercel-ai-gateway", baseUrl: "https://ai-gateway.vercel.sh", reasoning: true, - thinkingLevelMap: {"xhigh":"xhigh"}, + thinkingLevelMap: {"xhigh":"xhigh","off":null,"minimal":null,"low":null}, input: ["text", "image"], cost: { input: 30, diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts index 429098a6..c0da66e2 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/providers/amazon-bedrock.ts @@ -66,7 +66,7 @@ export interface BedrockOptions extends StreamOptions { * - "omitted": Thinking content is redacted but the signature still travels back * for multi-turn continuity, reducing time-to-first-text-token. * - * Note: Anthropic's API default for Claude Opus 4.7 and Mythos Preview is + * Note: Anthropic's API default for Claude Opus 4.8 and Mythos Preview is * "omitted". We default to "summarized" here to keep behavior consistent with * older Claude 4 models. Only applies to Claude models on Bedrock. */ @@ -481,12 +481,14 @@ function getModelMatchCandidates(modelId: string, modelName?: string): string[] function supportsAdaptiveThinking(modelId: string, modelName?: string): boolean { const candidates = getModelMatchCandidates(modelId, modelName); - return candidates.some((s) => s.includes("opus-4-6") || s.includes("opus-4-7") || s.includes("sonnet-4-6")); + return candidates.some( + (s) => s.includes("opus-4-6") || s.includes("opus-4-7") || s.includes("opus-4-8") || s.includes("sonnet-4-6"), + ); } function supportsNativeXhighEffort(model: Model<"bedrock-converse-stream">): boolean { const candidates = getModelMatchCandidates(model.id, model.name); - return candidates.some((s) => s.includes("opus-4-7")); + return candidates.some((s) => s.includes("opus-4-7") || s.includes("opus-4-8")); } function mapThinkingLevelToEffort( diff --git a/packages/ai/test/anthropic-adaptive-thinking-models.test.ts b/packages/ai/test/anthropic-adaptive-thinking-models.test.ts index f118401b..9386ce0b 100644 --- a/packages/ai/test/anthropic-adaptive-thinking-models.test.ts +++ b/packages/ai/test/anthropic-adaptive-thinking-models.test.ts @@ -2,22 +2,10 @@ import { describe, expect, it } from "vitest"; import { getModels, getProviders } from "../src/models.ts"; import type { Api, Model } from "../src/types.ts"; -const EXPECTED_ADAPTIVE_THINKING_MODELS = [ - "anthropic/claude-opus-4-6", - "anthropic/claude-opus-4-7", - "anthropic/claude-sonnet-4-6", - "cloudflare-ai-gateway/claude-opus-4-6", - "cloudflare-ai-gateway/claude-opus-4-7", - "cloudflare-ai-gateway/claude-sonnet-4-6", - "github-copilot/claude-opus-4.6", - "github-copilot/claude-opus-4.7", - "github-copilot/claude-sonnet-4.6", - "opencode/claude-opus-4-6", - "opencode/claude-opus-4-7", - "opencode/claude-sonnet-4-6", - "vercel-ai-gateway/anthropic/claude-opus-4.6", - "vercel-ai-gateway/anthropic/claude-opus-4.7", - "vercel-ai-gateway/anthropic/claude-sonnet-4.6", +const EXPECTED_CURRENT_ADAPTIVE_THINKING_MODELS = [ + "anthropic/claude-opus-4-8", + "opencode/claude-opus-4-8", + "vercel-ai-gateway/anthropic/claude-opus-4.8", ]; function getAllModels(): Model[] { @@ -25,13 +13,16 @@ function getAllModels(): Model[] { } describe("Anthropic adaptive thinking model metadata", () => { - it("marks exactly the built-in Anthropic Messages models that use adaptive thinking", () => { + it("marks built-in Anthropic Messages models that use adaptive thinking", () => { const flaggedModels = getAllModels() .filter((model): model is Model<"anthropic-messages"> => model.api === "anthropic-messages") .filter((model) => model.compat?.forceAdaptiveThinking === true) .map((model) => `${model.provider}/${model.id}`) .sort(); - expect(flaggedModels).toEqual([...EXPECTED_ADAPTIVE_THINKING_MODELS].sort()); + expect(flaggedModels).toEqual(expect.arrayContaining([...EXPECTED_CURRENT_ADAPTIVE_THINKING_MODELS].sort())); + expect(flaggedModels).toEqual( + flaggedModels.filter((modelId) => /(opus[-.]4[-.][678]|sonnet[-.]4[-.]6)/.test(modelId)), + ); }); }); diff --git a/packages/ai/test/anthropic-eager-tool-input-compat.test.ts b/packages/ai/test/anthropic-eager-tool-input-compat.test.ts index 0560563a..22ac0e59 100644 --- a/packages/ai/test/anthropic-eager-tool-input-compat.test.ts +++ b/packages/ai/test/anthropic-eager-tool-input-compat.test.ts @@ -12,8 +12,8 @@ interface CapturedRequest { function createModel(baseUrl: string, compat?: Model<"anthropic-messages">["compat"]): Model<"anthropic-messages"> { return { - id: "claude-opus-4-7", - name: "Claude Opus 4.7", + id: "claude-opus-4-8", + name: "Claude Opus 4.8", api: "anthropic-messages", provider: "test-anthropic", baseUrl, diff --git a/packages/ai/test/anthropic-force-adaptive-thinking.test.ts b/packages/ai/test/anthropic-force-adaptive-thinking.test.ts index fd18df96..5b3511ca 100644 --- a/packages/ai/test/anthropic-force-adaptive-thinking.test.ts +++ b/packages/ai/test/anthropic-force-adaptive-thinking.test.ts @@ -85,7 +85,7 @@ describe("Anthropic forceAdaptiveThinking compat override", () => { it("allows built-in adaptive models to opt out with compat.forceAdaptiveThinking false", async () => { const model: Model<"anthropic-messages"> = { - ...getModel("anthropic", "claude-opus-4-7"), + ...getModel("anthropic", "claude-opus-4-8"), compat: { forceAdaptiveThinking: false }, }; const payload = await capturePayload(model, { reasoning: "medium" }); diff --git a/packages/ai/test/anthropic-opus-4-7-smoke.test.ts b/packages/ai/test/anthropic-opus-4-8-smoke.test.ts similarity index 91% rename from packages/ai/test/anthropic-opus-4-7-smoke.test.ts rename to packages/ai/test/anthropic-opus-4-8-smoke.test.ts index 7314f8af..bb4b739b 100644 --- a/packages/ai/test/anthropic-opus-4-7-smoke.test.ts +++ b/packages/ai/test/anthropic-opus-4-8-smoke.test.ts @@ -22,9 +22,9 @@ function makeContext(): Context { }; } -describe.skipIf(!process.env.ANTHROPIC_API_KEY)("Anthropic Opus 4.7 smoke", () => { - it("streams Claude Opus 4.7 with reasoning enabled", { retry: 2, timeout: 30000 }, async () => { - const model = getModel("anthropic", "claude-opus-4-7"); +describe.skipIf(!process.env.ANTHROPIC_API_KEY)("Anthropic Opus 4.8 smoke", () => { + it("streams Claude Opus 4.8 with reasoning enabled", { retry: 2, timeout: 30000 }, async () => { + const model = getModel("anthropic", "claude-opus-4-8"); let capturedPayload: AnthropicThinkingPayload | undefined; const s = streamSimple(model, makeContext(), { reasoning: "high", @@ -53,12 +53,12 @@ describe.skipIf(!process.env.ANTHROPIC_API_KEY)("Anthropic Opus 4.7 smoke", () = const thinkingBlock = response.content.find((block) => block.type === "thinking"); expect(thinkingBlock?.type).toBe("thinking"); if (!thinkingBlock || thinkingBlock.type !== "thinking") { - throw new Error("Expected thinking block from Claude Opus 4.7"); + throw new Error("Expected thinking block from Claude Opus 4.8"); } expect(typeof thinkingBlock.thinkingSignature).toBe("string"); const thinkingSignature = thinkingBlock.thinkingSignature; if (!thinkingSignature) { - throw new Error("Expected thinking signature from Claude Opus 4.7"); + throw new Error("Expected thinking signature from Claude Opus 4.8"); } expect(thinkingSignature.length).toBeGreaterThan(0); diff --git a/packages/ai/test/anthropic-thinking-disable.test.ts b/packages/ai/test/anthropic-thinking-disable.test.ts index ea53043c..5ee89b1e 100644 --- a/packages/ai/test/anthropic-thinking-disable.test.ts +++ b/packages/ai/test/anthropic-thinking-disable.test.ts @@ -125,22 +125,22 @@ describe("Anthropic thinking disable payload", () => { expect(payload.output_config).toBeUndefined(); }); - it("sends thinking.type=disabled for Claude Opus 4.7 when thinking is off", async () => { - const payload = await capturePayload(getModel("anthropic", "claude-opus-4-7")); + it("sends thinking.type=disabled for Claude Opus 4.8 when thinking is off", async () => { + const payload = await capturePayload(getModel("anthropic", "claude-opus-4-8")); expect(payload.thinking).toEqual({ type: "disabled" }); expect(payload.output_config).toBeUndefined(); }); - it("uses adaptive thinking for Claude Opus 4.7 when reasoning is enabled", async () => { - const payload = await capturePayload(getModel("anthropic", "claude-opus-4-7"), { reasoning: "high" }); + it("uses adaptive thinking for Claude Opus 4.8 when reasoning is enabled", async () => { + const payload = await capturePayload(getModel("anthropic", "claude-opus-4-8"), { reasoning: "high" }); expect(payload.thinking).toEqual({ type: "adaptive", display: "summarized" }); expect(payload.output_config).toEqual({ effort: "high" }); }); - it("maps xhigh reasoning to effort=xhigh for Claude Opus 4.7", async () => { - const payload = await capturePayload(getModel("anthropic", "claude-opus-4-7"), { reasoning: "xhigh" }); + it("maps xhigh reasoning to effort=xhigh for Claude Opus 4.8", async () => { + const payload = await capturePayload(getModel("anthropic", "claude-opus-4-8"), { reasoning: "xhigh" }); expect(payload.thinking).toEqual({ type: "adaptive", display: "summarized" }); expect(payload.output_config).toEqual({ effort: "xhigh" }); diff --git a/packages/ai/test/bedrock-endpoint-resolution.test.ts b/packages/ai/test/bedrock-endpoint-resolution.test.ts index a45bc6cf..2483fcae 100644 --- a/packages/ai/test/bedrock-endpoint-resolution.test.ts +++ b/packages/ai/test/bedrock-endpoint-resolution.test.ts @@ -98,7 +98,7 @@ describe("bedrock endpoint resolution", () => { it("does not pin standard AWS endpoints when AWS_REGION is configured", async () => { process.env.AWS_REGION = "us-east-2"; - const model = getModel("amazon-bedrock", "us.anthropic.claude-opus-4-7"); + const model = getModel("amazon-bedrock", "us.anthropic.claude-opus-4-8"); const config = await captureClientConfig(model); @@ -117,7 +117,7 @@ describe("bedrock endpoint resolution", () => { it("still passes custom Bedrock endpoints through to the SDK client", async () => { process.env.AWS_REGION = "us-west-2"; - const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-opus-4-7"); + const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-opus-4-8"); const model: Model<"bedrock-converse-stream"> = { ...baseModel, baseUrl: "https://bedrock-vpc.example.com", diff --git a/packages/ai/test/bedrock-thinking-payload.test.ts b/packages/ai/test/bedrock-thinking-payload.test.ts index 46a5277b..93143e38 100644 --- a/packages/ai/test/bedrock-thinking-payload.test.ts +++ b/packages/ai/test/bedrock-thinking-payload.test.ts @@ -53,12 +53,12 @@ async function capturePayload( } describe("Bedrock thinking payload", () => { - it("uses adaptive thinking for Claude Opus 4.7 when reasoning is enabled", async () => { + it("uses adaptive thinking for Claude Opus 4.8 when reasoning is enabled", async () => { const baseModel = getModel("amazon-bedrock", "global.anthropic.claude-opus-4-6-v1"); const model: Model<"bedrock-converse-stream"> = { ...baseModel, - id: "global.anthropic.claude-opus-4-7-v1", - name: "Claude Opus 4.7 (Global)", + id: "global.anthropic.claude-opus-4-8-v1", + name: "Claude Opus 4.8 (Global)", }; const payload = await capturePayload(model); @@ -68,12 +68,12 @@ describe("Bedrock thinking payload", () => { expect(payload.additionalModelRequestFields?.anthropic_beta).toBeUndefined(); }); - it("maps xhigh reasoning to effort=xhigh for Claude Opus 4.7", async () => { + it("maps xhigh reasoning to effort=xhigh for Claude Opus 4.8", async () => { const baseModel = getModel("amazon-bedrock", "global.anthropic.claude-opus-4-6-v1"); const model: Model<"bedrock-converse-stream"> = { ...baseModel, - id: "global.anthropic.claude-opus-4-7-v1", - name: "Claude Opus 4.7 (Global)", + id: "global.anthropic.claude-opus-4-8-v1", + name: "Claude Opus 4.8 (Global)", }; const payload = await capturePayload(model, { reasoning: "xhigh" }); @@ -101,8 +101,8 @@ describe("Bedrock thinking payload", () => { const baseModel = getModel("amazon-bedrock", "global.anthropic.claude-opus-4-6-v1"); const model: Model<"bedrock-converse-stream"> = { ...baseModel, - id: "global.anthropic.claude-opus-4-7-v1", - name: "Claude Opus 4.7 (Global)", + id: "global.anthropic.claude-opus-4-8-v1", + name: "Claude Opus 4.8 (Global)", }; const payload = await capturePayload(model, { region: "us-gov-west-1" }); diff --git a/packages/ai/test/fireworks-models.test.ts b/packages/ai/test/fireworks-models.test.ts index 6d9bebc6..8b291b89 100644 --- a/packages/ai/test/fireworks-models.test.ts +++ b/packages/ai/test/fireworks-models.test.ts @@ -97,8 +97,8 @@ function createFireworksModel(compat?: Model<"anthropic-messages">["compat"]): M function createAnthropicModel(): Model<"anthropic-messages"> { return { - id: "claude-opus-4-7", - name: "Claude Opus 4.7", + id: "claude-opus-4-8", + name: "Claude Opus 4.8", api: "anthropic-messages", provider: "anthropic", baseUrl: "http://127.0.0.1:0", // overridden by captureAnthropicRequest diff --git a/packages/ai/test/openai-completions-response-model.test.ts b/packages/ai/test/openai-completions-response-model.test.ts index aab20dea..d8e4d2f9 100644 --- a/packages/ai/test/openai-completions-response-model.test.ts +++ b/packages/ai/test/openai-completions-response-model.test.ts @@ -60,10 +60,10 @@ describe("openai-completions responseModel", () => { it("surfaces routed chunk.model on responseModel without changing model", async () => { mockState.chunks = [ - { id: "chatcmpl-1", model: "anthropic/claude-opus-4.7", choices: [{ index: 0, delta: { content: "hi" } }] }, + { id: "chatcmpl-1", model: "anthropic/claude-opus-4.8", choices: [{ index: 0, delta: { content: "hi" } }] }, { id: "chatcmpl-1", - model: "anthropic/claude-opus-4.7", + model: "anthropic/claude-opus-4.8", choices: [{ index: 0, delta: {}, finish_reason: "stop" }], usage: { prompt_tokens: 10, @@ -81,7 +81,7 @@ describe("openai-completions responseModel", () => { ); expect(message.model).toBe("openrouter/auto"); - expect(message.responseModel).toBe("anthropic/claude-opus-4.7"); + expect(message.responseModel).toBe("anthropic/claude-opus-4.8"); expect(message.provider).toBe("openrouter"); expect(message.stopReason).toBe("stop"); }); diff --git a/packages/ai/test/supports-xhigh.test.ts b/packages/ai/test/supports-xhigh.test.ts index 4fb6f3b2..5d9bdd9e 100644 --- a/packages/ai/test/supports-xhigh.test.ts +++ b/packages/ai/test/supports-xhigh.test.ts @@ -8,8 +8,14 @@ describe("getSupportedThinkingLevels", () => { expect(getSupportedThinkingLevels(model!)).toContain("xhigh"); }); - it("includes xhigh for Anthropic Opus 4.7 on anthropic-messages API", () => { - const model = getModel("anthropic", "claude-opus-4-7"); + it("includes xhigh for Anthropic Opus 4.8 on anthropic-messages API", () => { + const model = getModel("anthropic", "claude-opus-4-8"); + expect(model).toBeDefined(); + expect(getSupportedThinkingLevels(model!)).toContain("xhigh"); + }); + + it("includes xhigh for Anthropic Opus 4.8 on anthropic-messages API", () => { + const model = getModel("anthropic", "claude-opus-4-8"); expect(model).toBeDefined(); expect(getSupportedThinkingLevels(model!)).toContain("xhigh"); }); @@ -26,6 +32,18 @@ describe("getSupportedThinkingLevels", () => { expect(getSupportedThinkingLevels(model!)).toContain("xhigh"); }); + it("includes only medium/high/xhigh for OpenAI GPT-5.5 Pro", () => { + const model = getModel("openai", "gpt-5.5-pro"); + expect(model).toBeDefined(); + expect(getSupportedThinkingLevels(model!)).toEqual(["medium", "high", "xhigh"]); + }); + + it("includes only medium/high/xhigh for OpenRouter GPT-5.5 Pro", () => { + const model = getModel("openrouter", "openai/gpt-5.5-pro"); + expect(model).toBeDefined(); + expect(getSupportedThinkingLevels(model!)).toEqual(["medium", "high", "xhigh"]); + }); + it("includes only high/xhigh plus off for DeepSeek V4 Flash on the DeepSeek provider", () => { const model = getModel("deepseek", "deepseek-v4-flash"); expect(model).toBeDefined(); diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts index 764c06f5..87f7354e 100644 --- a/packages/coding-agent/src/core/model-resolver.ts +++ b/packages/coding-agent/src/core/model-resolver.ts @@ -13,7 +13,7 @@ import type { ModelRegistry } from "./model-registry.ts"; /** Default model IDs for each known provider */ export const defaultModelPerProvider: Record = { "amazon-bedrock": "us.anthropic.claude-opus-4-6-v1", - anthropic: "claude-opus-4-7", + anthropic: "claude-opus-4-8", openai: "gpt-5.4", "azure-openai-responses": "gpt-5.4", "openai-codex": "gpt-5.5",