From 92a769d773a8ec63fd02744aff60d3691df7cf94 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Fri, 10 Apr 2026 17:44:15 -0400 Subject: [PATCH] fix(ui): harden provider model alias editing - keep raw JSON model mappings aligned with requested=upstream semantics - block malformed mapping lines instead of coercing them on save - render saved mappings as requested to upstream and add regression coverage Refs #941 --- .../ai-providers/provider-entry-card.tsx | 11 +++-- .../ai-providers/provider-entry-dialog.tsx | 18 +++++++- ui/src/lib/provider-config.ts | 17 +++++++ ui/src/pages/cliproxy-ai-providers.tsx | 44 ++++++++++--------- .../cliproxy/provider-entry-card.test.tsx | 41 +++++++++++++++++ ui/tests/unit/ui/lib/provider-config.test.ts | 8 ++++ 6 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 ui/tests/unit/components/cliproxy/provider-entry-card.test.tsx diff --git a/ui/src/components/cliproxy/ai-providers/provider-entry-card.tsx b/ui/src/components/cliproxy/ai-providers/provider-entry-card.tsx index 7457caae..941ac32b 100644 --- a/ui/src/components/cliproxy/ai-providers/provider-entry-card.tsx +++ b/ui/src/components/cliproxy/ai-providers/provider-entry-card.tsx @@ -1,5 +1,6 @@ import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; +import { getRequestedModelId } from '@/lib/provider-config'; import { cn } from '@/lib/utils'; import type { AiProviderEntryView, @@ -241,9 +242,13 @@ export function ProviderEntryCard({ key={`${model.name}:${model.alias}`} className="rounded-md border bg-muted/20 px-3 py-2" > - {model.name} - - {model.alias} + {getRequestedModelId(model)} + {model.alias.trim() ? ( + <> + + {model.name} + + ) : null} ))} diff --git a/ui/src/components/cliproxy/ai-providers/provider-entry-dialog.tsx b/ui/src/components/cliproxy/ai-providers/provider-entry-dialog.tsx index e6b54c0b..d3dfc74c 100644 --- a/ui/src/components/cliproxy/ai-providers/provider-entry-dialog.tsx +++ b/ui/src/components/cliproxy/ai-providers/provider-entry-dialog.tsx @@ -3,6 +3,7 @@ import { ProviderLogo } from '@/components/cliproxy/provider-logo'; import { formatRequestedUpstreamModelRules, getAiProviderFamilyVisual, + getRequestedUpstreamModelRuleErrors, parseRequestedUpstreamModelRules, } from '@/lib/provider-config'; import { Badge } from '@/components/ui/badge'; @@ -284,6 +285,10 @@ export function ProviderEntryDialog({ const [headers, setHeaders] = useState(() => formatHeaders(entry)); const [excludedModels, setExcludedModels] = useState(() => formatExcludedModels(entry)); const [modelAliases, setModelAliases] = useState(() => formatModelAliases(entry)); + const modelRuleErrors = useMemo( + () => getRequestedUpstreamModelRuleErrors(modelAliases), + [modelAliases] + ); const [advancedOpen, setAdvancedOpen] = useState(() => Boolean( entry?.headers.length || entry?.excludedModels.length || entry?.proxyUrl || entry?.prefix @@ -298,6 +303,10 @@ export function ProviderEntryDialog({ }, [entry?.secretConfigured, isEditing, supportsOpenAiCompat]); const handleSubmit = async () => { + if (modelRuleErrors.length > 0) { + return; + } + const nextApiKey = apiKey.trim(); const nextApiKeys = parseDelimitedLines(apiKeys); const preserveSecrets = @@ -459,6 +468,9 @@ export function ProviderEntryDialog({ placeholder={guide.aliasesPlaceholder} />

{guide.aliasesHelper}

+ {modelRuleErrors.length > 0 ? ( +

{modelRuleErrors[0]}

+ ) : null} @@ -560,7 +572,11 @@ export function ProviderEntryDialog({ -