Merge PR #6599: feat(catalog): add Claude Opus 5 entries for Amazon Bedrock (@k1riiiii)

This commit is contained in:
can1357
2026-07-26 15:46:04 +02:00
6 changed files with 309 additions and 1 deletions
+3
View File
@@ -8,6 +8,9 @@
- Fixed forced `tool_choice` 400s (`tool_choice 'specified' is incompatible with thinking enabled`) on Kimi Code's Anthropic-compatible endpoint for the `kimi-for-coding`, `kimi-for-coding-highspeed`, and `k3` aliases: the Anthropic-surface compat matcher only recognised Moonshot's native `kimi-k2.7-code*` ids, so thinking-locked kimi-code models kept `supportsForcedToolChoice: true` and the forced selector was sent to a host that always thinks. These models now resolve `requiresThinkingEnabled`, keeping thinking on and downgrading forced choices to `auto`.
- Retried empty successful provider discovery responses after the short non-authoritative interval instead of caching them for the full catalog TTL ([#6620](https://github.com/can1357/oh-my-pi/issues/6620)).
- Fixed GitHub Copilot Claude models with no bundled catalog reference (e.g. a freshly served `claude-opus-5`) discovering with `reasoning: false`/`thinking: null` and no effort dial, and disappearing along with their synthesized `-1m` sibling on offline reads: reference-less Copilot models on the anthropic-messages proxy now derive the adaptive reasoning ladder from the model id, and the cache restores their compile-time `COPILOT_API_HEADERS` by value instead of dropping them as unrestorable ([#6664](https://github.com/can1357/oh-my-pi/issues/6664)).
### 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
@@ -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
@@ -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<Record<OpenAIVariant, number>> = {
base: 0,
mini: 1,
+12
View File
@@ -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;
}
+151 -1
View File
@@ -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 @@
}
}
}
}
}
@@ -0,0 +1,127 @@
import { describe, expect, test } from "bun:test";
import { buildBedrockCompat } from "@oh-my-pi/pi-catalog/compat/bedrock";
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";
// 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",
];
// 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"] },
},
]),
),
},
};
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", () => {
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,
});
}
});
});