diff --git a/changelog.d/fixes/12921-bedrock-vendor-context-limits.md b/changelog.d/fixes/12921-bedrock-vendor-context-limits.md new file mode 100644 index 0000000000..6eb84d5967 --- /dev/null +++ b/changelog.d/fixes/12921-bedrock-vendor-context-limits.md @@ -0,0 +1 @@ +- **fix(bedrock):** model import now resolves context limits for every vendor prefix instead of only `anthropic.*`, so `global.openai.gpt-5.6-*` no longer imports with a null `inputTokenLimit` and gets rejected pre-flight at the 200k default ([#12921](https://github.com/diegosouzapw/OmniRoute/pull/12921)). diff --git a/open-sse/config/bedrock.ts b/open-sse/config/bedrock.ts index 37c3e9e1a8..9503b5717c 100644 --- a/open-sse/config/bedrock.ts +++ b/open-sse/config/bedrock.ts @@ -90,13 +90,19 @@ export function getBedrockKnownModelLimits(modelId: string): { if (!trimmed) return null; const unqualified = trimmed.includes("/") ? trimmed.slice(trimmed.indexOf("/") + 1) : trimmed; - const withoutProfilePrefix = unqualified.replace(/^(?:eu|us|global)\./i, ""); - const withoutProviderPrefix = withoutProfilePrefix.replace(/^anthropic\./i, ""); - const spec = - getModelSpec(trimmed) || - getModelSpec(unqualified) || - getModelSpec(withoutProfilePrefix) || - getModelSpec(withoutProviderPrefix); + // A Bedrock id is "." optionally behind a cross-region profile + // prefix: "global.openai.gpt-5.6-sol", "us.anthropic.claude-...". The model + // name itself contains dots ("gpt-5.6-sol"), so peel at most those two leading + // qualifiers and keep the first candidate a spec knows. Peeling only + // "anthropic." left every other vendor (openai, meta, amazon, ...) without a + // context window, and the caller then fell back to a 200k default (#12915). + const segments = unqualified.split("."); + const spec = [trimmed, unqualified, segments.slice(1).join("."), segments.slice(2).join(".")] + .filter((candidate) => candidate.length > 0) + .reduce>( + (found, candidate) => found || getModelSpec(candidate), + undefined + ); if (!spec?.contextWindow && !spec?.maxOutputTokens) return null; return { diff --git a/tests/unit/bedrock-vendor-context-limits-12915.test.ts b/tests/unit/bedrock-vendor-context-limits-12915.test.ts new file mode 100644 index 0000000000..3d8219a26c --- /dev/null +++ b/tests/unit/bedrock-vendor-context-limits-12915.test.ts @@ -0,0 +1,57 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { discoverBedrockNativeModels } from "../../open-sse/services/bedrock.ts"; + +// ─── #12915 — every Bedrock vendor prefix must resolve a context window ────── +// Bedrock ids are ".", optionally behind a cross-region profile +// prefix ("global.openai.gpt-5.6-sol"). The known-limits lookup used to peel +// only "anthropic.", so imported openai.* models carried no inputTokenLimit and +// the pre-flight context check fell back to a 200k default — rejecting 1M-context +// models locally, before the request ever reached AWS. + +function bedrockFetcher(): (url: string, init: RequestInit) => Promise { + return async (url: string) => { + const body = url.includes("/inference-profiles") + ? { inferenceProfileSummaries: [] } + : { + modelSummaries: [ + { + modelId: "global.openai.gpt-5.6-sol", + modelName: "GPT-5.6 Sol", + providerName: "OpenAI", + responseStreamingSupported: true, + }, + { + modelId: "global.anthropic.claude-opus-4-6-v1", + modelName: "Claude Opus 4.6", + providerName: "Anthropic", + responseStreamingSupported: true, + }, + ], + }; + return new Response(JSON.stringify(body), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }; +} + +describe("Bedrock model discovery (#12915)", () => { + it("carries a context window for openai.* models, not just anthropic.*", async () => { + const { models } = await discoverBedrockNativeModels({ + apiKey: "test-key", + providerSpecificData: { region: "eu-west-1" }, + fetcher: bedrockFetcher(), + }); + + const openai = models.find((m) => m.id === "global.openai.gpt-5.6-sol"); + const anthropic = models.find((m) => m.id === "global.anthropic.claude-opus-4-6-v1"); + + // 1_050_000 and 1_000_000 differ, so a lookup that silently answered with the + // anthropic model's limit would not pass either assertion. + assert.equal(openai?.inputTokenLimit, 1_050_000); + assert.equal(openai?.outputTokenLimit, 128_000); + // The anthropic path must keep working unchanged. + assert.equal(anthropic?.inputTokenLimit, 1_000_000); + }); +});