import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { getErrorMessage, useGetCoreConfigQuery, useGetDroppedRequestsQuery, useUpdateCoreConfigMutation } from "@/lib/store"; import { CoreConfig, DefaultCoreConfig, DefaultGlobalHeaderFilterConfig, GlobalHeaderFilterConfig } from "@/lib/types/config"; import { cn } from "@/lib/utils"; import LargePayloadSettingsFragment from "@enterprise/components/large-payload/largePayloadSettingsFragment"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import { useGetLargePayloadConfigQuery, useUpdateLargePayloadConfigMutation } from "@enterprise/lib/store/apis/largePayloadApi"; import { DefaultLargePayloadConfig, LargePayloadConfig } from "@enterprise/lib/types/largePayload"; import { Info, Plus, X } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; // Security headers that cannot be configured in allowlist/denylist // These headers are always blocked for security reasons regardless of configuration const SECURITY_HEADERS = [ "proxy-authorization", "cookie", "host", "content-length", "connection", "transfer-encoding", "x-api-key", "x-goog-api-key", "x-bf-api-key", "x-bf-vk", ]; // Helper to check if a header is a security header function isSecurityHeader(header: string): boolean { const h = header.toLowerCase().trim(); // Wildcard patterns are not literal security headers if (h.includes("*")) return false; return SECURITY_HEADERS.includes(h); } // Helper to compare header filter configs function headerFilterConfigEqual(a?: GlobalHeaderFilterConfig, b?: GlobalHeaderFilterConfig): boolean { const aAllowlist = a?.allowlist || []; const bAllowlist = b?.allowlist || []; const aDenylist = a?.denylist || []; const bDenylist = b?.denylist || []; if (aAllowlist.length !== bAllowlist.length || aDenylist.length !== bDenylist.length) { return false; } return aAllowlist.every((v, i) => v === bAllowlist[i]) && aDenylist.every((v, i) => v === bDenylist[i]); } // Helper to compare large payload configs function largePayloadConfigEqual(a: LargePayloadConfig, b: LargePayloadConfig): boolean { return ( a.enabled === b.enabled && a.request_threshold_bytes === b.request_threshold_bytes && a.response_threshold_bytes === b.response_threshold_bytes && a.prefetch_size_bytes === b.prefetch_size_bytes && a.max_payload_bytes === b.max_payload_bytes && a.truncated_log_bytes === b.truncated_log_bytes ); } export default function ClientSettingsView() { const hasSettingsUpdateAccess = useRbac(RbacResource.Settings, RbacOperation.Update); const [droppedRequests, setDroppedRequests] = useState(0); const { data: droppedRequestsData } = useGetDroppedRequestsQuery(); const { data: bifrostConfig, isLoading: isCoreConfigLoading } = useGetCoreConfigQuery({ fromDB: true }); const config = bifrostConfig?.client_config; const [updateCoreConfig, { isLoading: isSavingCoreConfig }] = useUpdateCoreConfigMutation(); const [localConfig, setLocalConfig] = useState(DefaultCoreConfig); // Large payload config state const { data: serverLargePayloadConfig, isLoading: isLargePayloadConfigLoading } = useGetLargePayloadConfigQuery(); const [updateLargePayloadConfig, { isLoading: isSavingLargePayload }] = useUpdateLargePayloadConfigMutation(); const [localLargePayloadConfig, setLocalLargePayloadConfig] = useState(DefaultLargePayloadConfig); const isQueriesLoading = isCoreConfigLoading || isLargePayloadConfigLoading; const isLoading = isSavingCoreConfig || isSavingLargePayload; useEffect(() => { if (droppedRequestsData) { setDroppedRequests(droppedRequestsData.dropped_requests); } }, [droppedRequestsData]); useEffect(() => { if (config) { setLocalConfig({ ...config, header_filter_config: config.header_filter_config || DefaultGlobalHeaderFilterConfig, }); } }, [config]); useEffect(() => { if (serverLargePayloadConfig) { setLocalLargePayloadConfig(serverLargePayloadConfig); } }, [serverLargePayloadConfig]); const hasCoreConfigChanges = useMemo(() => { if (!config) return false; return ( localConfig.drop_excess_requests !== config.drop_excess_requests || localConfig.disable_db_pings_in_health !== config.disable_db_pings_in_health || localConfig.async_job_result_ttl !== config.async_job_result_ttl || !headerFilterConfigEqual(localConfig.header_filter_config, config.header_filter_config) ); }, [config, localConfig]); const hasLargePayloadChanges = useMemo(() => { const baseline = serverLargePayloadConfig ?? DefaultLargePayloadConfig; return !largePayloadConfigEqual(localLargePayloadConfig, baseline); }, [serverLargePayloadConfig, localLargePayloadConfig]); const hasChanges = hasCoreConfigChanges || hasLargePayloadChanges; // Detect security headers in allowlist/denylist const invalidSecurityHeaders = useMemo(() => { const allowlist = localConfig.header_filter_config?.allowlist || []; const denylist = localConfig.header_filter_config?.denylist || []; const invalidInAllowlist = allowlist.filter((h) => h && isSecurityHeader(h)); const invalidInDenylist = denylist.filter((h) => h && isSecurityHeader(h)); return [...new Set([...invalidInAllowlist, ...invalidInDenylist])]; }, [localConfig.header_filter_config]); const hasSecurityHeaderError = invalidSecurityHeaders.length > 0; const handleConfigChange = useCallback((field: keyof CoreConfig, value: boolean | number | string[] | GlobalHeaderFilterConfig) => { setLocalConfig((prev) => ({ ...prev, [field]: value })); }, []); const handleLargePayloadConfigChange = useCallback((newConfig: LargePayloadConfig) => { setLocalLargePayloadConfig(newConfig); }, []); const handleSave = useCallback(async () => { // Defense in depth - don't save if security headers are present if (hasSecurityHeaderError) { return; } // Validate large payload config if it has changes if (hasLargePayloadChanges) { const minBytes = 1024; if ( localLargePayloadConfig.request_threshold_bytes < minBytes || localLargePayloadConfig.response_threshold_bytes < minBytes || localLargePayloadConfig.prefetch_size_bytes < minBytes || localLargePayloadConfig.max_payload_bytes < minBytes || localLargePayloadConfig.truncated_log_bytes < minBytes ) { toast.error("All byte values must be at least 1024 (1 KB)."); return; } if (localLargePayloadConfig.max_payload_bytes < localLargePayloadConfig.request_threshold_bytes) { toast.error("Max payload size must be greater than or equal to the request threshold."); return; } if (localLargePayloadConfig.max_payload_bytes < localLargePayloadConfig.response_threshold_bytes) { toast.error("Max payload size must be greater than or equal to the response threshold."); return; } } let coreConfigSaved = false; let largePayloadSaved = false; // Save core config if changed if (hasCoreConfigChanges) { if (!bifrostConfig) { toast.error("Configuration not loaded. Please refresh and try again."); return; } // Clean up empty strings from header filter config const cleanedConfig = { ...localConfig, header_filter_config: { allowlist: (localConfig.header_filter_config?.allowlist || []).filter((h) => h && h.trim().length > 0), denylist: (localConfig.header_filter_config?.denylist || []).filter((h) => h && h.trim().length > 0), }, }; try { await updateCoreConfig({ ...bifrostConfig!, client_config: cleanedConfig }).unwrap(); coreConfigSaved = true; } catch (error) { toast.error(`Failed to save client config: ${getErrorMessage(error)}`); } } // Save large payload config if changed if (hasLargePayloadChanges) { try { await updateLargePayloadConfig(localLargePayloadConfig).unwrap(); largePayloadSaved = true; } catch (error) { toast.error(`Failed to save large payload config: ${getErrorMessage(error)}`); } } if (coreConfigSaved || largePayloadSaved) { if (largePayloadSaved) { toast.success("Settings updated. Large payload changes require a restart to apply."); } else { toast.success("Client settings updated successfully."); } } }, [ bifrostConfig, hasSecurityHeaderError, hasCoreConfigChanges, hasLargePayloadChanges, localConfig, localLargePayloadConfig, updateCoreConfig, updateLargePayloadConfig, ]); // Header filter list handlers const handleAddAllowlistHeader = useCallback(() => { setLocalConfig((prev) => ({ ...prev, header_filter_config: { ...prev.header_filter_config, allowlist: [...(prev.header_filter_config?.allowlist || []), ""], }, })); }, []); const handleRemoveAllowlistHeader = useCallback((index: number) => { setLocalConfig((prev) => ({ ...prev, header_filter_config: { ...prev.header_filter_config, allowlist: (prev.header_filter_config?.allowlist || []).filter((_, i) => i !== index), }, })); }, []); const handleAllowlistChange = useCallback((index: number, value: string) => { const lowerValue = value.toLowerCase(); setLocalConfig((prev) => ({ ...prev, header_filter_config: { ...prev.header_filter_config, allowlist: (prev.header_filter_config?.allowlist || []).map((h, i) => (i === index ? lowerValue : h)), }, })); }, []); const handleAddDenylistHeader = useCallback(() => { setLocalConfig((prev) => ({ ...prev, header_filter_config: { ...prev.header_filter_config, denylist: [...(prev.header_filter_config?.denylist || []), ""], }, })); }, []); const handleRemoveDenylistHeader = useCallback((index: number) => { setLocalConfig((prev) => ({ ...prev, header_filter_config: { ...prev.header_filter_config, denylist: (prev.header_filter_config?.denylist || []).filter((_, i) => i !== index), }, })); }, []); const handleDenylistChange = useCallback((index: number, value: string) => { const lowerValue = value.toLowerCase(); setLocalConfig((prev) => ({ ...prev, header_filter_config: { ...prev.header_filter_config, denylist: (prev.header_filter_config?.denylist || []).map((h, i) => (i === index ? lowerValue : h)), }, })); }, []); return (

