Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/types/src/providers/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,30 @@ export const anthropicModels = {
description:
"Claude Opus 5.5 is Anthropic's most capable model for complex agentic coding and enterprise work.",
},
"claude-haiku-5-5": {
maxTokens: 128_000,
contextWindow: 1_000_000, // 1M context window native (no beta header required)
supportsImages: true,
supportsPromptCache: true,
// Standard pricing (prompts of 100,000 tokens or fewer) from Anthropic's
// pricing page. Prompts over 100,000 tokens bill at tiered rates
// ($0.50 in / $2.50 out / $0.625 5m cache write / $0.05 cache read per
// million tokens) that this entry does not model.
// https://platform.claude.com/docs/en/about-claude/pricing
inputPrice: 0.1, // $0.10 per million input tokens
outputPrice: 0.5, // $0.50 per million output tokens
cacheWritesPrice: 0.125, // $0.125 per million tokens (5m cache writes)
cacheReadsPrice: 0.01, // $0.01 per million tokens (cache hits / refreshes)
// Haiku 5.5 uses the same adaptive-thinking / binary-toggle convention as
// Sonnet 5 and Opus 5 on the direct Anthropic provider path. Manual
// extended thinking (budget_tokens) is not accepted, and sampling
// parameters (temperature/top_p/top_k) must be omitted.
supportsReasoningBudget: true,
supportsReasoningBinary: true,
supportsTemperature: false,
description:
"Claude Haiku 5.5 is the fastest, most efficient Claude 5.5 model, built for high-volume, latency-sensitive coding and agent work.",
},
"claude-fable-5-1": {
maxTokens: 128_000, // Overridden to 8k if `enableReasoningEffort` is false.
contextWindow: 1_000_000,
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/providers/openrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"anthropic/claude-fable-5.1",
"anthropic/claude-fable-5",
"anthropic/claude-haiku-4.5",
"anthropic/claude-haiku-5.5",

Check warning on line 52 in packages/types/src/providers/openrouter.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

packages/types/src/providers/openrouter.ts:52: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
"google/gemini-2.5-flash-preview",
"google/gemini-2.5-flash-preview:thinking",
"google/gemini-2.5-flash-preview-05-20",
Expand Down Expand Up @@ -88,6 +89,7 @@
"anthropic/claude-sonnet-4.6",
"anthropic/claude-sonnet-5",
"anthropic/claude-haiku-4.5",
"anthropic/claude-haiku-5.5",

Check warning on line 92 in packages/types/src/providers/openrouter.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

packages/types/src/providers/openrouter.ts:92: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
"google/gemini-2.5-pro-preview",
"google/gemini-2.5-pro",
"google/gemini-2.5-flash-preview-05-20",
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/providers/vercel-ai-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"anthropic/claude-3.5-haiku",
"anthropic/claude-3.5-sonnet",
"anthropic/claude-3.7-sonnet",
"anthropic/claude-haiku-5.5",

Check warning on line 12 in packages/types/src/providers/vercel-ai-gateway.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

packages/types/src/providers/vercel-ai-gateway.ts:12: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
"anthropic/claude-opus-4",
"anthropic/claude-opus-4.1",
"anthropic/claude-opus-4.5",
Expand Down Expand Up @@ -57,6 +58,7 @@
"anthropic/claude-3.5-sonnet",
"anthropic/claude-3.7-sonnet",
"anthropic/claude-haiku-4.5",
"anthropic/claude-haiku-5.5",

Check warning on line 61 in packages/types/src/providers/vercel-ai-gateway.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

packages/types/src/providers/vercel-ai-gateway.ts:61: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
"anthropic/claude-opus-4",
"anthropic/claude-opus-4.1",
"anthropic/claude-opus-4.5",
Expand Down
17 changes: 17 additions & 0 deletions packages/types/src/providers/vertex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,23 @@ export const vertexModels = {
description:
"Claude Opus 5.5 is Anthropic's most capable model for complex agentic coding and enterprise work.",
},
"claude-haiku-5-5": {
maxTokens: 128_000,
contextWindow: 1_000_000, // 1M context window native (no beta header required)
supportsImages: true,
supportsPromptCache: true,
// Standard pricing (prompts of 100,000 tokens or fewer). Prompts over
// 100,000 tokens bill at higher tiered rates per Anthropic's pricing page.
inputPrice: 0.1, // $0.10 per million input tokens
outputPrice: 0.5, // $0.50 per million output tokens
cacheWritesPrice: 0.125, // $0.125 per million tokens (5m cache writes)
cacheReadsPrice: 0.01, // $0.01 per million tokens (cache hits / refreshes)
supportsReasoningBudget: true,
supportsReasoningBinary: true,
supportsTemperature: false,
description:
"Claude Haiku 5.5 is the fastest, most efficient Claude 5.5 model, built for high-volume, latency-sensitive coding and agent work.",
},
"claude-fable-5-1": {
maxTokens: 128_000,
contextWindow: 1_000_000,
Expand Down
51 changes: 51 additions & 0 deletions src/api/providers/__tests__/anthropic-vertex.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,25 @@ describe("VertexHandler", () => {
expect(model.info.supportsTemperature).toBe(false)
})

it("should return Claude Haiku 5.5 model info", () => {
const handler = new AnthropicVertexHandler({
apiModelId: "claude-haiku-5-5",
vertexProjectId: "test-project",
vertexRegion: "us-central1",
})

const model = handler.getModel()
expect(model.id).toBe("claude-haiku-5-5")
expect(model.info.maxTokens).toBe(128_000)
expect(model.info.contextWindow).toBe(1_000_000)
expect(model.info.inputPrice).toBe(0.1)
expect(model.info.outputPrice).toBe(0.5)
expect(model.info.supportsReasoningBinary).toBe(true)
expect(model.info.supportsReasoningBudget).toBe(true)
expect(model.info.supportsPromptCache).toBe(true)
expect(model.info.supportsTemperature).toBe(false)
})

it("should not enable 1M context when flag is disabled", () => {
const handler = new AnthropicVertexHandler({
apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0],
Expand Down Expand Up @@ -1717,6 +1736,38 @@ describe("VertexHandler", () => {
expect(request.temperature).toBeUndefined()
})

it("should use adaptive thinking for Claude Haiku 5.5", async () => {
const haikuHandler = new AnthropicVertexHandler({
apiModelId: "claude-haiku-5-5",
vertexProjectId: "test-project",
vertexRegion: "us-central1",
enableReasoningEffort: true,
})

const mockCreate = vitest.fn().mockImplementation(async () =>
asyncStreamFrom([
{ type: "message_start", message: { usage: { input_tokens: 100, output_tokens: 50 } } },
{ type: "text_delta", delta: { type: "text_delta", text: "Hello" } },
]),
)
// The SDK client's overloaded `create` signature can't be assigned a
// vitest mock directly, so a structural double assertion is required.
;(haikuHandler["client"].messages as unknown as { create: typeof mockCreate }).create = mockCreate

await haikuHandler.createMessage("You are a helpful assistant", [{ role: "user", content: "Hello" }]).next()

expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
thinking: { type: "adaptive", display: "summarized" },
}),
undefined,
)

const request = mockCreate.mock.calls[0][0]
expect(request.thinking).not.toHaveProperty("budget_tokens")
expect(request.temperature).toBeUndefined()
})

it("should use adaptive thinking for Claude Opus 5.5", async () => {
const opusHandler = new AnthropicVertexHandler({
apiModelId: "claude-opus-5-5",
Expand Down
49 changes: 49 additions & 0 deletions src/api/providers/__tests__/anthropic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,32 @@ describe("AnthropicHandler", () => {
expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31")
})

it("should use adaptive thinking for Claude Haiku 5.5 when reasoning is enabled", async () => {
const haikuHandler = new AnthropicHandler({
apiKey: "test-api-key",
apiModelId: "claude-haiku-5-5",
enableReasoningEffort: true,
modelMaxTokens: 32768,
})

const stream = haikuHandler.createMessage(systemPrompt, [
{
role: "user",
content: [{ type: "text" as const, text: "Hello" }],
},
])

await collectStream(stream)

const requestBody = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[0]
const requestOptions = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[1]
expect(requestBody?.thinking).toEqual({ type: "adaptive", display: "summarized" })
expect(requestBody?.thinking).not.toHaveProperty("budget_tokens")
expect(requestBody?.temperature).toBeUndefined()
expect(requestBody?.max_tokens).toBe(32768)
expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31")
})

it("should send the custom model ID as-is and use adaptive thinking for a custom Sonnet-5-family model", async () => {
const customHandler = new AnthropicHandler({
apiKey: "test-api-key",
Expand Down Expand Up @@ -1368,6 +1394,29 @@ describe("AnthropicHandler", () => {
expect(model.reasoningBudget).toBeUndefined()
})

it("should handle Claude Haiku 5.5 model correctly", () => {
const handler = new AnthropicHandler({
apiKey: "test-api-key",
apiModelId: "claude-haiku-5-5",
})
const model = handler.getModel()
expect(model.id).toBe("claude-haiku-5-5")
expect(model.info.maxTokens).toBe(128000)
expect(model.info.contextWindow).toBe(1000000)
expect(model.info.inputPrice).toBe(0.1)
expect(model.info.outputPrice).toBe(0.5)
expect(model.info.cacheWritesPrice).toBe(0.125)
expect(model.info.cacheReadsPrice).toBe(0.01)
// Hybrid reasoning model with the reasoning toggle off: the request
// budget clamps to the Anthropic default, matching Sonnet 5 / Opus 5.
expect(model.maxTokens).toBe(8192)
expect(model.info.supportsReasoningBinary).toBe(true)
expect(model.info.supportsReasoningBudget).toBe(true)
expect(model.info.supportsPromptCache).toBe(true)
expect(model.info.supportsTemperature).toBe(false)
expect(model.reasoningBudget).toBeUndefined()
})

it("should enable 1M context for Claude 4.5 Sonnet when beta flag is set", () => {
const handler = new AnthropicHandler({
apiKey: "test-api-key",
Expand Down
1 change: 1 addition & 0 deletions src/api/providers/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
case "claude-opus-4-8":
case "claude-opus-5":
case "claude-opus-5-5":
case "claude-haiku-5-5":
case "claude-fable-5-1":
case "claude-fable-5":
case "claude-opus-4-5-20251101":
Expand Down
28 changes: 28 additions & 0 deletions src/api/providers/fetchers/__tests__/openrouter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,34 @@ describe("OpenRouter API", () => {
expect(result.supportsReasoningBinary).toBe(true)
})

it("sets claude-haiku-5-5 model to Anthropic max tokens and omits temperature", () => {
const mockModel = {
name: "Claude Haiku 5.5",
description: "Test model",
context_length: 1000000,
max_completion_tokens: 64000,
pricing: {
prompt: "0.0000001",
completion: "0.0000005",
},
}

const result = parseOpenRouterModel({
id: "anthropic/claude-haiku-5.5",
model: mockModel,
inputModality: ["text", "image"],
outputModality: ["text"],
maxTokens: 64000,
supportedParameters: ["reasoning", "include_reasoning"],
})

expect(result.maxTokens).toBe(128000)
expect(result.contextWindow).toBe(1000000)
expect(result.supportsTemperature).toBe(false)
expect(result.supportsReasoningBudget).toBe(true)
expect(result.supportsReasoningBinary).toBe(true)
})

it("sets horizon-alpha model to 32k max tokens", () => {
const mockModel = {
name: "Horizon Alpha",
Expand Down
25 changes: 25 additions & 0 deletions src/api/providers/fetchers/__tests__/requesty.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,31 @@ describe("getRequestyModels", () => {
expect(opus55.supportsTemperature).toBe(false)
})

it("applies Haiku 5.5 overrides when parsing anthropic/claude-haiku-5-5", async () => {
const rawHaiku55 = makeRawModel({
id: "anthropic/claude-haiku-5-5",
max_output_tokens: 128000,
context_window: 1000000,
supports_caching: true,
supports_vision: true,
supports_reasoning: true,
input_price: "0.0000001",
output_price: "0.0000005",
caching_price: "0.000000125",
cached_price: "0.00000001",
})

mockAxiosGet.mockResolvedValueOnce({ data: { data: [rawHaiku55] } })

const models = await getRequestyModels()
const haiku55 = models["anthropic/claude-haiku-5-5"]

expect(haiku55).toBeDefined()
expect(haiku55.supportsReasoningBudget).toBe(true)
expect(haiku55.supportsReasoningBinary).toBe(true)
expect(haiku55.supportsTemperature).toBe(false)
})

it("does not apply Fable 5 overrides to other models", async () => {
const rawSonnet = makeRawModel({
id: "anthropic/claude-sonnet-4.6",
Expand Down
16 changes: 16 additions & 0 deletions src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,22 @@ describe("Vercel AI Gateway Fetchers", () => {
expect(result.supportsTemperature).toBe(false)
})

it("marks Claude Haiku 5.5 as not supporting temperature", () => {
const result = parseVercelAiGatewayModel({
id: "anthropic/claude-haiku-5.5",
model: {
...baseModel,
id: "anthropic/claude-haiku-5.5",
context_window: 1000000,
max_tokens: 128000,
},
})

expect(result.maxTokens).toBe(128000)
expect(result.contextWindow).toBe(1000000)
expect(result.supportsTemperature).toBe(false)
})

it("detects vision-only models", () => {
// claude 3.5 haiku in VERCEL_AI_GATEWAY_VISION_ONLY_MODELS
const visionModel = {
Expand Down
7 changes: 7 additions & 0 deletions src/api/providers/fetchers/openrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,13 @@ export const parseOpenRouterModel = ({
modelInfo.supportsTemperature = false
}

// Set claude-haiku-5-5 model to use the correct Anthropic configuration
if (id === "anthropic/claude-haiku-5.5") {
modelInfo.maxTokens = anthropicModels["claude-haiku-5-5"].maxTokens
modelInfo.supportsReasoningBinary = true
modelInfo.supportsTemperature = false
}

// Ensure correct reasoning handling for Claude Haiku 4.5 on OpenRouter
// Use budget control and disable effort-based reasoning fallback
if (id === "anthropic/claude-haiku-4.5") {
Expand Down
6 changes: 6 additions & 0 deletions src/api/providers/fetchers/requesty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ export async function getRequestyModels(
modelInfo.supportsTemperature = false
}

if (rawModel.id === "anthropic/claude-haiku-5-5") {
modelInfo.supportsReasoningBudget = true
modelInfo.supportsReasoningBinary = true
modelInfo.supportsTemperature = false
}

models[rawModel.id] = modelInfo
}
} catch (error) {
Expand Down
4 changes: 4 additions & 0 deletions src/api/providers/fetchers/vercel-ai-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,9 @@ export const parseVercelAiGatewayModel = ({ id, model }: { id: string; model: Ve
modelInfo.supportsTemperature = false
}

if (id === "anthropic/claude-haiku-5.5") {
modelInfo.supportsTemperature = false
}

return modelInfo
}
31 changes: 31 additions & 0 deletions src/shared/__tests__/api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,37 @@ describe("getModelMaxOutputTokens", () => {
).toBe(32_768)
})

test("should preserve Anthropic hybrid token handling for Claude Haiku 5.5", () => {
const model: ModelInfo = {
contextWindow: 1_000_000,
supportsPromptCache: true,
supportsReasoningBudget: true,
supportsReasoningBinary: true,
supportsTemperature: false,
maxTokens: 128_000,
}

expect(
getModelMaxOutputTokens({
modelId: "claude-haiku-5-5",
model,
settings: { apiProvider: providerIdentifiers.anthropic, enableReasoningEffort: false },
}),
).toBe(ANTHROPIC_DEFAULT_MAX_TOKENS)

expect(
getModelMaxOutputTokens({
modelId: "claude-haiku-5-5",
model,
settings: {
apiProvider: providerIdentifiers.anthropic,
enableReasoningEffort: true,
modelMaxTokens: 32_768,
},
}),
).toBe(32_768)
})

test("should return model.maxTokens for non-Anthropic models that support reasoning budget but aren't using it", () => {
const geminiModelId = "gemini-2.5-flash-preview-04-17"
const model: ModelInfo = {
Expand Down
Loading