diff --git a/AGENTS.md b/AGENTS.md index 4b14710..81cd66e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -106,3 +106,8 @@ files themselves. above were found by executing that step and are fixed. It will fire on the next genuine upstream change, or by the 30-day `last_verified` re-stamp at the latest. +- 2026-09-04 — Coverage is a closed loop, enforced by a test: every id in a + provider's `tracked` list must have a record in `data/`, and every record must + be tracked. Adding an id without seeding the record turns the next scrape red, + because context windows and modalities are not scrapeable and `normalize()` + will not invent them. Seed the record in the same PR. diff --git a/data/anthropic.json b/data/anthropic.json index 3a2459e..8b0d610 100644 --- a/data/anthropic.json +++ b/data/anthropic.json @@ -1,6 +1,45 @@ { "schema_version": "1.0.0", "models": [ + { + "id": "anthropic:claude-fable-5", + "provider": "anthropic", + "model_id": "claude-fable-5", + "display_name": "Claude Fable 5", + "family": "claude", + "status": "available", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "context_window": 1000000, + "max_output_tokens": 128000, + "pricing": { + "currency": "USD", + "input_per_mtok": 10, + "output_per_mtok": 50, + "cached_input_per_mtok": 1, + "batch_input_per_mtok": 5, + "batch_output_per_mtok": 25 + }, + "dates": { + "released": "2026-06-09", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://platform.claude.com/docs/en/about-claude/model-deprecations", + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/fable-5/overview" + ], + "last_verified": "2026-09-04", + "notes": "Legacy. Retirement committed not sooner than 2027-06-09. Cache reads are the usual 10% of base input, unlike Claude Fable 5.1." + }, { "id": "anthropic:claude-fable-5-1", "provider": "anthropic", @@ -28,14 +67,15 @@ "batch_output_per_mtok": 25 }, "dates": { - "released": null, + "released": "2026-09-01", "deprecated": null, "retired": null }, "sources": [ "https://platform.claude.com/docs/en/about-claude/model-deprecations", "https://platform.claude.com/docs/en/about-claude/models/overview", - "https://platform.claude.com/docs/en/about-claude/pricing" + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/fable-5-1/overview" ], "last_verified": "2026-09-04", "notes": "Retirement committed not sooner than 2027-09-01. Cache reads are 2.5% of base input on this model, not the usual 10%." @@ -74,11 +114,168 @@ "sources": [ "https://platform.claude.com/docs/en/about-claude/model-deprecations", "https://platform.claude.com/docs/en/about-claude/models/overview", - "https://platform.claude.com/docs/en/about-claude/pricing" + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/haiku-4-5/overview" ], "last_verified": "2026-09-04", "notes": "Alias claude-haiku-4-5 resolves to this snapshot. Retirement committed not sooner than 2026-10-15." }, + { + "id": "anthropic:claude-opus-4-5-20251101", + "provider": "anthropic", + "model_id": "claude-opus-4-5-20251101", + "display_name": "Claude Opus 4.5", + "family": "claude", + "status": "available", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "context_window": 200000, + "max_output_tokens": 64000, + "pricing": { + "currency": "USD", + "input_per_mtok": 5, + "output_per_mtok": 25, + "cached_input_per_mtok": 0.5, + "batch_input_per_mtok": 2.5, + "batch_output_per_mtok": 12.5 + }, + "dates": { + "released": "2025-11-24", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://platform.claude.com/docs/en/about-claude/model-deprecations", + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/opus-4-5/overview" + ], + "last_verified": "2026-09-04", + "notes": "Legacy. Alias claude-opus-4-5 resolves to this snapshot. Retirement committed not sooner than 2026-11-24." + }, + { + "id": "anthropic:claude-opus-4-6", + "provider": "anthropic", + "model_id": "claude-opus-4-6", + "display_name": "Claude Opus 4.6", + "family": "claude", + "status": "available", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "context_window": 1000000, + "max_output_tokens": 128000, + "pricing": { + "currency": "USD", + "input_per_mtok": 5, + "output_per_mtok": 25, + "cached_input_per_mtok": 0.5, + "batch_input_per_mtok": 2.5, + "batch_output_per_mtok": 12.5 + }, + "dates": { + "released": "2026-02-05", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://platform.claude.com/docs/en/about-claude/model-deprecations", + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/opus-4-6/overview" + ], + "last_verified": "2026-09-04", + "notes": "Legacy. Retirement committed not sooner than 2027-02-05." + }, + { + "id": "anthropic:claude-opus-4-7", + "provider": "anthropic", + "model_id": "claude-opus-4-7", + "display_name": "Claude Opus 4.7", + "family": "claude", + "status": "available", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "context_window": 1000000, + "max_output_tokens": 128000, + "pricing": { + "currency": "USD", + "input_per_mtok": 5, + "output_per_mtok": 25, + "cached_input_per_mtok": 0.5, + "batch_input_per_mtok": 2.5, + "batch_output_per_mtok": 12.5 + }, + "dates": { + "released": "2026-04-16", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://platform.claude.com/docs/en/about-claude/model-deprecations", + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/opus-4-7/overview" + ], + "last_verified": "2026-09-04", + "notes": "Legacy. Retirement committed not sooner than 2027-04-16. First model on the current tokenizer, which produces about 30% more tokens for the same text than earlier models." + }, + { + "id": "anthropic:claude-opus-4-8", + "provider": "anthropic", + "model_id": "claude-opus-4-8", + "display_name": "Claude Opus 4.8", + "family": "claude", + "status": "available", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "context_window": 1000000, + "max_output_tokens": 128000, + "pricing": { + "currency": "USD", + "input_per_mtok": 5, + "output_per_mtok": 25, + "cached_input_per_mtok": 0.5, + "batch_input_per_mtok": 2.5, + "batch_output_per_mtok": 12.5 + }, + "dates": { + "released": "2026-05-28", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://platform.claude.com/docs/en/about-claude/model-deprecations", + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/opus-4-8/overview" + ], + "last_verified": "2026-09-04", + "notes": "Legacy. Retirement committed not sooner than 2027-05-28. Supports 300K output tokens on the Batch API with a beta header." + }, { "id": "anthropic:claude-opus-5", "provider": "anthropic", @@ -106,18 +303,97 @@ "batch_output_per_mtok": 12.5 }, "dates": { - "released": null, + "released": "2026-07-24", "deprecated": null, "retired": null }, "sources": [ "https://platform.claude.com/docs/en/about-claude/model-deprecations", "https://platform.claude.com/docs/en/about-claude/models/overview", - "https://platform.claude.com/docs/en/about-claude/pricing" + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/opus-5/overview" ], "last_verified": "2026-09-04", "notes": "Retirement committed not sooner than 2027-07-24. cached_input_per_mtok is the cache-read (hit) price; 5m/1h cache writes cost 1.25x/2x base input." }, + { + "id": "anthropic:claude-sonnet-4-5-20250929", + "provider": "anthropic", + "model_id": "claude-sonnet-4-5-20250929", + "display_name": "Claude Sonnet 4.5", + "family": "claude", + "status": "available", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "context_window": 200000, + "max_output_tokens": 64000, + "pricing": { + "currency": "USD", + "input_per_mtok": 3, + "output_per_mtok": 15, + "cached_input_per_mtok": 0.3, + "batch_input_per_mtok": 1.5, + "batch_output_per_mtok": 7.5 + }, + "dates": { + "released": "2025-09-29", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://platform.claude.com/docs/en/about-claude/model-deprecations", + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/sonnet-4-5/overview" + ], + "last_verified": "2026-09-04", + "notes": "Legacy. Alias claude-sonnet-4-5 resolves to this snapshot. Retirement committed not sooner than 2026-09-29." + }, + { + "id": "anthropic:claude-sonnet-4-6", + "provider": "anthropic", + "model_id": "claude-sonnet-4-6", + "display_name": "Claude Sonnet 4.6", + "family": "claude", + "status": "available", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "context_window": 1000000, + "max_output_tokens": 128000, + "pricing": { + "currency": "USD", + "input_per_mtok": 3, + "output_per_mtok": 15, + "cached_input_per_mtok": 0.3, + "batch_input_per_mtok": 1.5, + "batch_output_per_mtok": 7.5 + }, + "dates": { + "released": "2026-02-17", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://platform.claude.com/docs/en/about-claude/model-deprecations", + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/sonnet-4-6/overview" + ], + "last_verified": "2026-09-04", + "notes": "Legacy. Retirement committed not sooner than 2027-02-17." + }, { "id": "anthropic:claude-sonnet-5", "provider": "anthropic", @@ -145,14 +421,15 @@ "batch_output_per_mtok": 5 }, "dates": { - "released": null, + "released": "2026-06-30", "deprecated": null, "retired": null }, "sources": [ "https://platform.claude.com/docs/en/about-claude/model-deprecations", "https://platform.claude.com/docs/en/about-claude/models/overview", - "https://platform.claude.com/docs/en/about-claude/pricing" + "https://platform.claude.com/docs/en/about-claude/pricing", + "https://platform.claude.com/docs/en/models/sonnet-5/overview" ], "last_verified": "2026-09-04", "notes": "Retirement committed not sooner than 2027-06-30. Introductory $2/$10 pricing was made permanent; the planned 2026-09-01 increase was cancelled." diff --git a/data/google.json b/data/google.json index 22bd9f4..54e3e0c 100644 --- a/data/google.json +++ b/data/google.json @@ -34,6 +34,90 @@ "last_verified": "2026-09-04", "notes": "Shut down 2026-06-01; provider's recommended replacement is gemini-3.6-flash. Deprecation announcement date is not published on the deprecations page." }, + { + "id": "google:gemini-2.5-flash", + "provider": "google", + "model_id": "gemini-2.5-flash", + "display_name": "Gemini 2.5 Flash", + "family": "gemini", + "status": "available", + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "context_window": 1048576, + "max_output_tokens": 65536, + "pricing": { + "currency": "USD", + "input_per_mtok": 0.3, + "output_per_mtok": 2.5, + "cached_input_per_mtok": 0.03, + "batch_input_per_mtok": 0.15, + "batch_output_per_mtok": 1.25 + }, + "dates": { + "released": "2025-06-17", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://ai.google.dev/gemini-api/docs/deprecations", + "https://ai.google.dev/gemini-api/docs/models", + "https://ai.google.dev/gemini-api/docs/models/gemini-2-5-flash", + "https://ai.google.dev/gemini-api/docs/pricing" + ], + "last_verified": "2026-09-04", + "notes": "Input price shown is the text, image and video rate; audio input is billed higher." + }, + { + "id": "google:gemini-2.5-flash-lite", + "provider": "google", + "model_id": "gemini-2.5-flash-lite", + "display_name": "Gemini 2.5 Flash-Lite", + "family": "gemini", + "status": "available", + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "context_window": 1048576, + "max_output_tokens": 65536, + "pricing": { + "currency": "USD", + "input_per_mtok": 0.1, + "output_per_mtok": 0.4, + "cached_input_per_mtok": 0.01, + "batch_input_per_mtok": 0.05, + "batch_output_per_mtok": 0.2 + }, + "dates": { + "released": "2025-07-22", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://ai.google.dev/gemini-api/docs/deprecations", + "https://ai.google.dev/gemini-api/docs/models", + "https://ai.google.dev/gemini-api/docs/models/gemini-2-5-flash-lite", + "https://ai.google.dev/gemini-api/docs/pricing" + ], + "last_verified": "2026-09-04", + "notes": "Input price shown is the text, image and video rate; audio input is billed higher." + }, { "id": "google:gemini-2.5-pro", "provider": "google", @@ -76,6 +160,172 @@ "last_verified": "2026-09-04", "notes": "Prices shown are the <=200K-token prompt tier. Prompts over 200K tokens: $2.50 input / $15.00 output per MTok. No shutdown date announced." }, + { + "id": "google:gemini-3.5-flash", + "provider": "google", + "model_id": "gemini-3.5-flash", + "display_name": "Gemini 3.5 Flash", + "family": "gemini", + "status": "available", + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "context_window": 1048576, + "max_output_tokens": 65536, + "pricing": { + "currency": "USD", + "input_per_mtok": 1.5, + "output_per_mtok": 9, + "cached_input_per_mtok": 0.15, + "batch_input_per_mtok": 0.75, + "batch_output_per_mtok": 4.5 + }, + "dates": { + "released": "2026-05-19", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://ai.google.dev/gemini-api/docs/deprecations", + "https://ai.google.dev/gemini-api/docs/models", + "https://ai.google.dev/gemini-api/docs/models/gemini-3-5-flash", + "https://ai.google.dev/gemini-api/docs/pricing" + ], + "last_verified": "2026-09-04" + }, + { + "id": "google:gemini-3.5-flash-lite", + "provider": "google", + "model_id": "gemini-3.5-flash-lite", + "display_name": "Gemini 3.5 Flash-Lite", + "family": "gemini", + "status": "available", + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "context_window": 1048576, + "max_output_tokens": 65536, + "pricing": { + "currency": "USD", + "input_per_mtok": 0.3, + "output_per_mtok": 2.5, + "cached_input_per_mtok": 0.03, + "batch_input_per_mtok": 0.15, + "batch_output_per_mtok": 1.25 + }, + "dates": { + "released": "2026-07-21", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://ai.google.dev/gemini-api/docs/deprecations", + "https://ai.google.dev/gemini-api/docs/models", + "https://ai.google.dev/gemini-api/docs/models/gemini-3-5-flash-lite", + "https://ai.google.dev/gemini-api/docs/pricing" + ], + "last_verified": "2026-09-04" + }, + { + "id": "google:gemini-3.6-flash", + "provider": "google", + "model_id": "gemini-3.6-flash", + "display_name": "Gemini 3.6 Flash", + "family": "gemini", + "status": "available", + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "context_window": 1048576, + "max_output_tokens": 65536, + "pricing": { + "currency": "USD", + "input_per_mtok": 0.75, + "output_per_mtok": 3.75, + "cached_input_per_mtok": 0.075, + "batch_input_per_mtok": 0.375, + "batch_output_per_mtok": 1.875 + }, + "dates": { + "released": "2026-07-21", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://ai.google.dev/gemini-api/docs/deprecations", + "https://ai.google.dev/gemini-api/docs/models", + "https://ai.google.dev/gemini-api/docs/models/gemini-3-6-flash", + "https://ai.google.dev/gemini-api/docs/pricing" + ], + "last_verified": "2026-09-04", + "notes": "Paid tier. Provider states these prices apply through 2026-12-31 and double on 2027-01-01." + }, + { + "id": "google:gemini-3.7-flash", + "provider": "google", + "model_id": "gemini-3.7-flash", + "display_name": "Gemini 3.7 Flash", + "family": "gemini", + "status": "available", + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "context_window": 1048576, + "max_output_tokens": 65536, + "pricing": { + "currency": "USD", + "input_per_mtok": 0.75, + "output_per_mtok": 3.75, + "cached_input_per_mtok": 0.075, + "batch_input_per_mtok": 0.375, + "batch_output_per_mtok": 1.875 + }, + "dates": { + "released": "2026-08-13", + "deprecated": null, + "retired": null + }, + "sources": [ + "https://ai.google.dev/gemini-api/docs/deprecations", + "https://ai.google.dev/gemini-api/docs/models", + "https://ai.google.dev/gemini-api/docs/models/gemini-3-7-flash", + "https://ai.google.dev/gemini-api/docs/pricing" + ], + "last_verified": "2026-09-04", + "notes": "Paid tier. Provider states these prices apply through 2026-12-31 and double on 2027-01-01." + }, { "id": "google:gemini-3.8-flash", "provider": "google", @@ -112,6 +362,7 @@ "sources": [ "https://ai.google.dev/gemini-api/docs/deprecations", "https://ai.google.dev/gemini-api/docs/models", + "https://ai.google.dev/gemini-api/docs/models/gemini-3-8-flash", "https://ai.google.dev/gemini-api/docs/pricing" ], "last_verified": "2026-09-04", diff --git a/scrapers/providers/anthropic.js b/scrapers/providers/anthropic.js index f7b94a6..45632c8 100644 --- a/scrapers/providers/anthropic.js +++ b/scrapers/providers/anthropic.js @@ -52,8 +52,15 @@ export const sources = [ // run.js as an untracked model rather than silently dropped or half-populated. export const tracked = [ { model_id: "claude-fable-5-1" }, + { model_id: "claude-fable-5" }, { model_id: "claude-opus-5" }, + { model_id: "claude-opus-4-8" }, + { model_id: "claude-opus-4-7" }, + { model_id: "claude-opus-4-6" }, + { model_id: "claude-opus-4-5-20251101", aliases: ["claude-opus-4-5"] }, { model_id: "claude-sonnet-5" }, + { model_id: "claude-sonnet-4-6" }, + { model_id: "claude-sonnet-4-5-20250929", aliases: ["claude-sonnet-4-5"] }, { model_id: "claude-haiku-4-5-20251001", aliases: ["claude-haiku-4-5"] }, ]; diff --git a/scrapers/providers/google.js b/scrapers/providers/google.js index f466738..d04ce4c 100644 --- a/scrapers/providers/google.js +++ b/scrapers/providers/google.js @@ -36,7 +36,13 @@ export const sources = [ export const tracked = [ { model_id: "gemini-3.8-flash" }, + { model_id: "gemini-3.7-flash" }, + { model_id: "gemini-3.6-flash" }, + { model_id: "gemini-3.5-flash" }, + { model_id: "gemini-3.5-flash-lite" }, { model_id: "gemini-2.5-pro" }, + { model_id: "gemini-2.5-flash" }, + { model_id: "gemini-2.5-flash-lite" }, { model_id: "gemini-2.0-flash" }, ]; diff --git a/test/data.test.js b/test/data.test.js index f7c4280..fb210ac 100644 --- a/test/data.test.js +++ b/test/data.test.js @@ -1,6 +1,9 @@ // Cross-file invariants (build spec §4.3). import { test } from "node:test"; import assert from "node:assert/strict"; +import { readdirSync } from "node:fs"; +import { join, dirname, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { listDataFiles, readDataFile } from "../scrapers/lib/schema.js"; // Allowed hostnames for `sources`, per provider. Extend deliberately. @@ -104,3 +107,35 @@ test("retired models have no pricing, priced models have positive prices", () => } } }); + +// Coverage must be a closed loop: a model named in a provider's `tracked` list +// has to exist in data/, because context windows and modalities are not +// scrapeable and normalize() will not invent them. Without this, adding an id +// to `tracked` and forgetting to seed the record turns the next scrape red. +const providersDir = resolve( + dirname(fileURLToPath(import.meta.url)), + "..", + "scrapers", + "providers", +); + +test("every tracked model has a record, and every record is tracked", async () => { + for (const file of readdirSync(providersDir).filter((f) => f.endsWith(".js"))) { + const mod = await import(pathToFileURL(join(providersDir, file)).href); + const recorded = new Set(all.filter((m) => m.provider === mod.provider).map((m) => m.model_id)); + const trackedIds = new Set(mod.tracked.map((t) => t.model_id)); + + for (const id of trackedIds) { + assert.ok( + recorded.has(id), + `${mod.provider} tracks ${id} but data/${mod.provider}.json has no record`, + ); + } + for (const id of recorded) { + assert.ok( + trackedIds.has(id), + `data/${mod.provider}.json records ${id} but the module does not track it`, + ); + } + } +});