From a481a89e628b4cdb151a03868357d73c4e1cee64 Mon Sep 17 00:00:00 2001 From: k1riiiii <128486679+k1riiiii@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:54:26 +0000 Subject: [PATCH 1/2] feat(catalog): add Claude Opus 5 entries for Amazon Bedrock Changes: - Add Amazon Bedrock catalog entries for Claude Opus 5 - Exclude unsupported jp. inference profiles during generation - Add Bedrock prompt cache compatibility info, AWS model card sources, and regression tests Reason / background: - Incorporate the new model info from models.dev and align with the official AWS spec Scope of impact: - Model catalog generation, Bedrock compatibility info, catalog tests --- packages/catalog/CHANGELOG.md | 4 + packages/catalog/scripts/generate-models.ts | 2 + .../catalog/scripts/generated-policies.ts | 14 ++ packages/catalog/src/compat/bedrock.ts | 12 ++ packages/catalog/src/models.json | 152 +++++++++++++++++- .../test/amazon-bedrock-opus-5.test.ts | 84 ++++++++++ 6 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 packages/catalog/test/amazon-bedrock-opus-5.test.ts diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 669081f3f..e1d6775e5 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Added Claude Opus 5 model entries for Amazon Bedrock: `anthropic.claude-opus-5` plus its `us.`, `eu.`, `au.`, and `global.` regional/geo IDs. + ## [17.1.3] - 2026-07-24 ### Fixed diff --git a/packages/catalog/scripts/generate-models.ts b/packages/catalog/scripts/generate-models.ts index 20fb72a9c..6971eab37 100644 --- a/packages/catalog/scripts/generate-models.ts +++ b/packages/catalog/scripts/generate-models.ts @@ -52,6 +52,7 @@ import { applyCanonicalLimitFallback, applyGeneratedModelPolicies, CLOUDFLARE_FALLBACK_MODEL, + dropUnsupportedBedrockGeoIds, linkOpenAIPromotionTargets, } from "./generated-policies"; @@ -634,6 +635,7 @@ async function generateModels() { allModels = dropFireworksWireIds(allModels); allModels = dropUnusableZaiContextTierIds(allModels); allModels = dropXiaomiAudioOnlyIds(allModels); + allModels = dropUnsupportedBedrockGeoIds(allModels); allModels = normalizeAntigravityEndpoint(allModels); // Normalize display names: gateway author prefixes ("OpenAI: …"), alias // markers ("(latest)"), provider attribution ("(Antigravity)"), and diff --git a/packages/catalog/scripts/generated-policies.ts b/packages/catalog/scripts/generated-policies.ts index ecde56894..77a0bab37 100644 --- a/packages/catalog/scripts/generated-policies.ts +++ b/packages/catalog/scripts/generated-policies.ts @@ -51,6 +51,20 @@ export const CLOUDFLARE_FALLBACK_MODEL: ModelSpec<"anthropic-messages"> = { maxTokens: 64000, }; +/** + * `models.dev` currently lists `jp.anthropic.claude-opus-5`, but AWS's own + * Bedrock model card documents only `anthropic.claude-opus-5` plus the `us.`, + * `eu.`, `au.`, and `global.` Geo/Global inference-profile IDs under + * Programmatic Access; Japan regions are marked unsupported for Geo inference + * in the same card's regional-availability table. Bedrock rejects an + * undocumented inference-profile ID outright, so drop this specific upstream + * row rather than ship a selector that 4xxs on first use (PR #6591 review). + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5.html + */ +export function dropUnsupportedBedrockGeoIds(models: readonly ModelSpec[]): ModelSpec[] { + return models.filter(model => !(model.provider === "amazon-bedrock" && model.id === "jp.anthropic.claude-opus-5")); +} + const CODEX_GPT_5_4_PRIORITY_BY_VARIANT: Partial> = { base: 0, mini: 1, diff --git a/packages/catalog/src/compat/bedrock.ts b/packages/catalog/src/compat/bedrock.ts index 76fb35516..d8258c193 100644 --- a/packages/catalog/src/compat/bedrock.ts +++ b/packages/catalog/src/compat/bedrock.ts @@ -42,6 +42,15 @@ const EXPLICIT_CHECKPOINTS_4096_1H: ResolvedBedrockCompat = { promptCacheMaximumCheckpoints: 4, }; +// AWS モデルカード: 512 トークン、最大 4 個のキャッシュチェックポイント、5 分と 1 時間の TTL。 +// https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5.html +const EXPLICIT_CHECKPOINTS_512_1H: ResolvedBedrockCompat = { + promptCacheMode: "explicit", + supportsLongPromptCacheRetention: true, + promptCacheMinimumTokens: 512, + promptCacheMaximumCheckpoints: 4, +}; + /** * Explicit Nova cache points complement Bedrock's automatic prefix caching: * AWS recommends them for consistent cache hits and input-cost savings. Keep @@ -82,6 +91,9 @@ function detectedBedrockCompat(modelId: string): ResolvedBedrockCompat { ) { return EXPLICIT_CHECKPOINTS_4096_1H; } + if (id.includes("anthropic.claude-opus-5")) { + return EXPLICIT_CHECKPOINTS_512_1H; + } if (id.includes("anthropic.claude-opus-4-6")) { return EXPLICIT_CHECKPOINTS_4096_5M; } diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index 00f76c7da..a184329fc 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -7966,6 +7966,36 @@ "supportsDisplay": true } }, + "anthropic.claude-opus-5": { + "id": "anthropic.claude-opus-5", + "name": "Claude Opus 5", + "api": "bedrock-converse-stream", + "provider": "amazon-bedrock", + "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 5, + "output": 25, + "cacheRead": 0.5, + "cacheWrite": 6.25 + }, + "contextWindow": 1000000, + "maxTokens": 128000, + "thinking": { + "mode": "anthropic-adaptive", + "efforts": [ + "low", + "medium", + "high", + "max" + ], + "supportsDisplay": true + } + }, "anthropic.claude-sonnet-5": { "id": "anthropic.claude-sonnet-5", "name": "Claude Sonnet 5", @@ -8084,6 +8114,36 @@ "supportsDisplay": true } }, + "au.anthropic.claude-opus-5": { + "id": "au.anthropic.claude-opus-5", + "name": "Claude Opus 5 (AU)", + "api": "bedrock-converse-stream", + "provider": "amazon-bedrock", + "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 5, + "output": 25, + "cacheRead": 0.5, + "cacheWrite": 6.25 + }, + "contextWindow": 1000000, + "maxTokens": 128000, + "thinking": { + "mode": "anthropic-adaptive", + "efforts": [ + "low", + "medium", + "high", + "max" + ], + "supportsDisplay": true + } + }, "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)", @@ -8669,6 +8729,36 @@ "supportsDisplay": true } }, + "eu.anthropic.claude-opus-5": { + "id": "eu.anthropic.claude-opus-5", + "name": "Claude Opus 5 (EU)", + "api": "bedrock-converse-stream", + "provider": "amazon-bedrock", + "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 5.5, + "output": 27.5, + "cacheRead": 0.55, + "cacheWrite": 6.875 + }, + "contextWindow": 1000000, + "maxTokens": 128000, + "thinking": { + "mode": "anthropic-adaptive", + "efforts": [ + "low", + "medium", + "high", + "max" + ], + "supportsDisplay": true + } + }, "eu.anthropic.claude-sonnet-4-20250514-v1:0": { "id": "eu.anthropic.claude-sonnet-4-20250514-v1:0", "name": "Claude Sonnet 4 (EU)", @@ -8992,6 +9082,36 @@ "supportsDisplay": true } }, + "global.anthropic.claude-opus-5": { + "id": "global.anthropic.claude-opus-5", + "name": "Claude Opus 5 (Global)", + "api": "bedrock-converse-stream", + "provider": "amazon-bedrock", + "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 5, + "output": 25, + "cacheRead": 0.5, + "cacheWrite": 6.25 + }, + "contextWindow": 1000000, + "maxTokens": 128000, + "thinking": { + "mode": "anthropic-adaptive", + "efforts": [ + "low", + "medium", + "high", + "max" + ], + "supportsDisplay": true + } + }, "global.anthropic.claude-sonnet-4-20250514-v1:0": { "id": "global.anthropic.claude-sonnet-4-20250514-v1:0", "name": "Claude Sonnet 4 (Global)", @@ -10620,6 +10740,36 @@ "supportsDisplay": true } }, + "us.anthropic.claude-opus-5": { + "id": "us.anthropic.claude-opus-5", + "name": "Claude Opus 5 (US)", + "api": "bedrock-converse-stream", + "provider": "amazon-bedrock", + "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com", + "reasoning": true, + "input": [ + "text", + "image" + ], + "cost": { + "input": 5, + "output": 25, + "cacheRead": 0.5, + "cacheWrite": 6.25 + }, + "contextWindow": 1000000, + "maxTokens": 128000, + "thinking": { + "mode": "anthropic-adaptive", + "efforts": [ + "low", + "medium", + "high", + "max" + ], + "supportsDisplay": true + } + }, "us.anthropic.claude-sonnet-4-20250514-v1:0": { "id": "us.anthropic.claude-sonnet-4-20250514-v1:0", "name": "Claude Sonnet 4 (US)", @@ -94800,4 +94950,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/catalog/test/amazon-bedrock-opus-5.test.ts b/packages/catalog/test/amazon-bedrock-opus-5.test.ts new file mode 100644 index 000000000..df243eb4e --- /dev/null +++ b/packages/catalog/test/amazon-bedrock-opus-5.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, test } from "bun:test"; +import { buildBedrockCompat } from "@oh-my-pi/pi-catalog/compat/bedrock"; +import { getBundledModels } from "@oh-my-pi/pi-catalog/models"; +import type { ModelSpec } from "@oh-my-pi/pi-catalog/types"; +import { dropUnsupportedBedrockGeoIds } from "../scripts/generated-policies"; + +// AWS's Bedrock model card for Claude Opus 5 lists exactly these Programmatic +// Access IDs — the bare model ID plus the us./eu./au. Geo and global. +// inference profiles. Japan is explicitly marked unsupported for Geo +// inference in the same card's regional-availability table, so no `jp.` +// profile exists for this model (unlike several Opus 4.x generations). +// https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5.html +const AWS_DOCUMENTED_OPUS_5_IDS = [ + "anthropic.claude-opus-5", + "us.anthropic.claude-opus-5", + "eu.anthropic.claude-opus-5", + "au.anthropic.claude-opus-5", + "global.anthropic.claude-opus-5", +]; + +describe("Amazon Bedrock Claude Opus 5", () => { + test("bundles exactly the AWS-documented inference-profile IDs", () => { + const bedrockOpus5Ids = getBundledModels("amazon-bedrock") + .map(model => model.id) + .filter(id => id.endsWith("anthropic.claude-opus-5")); + + expect(new Set(bedrockOpus5Ids)).toEqual(new Set(AWS_DOCUMENTED_OPUS_5_IDS)); + // `models.dev` currently also lists `jp.anthropic.claude-opus-5`; Bedrock + // has no such inference profile for this model and would reject it. + expect(bedrockOpus5Ids).not.toContain("jp.anthropic.claude-opus-5"); + }); + + test("dropUnsupportedBedrockGeoIds filters the undocumented jp. profile without touching other providers/ids", () => { + const bareSpec = (provider: string, id: string): ModelSpec<"bedrock-converse-stream"> => ({ + id, + name: id, + api: "bedrock-converse-stream", + provider, + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 200000, + maxTokens: 64000, + }); + const input = [ + bareSpec("amazon-bedrock", "jp.anthropic.claude-opus-5"), + bareSpec("amazon-bedrock", "us.anthropic.claude-opus-5"), + // A `jp.` id on a different Bedrock model, or on a different provider, + // must survive — only this exact (provider, id) pair is undocumented. + bareSpec("amazon-bedrock", "jp.anthropic.claude-opus-4-8"), + bareSpec("some-other-provider", "jp.anthropic.claude-opus-5"), + ]; + + expect(dropUnsupportedBedrockGeoIds(input).map(model => model.id)).toEqual([ + "us.anthropic.claude-opus-5", + "jp.anthropic.claude-opus-4-8", + "jp.anthropic.claude-opus-5", + ]); + }); + + test("resolves the AWS-documented 512-token/four-checkpoint/1h prompt-cache capability", () => { + for (const id of AWS_DOCUMENTED_OPUS_5_IDS) { + const spec: ModelSpec<"bedrock-converse-stream"> = { + id, + name: id, + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + input: ["text", "image"], + cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 }, + contextWindow: 1_000_000, + maxTokens: 128_000, + }; + expect(buildBedrockCompat(spec)).toEqual({ + promptCacheMode: "explicit", + supportsLongPromptCacheRetention: true, + promptCacheMinimumTokens: 512, + promptCacheMaximumCheckpoints: 4, + }); + } + }); +}); From f853398254e7d8ca19660b0eccab37101f8419df Mon Sep 17 00:00:00 2001 From: k1riiiii <128486679+k1riiiii@users.noreply.github.com> Date: Sat, 25 Jul 2026 10:24:57 +0000 Subject: [PATCH 2/2] test(catalog): exercise Opus 5 source mapping instead of the bundled snapshot Codex review (P1) flagged that the first regression test asserted against the generated models.json snapshot via getBundledModels, so it validated only the committed artifact and could stay green when the source mapping broke. Per AGENTS.md, regression tests for generated catalogs must target the resolver/descriptor. Rewrite the test to feed a representative models.dev amazon-bedrock payload (all six opus-5 prefix rows, including the undocumented jp. profile and a filtered non-tool row) through mapModelsDevToModels and then dropUnsupportedBedrockGeoIds, asserting the five AWS-documented IDs survive. --- .../test/amazon-bedrock-opus-5.test.ts | 63 ++++++++++++++++--- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/packages/catalog/test/amazon-bedrock-opus-5.test.ts b/packages/catalog/test/amazon-bedrock-opus-5.test.ts index df243eb4e..83a2312d0 100644 --- a/packages/catalog/test/amazon-bedrock-opus-5.test.ts +++ b/packages/catalog/test/amazon-bedrock-opus-5.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { buildBedrockCompat } from "@oh-my-pi/pi-catalog/compat/bedrock"; -import { getBundledModels } from "@oh-my-pi/pi-catalog/models"; +import { MODELS_DEV_PROVIDER_DESCRIPTORS, mapModelsDevToModels } from "@oh-my-pi/pi-catalog/provider-models"; import type { ModelSpec } from "@oh-my-pi/pi-catalog/types"; import { dropUnsupportedBedrockGeoIds } from "../scripts/generated-policies"; @@ -18,16 +18,59 @@ const AWS_DOCUMENTED_OPUS_5_IDS = [ "global.anthropic.claude-opus-5", ]; -describe("Amazon Bedrock Claude Opus 5", () => { - test("bundles exactly the AWS-documented inference-profile IDs", () => { - const bedrockOpus5Ids = getBundledModels("amazon-bedrock") - .map(model => model.id) - .filter(id => id.endsWith("anthropic.claude-opus-5")); +// A representative `models.dev` "amazon-bedrock" payload for Claude Opus 5. +// models.dev lists each inference-profile prefix as its own row (the `eu.` +// row even carries distinct EU pricing), including the `jp.` profile that AWS +// does not actually expose for this model. We reproduce that shape so the test +// exercises the real source → catalog path — `mapModelsDevToModels` plus the +// `dropUnsupportedBedrockGeoIds` generation policy — rather than the committed +// `models.json` snapshot. A non-tool model is included to confirm the +// descriptor's `tool_call` filter still drops it. +const OPUS_5_MODELS_DEV_FIXTURE = { + "amazon-bedrock": { + models: Object.fromEntries( + [ + "anthropic.claude-opus-5", + "us.anthropic.claude-opus-5", + "eu.anthropic.claude-opus-5", + "au.anthropic.claude-opus-5", + "global.anthropic.claude-opus-5", + "jp.anthropic.claude-opus-5", + ].map(id => [ + id, + { + name: "Claude Opus 5", + tool_call: true, + reasoning: true, + limit: { context: 1_000_000, output: 128_000 }, + cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, + modalities: { input: ["text", "image"] }, + }, + ]), + ), + }, +}; - expect(new Set(bedrockOpus5Ids)).toEqual(new Set(AWS_DOCUMENTED_OPUS_5_IDS)); - // `models.dev` currently also lists `jp.anthropic.claude-opus-5`; Bedrock - // has no such inference profile for this model and would reject it. - expect(bedrockOpus5Ids).not.toContain("jp.anthropic.claude-opus-5"); +describe("Amazon Bedrock Claude Opus 5", () => { + test("source mapping plus generation policy yields exactly the AWS-documented inference-profile IDs", () => { + // Guard the source (models.dev descriptor + exclusion policy), not the + // bundled snapshot: the assertion must break if the mapping or policy + // stops reproducing the documented IDs, and must not falsely fail when + // upstream metadata legitimately shifts. + const mapped = mapModelsDevToModels(OPUS_5_MODELS_DEV_FIXTURE, MODELS_DEV_PROVIDER_DESCRIPTORS).filter( + model => model.provider === "amazon-bedrock" && model.id.endsWith("anthropic.claude-opus-5"), + ); + const opus5Ids = dropUnsupportedBedrockGeoIds(mapped).map(model => model.id); + + // Set semantics: the descriptor also derives an `eu.` variant from the + // bare `anthropic.` row, so `eu.` legitimately arrives from both that + // derivation and the standalone models.dev row (deduped downstream by + // the generator). We assert the documented ID coverage, not row count. + expect(new Set(opus5Ids)).toEqual(new Set(AWS_DOCUMENTED_OPUS_5_IDS)); + // `models.dev` lists `jp.anthropic.claude-opus-5`, but Bedrock has no such + // inference profile for this model and would reject it, so the generation + // policy must drop it before it reaches the catalog. + expect(opus5Ids).not.toContain("jp.anthropic.claude-opus-5"); }); test("dropUnsupportedBedrockGeoIds filters the undocumented jp. profile without touching other providers/ids", () => {