From 254abd504f7e53a4b3b8b22d2ef20f18ae3e1b0a Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 22 Apr 2026 20:02:54 -0400 Subject: [PATCH] fix(cliproxy): keep provider sections consistent for variants --- .../components/cliproxy/cliproxy-dialog.tsx | 12 ++-- .../cliproxy/cliproxy-edit-dialog.tsx | 12 ++-- .../cliproxy/provider-editor/index.tsx | 1 + .../provider-editor/provider-info-tab.tsx | 17 +++-- ui/src/lib/i18n.ts | 62 +++++++++++++++---- ui/src/lib/provider-config.ts | 29 +++++++-- ui/src/pages/cliproxy.tsx | 4 +- .../pages/settings/sections/proxy/index.tsx | 21 +++---- .../provider-info-tab.test.tsx | 20 ++++++ ui/tests/unit/ui/lib/provider-config.test.ts | 22 +++++++ 10 files changed, 149 insertions(+), 51 deletions(-) diff --git a/ui/src/components/cliproxy/cliproxy-dialog.tsx b/ui/src/components/cliproxy/cliproxy-dialog.tsx index 3256f236..cb8ff68b 100644 --- a/ui/src/components/cliproxy/cliproxy-dialog.tsx +++ b/ui/src/components/cliproxy/cliproxy-dialog.tsx @@ -206,7 +206,7 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) { > {CLIPROXY_PROVIDER_SECTIONS.map((section) => ( - + {providerOptions .filter((opt) => section.providers.includes(opt.value)) .map((opt) => ( @@ -224,9 +224,9 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) { )} {selectedProviderSection && (

- {selectedProviderSection.hint} + {t(selectedProviderSection.hintKey)} {isPlusExtraProvider(selectedProvider) - ? ' Requires the optional Plus backend while that track remains community-maintained.' + ? ` ${t('providerConfig.plusTrackNote')}` : ''}

)} @@ -320,7 +320,7 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) { className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" > {CLIPROXY_PROVIDER_SECTIONS.map((section) => ( - + {providerOptions .filter((opt) => section.providers.includes(opt.value)) .map((opt) => ( @@ -333,9 +333,9 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) { {compositeTiers?.[tier]?.provider && (

- {getProviderSection(compositeTiers[tier].provider)?.hint} + {t(getProviderSection(compositeTiers[tier].provider)?.hintKey || '')} {isPlusExtraProvider(compositeTiers[tier].provider) - ? ' Requires the optional Plus backend while that track remains community-maintained.' + ? ` ${t('providerConfig.plusTrackNote')}` : ''}

)} diff --git a/ui/src/components/cliproxy/cliproxy-edit-dialog.tsx b/ui/src/components/cliproxy/cliproxy-edit-dialog.tsx index 16825e7f..76540428 100644 --- a/ui/src/components/cliproxy/cliproxy-edit-dialog.tsx +++ b/ui/src/components/cliproxy/cliproxy-edit-dialog.tsx @@ -327,7 +327,7 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" > {CLIPROXY_PROVIDER_SECTIONS.map((section) => ( - + {providerOptions .filter((opt) => section.providers.includes(opt.value)) .map((opt) => ( @@ -340,9 +340,9 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit {compositeTiers?.[tier]?.provider && (

- {getProviderSection(compositeTiers[tier].provider)?.hint} + {t(getProviderSection(compositeTiers[tier].provider)?.hintKey || '')} {isPlusExtraProvider(compositeTiers[tier].provider) - ? ' Requires the optional Plus backend while that track remains community-maintained.' + ? ` ${t('providerConfig.plusTrackNote')}` : ''}

)} @@ -422,7 +422,7 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" > {CLIPROXY_PROVIDER_SECTIONS.map((section) => ( - + {providerOptions .filter((opt) => section.providers.includes(opt.value)) .map((opt) => ( @@ -435,9 +435,9 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit {selectedProvider && (

- {getProviderSection(selectedProvider)?.hint} + {t(getProviderSection(selectedProvider)?.hintKey || '')} {isPlusExtraProvider(selectedProvider) - ? ' Requires the optional Plus backend while that track remains community-maintained.' + ? ` ${t('providerConfig.plusTrackNote')}` : ''}

)} diff --git a/ui/src/components/cliproxy/provider-editor/index.tsx b/ui/src/components/cliproxy/provider-editor/index.tsx index 72b3d7ef..65be5b3b 100644 --- a/ui/src/components/cliproxy/provider-editor/index.tsx +++ b/ui/src/components/cliproxy/provider-editor/index.tsx @@ -304,6 +304,7 @@ export function ProviderEditor({ {providerSection && (
- Track + + {t('providerConfig.trackLabel')} +
- {providerSection.label} + {t(providerSection.labelKey)}

- {providerSection.hint} - {isPlusExtraProvider(provider) - ? ' Requires the optional Plus backend while that track remains community-maintained.' + {t(providerSection.hintKey)} + {isPlusExtraProvider(sectionProvider) + ? ` ${t('providerConfig.plusTrackNote')}` : ''}

diff --git a/ui/src/lib/i18n.ts b/ui/src/lib/i18n.ts index 5329df3a..b74a1194 100644 --- a/ui/src/lib/i18n.ts +++ b/ui/src/lib/i18n.ts @@ -977,10 +977,13 @@ const resources = { backendBinary: 'Backend Binary', stopProxyToSwitch: 'Stop the running proxy in Instance Status to switch backend.', default: 'Default', - plusDesc: 'Full provider support including Kiro and GitHub Copilot', - originalDesc: 'Original binary (Gemini, Codex, Antigravity only)', + plusDesc: + 'Optional track for extra providers. Still supported, but currently community-maintained instead of upstream-maintained.', + originalDesc: 'Default, always-available backend for the core provider track.', + plusFallbackNotice: + 'The Plus provider track is not deprecated, but local CLIProxy still falls back to the original backend while the maintained fork path is being brought back.', variantsIncompatible: - 'Existing Kiro/Copilot variants will not work with CLIProxyAPI. Switch to CLIProxyAPIPlus or remove those variants.', + 'Existing plus-extra variants ({{providers}}) will not run on the original backend. Keep them visible for reference, but switch to Plus before using them.', safety: 'Safety', agyModeTitle: 'Antigravity + Gemini Power User Mode', agyModeDesc: @@ -2234,6 +2237,13 @@ const resources = { }, providerConfig: { defaultDeviceCodeInstruction: 'Complete the authorization in your browser.', + trackLabel: 'Track', + sectionCoreLabel: 'Core / original backend', + sectionCoreHint: 'Default, always-available provider track', + sectionPlusLabel: 'Plus extras / community-maintained', + sectionPlusHint: 'Still supported, but separated from the default backend for now', + plusTrackNote: + 'Requires the optional Plus backend while that track remains community-maintained.', }, // ======================================== @@ -3443,10 +3453,12 @@ const resources = { backendBinary: '后端二进制', stopProxyToSwitch: '请先在实例状态中停止正在运行的代理,再切换后端。', default: '默认', - plusDesc: '完整支持包括 Kiro 和 GitHub Copilot 在内的提供商', - originalDesc: '原版二进制(仅 Gemini、Codex、Antigravity)', + plusDesc: '额外提供商的可选线路。仍受支持,但目前由社区维护而非上游维护。', + originalDesc: '核心提供商线路的默认、始终可用后端。', + plusFallbackNotice: + 'Plus 提供商线路并未弃用,但在受维护的 fork 恢复之前,本地 CLIProxy 仍会回退到原始后端。', variantsIncompatible: - '现有 Kiro/Copilot 变体与 CLIProxyAPI 不兼容。请切换到 CLIProxyAPIPlus 或移除这些变体。', + '现有 plus 扩展变体({{providers}})无法在原始后端上运行。可以保留作参考,但使用前请切换到 Plus。', safety: '安全', agyModeTitle: 'Antigravity + Gemini 高级模式', agyModeDesc: '跳过 AGY 责任确认清单,以及 Gemini Dashboard 中输入风险短语的步骤。', @@ -4656,6 +4668,12 @@ const resources = { }, providerConfig: { defaultDeviceCodeInstruction: '请在浏览器中完成授权。', + trackLabel: '分组', + sectionCoreLabel: '核心 / 原始后端', + sectionCoreHint: '默认且始终可用的提供商线路', + sectionPlusLabel: 'Plus 扩展 / 社区维护', + sectionPlusHint: '仍然受支持,但目前与默认后端分开显示', + plusTrackNote: '需要可选的 Plus 后端;当前这条线路由社区维护。', }, profileEditorSections: { imageAnalysis: '图片分析', @@ -5934,10 +5952,13 @@ const resources = { stopProxyToSwitch: 'Dừng proxy đang chạy trong Trạng thái phiên bản trước khi chuyển backend.', default: 'Mặc định', - plusDesc: 'Hỗ trợ đầy đủ nhà cung cấp, bao gồm Kiro và GitHub Copilot', - originalDesc: 'Binary gốc (chỉ Gemini, Codex, Antigravity)', + plusDesc: + 'Nhánh tùy chọn cho các nhà cung cấp bổ sung. Vẫn được hỗ trợ nhưng hiện do cộng đồng duy trì thay vì upstream.', + originalDesc: 'Backend mặc định, luôn sẵn sàng cho nhóm nhà cung cấp cốt lõi.', + plusFallbackNotice: + 'Nhánh nhà cung cấp Plus chưa bị khai tử, nhưng CLIProxy cục bộ vẫn quay về backend gốc cho tới khi đường dẫn fork được duy trì được bật lại.', variantsIncompatible: - 'Các biến thể Kiro/Copilot hiện tại sẽ không hoạt động với CLIProxyAPI. Chuyển sang CLIProxyAPIPlus hoặc xóa các biến thể đó.', + 'Các biến thể plus-extra hiện có ({{providers}}) sẽ không chạy trên backend gốc. Có thể giữ lại để tham chiếu, nhưng hãy chuyển sang Plus trước khi dùng.', safety: 'An toàn', agyModeTitle: 'Chế độ power user Antigravity + Gemini', agyModeDesc: @@ -7170,6 +7191,12 @@ const resources = { }, providerConfig: { defaultDeviceCodeInstruction: 'Hoàn tất việc cấp quyền trong trình duyệt của bạn.', + trackLabel: 'Nhóm', + sectionCoreLabel: 'Core / backend gốc', + sectionCoreHint: 'Nhóm nhà cung cấp mặc định, luôn sẵn sàng', + sectionPlusLabel: 'Plus extras / cộng đồng duy trì', + sectionPlusHint: 'Vẫn được hỗ trợ nhưng hiện được tách khỏi backend mặc định', + plusTrackNote: 'Cần backend Plus tùy chọn; hiện tại nhánh này do cộng đồng duy trì.', }, profileEditorSections: { imageAnalysis: 'Phân tích hình ảnh', @@ -8461,10 +8488,13 @@ const resources = { stopProxyToSwitch: 'バックエンドを切り替える前に、インスタンス状態から実行中のプロキシを停止してください。', default: 'デフォルト', - plusDesc: 'Kiro と GitHub Copilot を含むすべてのプロバイダーをサポート', - originalDesc: '元のバイナリ(Gemini、Codex、Antigravity のみ)', + plusDesc: + '追加プロバイダー向けのオプショントラックです。引き続きサポートされていますが、現在は upstream ではなくコミュニティ保守です。', + originalDesc: 'コアプロバイダートラック向けの既定かつ常時利用可能な backend。', + plusFallbackNotice: + 'Plus プロバイダートラックは廃止ではありませんが、保守中の fork が戻るまではローカル CLIProxy は引き続きオリジナル backend にフォールバックします。', variantsIncompatible: - '既存の Kiro/Copilot バリアントは CLIProxyAPI では動作しません。CLIProxyAPIPlus に切り替えるか、それらのバリアントを削除してください。', + '既存の plus-extra バリアント({{providers}})はオリジナル backend では動作しません。参照用に残すことはできますが、使用前に Plus へ切り替えてください。', safety: '安全設定', agyModeTitle: 'Antigravity + Gemini パワーユーザーモード', agyModeDesc: @@ -9984,6 +10014,14 @@ const resources = { }, providerConfig: { defaultDeviceCodeInstruction: 'ブラウザーで認証を完了してください。', + trackLabel: 'トラック', + sectionCoreLabel: 'コア / オリジナル backend', + sectionCoreHint: '既定で常に利用できるプロバイダートラック', + sectionPlusLabel: 'Plus 拡張 / コミュニティ保守', + sectionPlusHint: + '引き続きサポートされていますが、当面は既定の backend から分離されています', + plusTrackNote: + 'このトラックはオプションの Plus backend が必要で、現在はコミュニティ保守です。', }, updatesSpotlight: { openUpdatesCenter: '更新センターを開く', diff --git a/ui/src/lib/provider-config.ts b/ui/src/lib/provider-config.ts index 7f824ce0..00c34cc7 100644 --- a/ui/src/lib/provider-config.ts +++ b/ui/src/lib/provider-config.ts @@ -24,8 +24,8 @@ export type CLIProxyProviderSectionId = 'core' | 'plus-extra'; export interface CLIProxyProviderSection { id: CLIProxyProviderSectionId; - label: string; - hint: string; + labelKey: string; + hintKey: string; providers: readonly CLIProxyProvider[]; } @@ -42,14 +42,14 @@ export const PLUS_EXTRA_CLIPROXY_PROVIDERS: readonly CLIProxyProvider[] = Object export const CLIPROXY_PROVIDER_SECTIONS: readonly CLIProxyProviderSection[] = Object.freeze([ { id: 'core', - label: 'Core / original backend', - hint: 'Default, always-available provider track', + labelKey: 'providerConfig.sectionCoreLabel', + hintKey: 'providerConfig.sectionCoreHint', providers: CORE_CLIPROXY_PROVIDERS, }, { id: 'plus-extra', - label: 'Plus extras / community-maintained', - hint: 'Still supported, but separated from the default backend for now', + labelKey: 'providerConfig.sectionPlusLabel', + hintKey: 'providerConfig.sectionPlusHint', providers: PLUS_EXTRA_CLIPROXY_PROVIDERS, }, ]); @@ -309,6 +309,23 @@ export function getProviderSection(provider: unknown): CLIProxyProviderSection | ); } +interface VariantLike { + provider?: unknown; + tiers?: Record | null; +} + +export function variantUsesPlusExtraProvider(variant: VariantLike | null | undefined): boolean { + if (!variant) { + return false; + } + + if (variant.tiers) { + return Object.values(variant.tiers).some((tier) => isPlusExtraProvider(tier?.provider)); + } + + return isPlusExtraProvider(variant.provider); +} + export function groupProvidersBySection( items: readonly T[], getProvider: (item: T) => unknown diff --git a/ui/src/pages/cliproxy.tsx b/ui/src/pages/cliproxy.tsx index f48374e2..962591e2 100644 --- a/ui/src/pages/cliproxy.tsx +++ b/ui/src/pages/cliproxy.tsx @@ -406,10 +406,10 @@ export function CliproxyPage() {
- {section.label} + {t(section.labelKey)}

- {section.hint} + {t(section.hintKey)}

diff --git a/ui/src/pages/settings/sections/proxy/index.tsx b/ui/src/pages/settings/sections/proxy/index.tsx index f5b237ae..441de0f8 100644 --- a/ui/src/pages/settings/sections/proxy/index.tsx +++ b/ui/src/pages/settings/sections/proxy/index.tsx @@ -33,7 +33,7 @@ import { CORE_CLIPROXY_PROVIDERS, PLUS_EXTRA_CLIPROXY_PROVIDERS, getProviderDisplayName, - isPlusExtraProvider, + variantUsesPlusExtraProvider, } from '@/lib/provider-config'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; @@ -216,7 +216,9 @@ export default function ProxySection() { const checkPlusOnlyVariants = useCallback(async () => { try { const result = await api.cliproxy.list(); - const hasIncompatible = result.variants.some((v) => isPlusExtraProvider(v.provider)); + const hasIncompatible = result.variants.some((variant) => + variantUsesPlusExtraProvider(variant) + ); setHasKiroGhcpVariants(hasIncompatible); } catch (err) { console.error('[Proxy] Failed to check variants:', err); @@ -496,10 +498,7 @@ export default function ProxySection() {
{t('settingsProxy.backendPlusApi')}
-

- Optional track for extra providers. Still supported, but currently - community-maintained instead of upstream-maintained. -

+

{t('settingsProxy.plusDesc')}

{plusProviderNames}

@@ -521,9 +520,7 @@ export default function ProxySection() { {t('settingsProxy.default')}
-

- Default, always-available backend for the core provider track. -

+

{t('settingsProxy.originalDesc')}

{coreProviderNames}

@@ -533,8 +530,7 @@ export default function ProxySection() { - The Plus provider track is not deprecated, but local CLIProxy still falls back to - the original backend while the maintained fork path is being brought back. + {t('settingsProxy.plusFallbackNotice')} )} @@ -543,8 +539,7 @@ export default function ProxySection() { - Existing plus-extra variants ({plusProviderNames}) will not run on the original - backend. Keep them visible for reference, but switch to Plus before using them. + {t('settingsProxy.variantsIncompatible', { providers: plusProviderNames })} )} diff --git a/ui/tests/unit/components/cliproxy/provider-editor/provider-info-tab.test.tsx b/ui/tests/unit/components/cliproxy/provider-editor/provider-info-tab.test.tsx index 0606a688..1c518ba6 100644 --- a/ui/tests/unit/components/cliproxy/provider-editor/provider-info-tab.test.tsx +++ b/ui/tests/unit/components/cliproxy/provider-editor/provider-info-tab.test.tsx @@ -75,4 +75,24 @@ describe('ProviderInfoTab', () => { ) ).toBeInTheDocument(); }); + + it('uses the base provider when rendering variant track metadata', () => { + render( + + ); + + expect(screen.getByText('Track')).toBeInTheDocument(); + expect(screen.getByText('Plus extras / community-maintained')).toBeInTheDocument(); + }); }); diff --git a/ui/tests/unit/ui/lib/provider-config.test.ts b/ui/tests/unit/ui/lib/provider-config.test.ts index 89905299..f83ede63 100644 --- a/ui/tests/unit/ui/lib/provider-config.test.ts +++ b/ui/tests/unit/ui/lib/provider-config.test.ts @@ -16,6 +16,7 @@ import { parseRequestedUpstreamModelRules, PLUS_EXTRA_CLIPROXY_PROVIDERS, PROVIDER_COLORS, + variantUsesPlusExtraProvider, } from '@/lib/provider-config'; describe('provider model mapping helpers', () => { @@ -89,6 +90,27 @@ describe('provider presentation metadata', () => { expect(grouped[1]?.items.map((entry) => entry.provider)).toEqual(['cursor']); }); + it('detects plus-extra providers inside composite variants', () => { + expect( + variantUsesPlusExtraProvider({ + provider: 'gemini', + tiers: { + opus: { provider: 'gemini' }, + sonnet: { provider: 'cursor' }, + }, + }) + ).toBe(true); + expect( + variantUsesPlusExtraProvider({ + provider: 'gemini', + tiers: { + opus: { provider: 'gemini' }, + sonnet: { provider: 'kimi' }, + }, + }) + ).toBe(false); + }); + it.each([ ['cursor', 'Cursor', 'Cursor browser-authenticated provider', '/assets/sidebar/cursor.svg'], ['gitlab', 'GitLab Duo', 'GitLab Duo with OAuth or PAT auth', '/assets/providers/gitlab.svg'],