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 | undefined | null): Record { 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).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; providerName: string; form: UseFormReturn; } // Batch API form field for all providers function BatchAPIFormField({ control }: { control: Control; form: UseFormReturn }) { return ( (
Use for Batch APIs Enable this key for batch API operations. Only keys with this enabled will be used for batch requests.
)} /> ); } 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 (
( Name )} />
(
Weight

Determines traffic distribution between keys. Higher weights receive more requests.

{ // 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" />
)} />
{/* Hide API Key field for providers with dedicated auth tabs */} {!isAzure && !isBedrock && !isVertex && ( ( API Key {isVLLM ? "(Optional)" : ""} )} /> )} {!isVLLM && ( <> (
Allowed Models

Select specific models this key applies to, or choose "Allow All Models" to allow all. Leave empty to deny all.

{ 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} />
)} /> (
Blocked Models

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.

{ 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} />
)} /> ( Aliases (Optional) Map each request model name to the provider's identifier (deployment name, inference profile ID, fine-tuned endpoint ID, etc.) or just a custom name, e.g. "claude-sonnet-4-5" -> "custom-claude-4.5-sonnet".
{ 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) => ( )} />
)} /> )} {supportsBatchAPI && !isBedrock && !isAzure && } {isAzure && (
Authentication Method { 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 }); } }} > Default Credential API Key Entra ID (Service Principal)
{azureAuthType === "api_key" && ( ( API Key {isVertex ? "(Supported only for gemini and fine-tuned models)" : isVLLM ? "(Optional)" : ""} )} /> )} {azureAuthType === "default_credential" && (

Uses DefaultAzureCredential — automatically detects managed identity on Azure VMs and containers, workload identity in AKS, environment variables, and Azure CLI. No credentials required.

)} ( Endpoint (Required) )} /> ( API Version (Optional) )} /> {azureAuthType === "entra_id" && ( <> ( Client ID (Required) )} /> ( Client Secret (Required) )} /> ( Tenant ID (Required) )} /> (
Scopes (Optional)

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.

)} /> )} {supportsBatchAPI && }
)} {isVertex && (
Authentication Method { 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 }); } }} > Service Account (Attached) Service Account (JSON) API Key {vertexAuthType === "service_account" && (

Uses the service account attached to your environment (GCE, GKE, Cloud Run). No credentials required.

)}
( Project ID (Required) )} /> ( Project Number (Required only for fine-tuned models) )} /> ( Region (Required) )} /> {vertexAuthType === "service_account_json" && ( ( Auth Credentials (Required) Service account JSON object or env.VAR_NAME {isRedacted(field.value?.value ?? "") && (
Credentials are stored securely. Edit to update.
)}
)} /> )} {vertexAuthType === "api_key" && ( ( API Key (Supported only for gemini and fine-tuned models) )} /> )}
)} {isReplicate && (
(
Use Deployments Endpoint Route requests through the Replicate deployments endpoint instead of the models endpoint.
)} />
)} {isVLLM && (
( Server URL (Required) Base URL of the vLLM server (e.g. http://vllm-server:8000 or env.VLLM_URL) )} /> ( Model Name (Required) Exact model name served on this vLLM instance )} />
)} {isKeylessProvider && (
( Server URL (Required) 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"}) )} />
)} {isBedrock && (
Authentication Method { 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 }); } }} > IAM Role (Inherited) Explicit Credentials API Key {bedrockAuthType === "iam_role" && (

Uses IAM roles attached to your environment (EC2, Lambda, ECS, EKS).

)} {bedrockAuthType === "api_key" && (

Uses a Bearer token for API key authentication.

)}
{bedrockAuthType === "explicit" && ( <> ( Access Key (Required) )} /> ( Secret Key (Required) )} /> ( Session Token (Optional) )} /> )} {bedrockAuthType === "api_key" && ( ( API Key )} /> )} ( Region (Required) )} /> {bedrockAuthType !== "api_key" && ( <> ( Assume Role ARN (Optional) Assume an IAM role before requests. Works with both explicit credentials and inherited IAM (EC2, ECS, EKS). )} /> ( External ID (Optional) Required by the role's trust policy when using cross-account access )} /> ( Session Name (Optional) AssumeRole session name (defaults to bifrost-session) )} /> )} ( ARN (Optional) )} /> {supportsBatchAPI && }
)}
); }