diff --git a/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap b/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap index ab023ddc00..72277b5210 100644 --- a/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap +++ b/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap @@ -1165,6 +1165,138 @@ exports[`Registry Snapshots endpoint configurations snapshot 1`] = ` ], }, }, + "anthropic/claude-4.7-opus": { + "claude-4.7-opus:anthropic": { + "context": 1000000, + "crossRegion": false, + "maxTokens": 128000, + "modelId": "claude-opus-4-7", + "parameters": [ + "include_reasoning", + "max_tokens", + "reasoning", + "stop", + "temperature", + "tool_choice", + "tools", + ], + "provider": "anthropic", + "ptbEnabled": true, + "regions": [ + "*", + ], + }, + }, + "anthropic/claude-4.8-opus": { + "claude-4.8-opus:anthropic": { + "context": 1000000, + "crossRegion": false, + "maxTokens": 128000, + "modelId": "claude-opus-4-8", + "parameters": [ + "include_reasoning", + "max_tokens", + "reasoning", + "stop", + "temperature", + "tool_choice", + "tools", + ], + "provider": "anthropic", + "ptbEnabled": true, + "regions": [ + "*", + ], + }, + }, + "anthropic/claude-5-fable": { + "claude-5-fable:anthropic": { + "context": 1000000, + "crossRegion": false, + "maxTokens": 128000, + "modelId": "claude-fable-5", + "parameters": [ + "include_reasoning", + "max_tokens", + "reasoning", + "stop", + "temperature", + "tool_choice", + "tools", + ], + "provider": "anthropic", + "ptbEnabled": true, + "regions": [ + "*", + ], + }, + }, + "anthropic/claude-5-opus": { + "claude-5-opus:anthropic": { + "context": 1000000, + "crossRegion": false, + "maxTokens": 128000, + "modelId": "claude-opus-5", + "parameters": [ + "include_reasoning", + "max_tokens", + "reasoning", + "stop", + "temperature", + "tool_choice", + "tools", + ], + "provider": "anthropic", + "ptbEnabled": true, + "regions": [ + "*", + ], + }, + }, + "anthropic/claude-5-sonnet": { + "claude-5-sonnet:anthropic": { + "context": 1000000, + "crossRegion": false, + "maxTokens": 128000, + "modelId": "claude-sonnet-5", + "parameters": [ + "include_reasoning", + "max_tokens", + "reasoning", + "stop", + "temperature", + "tool_choice", + "tools", + ], + "provider": "anthropic", + "ptbEnabled": true, + "regions": [ + "*", + ], + }, + }, + "anthropic/claude-5.1-fable": { + "claude-5.1-fable:anthropic": { + "context": 1000000, + "crossRegion": false, + "maxTokens": 128000, + "modelId": "claude-fable-5-1", + "parameters": [ + "include_reasoning", + "max_tokens", + "reasoning", + "stop", + "temperature", + "tool_choice", + "tools", + ], + "provider": "anthropic", + "ptbEnabled": true, + "regions": [ + "*", + ], + }, + }, "anthropic/claude-haiku-4-5-20251001": { "claude-haiku-4-5-20251001:anthropic": { "context": 200000, @@ -7372,6 +7504,24 @@ exports[`Registry Snapshots model coverage snapshot 1`] = ` "helicone", "vertex", ], + "anthropic/claude-4.7-opus": [ + "anthropic", + ], + "anthropic/claude-4.8-opus": [ + "anthropic", + ], + "anthropic/claude-5-fable": [ + "anthropic", + ], + "anthropic/claude-5-opus": [ + "anthropic", + ], + "anthropic/claude-5-sonnet": [ + "anthropic", + ], + "anthropic/claude-5.1-fable": [ + "anthropic", + ], "anthropic/claude-haiku-4-5-20251001": [ "anthropic", "bedrock", @@ -8304,6 +8454,96 @@ exports[`Registry Snapshots pricing snapshot 1`] = ` }, ], }, + "anthropic/claude-4.7-opus": { + "anthropic": [ + { + "cacheMultipliers": { + "cachedInput": 0.1, + "write1h": 2, + "write5m": 1.25, + }, + "input": 0.000005, + "output": 0.000025, + "threshold": 0, + "web_search": 0.01, + }, + ], + }, + "anthropic/claude-4.8-opus": { + "anthropic": [ + { + "cacheMultipliers": { + "cachedInput": 0.1, + "write1h": 2, + "write5m": 1.25, + }, + "input": 0.000005, + "output": 0.000025, + "threshold": 0, + "web_search": 0.01, + }, + ], + }, + "anthropic/claude-5-fable": { + "anthropic": [ + { + "cacheMultipliers": { + "cachedInput": 0.1, + "write1h": 2, + "write5m": 1.25, + }, + "input": 0.00001, + "output": 0.00005, + "threshold": 0, + "web_search": 0.01, + }, + ], + }, + "anthropic/claude-5-opus": { + "anthropic": [ + { + "cacheMultipliers": { + "cachedInput": 0.1, + "write1h": 2, + "write5m": 1.25, + }, + "input": 0.000005, + "output": 0.000025, + "threshold": 0, + "web_search": 0.01, + }, + ], + }, + "anthropic/claude-5-sonnet": { + "anthropic": [ + { + "cacheMultipliers": { + "cachedInput": 0.1, + "write1h": 2, + "write5m": 1.25, + }, + "input": 0.000002, + "output": 0.00001, + "threshold": 0, + "web_search": 0.01, + }, + ], + }, + "anthropic/claude-5.1-fable": { + "anthropic": [ + { + "cacheMultipliers": { + "cachedInput": 0.025, + "write1h": 2, + "write5m": 1.25, + }, + "input": 0.00001, + "output": 0.00005, + "threshold": 0, + "web_search": 0.01, + }, + ], + }, "anthropic/claude-haiku-4-5-20251001": { "anthropic": [ { @@ -10053,6 +10293,42 @@ exports[`Registry Snapshots verify registry state 1`] = ` "vertex", ], }, + { + "model": "claude-4.7-opus", + "providers": [ + "anthropic", + ], + }, + { + "model": "claude-4.8-opus", + "providers": [ + "anthropic", + ], + }, + { + "model": "claude-5-fable", + "providers": [ + "anthropic", + ], + }, + { + "model": "claude-5-opus", + "providers": [ + "anthropic", + ], + }, + { + "model": "claude-5-sonnet", + "providers": [ + "anthropic", + ], + }, + { + "model": "claude-5.1-fable", + "providers": [ + "anthropic", + ], + }, { "model": "claude-haiku-4-5-20251001", "providers": [ @@ -10846,7 +11122,7 @@ exports[`Registry Snapshots verify registry state 1`] = ` ], "providerBreakdown": [ { - "modelCount": 15, + "modelCount": 21, "provider": "anthropic", }, { @@ -10941,6 +11217,12 @@ exports[`Registry Snapshots verify registry state 1`] = ` "claude-4.5-sonnet", "claude-4.6-opus", "claude-4.6-sonnet", + "claude-4.7-opus", + "claude-4.8-opus", + "claude-5-fable", + "claude-5-opus", + "claude-5-sonnet", + "claude-5.1-fable", "claude-haiku-4-5-20251001", "claude-opus-4", "claude-opus-4-1", @@ -11049,9 +11331,9 @@ exports[`Registry Snapshots verify registry state 1`] = ` "claude-3.5-haiku:anthropic:*", ], "totalArchivedConfigs": 0, - "totalEndpoints": 329, - "totalModelProviderConfigs": 329, - "totalModelsWithPtb": 108, + "totalEndpoints": 335, + "totalModelProviderConfigs": 335, + "totalModelsWithPtb": 114, "totalProviders": 21, } `; diff --git a/packages/__tests__/cost/modelCostFromRegistry.test.ts b/packages/__tests__/cost/modelCostFromRegistry.test.ts index 73aa4f6602..cbe3cb41ba 100644 --- a/packages/__tests__/cost/modelCostFromRegistry.test.ts +++ b/packages/__tests__/cost/modelCostFromRegistry.test.ts @@ -1,6 +1,6 @@ import type { ModelUsage } from "../../cost/usage/types"; import type { ModelProviderName } from "../../cost/models/providers"; -import { modelCostBreakdownFromRegistry } from "../../cost/costCalc"; +import { modelCost, modelCostBreakdownFromRegistry } from "../../cost/costCalc"; describe("modelCostBreakdownFromRegistry", () => { it("should calculate cost for basic GPT-4o usage", () => { @@ -436,4 +436,216 @@ describe("modelCostBreakdownFromRegistry", () => { } }); }); +}); + +describe("unpriced model warning", () => { + let warnSpy: jest.SpyInstance; + + beforeEach(() => { + warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + + const legacyUsage = { + sum_prompt_tokens: 100, + prompt_cache_write_tokens: 0, + prompt_cache_read_tokens: 0, + prompt_audio_tokens: 0, + sum_completion_tokens: 50, + completion_audio_tokens: 0, + prompt_cache_write_5m: 0, + prompt_cache_write_1h: 0, + }; + + it("registry path warns with provider and model when no pricing exists", () => { + const breakdown = modelCostBreakdownFromRegistry({ + modelUsage: { input: 100, output: 50 }, + providerModelId: "not-a-real-model", + provider: "anthropic" as ModelProviderName, + }); + + expect(breakdown).toBeNull(); + expect(warnSpy).toHaveBeenCalledTimes(1); + const message = String(warnSpy.mock.calls[0][0]); + expect(message).toContain("not-a-real-model"); + expect(message).toContain("anthropic"); + }); + + it("registry path does not warn when pricing exists", () => { + const breakdown = modelCostBreakdownFromRegistry({ + modelUsage: { input: 100, output: 50 }, + providerModelId: "gpt-4o", + provider: "openai" as ModelProviderName, + }); + + expect(breakdown).not.toBeNull(); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it("legacy modelCost warns and returns 0 when neither the legacy table nor the registry has pricing", () => { + const cost = modelCost({ + provider: "ANTHROPIC", + model: "not-a-real-model", + ...legacyUsage, + }); + + expect(cost).toBe(0); + expect(warnSpy).toHaveBeenCalledTimes(1); + const message = String(warnSpy.mock.calls[0][0]); + expect(message).toContain("not-a-real-model"); + expect(message).toContain("ANTHROPIC"); + }); + + it("legacy modelCost does not warn for a model the registry prices", () => { + // gpt-5.4 has a registry entry but no row in the legacy openai table. + // Callers prefer the registry result, so a legacy miss here is not a silent zero. + const cost = modelCost({ + provider: "OPENAI", + model: "gpt-5.4", + ...legacyUsage, + }); + + expect(cost).toBe(0); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it("legacy modelCost does not warn when the legacy table has pricing", () => { + const cost = modelCost({ + provider: "ANTHROPIC", + model: "claude-sonnet-4-6", + ...legacyUsage, + sum_prompt_tokens: 1_000_000, + sum_completion_tokens: 0, + }); + + expect(cost).toBeCloseTo(3, 10); + expect(warnSpy).not.toHaveBeenCalled(); + }); +}); + +describe("Claude Opus 4.7 / 4.8 / 5, Sonnet 5, Fable 5 / 5.1 pricing (anthropic)", () => { + // Rates per https://platform.claude.com/docs/en/about-claude/pricing (per MTok): + // Opus 4.7 / 4.8 / 5: $5 in | $6.25 5m write | $10 1h write | $0.50 cache read | $25 out + // Sonnet 5: $2 in | $2.50 5m write | $4 1h write | $0.20 cache read | $10 out + // Fable 5: $10 in | $12.50 5m write | $20 1h write | $1 cache read | $50 out + // Fable 5.1: $10 in | $12.50 5m write | $20 1h write | $0.25 cache read (0.025x) | $50 out + + const oneMillionOfEverything: ModelUsage = { + input: 1_000_000, + output: 1_000_000, + cacheDetails: { + cachedInput: 1_000_000, + write5m: 1_000_000, + write1h: 1_000_000, + }, + }; + + it("prices the pricing page worked example (50,000 in + 15,000 out at $5/$25) at $0.625 on Opus 4.7, 4.8 and 5", () => { + for (const providerModelId of ["claude-opus-4-7", "claude-opus-4-8", "claude-opus-5"]) { + const breakdown = modelCostBreakdownFromRegistry({ + modelUsage: { input: 50_000, output: 15_000 }, + providerModelId, + provider: "anthropic" as ModelProviderName, + }); + + expect(breakdown).not.toBeNull(); + expect(breakdown!.inputCost).toBeCloseTo(0.25, 10); + expect(breakdown!.outputCost).toBeCloseTo(0.375, 10); + expect(breakdown!.totalCost).toBeCloseTo(0.625, 10); + } + }); + + it("applies $6.25 / $10 / $0.50 cache rates on Opus 4.8", () => { + const breakdown = modelCostBreakdownFromRegistry({ + modelUsage: oneMillionOfEverything, + providerModelId: "claude-opus-4-8", + provider: "anthropic" as ModelProviderName, + }); + + expect(breakdown).not.toBeNull(); + expect(breakdown!.inputCost).toBeCloseTo(5, 10); + expect(breakdown!.outputCost).toBeCloseTo(25, 10); + expect(breakdown!.cacheWrite5mCost).toBeCloseTo(6.25, 10); + expect(breakdown!.cacheWrite1hCost).toBeCloseTo(10, 10); + expect(breakdown!.cachedInputCost).toBeCloseTo(0.5, 10); + }); + + it("prices Sonnet 5 at $2 / $10 with $2.50 / $4 / $0.20 cache rates", () => { + const breakdown = modelCostBreakdownFromRegistry({ + modelUsage: oneMillionOfEverything, + providerModelId: "claude-sonnet-5", + provider: "anthropic" as ModelProviderName, + }); + + expect(breakdown).not.toBeNull(); + expect(breakdown!.inputCost).toBeCloseTo(2, 10); + expect(breakdown!.outputCost).toBeCloseTo(10, 10); + expect(breakdown!.cacheWrite5mCost).toBeCloseTo(2.5, 10); + expect(breakdown!.cacheWrite1hCost).toBeCloseTo(4, 10); + expect(breakdown!.cachedInputCost).toBeCloseTo(0.2, 10); + }); + + it("prices Fable 5 at $10 / $50 with a $1 cache read (0.1x)", () => { + const breakdown = modelCostBreakdownFromRegistry({ + modelUsage: oneMillionOfEverything, + providerModelId: "claude-fable-5", + provider: "anthropic" as ModelProviderName, + }); + + expect(breakdown).not.toBeNull(); + expect(breakdown!.inputCost).toBeCloseTo(10, 10); + expect(breakdown!.outputCost).toBeCloseTo(50, 10); + expect(breakdown!.cacheWrite5mCost).toBeCloseTo(12.5, 10); + expect(breakdown!.cacheWrite1hCost).toBeCloseTo(20, 10); + expect(breakdown!.cachedInputCost).toBeCloseTo(1, 10); + }); + + it("prices Fable 5.1 cache reads at $0.25 per MTok (0.025x), not the 0.1x default", () => { + const breakdown = modelCostBreakdownFromRegistry({ + modelUsage: oneMillionOfEverything, + providerModelId: "claude-fable-5-1", + provider: "anthropic" as ModelProviderName, + }); + + expect(breakdown).not.toBeNull(); + expect(breakdown!.inputCost).toBeCloseTo(10, 10); + expect(breakdown!.outputCost).toBeCloseTo(50, 10); + expect(breakdown!.cacheWrite5mCost).toBeCloseTo(12.5, 10); + expect(breakdown!.cacheWrite1hCost).toBeCloseTo(20, 10); + expect(breakdown!.cachedInputCost).toBeCloseTo(0.25, 10); + }); + + it("legacy table prices claude-opus-4-8 and the Fable 5.1 cache rates as well", () => { + const opus = modelCost({ + provider: "ANTHROPIC", + model: "claude-opus-4-8", + sum_prompt_tokens: 50_000, + prompt_cache_write_tokens: 0, + prompt_cache_read_tokens: 0, + prompt_audio_tokens: 0, + sum_completion_tokens: 15_000, + completion_audio_tokens: 0, + prompt_cache_write_5m: 0, + prompt_cache_write_1h: 0, + }); + expect(opus).toBeCloseTo(0.625, 10); + + const fable51 = modelCost({ + provider: "ANTHROPIC", + model: "claude-fable-5-1", + sum_prompt_tokens: 0, + prompt_cache_write_tokens: 2_000_000, + prompt_cache_read_tokens: 1_000_000, + prompt_audio_tokens: 0, + sum_completion_tokens: 0, + completion_audio_tokens: 0, + prompt_cache_write_5m: 1_000_000, + prompt_cache_write_1h: 1_000_000, + }); + // $0.25 cache read + $12.50 5m write + $20 1h write + expect(fable51).toBeCloseTo(32.75, 10); + }); }); \ No newline at end of file diff --git a/packages/cost/costCalc.ts b/packages/cost/costCalc.ts index 404e31423f..18054d29e6 100644 --- a/packages/cost/costCalc.ts +++ b/packages/cost/costCalc.ts @@ -2,6 +2,9 @@ import { costOfPrompt } from "./index"; import type { ModelUsage } from "./usage/types"; import type { ModelProviderName } from "./models/providers"; import { calculateModelCostBreakdown, CostBreakdown } from "./models/calculate-cost"; +import { registry } from "./models/registry"; +import { heliconeProviderToModelProviderName } from "./models/provider-helpers"; +import type { Provider } from "@helicone-package/llm-mapper/types"; // since costs in clickhouse are multiplied by the multiplier // divide to get real cost in USD in dollars @@ -29,22 +32,50 @@ export function modelCost( multiple?: number; }, ): number { - return ( - costOfPrompt({ - provider: params.provider, - model: params.model, - promptTokens: params.sum_prompt_tokens, - promptCacheWriteTokens: params.prompt_cache_write_tokens, - promptCacheReadTokens: params.prompt_cache_read_tokens, - promptAudioTokens: params.prompt_audio_tokens, - completionTokens: params.sum_completion_tokens, - completionAudioTokens: params.completion_audio_tokens, - promptCacheWrite5m: params.prompt_cache_write_5m, - promptCacheWrite1h: params.prompt_cache_write_1h, - perCall: params.per_call, - images: params.per_image, - multiple: params.multiple, - }) ?? 0 + const cost = costOfPrompt({ + provider: params.provider, + model: params.model, + promptTokens: params.sum_prompt_tokens, + promptCacheWriteTokens: params.prompt_cache_write_tokens, + promptCacheReadTokens: params.prompt_cache_read_tokens, + promptAudioTokens: params.prompt_audio_tokens, + completionTokens: params.sum_completion_tokens, + completionAudioTokens: params.completion_audio_tokens, + promptCacheWrite5m: params.prompt_cache_write_5m, + promptCacheWrite1h: params.prompt_cache_write_1h, + perCall: params.per_call, + images: params.per_image, + multiple: params.multiple, + }); + + if (cost === null) { + warnIfUnpriced(params.provider, params.model); + return 0; + } + + return cost; +} + +/** + * Warn when a model has no pricing in the legacy cost table and none in the + * model registry either. Callers prefer the registry result over the legacy + * one, so a legacy miss on a model the registry prices is expected and stays + * quiet; a miss in both is a request that will be recorded at $0. + */ +function warnIfUnpriced(provider: string, model: string): void { + const modelProvider = heliconeProviderToModelProviderName(provider as Provider); + if (modelProvider) { + const registryResult = registry.getModelProviderConfigByProviderModelId( + model, + modelProvider + ); + if (registryResult.data) { + return; + } + } + + console.warn( + `No pricing found for model "${model}" (provider "${provider}") in the legacy cost table or the model registry; recording cost as 0` ); } diff --git a/packages/cost/models/authors/anthropic/claude-4.7-opus/endpoints.ts b/packages/cost/models/authors/anthropic/claude-4.7-opus/endpoints.ts new file mode 100644 index 0000000000..1a5a328add --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-4.7-opus/endpoints.ts @@ -0,0 +1,43 @@ +import { ModelProviderName } from "../../../providers"; +import type { ModelProviderConfig } from "../../../types"; +import { ClaudeOpus47ModelName } from "./model"; + +export const endpoints = { + "claude-4.7-opus:anthropic": { + providerModelId: "claude-opus-4-7", + provider: "anthropic", + author: "anthropic", + pricing: [ + { + threshold: 0, + input: 0.000005, // $5 / MTok + output: 0.000025, // $25 / MTok + web_search: 0.01, // $10 per 1000 searches (1:1 USD; 10/1K) + cacheMultipliers: { + cachedInput: 0.1, // $0.50 / MTok (10% of $5) + write5m: 1.25, // $6.25 / MTok (125% of $5) + write1h: 2.0, // $10 / MTok (200% of $5) + }, + }, + ], + contextLength: 1000000, + maxCompletionTokens: 128000, + supportedParameters: [ + "max_tokens", + "temperature", + "stop", + "reasoning", + "include_reasoning", + "tools", + "tool_choice", + ], + supportedPlugins: ["web"], + ptbEnabled: true, + responseFormat: "ANTHROPIC", + endpointConfigs: { + "*": {}, + }, + }, +} satisfies Partial< + Record<`${ClaudeOpus47ModelName}:${ModelProviderName}`, ModelProviderConfig> +>; diff --git a/packages/cost/models/authors/anthropic/claude-4.7-opus/model.ts b/packages/cost/models/authors/anthropic/claude-4.7-opus/model.ts new file mode 100644 index 0000000000..6efd43008e --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-4.7-opus/model.ts @@ -0,0 +1,17 @@ +import type { ModelConfig } from "../../../types"; + +export const models = { + "claude-4.7-opus": { + name: "Anthropic: Claude Opus 4.7", + author: "anthropic", + description: + "Claude Opus 4.7 is Anthropic's Opus-tier model released April 2026, now a legacy model succeeded by Claude Opus 5. Features a 1M context window, 128K max output, adaptive thinking, and text and image input. API model name: claude-opus-4-7", + contextLength: 1000000, + maxOutputTokens: 128000, + created: "2026-04-16T00:00:00.000Z", + modality: { inputs: ["text", "image"], outputs: ["text"] }, + tokenizer: "Claude", + }, +} satisfies Record; + +export type ClaudeOpus47ModelName = keyof typeof models; diff --git a/packages/cost/models/authors/anthropic/claude-4.8-opus/endpoints.ts b/packages/cost/models/authors/anthropic/claude-4.8-opus/endpoints.ts new file mode 100644 index 0000000000..c93ab69e4a --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-4.8-opus/endpoints.ts @@ -0,0 +1,43 @@ +import { ModelProviderName } from "../../../providers"; +import type { ModelProviderConfig } from "../../../types"; +import { ClaudeOpus48ModelName } from "./model"; + +export const endpoints = { + "claude-4.8-opus:anthropic": { + providerModelId: "claude-opus-4-8", + provider: "anthropic", + author: "anthropic", + pricing: [ + { + threshold: 0, + input: 0.000005, // $5 / MTok + output: 0.000025, // $25 / MTok + web_search: 0.01, // $10 per 1000 searches (1:1 USD; 10/1K) + cacheMultipliers: { + cachedInput: 0.1, // $0.50 / MTok (10% of $5) + write5m: 1.25, // $6.25 / MTok (125% of $5) + write1h: 2.0, // $10 / MTok (200% of $5) + }, + }, + ], + contextLength: 1000000, + maxCompletionTokens: 128000, + supportedParameters: [ + "max_tokens", + "temperature", + "stop", + "reasoning", + "include_reasoning", + "tools", + "tool_choice", + ], + supportedPlugins: ["web"], + ptbEnabled: true, + responseFormat: "ANTHROPIC", + endpointConfigs: { + "*": {}, + }, + }, +} satisfies Partial< + Record<`${ClaudeOpus48ModelName}:${ModelProviderName}`, ModelProviderConfig> +>; diff --git a/packages/cost/models/authors/anthropic/claude-4.8-opus/model.ts b/packages/cost/models/authors/anthropic/claude-4.8-opus/model.ts new file mode 100644 index 0000000000..002914bbed --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-4.8-opus/model.ts @@ -0,0 +1,17 @@ +import type { ModelConfig } from "../../../types"; + +export const models = { + "claude-4.8-opus": { + name: "Anthropic: Claude Opus 4.8", + author: "anthropic", + description: + "Claude Opus 4.8 is Anthropic's Opus-tier model released May 2026, now a legacy model succeeded by Claude Opus 5. Features a 1M context window, 128K max output, adaptive thinking, and text and image input. API model name: claude-opus-4-8", + contextLength: 1000000, + maxOutputTokens: 128000, + created: "2026-05-28T00:00:00.000Z", + modality: { inputs: ["text", "image"], outputs: ["text"] }, + tokenizer: "Claude", + }, +} satisfies Record; + +export type ClaudeOpus48ModelName = keyof typeof models; diff --git a/packages/cost/models/authors/anthropic/claude-5-fable/endpoints.ts b/packages/cost/models/authors/anthropic/claude-5-fable/endpoints.ts new file mode 100644 index 0000000000..d75e0e98e4 --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-5-fable/endpoints.ts @@ -0,0 +1,43 @@ +import { ModelProviderName } from "../../../providers"; +import type { ModelProviderConfig } from "../../../types"; +import { ClaudeFable5ModelName } from "./model"; + +export const endpoints = { + "claude-5-fable:anthropic": { + providerModelId: "claude-fable-5", + provider: "anthropic", + author: "anthropic", + pricing: [ + { + threshold: 0, + input: 0.00001, // $10 / MTok + output: 0.00005, // $50 / MTok + web_search: 0.01, // $10 per 1000 searches (1:1 USD; 10/1K) + cacheMultipliers: { + cachedInput: 0.1, // $1 / MTok (10% of $10) + write5m: 1.25, // $12.50 / MTok (125% of $10) + write1h: 2.0, // $20 / MTok (200% of $10) + }, + }, + ], + contextLength: 1000000, + maxCompletionTokens: 128000, + supportedParameters: [ + "max_tokens", + "temperature", + "stop", + "reasoning", + "include_reasoning", + "tools", + "tool_choice", + ], + supportedPlugins: ["web"], + ptbEnabled: true, + responseFormat: "ANTHROPIC", + endpointConfigs: { + "*": {}, + }, + }, +} satisfies Partial< + Record<`${ClaudeFable5ModelName}:${ModelProviderName}`, ModelProviderConfig> +>; diff --git a/packages/cost/models/authors/anthropic/claude-5-fable/model.ts b/packages/cost/models/authors/anthropic/claude-5-fable/model.ts new file mode 100644 index 0000000000..f244d769ce --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-5-fable/model.ts @@ -0,0 +1,17 @@ +import type { ModelConfig } from "../../../types"; + +export const models = { + "claude-5-fable": { + name: "Anthropic: Claude Fable 5", + author: "anthropic", + description: + "Claude Fable 5 is Anthropic's Fable-tier model released June 2026 for demanding reasoning and long-horizon agentic work, now a legacy model succeeded by Claude Fable 5.1. Features a 1M context window, 128K max output, adaptive thinking (always on), and text and image input. API model name: claude-fable-5", + contextLength: 1000000, + maxOutputTokens: 128000, + created: "2026-06-09T00:00:00.000Z", + modality: { inputs: ["text", "image"], outputs: ["text"] }, + tokenizer: "Claude", + }, +} satisfies Record; + +export type ClaudeFable5ModelName = keyof typeof models; diff --git a/packages/cost/models/authors/anthropic/claude-5-opus/endpoints.ts b/packages/cost/models/authors/anthropic/claude-5-opus/endpoints.ts new file mode 100644 index 0000000000..f1939e2f80 --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-5-opus/endpoints.ts @@ -0,0 +1,43 @@ +import { ModelProviderName } from "../../../providers"; +import type { ModelProviderConfig } from "../../../types"; +import { ClaudeOpus5ModelName } from "./model"; + +export const endpoints = { + "claude-5-opus:anthropic": { + providerModelId: "claude-opus-5", + provider: "anthropic", + author: "anthropic", + pricing: [ + { + threshold: 0, + input: 0.000005, // $5 / MTok + output: 0.000025, // $25 / MTok + web_search: 0.01, // $10 per 1000 searches (1:1 USD; 10/1K) + cacheMultipliers: { + cachedInput: 0.1, // $0.50 / MTok (10% of $5) + write5m: 1.25, // $6.25 / MTok (125% of $5) + write1h: 2.0, // $10 / MTok (200% of $5) + }, + }, + ], + contextLength: 1000000, + maxCompletionTokens: 128000, + supportedParameters: [ + "max_tokens", + "temperature", + "stop", + "reasoning", + "include_reasoning", + "tools", + "tool_choice", + ], + supportedPlugins: ["web"], + ptbEnabled: true, + responseFormat: "ANTHROPIC", + endpointConfigs: { + "*": {}, + }, + }, +} satisfies Partial< + Record<`${ClaudeOpus5ModelName}:${ModelProviderName}`, ModelProviderConfig> +>; diff --git a/packages/cost/models/authors/anthropic/claude-5-opus/model.ts b/packages/cost/models/authors/anthropic/claude-5-opus/model.ts new file mode 100644 index 0000000000..401476e755 --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-5-opus/model.ts @@ -0,0 +1,17 @@ +import type { ModelConfig } from "../../../types"; + +export const models = { + "claude-5-opus": { + name: "Anthropic: Claude Opus 5", + author: "anthropic", + description: + "Claude Opus 5 is Anthropic's current Opus model, released July 2026, for complex agentic coding and enterprise work. Features a 1M context window, 128K max output, adaptive thinking on by default, and text and image input. API model name: claude-opus-5", + contextLength: 1000000, + maxOutputTokens: 128000, + created: "2026-07-24T00:00:00.000Z", + modality: { inputs: ["text", "image"], outputs: ["text"] }, + tokenizer: "Claude", + }, +} satisfies Record; + +export type ClaudeOpus5ModelName = keyof typeof models; diff --git a/packages/cost/models/authors/anthropic/claude-5-sonnet/endpoints.ts b/packages/cost/models/authors/anthropic/claude-5-sonnet/endpoints.ts new file mode 100644 index 0000000000..8f153d1f88 --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-5-sonnet/endpoints.ts @@ -0,0 +1,43 @@ +import { ModelProviderName } from "../../../providers"; +import type { ModelProviderConfig } from "../../../types"; +import { ClaudeSonnet5ModelName } from "./model"; + +export const endpoints = { + "claude-5-sonnet:anthropic": { + providerModelId: "claude-sonnet-5", + provider: "anthropic", + author: "anthropic", + pricing: [ + { + threshold: 0, + input: 0.000002, // $2 / MTok + output: 0.00001, // $10 / MTok + web_search: 0.01, // $10 per 1000 searches (1:1 USD; 10/1K) + cacheMultipliers: { + cachedInput: 0.1, // $0.20 / MTok (10% of $2) + write5m: 1.25, // $2.50 / MTok (125% of $2) + write1h: 2.0, // $4 / MTok (200% of $2) + }, + }, + ], + contextLength: 1000000, + maxCompletionTokens: 128000, + supportedParameters: [ + "max_tokens", + "temperature", + "stop", + "reasoning", + "include_reasoning", + "tools", + "tool_choice", + ], + supportedPlugins: ["web"], + ptbEnabled: true, + responseFormat: "ANTHROPIC", + endpointConfigs: { + "*": {}, + }, + }, +} satisfies Partial< + Record<`${ClaudeSonnet5ModelName}:${ModelProviderName}`, ModelProviderConfig> +>; diff --git a/packages/cost/models/authors/anthropic/claude-5-sonnet/model.ts b/packages/cost/models/authors/anthropic/claude-5-sonnet/model.ts new file mode 100644 index 0000000000..cadc0ab9d0 --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-5-sonnet/model.ts @@ -0,0 +1,17 @@ +import type { ModelConfig } from "../../../types"; + +export const models = { + "claude-5-sonnet": { + name: "Anthropic: Claude Sonnet 5", + author: "anthropic", + description: + "Claude Sonnet 5 is Anthropic's current Sonnet model, released June 2026, offering the best combination of speed and intelligence. Features a 1M context window, 128K max output, adaptive thinking on by default, and text and image input. API model name: claude-sonnet-5", + contextLength: 1000000, + maxOutputTokens: 128000, + created: "2026-06-30T00:00:00.000Z", + modality: { inputs: ["text", "image"], outputs: ["text"] }, + tokenizer: "Claude", + }, +} satisfies Record; + +export type ClaudeSonnet5ModelName = keyof typeof models; diff --git a/packages/cost/models/authors/anthropic/claude-5.1-fable/endpoints.ts b/packages/cost/models/authors/anthropic/claude-5.1-fable/endpoints.ts new file mode 100644 index 0000000000..b7a2a5f252 --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-5.1-fable/endpoints.ts @@ -0,0 +1,43 @@ +import { ModelProviderName } from "../../../providers"; +import type { ModelProviderConfig } from "../../../types"; +import { ClaudeFable51ModelName } from "./model"; + +export const endpoints = { + "claude-5.1-fable:anthropic": { + providerModelId: "claude-fable-5-1", + provider: "anthropic", + author: "anthropic", + pricing: [ + { + threshold: 0, + input: 0.00001, // $10 / MTok + output: 0.00005, // $50 / MTok + web_search: 0.01, // $10 per 1000 searches (1:1 USD; 10/1K) + cacheMultipliers: { + cachedInput: 0.025, // $0.25 / MTok (2.5% of $10; 0.025x on Fable 5.1, not the usual 0.1x) + write5m: 1.25, // $12.50 / MTok (125% of $10) + write1h: 2.0, // $20 / MTok (200% of $10) + }, + }, + ], + contextLength: 1000000, + maxCompletionTokens: 128000, + supportedParameters: [ + "max_tokens", + "temperature", + "stop", + "reasoning", + "include_reasoning", + "tools", + "tool_choice", + ], + supportedPlugins: ["web"], + ptbEnabled: true, + responseFormat: "ANTHROPIC", + endpointConfigs: { + "*": {}, + }, + }, +} satisfies Partial< + Record<`${ClaudeFable51ModelName}:${ModelProviderName}`, ModelProviderConfig> +>; diff --git a/packages/cost/models/authors/anthropic/claude-5.1-fable/model.ts b/packages/cost/models/authors/anthropic/claude-5.1-fable/model.ts new file mode 100644 index 0000000000..9988db9112 --- /dev/null +++ b/packages/cost/models/authors/anthropic/claude-5.1-fable/model.ts @@ -0,0 +1,17 @@ +import type { ModelConfig } from "../../../types"; + +export const models = { + "claude-5.1-fable": { + name: "Anthropic: Claude Fable 5.1", + author: "anthropic", + description: + "Claude Fable 5.1 is Anthropic's most capable widely released model, released September 2026, for demanding reasoning and long-horizon agentic work. Features a 1M context window, 128K max output, adaptive thinking (always on), text and image input, and cache reads priced at 0.025x the base input price. API model name: claude-fable-5-1", + contextLength: 1000000, + maxOutputTokens: 128000, + created: "2026-09-01T00:00:00.000Z", + modality: { inputs: ["text", "image"], outputs: ["text"] }, + tokenizer: "Claude", + }, +} satisfies Record; + +export type ClaudeFable51ModelName = keyof typeof models; diff --git a/packages/cost/models/authors/anthropic/index.ts b/packages/cost/models/authors/anthropic/index.ts index 28d82637c4..e5a863349b 100644 --- a/packages/cost/models/authors/anthropic/index.ts +++ b/packages/cost/models/authors/anthropic/index.ts @@ -21,6 +21,12 @@ import { models as claudeOpus4120250805Models } from "./claude-opus-4-1-20250805 import { models as claudeOpus45Models } from "./claude-4.5-opus/model"; import { models as claudeOpus46Models } from "./claude-4.6-opus/model"; import { models as claudeSonnet46Models } from "./claude-4.6-sonnet/model"; +import { models as claudeOpus47Models } from "./claude-4.7-opus/model"; +import { models as claudeOpus48Models } from "./claude-4.8-opus/model"; +import { models as claudeOpus5Models } from "./claude-5-opus/model"; +import { models as claudeSonnet5Models } from "./claude-5-sonnet/model"; +import { models as claudeFable5Models } from "./claude-5-fable/model"; +import { models as claudeFable51Models } from "./claude-5.1-fable/model"; // Import endpoints import { endpoints as claudeOpus41Endpoints } from "./claude-opus-4-1/endpoints"; @@ -38,6 +44,12 @@ import { endpoints as claudeOpus4120250805Endpoints } from "./claude-opus-4-1-20 import { endpoints as claudeOpus45Endpoints } from "./claude-4.5-opus/endpoints"; import { endpoints as claudeOpus46Endpoints } from "./claude-4.6-opus/endpoints"; import { endpoints as claudeSonnet46Endpoints } from "./claude-4.6-sonnet/endpoints"; +import { endpoints as claudeOpus47Endpoints } from "./claude-4.7-opus/endpoints"; +import { endpoints as claudeOpus48Endpoints } from "./claude-4.8-opus/endpoints"; +import { endpoints as claudeOpus5Endpoints } from "./claude-5-opus/endpoints"; +import { endpoints as claudeSonnet5Endpoints } from "./claude-5-sonnet/endpoints"; +import { endpoints as claudeFable5Endpoints } from "./claude-5-fable/endpoints"; +import { endpoints as claudeFable51Endpoints } from "./claude-5.1-fable/endpoints"; // Aggregate models export const anthropicModels = { @@ -56,6 +68,12 @@ export const anthropicModels = { ...claudeOpus45Models, ...claudeOpus46Models, ...claudeSonnet46Models, + ...claudeOpus47Models, + ...claudeOpus48Models, + ...claudeOpus5Models, + ...claudeSonnet5Models, + ...claudeFable5Models, + ...claudeFable51Models, } satisfies Record; // Aggregate endpoints @@ -75,4 +93,10 @@ export const anthropicEndpointConfig = { ...claudeOpus45Endpoints, ...claudeOpus46Endpoints, ...claudeSonnet46Endpoints, + ...claudeOpus47Endpoints, + ...claudeOpus48Endpoints, + ...claudeOpus5Endpoints, + ...claudeSonnet5Endpoints, + ...claudeFable5Endpoints, + ...claudeFable51Endpoints, } satisfies Record; diff --git a/packages/cost/models/calculate-cost.ts b/packages/cost/models/calculate-cost.ts index 860396a924..c2785e56a1 100644 --- a/packages/cost/models/calculate-cost.ts +++ b/packages/cost/models/calculate-cost.ts @@ -178,7 +178,12 @@ export function calculateModelCostBreakdown(params: { providerModelId, provider, ); - if (configResult.error || !configResult.data) return null; + if (configResult.error || !configResult.data) { + console.warn( + `No pricing found in the model registry for providerModelId "${providerModelId}" (provider "${provider}"); cost cannot be calculated` + ); + return null; + } const config: ModelProviderConfig = configResult.data; diff --git a/packages/cost/providers/anthropic/index.ts b/packages/cost/providers/anthropic/index.ts index dbedf90e9a..2b46faf4eb 100644 --- a/packages/cost/providers/anthropic/index.ts +++ b/packages/cost/providers/anthropic/index.ts @@ -288,6 +288,90 @@ export const costs: ModelRow[] = [ }, showInPlayground: true, }, + { + model: { + operator: "equals", + value: "claude-opus-4-7", + }, + cost: { + prompt_token: 0.000005, // $5 / MTok + completion_token: 0.000025, // $25 / MTok + prompt_cache_write_token: 0.00000625, // 5m cache write: $6.25 / MTok + prompt_cache_read_token: 0.0000005, // Cache hits/refreshes: $0.50 / MTok + prompt_cache_creation_5m: 0.00000625, // $6.25 / MTok + prompt_cache_creation_1h: 0.00001, // 1h cache write: $10 / MTok + }, + }, + { + model: { + operator: "equals", + value: "claude-opus-4-8", + }, + cost: { + prompt_token: 0.000005, // $5 / MTok + completion_token: 0.000025, // $25 / MTok + prompt_cache_write_token: 0.00000625, // 5m cache write: $6.25 / MTok + prompt_cache_read_token: 0.0000005, // Cache hits/refreshes: $0.50 / MTok + prompt_cache_creation_5m: 0.00000625, // $6.25 / MTok + prompt_cache_creation_1h: 0.00001, // 1h cache write: $10 / MTok + }, + }, + { + model: { + operator: "equals", + value: "claude-opus-5", + }, + cost: { + prompt_token: 0.000005, // $5 / MTok + completion_token: 0.000025, // $25 / MTok + prompt_cache_write_token: 0.00000625, // 5m cache write: $6.25 / MTok + prompt_cache_read_token: 0.0000005, // Cache hits/refreshes: $0.50 / MTok + prompt_cache_creation_5m: 0.00000625, // $6.25 / MTok + prompt_cache_creation_1h: 0.00001, // 1h cache write: $10 / MTok + }, + }, + { + model: { + operator: "equals", + value: "claude-sonnet-5", + }, + cost: { + prompt_token: 0.000002, // $2 / MTok + completion_token: 0.00001, // $10 / MTok + prompt_cache_write_token: 0.0000025, // 5m cache write: $2.50 / MTok + prompt_cache_read_token: 0.0000002, // Cache hits/refreshes: $0.20 / MTok + prompt_cache_creation_5m: 0.0000025, // $2.50 / MTok + prompt_cache_creation_1h: 0.000004, // 1h cache write: $4 / MTok + }, + }, + { + model: { + operator: "equals", + value: "claude-fable-5", + }, + cost: { + prompt_token: 0.00001, // $10 / MTok + completion_token: 0.00005, // $50 / MTok + prompt_cache_write_token: 0.0000125, // 5m cache write: $12.50 / MTok + prompt_cache_read_token: 0.000001, // Cache hits/refreshes: $1 / MTok + prompt_cache_creation_5m: 0.0000125, // $12.50 / MTok + prompt_cache_creation_1h: 0.00002, // 1h cache write: $20 / MTok + }, + }, + { + model: { + operator: "equals", + value: "claude-fable-5-1", + }, + cost: { + prompt_token: 0.00001, // $10 / MTok + completion_token: 0.00005, // $50 / MTok + prompt_cache_write_token: 0.0000125, // 5m cache write: $12.50 / MTok + prompt_cache_read_token: 0.00000025, // Cache hits/refreshes: $0.25 / MTok (0.025x on Fable 5.1) + prompt_cache_creation_5m: 0.0000125, // $12.50 / MTok + prompt_cache_creation_1h: 0.00002, // 1h cache write: $20 / MTok + }, + }, ]; export const modelDetails: ModelDetailsMap = {