Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 1 addition & 33 deletions apps/gateway/src/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { streamSSE } from "hono/streaming";

import { detectCodingAgentFromUserAgent } from "@/chat/tools/detect-coding-agent.js";
import { extractFirstSseEventData } from "@/chat/tools/extract-first-sse-event-data.js";
import { applyPinnedDefaultRegions } from "@/chat/tools/pin-default-regions.js";
import { validateSource } from "@/chat/tools/validate-source.js";
import { getApiKeyFingerprint } from "@/lib/api-key-fingerprint.js";
import {
Expand Down Expand Up @@ -389,39 +390,6 @@ function filterRegionsByAvailableKeys(
});
}

/**
* For providers with `regionConfig.pinDefaultRegion: true`, drop all regional
* candidates except the defaultRegion (and the synthetic root) when no
* explicit choice was made. This makes AWS Bedrock default to `:global`
* unless the caller opts in via the `:region` URL suffix or via the
* provider-key region option. Providers without `pinDefaultRegion`
* (e.g. Alibaba) pass through unchanged so the gateway can route to the
* cheapest region.
*/
function applyPinnedDefaultRegions(
mappings: ProviderModelMapping[],
options: {
explicitLocks?: Map<string, string>;
requestedRegion?: string;
} = {},
): ProviderModelMapping[] {
if (options.requestedRegion) {
return mappings;
}
return mappings.filter((m) => {
const def = providers.find((p) => p.id === m.providerId) as
| ProviderDefinition
| undefined;
if (!def?.regionConfig?.pinDefaultRegion) {
return true;
}
if (options.explicitLocks?.has(m.providerId)) {
return true;
}
return !m.region || m.region === def.regionConfig.defaultRegion;
});
}

function preferConcreteRegionalMappings(
providers: ProviderModelMapping[],
): ProviderModelMapping[] {
Expand Down
75 changes: 75 additions & 0 deletions apps/gateway/src/chat/tools/pin-default-regions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { describe, it, expect } from "vitest";

import {
expandAllProviderRegions,
models,
type ProviderModelMapping,
} from "@llmgateway/models";

import { applyPinnedDefaultRegions } from "./pin-default-regions.js";

function bedrockCandidates(modelId: string): ProviderModelMapping[] {
const model = models.find((m) => m.id === modelId);
if (!model) {
throw new Error(`unknown model ${modelId}`);
}
return expandAllProviderRegions(
model.providers.filter((p) => p.providerId === "aws-bedrock"),
);
}

describe("applyPinnedDefaultRegions", () => {
it("pins Bedrock models with a global profile to the synthetic root + global", () => {
const pinned = applyPinnedDefaultRegions(
bedrockCandidates("claude-haiku-4-5-20251001"),
);
const regions = pinned.map((m) => m.region ?? "(root)");
expect(regions).toContain("(root)");
expect(regions).toContain("global");
expect(regions).not.toContain("us");
expect(regions).not.toContain("eu");
});

it("pins Bedrock models without a global profile to their concrete region (no synthetic root)", () => {
// Opus 4.1 only has a `us` cross-region inference profile on Bedrock. The
// synthetic root would resolve to `global.` which Bedrock rejects, so it
// must be dropped in favour of the `us` variant.
const pinned = applyPinnedDefaultRegions(
bedrockCandidates("claude-opus-4-1-20250805"),
);
expect(pinned.map((m) => m.region)).toEqual(["us"]);
});

it("applies the same fallback to Llama Bedrock models that lack a global profile", () => {
const pinned = applyPinnedDefaultRegions(
bedrockCandidates("llama-3.1-70b-instruct"),
);
expect(pinned.map((m) => m.region)).toEqual(["us"]);
});

it("keeps the synthetic root for Bedrock mappings that expose no regional variants", () => {
const mapping: ProviderModelMapping = {
providerId: "aws-bedrock",
externalId: "some.legacy-model-v1:0",
inputPrice: "1e-6",
outputPrice: "1e-6",
streaming: true,
};
expect(applyPinnedDefaultRegions([mapping])).toEqual([mapping]);
});

it("passes everything through when a region was explicitly requested", () => {
const candidates = bedrockCandidates("claude-opus-4-1-20250805");
expect(
applyPinnedDefaultRegions(candidates, { requestedRegion: "us" }),
).toEqual(candidates);
});

it("passes everything through for providers with an explicit region lock", () => {
const candidates = bedrockCandidates("claude-haiku-4-5-20251001");
const pinned = applyPinnedDefaultRegions(candidates, {
explicitLocks: new Map([["aws-bedrock", "eu"]]),
});
expect(pinned).toEqual(candidates);
});
});
68 changes: 68 additions & 0 deletions apps/gateway/src/chat/tools/pin-default-regions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
type ProviderDefinition,
type ProviderModelMapping,
providers,
} from "@llmgateway/models";

/**
* For providers with `regionConfig.pinDefaultRegion: true`, drop all regional
* candidates except the defaultRegion (and the synthetic root) when no
* explicit choice was made. This makes AWS Bedrock default to `:global`
* unless the caller opts in via the `:region` URL suffix or via the
* provider-key region option. Providers without `pinDefaultRegion`
* (e.g. Alibaba) pass through unchanged so the gateway can route to the
* cheapest region.
*
* When a model has regional variants but none for the provider's default
* region (e.g. Claude Opus 4.1 and several Llama models on AWS Bedrock offer
* only a `us` cross-region inference profile, not `global`), the synthetic
* root cannot be used: it resolves to the default-region prefix (`global.`)
* which Bedrock rejects with "The provided model identifier is invalid."
* In that case we pin to the model's concrete regional variants instead.
*/
export function applyPinnedDefaultRegions(
mappings: ProviderModelMapping[],
options: {
explicitLocks?: Map<string, string>;
requestedRegion?: string;
} = {},
): ProviderModelMapping[] {
if (options.requestedRegion) {
return mappings;
}
const providerHasAnyRegion = new Set<string>();
const providerHasDefaultRegion = new Set<string>();
for (const m of mappings) {
if (!m.region) {
continue;
}
providerHasAnyRegion.add(m.providerId);
const def = providers.find((p) => p.id === m.providerId) as
| ProviderDefinition
| undefined;
if (m.region === def?.regionConfig?.defaultRegion) {
providerHasDefaultRegion.add(m.providerId);
}
}
return mappings.filter((m) => {
const def = providers.find((p) => p.id === m.providerId) as
| ProviderDefinition
| undefined;
if (!def?.regionConfig?.pinDefaultRegion) {
return true;
}
if (options.explicitLocks?.has(m.providerId)) {
return true;
}
if (
providerHasAnyRegion.has(m.providerId) &&
!providerHasDefaultRegion.has(m.providerId)
) {
// No default-region profile exists for this model — the synthetic root
// would resolve to the invalid default-region prefix, so keep only the
// concrete regional variants.
return Boolean(m.region);
}
return !m.region || m.region === def.regionConfig.defaultRegion;
});
}
Loading