first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:52:23 +03:00
commit 880f412e2c
2662 changed files with 866266 additions and 0 deletions

View File

@@ -0,0 +1,967 @@
import { EnvVarInput } from "@/components/ui/envVarInput";
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { HeadersTable, type CellRenderParams } from "@/components/ui/headersTable";
import { Input } from "@/components/ui/input";
import { ModelMultiselect } from "@/components/ui/modelMultiselect";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { TagInput } from "@/components/ui/tagInput";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { isRedacted } from "@/lib/utils/validation";
import { Info } from "lucide-react";
import { useEffect, useState } from "react";
import { Control, UseFormReturn } from "react-hook-form";
// Providers that support batch APIs
const BATCH_SUPPORTED_PROVIDERS = ["openai", "bedrock", "anthropic", "gemini", "azure"];
/** Normalize form value (object or legacy JSON string) for the alias map editor. */
function normalizeAliasesValue(v: Record<string, string> | string | undefined | null): Record<string, string> {
if (v == null) {
return {};
}
if (typeof v === "string") {
const t = v.trim();
if (!t) {
return {};
}
try {
const p = JSON.parse(t) as unknown;
if (typeof p === "object" && p !== null && !Array.isArray(p)) {
return Object.fromEntries(Object.entries(p as Record<string, unknown>).map(([k, val]) => [k, String(val ?? "")]));
}
} catch {
return {};
}
return {};
}
if (typeof v === "object" && !Array.isArray(v)) {
return Object.fromEntries(Object.entries(v).map(([k, val]) => [k, typeof val === "string" ? val : String(val ?? "")]));
}
return {};
}
interface Props {
control: Control<any>;
providerName: string;
form: UseFormReturn<any>;
}
// Batch API form field for all providers
function BatchAPIFormField({ control }: { control: Control<any>; form: UseFormReturn<any> }) {
return (
<FormField
control={control}
name={`key.use_for_batch_api`}
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-sm border p-2">
<div className="space-y-1.5">
<FormLabel>Use for Batch APIs</FormLabel>
<FormDescription>
Enable this key for batch API operations. Only keys with this enabled will be used for batch requests.
</FormDescription>
</div>
<FormControl>
<Switch checked={field.value ?? false} onCheckedChange={field.onChange} />
</FormControl>
</FormItem>
)}
/>
);
}
export function ApiKeyFormFragment({ control, providerName, form }: Props) {
const isBedrock = providerName === "bedrock";
const isVertex = providerName === "vertex";
const isAzure = providerName === "azure";
const isReplicate = providerName === "replicate";
const isVLLM = providerName === "vllm";
const isOllama = providerName === "ollama";
const isSGL = providerName === "sgl";
const isKeylessProvider = isOllama || isSGL;
const supportsBatchAPI = BATCH_SUPPORTED_PROVIDERS.includes(providerName);
// Auth type state for Azure: 'api_key', 'entra_id', or 'default_credential'
const [azureAuthType, setAzureAuthType] = useState<"api_key" | "entra_id" | "default_credential">("api_key");
// Auth type state for Bedrock: 'iam_role', 'explicit', or 'api_key'
const [bedrockAuthType, setBedrockAuthType] = useState<"iam_role" | "explicit" | "api_key">("iam_role");
// Auth type state for Vertex: 'service_account', 'service_account_json', or 'api_key'
const [vertexAuthType, setVertexAuthType] = useState<"service_account" | "service_account_json" | "api_key">("service_account");
// Detect auth type from existing form values when editing
useEffect(() => {
if (form.formState.isDirty) return;
if (isAzure) {
const clientId = form.getValues("key.azure_key_config.client_id");
const clientSecret = form.getValues("key.azure_key_config.client_secret");
const tenantId = form.getValues("key.azure_key_config.tenant_id");
const apiKey = form.getValues("key.value");
const hasEntraField =
clientId?.value || clientId?.env_var || clientSecret?.value || clientSecret?.env_var || tenantId?.value || tenantId?.env_var;
const hasApiKey = apiKey?.value || apiKey?.env_var;
let detected: "api_key" | "entra_id" | "default_credential" = "api_key";
if (hasEntraField) {
detected = "entra_id";
} else if (!hasApiKey) {
detected = "default_credential";
}
setAzureAuthType(detected);
form.setValue("key.azure_key_config._auth_type", detected);
}
}, [isAzure, form]);
useEffect(() => {
if (form.formState.isDirty) return;
if (isVertex) {
const authCredentials = form.getValues("key.vertex_key_config.auth_credentials")?.value;
const authCredentialsEnv = form.getValues("key.vertex_key_config.auth_credentials")?.env_var;
const apiKey = form.getValues("key.value")?.value;
const apiKeyEnv = form.getValues("key.value")?.env_var;
let detected: "service_account" | "service_account_json" | "api_key" = "service_account";
if (authCredentials || authCredentialsEnv) {
detected = "service_account_json";
} else if (apiKey || apiKeyEnv) {
detected = "api_key";
}
setVertexAuthType(detected);
form.setValue("key.vertex_key_config._auth_type", detected);
}
}, [isVertex, form]);
useEffect(() => {
if (form.formState.isDirty) return;
if (isBedrock) {
const accessKey = form.getValues("key.bedrock_key_config.access_key");
const secretKey = form.getValues("key.bedrock_key_config.secret_key");
const apiKey = form.getValues("key.value");
const hasExplicitCreds = accessKey?.value || accessKey?.env_var || secretKey?.value || secretKey?.env_var;
const hasApiKey = apiKey?.value || apiKey?.env_var;
let detected: "iam_role" | "explicit" | "api_key" = "iam_role";
if (hasExplicitCreds) {
detected = "explicit";
} else if (hasApiKey) {
detected = "api_key";
}
setBedrockAuthType(detected);
form.setValue("key.bedrock_key_config._auth_type", detected);
}
}, [isBedrock, form]);
return (
<div data-tab="api-keys" className="space-y-4 overflow-hidden">
<div className="flex items-start gap-4">
<div className="flex-1">
<FormField
control={control}
name={`key.name`}
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Production Key" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={control}
name={`key.weight`}
render={({ field }) => (
<FormItem>
<div className="flex items-center gap-2">
<FormLabel>Weight</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Info className="text-muted-foreground h-3 w-3" />
</span>
</TooltipTrigger>
<TooltipContent>
<p>Determines traffic distribution between keys. Higher weights receive more requests.</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder="1.0"
className="w-[260px]"
value={field.value === undefined || field.value === null ? "" : String(field.value)}
onChange={(e) => {
// Keep as string during typing to allow partial input
field.onChange(e.target.value === "" ? "" : e.target.value);
}}
onBlur={(e) => {
const v = e.target.value.trim();
if (v !== "") {
const num = parseFloat(v);
if (!isNaN(num)) {
field.onChange(num);
}
}
field.onBlur();
}}
name={field.name}
ref={field.ref}
type="text"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Hide API Key field for providers with dedicated auth tabs */}
{!isAzure && !isBedrock && !isVertex && (
<FormField
control={control}
name={`key.value`}
render={({ field }) => (
<FormItem>
<FormLabel>API Key {isVLLM ? "(Optional)" : ""}</FormLabel>
<FormControl>
<EnvVarInput placeholder="API Key or env.MY_KEY" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{!isVLLM && (
<>
<FormField
control={control}
name={`key.models`}
render={({ field }) => (
<FormItem>
<div className="flex items-center gap-2">
<FormLabel>Allowed Models</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Info className="text-muted-foreground h-3 w-3" />
</span>
</TooltipTrigger>
<TooltipContent>
<p>
Select specific models this key applies to, or choose "Allow All Models" to allow all. Leave empty to deny all.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<ModelMultiselect
data-testid="api-keys-models-multiselect"
provider={providerName}
allowAllOption={true}
value={field.value || []}
onChange={(models: string[]) => {
const hadStar = (field.value || []).includes("*");
const hasStar = models.includes("*");
if (!hadStar && hasStar) {
field.onChange(["*"]);
} else if (hadStar && hasStar && models.length > 1) {
field.onChange(models.filter((m: string) => m !== "*"));
} else {
field.onChange(models);
}
}}
placeholder={
(field.value || []).includes("*")
? "All models allowed"
: (field.value || []).length === 0
? "No models (deny all)"
: "Search models..."
}
unfiltered={true}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.blacklisted_models`}
render={({ field }) => (
<FormItem data-testid="apikey-blacklisted-models-field">
<div className="flex items-center gap-2">
<FormLabel>Blocked Models</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Info className="text-muted-foreground h-3 w-3" />
</span>
</TooltipTrigger>
<TooltipContent className="max-w-sm">
<p>
Models this key must never serve. The denylist always wins if a model appears in both Allowed Models and here,
it is blocked. Select "All Models" to block every model on this key.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<ModelMultiselect
data-testid="api-keys-blocked-models-multiselect"
provider={providerName}
allowAllOption={true}
value={field.value || []}
onChange={(models: string[]) => {
const hadStar = (field.value || []).includes("*");
const hasStar = models.includes("*");
if (!hadStar && hasStar) {
field.onChange(["*"]);
} else if (hadStar && hasStar && models.length > 1) {
field.onChange(models.filter((m: string) => m !== "*"));
} else {
field.onChange(models);
}
}}
placeholder={
(field.value || []).includes("*")
? "All models blocked"
: (field.value || []).length === 0
? "No models blocked"
: "Search models..."
}
unfiltered={true}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.aliases`}
render={({ field }) => (
<FormItem data-testid="apikey-aliases-field">
<FormLabel>Aliases (Optional)</FormLabel>
<FormDescription>
Map each request model name to the provider&apos;s identifier (deployment name, inference profile ID, fine-tuned endpoint
ID, etc.) or just a custom name, e.g. &quot;claude-sonnet-4-5&quot; -&gt; &quot;custom-claude-4.5-sonnet&quot;.
</FormDescription>
<FormControl>
<div data-testid="apikey-aliases-table">
<HeadersTable
label=""
value={normalizeAliasesValue(field.value)}
onChange={(next) => {
form.clearErrors("key.aliases");
field.onChange(Object.keys(next).length > 0 ? next : {});
}}
keyPlaceholder="Request model name"
valuePlaceholder="Deployment / profile / resource ID"
renderValueInput={({ value: cellValue, onChange, placeholder, disabled }: CellRenderParams) => (
<ModelMultiselect
isSingleSelect
provider={providerName}
value={cellValue}
onChange={onChange}
placeholder={placeholder ?? "Deployment / profile / resource ID"}
disabled={disabled}
unfiltered={true}
/>
)}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
)}
{supportsBatchAPI && !isBedrock && !isAzure && <BatchAPIFormField control={control} form={form} />}
{isAzure && (
<div className="space-y-4">
<Separator className="my-6" />
<div className="space-y-2">
<FormLabel>Authentication Method</FormLabel>
<Tabs
value={azureAuthType}
onValueChange={(v) => {
setAzureAuthType(v as "api_key" | "entra_id" | "default_credential");
form.setValue("key.azure_key_config._auth_type", v, { shouldDirty: true, shouldValidate: true });
if (v === "entra_id" || v === "default_credential") {
// Clear API key when switching away from API Key
form.setValue("key.value", undefined, { shouldDirty: true });
}
if (v === "api_key" || v === "default_credential") {
// Clear Entra ID fields when switching away from Entra ID
form.setValue("key.azure_key_config.client_id", undefined, { shouldDirty: true });
form.setValue("key.azure_key_config.client_secret", undefined, { shouldDirty: true });
form.setValue("key.azure_key_config.tenant_id", undefined, { shouldDirty: true });
form.setValue("key.azure_key_config.scopes", undefined, { shouldDirty: true });
}
}}
>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger data-testid="apikey-azure-default-credential-tab" value="default_credential">
Default Credential
</TabsTrigger>
<TabsTrigger data-testid="apikey-azure-api-key-tab" value="api_key">
API Key
</TabsTrigger>
<TabsTrigger data-testid="apikey-azure-entra-id-tab" value="entra_id">
Entra ID (Service Principal)
</TabsTrigger>
</TabsList>
</Tabs>
</div>
{azureAuthType === "api_key" && (
<FormField
control={control}
name={`key.value`}
render={({ field }) => (
<FormItem>
<FormLabel>
API Key {isVertex ? "(Supported only for gemini and fine-tuned models)" : isVLLM ? "(Optional)" : ""}
</FormLabel>
<FormControl>
<EnvVarInput placeholder="API Key or env.MY_KEY" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{azureAuthType === "default_credential" && (
<p className="text-muted-foreground text-sm">
Uses DefaultAzureCredential automatically detects managed identity on Azure VMs and containers, workload identity in AKS,
environment variables, and Azure CLI. No credentials required.
</p>
)}
<FormField
control={control}
name={`key.azure_key_config.endpoint`}
render={({ field }) => (
<FormItem>
<FormLabel>Endpoint (Required)</FormLabel>
<FormControl>
<EnvVarInput placeholder="https://your-resource.openai.azure.com or env.AZURE_ENDPOINT" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.azure_key_config.api_version`}
render={({ field }) => (
<FormItem>
<FormLabel>API Version (Optional)</FormLabel>
<FormControl>
<EnvVarInput placeholder="2024-02-01 or env.AZURE_API_VERSION" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{azureAuthType === "entra_id" && (
<>
<FormField
control={control}
name={`key.azure_key_config.client_id`}
render={({ field }) => (
<FormItem>
<FormLabel>Client ID (Required)</FormLabel>
<FormControl>
<EnvVarInput placeholder="your-client-id or env.AZURE_CLIENT_ID" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.azure_key_config.client_secret`}
render={({ field }) => (
<FormItem>
<FormLabel>Client Secret (Required)</FormLabel>
<FormControl>
<EnvVarInput placeholder="your-client-secret or env.AZURE_CLIENT_SECRET" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.azure_key_config.tenant_id`}
render={({ field }) => (
<FormItem>
<FormLabel>Tenant ID (Required)</FormLabel>
<FormControl>
<EnvVarInput placeholder="your-tenant-id or env.AZURE_TENANT_ID" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.azure_key_config.scopes`}
render={({ field }) => (
<FormItem>
<div className="flex items-center gap-2">
<FormLabel>Scopes (Optional)</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Info className="text-muted-foreground h-3 w-3" />
</span>
</TooltipTrigger>
<TooltipContent>
<p>
Optional OAuth scopes for token requests. By default we use https://cognitiveservices.azure.com/.default - add
additional scopes here if your setup requires extra permissions.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<TagInput
data-testid="apikey-azure-scopes-input"
placeholder="Add scope (Enter or comma)"
value={field.value ?? []}
onValueChange={field.onChange}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
)}
{supportsBatchAPI && <BatchAPIFormField control={control} form={form} />}
</div>
)}
{isVertex && (
<div className="space-y-4">
<Separator className="my-6" />
<div className="space-y-2">
<FormLabel>Authentication Method</FormLabel>
<Tabs
value={vertexAuthType}
onValueChange={(v) => {
setVertexAuthType(v as "service_account" | "service_account_json" | "api_key");
form.setValue("key.vertex_key_config._auth_type", v, { shouldDirty: true, shouldValidate: true });
if (v === "service_account" || v === "api_key") {
// Clear auth credentials when switching away from service account JSON
form.setValue("key.vertex_key_config.auth_credentials", undefined, { shouldDirty: true });
}
if (v === "service_account" || v === "service_account_json") {
// Clear API key when switching away from API Key
form.setValue("key.value", undefined, { shouldDirty: true });
}
}}
>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger data-testid="apikey-vertex-service-account-tab" value="service_account">
Service Account (Attached)
</TabsTrigger>
<TabsTrigger data-testid="apikey-vertex-service-account-json-tab" value="service_account_json">
Service Account (JSON)
</TabsTrigger>
<TabsTrigger data-testid="apikey-vertex-api-key-tab" value="api_key">
API Key
</TabsTrigger>
</TabsList>
</Tabs>
{vertexAuthType === "service_account" && (
<p className="text-muted-foreground text-sm">
Uses the service account attached to your environment (GCE, GKE, Cloud Run). No credentials required.
</p>
)}
</div>
<FormField
control={control}
name={`key.vertex_key_config.project_id`}
render={({ field }) => (
<FormItem>
<FormLabel>Project ID (Required)</FormLabel>
<FormControl>
<EnvVarInput placeholder="your-gcp-project-id or env.VERTEX_PROJECT_ID" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.vertex_key_config.project_number`}
render={({ field }) => (
<FormItem>
<FormLabel>Project Number (Required only for fine-tuned models)</FormLabel>
<FormControl>
<EnvVarInput placeholder="your-gcp-project-number or env.VERTEX_PROJECT_NUMBER" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.vertex_key_config.region`}
render={({ field }) => (
<FormItem>
<FormLabel>Region (Required)</FormLabel>
<FormControl>
<EnvVarInput placeholder="us-central1 or env.VERTEX_REGION" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{vertexAuthType === "service_account_json" && (
<FormField
control={control}
name={`key.vertex_key_config.auth_credentials`}
render={({ field }) => (
<FormItem>
<FormLabel>Auth Credentials (Required)</FormLabel>
<FormDescription>Service account JSON object or env.VAR_NAME</FormDescription>
<FormControl>
<EnvVarInput
data-testid="apikey-vertex-auth-credentials-input"
variant="textarea"
rows={4}
placeholder='{"type":"service_account","project_id":"your-gcp-project",...} or env.VERTEX_CREDENTIALS'
inputClassName="font-mono text-sm"
{...field}
/>
</FormControl>
{isRedacted(field.value?.value ?? "") && (
<div className="text-muted-foreground mt-1 flex items-center gap-1 text-xs">
<Info className="h-3 w-3" />
<span>Credentials are stored securely. Edit to update.</span>
</div>
)}
<FormMessage />
</FormItem>
)}
/>
)}
{vertexAuthType === "api_key" && (
<FormField
control={control}
name={`key.value`}
render={({ field }) => (
<FormItem>
<FormLabel>API Key (Supported only for gemini and fine-tuned models)</FormLabel>
<FormControl>
<EnvVarInput data-testid="apikey-vertex-api-key-input" placeholder="API Key or env.MY_KEY" type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
</div>
)}
{isReplicate && (
<div className="space-y-4">
<Separator className="my-6" />
<FormField
control={control}
name="key.replicate_key_config.use_deployments_endpoint"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-sm border p-2">
<div className="space-y-1.5">
<FormLabel>Use Deployments Endpoint</FormLabel>
<FormDescription>
Route requests through the Replicate deployments endpoint instead of the models endpoint.
</FormDescription>
</div>
<FormControl>
<Switch checked={field.value ?? false} onCheckedChange={field.onChange} />
</FormControl>
</FormItem>
)}
/>
</div>
)}
{isVLLM && (
<div className="space-y-4">
<Separator className="my-6" />
<FormField
control={control}
name="key.vllm_key_config.url"
render={({ field }) => (
<FormItem>
<FormLabel>Server URL (Required)</FormLabel>
<FormDescription>Base URL of the vLLM server (e.g. http://vllm-server:8000 or env.VLLM_URL)</FormDescription>
<FormControl>
<EnvVarInput data-testid="key-input-vllm-url" placeholder="http://vllm-server:8000" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name="key.vllm_key_config.model_name"
render={({ field }) => (
<FormItem>
<FormLabel>Model Name (Required)</FormLabel>
<FormDescription>Exact model name served on this vLLM instance</FormDescription>
<FormControl>
<Input data-testid="key-input-vllm-model-name" placeholder="meta-llama/Llama-3-70b-hf" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
{isKeylessProvider && (
<div className="space-y-4">
<FormField
control={control}
name={`key.${isOllama ? "ollama_key_config" : "sgl_key_config"}.url`}
render={({ field }) => (
<FormItem>
<FormLabel>Server URL (Required)</FormLabel>
<FormDescription>
Base URL of the {isOllama ? "Ollama" : "SGLang"} server (e.g.{" "}
{isOllama ? "http://localhost:11434" : "http://localhost:30000"} or {isOllama ? "env.OLLAMA_URL" : "env.SGL_URL"})
</FormDescription>
<FormControl>
<EnvVarInput
data-testid={`key-input-${isOllama ? "ollama" : "sgl"}-url`}
placeholder={isOllama ? "http://localhost:11434" : "http://localhost:30000"}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
{isBedrock && (
<div className="space-y-4">
<Separator className="my-6" />
<div className="space-y-2">
<FormLabel>Authentication Method</FormLabel>
<Tabs
value={bedrockAuthType}
onValueChange={(v) => {
setBedrockAuthType(v as "iam_role" | "explicit" | "api_key");
form.setValue("key.bedrock_key_config._auth_type", v, { shouldDirty: true, shouldValidate: true });
if (v === "iam_role") {
// Clear explicit credentials and API key when switching to IAM Role
form.setValue("key.bedrock_key_config.access_key", undefined, { shouldDirty: true });
form.setValue("key.bedrock_key_config.secret_key", undefined, { shouldDirty: true });
form.setValue("key.bedrock_key_config.session_token", undefined, { shouldDirty: true });
form.setValue("key.value", undefined, { shouldDirty: true });
} else if (v === "explicit") {
// Clear API key when switching to Explicit Credentials
form.setValue("key.value", undefined, { shouldDirty: true });
} else if (v === "api_key") {
// Clear AWS credentials and assume-role fields when switching to API Key
form.setValue("key.bedrock_key_config.access_key", undefined, { shouldDirty: true });
form.setValue("key.bedrock_key_config.secret_key", undefined, { shouldDirty: true });
form.setValue("key.bedrock_key_config.session_token", undefined, { shouldDirty: true });
form.setValue("key.bedrock_key_config.role_arn", undefined, { shouldDirty: true });
form.setValue("key.bedrock_key_config.external_id", undefined, { shouldDirty: true });
form.setValue("key.bedrock_key_config.session_name", undefined, { shouldDirty: true });
}
}}
>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger data-testid="apikey-bedrock-iam-role-tab" value="iam_role">
IAM Role (Inherited)
</TabsTrigger>
<TabsTrigger data-testid="apikey-bedrock-explicit-credentials-tab" value="explicit">
Explicit Credentials
</TabsTrigger>
<TabsTrigger data-testid="apikey-bedrock-api-key-tab" value="api_key">
API Key
</TabsTrigger>
</TabsList>
</Tabs>
{bedrockAuthType === "iam_role" && (
<p className="text-muted-foreground text-sm">Uses IAM roles attached to your environment (EC2, Lambda, ECS, EKS).</p>
)}
{bedrockAuthType === "api_key" && (
<p className="text-muted-foreground text-sm">Uses a Bearer token for API key authentication.</p>
)}
</div>
{bedrockAuthType === "explicit" && (
<>
<FormField
control={control}
name={`key.bedrock_key_config.access_key`}
render={({ field }) => (
<FormItem>
<FormLabel>Access Key (Required)</FormLabel>
<FormControl>
<EnvVarInput placeholder="your-aws-access-key or env.AWS_ACCESS_KEY_ID" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.bedrock_key_config.secret_key`}
render={({ field }) => (
<FormItem>
<FormLabel>Secret Key (Required)</FormLabel>
<FormControl>
<EnvVarInput placeholder="your-aws-secret-key or env.AWS_SECRET_ACCESS_KEY" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.bedrock_key_config.session_token`}
render={({ field }) => (
<FormItem>
<FormLabel>Session Token (Optional)</FormLabel>
<FormControl>
<EnvVarInput placeholder="your-aws-session-token or env.AWS_SESSION_TOKEN" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
)}
{bedrockAuthType === "api_key" && (
<FormField
control={control}
name={`key.value`}
render={({ field }) => (
<FormItem>
<FormLabel>API Key</FormLabel>
<FormControl>
<EnvVarInput
data-testid="apikey-bedrock-api-key-input"
placeholder="API Key or env.BEDROCK_API_KEY"
type="text"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<FormField
control={control}
name={`key.bedrock_key_config.region`}
render={({ field }) => (
<FormItem>
<FormLabel>Region (Required)</FormLabel>
<FormControl>
<EnvVarInput placeholder="us-east-1 or env.AWS_REGION" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{bedrockAuthType !== "api_key" && (
<>
<FormField
control={control}
name={`key.bedrock_key_config.role_arn`}
render={({ field }) => (
<FormItem>
<FormLabel>Assume Role ARN (Optional)</FormLabel>
<FormDescription>
Assume an IAM role before requests. Works with both explicit credentials and inherited IAM (EC2, ECS, EKS).
</FormDescription>
<FormControl>
<EnvVarInput
data-testid="apikey-bedrock-role-arn-input"
placeholder="arn:aws:iam::123456789:role/MyRole or env.AWS_ROLE_ARN"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.bedrock_key_config.external_id`}
render={({ field }) => (
<FormItem>
<FormLabel>External ID (Optional)</FormLabel>
<FormDescription>Required by the role's trust policy when using cross-account access</FormDescription>
<FormControl>
<EnvVarInput
data-testid="apikey-bedrock-external-id-input"
placeholder="external-id or env.AWS_EXTERNAL_ID"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name={`key.bedrock_key_config.session_name`}
render={({ field }) => (
<FormItem>
<FormLabel>Session Name (Optional)</FormLabel>
<FormDescription>AssumeRole session name (defaults to bifrost-session)</FormDescription>
<FormControl>
<EnvVarInput
data-testid="apikey-bedrock-session-name-input"
placeholder="bifrost-session or env.AWS_SESSION_NAME"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
)}
<FormField
control={control}
name={`key.bedrock_key_config.arn`}
render={({ field }) => (
<FormItem>
<FormLabel>ARN (Optional)</FormLabel>
<FormControl>
<EnvVarInput placeholder="arn:aws:bedrock:us-east-1:123:inference-profile or env.AWS_ARN" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{supportsBatchAPI && <BatchAPIFormField control={control} form={form} />}
</div>
)}
</div>
);
}