From 56ca9d3f4805884cada7a0c43dc2fcf69a79b4c0 Mon Sep 17 00:00:00 2001 From: "dayuan.jiang" Date: Mon, 22 Dec 2025 20:09:16 +0900 Subject: [PATCH] feat: add AWS credentials support for Bedrock provider - Add AWS Access Key ID, Secret Access Key, Region fields for Bedrock - Show different credential fields based on provider type - Update validation API to handle Bedrock with AWS credentials - Add region selector with common AWS regions --- app/api/validate-model/route.ts | 39 ++- components/model-config-dialog.tsx | 462 ++++++++++++++++++++++------- lib/types/model-config.ts | 15 + 3 files changed, 400 insertions(+), 116 deletions(-) diff --git a/app/api/validate-model/route.ts b/app/api/validate-model/route.ts index 20633a0..7beeaa4 100644 --- a/app/api/validate-model/route.ts +++ b/app/api/validate-model/route.ts @@ -1,3 +1,4 @@ +import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock" import { createAnthropic } from "@ai-sdk/anthropic" import { createDeepSeek, deepseek } from "@ai-sdk/deepseek" import { createGateway } from "@ai-sdk/gateway" @@ -15,12 +16,24 @@ interface ValidateRequest { apiKey: string baseUrl?: string modelId: string + // AWS Bedrock specific + awsAccessKeyId?: string + awsSecretAccessKey?: string + awsRegion?: string } export async function POST(req: Request) { try { const body: ValidateRequest = await req.json() - const { provider, apiKey, baseUrl, modelId } = body + const { + provider, + apiKey, + baseUrl, + modelId, + awsAccessKeyId, + awsSecretAccessKey, + awsRegion, + } = body if (!provider || !modelId) { return NextResponse.json( @@ -29,8 +42,18 @@ export async function POST(req: Request) { ) } - // Ollama doesn't require API key - if (provider !== "ollama" && !apiKey) { + // Validate credentials based on provider + if (provider === "bedrock") { + if (!awsAccessKeyId || !awsSecretAccessKey || !awsRegion) { + return NextResponse.json( + { + valid: false, + error: "AWS credentials (Access Key ID, Secret Access Key, Region) are required", + }, + { status: 400 }, + ) + } + } else if (provider !== "ollama" && !apiKey) { return NextResponse.json( { valid: false, error: "API key is required" }, { status: 400 }, @@ -76,6 +99,16 @@ export async function POST(req: Request) { break } + case "bedrock": { + const bedrock = createAmazonBedrock({ + accessKeyId: awsAccessKeyId, + secretAccessKey: awsSecretAccessKey, + region: awsRegion, + }) + model = bedrock(modelId) + break + } + case "openrouter": { const openrouter = createOpenRouter({ apiKey, diff --git a/components/model-config-dialog.tsx b/components/model-config-dialog.tsx index de1ba11..c2612e4 100644 --- a/components/model-config-dialog.tsx +++ b/components/model-config-dialog.tsx @@ -175,8 +175,15 @@ export function ModelConfigDialog({ ) => { if (!selectedProviderId) return updateProvider(selectedProviderId, { [field]: value }) - // Reset validation when API key or base URL changes - if (field === "apiKey" || field === "baseUrl") { + // Reset validation when credentials change + const credentialFields = [ + "apiKey", + "baseUrl", + "awsAccessKeyId", + "awsSecretAccessKey", + "awsRegion", + ] + if (credentialFields.includes(field)) { setValidationStatus("idle") updateProvider(selectedProviderId, { validated: false }) } @@ -205,7 +212,21 @@ export function ModelConfigDialog({ // Validate all models const handleValidate = useCallback(async () => { - if (!selectedProvider || !selectedProvider.apiKey) return + if (!selectedProvider) return + + // Check credentials based on provider type + const isBedrock = selectedProvider.provider === "bedrock" + if (isBedrock) { + if ( + !selectedProvider.awsAccessKeyId || + !selectedProvider.awsSecretAccessKey || + !selectedProvider.awsRegion + ) { + return + } + } else if (!selectedProvider.apiKey) { + return + } // Need at least one model to validate if (selectedProvider.models.length === 0) { @@ -234,6 +255,10 @@ export function ModelConfigDialog({ apiKey: selectedProvider.apiKey, baseUrl: selectedProvider.baseUrl, modelId: model.modelId, + // AWS Bedrock credentials + awsAccessKeyId: selectedProvider.awsAccessKeyId, + awsSecretAccessKey: selectedProvider.awsSecretAccessKey, + awsRegion: selectedProvider.awsRegion, }), }) @@ -499,136 +524,347 @@ export function ModelConfigDialog({ /> - {/* API Key */} -
- -
-
+ {/* Credentials - different for Bedrock vs other providers */} + {selectedProvider.provider === + "bedrock" ? ( + <> + {/* AWS Access Key ID */} +
+ handleProviderUpdate( - "apiKey", + "awsAccessKeyId", e.target .value, ) } - placeholder="Enter your API key" - className="h-9 pr-10 font-mono text-xs" + placeholder="AKIA..." + className="h-9 font-mono text-xs" /> -
+ + {/* AWS Secret Access Key */} +
+ +
+ + handleProviderUpdate( + "awsSecretAccessKey", + e + .target + .value, + ) + } + placeholder="Enter your secret access key" + className="h-9 pr-10 font-mono text-xs" + /> + +
+
+ + {/* AWS Region */} +
+ +
- -
- {validationStatus === - "error" && - validationError && ( -

- - { - validationError - } -

- )} -
- {/* Base URL */} -
- - - handleProviderUpdate( - "baseUrl", - e.target.value, - ) - } - placeholder={ - PROVIDER_INFO[ - selectedProvider - .provider - ].defaultBaseUrl || - "Custom endpoint URL" - } - className="h-9 font-mono text-xs" - /> -
+ {/* Test Button for Bedrock */} +
+ + {validationStatus === + "error" && + validationError && ( +

+ + { + validationError + } +

+ )} +
+ + ) : ( + <> + {/* API Key */} +
+ +
+
+ + handleProviderUpdate( + "apiKey", + e + .target + .value, + ) + } + placeholder="Enter your API key" + className="h-9 pr-10 font-mono text-xs" + /> + +
+ +
+ {validationStatus === + "error" && + validationError && ( +

+ + { + validationError + } +

+ )} +
+ + {/* Base URL */} +
+ + + handleProviderUpdate( + "baseUrl", + e.target + .value, + ) + } + placeholder={ + PROVIDER_INFO[ + selectedProvider + .provider + ] + .defaultBaseUrl || + "Custom endpoint URL" + } + className="h-9 font-mono text-xs" + /> +
+ + )}
diff --git a/lib/types/model-config.ts b/lib/types/model-config.ts index 046e95f..fd17250 100644 --- a/lib/types/model-config.ts +++ b/lib/types/model-config.ts @@ -26,6 +26,11 @@ export interface ProviderConfig { name?: string // Custom display name (e.g., "OpenAI Production") apiKey: string baseUrl?: string + // AWS Bedrock specific fields + awsAccessKeyId?: string + awsSecretAccessKey?: string + awsRegion?: string + awsSessionToken?: string // Optional, for temporary credentials models: ModelConfig[] validated?: boolean // Has API key been validated } @@ -45,6 +50,11 @@ export interface FlattenedModel { providerLabel: string // Provider display name apiKey: string baseUrl?: string + // AWS Bedrock specific fields + awsAccessKeyId?: string + awsSecretAccessKey?: string + awsRegion?: string + awsSessionToken?: string validated?: boolean // Has this model been validated } @@ -252,6 +262,11 @@ export function flattenModels(config: MultiModelConfig): FlattenedModel[] { providerLabel, apiKey: provider.apiKey, baseUrl: provider.baseUrl, + // AWS Bedrock fields + awsAccessKeyId: provider.awsAccessKeyId, + awsSecretAccessKey: provider.awsSecretAccessKey, + awsRegion: provider.awsRegion, + awsSessionToken: provider.awsSessionToken, validated: model.validated, }) }