From e9671d026f81038fbeec2a555b7f2cc9f6ead4b7 Mon Sep 17 00:00:00 2001 From: Randi <55005611+rdself@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:43:18 -0400 Subject: [PATCH] fix(openrouter): show preset field when editing connections (#3921) Integrated into release/v3.8.26 --- .../[id]/components/OpenRouterPresetInput.tsx | 3 +- .../[id]/components/ProviderModalsPanel.tsx | 27 ++-- .../[id]/components/modals/AddApiKeyModal.tsx | 24 ++-- .../components/modals/EditConnectionModal.tsx | 133 +++++++++--------- .../modals/__tests__/connModals.test.tsx | 50 ++++++- 5 files changed, 141 insertions(+), 96 deletions(-) diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/OpenRouterPresetInput.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/OpenRouterPresetInput.tsx index 280f420f69..5da8ca294b 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/OpenRouterPresetInput.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/OpenRouterPresetInput.tsx @@ -17,7 +17,8 @@ export default function OpenRouterPresetInput({ value, onChange, t }: OpenRouter label={providerText(t, "openRouterPresetLabel", "OpenRouter preset")} value={value} onChange={(e) => onChange(e.target.value)} - placeholder="email-copywriter" + placeholder="@preset/slug" + data-testid="openrouter-preset-input" maxLength={OPENROUTER_PRESET_MAX_LENGTH} hint={providerText( t, diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderModalsPanel.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderModalsPanel.tsx index 7959d7e431..ff6d485c68 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderModalsPanel.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderModalsPanel.tsx @@ -2,7 +2,14 @@ // Phase 1t.5 extraction — Issue #3501 // Pure composition of all modal elements rendered by ProviderDetailPageClient. -import { ConfirmModal, OAuthModal, KiroOAuthWrapper, CursorAuthModal, TraeAuthModal, ProxyConfigModal } from "@/shared/components"; +import { + ConfirmModal, + OAuthModal, + KiroOAuthWrapper, + CursorAuthModal, + TraeAuthModal, + ProxyConfigModal, +} from "@/shared/components"; import RiskNoticeModal from "../../components/RiskNoticeModal"; import CodexCliGuideModal from "../../components/CodexCliGuideModal"; import SiliconFlowEndpointModal from "./SiliconFlowEndpointModal"; @@ -13,18 +20,9 @@ import ExternalLinkModal from "./ExternalLinkModal"; import BatchTestResultsModal from "./BatchTestResultsModal"; import ImportProgressModal from "./ImportProgressModal"; import { AdaptaTutorialModal } from "./AdaptaTutorialModal"; -import { - ImportCodexAuthModal, - ApplyCodexAuthModal, -} from "./modals/ImportCodexAuthModal"; -import { - ImportClaudeAuthModal, - ApplyClaudeAuthModal, -} from "./modals/ImportClaudeAuthModal"; -import { - ImportGeminiAuthModal, - ApplyGeminiAuthModal, -} from "./modals/ImportGeminiAuthModal"; +import { ImportCodexAuthModal, ApplyCodexAuthModal } from "./modals/ImportCodexAuthModal"; +import { ImportClaudeAuthModal, ApplyClaudeAuthModal } from "./modals/ImportClaudeAuthModal"; +import { ImportGeminiAuthModal, ApplyGeminiAuthModal } from "./modals/ImportGeminiAuthModal"; import { type ConnectionRowConnection } from "./ConnectionRow"; import { type BatchTestResults } from "../hooks/useProviderConnections"; import { type ImportProgress } from "../hooks/useModelImportHandlers"; @@ -97,7 +95,7 @@ interface ProviderModalsPanelProps { // Edit connection showEditModal: boolean; setShowEditModal: (open: boolean) => void; - selectedConnection: { id: string } | null; + selectedConnection: ConnectionRowConnection | null; handleUpdateConnection: (data: any) => Promise; // Edit compatible node showEditNodeModal: boolean; @@ -316,6 +314,7 @@ export default function ProviderModalsPanel({ setShowEditModal(false)} /> diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx index 59d94f0131..0e6b5ee065 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx @@ -94,7 +94,6 @@ export default function AddApiKeyModal({ error: "Connection failed", }[commandCodeAuthState.phase] : null; - const [formData, setFormData] = useState({ name: "", apiKey: "", @@ -119,7 +118,6 @@ export default function AddApiKeyModal({ const [showAdvanced, setShowAdvanced] = useState(false); const [copiedCommandCodeField, setCopiedCommandCodeField] = useState(null); const wasOpenRef = useRef(false); - useEffect(() => { const wasOpen = wasOpenRef.current; wasOpenRef.current = isOpen; @@ -674,16 +672,19 @@ export default function AddApiKeyModal({ {saveError} )} - {isCcCompatible && ( + {(isCcCompatible || openRouterPreset.input) && (
- - setFormData({ ...formData, ccCompatibleContext1m: checked }) - } - label={t("ccCompatibleContext1mLabel")} - description={t("ccCompatibleContext1mDescription")} - /> + {isCcCompatible && ( + + setFormData({ ...formData, ccCompatibleContext1m: checked }) + } + label={t("ccCompatibleContext1mLabel")} + description={t("ccCompatibleContext1mDescription")} + /> + )} + {openRouterPreset.input}
)} {isCompatible && !isCcCompatible && ( @@ -724,7 +725,6 @@ export default function AddApiKeyModal({ placeholder="my-app/1.0" hint={t("customUserAgentHint")} /> - {openRouterPreset.input} Promise; onClose: () => void; } +const stringField = (value: unknown) => (typeof value === "string" ? value : ""); + export default function EditConnectionModal({ isOpen, connection, + providerId, onSave, onClose, }: EditConnectionModalProps) { const t = useTranslations("providers"); const notify = useNotificationStore(); + const provider = connection?.provider || providerId; const [formData, setFormData] = useState({ name: "", priority: 1, @@ -107,8 +112,8 @@ export default function EditConnectionModal({ cloudCodeProjectId: "", antigravityClientProfile: "ide", blockExtraUsage: - connection?.provider === "claude" - ? isClaudeExtraUsageBlockEnabled(connection?.provider, connection?.providerSpecificData) + provider === "claude" + ? isClaudeExtraUsageBlockEnabled(provider, connection?.providerSpecificData) : false, passthroughModels: connection?.providerSpecificData?.passthroughModels === true, disableCooling: connection?.providerSpecificData?.disableCooling === true, @@ -136,33 +141,36 @@ export default function EditConnectionModal({ const [showAdvanced, setShowAdvanced] = useState(false); const showEmail = useEmailPrivacyStore((state) => state.emailsVisible); - 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 usesBaseUrl = isBaseUrlConfigurableProvider(provider); + const defaultBaseUrl = getProviderBaseUrlDefault(provider); + const isVertex = provider === "vertex" || provider === "vertex-partner"; + const isBedrock = provider === "bedrock"; const showsRegion = isVertex || isBedrock; - const isGlm = isGlmProvider(connection?.provider); - const isCloudflare = connection?.provider === "cloudflare-ai"; - const openRouterPreset = useOpenRouterPresetControl(connection?.provider, t); + const isGlm = isGlmProvider(provider); + const isCloudflare = provider === "cloudflare-ai"; + const openRouterPreset = useOpenRouterPresetControl(provider, t); const setOpenRouterPreset = openRouterPreset.setValue; - const isCodex = connection?.provider === "codex"; - const isClaude = connection?.provider === "claude"; - const isGeminiCli = connection?.provider === "gemini-cli"; - const isAntigravity = connection?.provider === "antigravity"; + const isCodex = provider === "codex"; + const isClaude = provider === "claude"; + const isGeminiCli = provider === "gemini-cli"; + const isAntigravity = provider === "antigravity"; const supportsGoogleProjectId = isGeminiCli || isAntigravity; - const localProviderMetadata = getLocalProviderMetadata(connection?.provider); + const localProviderMetadata = getLocalProviderMetadata(provider); const isLocalSelfHostedProvider = !!localProviderMetadata; - const isGooglePse = connection?.provider === "google-pse-search"; - const webSessionCredential = getWebSessionCredentialRequirement(connection?.provider); + const isGooglePse = provider === "google-pse-search"; + const webSessionCredential = getWebSessionCredentialRequirement(provider); const isNoAuthWebSessionCredential = webSessionCredential?.kind === "none"; const isWebSessionCredential = !!webSessionCredential && webSessionCredential.kind !== "none"; const providerDisplayName = - (connection?.provider ? resolveDashboardProviderInfo(connection.provider)?.name : null) || - connection?.provider || + (provider ? resolveDashboardProviderInfo(provider)?.name : null) || + localProviderMetadata?.name || + provider || ""; const apiKeyOptional = - providerAllowsOptionalApiKey(connection?.provider) || Boolean(isNoAuthWebSessionCredential); - const isCcCompatible = isClaudeCodeCompatibleProvider(connection?.provider); + providerAllowsOptionalApiKey(provider) || Boolean(isNoAuthWebSessionCredential); + const isCcCompatible = isClaudeCodeCompatibleProvider(provider); + const isCompatible = + isOpenAICompatibleProvider(provider) || isAnthropicCompatibleProvider(provider); const defaultRegion = isBedrock ? "eu-west-2" : "us-central1"; const apiCredentialLabel = webSessionCredential ? getWebSessionCredentialLabel(t, webSessionCredential, apiKeyOptional) @@ -178,7 +186,7 @@ export default function EditConnectionModal({ ? getWebSessionCredentialHint(t, webSessionCredential, providerDisplayName, true) : isLocalSelfHostedProvider ? t("localProviderApiKeyOptionalHint", { - provider: localProviderMetadata?.name || connection?.provider || "", + provider: localProviderMetadata?.name || provider || "", }) : apiKeyOptional ? t("apiKeyOptionalHint") @@ -194,26 +202,18 @@ export default function EditConnectionModal({ 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 rawOpenRouterPreset = connection.providerSpecificData?.preset; - const existingOpenRouterPreset = - typeof rawOpenRouterPreset === "string" ? rawOpenRouterPreset : ""; - const rawCx = connection.providerSpecificData?.cx; - const existingCx = typeof rawCx === "string" ? rawCx : ""; - const rawAccountId = connection.providerSpecificData?.accountId; - const existingAccountId = typeof rawAccountId === "string" ? rawAccountId : ""; + const effectiveProvider = connection.provider || providerId; + const existingBaseUrl = stringField(connection.providerSpecificData?.baseUrl); + const existingRegion = stringField(connection.providerSpecificData?.region); + const existingCustomUserAgent = stringField(connection.providerSpecificData?.customUserAgent); + const existingOpenRouterPreset = stringField(connection.providerSpecificData?.preset); + const existingCx = stringField(connection.providerSpecificData?.cx); + const existingAccountId = stringField(connection.providerSpecificData?.accountId); const codexRequestDefaults = getCodexRequestDefaults(connection.providerSpecificData); const ccRequestDefaults = getClaudeCodeCompatibleRequestDefaults( connection.providerSpecificData ); - const rawConsoleApiKey = connection.providerSpecificData?.consoleApiKey; - const existingConsoleApiKey = typeof rawConsoleApiKey === "string" ? rawConsoleApiKey : ""; + const existingConsoleApiKey = stringField(connection.providerSpecificData?.consoleApiKey); setFormData({ name: connection.name || "", priority: connection.priority || 1, @@ -267,7 +267,7 @@ export default function EditConnectionModal({ connection.providerSpecificData?.clientProfile ), blockExtraUsage: isClaudeExtraUsageBlockEnabled( - connection.provider, + effectiveProvider, connection.providerSpecificData ), passthroughModels: connection?.providerSpecificData?.passthroughModels === true, @@ -290,18 +290,23 @@ export default function EditConnectionModal({ setApiKeyHealth(health || {}); setNewExtraKey(""); setOpenRouterPreset(existingOpenRouterPreset); - setShowAdvanced( - !!existingCustomUserAgent || - (connection.provider === "openrouter" && !!existingOpenRouterPreset) - ); + setShowAdvanced(!!existingCustomUserAgent); setTestResult(null); setValidationResult(null); setSaveError(null); } - }, [isOpen, connection, defaultBaseUrl, showsRegion, defaultRegion, setOpenRouterPreset]); + }, [ + isOpen, + connection, + providerId, + defaultBaseUrl, + showsRegion, + defaultRegion, + setOpenRouterPreset, + ]); const handleTest = async () => { - if (!connection?.provider) return; + if (!provider) return; setTesting(true); setTestResult(null); try { @@ -331,7 +336,7 @@ export default function EditConnectionModal({ const handleValidate = async () => { if ( - !connection?.provider || + !provider || isNoAuthWebSessionCredential || (!isCompatible && !apiKeyOptional && !formData.apiKey) ) { @@ -344,7 +349,7 @@ export default function EditConnectionModal({ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - provider: connection.provider, + provider, apiKey: formData.apiKey, validationModelId: formData.validationModelId || undefined, customUserAgent: formData.customUserAgent.trim() || undefined, @@ -435,7 +440,7 @@ export default function EditConnectionModal({ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - provider: connection.provider, + provider, apiKey: formData.apiKey, validationModelId: formData.validationModelId || undefined, customUserAgent: formData.customUserAgent.trim() || undefined, @@ -474,7 +479,7 @@ export default function EditConnectionModal({ ...openRouterPreset.getPatch(), ...(formData.passthroughModels ? { passthroughModels: true } : {}), }; - if (connection.provider === "bailian-coding-plan") { + if (provider === "bailian-coding-plan") { if (formData.consoleApiKey.trim()) { updates.providerSpecificData.consoleApiKey = formData.consoleApiKey.trim(); } else { @@ -562,9 +567,6 @@ export default function EditConnectionModal({ 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 @@ -643,17 +645,19 @@ export default function EditConnectionModal({ /> )} - {isCcCompatible && ( + {(isCcCompatible || openRouterPreset.input) && (
- setFormData({ ...formData, ccCompatibleContext1m: checked })} - label={t("ccCompatibleContext1mLabel")} - description={t("ccCompatibleContext1mDescription")} - /> + {isCcCompatible && ( + setFormData({ ...formData, ccCompatibleContext1m: checked })} + label={t("ccCompatibleContext1mLabel")} + description={t("ccCompatibleContext1mDescription")} + /> + )} + {openRouterPreset.input}
)} - {/* #2997: per-connection transient-cooldown opt-out (provider-agnostic) */}
- {openRouterPreset.input} - {connection.provider === "bailian-coding-plan" && ( + {provider === "bailian-coding-plan" && ( setFormData({ ...formData, baseUrl: e.target.value })} - placeholder={getProviderBaseUrlPlaceholder(connection.provider)} - hint={getProviderBaseUrlHint(connection.provider, t)} + placeholder={getProviderBaseUrlPlaceholder(provider)} + hint={getProviderBaseUrlHint(provider, t)} /> )} @@ -961,12 +964,10 @@ export default function EditConnectionModal({
)} - {/* T07: API Key Health Status */} {!isOAuth && connection?.apiKey && (
- {/* Primary Key Health */} {(() => { const keyId = "primary"; const health = apiKeyHealth[keyId]; @@ -1012,7 +1013,6 @@ export default function EditConnectionModal({
)} - {/* T07: Extra API Keys for round-robin rotation */} {!isOAuth && (
@@ -1136,7 +1136,6 @@ export default function EditConnectionModal({
)} - {/* Test Connection */} {!isCompatible && (