diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba6b4c68c9..730291ffba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
### ♻️ Code Quality
+- **providers/[id]: extract AddApiKeyModal + EditConnectionModal (+ WebSessionCredentialGuide) from the god-component into components/** ([#3501] Phase 1c): extracted the two heaviest inline modals — `AddApiKeyModal` (~787-LOC body) and `EditConnectionModal` (~1091-LOC body) — plus shared `WebSessionCredentialGuide` (~103 LOC) into standalone files under `providers/[id]/components/modals/` and `providers/[id]/components/` respectively. Added `ERROR_TYPE_LABELS` and `formatTimeAgo` to `providerPageHelpers.ts` (leaf) so `EditConnectionModal` and `ConnectionRow` share them without cycles. Pruned 14 now-unused imports from the god-component. `ProviderDetailPageClient.tsx`: 9,981 → 8,092 LOC (−1,889 lines). Frozen baselines: `AddApiKeyModal.tsx: 842`, `EditConnectionModal.tsx: 1170`. 6 new Phase-1c smoke tests; all 21 vitest modal tests pass; typecheck/cycles/lint green.
- **refactor: small db/utils cleanup** ([#3523] — thanks @androw): table-driven `compression_analytics` column migration (replaces 17 repeated `ALTER TABLE` calls), a single merged `serializeJsonField` helper in `db/providers.ts` (folded two byte-identical serializers), and removal of the dead no-op `syncProviderDataToCloud`/`getProvidersNeedingRefresh` stubs from `shared/utils/machine.ts` (no remaining callers). Pure refactor; behavior unchanged.
- **Provider-detail god-component decomposition — Phase 2b (remaining shared helpers→leaf)** ([#3501]): extended `providers/[id]/providerPageHelpers.ts` with all remaining pure helpers needed by the heavy modals (`AddApiKeyModal`/`EditConnectionModal`) before they can be extracted. Moved 22 symbols: web-session credential label/hint/check/title helpers; upstream-headers helpers (`upstreamHeadersRecordsEqual`, `headerRowsToRecord`, `effectiveUpstreamHeadersForProtocol`, `anyUpstreamHeadersBadge`, `getProtoSlice`) plus their `HeaderDraftRow`/`CompatModelRow`/`CompatModelMap`/`CompatByProtocolMap` types; Codex consts and helpers (`CODEX_REASONING_STRENGTH_OPTIONS`, `CODEX_ACCOUNT_SERVICE_TIER_VALUES`, `CODEX_GLOBAL_SERVICE_MODE_VALUES`, `getCodexServiceTierLabel`, `normalizeCodexLimitPolicy`, `getCodexRequestDefaults`, `getClaudeCodeCompatibleRequestDefaults`); misc helpers (`compatProtocolLabelKey`, `extractCommandCodeCredentialInput`, `normalizeAndValidateHttpBaseUrl`, `SILICONFLOW_ENDPOINTS`, `CommandCodeAuthFlowState`). New transitive imports wired into the leaf: `MODEL_COMPAT_PROTOCOL_KEYS` (`@/shared/constants/modelCompat`), `CodexServiceTier`/`getCodexRequestDefaults`/`getClaudeCodeCompatibleRequestDefaults` (`@/lib/providers/requestDefaults`), `CodexGlobalServiceMode` (`@/lib/providers/codexFastTier`), `WebSessionCredentialRequirement` (`./webSessionCredentials`). `ProviderDetailPageClient.tsx`: 10,288 → 9,980 LOC. Leaf module: 589 LOC (acyclic). 25-assertion unit test suite passes; smoke test 3/3; no import cycles. Co-authored with @oyi77.
- **Provider-detail god-component decomposition — Phase 2 (helpers→lib)** ([#3501]): extracted the pure shared helpers — `ProviderMessageTranslator`/`LocalProviderMetadata` types, `providerText`/`providerCountText`/`readBooleanToggle`, and the provider base-URL + routing-tag/excluded-model parse/format block — into a new leaf `providers/[id]/providerPageHelpers.ts` (imports only `@/shared`, so the client and modals share them with no import cycle). `ProviderDetailPageClient.tsx`: 10,435 → 10,288 LOC. Unblocks extracting the heavier `AddApiKeyModal`/`EditConnectionModal` (which depend on these helpers) without cycling. The Phase 0 smoke test caught a missing transitive import (`isSelfHostedChatProvider`) at mount — now wired + locked by a new helpers unit test (12 assertions). Co-authored with @oyi77.
diff --git a/file-size-baseline.json b/file-size-baseline.json
index 6cf62ccde3..44dddee110 100644
--- a/file-size-baseline.json
+++ b/file-size-baseline.json
@@ -48,7 +48,9 @@
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2570,
"src/app/(dashboard)/dashboard/health/page.tsx": 1091,
"src/app/(dashboard)/dashboard/playground/components/tabs/ApiTab.tsx": 847,
- "src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx": 9981,
+ "src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx": 8093,
+ "src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 843,
+ "src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1171,
"src/app/(dashboard)/dashboard/providers/components/onboarding/ProviderOnboardingWizard.tsx": 906,
"src/app/(dashboard)/dashboard/providers/page.tsx": 1925,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1127,
diff --git a/src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx b/src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx
index 70559febe5..1c639641ba 100644
--- a/src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx
+++ b/src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx
@@ -44,15 +44,10 @@ import {
isAnthropicCompatibleProvider,
isClaudeCodeCompatibleProvider,
isSelfHostedChatProvider,
- providerAllowsOptionalApiKey,
supportsApiKeyOnFreeProvider,
- supportsBulkApiKey,
+ // providerAllowsOptionalApiKey + supportsBulkApiKey used by extracted AddApiKeyModal
} from "@/shared/constants/providers";
-import {
- ANTIGRAVITY_CLIENT_PROFILE_OPTIONS,
- normalizeAntigravityClientProfileSetting,
-} from "@/shared/constants/antigravityClientProfile";
-import { parseBulkApiKeys } from "@/shared/utils/bulkApiKeyParser";
+// antigravityClientProfile + parseBulkApiKeys used by extracted modals (AddApiKeyModal, EditConnectionModal)
import { getModelsByProviderId } from "@/shared/constants/models";
import {
compatibleProviderSupportsModelImport,
@@ -69,7 +64,7 @@ import {
type ModelCompatProtocolKey,
} from "@/shared/constants/modelCompat";
import { resolveManagedModelAlias } from "@/shared/utils/providerModelAliases";
-import { maskEmail, pickMaskedDisplayValue, pickDisplayValue } from "@/shared/utils/maskEmail";
+import { pickDisplayValue } from "@/shared/utils/maskEmail";
import useEmailPrivacyStore from "@/store/emailPrivacyStore";
import EmailPrivacyToggle from "@/shared/components/EmailPrivacyToggle";
import ProviderIcon from "@/shared/components/ProviderIcon";
@@ -82,16 +77,13 @@ import {
type CodexGlobalServiceMode,
} from "@/lib/providers/codexFastTier";
import { isClaudeExtraUsageBlockEnabled } from "@/lib/providers/claudeExtraUsage";
-import { parseExtraApiKeys } from "@/shared/utils/parseApiKeys";
+// parseExtraApiKeys used by extracted EditConnectionModal
import { compareTr } from "@/shared/utils/turkishText";
import RiskNoticeModal from "../components/RiskNoticeModal";
import CodexCliGuideModal from "../components/CodexCliGuideModal";
import { isRiskAcknowledged, useRiskAcknowledged } from "../hooks/useRiskAcknowledged";
import { resolveDashboardProviderInfo } from "../providerPageUtils";
-import {
- getWebSessionCredentialRequirement,
- type WebSessionCredentialRequirement,
-} from "./webSessionCredentials";
+// webSessionCredentials used by extracted modals (AddApiKeyModal, EditConnectionModal)
import {
ImportCodexAuthModal,
ApplyCodexAuthModal,
@@ -106,43 +98,33 @@ import {
} from "./components/modals/ImportGeminiAuthModal";
import EditCompatibleNodeModal from "./components/modals/EditCompatibleNodeModal";
+import AddApiKeyModal from "./components/modals/AddApiKeyModal";
+import EditConnectionModal from "./components/modals/EditConnectionModal";
+import WebSessionCredentialGuide from "./components/WebSessionCredentialGuide";
import { CC_COMPATIBLE_DEFAULT_CHAT_PATH } from "./providerDetailConstants";
import {
- CONFIGURABLE_BASE_URL_PROVIDERS,
- DEFAULT_PROVIDER_BASE_URLS,
- getLocalProviderMetadata,
- isBaseUrlConfigurableProvider,
- getProviderBaseUrlDefault,
- getProviderBaseUrlHint,
- getProviderBaseUrlPlaceholder,
- isGlmProvider,
- parseRoutingTagsInput,
- parseExcludedModelsInput,
- formatRoutingTagsInput,
- formatExcludedModelsInput,
+ // CONFIGURABLE_BASE_URL_PROVIDERS, DEFAULT_PROVIDER_BASE_URLS, getLocalProviderMetadata,
+ // isBaseUrlConfigurableProvider, getProviderBaseUrlDefault, getProviderBaseUrlHint,
+ // getProviderBaseUrlPlaceholder, isGlmProvider, parseRoutingTagsInput, parseExcludedModelsInput,
+ // formatRoutingTagsInput, formatExcludedModelsInput, getWebSessionCredentialLabel,
+ // getWebSessionCredentialHint, getWebSessionCredentialCheckLabel, getAddCredentialModalTitle,
+ // CODEX_REASONING_STRENGTH_OPTIONS, CODEX_ACCOUNT_SERVICE_TIER_VALUES, getCodexRequestDefaults,
+ // getClaudeCodeCompatibleRequestDefaults, extractCommandCodeCredentialInput,
+ // normalizeAndValidateHttpBaseUrl, formatTimeAgo
+ // — all moved to extracted modals (AddApiKeyModal, EditConnectionModal, WebSessionCredentialGuide)
providerText,
providerCountText,
readBooleanToggle,
- getWebSessionCredentialLabel,
- getWebSessionCredentialHint,
- getWebSessionCredentialCheckLabel,
- getAddCredentialModalTitle,
upstreamHeadersRecordsEqual,
UPSTREAM_HEADERS_UI_MAX,
headerRowsToRecord,
effectiveUpstreamHeadersForProtocol,
anyUpstreamHeadersBadge,
getProtoSlice,
- CODEX_REASONING_STRENGTH_OPTIONS,
- CODEX_ACCOUNT_SERVICE_TIER_VALUES,
CODEX_GLOBAL_SERVICE_MODE_VALUES,
getCodexServiceTierLabel,
normalizeCodexLimitPolicy,
- getCodexRequestDefaults,
- getClaudeCodeCompatibleRequestDefaults,
compatProtocolLabelKey,
- extractCommandCodeCredentialInput,
- normalizeAndValidateHttpBaseUrl,
SILICONFLOW_ENDPOINTS,
type ProviderMessageTranslator,
type LocalProviderMetadata,
@@ -153,6 +135,7 @@ import {
type CompatModelMap,
buildPassthroughTestBody,
shouldSwitchToVisibleFilter,
+ ERROR_TYPE_LABELS,
} from "./providerPageHelpers";
/** PATCH fields for provider model compat (matches API + `ModelCompatPerProtocol` shape). */
type ModelCompatSavePatch = {
@@ -185,110 +168,6 @@ function isModelHidden(
return false;
}
-function WebSessionCredentialGuide({
- requirement,
- providerName,
- t,
-}: {
- requirement: WebSessionCredentialRequirement;
- providerName: string;
- t: ProviderMessageTranslator;
-}) {
- if (requirement.kind === "none") {
- return (
-
-
-
- check_circle
-
-
-
- {providerText(t, "webNoAuthGuideTitle", "No credential required")}
-
-
- {providerText(
- t,
- "webNoAuthGuideBody",
- "{provider} does not need an API key or cookie. Save the connection to use its free web endpoint.",
- { provider: providerName }
- )}
-
-
-
-
- );
- }
-
- const requiredCredentialKey =
- requirement.kind === "token" ? "webTokenRequiredCredential" : "webCookieRequiredCredential";
- const requiredCredentialFallback =
- requirement.kind === "token" ? "Required token: {credential}" : "Required cookie: {credential}";
-
- return (
-
-
-
cookie
-
-
-
- {providerText(t, "webSessionGuideTitle", "How to get the session credential")}
-
-
- {providerText(
- t,
- "webSessionGuideIntro",
- "{provider} uses a browser web session instead of an API key.",
- { provider: providerName }
- )}
-
-
-
- {providerText(t, requiredCredentialKey, requiredCredentialFallback, {
- credential: requirement.credentialName,
- })}
-
-
-
- {providerText(t, "webSessionGuideStep1", "Sign in to {provider} in your browser.", {
- provider: providerName,
- })}
-
-
- {providerText(
- t,
- "webSessionGuideStep2",
- "Open the browser developer tools and inspect a request made by the web app."
- )}
-
-
- {providerText(
- t,
- "webSessionGuideStep3",
- "Copy the required credential from the provider's own domain. For cookies, copy only the Cookie header value and omit Cookie:.",
- { credential: requirement.credentialName }
- )}
-
-
- {providerText(
- t,
- "webSessionGuideStep4",
- "Paste it here and check the connection. If it stops working, sign in again and replace it with a fresh value."
- )}
-
-
-
- {providerText(
- t,
- "webSessionSecurityHint",
- "Treat this like a password: it may access your signed-in web account until it expires or is revoked."
- )}
-
-
-
-
- );
-}
-
function effectiveNormalizeForProtocol(
modelId: string,
protocol: string,
@@ -656,63 +535,6 @@ interface ConnectionRowProps {
isExportingGeminiAuthFile?: boolean;
}
-interface AddApiKeyModalProps {
- isOpen: boolean;
- provider?: string;
- providerName?: string;
- initialBaseUrl?: string;
- isCompatible?: boolean;
- isAnthropic?: boolean;
- isCcCompatible?: boolean;
- isCommandCode?: boolean;
- commandCodeAuthState?: CommandCodeAuthFlowState;
- onStartCommandCodeAuth?: () => void;
- onSave: (data: {
- name: string;
- apiKey?: string;
- priority: number;
- baseUrl?: string;
- providerSpecificData?: Record;
- }) => Promise;
- onClose: () => void;
-}
-
-interface EditConnectionModalConnection {
- id?: string;
- name?: string;
- email?: string;
- priority?: number;
- maxConcurrent?: number | null;
- rateLimitOverrides?: Record | null;
- authType?: string;
- provider?: string;
- apiKey?: string;
- providerSpecificData?: Record;
- healthCheckInterval?: number;
- projectId?: string | null;
-}
-
-const formatTimeAgo = (dateStr: string): string => {
- const now = Date.now();
- const date = new Date(dateStr).getTime();
- const diff = now - date;
- if (diff < 0) return "just now";
- const minutes = Math.floor(diff / 60000);
- if (minutes < 1) return "just now";
- if (minutes < 60) return `${minutes}m ago`;
- const hours = Math.floor(minutes / 60);
- if (hours < 24) return `${hours}h ago`;
- const days = Math.floor(hours / 24);
- if (days < 30) return `${days}d ago`;
- return new Date(dateStr).toLocaleDateString();
-};
-
-interface EditConnectionModalProps {
- isOpen: boolean;
- connection: EditConnectionModalConnection | null;
- onSave: (data: unknown) => Promise;
- onClose: () => void;
-}
function ModelCompatPopover({
t,
effectiveModelNormalize,
@@ -7442,22 +7264,6 @@ function CooldownTimer({ until }: CooldownTimerProps) {
return ⏱ {remaining} ;
}
-const ERROR_TYPE_LABELS = {
- runtime_error: { labelKey: "errorTypeRuntime", variant: "warning" },
- upstream_auth_error: { labelKey: "errorTypeUpstreamAuth", variant: "error" },
- account_deactivated: { labelKey: "Account Deactivated", variant: "error" },
- auth_missing: { labelKey: "errorTypeMissingCredential", variant: "warning" },
- token_refresh_failed: { labelKey: "errorTypeRefreshFailed", variant: "warning" },
- token_expired: { labelKey: "errorTypeTokenExpired", variant: "warning" },
- upstream_rate_limited: { labelKey: "errorTypeRateLimited", variant: "warning" },
- upstream_unavailable: { labelKey: "errorTypeUpstreamUnavailable", variant: "error" },
- network_error: { labelKey: "errorTypeNetworkError", variant: "warning" },
- unsupported: { labelKey: "errorTypeTestUnsupported", variant: "default" },
- upstream_error: { labelKey: "errorTypeUpstreamError", variant: "error" },
- banned: { labelKey: "errorTypeBanned", variant: "error" },
- credits_exhausted: { labelKey: "errorTypeCreditsExhausted", variant: "warning" },
-};
-
function inferErrorType(connection, isCooldown) {
if (isCooldown) return "upstream_rate_limited";
if (connection.testStatus === "banned") return "banned";
@@ -8284,1883 +8090,3 @@ function SiliconFlowEndpointModal({
);
}
-
-function AddApiKeyModal({
- isOpen,
- provider,
- providerName,
- initialBaseUrl,
- isCompatible,
- isAnthropic,
- isCcCompatible,
- isCommandCode,
- commandCodeAuthState,
- onStartCommandCodeAuth,
- onSave,
- onClose,
-}: AddApiKeyModalProps) {
- const t = useTranslations("providers");
- const usesBaseUrl = isBaseUrlConfigurableProvider(provider);
- const defaultBaseUrl = getProviderBaseUrlDefault(provider);
- const isVertex = provider === "vertex" || provider === "vertex-partner";
- const isBedrock = provider === "bedrock";
- const showsRegion = isVertex || isBedrock;
- const defaultRegion = isBedrock ? "eu-west-2" : "us-central1";
- const isGlm = isGlmProvider(provider);
- const isQoder = provider === "qoder";
- const isCloudflare = provider === "cloudflare-ai";
- const localProviderMetadata = getLocalProviderMetadata(provider);
- const isLocalSelfHostedProvider = !!localProviderMetadata;
- const isGooglePse = provider === "google-pse-search";
- const webSessionCredential = getWebSessionCredentialRequirement(provider);
- const isNoAuthWebSessionCredential = webSessionCredential?.kind === "none";
- const isWebSessionCredential = !!webSessionCredential && webSessionCredential.kind !== "none";
- const providerDisplayName = providerName || provider || "";
- const apiKeyOptional =
- providerAllowsOptionalApiKey(provider) || Boolean(isNoAuthWebSessionCredential);
- const commandCodeAuthPhaseLabel = commandCodeAuthState
- ? {
- idle: "Ready",
- starting: "Starting…",
- polling: "Waiting for browser…",
- received: "Browser approved",
- applying: "Applying key…",
- applied: "Connected",
- expired: "Link expired",
- error: "Connection failed",
- }[commandCodeAuthState.phase]
- : null;
-
- const [formData, setFormData] = useState({
- name: "",
- apiKey: "",
- priority: 1,
- baseUrl: initialBaseUrl || defaultBaseUrl,
- cx: "",
- region: showsRegion ? defaultRegion : "",
- apiRegion: "international",
- validationModelId: "",
- routingTags: "",
- excludedModels: "",
- customUserAgent: "",
- accountId: "",
- consoleApiKey: "",
- ccCompatibleContext1m: false,
- passthroughModels: false,
- });
- const [validating, setValidating] = useState(false);
- const [validationResult, setValidationResult] = useState(null);
- const [saving, setSaving] = useState(false);
- const [saveError, setSaveError] = useState(null);
- const [showAdvanced, setShowAdvanced] = useState(false);
- const [copiedCommandCodeField, setCopiedCommandCodeField] = useState(null);
- const wasOpenRef = useRef(false);
-
- useEffect(() => {
- const wasOpen = wasOpenRef.current;
- wasOpenRef.current = isOpen;
- if (!isOpen || wasOpen) return;
- setFormData((current) => ({
- ...current,
- baseUrl: initialBaseUrl || defaultBaseUrl,
- }));
- }, [defaultBaseUrl, initialBaseUrl, isOpen]);
-
- const bulkSupported = supportsBulkApiKey(provider);
- const [mode, setMode] = useState<"single" | "bulk">("single");
- const [bulkText, setBulkText] = useState("");
- const [bulkValidateKeys, setBulkValidateKeys] = useState(false);
- const [bulkResult, setBulkResult] = useState<{
- success: number;
- failed: number;
- total: number;
- errors: Array<{ index: number; name: string; message: string }>;
- } | null>(null);
- const [bulkWarnings, setBulkWarnings] = useState([]);
- const apiCredentialLabel = isQoder
- ? t("personalAccessTokenLabel")
- : webSessionCredential
- ? getWebSessionCredentialLabel(t, webSessionCredential, apiKeyOptional)
- : apiKeyOptional
- ? `${t("apiKeyLabel")} (${t("optional").toLowerCase()})`
- : t("apiKeyLabel");
- const apiCredentialPlaceholder = isVertex
- ? t("vertexServiceAccountPlaceholder")
- : isWebSessionCredential
- ? webSessionCredential.placeholder
- : isQoder
- ? t("qoderPatPlaceholder")
- : apiKeyOptional
- ? t("optional")
- : undefined;
- const apiCredentialHint = isQoder
- ? t("qoderPatHint")
- : isWebSessionCredential
- ? getWebSessionCredentialHint(t, webSessionCredential, providerDisplayName, false)
- : isLocalSelfHostedProvider
- ? t("localProviderApiKeyOptionalHint", {
- provider: localProviderMetadata?.name || providerName || provider || "",
- })
- : apiKeyOptional
- ? t("apiKeyOptionalHint")
- : undefined;
- const credentialValidationFailedMessage = isWebSessionCredential
- ? providerText(
- t,
- "webSessionCredentialValidationFailed",
- "Session credential validation failed. Sign in again, copy a fresh credential, and try again."
- )
- : t("apiKeyValidationFailed");
-
- const handleValidate = async () => {
- setValidating(true);
- setSaveError(null);
- try {
- const credentialInput = isCommandCode
- ? extractCommandCodeCredentialInput(formData.apiKey)
- : formData.apiKey;
- const res = await fetch("/api/providers/validate", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- provider,
- apiKey: credentialInput,
- validationModelId: formData.validationModelId || undefined,
- customUserAgent: formData.customUserAgent.trim() || undefined,
- baseUrl: formData.baseUrl.trim() || undefined,
- region: showsRegion ? formData.region.trim() || defaultRegion : undefined,
- cx: formData.cx.trim() || undefined,
- }),
- });
- const data = await res.json();
- setValidationResult(data.valid ? "success" : "failed");
- } catch {
- setValidationResult("failed");
- } finally {
- setValidating(false);
- }
- };
-
- const copyCommandCodeValue = async (value: string | undefined, key: string) => {
- if (!value) return;
- try {
- await navigator.clipboard.writeText(value);
- setCopiedCommandCodeField(key);
- window.setTimeout(() => setCopiedCommandCodeField(null), 1500);
- } catch {
- setSaveError("Copy failed. Select the text and copy it manually.");
- }
- };
-
- const handleSubmit = async () => {
- const credentialInput = isCommandCode
- ? extractCommandCodeCredentialInput(formData.apiKey)
- : formData.apiKey;
- if (!provider || (!isCompatible && !apiKeyOptional && !credentialInput)) return;
-
- setSaving(true);
- setSaveError(null);
- try {
- if (isGooglePse && !formData.cx.trim()) {
- setSaveError(t("searchEngineIdRequired"));
- return;
- }
-
- let validatedBaseUrl = null;
- if (usesBaseUrl) {
- const checked = normalizeAndValidateHttpBaseUrl(formData.baseUrl, defaultBaseUrl);
- if (checked.error) {
- setSaveError(checked.error);
- return;
- }
- validatedBaseUrl = checked.value;
- }
-
- let isValid = Boolean(isNoAuthWebSessionCredential && !credentialInput);
- let validationError: string | null = null;
- if (!isValid) {
- try {
- setValidating(true);
- setValidationResult(null);
- const res = await fetch("/api/providers/validate", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- provider,
- apiKey: credentialInput,
- validationModelId: formData.validationModelId || undefined,
- customUserAgent: formData.customUserAgent.trim() || undefined,
- baseUrl: formData.baseUrl.trim() || undefined,
- region: showsRegion ? formData.region.trim() || defaultRegion : undefined,
- cx: formData.cx.trim() || undefined,
- }),
- });
- const data = await res.json();
- isValid = !!data.valid;
- if (!isValid && data.error) {
- validationError = data.error;
- }
- setValidationResult(isValid ? "success" : "failed");
- } catch {
- setValidationResult("failed");
- } finally {
- setValidating(false);
- }
- }
-
- if (!isValid) {
- if (apiKeyOptional && !credentialInput) {
- // Bypass validation block for local/optional providers when no key is provided
- console.debug("Validation failed but apiKey is optional; proceeding to save.");
- } else {
- setSaveError(validationError || credentialValidationFailedMessage);
- return;
- }
- }
-
- const providerSpecificData: Record = {};
- if (formData.customUserAgent.trim()) {
- providerSpecificData.customUserAgent = formData.customUserAgent.trim();
- }
- if (formData.routingTags.trim()) {
- providerSpecificData.tags = parseRoutingTagsInput(formData.routingTags);
- }
- if (formData.excludedModels.trim()) {
- providerSpecificData.excludedModels = parseExcludedModelsInput(formData.excludedModels);
- }
- if (formData.passthroughModels) {
- providerSpecificData.passthroughModels = true;
- }
- if (provider === "bailian-coding-plan" && formData.consoleApiKey.trim()) {
- providerSpecificData.consoleApiKey = formData.consoleApiKey.trim();
- }
- if (isGooglePse && formData.cx.trim()) {
- providerSpecificData.cx = formData.cx.trim();
- }
- if (usesBaseUrl) {
- providerSpecificData.baseUrl = validatedBaseUrl;
- } else if (showsRegion) {
- providerSpecificData.region = formData.region.trim() || defaultRegion;
- } else if (isGlm) {
- providerSpecificData.apiRegion = formData.apiRegion;
- } else if (isCloudflare && formData.accountId.trim()) {
- providerSpecificData.accountId = formData.accountId.trim();
- }
- if (isCcCompatible && formData.ccCompatibleContext1m) {
- providerSpecificData.requestDefaults = { context1m: true };
- }
-
- const payload = {
- name: formData.name,
- apiKey: credentialInput.trim() || undefined,
- priority: formData.priority,
- testStatus: "active",
- providerSpecificData:
- Object.keys(providerSpecificData).length > 0 ? providerSpecificData : undefined,
- };
-
- const error = await onSave(payload);
- if (error) {
- setSaveError(typeof error === "string" ? error : t("failedSaveConnection"));
- }
- } finally {
- setSaving(false);
- }
- };
-
- const handleBulkSubmit = async () => {
- if (!provider) return;
- const parsed = parseBulkApiKeys(bulkText);
- setBulkWarnings(parsed.warnings);
- if (parsed.entries.length === 0) return;
-
- setSaving(true);
- setBulkResult(null);
- setSaveError(null);
-
- try {
- let providerSpecificData: Record | undefined;
- if (usesBaseUrl) {
- const checked = normalizeAndValidateHttpBaseUrl(formData.baseUrl, defaultBaseUrl);
- if (checked.error) {
- setSaveError(checked.error);
- return;
- }
- providerSpecificData = { baseUrl: checked.value };
- }
-
- const res = await fetch("/api/providers/bulk", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- provider,
- entries: parsed.entries.map((e) => ({ name: e.name, apiKey: e.apiKey })),
- priority: formData.priority || 1,
- providerSpecificData,
- validateKeys: bulkValidateKeys,
- }),
- });
- const data = await res.json();
- if (!res.ok) {
- setSaveError(typeof data?.error === "string" ? data.error : t("failedSaveConnection"));
- return;
- }
- setBulkResult({
- success: data.success || 0,
- failed: data.failed || 0,
- total: data.total || 0,
- errors: Array.isArray(data.errors) ? data.errors : [],
- });
- } catch (err) {
- setSaveError(err instanceof Error ? err.message : t("failedSaveConnection"));
- } finally {
- setSaving(false);
- }
- };
-
- if (!provider) return null;
-
- return (
-
-
- {bulkSupported && (
-
- {
- setMode("single");
- setBulkResult(null);
- setBulkWarnings([]);
- }}
- className={`px-3 py-1.5 text-sm font-medium transition-colors ${
- mode === "single"
- ? "border-b-2 border-primary text-text-main"
- : "text-text-muted hover:text-text-main"
- }`}
- >
- {t("bulkTabSingle")}
-
- {
- setMode("bulk");
- setSaveError(null);
- }}
- className={`px-3 py-1.5 text-sm font-medium transition-colors ${
- mode === "bulk"
- ? "border-b-2 border-primary text-text-main"
- : "text-text-muted hover:text-text-main"
- }`}
- >
- {t("bulkTabBulkAdd")}
-
-
- )}
-
- {bulkSupported && mode === "bulk" && (
-
-
{t("bulkAddFormatHint")}
-
- )}
-
- {(!bulkSupported || mode === "single") && (
- <>
- {isCcCompatible && (
-
-
-
- warning
-
-
{t("ccCompatibleValidationHint")}
-
-
- )}
- {isCommandCode && onStartCommandCodeAuth && (
-
-
-
- open_in_new
-
-
-
- {t("providerDetailBrowserManualConnect")}
-
-
- Open Command Code Studio, then paste the returned key/JSON/URL into the API
- key field below.
-
- {commandCodeAuthState?.message && (
-
- {commandCodeAuthPhaseLabel}: {commandCodeAuthState.message}
-
- )}
- {commandCodeAuthState?.authUrl && (
-
-
-
- {t("providerDetailAuthUrl")}
-
-
-
-
- copyCommandCodeValue(commandCodeAuthState.authUrl, "authUrl")
- }
- />
-
-
- {commandCodeAuthState.callbackUrl && (
-
-
- {t("providerDetailCallbackUrl")}
-
-
-
-
- copyCommandCodeValue(
- commandCodeAuthState.callbackUrl,
- "callbackUrl"
- )
- }
- />
-
-
- )}
-
- )}
-
-
- Connect in browser
-
-
-
- )}
-
setFormData({ ...formData, name: e.target.value })}
- placeholder={isQoder ? t("personalAccessTokenLabel") : t("productionKey")}
- />
- {webSessionCredential && (
-
- )}
- {!isNoAuthWebSessionCredential && (
-
-
setFormData({ ...formData, apiKey: e.target.value })}
- className="flex-1"
- placeholder={apiCredentialPlaceholder}
- hint={apiCredentialHint}
- autoComplete="off"
- spellCheck={false}
- autoCapitalize="off"
- />
-
-
- {validating
- ? t("checking")
- : webSessionCredential
- ? getWebSessionCredentialCheckLabel(t, webSessionCredential)
- : t("check")}
-
-
-
- )}
- {isGooglePse && (
-
setFormData({ ...formData, cx: e.target.value })}
- placeholder="012345678901234567890:abc123xyz"
- hint={t("searchEngineIdHint")}
- />
- )}
- {validationResult && (
-
- {validationResult === "success" ? t("valid") : t("invalid")}
-
- )}
- {saveError && (
-
- {saveError}
-
- )}
- {isCcCompatible && (
-
-
- setFormData({ ...formData, ccCompatibleContext1m: checked })
- }
- label={t("ccCompatibleContext1mLabel")}
- description={t("ccCompatibleContext1mDescription")}
- />
-
- )}
- {isCompatible && !isCcCompatible && (
-
- {isAnthropic
- ? t("validationChecksAnthropicCompatible", {
- provider: providerName || t("anthropicCompatibleName"),
- })
- : t("validationChecksOpenAiCompatible", {
- provider: providerName || t("openaiCompatibleName"),
- })}
-
- )}
-
setShowAdvanced(!showAdvanced)}
- aria-expanded={showAdvanced}
- aria-controls="add-api-key-advanced-settings"
- >
-
- ▶
-
- {t("advancedSettings")}
-
- {showAdvanced && (
-
- setFormData({ ...formData, customUserAgent: e.target.value })}
- placeholder="my-app/1.0"
- hint={t("customUserAgentHint")}
- />
- setFormData({ ...formData, routingTags: e.target.value })}
- placeholder={t("routingTagsPlaceholder")}
- hint={t("routingTagsHint")}
- />
- setFormData({ ...formData, excludedModels: e.target.value })}
- placeholder={t("excludedModelsPlaceholder")}
- hint={t("excludedModelsHint")}
- />
- setFormData({ ...formData, passthroughModels: checked })}
- label={t("perModelQuotaLabel")}
- description={t("perModelQuotaDescription")}
- />
- {provider === "bailian-coding-plan" && (
- setFormData({ ...formData, consoleApiKey: e.target.value })}
- placeholder={t("consoleApiKeyOraclePlaceholder")}
- hint={t("consoleApiKeyOracleHint")}
- type="password"
- />
- )}
-
- )}
-
setFormData({ ...formData, validationModelId: e.target.value })}
- hint={t("validationModelIdHint")}
- />
-
- setFormData({ ...formData, priority: Number.parseInt(e.target.value) || 1 })
- }
- />
- {usesBaseUrl && (
-
setFormData({ ...formData, baseUrl: e.target.value })}
- placeholder={getProviderBaseUrlPlaceholder(provider)}
- hint={getProviderBaseUrlHint(provider, t)}
- />
- )}
- {showsRegion && (
-
setFormData({ ...formData, region: e.target.value })}
- placeholder={defaultRegion}
- hint={t("regionHint")}
- />
- )}
- {isCloudflare && (
-
setFormData({ ...formData, accountId: e.target.value })}
- placeholder={t("accountIdPlaceholder")}
- hint={t("accountIdHint")}
- />
- )}
- {isGlm && (
-
-
- {t("apiRegionLabel")}
-
-
setFormData({ ...formData, apiRegion: e.target.value })}
- className="w-full px-3 py-2 text-sm border border-border rounded-lg bg-background focus:outline-none focus:border-primary"
- >
- {t("apiRegionInternational")}
- {t("apiRegionChina")}
-
-
{t("apiRegionHint")}
-
- )}
-
-
- {saving ? t("saving") : t("save")}
-
-
- {t("cancel")}
-
-
- >
- )}
-
-
- );
-}
-
-function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnectionModalProps) {
- const t = useTranslations("providers");
- const notify = useNotificationStore();
- const [formData, setFormData] = useState({
- name: "",
- priority: 1,
- maxConcurrent: "",
- rpm: "",
- tpm: "",
- tpd: "",
- minTime: "",
- rateLimitMaxConcurrent: "",
- apiKey: "",
- healthCheckInterval: 60,
- baseUrl: "",
- cx: "",
- region: "",
- apiRegion: "international",
- validationModelId: "",
- tag: "",
- routingTags: "",
- excludedModels: "",
- customUserAgent: "",
- accountId: "",
- codexReasoningEffort: "medium",
- codexServiceTier: "default" as CodexServiceTier,
- codexOpenaiStoreEnabled: false,
- consoleApiKey: "",
- ccCompatibleContext1m: false,
- cloudCodeProjectId: "",
- antigravityClientProfile: "ide",
- blockExtraUsage:
- connection?.provider === "claude"
- ? isClaudeExtraUsageBlockEnabled(connection?.provider, connection?.providerSpecificData)
- : false,
- passthroughModels: connection?.providerSpecificData?.passthroughModels === true,
- });
- const [testing, setTesting] = useState(false);
- const [testResult, setTestResult] = useState(null);
- const [validating, setValidating] = useState(false);
- const [validationResult, setValidationResult] = useState(null);
- const [saving, setSaving] = useState(false);
- const [saveError, setSaveError] = useState(null);
- const [extraApiKeys, setExtraApiKeys] = useState([]);
- const [newExtraKey, setNewExtraKey] = useState("");
- const [apiKeyHealth, setApiKeyHealth] = useState<
- Record<
- string,
- {
- status: "active" | "warning" | "invalid";
- failures: number;
- lastFailure: string | null;
- totalRequests?: number;
- totalFailures?: number;
- }
- >
- >({});
- const [showAdvanced, setShowAdvanced] = useState(false);
- const { emailsVisible: showEmail, toggleEmailVisibility: toggleShowEmail } =
- useEmailPrivacyStore();
-
- const usesBaseUrl = isBaseUrlConfigurableProvider(connection?.provider);
- const defaultBaseUrl = getProviderBaseUrlDefault(connection?.provider);
- const isVertex = connection?.provider === "vertex" || connection?.provider === "vertex-partner";
- const isBedrock = connection?.provider === "bedrock";
- const showsRegion = isVertex || isBedrock;
- const isGlm = isGlmProvider(connection?.provider);
- const isCloudflare = connection?.provider === "cloudflare-ai";
- const isCodex = connection?.provider === "codex";
- const isClaude = connection?.provider === "claude";
- const isGeminiCli = connection?.provider === "gemini-cli";
- const isAntigravity = connection?.provider === "antigravity";
- const supportsGoogleProjectId = isGeminiCli || isAntigravity;
- const localProviderMetadata = getLocalProviderMetadata(connection?.provider);
- const isLocalSelfHostedProvider = !!localProviderMetadata;
- const isGooglePse = connection?.provider === "google-pse-search";
- const webSessionCredential = getWebSessionCredentialRequirement(connection?.provider);
- const isNoAuthWebSessionCredential = webSessionCredential?.kind === "none";
- const isWebSessionCredential = !!webSessionCredential && webSessionCredential.kind !== "none";
- const providerDisplayName =
- (connection?.provider ? resolveDashboardProviderInfo(connection.provider)?.name : null) ||
- connection?.provider ||
- "";
- const apiKeyOptional =
- providerAllowsOptionalApiKey(connection?.provider) || Boolean(isNoAuthWebSessionCredential);
- const isCcCompatible = isClaudeCodeCompatibleProvider(connection?.provider);
- const defaultRegion = isBedrock ? "eu-west-2" : "us-central1";
- const apiCredentialLabel = webSessionCredential
- ? getWebSessionCredentialLabel(t, webSessionCredential, apiKeyOptional)
- : apiKeyOptional
- ? t("apiKeyOptionalLabel")
- : t("apiKeyLabel");
- const apiCredentialPlaceholder = isWebSessionCredential
- ? webSessionCredential.placeholder
- : isVertex
- ? t("vertexServiceAccountPlaceholder")
- : t("enterNewApiKey");
- const apiCredentialHint = isWebSessionCredential
- ? getWebSessionCredentialHint(t, webSessionCredential, providerDisplayName, true)
- : isLocalSelfHostedProvider
- ? t("localProviderApiKeyOptionalHint", {
- provider: localProviderMetadata?.name || connection?.provider || "",
- })
- : apiKeyOptional
- ? t("apiKeyOptionalHint")
- : t("leaveBlankKeepCurrentApiKey");
- const codexAccountServiceTierOptions = useMemo(
- () =>
- CODEX_ACCOUNT_SERVICE_TIER_VALUES.map((value) => ({
- value,
- label: getCodexServiceTierLabel(t, value),
- })),
- [t]
- );
-
- useEffect(() => {
- if (isOpen && connection) {
- const rawBaseUrl = connection.providerSpecificData?.baseUrl;
- const existingBaseUrl = typeof rawBaseUrl === "string" ? rawBaseUrl : "";
- const rawRegion = connection.providerSpecificData?.region;
- const existingRegion = typeof rawRegion === "string" ? rawRegion : "";
- const rawCustomUserAgent = connection.providerSpecificData?.customUserAgent;
- const existingCustomUserAgent =
- typeof rawCustomUserAgent === "string" ? rawCustomUserAgent : "";
- const rawCx = connection.providerSpecificData?.cx;
- const existingCx = typeof rawCx === "string" ? rawCx : "";
- const rawAccountId = connection.providerSpecificData?.accountId;
- const existingAccountId = typeof rawAccountId === "string" ? rawAccountId : "";
- const codexRequestDefaults = getCodexRequestDefaults(connection.providerSpecificData);
- const ccRequestDefaults = getClaudeCodeCompatibleRequestDefaults(
- connection.providerSpecificData
- );
- const rawConsoleApiKey = connection.providerSpecificData?.consoleApiKey;
- const existingConsoleApiKey = typeof rawConsoleApiKey === "string" ? rawConsoleApiKey : "";
- setFormData({
- name: connection.name || "",
- priority: connection.priority || 1,
- maxConcurrent:
- connection.maxConcurrent !== null && connection.maxConcurrent !== undefined
- ? String(connection.maxConcurrent)
- : "",
- rpm:
- connection.rateLimitOverrides?.rpm != null
- ? String(connection.rateLimitOverrides.rpm)
- : "",
- tpm:
- connection.rateLimitOverrides?.tpm != null
- ? String(connection.rateLimitOverrides.tpm)
- : "",
- tpd:
- connection.rateLimitOverrides?.tpd != null
- ? String(connection.rateLimitOverrides.tpd)
- : "",
- minTime:
- connection.rateLimitOverrides?.minTime != null
- ? String(connection.rateLimitOverrides.minTime)
- : "",
- rateLimitMaxConcurrent:
- connection.rateLimitOverrides?.maxConcurrent != null
- ? String(connection.rateLimitOverrides.maxConcurrent)
- : "",
- apiKey: "",
- healthCheckInterval: connection.healthCheckInterval ?? 60,
- baseUrl: existingBaseUrl || defaultBaseUrl,
- cx: existingCx,
- region: existingRegion || (showsRegion ? defaultRegion : ""),
- apiRegion: (connection.providerSpecificData?.apiRegion as string) || "international",
- validationModelId: (connection.providerSpecificData?.validationModelId as string) || "",
- tag: (connection.providerSpecificData?.tag as string) || "",
- routingTags: formatRoutingTagsInput(connection.providerSpecificData?.tags),
- excludedModels: formatExcludedModelsInput(
- connection.providerSpecificData?.excludedModels ??
- connection.providerSpecificData?.excluded_models
- ),
- customUserAgent: existingCustomUserAgent,
- accountId: existingAccountId,
- codexReasoningEffort: codexRequestDefaults.reasoningEffort,
- codexServiceTier: codexRequestDefaults.serviceTier ?? "default",
- codexOpenaiStoreEnabled: connection.providerSpecificData?.openaiStoreEnabled === true,
- consoleApiKey: existingConsoleApiKey,
- ccCompatibleContext1m: ccRequestDefaults.context1m,
- cloudCodeProjectId:
- (connection.providerSpecificData?.projectId as string) || connection.projectId || "",
- antigravityClientProfile: normalizeAntigravityClientProfileSetting(
- connection.providerSpecificData?.clientProfile
- ),
- blockExtraUsage: isClaudeExtraUsageBlockEnabled(
- connection.provider,
- connection.providerSpecificData
- ),
- passthroughModels: connection?.providerSpecificData?.passthroughModels === true,
- });
- // Load existing extra keys from providerSpecificData
- const existing = connection.providerSpecificData?.extraApiKeys;
- setExtraApiKeys(Array.isArray(existing) ? existing : []);
- // Load API key health status
- const health = connection.providerSpecificData?.apiKeyHealth as
- | Record<
- string,
- {
- status: "active" | "warning" | "invalid";
- failures: number;
- lastFailure: string | null;
- totalRequests?: number;
- totalFailures?: number;
- }
- >
- | undefined;
- setApiKeyHealth(health || {});
- setNewExtraKey("");
- setShowAdvanced(!!existingCustomUserAgent);
- // email visibility controlled by global store
- setTestResult(null);
- setValidationResult(null);
- setSaveError(null);
- }
- }, [isOpen, connection, defaultBaseUrl, showsRegion, defaultRegion]);
-
- const handleTest = async () => {
- if (!connection?.provider) return;
- setTesting(true);
- setTestResult(null);
- try {
- const res = await fetch(`/api/providers/${connection.id}/test`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- validationModelId: formData.validationModelId || undefined,
- }),
- });
- const data = await res.json();
- setTestResult({
- valid: !!data.valid,
- diagnosis: data.diagnosis || null,
- message: data.error || null,
- });
- } catch {
- setTestResult({
- valid: false,
- diagnosis: { type: "network_error" },
- message: t("failedTestConnection"),
- });
- } finally {
- setTesting(false);
- }
- };
-
- const handleValidate = async () => {
- if (
- !connection?.provider ||
- isNoAuthWebSessionCredential ||
- (!isCompatible && !apiKeyOptional && !formData.apiKey)
- ) {
- return;
- }
- setValidating(true);
- setValidationResult(null);
- try {
- const res = await fetch("/api/providers/validate", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- provider: connection.provider,
- apiKey: formData.apiKey,
- validationModelId: formData.validationModelId || undefined,
- customUserAgent: formData.customUserAgent.trim() || undefined,
- baseUrl: formData.baseUrl.trim() || undefined,
- region: showsRegion ? formData.region.trim() || defaultRegion : undefined,
- cx: formData.cx.trim() || undefined,
- }),
- });
- const data = await res.json();
- setValidationResult(data.valid ? "success" : "failed");
- } catch {
- setValidationResult("failed");
- } finally {
- setValidating(false);
- }
- };
-
- const handleAddParsedExtraKeys = (raw: string) => {
- const { added, duplicates } = parseExtraApiKeys(raw, extraApiKeys);
- if (added.length > 0) {
- setExtraApiKeys((prev) => [...prev, ...added]);
- notify.success(t("bulkPasteAdded", { count: added.length }));
- }
- if (duplicates > 0) {
- notify.warning(t("bulkPasteDuplicatesIgnored", { count: duplicates }));
- }
- };
-
- const handleSubmit = async () => {
- setSaving(true);
- setSaveError(null);
- try {
- const trimmedMaxConcurrent = formData.maxConcurrent.trim();
- const trimmedCloudCodeProjectId = formData.cloudCodeProjectId.trim();
- let parsedMaxConcurrent: number | null = null;
- if (trimmedMaxConcurrent) {
- const numericMaxConcurrent = Number(trimmedMaxConcurrent);
- if (!Number.isInteger(numericMaxConcurrent) || numericMaxConcurrent < 0) {
- setSaveError(t("maxConcurrentWholeNumberError"));
- return;
- }
- parsedMaxConcurrent = numericMaxConcurrent;
- }
-
- const updates: any = {
- name: formData.name,
- priority: formData.priority,
- maxConcurrent: parsedMaxConcurrent,
- healthCheckInterval: formData.healthCheckInterval,
- };
-
- // Build rateLimitOverrides from non-empty fields
- const overrides: Record = {};
- if (formData.rpm.trim()) overrides.rpm = Number(formData.rpm);
- if (formData.tpm.trim()) overrides.tpm = Number(formData.tpm);
- if (formData.tpd.trim()) overrides.tpd = Number(formData.tpd);
- if (formData.minTime.trim()) overrides.minTime = Number(formData.minTime);
- if (formData.rateLimitMaxConcurrent.trim())
- overrides.maxConcurrent = Number(formData.rateLimitMaxConcurrent);
- updates.rateLimitOverrides = Object.keys(overrides).length > 0 ? overrides : null;
-
- if (supportsGoogleProjectId) {
- updates.projectId = trimmedCloudCodeProjectId || null;
- }
-
- if (isGooglePse && !formData.cx.trim()) {
- setSaveError(t("searchEngineIdRequired"));
- return;
- }
-
- let validatedBaseUrl = null;
- if (usesBaseUrl) {
- const checked = normalizeAndValidateHttpBaseUrl(formData.baseUrl, defaultBaseUrl);
- if (checked.error) {
- setSaveError(checked.error);
- return;
- }
- validatedBaseUrl = checked.value;
- }
-
- if (!isOAuth && formData.apiKey) {
- updates.apiKey = formData.apiKey;
- let isValid = validationResult === "success";
- if (!isValid) {
- try {
- setValidating(true);
- setValidationResult(null);
- const res = await fetch("/api/providers/validate", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- provider: connection.provider,
- apiKey: formData.apiKey,
- validationModelId: formData.validationModelId || undefined,
- customUserAgent: formData.customUserAgent.trim() || undefined,
- baseUrl: formData.baseUrl.trim() || undefined,
- region: showsRegion ? formData.region.trim() || defaultRegion : undefined,
- cx: formData.cx.trim() || undefined,
- }),
- });
- const data = await res.json();
- isValid = !!data.valid;
- setValidationResult(isValid ? "success" : "failed");
- } catch {
- setValidationResult("failed");
- } finally {
- setValidating(false);
- }
- }
- if (isValid) {
- updates.testStatus = "active";
- updates.lastError = null;
- updates.lastErrorAt = null;
- updates.lastErrorType = null;
- updates.lastErrorSource = null;
- updates.errorCode = null;
- updates.rateLimitedUntil = null;
- }
- }
- // Persist extra API keys and baseUrl in providerSpecificData
- if (!isOAuth) {
- updates.providerSpecificData = {
- ...(connection.providerSpecificData || {}),
- extraApiKeys: extraApiKeys.filter((k) => k.trim().length > 0),
- tag: formData.tag.trim() || undefined,
- tags: parseRoutingTagsInput(formData.routingTags),
- excludedModels: parseExcludedModelsInput(formData.excludedModels),
- customUserAgent: formData.customUserAgent.trim(),
- // Only write when explicitly enabled; omit to let registry default take effect
- ...(formData.passthroughModels ? { passthroughModels: true } : {}),
- };
- if (connection.provider === "bailian-coding-plan") {
- if (formData.consoleApiKey.trim()) {
- updates.providerSpecificData.consoleApiKey = formData.consoleApiKey.trim();
- } else {
- updates.providerSpecificData.consoleApiKey = undefined;
- }
- }
- if (formData.validationModelId) {
- updates.providerSpecificData.validationModelId = formData.validationModelId;
- }
- if (isGooglePse) {
- updates.providerSpecificData.cx = formData.cx.trim() || undefined;
- }
- if (usesBaseUrl) {
- updates.providerSpecificData.baseUrl = validatedBaseUrl;
- } else if (showsRegion) {
- updates.providerSpecificData.region = formData.region.trim() || defaultRegion;
- } else if (isGlm) {
- updates.providerSpecificData.apiRegion = formData.apiRegion;
- } else if (isCloudflare && formData.accountId.trim()) {
- updates.providerSpecificData.accountId = formData.accountId.trim();
- }
- if (supportsGoogleProjectId) {
- updates.providerSpecificData.projectId = trimmedCloudCodeProjectId || null;
- }
- if (isCcCompatible) {
- const currentRequestDefaults =
- updates.providerSpecificData.requestDefaults &&
- typeof updates.providerSpecificData.requestDefaults === "object" &&
- !Array.isArray(updates.providerSpecificData.requestDefaults)
- ? { ...(updates.providerSpecificData.requestDefaults as Record) }
- : {};
- if (formData.ccCompatibleContext1m) {
- currentRequestDefaults.context1m = true;
- } else {
- delete currentRequestDefaults.context1m;
- }
- updates.providerSpecificData.requestDefaults =
- Object.keys(currentRequestDefaults).length > 0 ? currentRequestDefaults : undefined;
- }
- } else {
- // Also persist tag for OAuth accounts
- updates.providerSpecificData = {
- ...(connection.providerSpecificData || {}),
- tag: formData.tag.trim() || undefined,
- tags: parseRoutingTagsInput(formData.routingTags),
- excludedModels: parseExcludedModelsInput(formData.excludedModels),
- };
- if (isClaude) {
- updates.providerSpecificData.blockExtraUsage = formData.blockExtraUsage;
- }
- if (isCodex) {
- updates.providerSpecificData.requestDefaults = {
- reasoningEffort: formData.codexReasoningEffort,
- ...(formData.codexServiceTier !== "default"
- ? { serviceTier: formData.codexServiceTier }
- : {}),
- };
- updates.providerSpecificData.openaiStoreEnabled =
- formData.codexOpenaiStoreEnabled === true;
- }
- if (supportsGoogleProjectId) {
- updates.providerSpecificData.projectId = trimmedCloudCodeProjectId || null;
- }
- }
- if (isAntigravity) {
- updates.providerSpecificData = {
- ...(connection.providerSpecificData || {}),
- ...(updates.providerSpecificData || {}),
- clientProfile: normalizeAntigravityClientProfileSetting(
- formData.antigravityClientProfile
- ),
- };
- }
- const error = (await onSave(updates)) as void | unknown;
- if (error) {
- setSaveError(typeof error === "string" ? error : t("failedSaveConnection"));
- }
- } finally {
- setSaving(false);
- }
- };
-
- if (!connection) return null;
-
- const isOAuth = connection.authType === "oauth";
- const isCompatible =
- isOpenAICompatibleProvider(connection.provider) ||
- isAnthropicCompatibleProvider(connection.provider);
- const testErrorMeta =
- !testResult?.valid && testResult?.diagnosis?.type
- ? ERROR_TYPE_LABELS[testResult.diagnosis.type] || null
- : null;
-
- return (
-
-
-
setFormData({ ...formData, name: e.target.value })}
- placeholder={isOAuth ? t("accountName") : t("productionKey")}
- />
-
setFormData({ ...formData, tag: e.target.value })}
- placeholder={t("tagGroupPlaceholder")}
- hint={t("tagGroupHint")}
- />
-
setFormData({ ...formData, routingTags: e.target.value })}
- placeholder={t("routingTagsPlaceholder")}
- hint={t("routingTagsHint")}
- />
-
setFormData({ ...formData, excludedModels: e.target.value })}
- placeholder={t("excludedModelsPlaceholder")}
- hint={t("excludedModelsHint")}
- />
- {isCodex && (
-
- setFormData({ ...formData, codexReasoningEffort: e.target.value })}
- hint={t("defaultThinkingStrengthHint")}
- />
-
- setFormData({
- ...formData,
- codexServiceTier: event.target.value as CodexServiceTier,
- })
- }
- hint={providerText(
- t,
- "codexServiceTierDescription",
- "Default uses the normal Codex tier. Priority shows as Fast; Flex uses the flex service tier when available."
- )}
- />
- setFormData({ ...formData, codexOpenaiStoreEnabled: checked })}
- label={t("openaiResponsesStoreLabel")}
- description={t("openaiResponsesStoreDescription")}
- />
-
- )}
- {isClaude && (
-
- setFormData({ ...formData, blockExtraUsage: checked })}
- label={t("blockClaudeExtraUsageLabel")}
- description={t("blockClaudeExtraUsageDescription")}
- />
-
- )}
- {isCcCompatible && (
-
- setFormData({ ...formData, ccCompatibleContext1m: checked })}
- label={t("ccCompatibleContext1mLabel")}
- description={t("ccCompatibleContext1mDescription")}
- />
-
- )}
- {supportsGoogleProjectId && (
-
- {isAntigravity && (
- ({
- value: option.value,
- label: t(option.labelKey),
- }))}
- onChange={(e) =>
- setFormData({ ...formData, antigravityClientProfile: e.target.value })
- }
- hint={t("antigravityClientProfileHint")}
- />
- )}
- setFormData({ ...formData, cloudCodeProjectId: e.target.value })}
- placeholder={
- isAntigravity
- ? t("antigravityProjectIdPlaceholder")
- : t("geminiCliProjectIdPlaceholder")
- }
- hint={isAntigravity ? t("antigravityProjectIdHint") : t("geminiCliProjectIdHint")}
- className="font-mono text-xs"
- />
-
- )}
- {isOAuth && connection.email && (
-
-
{t("email")}
-
-
- {showEmail ? connection.email : maskEmail(connection.email)}
-
-
-
- {showEmail ? "visibility_off" : "visibility"}
-
-
-
-
- )}
- {isOAuth && (
-
- setFormData({
- ...formData,
- healthCheckInterval: Math.max(0, Number.parseInt(e.target.value) || 0),
- })
- }
- hint={t("healthCheckHint")}
- />
- )}
-
- setFormData({ ...formData, priority: Number.parseInt(e.target.value) || 1 })
- }
- />
-
{
- const nextValue = e.target.value;
- setFormData({ ...formData, maxConcurrent: nextValue });
- if (saveError && nextValue.trim()) {
- const numericValue = Number(nextValue);
- if (Number.isInteger(numericValue) && numericValue >= 0) {
- setSaveError(null);
- }
- }
- }}
- placeholder="0"
- hint={t("accountConcurrencyCapHint")}
- />
- {saveError && (
-
- {saveError}
-
- )}
- {!isOAuth && (
- <>
- {webSessionCredential && (
-
- )}
- {!isNoAuthWebSessionCredential && (
-
-
setFormData({ ...formData, apiKey: e.target.value })}
- placeholder={apiCredentialPlaceholder}
- hint={apiCredentialHint}
- className="flex-1"
- autoComplete="off"
- spellCheck={false}
- autoCapitalize="off"
- />
-
-
- {validating
- ? t("checking")
- : webSessionCredential
- ? getWebSessionCredentialCheckLabel(t, webSessionCredential)
- : t("check")}
-
-
-
- )}
- {isGooglePse && (
-
setFormData({ ...formData, cx: e.target.value })}
- placeholder="012345678901234567890:abc123xyz"
- hint={t("searchEngineIdHint")}
- />
- )}
- {validationResult && (
-
- {validationResult === "success" ? t("valid") : t("invalid")}
-
- )}
-
setShowAdvanced(!showAdvanced)}
- aria-expanded={showAdvanced}
- aria-controls="edit-connection-advanced-settings"
- >
-
- ▶
-
- {t("advancedSettings")}
-
- {showAdvanced && (
-
- )}
-
setFormData({ ...formData, validationModelId: e.target.value })}
- hint={t("validationModelIdHint")}
- />
- >
- )}
-
- {usesBaseUrl && (
-
setFormData({ ...formData, baseUrl: e.target.value })}
- placeholder={getProviderBaseUrlPlaceholder(connection.provider)}
- hint={getProviderBaseUrlHint(connection.provider, t)}
- />
- )}
-
- {showsRegion && (
-
setFormData({ ...formData, region: e.target.value })}
- placeholder={defaultRegion}
- hint={t("regionHint")}
- />
- )}
-
- {isCloudflare && (
-
setFormData({ ...formData, accountId: e.target.value })}
- placeholder={t("accountIdPlaceholder")}
- hint={t("accountIdHint")}
- />
- )}
-
- {isGlm && (
-
-
- {t("apiRegionLabel")}
-
-
setFormData({ ...formData, apiRegion: e.target.value })}
- className="w-full px-3 py-2 text-sm border border-border rounded-lg bg-background focus:outline-none focus:border-primary"
- >
- {t("apiRegionInternational")}
- {t("apiRegionChina")}
-
-
{t("apiRegionHint")}
-
- )}
-
- {/* T07: API Key Health Status */}
- {!isOAuth && connection?.apiKey && (
-
-
{t("apiKeyHealthLabel")}
-
- {/* Primary Key Health */}
- {(() => {
- const keyId = "primary";
- const health = apiKeyHealth[keyId];
- const statusColor =
- health?.status === "invalid"
- ? "text-red-400"
- : health?.status === "warning"
- ? "text-yellow-400"
- : "text-text-muted";
- const statusIcon =
- health?.status === "invalid" ? "🔴" : health?.status === "warning" ? "🟡" : "🟢";
- const statusLabel =
- health?.status === "invalid"
- ? t("apiKeyStatusInvalid")
- : health?.status === "warning"
- ? t("apiKeyStatusWarning", { count: health.failures })
- : t("apiKeyStatusActive");
-
- return (
-
-
- {statusIcon} {t("primaryKey")}: {connection.apiKey.slice(0, 6)}...
- {connection.apiKey.slice(-4)}
-
- {health && (
-
- {health.failures}x
- {health.lastFailure ? ` · ${formatTimeAgo(health.lastFailure)}` : ""}
- {health.totalRequests != null
- ? ` · (${health.totalRequests} req${health.totalFailures != null ? `, ${health.totalFailures} fail` : ""})`
- : ""}
-
- )}
-
- );
- })()}
-
-
- )}
-
- {/* T07: Extra API Keys for round-robin rotation */}
- {!isOAuth && (
-
-
-
- {t("extraApiKeysLabel")}
-
- ({t("extraApiKeysHint")})
-
-
- {extraApiKeys.length > 0 && (
- setExtraApiKeys([])}
- className="px-2.5 py-1.5 rounded-md bg-red-500/10 text-red-400 hover:bg-red-500/20 hover:text-red-300 text-xs font-medium transition-colors"
- >
- {t("deleteAllExtraApiKeys")}
-
- )}
-
- {extraApiKeys.length > 0 && (
-
- {extraApiKeys.map((key, idx) => {
- const keyId = `extra_${idx}`;
- const health = apiKeyHealth[keyId];
- const statusColor =
- health?.status === "invalid"
- ? "text-red-400"
- : health?.status === "warning"
- ? "text-yellow-400"
- : "text-text-muted";
- const statusIcon =
- health?.status === "invalid"
- ? "🔴"
- : health?.status === "warning"
- ? "🟡"
- : "🟢";
- const statusLabel =
- health?.status === "invalid"
- ? t("apiKeyStatusInvalid")
- : health?.status === "warning"
- ? t("apiKeyStatusWarning", { count: health.failures })
- : t("apiKeyStatusActive");
-
- return (
-
-
- {statusIcon}{" "}
- {t("extraApiKeyMasked", {
- index: idx + 2,
- prefix: key.slice(0, 6),
- suffix: key.slice(-4),
- })}
-
-
- {health && (
-
- {health.failures}x
- {health.lastFailure ? ` · ${formatTimeAgo(health.lastFailure)}` : ""}
- {health.totalRequests != null
- ? ` · (${health.totalRequests} req${health.totalFailures != null ? `, ${health.totalFailures} fail` : ""})`
- : ""}
-
- )}
- setExtraApiKeys(extraApiKeys.filter((_, i) => i !== idx))}
- className="p-1.5 rounded hover:bg-red-500/10 text-red-400 hover:text-red-500"
- title={t("removeThisKey")}
- >
- close
-
-
-
- );
- })}
-
- )}
-
- setNewExtraKey(e.target.value)}
- placeholder={t("addAnotherApiKey")}
- className="flex-1 text-sm bg-sidebar/50 border border-border rounded px-3 py-2 text-text-main placeholder:text-text-muted focus:ring-1 focus:ring-primary outline-none"
- onKeyDown={(e) => {
- if (e.key === "Enter" && newExtraKey.trim()) {
- setExtraApiKeys([...extraApiKeys, newExtraKey.trim()]);
- setNewExtraKey("");
- }
- }}
- onPaste={(e) => {
- const text = e.clipboardData.getData("text");
- if (!/\r?\n/.test(text)) return;
- e.preventDefault();
- handleAddParsedExtraKeys(text);
- }}
- />
- {
- if (newExtraKey.trim()) {
- setExtraApiKeys([...extraApiKeys, newExtraKey.trim()]);
- setNewExtraKey("");
- }
- }}
- disabled={!newExtraKey.trim()}
- className="px-3 py-2 rounded bg-primary/10 text-primary hover:bg-primary/20 disabled:opacity-40 text-sm font-medium"
- >
- {t("add")}
-
-
-
{t("bulkPasteHint")}
- {extraApiKeys.length > 0 && (
-
- {t("totalKeysRotating", { count: extraApiKeys.length + 1 })}
-
- )}
-
- )}
-
- {/* Test Connection */}
- {!isCompatible && (
-
-
- {testing ? t("testing") : t("testConnection")}
-
- {testResult && (
- <>
-
- {testResult.valid ? t("valid") : t("failed")}
-
- {testErrorMeta && (
- {t(testErrorMeta.labelKey)}
- )}
- >
- )}
-
- )}
-
-
-
- {saving ? t("saving") : t("save")}
-
-
- {t("cancel")}
-
-
-
-
- );
-}
diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/WebSessionCredentialGuide.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/WebSessionCredentialGuide.tsx
new file mode 100644
index 0000000000..f4f30ddce9
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/providers/[id]/components/WebSessionCredentialGuide.tsx
@@ -0,0 +1,114 @@
+"use client";
+
+// Issue #3501 Phase 1c — extracted from the god-component.
+// Shared by AddApiKeyModal and EditConnectionModal; imports only leaf modules
+// (no cycle risk).
+
+import type { WebSessionCredentialRequirement } from "../webSessionCredentials";
+import { providerText, type ProviderMessageTranslator } from "../providerPageHelpers";
+
+export interface WebSessionCredentialGuideProps {
+ requirement: WebSessionCredentialRequirement;
+ providerName: string;
+ t: ProviderMessageTranslator;
+}
+
+export default function WebSessionCredentialGuide({
+ requirement,
+ providerName,
+ t,
+}: WebSessionCredentialGuideProps) {
+ if (requirement.kind === "none") {
+ return (
+
+
+
+ check_circle
+
+
+
+ {providerText(t, "webNoAuthGuideTitle", "No credential required")}
+
+
+ {providerText(
+ t,
+ "webNoAuthGuideBody",
+ "{provider} does not need an API key or cookie. Save the connection to use its free web endpoint.",
+ { provider: providerName }
+ )}
+
+
+
+
+ );
+ }
+
+ const requiredCredentialKey =
+ requirement.kind === "token" ? "webTokenRequiredCredential" : "webCookieRequiredCredential";
+ const requiredCredentialFallback =
+ requirement.kind === "token" ? "Required token: {credential}" : "Required cookie: {credential}";
+
+ return (
+
+
+
cookie
+
+
+
+ {providerText(t, "webSessionGuideTitle", "How to get the session credential")}
+
+
+ {providerText(
+ t,
+ "webSessionGuideIntro",
+ "{provider} uses a browser web session instead of an API key.",
+ { provider: providerName }
+ )}
+
+
+
+ {providerText(t, requiredCredentialKey, requiredCredentialFallback, {
+ credential: requirement.credentialName,
+ })}
+
+
+
+ {providerText(t, "webSessionGuideStep1", "Sign in to {provider} in your browser.", {
+ provider: providerName,
+ })}
+
+
+ {providerText(
+ t,
+ "webSessionGuideStep2",
+ "Open the browser developer tools and inspect a request made by the web app."
+ )}
+
+
+ {providerText(
+ t,
+ "webSessionGuideStep3",
+ "Copy the required credential from the provider's own domain. For cookies, copy only the Cookie header value and omit Cookie:.",
+ { credential: requirement.credentialName }
+ )}
+
+
+ {providerText(
+ t,
+ "webSessionGuideStep4",
+ "Paste it here and check the connection. If it stops working, sign in again and replace it with a fresh value."
+ )}
+
+
+
+ {providerText(
+ t,
+ "webSessionSecurityHint",
+ "Treat this like a password: it may access your signed-in web account until it expires or is revoked."
+ )}
+
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx
new file mode 100644
index 0000000000..df432143dc
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx
@@ -0,0 +1,842 @@
+"use client";
+
+// Issue #3501 Phase 1c — extracted from the god-component.
+// ~787-LOC modal for adding a new API key / credential to a provider.
+
+import { useState, useEffect, useRef } from "react";
+import { useTranslations } from "next-intl";
+import { Button, Badge, Input, Modal, Toggle } from "@/shared/components";
+import {
+ providerAllowsOptionalApiKey,
+ supportsBulkApiKey,
+} from "@/shared/constants/providers";
+import { parseBulkApiKeys } from "@/shared/utils/bulkApiKeyParser";
+import {
+ isBaseUrlConfigurableProvider,
+ getProviderBaseUrlDefault,
+ getProviderBaseUrlHint,
+ getProviderBaseUrlPlaceholder,
+ isGlmProvider,
+ parseRoutingTagsInput,
+ parseExcludedModelsInput,
+ getWebSessionCredentialLabel,
+ getWebSessionCredentialHint,
+ getWebSessionCredentialCheckLabel,
+ getAddCredentialModalTitle,
+ getLocalProviderMetadata,
+ normalizeAndValidateHttpBaseUrl,
+ extractCommandCodeCredentialInput,
+ providerText,
+ type CommandCodeAuthFlowState,
+} from "../../providerPageHelpers";
+import { getWebSessionCredentialRequirement } from "../../webSessionCredentials";
+import WebSessionCredentialGuide from "../WebSessionCredentialGuide";
+
+export interface AddApiKeyModalProps {
+ isOpen: boolean;
+ provider?: string;
+ providerName?: string;
+ initialBaseUrl?: string;
+ isCompatible?: boolean;
+ isAnthropic?: boolean;
+ isCcCompatible?: boolean;
+ isCommandCode?: boolean;
+ commandCodeAuthState?: CommandCodeAuthFlowState;
+ onStartCommandCodeAuth?: () => void;
+ onSave: (data: {
+ name: string;
+ apiKey?: string;
+ priority: number;
+ baseUrl?: string;
+ providerSpecificData?: Record;
+ }) => Promise;
+ onClose: () => void;
+}
+
+export default function AddApiKeyModal({
+ isOpen,
+ provider,
+ providerName,
+ initialBaseUrl,
+ isCompatible,
+ isAnthropic,
+ isCcCompatible,
+ isCommandCode,
+ commandCodeAuthState,
+ onStartCommandCodeAuth,
+ onSave,
+ onClose,
+}: AddApiKeyModalProps) {
+ const t = useTranslations("providers");
+ const usesBaseUrl = isBaseUrlConfigurableProvider(provider);
+ const defaultBaseUrl = getProviderBaseUrlDefault(provider);
+ const isVertex = provider === "vertex" || provider === "vertex-partner";
+ const isBedrock = provider === "bedrock";
+ const showsRegion = isVertex || isBedrock;
+ const defaultRegion = isBedrock ? "eu-west-2" : "us-central1";
+ const isGlm = isGlmProvider(provider);
+ const isQoder = provider === "qoder";
+ const isCloudflare = provider === "cloudflare-ai";
+ const localProviderMetadata = getLocalProviderMetadata(provider);
+ const isLocalSelfHostedProvider = !!localProviderMetadata;
+ const isGooglePse = provider === "google-pse-search";
+ const webSessionCredential = getWebSessionCredentialRequirement(provider);
+ const isNoAuthWebSessionCredential = webSessionCredential?.kind === "none";
+ const isWebSessionCredential = !!webSessionCredential && webSessionCredential.kind !== "none";
+ const providerDisplayName = providerName || provider || "";
+ const apiKeyOptional =
+ providerAllowsOptionalApiKey(provider) || Boolean(isNoAuthWebSessionCredential);
+ const commandCodeAuthPhaseLabel = commandCodeAuthState
+ ? {
+ idle: "Ready",
+ starting: "Starting…",
+ polling: "Waiting for browser…",
+ received: "Browser approved",
+ applying: "Applying key…",
+ applied: "Connected",
+ expired: "Link expired",
+ error: "Connection failed",
+ }[commandCodeAuthState.phase]
+ : null;
+
+ const [formData, setFormData] = useState({
+ name: "",
+ apiKey: "",
+ priority: 1,
+ baseUrl: initialBaseUrl || defaultBaseUrl,
+ cx: "",
+ region: showsRegion ? defaultRegion : "",
+ apiRegion: "international",
+ validationModelId: "",
+ routingTags: "",
+ excludedModels: "",
+ customUserAgent: "",
+ accountId: "",
+ consoleApiKey: "",
+ ccCompatibleContext1m: false,
+ passthroughModels: false,
+ });
+ const [validating, setValidating] = useState(false);
+ const [validationResult, setValidationResult] = useState(null);
+ const [saving, setSaving] = useState(false);
+ const [saveError, setSaveError] = useState(null);
+ const [showAdvanced, setShowAdvanced] = useState(false);
+ const [copiedCommandCodeField, setCopiedCommandCodeField] = useState(null);
+ const wasOpenRef = useRef(false);
+
+ useEffect(() => {
+ const wasOpen = wasOpenRef.current;
+ wasOpenRef.current = isOpen;
+ if (!isOpen || wasOpen) return;
+ setFormData((current) => ({
+ ...current,
+ baseUrl: initialBaseUrl || defaultBaseUrl,
+ }));
+ }, [defaultBaseUrl, initialBaseUrl, isOpen]);
+
+ const bulkSupported = supportsBulkApiKey(provider);
+ const [mode, setMode] = useState<"single" | "bulk">("single");
+ const [bulkText, setBulkText] = useState("");
+ const [bulkValidateKeys, setBulkValidateKeys] = useState(false);
+ const [bulkResult, setBulkResult] = useState<{
+ success: number;
+ failed: number;
+ total: number;
+ errors: Array<{ index: number; name: string; message: string }>;
+ } | null>(null);
+ const [bulkWarnings, setBulkWarnings] = useState([]);
+ const apiCredentialLabel = isQoder
+ ? t("personalAccessTokenLabel")
+ : webSessionCredential
+ ? getWebSessionCredentialLabel(t, webSessionCredential, apiKeyOptional)
+ : apiKeyOptional
+ ? `${t("apiKeyLabel")} (${t("optional").toLowerCase()})`
+ : t("apiKeyLabel");
+ const apiCredentialPlaceholder = isVertex
+ ? t("vertexServiceAccountPlaceholder")
+ : isWebSessionCredential
+ ? webSessionCredential.placeholder
+ : isQoder
+ ? t("qoderPatPlaceholder")
+ : apiKeyOptional
+ ? t("optional")
+ : undefined;
+ const apiCredentialHint = isQoder
+ ? t("qoderPatHint")
+ : isWebSessionCredential
+ ? getWebSessionCredentialHint(t, webSessionCredential, providerDisplayName, false)
+ : isLocalSelfHostedProvider
+ ? t("localProviderApiKeyOptionalHint", {
+ provider: localProviderMetadata?.name || providerName || provider || "",
+ })
+ : apiKeyOptional
+ ? t("apiKeyOptionalHint")
+ : undefined;
+ const credentialValidationFailedMessage = isWebSessionCredential
+ ? providerText(
+ t,
+ "webSessionCredentialValidationFailed",
+ "Session credential validation failed. Sign in again, copy a fresh credential, and try again."
+ )
+ : t("apiKeyValidationFailed");
+
+ const handleValidate = async () => {
+ setValidating(true);
+ setSaveError(null);
+ try {
+ const credentialInput = isCommandCode
+ ? extractCommandCodeCredentialInput(formData.apiKey)
+ : formData.apiKey;
+ const res = await fetch("/api/providers/validate", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ provider,
+ apiKey: credentialInput,
+ validationModelId: formData.validationModelId || undefined,
+ customUserAgent: formData.customUserAgent.trim() || undefined,
+ baseUrl: formData.baseUrl.trim() || undefined,
+ region: showsRegion ? formData.region.trim() || defaultRegion : undefined,
+ cx: formData.cx.trim() || undefined,
+ }),
+ });
+ const data = await res.json();
+ setValidationResult(data.valid ? "success" : "failed");
+ } catch {
+ setValidationResult("failed");
+ } finally {
+ setValidating(false);
+ }
+ };
+
+ const copyCommandCodeValue = async (value: string | undefined, key: string) => {
+ if (!value) return;
+ try {
+ await navigator.clipboard.writeText(value);
+ setCopiedCommandCodeField(key);
+ window.setTimeout(() => setCopiedCommandCodeField(null), 1500);
+ } catch {
+ setSaveError("Copy failed. Select the text and copy it manually.");
+ }
+ };
+
+ const handleSubmit = async () => {
+ const credentialInput = isCommandCode
+ ? extractCommandCodeCredentialInput(formData.apiKey)
+ : formData.apiKey;
+ if (!provider || (!isCompatible && !apiKeyOptional && !credentialInput)) return;
+
+ setSaving(true);
+ setSaveError(null);
+ try {
+ if (isGooglePse && !formData.cx.trim()) {
+ setSaveError(t("searchEngineIdRequired"));
+ return;
+ }
+
+ let validatedBaseUrl = null;
+ if (usesBaseUrl) {
+ const checked = normalizeAndValidateHttpBaseUrl(formData.baseUrl, defaultBaseUrl);
+ if (checked.error) {
+ setSaveError(checked.error);
+ return;
+ }
+ validatedBaseUrl = checked.value;
+ }
+
+ let isValid = Boolean(isNoAuthWebSessionCredential && !credentialInput);
+ let validationError: string | null = null;
+ if (!isValid) {
+ try {
+ setValidating(true);
+ setValidationResult(null);
+ const res = await fetch("/api/providers/validate", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ provider,
+ apiKey: credentialInput,
+ validationModelId: formData.validationModelId || undefined,
+ customUserAgent: formData.customUserAgent.trim() || undefined,
+ baseUrl: formData.baseUrl.trim() || undefined,
+ region: showsRegion ? formData.region.trim() || defaultRegion : undefined,
+ cx: formData.cx.trim() || undefined,
+ }),
+ });
+ const data = await res.json();
+ isValid = !!data.valid;
+ if (!isValid && data.error) {
+ validationError = data.error;
+ }
+ setValidationResult(isValid ? "success" : "failed");
+ } catch {
+ setValidationResult("failed");
+ } finally {
+ setValidating(false);
+ }
+ }
+
+ if (!isValid) {
+ if (apiKeyOptional && !credentialInput) {
+ // Bypass validation block for local/optional providers when no key is provided
+ console.debug("Validation failed but apiKey is optional; proceeding to save.");
+ } else {
+ setSaveError(validationError || credentialValidationFailedMessage);
+ return;
+ }
+ }
+
+ const providerSpecificData: Record = {};
+ if (formData.customUserAgent.trim()) {
+ providerSpecificData.customUserAgent = formData.customUserAgent.trim();
+ }
+ if (formData.routingTags.trim()) {
+ providerSpecificData.tags = parseRoutingTagsInput(formData.routingTags);
+ }
+ if (formData.excludedModels.trim()) {
+ providerSpecificData.excludedModels = parseExcludedModelsInput(formData.excludedModels);
+ }
+ if (formData.passthroughModels) {
+ providerSpecificData.passthroughModels = true;
+ }
+ if (provider === "bailian-coding-plan" && formData.consoleApiKey.trim()) {
+ providerSpecificData.consoleApiKey = formData.consoleApiKey.trim();
+ }
+ if (isGooglePse && formData.cx.trim()) {
+ providerSpecificData.cx = formData.cx.trim();
+ }
+ if (usesBaseUrl) {
+ providerSpecificData.baseUrl = validatedBaseUrl;
+ } else if (showsRegion) {
+ providerSpecificData.region = formData.region.trim() || defaultRegion;
+ } else if (isGlm) {
+ providerSpecificData.apiRegion = formData.apiRegion;
+ } else if (isCloudflare && formData.accountId.trim()) {
+ providerSpecificData.accountId = formData.accountId.trim();
+ }
+ if (isCcCompatible && formData.ccCompatibleContext1m) {
+ providerSpecificData.requestDefaults = { context1m: true };
+ }
+
+ const payload = {
+ name: formData.name,
+ apiKey: credentialInput.trim() || undefined,
+ priority: formData.priority,
+ testStatus: "active",
+ providerSpecificData:
+ Object.keys(providerSpecificData).length > 0 ? providerSpecificData : undefined,
+ };
+
+ const error = await onSave(payload);
+ if (error) {
+ setSaveError(typeof error === "string" ? error : t("failedSaveConnection"));
+ }
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const handleBulkSubmit = async () => {
+ if (!provider) return;
+ const parsed = parseBulkApiKeys(bulkText);
+ setBulkWarnings(parsed.warnings);
+ if (parsed.entries.length === 0) return;
+
+ setSaving(true);
+ setBulkResult(null);
+ setSaveError(null);
+
+ try {
+ let providerSpecificData: Record | undefined;
+ if (usesBaseUrl) {
+ const checked = normalizeAndValidateHttpBaseUrl(formData.baseUrl, defaultBaseUrl);
+ if (checked.error) {
+ setSaveError(checked.error);
+ return;
+ }
+ providerSpecificData = { baseUrl: checked.value };
+ }
+
+ const res = await fetch("/api/providers/bulk", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ provider,
+ entries: parsed.entries.map((e) => ({ name: e.name, apiKey: e.apiKey })),
+ priority: formData.priority || 1,
+ providerSpecificData,
+ validateKeys: bulkValidateKeys,
+ }),
+ });
+ const data = await res.json();
+ if (!res.ok) {
+ setSaveError(typeof data?.error === "string" ? data.error : t("failedSaveConnection"));
+ return;
+ }
+ setBulkResult({
+ success: data.success || 0,
+ failed: data.failed || 0,
+ total: data.total || 0,
+ errors: Array.isArray(data.errors) ? data.errors : [],
+ });
+ } catch (err) {
+ setSaveError(err instanceof Error ? err.message : t("failedSaveConnection"));
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ if (!provider) return null;
+
+ return (
+
+
+ {bulkSupported && (
+
+ {
+ setMode("single");
+ setBulkResult(null);
+ setBulkWarnings([]);
+ }}
+ className={`px-3 py-1.5 text-sm font-medium transition-colors ${
+ mode === "single"
+ ? "border-b-2 border-primary text-text-main"
+ : "text-text-muted hover:text-text-main"
+ }`}
+ >
+ {t("bulkTabSingle")}
+
+ {
+ setMode("bulk");
+ setSaveError(null);
+ }}
+ className={`px-3 py-1.5 text-sm font-medium transition-colors ${
+ mode === "bulk"
+ ? "border-b-2 border-primary text-text-main"
+ : "text-text-muted hover:text-text-main"
+ }`}
+ >
+ {t("bulkTabBulkAdd")}
+
+
+ )}
+
+ {bulkSupported && mode === "bulk" && (
+
+
{t("bulkAddFormatHint")}
+
+ )}
+
+ {(!bulkSupported || mode === "single") && (
+ <>
+ {isCcCompatible && (
+
+
+
+ warning
+
+
{t("ccCompatibleValidationHint")}
+
+
+ )}
+ {isCommandCode && onStartCommandCodeAuth && (
+
+
+
+ open_in_new
+
+
+
+ {t("providerDetailBrowserManualConnect")}
+
+
+ Open Command Code Studio, then paste the returned key/JSON/URL into the API
+ key field below.
+
+ {commandCodeAuthState?.message && (
+
+ {commandCodeAuthPhaseLabel}: {commandCodeAuthState.message}
+
+ )}
+ {commandCodeAuthState?.authUrl && (
+
+
+
+ {t("providerDetailAuthUrl")}
+
+
+
+
+ copyCommandCodeValue(commandCodeAuthState.authUrl, "authUrl")
+ }
+ />
+
+
+ {commandCodeAuthState.callbackUrl && (
+
+
+ {t("providerDetailCallbackUrl")}
+
+
+
+
+ copyCommandCodeValue(
+ commandCodeAuthState.callbackUrl,
+ "callbackUrl"
+ )
+ }
+ />
+
+
+ )}
+
+ )}
+
+
+ Connect in browser
+
+
+
+ )}
+
setFormData({ ...formData, name: e.target.value })}
+ placeholder={isQoder ? t("personalAccessTokenLabel") : t("productionKey")}
+ />
+ {webSessionCredential && (
+
+ )}
+ {!isNoAuthWebSessionCredential && (
+
+
setFormData({ ...formData, apiKey: e.target.value })}
+ className="flex-1"
+ placeholder={apiCredentialPlaceholder}
+ hint={apiCredentialHint}
+ autoComplete="off"
+ spellCheck={false}
+ autoCapitalize="off"
+ />
+
+
+ {validating
+ ? t("checking")
+ : webSessionCredential
+ ? getWebSessionCredentialCheckLabel(t, webSessionCredential)
+ : t("check")}
+
+
+
+ )}
+ {isGooglePse && (
+
setFormData({ ...formData, cx: e.target.value })}
+ placeholder="012345678901234567890:abc123xyz"
+ hint={t("searchEngineIdHint")}
+ />
+ )}
+ {validationResult && (
+
+ {validationResult === "success" ? t("valid") : t("invalid")}
+
+ )}
+ {saveError && (
+
+ {saveError}
+
+ )}
+ {isCcCompatible && (
+
+
+ setFormData({ ...formData, ccCompatibleContext1m: checked })
+ }
+ label={t("ccCompatibleContext1mLabel")}
+ description={t("ccCompatibleContext1mDescription")}
+ />
+
+ )}
+ {isCompatible && !isCcCompatible && (
+
+ {isAnthropic
+ ? t("validationChecksAnthropicCompatible", {
+ provider: providerName || t("anthropicCompatibleName"),
+ })
+ : t("validationChecksOpenAiCompatible", {
+ provider: providerName || t("openaiCompatibleName"),
+ })}
+
+ )}
+
setShowAdvanced(!showAdvanced)}
+ aria-expanded={showAdvanced}
+ aria-controls="add-api-key-advanced-settings"
+ >
+
+ ▶
+
+ {t("advancedSettings")}
+
+ {showAdvanced && (
+
+ setFormData({ ...formData, customUserAgent: e.target.value })}
+ placeholder="my-app/1.0"
+ hint={t("customUserAgentHint")}
+ />
+ setFormData({ ...formData, routingTags: e.target.value })}
+ placeholder={t("routingTagsPlaceholder")}
+ hint={t("routingTagsHint")}
+ />
+ setFormData({ ...formData, excludedModels: e.target.value })}
+ placeholder={t("excludedModelsPlaceholder")}
+ hint={t("excludedModelsHint")}
+ />
+ setFormData({ ...formData, passthroughModels: checked })}
+ label={t("perModelQuotaLabel")}
+ description={t("perModelQuotaDescription")}
+ />
+ {provider === "bailian-coding-plan" && (
+ setFormData({ ...formData, consoleApiKey: e.target.value })}
+ placeholder={t("consoleApiKeyOraclePlaceholder")}
+ hint={t("consoleApiKeyOracleHint")}
+ type="password"
+ />
+ )}
+
+ )}
+
setFormData({ ...formData, validationModelId: e.target.value })}
+ hint={t("validationModelIdHint")}
+ />
+
+ setFormData({ ...formData, priority: Number.parseInt(e.target.value) || 1 })
+ }
+ />
+ {usesBaseUrl && (
+
setFormData({ ...formData, baseUrl: e.target.value })}
+ placeholder={getProviderBaseUrlPlaceholder(provider)}
+ hint={getProviderBaseUrlHint(provider, t)}
+ />
+ )}
+ {showsRegion && (
+
setFormData({ ...formData, region: e.target.value })}
+ placeholder={defaultRegion}
+ hint={t("regionHint")}
+ />
+ )}
+ {isCloudflare && (
+
setFormData({ ...formData, accountId: e.target.value })}
+ placeholder={t("accountIdPlaceholder")}
+ hint={t("accountIdHint")}
+ />
+ )}
+ {isGlm && (
+
+
+ {t("apiRegionLabel")}
+
+
setFormData({ ...formData, apiRegion: e.target.value })}
+ className="w-full px-3 py-2 text-sm border border-border rounded-lg bg-background focus:outline-none focus:border-primary"
+ >
+ {t("apiRegionInternational")}
+ {t("apiRegionChina")}
+
+
{t("apiRegionHint")}
+
+ )}
+
+
+ {saving ? t("saving") : t("save")}
+
+
+ {t("cancel")}
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx
new file mode 100644
index 0000000000..1cdf280c77
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx
@@ -0,0 +1,1170 @@
+"use client";
+
+// Issue #3501 Phase 1c — extracted from the god-component.
+// ~1091-LOC modal for editing an existing provider connection.
+
+import { useState, useEffect, useMemo } from "react";
+import { useTranslations } from "next-intl";
+import { Button, Badge, Input, Modal, Toggle, Select } from "@/shared/components";
+import {
+ isOpenAICompatibleProvider,
+ isAnthropicCompatibleProvider,
+ isClaudeCodeCompatibleProvider,
+ providerAllowsOptionalApiKey,
+} from "@/shared/constants/providers";
+import {
+ ANTIGRAVITY_CLIENT_PROFILE_OPTIONS,
+ normalizeAntigravityClientProfileSetting,
+} from "@/shared/constants/antigravityClientProfile";
+import { parseExtraApiKeys } from "@/shared/utils/parseApiKeys";
+import { maskEmail } from "@/shared/utils/maskEmail";
+import useEmailPrivacyStore from "@/store/emailPrivacyStore";
+import { useNotificationStore } from "@/store/notificationStore";
+import { type CodexServiceTier } from "@/lib/providers/requestDefaults";
+import { isClaudeExtraUsageBlockEnabled } from "@/lib/providers/claudeExtraUsage";
+import { resolveDashboardProviderInfo } from "../../../providerPageUtils";
+import {
+ isBaseUrlConfigurableProvider,
+ getProviderBaseUrlDefault,
+ getProviderBaseUrlHint,
+ getProviderBaseUrlPlaceholder,
+ isGlmProvider,
+ parseRoutingTagsInput,
+ parseExcludedModelsInput,
+ formatRoutingTagsInput,
+ formatExcludedModelsInput,
+ getWebSessionCredentialLabel,
+ getWebSessionCredentialHint,
+ getWebSessionCredentialCheckLabel,
+ getLocalProviderMetadata,
+ normalizeAndValidateHttpBaseUrl,
+ CODEX_REASONING_STRENGTH_OPTIONS,
+ CODEX_ACCOUNT_SERVICE_TIER_VALUES,
+ getCodexServiceTierLabel,
+ getCodexRequestDefaults,
+ getClaudeCodeCompatibleRequestDefaults,
+ providerText,
+ ERROR_TYPE_LABELS,
+ formatTimeAgo,
+} from "../../providerPageHelpers";
+import { getWebSessionCredentialRequirement } from "../../webSessionCredentials";
+import WebSessionCredentialGuide from "../WebSessionCredentialGuide";
+
+export interface EditConnectionModalConnection {
+ id?: string;
+ name?: string;
+ email?: string;
+ priority?: number;
+ maxConcurrent?: number | null;
+ rateLimitOverrides?: Record | null;
+ authType?: string;
+ provider?: string;
+ apiKey?: string;
+ providerSpecificData?: Record;
+ healthCheckInterval?: number;
+ projectId?: string | null;
+}
+
+export interface EditConnectionModalProps {
+ isOpen: boolean;
+ connection: EditConnectionModalConnection | null;
+ onSave: (data: unknown) => Promise;
+ onClose: () => void;
+}
+
+export default function EditConnectionModal({
+ isOpen,
+ connection,
+ onSave,
+ onClose,
+}: EditConnectionModalProps) {
+ const t = useTranslations("providers");
+ const notify = useNotificationStore();
+ const [formData, setFormData] = useState({
+ name: "",
+ priority: 1,
+ maxConcurrent: "",
+ rpm: "",
+ tpm: "",
+ tpd: "",
+ minTime: "",
+ rateLimitMaxConcurrent: "",
+ apiKey: "",
+ healthCheckInterval: 60,
+ baseUrl: "",
+ cx: "",
+ region: "",
+ apiRegion: "international",
+ validationModelId: "",
+ tag: "",
+ routingTags: "",
+ excludedModels: "",
+ customUserAgent: "",
+ accountId: "",
+ codexReasoningEffort: "medium",
+ codexServiceTier: "default" as CodexServiceTier,
+ codexOpenaiStoreEnabled: false,
+ consoleApiKey: "",
+ ccCompatibleContext1m: false,
+ cloudCodeProjectId: "",
+ antigravityClientProfile: "ide",
+ blockExtraUsage:
+ connection?.provider === "claude"
+ ? isClaudeExtraUsageBlockEnabled(connection?.provider, connection?.providerSpecificData)
+ : false,
+ passthroughModels: connection?.providerSpecificData?.passthroughModels === true,
+ });
+ const [testing, setTesting] = useState(false);
+ const [testResult, setTestResult] = useState(null);
+ const [validating, setValidating] = useState(false);
+ const [validationResult, setValidationResult] = useState(null);
+ const [saving, setSaving] = useState(false);
+ const [saveError, setSaveError] = useState(null);
+ const [extraApiKeys, setExtraApiKeys] = useState([]);
+ const [newExtraKey, setNewExtraKey] = useState("");
+ const [apiKeyHealth, setApiKeyHealth] = useState<
+ Record<
+ string,
+ {
+ status: "active" | "warning" | "invalid";
+ failures: number;
+ lastFailure: string | null;
+ totalRequests?: number;
+ totalFailures?: number;
+ }
+ >
+ >({});
+ const [showAdvanced, setShowAdvanced] = useState(false);
+ const { emailsVisible: showEmail, toggleEmailVisibility: toggleShowEmail } =
+ useEmailPrivacyStore();
+
+ const usesBaseUrl = isBaseUrlConfigurableProvider(connection?.provider);
+ const defaultBaseUrl = getProviderBaseUrlDefault(connection?.provider);
+ const isVertex = connection?.provider === "vertex" || connection?.provider === "vertex-partner";
+ const isBedrock = connection?.provider === "bedrock";
+ const showsRegion = isVertex || isBedrock;
+ const isGlm = isGlmProvider(connection?.provider);
+ const isCloudflare = connection?.provider === "cloudflare-ai";
+ const isCodex = connection?.provider === "codex";
+ const isClaude = connection?.provider === "claude";
+ const isGeminiCli = connection?.provider === "gemini-cli";
+ const isAntigravity = connection?.provider === "antigravity";
+ const supportsGoogleProjectId = isGeminiCli || isAntigravity;
+ const localProviderMetadata = getLocalProviderMetadata(connection?.provider);
+ const isLocalSelfHostedProvider = !!localProviderMetadata;
+ const isGooglePse = connection?.provider === "google-pse-search";
+ const webSessionCredential = getWebSessionCredentialRequirement(connection?.provider);
+ const isNoAuthWebSessionCredential = webSessionCredential?.kind === "none";
+ const isWebSessionCredential = !!webSessionCredential && webSessionCredential.kind !== "none";
+ const providerDisplayName =
+ (connection?.provider ? resolveDashboardProviderInfo(connection.provider)?.name : null) ||
+ connection?.provider ||
+ "";
+ const apiKeyOptional =
+ providerAllowsOptionalApiKey(connection?.provider) || Boolean(isNoAuthWebSessionCredential);
+ const isCcCompatible = isClaudeCodeCompatibleProvider(connection?.provider);
+ const defaultRegion = isBedrock ? "eu-west-2" : "us-central1";
+ const apiCredentialLabel = webSessionCredential
+ ? getWebSessionCredentialLabel(t, webSessionCredential, apiKeyOptional)
+ : apiKeyOptional
+ ? t("apiKeyOptionalLabel")
+ : t("apiKeyLabel");
+ const apiCredentialPlaceholder = isWebSessionCredential
+ ? webSessionCredential.placeholder
+ : isVertex
+ ? t("vertexServiceAccountPlaceholder")
+ : t("enterNewApiKey");
+ const apiCredentialHint = isWebSessionCredential
+ ? getWebSessionCredentialHint(t, webSessionCredential, providerDisplayName, true)
+ : isLocalSelfHostedProvider
+ ? t("localProviderApiKeyOptionalHint", {
+ provider: localProviderMetadata?.name || connection?.provider || "",
+ })
+ : apiKeyOptional
+ ? t("apiKeyOptionalHint")
+ : t("leaveBlankKeepCurrentApiKey");
+ const codexAccountServiceTierOptions = useMemo(
+ () =>
+ CODEX_ACCOUNT_SERVICE_TIER_VALUES.map((value) => ({
+ value,
+ label: getCodexServiceTierLabel(t, value),
+ })),
+ [t]
+ );
+
+ useEffect(() => {
+ if (isOpen && connection) {
+ const rawBaseUrl = connection.providerSpecificData?.baseUrl;
+ const existingBaseUrl = typeof rawBaseUrl === "string" ? rawBaseUrl : "";
+ const rawRegion = connection.providerSpecificData?.region;
+ const existingRegion = typeof rawRegion === "string" ? rawRegion : "";
+ const rawCustomUserAgent = connection.providerSpecificData?.customUserAgent;
+ const existingCustomUserAgent =
+ typeof rawCustomUserAgent === "string" ? rawCustomUserAgent : "";
+ const rawCx = connection.providerSpecificData?.cx;
+ const existingCx = typeof rawCx === "string" ? rawCx : "";
+ const rawAccountId = connection.providerSpecificData?.accountId;
+ const existingAccountId = typeof rawAccountId === "string" ? rawAccountId : "";
+ const codexRequestDefaults = getCodexRequestDefaults(connection.providerSpecificData);
+ const ccRequestDefaults = getClaudeCodeCompatibleRequestDefaults(
+ connection.providerSpecificData
+ );
+ const rawConsoleApiKey = connection.providerSpecificData?.consoleApiKey;
+ const existingConsoleApiKey = typeof rawConsoleApiKey === "string" ? rawConsoleApiKey : "";
+ setFormData({
+ name: connection.name || "",
+ priority: connection.priority || 1,
+ maxConcurrent:
+ connection.maxConcurrent !== null && connection.maxConcurrent !== undefined
+ ? String(connection.maxConcurrent)
+ : "",
+ rpm:
+ connection.rateLimitOverrides?.rpm != null
+ ? String(connection.rateLimitOverrides.rpm)
+ : "",
+ tpm:
+ connection.rateLimitOverrides?.tpm != null
+ ? String(connection.rateLimitOverrides.tpm)
+ : "",
+ tpd:
+ connection.rateLimitOverrides?.tpd != null
+ ? String(connection.rateLimitOverrides.tpd)
+ : "",
+ minTime:
+ connection.rateLimitOverrides?.minTime != null
+ ? String(connection.rateLimitOverrides.minTime)
+ : "",
+ rateLimitMaxConcurrent:
+ connection.rateLimitOverrides?.maxConcurrent != null
+ ? String(connection.rateLimitOverrides.maxConcurrent)
+ : "",
+ apiKey: "",
+ healthCheckInterval: connection.healthCheckInterval ?? 60,
+ baseUrl: existingBaseUrl || defaultBaseUrl,
+ cx: existingCx,
+ region: existingRegion || (showsRegion ? defaultRegion : ""),
+ apiRegion: (connection.providerSpecificData?.apiRegion as string) || "international",
+ validationModelId: (connection.providerSpecificData?.validationModelId as string) || "",
+ tag: (connection.providerSpecificData?.tag as string) || "",
+ routingTags: formatRoutingTagsInput(connection.providerSpecificData?.tags),
+ excludedModels: formatExcludedModelsInput(
+ connection.providerSpecificData?.excludedModels ??
+ connection.providerSpecificData?.excluded_models
+ ),
+ customUserAgent: existingCustomUserAgent,
+ accountId: existingAccountId,
+ codexReasoningEffort: codexRequestDefaults.reasoningEffort,
+ codexServiceTier: codexRequestDefaults.serviceTier ?? "default",
+ codexOpenaiStoreEnabled: connection.providerSpecificData?.openaiStoreEnabled === true,
+ consoleApiKey: existingConsoleApiKey,
+ ccCompatibleContext1m: ccRequestDefaults.context1m,
+ cloudCodeProjectId:
+ (connection.providerSpecificData?.projectId as string) || connection.projectId || "",
+ antigravityClientProfile: normalizeAntigravityClientProfileSetting(
+ connection.providerSpecificData?.clientProfile
+ ),
+ blockExtraUsage: isClaudeExtraUsageBlockEnabled(
+ connection.provider,
+ connection.providerSpecificData
+ ),
+ passthroughModels: connection?.providerSpecificData?.passthroughModels === true,
+ });
+ // Load existing extra keys from providerSpecificData
+ const existing = connection.providerSpecificData?.extraApiKeys;
+ setExtraApiKeys(Array.isArray(existing) ? existing : []);
+ // Load API key health status
+ const health = connection.providerSpecificData?.apiKeyHealth as
+ | Record<
+ string,
+ {
+ status: "active" | "warning" | "invalid";
+ failures: number;
+ lastFailure: string | null;
+ totalRequests?: number;
+ totalFailures?: number;
+ }
+ >
+ | undefined;
+ setApiKeyHealth(health || {});
+ setNewExtraKey("");
+ setShowAdvanced(!!existingCustomUserAgent);
+ // email visibility controlled by global store
+ setTestResult(null);
+ setValidationResult(null);
+ setSaveError(null);
+ }
+ }, [isOpen, connection, defaultBaseUrl, showsRegion, defaultRegion]);
+
+ const handleTest = async () => {
+ if (!connection?.provider) return;
+ setTesting(true);
+ setTestResult(null);
+ try {
+ const res = await fetch(`/api/providers/${connection.id}/test`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ validationModelId: formData.validationModelId || undefined,
+ }),
+ });
+ const data = await res.json();
+ setTestResult({
+ valid: !!data.valid,
+ diagnosis: data.diagnosis || null,
+ message: data.error || null,
+ });
+ } catch {
+ setTestResult({
+ valid: false,
+ diagnosis: { type: "network_error" },
+ message: t("failedTestConnection"),
+ });
+ } finally {
+ setTesting(false);
+ }
+ };
+
+ const handleValidate = async () => {
+ if (
+ !connection?.provider ||
+ isNoAuthWebSessionCredential ||
+ (!isCompatible && !apiKeyOptional && !formData.apiKey)
+ ) {
+ return;
+ }
+ setValidating(true);
+ setValidationResult(null);
+ try {
+ const res = await fetch("/api/providers/validate", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ provider: connection.provider,
+ apiKey: formData.apiKey,
+ validationModelId: formData.validationModelId || undefined,
+ customUserAgent: formData.customUserAgent.trim() || undefined,
+ baseUrl: formData.baseUrl.trim() || undefined,
+ region: showsRegion ? formData.region.trim() || defaultRegion : undefined,
+ cx: formData.cx.trim() || undefined,
+ }),
+ });
+ const data = await res.json();
+ setValidationResult(data.valid ? "success" : "failed");
+ } catch {
+ setValidationResult("failed");
+ } finally {
+ setValidating(false);
+ }
+ };
+
+ const handleAddParsedExtraKeys = (raw: string) => {
+ const { added, duplicates } = parseExtraApiKeys(raw, extraApiKeys);
+ if (added.length > 0) {
+ setExtraApiKeys((prev) => [...prev, ...added]);
+ notify.success(t("bulkPasteAdded", { count: added.length }));
+ }
+ if (duplicates > 0) {
+ notify.warning(t("bulkPasteDuplicatesIgnored", { count: duplicates }));
+ }
+ };
+
+ const handleSubmit = async () => {
+ setSaving(true);
+ setSaveError(null);
+ try {
+ const trimmedMaxConcurrent = formData.maxConcurrent.trim();
+ const trimmedCloudCodeProjectId = formData.cloudCodeProjectId.trim();
+ let parsedMaxConcurrent: number | null = null;
+ if (trimmedMaxConcurrent) {
+ const numericMaxConcurrent = Number(trimmedMaxConcurrent);
+ if (!Number.isInteger(numericMaxConcurrent) || numericMaxConcurrent < 0) {
+ setSaveError(t("maxConcurrentWholeNumberError"));
+ return;
+ }
+ parsedMaxConcurrent = numericMaxConcurrent;
+ }
+
+ const updates: any = {
+ name: formData.name,
+ priority: formData.priority,
+ maxConcurrent: parsedMaxConcurrent,
+ healthCheckInterval: formData.healthCheckInterval,
+ };
+
+ // Build rateLimitOverrides from non-empty fields
+ const overrides: Record = {};
+ if (formData.rpm.trim()) overrides.rpm = Number(formData.rpm);
+ if (formData.tpm.trim()) overrides.tpm = Number(formData.tpm);
+ if (formData.tpd.trim()) overrides.tpd = Number(formData.tpd);
+ if (formData.minTime.trim()) overrides.minTime = Number(formData.minTime);
+ if (formData.rateLimitMaxConcurrent.trim())
+ overrides.maxConcurrent = Number(formData.rateLimitMaxConcurrent);
+ updates.rateLimitOverrides = Object.keys(overrides).length > 0 ? overrides : null;
+
+ if (supportsGoogleProjectId) {
+ updates.projectId = trimmedCloudCodeProjectId || null;
+ }
+
+ if (isGooglePse && !formData.cx.trim()) {
+ setSaveError(t("searchEngineIdRequired"));
+ return;
+ }
+
+ let validatedBaseUrl = null;
+ if (usesBaseUrl) {
+ const checked = normalizeAndValidateHttpBaseUrl(formData.baseUrl, defaultBaseUrl);
+ if (checked.error) {
+ setSaveError(checked.error);
+ return;
+ }
+ validatedBaseUrl = checked.value;
+ }
+
+ if (!isOAuth && formData.apiKey) {
+ updates.apiKey = formData.apiKey;
+ let isValid = validationResult === "success";
+ if (!isValid) {
+ try {
+ setValidating(true);
+ setValidationResult(null);
+ const res = await fetch("/api/providers/validate", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ provider: connection.provider,
+ apiKey: formData.apiKey,
+ validationModelId: formData.validationModelId || undefined,
+ customUserAgent: formData.customUserAgent.trim() || undefined,
+ baseUrl: formData.baseUrl.trim() || undefined,
+ region: showsRegion ? formData.region.trim() || defaultRegion : undefined,
+ cx: formData.cx.trim() || undefined,
+ }),
+ });
+ const data = await res.json();
+ isValid = !!data.valid;
+ setValidationResult(isValid ? "success" : "failed");
+ } catch {
+ setValidationResult("failed");
+ } finally {
+ setValidating(false);
+ }
+ }
+ if (isValid) {
+ updates.testStatus = "active";
+ updates.lastError = null;
+ updates.lastErrorAt = null;
+ updates.lastErrorType = null;
+ updates.lastErrorSource = null;
+ updates.errorCode = null;
+ updates.rateLimitedUntil = null;
+ }
+ }
+ // Persist extra API keys and baseUrl in providerSpecificData
+ if (!isOAuth) {
+ updates.providerSpecificData = {
+ ...(connection.providerSpecificData || {}),
+ extraApiKeys: extraApiKeys.filter((k) => k.trim().length > 0),
+ tag: formData.tag.trim() || undefined,
+ tags: parseRoutingTagsInput(formData.routingTags),
+ excludedModels: parseExcludedModelsInput(formData.excludedModels),
+ customUserAgent: formData.customUserAgent.trim(),
+ // Only write when explicitly enabled; omit to let registry default take effect
+ ...(formData.passthroughModels ? { passthroughModels: true } : {}),
+ };
+ if (connection.provider === "bailian-coding-plan") {
+ if (formData.consoleApiKey.trim()) {
+ updates.providerSpecificData.consoleApiKey = formData.consoleApiKey.trim();
+ } else {
+ updates.providerSpecificData.consoleApiKey = undefined;
+ }
+ }
+ if (formData.validationModelId) {
+ updates.providerSpecificData.validationModelId = formData.validationModelId;
+ }
+ if (isGooglePse) {
+ updates.providerSpecificData.cx = formData.cx.trim() || undefined;
+ }
+ if (usesBaseUrl) {
+ updates.providerSpecificData.baseUrl = validatedBaseUrl;
+ } else if (showsRegion) {
+ updates.providerSpecificData.region = formData.region.trim() || defaultRegion;
+ } else if (isGlm) {
+ updates.providerSpecificData.apiRegion = formData.apiRegion;
+ } else if (isCloudflare && formData.accountId.trim()) {
+ updates.providerSpecificData.accountId = formData.accountId.trim();
+ }
+ if (supportsGoogleProjectId) {
+ updates.providerSpecificData.projectId = trimmedCloudCodeProjectId || null;
+ }
+ if (isCcCompatible) {
+ const currentRequestDefaults =
+ updates.providerSpecificData.requestDefaults &&
+ typeof updates.providerSpecificData.requestDefaults === "object" &&
+ !Array.isArray(updates.providerSpecificData.requestDefaults)
+ ? { ...(updates.providerSpecificData.requestDefaults as Record) }
+ : {};
+ if (formData.ccCompatibleContext1m) {
+ currentRequestDefaults.context1m = true;
+ } else {
+ delete currentRequestDefaults.context1m;
+ }
+ updates.providerSpecificData.requestDefaults =
+ Object.keys(currentRequestDefaults).length > 0 ? currentRequestDefaults : undefined;
+ }
+ } else {
+ // Also persist tag for OAuth accounts
+ updates.providerSpecificData = {
+ ...(connection.providerSpecificData || {}),
+ tag: formData.tag.trim() || undefined,
+ tags: parseRoutingTagsInput(formData.routingTags),
+ excludedModels: parseExcludedModelsInput(formData.excludedModels),
+ };
+ if (isClaude) {
+ updates.providerSpecificData.blockExtraUsage = formData.blockExtraUsage;
+ }
+ if (isCodex) {
+ updates.providerSpecificData.requestDefaults = {
+ reasoningEffort: formData.codexReasoningEffort,
+ ...(formData.codexServiceTier !== "default"
+ ? { serviceTier: formData.codexServiceTier }
+ : {}),
+ };
+ updates.providerSpecificData.openaiStoreEnabled =
+ formData.codexOpenaiStoreEnabled === true;
+ }
+ if (supportsGoogleProjectId) {
+ updates.providerSpecificData.projectId = trimmedCloudCodeProjectId || null;
+ }
+ }
+ if (isAntigravity) {
+ updates.providerSpecificData = {
+ ...(connection.providerSpecificData || {}),
+ ...(updates.providerSpecificData || {}),
+ clientProfile: normalizeAntigravityClientProfileSetting(
+ formData.antigravityClientProfile
+ ),
+ };
+ }
+ const error = (await onSave(updates)) as void | unknown;
+ if (error) {
+ setSaveError(typeof error === "string" ? error : t("failedSaveConnection"));
+ }
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ if (!connection) return null;
+
+ const isOAuth = connection.authType === "oauth";
+ const isCompatible =
+ isOpenAICompatibleProvider(connection.provider) ||
+ isAnthropicCompatibleProvider(connection.provider);
+ const testErrorMeta =
+ !testResult?.valid && testResult?.diagnosis?.type
+ ? ERROR_TYPE_LABELS[testResult.diagnosis.type] || null
+ : null;
+
+ return (
+
+
+
setFormData({ ...formData, name: e.target.value })}
+ placeholder={isOAuth ? t("accountName") : t("productionKey")}
+ />
+
setFormData({ ...formData, tag: e.target.value })}
+ placeholder={t("tagGroupPlaceholder")}
+ hint={t("tagGroupHint")}
+ />
+
setFormData({ ...formData, routingTags: e.target.value })}
+ placeholder={t("routingTagsPlaceholder")}
+ hint={t("routingTagsHint")}
+ />
+
setFormData({ ...formData, excludedModels: e.target.value })}
+ placeholder={t("excludedModelsPlaceholder")}
+ hint={t("excludedModelsHint")}
+ />
+ {isCodex && (
+
+ setFormData({ ...formData, codexReasoningEffort: e.target.value })}
+ hint={t("defaultThinkingStrengthHint")}
+ />
+
+ setFormData({
+ ...formData,
+ codexServiceTier: event.target.value as CodexServiceTier,
+ })
+ }
+ hint={providerText(
+ t,
+ "codexServiceTierDescription",
+ "Default uses the normal Codex tier. Priority shows as Fast; Flex uses the flex service tier when available."
+ )}
+ />
+ setFormData({ ...formData, codexOpenaiStoreEnabled: checked })}
+ label={t("openaiResponsesStoreLabel")}
+ description={t("openaiResponsesStoreDescription")}
+ />
+
+ )}
+ {isClaude && (
+
+ setFormData({ ...formData, blockExtraUsage: checked })}
+ label={t("blockClaudeExtraUsageLabel")}
+ description={t("blockClaudeExtraUsageDescription")}
+ />
+
+ )}
+ {isCcCompatible && (
+
+ setFormData({ ...formData, ccCompatibleContext1m: checked })}
+ label={t("ccCompatibleContext1mLabel")}
+ description={t("ccCompatibleContext1mDescription")}
+ />
+
+ )}
+ {supportsGoogleProjectId && (
+
+ {isAntigravity && (
+ ({
+ value: option.value,
+ label: t(option.labelKey),
+ }))}
+ onChange={(e) =>
+ setFormData({ ...formData, antigravityClientProfile: e.target.value })
+ }
+ hint={t("antigravityClientProfileHint")}
+ />
+ )}
+ setFormData({ ...formData, cloudCodeProjectId: e.target.value })}
+ placeholder={
+ isAntigravity
+ ? t("antigravityProjectIdPlaceholder")
+ : t("geminiCliProjectIdPlaceholder")
+ }
+ hint={isAntigravity ? t("antigravityProjectIdHint") : t("geminiCliProjectIdHint")}
+ className="font-mono text-xs"
+ />
+
+ )}
+ {isOAuth && connection.email && (
+
+
{t("email")}
+
+
+ {showEmail ? connection.email : maskEmail(connection.email)}
+
+
+
+ {showEmail ? "visibility_off" : "visibility"}
+
+
+
+
+ )}
+ {isOAuth && (
+
+ setFormData({
+ ...formData,
+ healthCheckInterval: Math.max(0, Number.parseInt(e.target.value) || 0),
+ })
+ }
+ hint={t("healthCheckHint")}
+ />
+ )}
+
+ setFormData({ ...formData, priority: Number.parseInt(e.target.value) || 1 })
+ }
+ />
+
{
+ const nextValue = e.target.value;
+ setFormData({ ...formData, maxConcurrent: nextValue });
+ if (saveError && nextValue.trim()) {
+ const numericValue = Number(nextValue);
+ if (Number.isInteger(numericValue) && numericValue >= 0) {
+ setSaveError(null);
+ }
+ }
+ }}
+ placeholder="0"
+ hint={t("accountConcurrencyCapHint")}
+ />
+ {saveError && (
+
+ {saveError}
+
+ )}
+ {!isOAuth && (
+ <>
+ {webSessionCredential && (
+
+ )}
+ {!isNoAuthWebSessionCredential && (
+
+
setFormData({ ...formData, apiKey: e.target.value })}
+ placeholder={apiCredentialPlaceholder}
+ hint={apiCredentialHint}
+ className="flex-1"
+ autoComplete="off"
+ spellCheck={false}
+ autoCapitalize="off"
+ />
+
+
+ {validating
+ ? t("checking")
+ : webSessionCredential
+ ? getWebSessionCredentialCheckLabel(t, webSessionCredential)
+ : t("check")}
+
+
+
+ )}
+ {isGooglePse && (
+
setFormData({ ...formData, cx: e.target.value })}
+ placeholder="012345678901234567890:abc123xyz"
+ hint={t("searchEngineIdHint")}
+ />
+ )}
+ {validationResult && (
+
+ {validationResult === "success" ? t("valid") : t("invalid")}
+
+ )}
+
setShowAdvanced(!showAdvanced)}
+ aria-expanded={showAdvanced}
+ aria-controls="edit-connection-advanced-settings"
+ >
+
+ ▶
+
+ {t("advancedSettings")}
+
+ {showAdvanced && (
+
+ )}
+
setFormData({ ...formData, validationModelId: e.target.value })}
+ hint={t("validationModelIdHint")}
+ />
+ >
+ )}
+
+ {usesBaseUrl && (
+
setFormData({ ...formData, baseUrl: e.target.value })}
+ placeholder={getProviderBaseUrlPlaceholder(connection.provider)}
+ hint={getProviderBaseUrlHint(connection.provider, t)}
+ />
+ )}
+
+ {showsRegion && (
+
setFormData({ ...formData, region: e.target.value })}
+ placeholder={defaultRegion}
+ hint={t("regionHint")}
+ />
+ )}
+
+ {isCloudflare && (
+
setFormData({ ...formData, accountId: e.target.value })}
+ placeholder={t("accountIdPlaceholder")}
+ hint={t("accountIdHint")}
+ />
+ )}
+
+ {isGlm && (
+
+
+ {t("apiRegionLabel")}
+
+
setFormData({ ...formData, apiRegion: e.target.value })}
+ className="w-full px-3 py-2 text-sm border border-border rounded-lg bg-background focus:outline-none focus:border-primary"
+ >
+ {t("apiRegionInternational")}
+ {t("apiRegionChina")}
+
+
{t("apiRegionHint")}
+
+ )}
+
+ {/* T07: API Key Health Status */}
+ {!isOAuth && connection?.apiKey && (
+
+
{t("apiKeyHealthLabel")}
+
+ {/* Primary Key Health */}
+ {(() => {
+ const keyId = "primary";
+ const health = apiKeyHealth[keyId];
+ const statusColor =
+ health?.status === "invalid"
+ ? "text-red-400"
+ : health?.status === "warning"
+ ? "text-yellow-400"
+ : "text-text-muted";
+ const statusIcon =
+ health?.status === "invalid" ? "🔴" : health?.status === "warning" ? "🟡" : "🟢";
+ const statusLabel =
+ health?.status === "invalid"
+ ? t("apiKeyStatusInvalid")
+ : health?.status === "warning"
+ ? t("apiKeyStatusWarning", { count: health.failures })
+ : t("apiKeyStatusActive");
+
+ return (
+
+
+ {statusIcon} {t("primaryKey")}: {connection.apiKey.slice(0, 6)}...
+ {connection.apiKey.slice(-4)}
+
+ {health && (
+
+ {health.failures}x
+ {health.lastFailure ? ` · ${formatTimeAgo(health.lastFailure)}` : ""}
+ {health.totalRequests != null
+ ? ` · (${health.totalRequests} req${health.totalFailures != null ? `, ${health.totalFailures} fail` : ""})`
+ : ""}
+
+ )}
+
+ );
+ })()}
+
+
+ )}
+
+ {/* T07: Extra API Keys for round-robin rotation */}
+ {!isOAuth && (
+
+
+
+ {t("extraApiKeysLabel")}
+
+ ({t("extraApiKeysHint")})
+
+
+ {extraApiKeys.length > 0 && (
+ setExtraApiKeys([])}
+ className="px-2.5 py-1.5 rounded-md bg-red-500/10 text-red-400 hover:bg-red-500/20 hover:text-red-300 text-xs font-medium transition-colors"
+ >
+ {t("deleteAllExtraApiKeys")}
+
+ )}
+
+ {extraApiKeys.length > 0 && (
+
+ {extraApiKeys.map((key, idx) => {
+ const keyId = `extra_${idx}`;
+ const health = apiKeyHealth[keyId];
+ const statusColor =
+ health?.status === "invalid"
+ ? "text-red-400"
+ : health?.status === "warning"
+ ? "text-yellow-400"
+ : "text-text-muted";
+ const statusIcon =
+ health?.status === "invalid"
+ ? "🔴"
+ : health?.status === "warning"
+ ? "🟡"
+ : "🟢";
+ const statusLabel =
+ health?.status === "invalid"
+ ? t("apiKeyStatusInvalid")
+ : health?.status === "warning"
+ ? t("apiKeyStatusWarning", { count: health.failures })
+ : t("apiKeyStatusActive");
+
+ return (
+
+
+ {statusIcon}{" "}
+ {t("extraApiKeyMasked", {
+ index: idx + 2,
+ prefix: key.slice(0, 6),
+ suffix: key.slice(-4),
+ })}
+
+
+ {health && (
+
+ {health.failures}x
+ {health.lastFailure ? ` · ${formatTimeAgo(health.lastFailure)}` : ""}
+ {health.totalRequests != null
+ ? ` · (${health.totalRequests} req${health.totalFailures != null ? `, ${health.totalFailures} fail` : ""})`
+ : ""}
+
+ )}
+ setExtraApiKeys(extraApiKeys.filter((_, i) => i !== idx))}
+ className="p-1.5 rounded hover:bg-red-500/10 text-red-400 hover:text-red-500"
+ title={t("removeThisKey")}
+ >
+ close
+
+
+
+ );
+ })}
+
+ )}
+
+ setNewExtraKey(e.target.value)}
+ placeholder={t("addAnotherApiKey")}
+ className="flex-1 text-sm bg-sidebar/50 border border-border rounded px-3 py-2 text-text-main placeholder:text-text-muted focus:ring-1 focus:ring-primary outline-none"
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && newExtraKey.trim()) {
+ setExtraApiKeys([...extraApiKeys, newExtraKey.trim()]);
+ setNewExtraKey("");
+ }
+ }}
+ onPaste={(e) => {
+ const text = e.clipboardData.getData("text");
+ if (!/\r?\n/.test(text)) return;
+ e.preventDefault();
+ handleAddParsedExtraKeys(text);
+ }}
+ />
+ {
+ if (newExtraKey.trim()) {
+ setExtraApiKeys([...extraApiKeys, newExtraKey.trim()]);
+ setNewExtraKey("");
+ }
+ }}
+ disabled={!newExtraKey.trim()}
+ className="px-3 py-2 rounded bg-primary/10 text-primary hover:bg-primary/20 disabled:opacity-40 text-sm font-medium"
+ >
+ {t("add")}
+
+
+
{t("bulkPasteHint")}
+ {extraApiKeys.length > 0 && (
+
+ {t("totalKeysRotating", { count: extraApiKeys.length + 1 })}
+
+ )}
+
+ )}
+
+ {/* Test Connection */}
+ {!isCompatible && (
+
+
+ {testing ? t("testing") : t("testConnection")}
+
+ {testResult && (
+ <>
+
+ {testResult.valid ? t("valid") : t("failed")}
+
+ {testErrorMeta && (
+ {t(testErrorMeta.labelKey)}
+ )}
+ >
+ )}
+
+ )}
+
+
+
+ {saving ? t("saving") : t("save")}
+
+
+ {t("cancel")}
+
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/__tests__/connModals.test.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/__tests__/connModals.test.tsx
new file mode 100644
index 0000000000..ce4e2ac998
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/__tests__/connModals.test.tsx
@@ -0,0 +1,155 @@
+// @vitest-environment jsdom
+//
+// Phase 1c regression tests for Issue #3501. AddApiKeyModal and EditConnectionModal
+// were extracted from the god-component. This proves each mounts in isolation with
+// its clean Props interface (Hard Rule #8, Rule #18 TDD gate).
+import React, { act } from "react";
+import { createRoot } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import AddApiKeyModal from "../AddApiKeyModal";
+import EditConnectionModal from "../EditConnectionModal";
+
+vi.mock("next/navigation", () => ({
+ useParams: () => ({ id: "openai" }),
+ useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
+}));
+vi.mock("next-intl", () => ({
+ useTranslations: (ns?: string) => (k: string) => (ns ? `${ns}.${k}` : k),
+}));
+
+const cleanups: Array<() => void> = [];
+
+function renderModal(node: React.ReactElement) {
+ const container = document.createElement("div");
+ document.body.appendChild(container);
+ const root = createRoot(container);
+ act(() => root.render(node));
+ cleanups.push(() => {
+ act(() => root.unmount());
+ container.remove();
+ });
+ return container;
+}
+
+describe("conn-modals (Phase 1c extraction)", () => {
+ beforeEach(() => {
+ (
+ globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+ ).IS_REACT_ACT_ENVIRONMENT = true;
+ vi.stubGlobal(
+ "fetch",
+ vi.fn(() =>
+ Promise.resolve({ ok: true, json: async () => ({}), text: async () => "" } as Response)
+ )
+ );
+ vi.stubGlobal("localStorage", {
+ getItem: () => null,
+ setItem: () => undefined,
+ removeItem: () => undefined,
+ clear: () => undefined,
+ });
+ });
+
+ afterEach(() => {
+ while (cleanups.length) cleanups.pop()?.();
+ document.body.innerHTML = "";
+ vi.unstubAllGlobals();
+ });
+
+ it("AddApiKeyModal mounts standalone when isOpen=false", () => {
+ const onSave = vi.fn().mockResolvedValue(undefined);
+ const c = renderModal(
+
+ );
+ // When isOpen=false the modal renders nothing (null body) — no throw is the assertion.
+ expect(c).toBeDefined();
+ });
+
+ it("AddApiKeyModal mounts standalone when isOpen=true", () => {
+ const onSave = vi.fn().mockResolvedValue(undefined);
+ const c = renderModal(
+
+ );
+ expect(c.querySelector("*")).not.toBeNull();
+ });
+
+ it("AddApiKeyModal returns null when provider is falsy", () => {
+ const onSave = vi.fn().mockResolvedValue(undefined);
+ const c = renderModal(
+
+ );
+ // No provider → renders null
+ expect(c.textContent).toBe("");
+ });
+
+ it("EditConnectionModal mounts standalone when connection=null", () => {
+ const onSave = vi.fn().mockResolvedValue(undefined);
+ const c = renderModal(
+
+ );
+ // connection=null → renders null — no throw is the assertion
+ expect(c).toBeDefined();
+ });
+
+ it("EditConnectionModal mounts standalone with a connection", () => {
+ const onSave = vi.fn().mockResolvedValue(undefined);
+ const connection = {
+ id: "conn-1",
+ name: "Test Connection",
+ provider: "openai",
+ authType: "apikey",
+ priority: 1,
+ };
+ const c = renderModal(
+
+ );
+ expect(c.querySelector("*")).not.toBeNull();
+ });
+
+ it("EditConnectionModal renders without ReferenceError for oauth connection", () => {
+ const onSave = vi.fn().mockResolvedValue(undefined);
+ const connection = {
+ id: "conn-oauth",
+ name: "OAuth Account",
+ email: "user@example.com",
+ provider: "claude",
+ authType: "oauth",
+ priority: 1,
+ };
+ // Must not throw; tests that ERROR_TYPE_LABELS and formatTimeAgo are properly imported
+ expect(() =>
+ renderModal(
+
+ )
+ ).not.toThrow();
+ });
+});
diff --git a/src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts b/src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts
index 2f3922c63b..a3ba6ac96a 100644
--- a/src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts
+++ b/src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts
@@ -629,3 +629,43 @@ export function shouldSwitchToVisibleFilter(opts: {
}): boolean {
return opts.autoHideFailed && opts.hiddenCount > 0;
}
+
+// ---------------------------------------------------------------------------
+// Error-type label map — shared by ConnectionRow and EditConnectionModal
+// ---------------------------------------------------------------------------
+export const ERROR_TYPE_LABELS: Record<
+ string,
+ { labelKey: string; variant: string }
+> = {
+ runtime_error: { labelKey: "errorTypeRuntime", variant: "warning" },
+ upstream_auth_error: { labelKey: "errorTypeUpstreamAuth", variant: "error" },
+ account_deactivated: { labelKey: "Account Deactivated", variant: "error" },
+ auth_missing: { labelKey: "errorTypeMissingCredential", variant: "warning" },
+ token_refresh_failed: { labelKey: "errorTypeRefreshFailed", variant: "warning" },
+ token_expired: { labelKey: "errorTypeTokenExpired", variant: "warning" },
+ upstream_rate_limited: { labelKey: "errorTypeRateLimited", variant: "warning" },
+ upstream_unavailable: { labelKey: "errorTypeUpstreamUnavailable", variant: "error" },
+ network_error: { labelKey: "errorTypeNetworkError", variant: "warning" },
+ unsupported: { labelKey: "errorTypeTestUnsupported", variant: "default" },
+ upstream_error: { labelKey: "errorTypeUpstreamError", variant: "error" },
+ banned: { labelKey: "errorTypeBanned", variant: "error" },
+ credits_exhausted: { labelKey: "errorTypeCreditsExhausted", variant: "warning" },
+};
+
+// ---------------------------------------------------------------------------
+// formatTimeAgo — used in EditConnectionModal's extra-key health display
+// ---------------------------------------------------------------------------
+export function formatTimeAgo(dateStr: string): string {
+ const now = Date.now();
+ const date = new Date(dateStr).getTime();
+ const diff = now - date;
+ if (diff < 0) return "just now";
+ const minutes = Math.floor(diff / 60000);
+ if (minutes < 1) return "just now";
+ if (minutes < 60) return `${minutes}m ago`;
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return `${hours}h ago`;
+ const days = Math.floor(hours / 24);
+ if (days < 30) return `${days}d ago`;
+ return new Date(dateStr).toLocaleDateString();
+}