Client Settings

Configure client behavior and request handling.

{/* Drop Excess Requests */}

If enabled, Bifrost will drop requests that exceed pool capacity.{" "} {localConfig.drop_excess_requests && droppedRequests > 0 ? ( Have dropped {droppedRequests} requests since last restart. ) : ( <> )}

handleConfigChange("drop_excess_requests", checked)} disabled={!hasSettingsUpdateAccess} />
{/* Disable DB Pings in Health */}

If enabled, the /health endpoint will skip database connectivity checks and return OK immediately.

handleConfigChange("disable_db_pings_in_health", checked)} disabled={!hasSettingsUpdateAccess} />
{/* Async Job Result TTL */}

Default time-to-live for async job results in seconds. Results are automatically cleaned up after expiry.

handleConfigChange("async_job_result_ttl", parseInt(e.target.value) || 0)} disabled={!hasSettingsUpdateAccess} data-testid="client-settings-async-job-result-ttl-input" />
{/* Header Filter Section */}

Header Forwarding

Control which extra headers are forwarded to LLM providers.

About Header Forwarding

Two ways to forward headers:

  • Prefixed headers: Use{" "} x-bf-eh-* prefix. For example,{" "} x-bf-eh-custom-id is forwarded as{" "} custom-id.
  • Direct headers: Any header explicitly added to the allowlist can be forwarded directly without the prefix (e.g.,{" "} anthropic-beta).

