Skip to content
Closed
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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ LLM_Z_AI_API_KEY=your_zai_key_here

# AWS Bedrock
LLM_AWS_BEDROCK_API_KEY=your_aws_bedrock_key_here
# AWS Bedrock Region (options: us-east-1, us-west-2, eu-west-1, eu-central-1, ap-northeast-1, ap-southeast-1)
# LLM_AWS_BEDROCK_REGION=us-east-1

# Azure
LLM_AZURE_API_KEY=your_azure_key_here
Expand Down
24 changes: 24 additions & 0 deletions apps/api/src/routes/keys-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ const providerKeySchema = z.object({
options: z
.object({
aws_bedrock_region_prefix: z.enum(["us.", "global.", "eu."]).optional(),
aws_bedrock_region: z
.enum([
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2",
"eu-west-1",
"eu-central-1",
"ap-northeast-1",
"ap-southeast-1",
])
.optional(),
azure_resource: z.string().optional(),
azure_api_version: z.string().optional(),
azure_deployment_type: z.enum(["openai", "ai-foundry"]).optional(),
Expand Down Expand Up @@ -59,6 +71,18 @@ const createProviderKeySchema = z.object({
options: z
.object({
aws_bedrock_region_prefix: z.enum(["us.", "global.", "eu."]).optional(),
aws_bedrock_region: z
.enum([
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2",
"eu-west-1",
"eu-central-1",
"ap-northeast-1",
"ap-southeast-1",
])
.optional(),
azure_resource: z.string().optional(),
azure_api_version: z.string().optional(),
azure_deployment_type: z.enum(["openai", "ai-foundry"]).optional(),
Expand Down
8 changes: 8 additions & 0 deletions apps/code/src/lib/api/v1.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4837,6 +4837,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -4874,6 +4876,8 @@ export interface paths {
options?: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -4904,6 +4908,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -5022,6 +5028,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down
12 changes: 12 additions & 0 deletions apps/gateway/src/chat-helpers.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,13 @@ export const testModels = filteredModels
model.providers as ProviderModelMapping[],
);
for (const provider of expandedProviders) {
// Skip region-specific entries by default — they duplicate the root
// entry and require region-aware provider keys. Only include them
// when explicitly requested via TEST_MODELS with a region suffix.
if (provider.region && !specifiedModels) {
continue;
}

// Skip deactivated provider mappings
if (provider.deactivatedAt && new Date() > provider.deactivatedAt) {
continue;
Expand Down Expand Up @@ -352,6 +359,11 @@ export const providerModels = filteredModels
model.providers as ProviderModelMapping[],
);
for (const provider of expandedProviders) {
// Skip region-specific entries by default
if (provider.region && !specifiedModels) {
continue;
}

// Skip deactivated provider mappings
if (provider.deactivatedAt && new Date() > provider.deactivatedAt) {
continue;
Expand Down
79 changes: 79 additions & 0 deletions apps/gateway/src/lib/costs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,85 @@ describe("calculateCosts", () => {
expect(result.imageInputCost).toBeCloseTo(1120 * (2 / 1e6) * 0.8);
});

describe("region-aware cost lookup", () => {
it("should calculate costs for a bedrock model with region suffix", async () => {
// anthropic.claude-sonnet-4-5-20250929-v1:0 on aws-bedrock with us-east-1 region
const result = await calculateCosts(
"anthropic.claude-sonnet-4-5-20250929-v1:0:us-east-1",
"aws-bedrock",
100,
50,
null,
);

// Should find the model via expanded region entries
expect(result.inputCost).not.toBeNull();
expect(result.outputCost).not.toBeNull();
expect(result.totalCost).not.toBeNull();
expect(result.promptTokens).toBe(100);
expect(result.completionTokens).toBe(50);
// aws-bedrock claude-sonnet-4-5-20250929 inputPrice: 3.0/1e6, outputPrice: 15.0/1e6, discount: 0.3
expect(result.inputCost).toBeCloseTo(100 * (3.0 / 1e6) * 0.7);
expect(result.outputCost).toBeCloseTo(50 * (15.0 / 1e6) * 0.7);
});

it("should calculate costs for base bedrock model without region suffix", async () => {
const result = await calculateCosts(
"anthropic.claude-sonnet-4-5-20250929-v1:0",
"aws-bedrock",
100,
50,
null,
);

expect(result.inputCost).not.toBeNull();
expect(result.outputCost).not.toBeNull();
expect(result.inputCost).toBeCloseTo(100 * (3.0 / 1e6) * 0.7);
expect(result.outputCost).toBeCloseTo(50 * (15.0 / 1e6) * 0.7);
});

it("should return null costs for non-existent region on bedrock model", async () => {
// Using a region that doesn't exist for this model.
// The baseModel extraction splits on ":" so "v1:0:sa-east-1" becomes
// "anthropic.claude-sonnet-4-5-20250929-v1" which doesn't match any model.
const result = await calculateCosts(
"anthropic.claude-sonnet-4-5-20250929-v1:0:sa-east-1",
"aws-bedrock",
100,
50,
null,
);

// Cost lookup returns null because the colon-based baseModel extraction
// can't handle Bedrock model names that already contain colons
expect(result.inputCost).toBeNull();
});

it("should use region-specific pricing when defined (alibaba)", async () => {
// qwen-max on alibaba: default inputPrice 1.6/1e6, cn-beijing inputPrice 0.345/1e6
const resultDefault = await calculateCosts(
"qwen-max:singapore",
"alibaba",
1000,
500,
null,
);

const resultBeijing = await calculateCosts(
"qwen-max:cn-beijing",
"alibaba",
1000,
500,
null,
);

expect(resultDefault.inputCost).not.toBeNull();
expect(resultBeijing.inputCost).not.toBeNull();
// Beijing pricing should be cheaper than Singapore
expect(resultBeijing.inputCost!).toBeLessThan(resultDefault.inputCost!);
});
});

it("should include image costs in totalCost sum", async () => {
// totalCost = inputCost + outputCost + cachedInputCost + requestCost + webSearchCost
// (inputCost already includes imageInputCost, outputCost already includes imageOutputCost)
Expand Down
10 changes: 10 additions & 0 deletions apps/gateway/src/lib/costs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ export async function calculateCosts(
) as ModelDefinition;
}

// Fallback: search expanded providers for region-suffixed model names
// (e.g., "anthropic.claude-3-5-haiku-20241022-v1:0:us-east-1")
if (!modelInfo) {
modelInfo = models.find((m) =>
expandAllProviderRegions(m.providers as ProviderModelMapping[]).some(
(p) => p.modelName === model,
),
) as ModelDefinition;
}

if (!modelInfo) {
return {
inputCost: null,
Expand Down
21 changes: 18 additions & 3 deletions apps/playground/src/components/model-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -462,11 +462,26 @@ export function ModelSelector({
const [selectedProviderId, selectedModelIdRaw] = raw.includes("/")
? (raw.split("/") as [string, string])
: ["", raw];
// Strip :region suffix for root model lookup, keep raw for mapping match
// Strip the last :region segment for root model lookup, keep raw for mapping match.
// Model names can contain colons (e.g., "anthropic.claude-3-5-haiku-20241022-v1:0"),
// so we only strip the final segment to preserve the base model name.
const selectedModelId = selectedModelIdRaw.includes(":")
? selectedModelIdRaw.split(":")[0]
? selectedModelIdRaw.split(":").slice(0, -1).join(":")
: selectedModelIdRaw;
const selectedModel = models.find((m) => m.id === selectedModelId);
// Direct lookup by model.id, then fallback: search all models for a mapping
// that matches the raw provider model name (handles region-expanded names like
// "anthropic.claude-3-5-haiku-20241022-v1:0:us-east-1")
const selectedModel =
models.find((m) => m.id === selectedModelId) ??
(selectedProviderId
? models.find((m) =>
m.mappings.some(
(p) =>
p.providerId === selectedProviderId &&
p.modelName === selectedModelIdRaw,
),
)
: undefined);
const selectedProviderDef = providers.find(
(p) => p.id === selectedProviderId,
);
Expand Down
8 changes: 8 additions & 0 deletions apps/playground/src/lib/api/v1.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4837,6 +4837,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -4874,6 +4876,8 @@ export interface paths {
options?: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -4904,6 +4908,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -5022,6 +5028,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down
2 changes: 2 additions & 0 deletions apps/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"api": "workspace:*",
"babel-plugin-react-compiler": "1.0.0",
"better-auth": "1.5.5",
"canvas-confetti": "1.9.4",
"class-variance-authority": "0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
Expand Down Expand Up @@ -91,6 +92,7 @@
},
"devDependencies": {
"@tanstack/react-query-devtools": "5.90.2",
"@types/canvas-confetti": "1.9.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"autoprefixer": "^10.4.21",
Expand Down
3 changes: 2 additions & 1 deletion apps/ui/src/components/provider-keys/provider-keys-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ interface ProviderKeysListProps {

function formatOptionLabel(key: string, value: string): string {
const labels: Record<string, string> = {
aws_bedrock_region_prefix: "Region",
aws_bedrock_region_prefix: "Cross-Region Prefix",
aws_bedrock_region: "Region",
azure_resource: "Resource",
azure_api_version: "API Version",
azure_deployment_type: "Deployment",
Expand Down
8 changes: 8 additions & 0 deletions apps/ui/src/lib/api/v1.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4837,6 +4837,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -4874,6 +4876,8 @@ export interface paths {
options?: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -4904,6 +4908,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -5022,6 +5028,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down
8 changes: 8 additions & 0 deletions ee/admin/src/lib/api/v1.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4837,6 +4837,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -4874,6 +4876,8 @@ export interface paths {
options?: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -4904,6 +4908,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down Expand Up @@ -5022,6 +5028,8 @@ export interface paths {
options: {
/** @enum {string} */
aws_bedrock_region_prefix?: "us." | "global." | "eu.";
/** @enum {string} */
aws_bedrock_region?: "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-central-1" | "ap-northeast-1" | "ap-southeast-1";
azure_resource?: string;
azure_api_version?: string;
/** @enum {string} */
Expand Down
Loading
Loading