diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index abb9514bc..749c05e98 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -11,6 +11,7 @@ - Added `durationMs` to `AssistantMessage`: `AssistantMessageEventStream` measures each response with a monotonic clock from the start of the request to its final message, for every API implementation, including direct calls. Deferred results fetched later stay untimed ([#10549](https://github.com/earendil-works/pi/issues/10549)) - Added an optional `durationMs` to `ToolResultMessage` for the execution time of the tool ([#10549](https://github.com/earendil-works/pi/issues/10549)) - Added the `openai-decisions` classifier API for OpenAI's Decisions API, with `gpt-6-luna` as a classifier model of the `openai` provider. It needs an API key, so it is not listed as available while `openai` uses Sign in with ChatGPT +- Added Claude Haiku 5.5 (`claude-haiku-5-5`) to the `anthropic` provider with its prompt-length pricing tier, adaptive thinking with `xhigh`/`max` effort, per-message effort, and mid-conversation system messages and tool changes. Bedrock requests for Haiku 5.5 use adaptive thinking, native `xhigh`, and prompt caching - Added optional `images` to `ClassifierContext`. Models whose `input` includes `"image"` judge them with the state; `classify()` returns an error result for other models and for APIs that cannot send images ### Changed diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 1d4a0ab1e..10a3bcf27 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -610,7 +610,7 @@ function supportsAnthropicMidConvoEffort(modelId: string): boolean { const id = modelId.toLowerCase().replace(/^~?anthropic\//, ""); return ( /^claude-opus-(?:5|5[.-]5)(?:-\d{8})?$/.test(id) || - /^claude-sonnet-5[.-]5(?:-\d{8})?$/.test(id) || + /^claude-(?:sonnet|haiku)-5[.-]5(?:-\d{8})?$/.test(id) || /^claude-(?:fable|mythos)-5(?:[.-]1)(?:-\d{8})?$/.test(id) ); } @@ -618,7 +618,7 @@ function supportsAnthropicMidConvoEffort(modelId: string): boolean { function supportsAnthropicMidConvoSystemMessages(modelId: string): boolean { return ( /^claude-opus-(?:4[.-]8|5(?:[.-]5)?)(?:-\d{8})?$/.test(modelId) || - /^claude-sonnet-5[.-]5(?:-\d{8})?$/.test(modelId) || + /^claude-(?:sonnet|haiku)-5[.-]5(?:-\d{8})?$/.test(modelId) || /^claude-(?:fable|mythos)-5(?:[.-]1)?(?:-\d{8})?$/.test(modelId) ); } @@ -637,6 +637,8 @@ function isAnthropicAdaptiveThinkingModel(modelId: string): boolean { modelId.includes("sonnet-4.6") || modelId.includes("sonnet-5") || modelId.includes("sonnet.5") || + modelId.includes("haiku-5") || + modelId.includes("haiku.5") || modelId.includes("fable-5") || modelId.includes("mythos-5") ); @@ -652,7 +654,9 @@ function isAnthropicTemperatureUnsupportedModel(modelId: string): boolean { id.includes("opus-5") || id.includes("opus.5") || id.includes("sonnet-5-5") || - id.includes("sonnet-5.5") + id.includes("sonnet-5.5") || + id.includes("haiku-5-5") || + id.includes("haiku-5.5") ); } @@ -1091,7 +1095,7 @@ function applyThinkingLevelMetadata(model: Model): void { } // Anthropic adaptive-thinking effort support (per Anthropic adaptive thinking docs): // - "max" is available on all adaptive-thinking Claude models. - // - "xhigh" is only available on Opus 4.7/4.8/5, Sonnet 5, and Fable 5. + // - "xhigh" is only available on Opus 4.7/4.8/5, Sonnet 5, Haiku 5.5, and Fable 5. if ( model.id.includes("opus-4-6") || model.id.includes("opus-4.6") || @@ -1108,7 +1112,9 @@ function applyThinkingLevelMetadata(model: Model): void { model.id.includes("opus-5") || model.id.includes("opus.5") || model.id.includes("sonnet-5") || - model.id.includes("sonnet.5") + model.id.includes("sonnet.5") || + model.id.includes("haiku-5") || + model.id.includes("haiku.5") ) { mergeThinkingLevelMap(model, { xhigh: "xhigh", max: "max" }); } @@ -2877,6 +2883,39 @@ async function generateModels() { }); } + // Add Claude Haiku 5.5 until models.dev includes it. Prompts over 100k input tokens + // are billed at 5x for the whole request. + // https://platform.claude.com/docs/en/models/haiku-5-5/overview + if (!allModels.some((model) => model.provider === "anthropic" && model.id === "claude-haiku-5-5")) { + allModels.push({ + id: "claude-haiku-5-5", + name: "Claude Haiku 5.5", + api: "anthropic-messages", + provider: "anthropic", + baseUrl: "https://api.anthropic.com", + reasoning: true, + thinkingLevelMap: { + off: null, + minimal: null, + low: "low", + medium: "medium", + high: "high", + xhigh: "xhigh", + max: "max", + }, + input: ["text", "image"], + cost: { + input: 0.1, + output: 0.5, + cacheRead: 0.01, + cacheWrite: 0.125, + tiers: [{ inputTokensAbove: 100000, input: 0.5, output: 2.5, cacheRead: 0.05, cacheWrite: 0.625 }], + }, + contextWindow: 1000000, + maxTokens: 128000, + }); + } + // The authenticated Copilot catalog advertised these models on 2026-09-22, // but models.dev did not include them yet. const missingCopilotModels: Model[] = [ @@ -2928,10 +2967,12 @@ async function generateModels() { candidate.contextWindow = 1000000; } - // models.dev may list Opus 5.5 and Sonnet 5.5 before their effort metadata is complete. + // models.dev may list Opus 5.5, Sonnet 5.5, and Haiku 5.5 before their effort metadata is complete. if ( (candidate.provider === "anthropic" && - (candidate.id === "claude-opus-5-5" || candidate.id === "claude-sonnet-5-5")) || + (candidate.id === "claude-opus-5-5" || + candidate.id === "claude-sonnet-5-5" || + candidate.id === "claude-haiku-5-5")) || (candidate.provider === "github-copilot" && candidate.id === "claude-opus-5.5") ) { mergeThinkingLevelMap(candidate, { diff --git a/packages/ai/src/api/bedrock-converse-stream.ts b/packages/ai/src/api/bedrock-converse-stream.ts index aecd2f2ea..1bada0746 100644 --- a/packages/ai/src/api/bedrock-converse-stream.ts +++ b/packages/ai/src/api/bedrock-converse-stream.ts @@ -773,6 +773,7 @@ function supportsAdaptiveThinking(modelId: string, modelName?: string): boolean s.includes("opus-5") || s.includes("sonnet-4-6") || s.includes("sonnet-5") || + s.includes("haiku-5") || s.includes("fable-5"), ); } @@ -785,6 +786,7 @@ function supportsNativeXhighEffort(model: Model<"bedrock-converse-stream">): boo s.includes("opus-4-8") || s.includes("opus-5") || s.includes("sonnet-5") || + s.includes("haiku-5") || s.includes("fable-5"), ); } @@ -801,6 +803,7 @@ function supportsThinkingBlockBinding(model: Model<"bedrock-converse-stream">): s.includes("opus-4-8") || s.includes("opus-5") || s.includes("sonnet-5") || + s.includes("haiku-5") || s.includes("fable-5"), ); } @@ -880,8 +883,13 @@ function supportsPromptCaching(model: Model<"bedrock-converse-stream">, env?: Pr if (getProviderEnvValue("AWS_BEDROCK_FORCE_CACHE", env) === "1") return true; return false; } - // Claude 5 models (fable-5, opus-5, sonnet-5) - if (candidates.some((s) => s.includes("fable-5") || s.includes("opus-5") || s.includes("sonnet-5"))) return true; + // Claude 5 models (fable-5, opus-5, sonnet-5, haiku-5) + if ( + candidates.some( + (s) => s.includes("fable-5") || s.includes("opus-5") || s.includes("sonnet-5") || s.includes("haiku-5"), + ) + ) + return true; // Claude 4.x models (opus-4, sonnet-4, haiku-4) if (candidates.some((s) => s.includes("-4-"))) return true; // Claude 3.7 Sonnet