How allowlist and denylist work:

  • Allowlist empty: Only{" "} x-bf-eh-* prefixed headers are forwarded (default behavior)
  • Allowlist configured: Prefixed headers filtered by allowlist, plus any direct header in the allowlist is forwarded
  • Denylist: Headers in the denylist are always blocked from forwarding
  • Wildcards: Use{" "} * at the end of a pattern to match prefixes (e.g., anthropic-* matches all headers starting with anthropic-). Use{" "} * alone to match all headers.

Important:

  • Allowlist/denylist entries should be the header name without the{" "} x-bf-eh- prefix
  • Example: To allow x-bf-eh-custom-id or direct{" "} custom-id, add{" "} custom-id to the allowlist
Security Note

Some headers are always blocked for security reasons regardless of configuration. These headers cannot be added to the allowlist or denylist:

proxy-authorization, cookie, host, content-length, connection, transfer-encoding, x-api-key, x-goog-api-key, x-bf-api-key, x-bf-vk

{/* Allowlist Section */}

Allowlist

Headers to allow. Enter names without the x-bf-eh- prefix. Any header in this list can also be sent directly without the prefix.

{(localConfig.header_filter_config?.allowlist || []).map((header, index) => (
handleAllowlistChange(index, e.target.value)} disabled={!hasSettingsUpdateAccess} />
))}
{/* Denylist Section */}

Denylist

Headers to block. Enter names without the x-bf-eh- prefix. Applies to both prefixed and direct header forwarding.

{(localConfig.header_filter_config?.denylist || []).map((header, index) => (
handleDenylistChange(index, e.target.value)} disabled={!hasSettingsUpdateAccess} />
))}
{/* Large Payload Optimization - Enterprise only */}
{hasSecurityHeaderError ? ( Remove security header{invalidSecurityHeaders.length > 1 ? "s" : ""}: {invalidSecurityHeaders.join(", ")} ) : ( )}
); }