From 673ab1f7a441a94985607531601440843a7d6703 Mon Sep 17 00:00:00 2001 From: seeones Date: Sun, 27 Sep 2026 07:31:57 +0800 Subject: [PATCH 01/12] feat: inline chat model selector (split from chat-input-ux) Extract the chat input model picker (ChatModelSelector + useChatModelSelector) into its own PR so each PR stays under the mutation-diff changed-lines gate. Mounts between ApiConfigSelector and AutoApproveDropdown in the chat composer action bar. Includes the Tab-navigation visual test (loop widened to 50 stops for the extra button) and the composer snapshots rendered with the selector present. --- .../src/components/chat/ChatModelSelector.tsx | 172 ++++++++ .../src/components/chat/ChatTextArea.tsx | 6 + .../chat/__tests__/ChatModelSelector.spec.tsx | 179 +++++++++ .../chat/__tests__/ChatTextArea.visual.tsx | 2 +- .../chat-composer-focus-dark.png | Bin 9166 -> 9949 bytes ...hat-composer-focus-high-contrast-light.png | Bin 9606 -> 10270 bytes .../chat-composer-focus-high-contrast.png | Bin 9624 -> 10477 bytes .../chat-composer-focus-light.png | Bin 9326 -> 9973 bytes .../chat-composer-resting-dark.png | Bin 9162 -> 9945 bytes ...t-composer-resting-high-contrast-light.png | Bin 9606 -> 10270 bytes .../chat-composer-resting-high-contrast.png | Bin 9624 -> 10477 bytes .../chat-composer-resting-light.png | Bin 9326 -> 9973 bytes .../__tests__/useChatModelSelector.spec.tsx | 337 ++++++++++++++++ .../chat/hooks/useChatModelSelector.ts | 367 ++++++++++++++++++ webview-ui/src/i18n/locales/ca/chat.json | 6 +- webview-ui/src/i18n/locales/de/chat.json | 6 +- webview-ui/src/i18n/locales/en/chat.json | 6 +- webview-ui/src/i18n/locales/es/chat.json | 6 +- webview-ui/src/i18n/locales/fr/chat.json | 6 +- webview-ui/src/i18n/locales/hi/chat.json | 6 +- webview-ui/src/i18n/locales/id/chat.json | 6 +- webview-ui/src/i18n/locales/it/chat.json | 6 +- webview-ui/src/i18n/locales/ja/chat.json | 6 +- webview-ui/src/i18n/locales/ko/chat.json | 6 +- webview-ui/src/i18n/locales/nl/chat.json | 6 +- webview-ui/src/i18n/locales/pl/chat.json | 6 +- webview-ui/src/i18n/locales/pt-BR/chat.json | 6 +- webview-ui/src/i18n/locales/ru/chat.json | 6 +- webview-ui/src/i18n/locales/tr/chat.json | 6 +- webview-ui/src/i18n/locales/vi/chat.json | 6 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 6 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 6 +- 32 files changed, 1152 insertions(+), 19 deletions(-) create mode 100644 webview-ui/src/components/chat/ChatModelSelector.tsx create mode 100644 webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx create mode 100644 webview-ui/src/components/chat/hooks/__tests__/useChatModelSelector.spec.tsx create mode 100644 webview-ui/src/components/chat/hooks/useChatModelSelector.ts diff --git a/webview-ui/src/components/chat/ChatModelSelector.tsx b/webview-ui/src/components/chat/ChatModelSelector.tsx new file mode 100644 index 0000000000..7cd3450e01 --- /dev/null +++ b/webview-ui/src/components/chat/ChatModelSelector.tsx @@ -0,0 +1,172 @@ +import { useMemo, useState, useCallback } from "react" + +import { cn } from "@/lib/utils" +import { useRooPortal } from "@/components/ui/hooks/useRooPortal" +import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui" +import { useAppTranslation } from "@/i18n/TranslationContext" +import { vscode } from "@/utils/vscode" +import { useExtensionState } from "@/context/ExtensionStateContext" +import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel" +import { filterModels } from "@/components/settings/utils/organizationFilters" +import { useChatModelSelector } from "./hooks/useChatModelSelector" + +interface ChatModelSelectorProps { + disabled?: boolean + title: string + triggerClassName?: string +} + +export const ChatModelSelector = ({ disabled = false, title, triggerClassName = "" }: ChatModelSelectorProps) => { + const { t } = useAppTranslation() + const { apiConfiguration, organizationAllowList, currentApiConfigName } = useExtensionState() + const { id: selectedModelId } = useSelectedModel(apiConfiguration) + const { provider, models, modelIdKey, defaultModelId, isLoading, valueTransform, displayTransform } = + useChatModelSelector() + + const [open, setOpen] = useState(false) + const [searchValue, setSearchValue] = useState("") + const portalContainer = useRooPortal("roo-portal") + + // Filter deprecated models but always keep the currently selected one visible. + const modelIds = useMemo(() => { + const filteredModels = filterModels(models, provider, organizationAllowList) + const available = Object.entries(filteredModels ?? {}) + .filter(([modelId, modelInfo]) => { + if (modelId === selectedModelId) return true + return !modelInfo.deprecated + }) + .map(([modelId]) => modelId) + .sort((a, b) => a.localeCompare(b)) + return available + }, [models, provider, organizationAllowList, selectedModelId]) + + // Resolve the display value (custom transform for compound config values like VSCode LM). + const displayValue = useMemo(() => { + if (displayTransform && modelIdKey) { + const storedValue = apiConfiguration?.[modelIdKey] + return storedValue ? displayTransform(storedValue) : undefined + } + return selectedModelId || (searchValue ? searchValue : undefined) + }, [displayTransform, modelIdKey, apiConfiguration, selectedModelId, searchValue]) + + const filteredModelIds = useMemo(() => { + if (!searchValue) return modelIds + const q = searchValue.toLowerCase() + return modelIds.filter((id) => id.toLowerCase().includes(q)) + }, [modelIds, searchValue]) + + const onSelect = useCallback( + (modelId: string) => { + if (!modelId || !modelIdKey || !apiConfiguration) { + return + } + + setOpen(false) + setSearchValue("") + + // Transform the model id for storage if needed (e.g. VSCode LM selector object). + const valueToStore = valueTransform ? valueTransform(modelId) : modelId + + // Persist the change to the current API configuration profile. The + // backend will save the profile, activate it and broadcast the + // updated apiConfiguration back to the webview. + vscode.postMessage({ + type: "upsertApiConfiguration", + text: currentApiConfigName, + apiConfiguration: { + ...apiConfiguration, + [modelIdKey]: valueToStore, + }, + }) + }, + [modelIdKey, apiConfiguration, valueTransform, currentApiConfigName], + ) + + const onClearSearch = useCallback(() => { + setSearchValue("") + }, []) + + return ( + + + + + {isLoading ? "…" : displayValue || defaultModelId || t("chat:selectModel")} + + + + +
+
+ setSearchValue(e.target.value)} + placeholder={t("chat:searchModel")} + className="w-full h-8 px-2 py-1 text-xs bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded focus:outline-0" + autoFocus + /> + {searchValue.length > 0 && ( +
+ +
+ )} +
+ + {modelIds.length === 0 && !isLoading && ( +
{t("chat:modelListEmpty")}
+ )} + + {modelIds.length > 0 && ( +
+ {filteredModelIds.map((modelId) => ( +
onSelect(modelId)} + data-testid={`chat-model-option-${modelId}`} + className={cn( + "px-3 py-1.5 text-sm cursor-pointer flex items-center gap-2", + "hover:bg-vscode-list-hoverBackground", + modelId === displayValue && + "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground", + )}> + + {modelId} + + {modelId === displayValue && } +
+ ))} +
+ )} + + {searchValue && !modelIds.includes(searchValue) && ( +
onSelect(searchValue)} + className="px-3 py-1.5 text-sm cursor-pointer hover:bg-vscode-list-hoverBackground border-t border-vscode-input-border"> + {t("chat:useCustomModel", { modelId: searchValue })} +
+ )} +
+
+
+ ) +} diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 2c539766fe..27150a9e92 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -27,6 +27,7 @@ import { StandardTooltip } from "@src/components/ui" import Thumbnails from "../common/Thumbnails" import { ModeSelector } from "./ModeSelector" import { ApiConfigSelector } from "./ApiConfigSelector" +import { ChatModelSelector } from "./ChatModelSelector" import { AutoApproveDropdown } from "./AutoApproveDropdown" import { MAX_IMAGES_PER_MESSAGE } from "./constants" import ContextMenu from "./ContextMenu" @@ -1319,6 +1320,11 @@ export const ChatTextArea = forwardRef( lockApiConfigAcrossModes={!!lockApiConfigAcrossModes} onToggleLockApiConfig={handleToggleLockApiConfig} /> +
diff --git a/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx new file mode 100644 index 0000000000..a819971bcb --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx @@ -0,0 +1,179 @@ +import type { ReactNode } from "react" +import { render, screen, fireEvent } from "@/utils/test-utils" +import { providerIdentifiers } from "@roo-code/types" +import { vscode } from "@/utils/vscode" + +import { ChatModelSelector } from "../ChatModelSelector" +import { useChatModelSelector } from "../hooks/useChatModelSelector" + +// Mock the dependencies +vi.mock("@/utils/vscode", () => ({ + vscode: { + postMessage: vi.fn(), + }, +})) + +vi.mock("@/i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ + t: (key: string) => key, + }), +})) + +vi.mock("@/components/ui/hooks/useRooPortal", () => ({ + useRooPortal: () => document.body, +})) + +// Mock the ExtensionStateContext +vi.mock("@/context/ExtensionStateContext", () => ({ + useExtensionState: () => ({ + apiConfiguration: { + apiProvider: providerIdentifiers.anthropic, + apiModelId: "claude-opus-4-20250514", + }, + organizationAllowList: { allowAll: true, providers: {} }, + currentApiConfigName: "default", + }), +})) + +// Mock useSelectedModel +vi.mock("@/components/ui/hooks/useSelectedModel", () => ({ + useSelectedModel: () => ({ id: "claude-opus-4-20250514", info: undefined }), +})) + +// Mock useChatModelSelector hook +vi.mock("../hooks/useChatModelSelector", () => ({ + useChatModelSelector: vi.fn(), +})) + +// Mock Popover components to be testable +vi.mock("@/components/ui", () => { + type PopoverProps = { children: ReactNode; open?: boolean; onOpenChange?: (open: boolean) => void } + type TriggerProps = { + children: ReactNode + disabled?: boolean + className?: string + onClick?: () => void + } + return { + Popover: ({ children, open }: PopoverProps) => ( +
+ {children} +
+ ), + PopoverTrigger: ({ children, disabled, className, onClick }: TriggerProps) => ( + + ), + PopoverContent: ({ children }: { children: ReactNode }) =>
{children}
, + StandardTooltip: ({ children }: { children: ReactNode }) => <>{children}, + } +}) + +const mockUseChatModelSelector = useChatModelSelector as ReturnType + +const anthropicModels = { + "claude-opus-4-20250514": { + maxTokens: 32000, + contextWindow: 200000, + supportsPromptCache: true, + supportsImages: true, + }, + "claude-sonnet-4-20250514": { + maxTokens: 32000, + contextWindow: 200000, + supportsPromptCache: true, + supportsImages: true, + }, +} + +describe("ChatModelSelector", () => { + const defaultProps = { + title: "Select model", + } + + beforeEach(() => { + vi.clearAllMocks() + mockUseChatModelSelector.mockReturnValue({ + provider: providerIdentifiers.anthropic, + models: anthropicModels, + modelIdKey: "apiModelId", + defaultModelId: "claude-opus-4-20250514", + isLoading: false, + }) + }) + + test("renders the trigger with the current model id", () => { + render() + + const trigger = screen.getByTestId("chat-model-selector-trigger") + expect(trigger).toBeInTheDocument() + expect(trigger).toHaveTextContent("claude-opus-4-20250514") + }) + + test("disables the trigger when disabled prop is true", () => { + render() + + const trigger = screen.getByTestId("chat-model-selector-trigger") + expect(trigger).toBeDisabled() + }) + + test("renders model list when opened", () => { + render() + + const trigger = screen.getByTestId("chat-model-selector-trigger") + fireEvent.click(trigger) + + // The mocked Popover always renders content, so the models are visible. + // Use testids instead of text queries because the selected model id also + // appears in the trigger label. + expect(screen.getByTestId("chat-model-option-claude-opus-4-20250514")).toBeInTheDocument() + expect(screen.getByTestId("chat-model-option-claude-sonnet-4-20250514")).toBeInTheDocument() + }) + + test("selecting a model posts upsertApiConfiguration message", () => { + render() + + const trigger = screen.getByTestId("chat-model-selector-trigger") + fireEvent.click(trigger) + + const option = screen.getByTestId("chat-model-option-claude-sonnet-4-20250514") + fireEvent.click(option) + + expect(vscode.postMessage).toHaveBeenCalledWith({ + type: "upsertApiConfiguration", + text: "default", + apiConfiguration: { + apiProvider: providerIdentifiers.anthropic, + apiModelId: "claude-sonnet-4-20250514", + }, + }) + }) + + test("supports custom model via search", () => { + render() + + const trigger = screen.getByTestId("chat-model-selector-trigger") + fireEvent.click(trigger) + + // Type in the search box (present in the always-rendered PopoverContent) + const searchInput = screen.getByPlaceholderText("chat:searchModel") + fireEvent.change(searchInput, { target: { value: "claude-3-5-haiku" } }) + + const customOption = screen.getByTestId("chat-model-use-custom") + fireEvent.click(customOption) + + expect(vscode.postMessage).toHaveBeenCalledWith({ + type: "upsertApiConfiguration", + text: "default", + apiConfiguration: { + apiProvider: providerIdentifiers.anthropic, + apiModelId: "claude-3-5-haiku", + }, + }) + }) +}) diff --git a/webview-ui/src/components/chat/__tests__/ChatTextArea.visual.tsx b/webview-ui/src/components/chat/__tests__/ChatTextArea.visual.tsx index 9c447fe011..6c955767f8 100644 --- a/webview-ui/src/components/chat/__tests__/ChatTextArea.visual.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatTextArea.visual.tsx @@ -17,7 +17,7 @@ for (const theme of visualThemes) { await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur()) for ( let index = 0; - index < 10 && !(await editor.evaluate((element) => element === document.activeElement)); + index < 50 && !(await editor.evaluate((element) => element === document.activeElement)); index++ ) { await page.keyboard.press("Tab") diff --git a/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-focus-dark.png b/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-focus-dark.png index 4de566f736bcf2ebeb288f4d8eaea9b509255c29..9bab7a3a7e705ad8db2ee66651c50392994f2613 100644 GIT binary patch literal 9949 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 literal 9166 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 diff --git a/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-focus-high-contrast-light.png b/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-focus-high-contrast-light.png index f5c759fad8546da967cdbb07f93b034d78d293be..8e9c01c7fef69c4a6aa08b4c796493338c671b8d 100644 GIT binary patch literal 10270 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 literal 9606 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 G2L3Of2;d|D diff --git a/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-focus-high-contrast.png b/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-focus-high-contrast.png index 465ffc0263d816e953f37275f9ceb514223f33bf..8d232cb2ab90a08f637f54576ea195ada3ad1802 100644 GIT binary patch literal 10477 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�T)Xjeo%zI=0Jw}Nu$swu0L3TL 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 L@}d>O`houi$=aJZ diff --git a/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-focus-light.png b/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-focus-light.png index 1f238a4619657763f15f05e20656ec0f08a03eb9..b6976d6cff55dab62a17382b98838d18e49ba732 100644 GIT binary patch literal 9973 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

b3tsdW2#AyUx(s?ztAX#V`YOaQpN7 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 diff --git a/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-resting-dark.png b/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-resting-dark.png index fad9aa30b923ada5cbd0e6c1f1a5df83319b1d39..6c90b6f6102d1593f3dd15421757624b6cc94313 100644 GIT binary patch literal 9945 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 CivmCZ diff --git a/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-resting-high-contrast-light.png b/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-resting-high-contrast-light.png index f5c759fad8546da967cdbb07f93b034d78d293be..8e9c01c7fef69c4a6aa08b4c796493338c671b8d 100644 GIT binary patch literal 10270 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 literal 9606 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 G2L3Of2;d|D diff --git a/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-resting-high-contrast.png b/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-resting-high-contrast.png index 465ffc0263d816e953f37275f9ceb514223f33bf..8d232cb2ab90a08f637f54576ea195ada3ad1802 100644 GIT binary patch literal 10477 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�T)Xjeo%zI=0Jw}Nu$swu0L3TL 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 L@}d>O`houi$=aJZ diff --git a/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-resting-light.png b/webview-ui/src/components/chat/__tests__/__screenshots__/chat-composer-resting-light.png index 1f238a4619657763f15f05e20656ec0f08a03eb9..b6976d6cff55dab62a17382b98838d18e49ba732 100644 GIT binary patch literal 9973 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

b3tsdW2#AyUx(s?ztAX#V`YOaQpN7 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 diff --git a/webview-ui/src/components/chat/hooks/__tests__/useChatModelSelector.spec.tsx b/webview-ui/src/components/chat/hooks/__tests__/useChatModelSelector.spec.tsx new file mode 100644 index 0000000000..6f19925992 --- /dev/null +++ b/webview-ui/src/components/chat/hooks/__tests__/useChatModelSelector.spec.tsx @@ -0,0 +1,337 @@ +import React from "react" +import { act, renderHook, waitFor } from "@testing-library/react" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" + +import { providerIdentifiers, retiredProviderIdentifiers } from "@roo-code/types" + +import { useChatModelSelector } from "../useChatModelSelector" +import { useExtensionState } from "@/context/ExtensionStateContext" +import { useRouterModels } from "@/components/ui/hooks/useRouterModels" +import { vscode } from "@/utils/vscode" + +vi.mock("@/context/ExtensionStateContext", () => ({ + useExtensionState: vi.fn(), +})) + +vi.mock("@/components/ui/hooks/useRouterModels", () => ({ + useRouterModels: vi.fn(), +})) + +vi.mock("@/utils/vscode", () => ({ + vscode: { + postMessage: vi.fn(), + }, +})) + +const mockUseExtensionState = useExtensionState as ReturnType +const mockUseRouterModels = useRouterModels as ReturnType +const mockPostMessage = vscode.postMessage as ReturnType + +const wrapper = ({ children }: { children: React.ReactNode }) => { + const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + return {children} +} + +const emitMessage = (message: unknown) => { + window.dispatchEvent(new MessageEvent("message", { data: message })) +} + +describe("useChatModelSelector", () => { + beforeEach(() => { + vi.clearAllMocks() + mockUseRouterModels.mockReturnValue({ + data: undefined, + isLoading: false, + isError: false, + }) + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.anthropic, apiModelId: "claude-opus-4-20250514" }, + routerModels: undefined, + }) + }) + + describe("static providers", () => { + it("returns static models for anthropic with apiModelId key", () => { + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.provider).toBe(providerIdentifiers.anthropic) + expect(result.current.modelIdKey).toBe("apiModelId") + expect(result.current.models).not.toBeNull() + expect(Object.keys(result.current.models!)).toContain("claude-opus-4-20250514") + expect(result.current.defaultModelId).toBeTruthy() + }) + + it("does not request message-based models for static providers", () => { + renderHook(() => useChatModelSelector(), { wrapper }) + + expect(mockPostMessage).not.toHaveBeenCalled() + }) + }) + + describe("zoo-gateway (dynamic router provider)", () => { + it("reads zoo-gateway models from the react-query routerModels", () => { + mockUseRouterModels.mockReturnValue({ + data: { + [providerIdentifiers.zooGateway]: { + "anthropic/claude-sonnet-4": { maxTokens: 1, contextWindow: 1 }, + }, + }, + isLoading: false, + isError: false, + }) + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.zooGateway, + zooGatewayModelId: "anthropic/claude-sonnet-4", + }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("zooGatewayModelId") + expect(result.current.defaultModelId).toBeTruthy() + expect(Object.keys(result.current.models!)).toEqual(["anthropic/claude-sonnet-4"]) + }) + }) + + describe("opencode-go (dynamic router provider)", () => { + it("reads opencode-go models from the react-query routerModels", () => { + mockUseRouterModels.mockReturnValue({ + data: { [providerIdentifiers.opencodeGo]: { "glm-5.2": { maxTokens: 1, contextWindow: 1 } } }, + isLoading: false, + isError: false, + }) + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.opencodeGo, opencodeGoModelId: "glm-5.2" }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("opencodeGoModelId") + expect(result.current.defaultModelId).toBeTruthy() + expect(Object.keys(result.current.models!)).toEqual(["glm-5.2"]) + }) + }) + + describe("kenari (dynamic router provider)", () => { + it("reads kenari models from the react-query routerModels", () => { + mockUseRouterModels.mockReturnValue({ + data: { [providerIdentifiers.kenari]: { "glm-5-2": { maxTokens: 1, contextWindow: 1 } } }, + isLoading: false, + isError: false, + }) + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.kenari, kenariModelId: "glm-5-2" }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("kenariModelId") + expect(result.current.defaultModelId).toBeTruthy() + expect(Object.keys(result.current.models!)).toEqual(["glm-5-2"]) + }) + }) + + describe("nanogpt (dynamic router provider)", () => { + it("reads nanogpt models from the react-query routerModels", () => { + mockUseRouterModels.mockReturnValue({ + data: { [providerIdentifiers.nanogpt]: { "openai/gpt-5.6-sol": { maxTokens: 1, contextWindow: 1 } } }, + isLoading: false, + isError: false, + }) + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.nanogpt, nanoGptModelId: "openai/gpt-5.6-sol" }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("nanoGptModelId") + expect(result.current.defaultModelId).toBeTruthy() + expect(Object.keys(result.current.models!)).toEqual(["openai/gpt-5.6-sol"]) + }) + }) + + describe("openai (OpenAI compatible)", () => { + it("requests openAi models on mount when baseUrl and apiKey are set", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.openai, + openAiBaseUrl: "https://api.example.com/v1", + openAiApiKey: "test-key", + openAiHeaders: {}, + }, + routerModels: undefined, + }) + + renderHook(() => useChatModelSelector(), { wrapper }) + + expect(mockPostMessage).toHaveBeenCalledWith({ + type: "requestOpenAiModels", + values: { + baseUrl: "https://api.example.com/v1", + apiKey: "test-key", + customHeaders: {}, + openAiHeaders: {}, + }, + }) + }) + + it("uses models delivered through the openAiModels message", async () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.openai, + openAiBaseUrl: "https://api.example.com/v1", + openAiApiKey: "test-key", + openAiHeaders: {}, + }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + act(() => { + emitMessage({ type: "openAiModels", openAiModels: ["gpt-4o", "gpt-4o-mini"] }) + }) + + await waitFor(() => { + expect(result.current.models).not.toBeNull() + }) + + expect(Object.keys(result.current.models!)).toEqual(expect.arrayContaining(["gpt-4o", "gpt-4o-mini"])) + expect(result.current.modelIdKey).toBe("openAiModelId") + }) + }) + + describe("router providers", () => { + it("reads openrouter models from the react-query routerModels", () => { + mockUseRouterModels.mockReturnValue({ + data: { [providerIdentifiers.openrouter]: { "openai/gpt-4o": { maxTokens: 1, contextWindow: 1 } } }, + isLoading: false, + isError: false, + }) + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.openrouter, openRouterModelId: "openai/gpt-4o" }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("openRouterModelId") + expect(Object.keys(result.current.models!)).toEqual(["openai/gpt-4o"]) + }) + + it("reads poe models from the backend-broadcast state routerModels", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.poe, poeApiKey: "test" }, + routerModels: { [providerIdentifiers.poe]: { "claude-sonnet": { maxTokens: 1, contextWindow: 1 } } }, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("apiModelId") + expect(Object.keys(result.current.models!)).toEqual(["claude-sonnet"]) + }) + + it("reads litellm models from the backend-broadcast state routerModels", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.litellm, + litellmApiKey: "test", + litellmBaseUrl: "http://x", + }, + routerModels: { [providerIdentifiers.litellm]: { "gpt-4o": { maxTokens: 1, contextWindow: 1 } } }, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("litellmModelId") + expect(Object.keys(result.current.models!)).toEqual(["gpt-4o"]) + }) + }) + + describe("ollama / lmstudio / vscode-lm", () => { + it("requests ollama models on mount", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.ollama, ollamaModelId: "llama3" }, + routerModels: undefined, + }) + + renderHook(() => useChatModelSelector(), { wrapper }) + + expect(mockPostMessage).toHaveBeenCalledWith({ type: "requestOllamaModels" }) + }) + + it("uses models delivered through the ollamaModels message", async () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.ollama, ollamaModelId: "llama3" }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + act(() => { + emitMessage({ type: "ollamaModels", ollamaModels: { llama3: { maxTokens: 1, contextWindow: 1 } } }) + }) + + await waitFor(() => { + expect(result.current.models).not.toBeNull() + }) + expect(result.current.modelIdKey).toBe("ollamaModelId") + expect(Object.keys(result.current.models!)).toEqual(["llama3"]) + }) + + it("requests lmstudio models on mount", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.lmstudio, lmStudioModelId: "local-model" }, + routerModels: undefined, + }) + + renderHook(() => useChatModelSelector(), { wrapper }) + + expect(mockPostMessage).toHaveBeenCalledWith({ type: "requestLmStudioModels" }) + }) + + it("requests vsCodeLm models on mount and builds model records", async () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.vscodeLm }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(mockPostMessage).toHaveBeenCalledWith({ type: "requestVsCodeLmModels" }) + + act(() => { + emitMessage({ + type: "vsCodeLmModels", + vsCodeLmModels: [{ vendor: "copilot", family: "gpt-4o" }], + }) + }) + + await waitFor(() => { + expect(result.current.models).not.toBeNull() + }) + expect(Object.keys(result.current.models!)).toEqual(["copilot/gpt-4o"]) + expect(result.current.modelIdKey).toBe("vsCodeLmModelSelector") + }) + }) + + describe("retired providers", () => { + it("returns empty data for retired providers", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: retiredProviderIdentifiers.groq as unknown as "openrouter" }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.provider).toBeUndefined() + expect(result.current.models).toBeNull() + expect(result.current.modelIdKey).toBeUndefined() + }) + }) +}) diff --git a/webview-ui/src/components/chat/hooks/useChatModelSelector.ts b/webview-ui/src/components/chat/hooks/useChatModelSelector.ts new file mode 100644 index 0000000000..07cfbbe77f --- /dev/null +++ b/webview-ui/src/components/chat/hooks/useChatModelSelector.ts @@ -0,0 +1,367 @@ +import { useCallback, useEffect, useMemo, useState } from "react" +import { useEvent } from "react-use" + +import { + type ProviderName, + type ProviderSettings, + type ModelInfo, + type ModelRecord, + type ExtensionMessage, + type LanguageModelChatSelector, + providerIdentifiers, + openAiModelInfoSaneDefaults, + anthropicDefaultModelId, + bedrockDefaultModelId, + deepSeekDefaultModelId, + moonshotDefaultModelId, + geminiDefaultModelId, + mistralDefaultModelId, + openAiNativeDefaultModelId, + qwenCodeDefaultModelId, + vertexDefaultModelId, + xaiDefaultModelId, + sambaNovaDefaultModelId, + internationalZAiDefaultModelId, + mainlandZAiDefaultModelId, + fireworksDefaultModelId, + friendliDefaultModelId, + minimaxDefaultModelId, + mimoDefaultModelId, + basetenDefaultModelId, + openRouterDefaultModelId, + requestyDefaultModelId, + unboundDefaultModelId, + litellmDefaultModelId, + vercelAiGatewayDefaultModelId, + zooGatewayDefaultModelId, + opencodeGoDefaultModelId, + kenariDefaultModelId, + nanoGptDefaultModelId, + kimiCodeDefaultModelId, + poeDefaultModelId, + isRetiredProvider, +} from "@roo-code/types" + +import { useRouterModels } from "@/components/ui/hooks/useRouterModels" +import { getStaticModelsForProvider } from "@/components/settings/utils/providerModelConfig" +import { MODELS_BY_PROVIDER } from "@/components/settings/constants" +import { useExtensionState } from "@/context/ExtensionStateContext" +import { vscode } from "@/utils/vscode" + +type ModelIdKey = keyof Pick< + ProviderSettings, + | "openRouterModelId" + | "requestyModelId" + | "unboundModelId" + | "openAiModelId" + | "litellmModelId" + | "vercelAiGatewayModelId" + | "zooGatewayModelId" + | "opencodeGoModelId" + | "kenariModelId" + | "nanoGptModelId" + | "apiModelId" + | "ollamaModelId" + | "lmStudioModelId" + | "vsCodeLmModelSelector" +> + +const STATIC_DEFAULT_MODEL_IDS: Partial> = { + [providerIdentifiers.anthropic]: anthropicDefaultModelId, + [providerIdentifiers.bedrock]: bedrockDefaultModelId, + [providerIdentifiers.deepseek]: deepSeekDefaultModelId, + [providerIdentifiers.moonshot]: moonshotDefaultModelId, + [providerIdentifiers.gemini]: geminiDefaultModelId, + [providerIdentifiers.mistral]: mistralDefaultModelId, + [providerIdentifiers.openaiNative]: openAiNativeDefaultModelId, + [providerIdentifiers.qwenCode]: qwenCodeDefaultModelId, + [providerIdentifiers.vertex]: vertexDefaultModelId, + [providerIdentifiers.xai]: xaiDefaultModelId, + [providerIdentifiers.sambanova]: sambaNovaDefaultModelId, + [providerIdentifiers.zai]: internationalZAiDefaultModelId, + [providerIdentifiers.fireworks]: fireworksDefaultModelId, + [providerIdentifiers.friendli]: friendliDefaultModelId, + [providerIdentifiers.minimax]: minimaxDefaultModelId, + [providerIdentifiers.mimo]: mimoDefaultModelId, + [providerIdentifiers.baseten]: basetenDefaultModelId, +} + +const ROUTER_DEFAULT_MODEL_IDS: Partial> = { + [providerIdentifiers.openrouter]: openRouterDefaultModelId, + [providerIdentifiers.requesty]: requestyDefaultModelId, + [providerIdentifiers.unbound]: unboundDefaultModelId, + [providerIdentifiers.litellm]: litellmDefaultModelId, + [providerIdentifiers.vercelAiGateway]: vercelAiGatewayDefaultModelId, + [providerIdentifiers.zooGateway]: zooGatewayDefaultModelId, + [providerIdentifiers.opencodeGo]: opencodeGoDefaultModelId, + [providerIdentifiers.kenari]: kenariDefaultModelId, + [providerIdentifiers.nanogpt]: nanoGptDefaultModelId, + [providerIdentifiers.kimiCode]: kimiCodeDefaultModelId, + [providerIdentifiers.poe]: poeDefaultModelId, +} + +// Providers whose model list is delivered through a dedicated message event +// (mirrors the settings page provider components). +const MESSAGE_BASED_PROVIDERS: ProviderName[] = [ + providerIdentifiers.openai, + providerIdentifiers.ollama, + providerIdentifiers.lmstudio, + providerIdentifiers.vscodeLm, +] + +export interface ChatModelSelectorData { + /** The provider key used to determine model source (undefined for retired providers). */ + provider: ProviderName | undefined + /** Record of available models for the current provider (null until loaded or when N/A). */ + models: Record | null + /** The configuration field key that stores the selected model for this provider. */ + modelIdKey: ModelIdKey | undefined + /** The default model id for this provider. */ + defaultModelId: string + /** Whether the model list is still loading. */ + isLoading: boolean + /** Transform a selected model id into the stored configuration value (e.g. VSCode LM selector object). */ + valueTransform?: (modelId: string) => unknown + /** Transform the stored configuration value back to a display string (e.g. VSCode LM selector). */ + displayTransform?: (value: unknown) => string +} + +/** + * Resolves the model list, storage key and defaults for the currently active + * provider so the chat input bar can render a compact model picker. + * + * The data sources mirror the settings page (`ApiOptions` and the provider + * components) so the chat selector shows the exact same models: + * - Router providers (openrouter, requesty, unbound, vercel-ai-gateway, + * zoo-gateway, opencode-go, kenari, nanogpt, kimi-code): + * react-query `useRouterModels` request. + * - litellm / poe: backend-broadcast `routerModels` from extension state. + * - openai (OpenAI compatible), ollama, lmstudio, vscode-lm: request on mount + * and listen for the corresponding `*Models` message event. + * - Static providers: `getStaticModelsForProvider`. + */ +export const useChatModelSelector = (): ChatModelSelectorData => { + const { apiConfiguration, routerModels: stateRouterModels } = useExtensionState() + + const provider = (apiConfiguration?.apiProvider || providerIdentifiers.openrouter) as ProviderName + const activeProvider = isRetiredProvider(provider) ? undefined : provider + + // Router providers are fetched through react-query (mirrors ApiOptions). + const routerModels = useRouterModels({ + provider: activeProvider, + enabled: !!activeProvider && ROUTER_DEFAULT_MODEL_IDS[activeProvider] !== undefined, + }) + + // Message-based providers: request the models on mount and keep the + // latest list delivered by the backend. + const [openAiModels, setOpenAiModels] = useState([]) + const [ollamaModels, setOllamaModels] = useState({}) + const [lmStudioModels, setLmStudioModels] = useState({}) + const [vsCodeLmModels, setVsCodeLmModels] = useState([]) + + const onMessage = useCallback((event: MessageEvent) => { + const message: ExtensionMessage = event.data + switch (message.type) { + case "openAiModels": + setOpenAiModels(message.openAiModels ?? []) + break + case "ollamaModels": + setOllamaModels(message.ollamaModels ?? {}) + break + case "lmStudioModels": + setLmStudioModels(message.lmStudioModels ?? {}) + break + case "vsCodeLmModels": + setVsCodeLmModels(message.vsCodeLmModels ?? []) + break + } + }, []) + useEvent("message", onMessage) + + // Request models on mount when a message-based provider is active + // (mirrors Ollama.tsx / LMStudio.tsx / OpenAICompatible.tsx behaviors). + useEffect(() => { + if (!activeProvider || !MESSAGE_BASED_PROVIDERS.includes(activeProvider)) { + return + } + + switch (activeProvider) { + case providerIdentifiers.openai: + if (apiConfiguration?.openAiBaseUrl && apiConfiguration?.openAiApiKey) { + vscode.postMessage({ + type: "requestOpenAiModels", + values: { + baseUrl: apiConfiguration.openAiBaseUrl, + apiKey: apiConfiguration.openAiApiKey, + customHeaders: {}, + openAiHeaders: apiConfiguration.openAiHeaders ?? {}, + }, + }) + } + break + case providerIdentifiers.ollama: + vscode.postMessage({ type: "requestOllamaModels" }) + break + case providerIdentifiers.lmstudio: + vscode.postMessage({ type: "requestLmStudioModels" }) + break + case providerIdentifiers.vscodeLm: + vscode.postMessage({ type: "requestVsCodeLmModels" }) + break + } + }, [ + activeProvider, + apiConfiguration?.openAiBaseUrl, + apiConfiguration?.openAiApiKey, + apiConfiguration?.openAiHeaders, + ]) + + // Map provider -> config field key + model list + default id. + const result = useMemo(() => { + if (!activeProvider) { + return { + provider: undefined, + models: null, + modelIdKey: undefined, + defaultModelId: "", + isLoading: false, + } + } + + const defaultModelId = + (activeProvider === providerIdentifiers.zai && apiConfiguration?.zaiApiLine === "china_coding" + ? mainlandZAiDefaultModelId + : (ROUTER_DEFAULT_MODEL_IDS[activeProvider] ?? STATIC_DEFAULT_MODEL_IDS[activeProvider] ?? "")) ?? "" + + let models: Record | null = null + let modelIdKey: ModelIdKey | undefined = undefined + let valueTransform: ((modelId: string) => unknown) | undefined + let displayTransform: ((value: unknown) => string) | undefined + let isLoading = false + + switch (activeProvider) { + case providerIdentifiers.openrouter: + models = routerModels.data?.openrouter ?? null + modelIdKey = "openRouterModelId" + break + case providerIdentifiers.requesty: + models = routerModels.data?.requesty ?? null + modelIdKey = "requestyModelId" + break + case providerIdentifiers.unbound: + models = routerModels.data?.unbound ?? null + modelIdKey = "unboundModelId" + break + case providerIdentifiers.litellm: + // The settings page reads litellm models from the backend + // broadcast cache (stateRouterModels), not from react-query. + models = stateRouterModels?.litellm ?? null + modelIdKey = "litellmModelId" + break + case providerIdentifiers.vercelAiGateway: + models = routerModels.data?.[providerIdentifiers.vercelAiGateway] ?? null + modelIdKey = "vercelAiGatewayModelId" + break + case providerIdentifiers.zooGateway: + models = routerModels.data?.[providerIdentifiers.zooGateway] ?? null + modelIdKey = "zooGatewayModelId" + break + case providerIdentifiers.opencodeGo: + models = routerModels.data?.[providerIdentifiers.opencodeGo] ?? null + modelIdKey = "opencodeGoModelId" + break + case providerIdentifiers.kenari: + models = routerModels.data?.[providerIdentifiers.kenari] ?? null + modelIdKey = "kenariModelId" + break + case providerIdentifiers.nanogpt: + models = routerModels.data?.[providerIdentifiers.nanogpt] ?? null + modelIdKey = "nanoGptModelId" + break + case providerIdentifiers.kimiCode: + // Kimi Code stores its model id in apiModelId (mirrors useSelectedModel). + models = routerModels.data?.[providerIdentifiers.kimiCode] ?? null + modelIdKey = "apiModelId" + break + case providerIdentifiers.poe: + // Same as litellm: poe uses the backend broadcast cache. + models = stateRouterModels?.poe ?? null + modelIdKey = "apiModelId" + break + case providerIdentifiers.openai: + // OpenAI Compatible: the list is fetched from the baseUrl via + // `requestOpenAiModels` and delivered through `openAiModels`. + models = + Object.keys(openAiModels).length > 0 + ? Object.fromEntries(openAiModels.map((item) => [item, openAiModelInfoSaneDefaults])) + : null + modelIdKey = "openAiModelId" + break + case providerIdentifiers.ollama: + models = Object.keys(ollamaModels).length > 0 ? ollamaModels : null + modelIdKey = "ollamaModelId" + break + case providerIdentifiers.lmstudio: + models = Object.keys(lmStudioModels).length > 0 ? lmStudioModels : null + modelIdKey = "lmStudioModelId" + break + case providerIdentifiers.vscodeLm: + models = vsCodeLmModels.reduce( + (acc, model) => { + const modelId = `${model.vendor}/${model.family}` + acc[modelId] = { + maxTokens: 0, + contextWindow: 0, + supportsPromptCache: false, + description: `${model.vendor} - ${model.family}`, + } + return acc + }, + {} as Record, + ) + modelIdKey = "vsCodeLmModelSelector" + valueTransform = (modelId) => { + const [vendor, family] = modelId.split("/") + return { vendor, family } + } + displayTransform = (value) => { + if (!value) return "" + const selector = value as { vendor?: string; family?: string } + return selector.vendor && selector.family ? `${selector.vendor}/${selector.family}` : "" + } + break + default: + // Static models providers (anthropic, bedrock, gemini, etc.). + models = MODELS_BY_PROVIDER[activeProvider] ? getStaticModelsForProvider(activeProvider) : null + modelIdKey = "apiModelId" + } + + isLoading = + (ROUTER_DEFAULT_MODEL_IDS[activeProvider] !== undefined && routerModels.isLoading) || + (activeProvider === providerIdentifiers.openai && + !!apiConfiguration?.openAiBaseUrl && + !!apiConfiguration?.openAiApiKey && + openAiModels.length === 0) + + return { + provider: activeProvider, + models, + modelIdKey, + defaultModelId, + isLoading, + valueTransform, + displayTransform, + } + }, [ + activeProvider, + apiConfiguration, + routerModels, + stateRouterModels, + openAiModels, + ollamaModels, + lmStudioModels, + vsCodeLmModels, + ]) + + return result +} diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 687d9962a3..5b8c042790 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -495,5 +495,9 @@ "title": "Proveïdor ja no compatible", "message": "Aquest proveïdor ja no està disponible. Selecciona un proveïdor compatible per continuar.", "openSettings": "Obrir configuració" - } + }, + "searchModel": "Cerca models...", + "modelListEmpty": "Aquest proveïdor no té models disponibles", + "useCustomModel": "Utilitza el model personalitzat: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index e862fa9564..ea5b4ffd62 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -495,5 +495,9 @@ "title": "Anbieter wird nicht mehr unterstützt", "message": "Dieser Anbieter ist nicht mehr verfügbar. Wähle einen unterstützten Anbieter aus, um fortzufahren.", "openSettings": "Einstellungen öffnen" - } + }, + "searchModel": "Modelle durchsuchen...", + "modelListEmpty": "Für diesen Anbieter sind keine Modelle verfügbar", + "useCustomModel": "Benutzerdefiniertes Modell verwenden: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index cc02656a30..2541fb98d4 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -483,5 +483,9 @@ "title": "Provider no longer supported", "message": "This provider is no longer available. Select a supported provider to continue.", "openSettings": "Open Settings" - } + }, + "searchModel": "Search models...", + "modelListEmpty": "No models available for this provider", + "useCustomModel": "Use custom model: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 937cb0cf56..01aff817c9 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -495,5 +495,9 @@ "title": "Proveedor ya no compatible", "message": "Este proveedor ya no está disponible. Selecciona un proveedor compatible para continuar.", "openSettings": "Abrir configuración" - } + }, + "searchModel": "Buscar modelos...", + "modelListEmpty": "No hay modelos disponibles para este proveedor", + "useCustomModel": "Usar modelo personalizado: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 4656d618a7..df937ecfea 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -495,5 +495,9 @@ "title": "Fournisseur plus pris en charge", "message": "Ce fournisseur n'est plus disponible. Choisis un fournisseur pris en charge pour continuer.", "openSettings": "Ouvrir les paramètres" - } + }, + "searchModel": "Rechercher des modèles...", + "modelListEmpty": "Aucun modèle disponible pour ce fournisseur", + "useCustomModel": "Utiliser un modèle personnalisé : {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index c36bca4e0f..8bafad2daf 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -495,5 +495,9 @@ "title": "प्रदाता अब समर्थित नहीं है", "message": "यह प्रदाता अब उपलब्ध नहीं है। जारी रखने के लिए कोई समर्थित प्रदाता चुनें।", "openSettings": "सेटिंग्स खोलें" - } + }, + "searchModel": "मॉडल खोजें...", + "modelListEmpty": "इस प्रदाता के लिए कोई मॉडल उपलब्ध नहीं है", + "useCustomModel": "कस्टम मॉडल का उपयोग करें: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index d8a642b8ee..6a3fe89f24 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -501,5 +501,9 @@ "title": "Penyedia tidak lagi didukung", "message": "Penyedia ini sudah tidak tersedia. Pilih penyedia yang didukung untuk melanjutkan.", "openSettings": "Buka Pengaturan" - } + }, + "searchModel": "Cari model...", + "modelListEmpty": "Tidak ada model yang tersedia untuk penyedia ini", + "useCustomModel": "Gunakan model kustom: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 6ce80b2316..3f9b809c66 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -495,5 +495,9 @@ "title": "Provider non più supportato", "message": "Questo provider non è più disponibile. Seleziona un provider supportato per continuare.", "openSettings": "Apri impostazioni" - } + }, + "searchModel": "Cerca modelli...", + "modelListEmpty": "Nessun modello disponibile per questo provider", + "useCustomModel": "Usa modello personalizzato: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index f9e535ee89..0211190cd5 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -495,5 +495,9 @@ "title": "プロバイダーのサポート終了", "message": "このプロバイダーは現在利用できません。続行するには、サポートされているプロバイダーを選択してください。", "openSettings": "設定を開く" - } + }, + "searchModel": "モデルを検索...", + "modelListEmpty": "このプロバイダーで利用可能なモデルはありません", + "useCustomModel": "カスタムモデルを使用: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index a633602e26..8c4185593c 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -495,5 +495,9 @@ "title": "공급자 지원 종료", "message": "이 공급자는 더 이상 사용할 수 없습니다. 계속하려면 지원되는 공급자를 선택하세요.", "openSettings": "설정 열기" - } + }, + "searchModel": "모델 검색...", + "modelListEmpty": "이 공급자에 사용 가능한 모델이 없습니다", + "useCustomModel": "사용자 지정 모델 사용: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 67407b6b7b..bd92d05864 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -495,5 +495,9 @@ "title": "Provider wordt niet meer ondersteund", "message": "Deze provider is niet meer beschikbaar. Selecteer een ondersteunde provider om door te gaan.", "openSettings": "Instellingen openen" - } + }, + "searchModel": "Modellen zoeken...", + "modelListEmpty": "Geen modellen beschikbaar voor deze provider", + "useCustomModel": "Aangepast model gebruiken: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index f7f88dc87a..fba8b93446 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -495,5 +495,9 @@ "title": "Dostawca nie jest już obsługiwany", "message": "Ten dostawca nie jest już dostępny. Wybierz obsługiwanego dostawcę, aby kontynuować.", "openSettings": "Otwórz ustawienia" - } + }, + "searchModel": "Szukaj modeli...", + "modelListEmpty": "Brak dostępnych modeli dla tego dostawcy", + "useCustomModel": "Użyj niestandardowego modelu: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 1adf5870a8..a08d6bf04f 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -495,5 +495,9 @@ "title": "Provedor não é mais suportado", "message": "Este provedor não está mais disponível. Selecione um provedor compatível para continuar.", "openSettings": "Abrir configurações" - } + }, + "searchModel": "Pesquisar modelos...", + "modelListEmpty": "Nenhum modelo disponível para este provedor", + "useCustomModel": "Usar modelo personalizado: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index f42363c5f4..b79468065e 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -496,5 +496,9 @@ "title": "Провайдер больше не поддерживается", "message": "Этот провайдер больше недоступен. Выберите поддерживаемого провайдера, чтобы продолжить.", "openSettings": "Открыть настройки" - } + }, + "searchModel": "Поиск моделей...", + "modelListEmpty": "Нет доступных моделей для этого провайдера", + "useCustomModel": "Использовать пользовательскую модель: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 4d8ba6fead..bc10ad8643 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -496,5 +496,9 @@ "title": "Sağlayıcı artık desteklenmiyor", "message": "Bu sağlayıcı artık kullanılamıyor. Devam etmek için desteklenen bir sağlayıcı seçin.", "openSettings": "Ayarları aç" - } + }, + "searchModel": "Modelleri ara...", + "modelListEmpty": "Bu sağlayıcı için kullanılabilir model yok", + "useCustomModel": "Özel model kullan: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index d48351deac..41ffae05c8 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -496,5 +496,9 @@ "title": "Nhà cung cấp không còn được hỗ trợ", "message": "Nhà cung cấp này không còn khả dụng. Hãy chọn một nhà cung cấp được hỗ trợ để tiếp tục.", "openSettings": "Mở cài đặt" - } + }, + "searchModel": "Tìm kiếm mô hình...", + "modelListEmpty": "Không có mô hình nào khả dụng cho nhà cung cấp này", + "useCustomModel": "Sử dụng mô hình tùy chỉnh: {{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 00b0e3a85b..64420a6a49 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -496,5 +496,9 @@ "title": "供应商不再受支持", "message": "此提供商已不可用。请选择受支持的提供商以继续。", "openSettings": "打开设置" - } + }, + "searchModel": "搜索模型...", + "modelListEmpty": "此提供商没有可用的模型", + "useCustomModel": "使用自定义模型:{{modelId}}", + "selectModel": "Select a model" } diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index d04b1fb08b..80a15f1179 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -486,5 +486,9 @@ "title": "供應商不再受支援", "message": "此供應商已不可用。請選擇受支援的供應商以繼續。", "openSettings": "開啟設定" - } + }, + "searchModel": "搜尋模型...", + "modelListEmpty": "此提供者沒有可用的模型", + "useCustomModel": "使用自訂模型:{{modelId}}", + "selectModel": "Select a model" } From 2f6b25d399057a01f3210424368029e32dc64b7a Mon Sep 17 00:00:00 2001 From: seeones Date: Sun, 27 Sep 2026 11:54:24 +0800 Subject: [PATCH 02/12] test(webview): cover model selector provider/transform/clear/early-return paths --- .../chat/__tests__/ChatModelSelector.spec.tsx | 99 +++++++++++-- .../__tests__/useChatModelSelector.spec.tsx | 138 ++++++++++++++++++ 2 files changed, 228 insertions(+), 9 deletions(-) diff --git a/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx index a819971bcb..65642a1715 100644 --- a/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx @@ -23,16 +23,13 @@ vi.mock("@/components/ui/hooks/useRooPortal", () => ({ useRooPortal: () => document.body, })) -// Mock the ExtensionStateContext +// Mock the ExtensionStateContext (configurable per test) +const { mockUseExtensionState } = vi.hoisted(() => ({ + mockUseExtensionState: vi.fn(), +})) + vi.mock("@/context/ExtensionStateContext", () => ({ - useExtensionState: () => ({ - apiConfiguration: { - apiProvider: providerIdentifiers.anthropic, - apiModelId: "claude-opus-4-20250514", - }, - organizationAllowList: { allowAll: true, providers: {} }, - currentApiConfigName: "default", - }), + useExtensionState: (...args: unknown[]) => mockUseExtensionState(...args), })) // Mock useSelectedModel @@ -98,6 +95,14 @@ describe("ChatModelSelector", () => { beforeEach(() => { vi.clearAllMocks() + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.anthropic, + apiModelId: "claude-opus-4-20250514", + }, + organizationAllowList: { allowAll: true, providers: {} }, + currentApiConfigName: "default", + }) mockUseChatModelSelector.mockReturnValue({ provider: providerIdentifiers.anthropic, models: anthropicModels, @@ -176,4 +181,80 @@ describe("ChatModelSelector", () => { }, }) }) + + test("clears the search query via the clear button", () => { + render() + + const trigger = screen.getByTestId("chat-model-selector-trigger") + fireEvent.click(trigger) + + const searchInput = screen.getByPlaceholderText("chat:searchModel") as HTMLInputElement + fireEvent.change(searchInput, { target: { value: "claude-3" } }) + expect(searchInput.value).toBe("claude-3") + + const clearButton = searchInput.parentElement?.querySelector(".codicon-close") as HTMLElement | null + expect(clearButton).toBeTruthy() + fireEvent.click(clearButton!) + + expect((screen.getByPlaceholderText("chat:searchModel") as HTMLInputElement).value).toBe("") + }) + + test("does not post a message when selecting with no apiConfiguration", () => { + mockUseChatModelSelector.mockReturnValue({ + provider: providerIdentifiers.anthropic, + models: anthropicModels, + modelIdKey: "apiModelId", + defaultModelId: "claude-opus-4-20250514", + isLoading: false, + }) + // Simulate a missing apiConfiguration so onSelect takes its early-return branch + // (the mock of useExtensionState returns an apiConfiguration; the real early + // return also triggers when modelIdKey is unset, which we cover via a hook mock + // without a modelIdKey below). + mockUseChatModelSelector.mockReturnValueOnce({ + provider: providerIdentifiers.anthropic, + models: anthropicModels, + modelIdKey: undefined, + defaultModelId: undefined, + isLoading: false, + }) + + render() + + const trigger = screen.getByTestId("chat-model-selector-trigger") + fireEvent.click(trigger) + + const option = screen.getByTestId("chat-model-option-claude-opus-4-20250514") + fireEvent.click(option) + + expect(vscode.postMessage).not.toHaveBeenCalled() + }) + + test("renders the display-transformed value for compound configs (e.g. VSCode LM)", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.vscodeLm, + vsCodeLmModelSelector: { vendor: "copilot", family: "gpt-4o" }, + }, + organizationAllowList: { allowAll: true, providers: {} }, + currentApiConfigName: "default", + }) + mockUseChatModelSelector.mockReturnValue({ + provider: providerIdentifiers.vscodeLm, + models: { "copilot/gpt-4o": { maxTokens: 1, contextWindow: 1 } }, + modelIdKey: "vsCodeLmModelSelector", + defaultModelId: undefined, + isLoading: false, + displayTransform: (value: unknown) => { + const selector = value as { vendor?: string; family?: string } + return selector.vendor && selector.family ? `${selector.vendor}/${selector.family}` : "" + }, + }) + + render() + + // The trigger shows the display-transformed value instead of the raw model id. + const trigger = screen.getByTestId("chat-model-selector-trigger") + expect(trigger).toHaveTextContent("copilot/gpt-4o") + }) }) diff --git a/webview-ui/src/components/chat/hooks/__tests__/useChatModelSelector.spec.tsx b/webview-ui/src/components/chat/hooks/__tests__/useChatModelSelector.spec.tsx index 6f19925992..8fd1d4045a 100644 --- a/webview-ui/src/components/chat/hooks/__tests__/useChatModelSelector.spec.tsx +++ b/webview-ui/src/components/chat/hooks/__tests__/useChatModelSelector.spec.tsx @@ -155,6 +155,91 @@ describe("useChatModelSelector", () => { }) }) + describe("requesty (dynamic router provider)", () => { + it("reads requesty models from the react-query routerModels", () => { + mockUseRouterModels.mockReturnValue({ + data: { [providerIdentifiers.requesty]: { "openai/gpt-5.1": { maxTokens: 1, contextWindow: 1 } } }, + isLoading: false, + isError: false, + }) + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.requesty, requestyModelId: "openai/gpt-5.1" }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("requestyModelId") + expect(result.current.defaultModelId).toBeTruthy() + expect(Object.keys(result.current.models!)).toEqual(["openai/gpt-5.1"]) + }) + }) + + describe("unbound (dynamic router provider)", () => { + it("reads unbound models from the react-query routerModels", () => { + mockUseRouterModels.mockReturnValue({ + data: { [providerIdentifiers.unbound]: { "openai/gpt-4o": { maxTokens: 1, contextWindow: 1 } } }, + isLoading: false, + isError: false, + }) + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.unbound, unboundModelId: "openai/gpt-4o" }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("unboundModelId") + expect(result.current.defaultModelId).toBeTruthy() + expect(Object.keys(result.current.models!)).toEqual(["openai/gpt-4o"]) + }) + }) + + describe("vercel-ai-gateway (dynamic router provider)", () => { + it("reads vercel-ai-gateway models from the react-query routerModels", () => { + mockUseRouterModels.mockReturnValue({ + data: { + [providerIdentifiers.vercelAiGateway]: { "openai/gpt-4o-mini": { maxTokens: 1, contextWindow: 1 } }, + }, + isLoading: false, + isError: false, + }) + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.vercelAiGateway, + vercelAiGatewayModelId: "openai/gpt-4o-mini", + }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("vercelAiGatewayModelId") + expect(result.current.defaultModelId).toBeTruthy() + expect(Object.keys(result.current.models!)).toEqual(["openai/gpt-4o-mini"]) + }) + }) + + describe("kimi-code (dynamic router provider)", () => { + it("reads kimi-code models from the react-query routerModels using apiModelId", () => { + mockUseRouterModels.mockReturnValue({ + data: { [providerIdentifiers.kimiCode]: { "kimi-k2": { maxTokens: 1, contextWindow: 1 } } }, + isLoading: false, + isError: false, + }) + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.kimiCode, apiModelId: "kimi-k2" }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + expect(result.current.modelIdKey).toBe("apiModelId") + expect(result.current.defaultModelId).toBeTruthy() + expect(Object.keys(result.current.models!)).toEqual(["kimi-k2"]) + }) + }) + describe("openai (OpenAI compatible)", () => { it("requests openAi models on mount when baseUrl and apiKey are set", () => { mockUseExtensionState.mockReturnValue({ @@ -295,6 +380,28 @@ describe("useChatModelSelector", () => { expect(mockPostMessage).toHaveBeenCalledWith({ type: "requestLmStudioModels" }) }) + it("uses models delivered through the lmStudioModels message", async () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { apiProvider: providerIdentifiers.lmstudio, lmStudioModelId: "local-model" }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + act(() => { + emitMessage({ + type: "lmStudioModels", + lmStudioModels: { "local-model": { maxTokens: 1, contextWindow: 1 } }, + }) + }) + + await waitFor(() => { + expect(result.current.models).not.toBeNull() + }) + expect(result.current.modelIdKey).toBe("lmStudioModelId") + expect(Object.keys(result.current.models!)).toEqual(["local-model"]) + }) + it("requests vsCodeLm models on mount and builds model records", async () => { mockUseExtensionState.mockReturnValue({ apiConfiguration: { apiProvider: providerIdentifiers.vscodeLm }, @@ -318,6 +425,37 @@ describe("useChatModelSelector", () => { expect(Object.keys(result.current.models!)).toEqual(["copilot/gpt-4o"]) expect(result.current.modelIdKey).toBe("vsCodeLmModelSelector") }) + + it("transforms vsCodeLm model ids to vendor/family and formats display values", async () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.vscodeLm, + vsCodeLmModelSelector: { vendor: "copilot", family: "gpt-4o" }, + }, + routerModels: undefined, + }) + + const { result } = renderHook(() => useChatModelSelector(), { wrapper }) + + act(() => { + emitMessage({ + type: "vsCodeLmModels", + vsCodeLmModels: [{ vendor: "copilot", family: "gpt-4o" }], + }) + }) + + await waitFor(() => { + expect(result.current.models).not.toBeNull() + }) + + // valueTransform: "copilot/gpt-4o" -> { vendor: "copilot", family: "gpt-4o" } + expect(result.current.valueTransform!("copilot/gpt-4o")).toEqual({ vendor: "copilot", family: "gpt-4o" }) + // displayTransform: { vendor, family } -> "copilot/gpt-4o" + expect(result.current.displayTransform!({ vendor: "copilot", family: "gpt-4o" })).toBe("copilot/gpt-4o") + // displayTransform returns "" for missing values + expect(result.current.displayTransform!(undefined)).toBe("") + expect(result.current.displayTransform!({ vendor: "copilot" })).toBe("") + }) }) describe("retired providers", () => { From f36f9a76ad3bc3687ea28b29fefaebdc4726752e Mon Sep 17 00:00:00 2001 From: seeones Date: Sun, 27 Sep 2026 12:52:14 +0800 Subject: [PATCH 03/12] test(e2e): regenerate electron chat-dark sidebar snapshot from pr/chat-model-selector source --- .../electron-chat-dark-sidebar.png | Bin 30321 -> 30026 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/vscode-e2e/src/visual/__screenshots__/electron-chat-dark-sidebar.png b/apps/vscode-e2e/src/visual/__screenshots__/electron-chat-dark-sidebar.png index e8497070ef3440fe4a3bbb3ef49f9a1de9d4c4d9..3c03cbac035d27aa3820b5de342f0cf07ad05074 100644 GIT binary patch delta 10520 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 Date: Tue, 29 Sep 2026 11:37:41 +0800 Subject: [PATCH 04/12] fix(chat): harden inline model selector (allow-list, a11y, Z.AI line, race) - Enforce the organization allow-list on both the webview and the extension host - Make the selector trigger keyboard accessible (div -> button) - Correct Z.AI China API line handling so the mainland model list loads - Guard the OpenAI model list request against identity races - Show the currently saved model in the trigger --- src/core/webview/ClineProvider.ts | 26 +++++++ src/core/webview/webviewMessageHandler.ts | 30 +++++++- .../src/components/chat/ChatModelSelector.tsx | 69 +++++++++++++++---- .../chat/hooks/useChatModelSelector.ts | 40 ++++++++++- 4 files changed, 149 insertions(+), 16 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 6fac386383..b96efcd193 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1836,6 +1836,32 @@ export class ClineProvider providerSettings: ProviderSettings, activate: boolean = true, ): Promise { + // Enforce the organization model allow-list before persisting or + // activating a profile. The webview is not a trusted boundary, so the + // model selector's client-side gating cannot be the only check. + // Task creation validates too, but rejecting here prevents an + // unauthorized profile from being written or activated at all. + let organizationAllowList = ORGANIZATION_ALLOW_ALL + + try { + organizationAllowList = await CloudService.instance.getAllowList() + } catch (error) { + // No cloud instance / not authenticated: fall back to allow-all, + // matching `getState` and task-creation semantics. + this.log( + `[upsertProviderProfile] organization allow-list unavailable, using allow-all: ${ + error instanceof Error ? error.message : String(error) + }`, + ) + } + + if (!ProfileValidator.isProfileAllowed(providerSettings, organizationAllowList)) { + this.log( + `[upsertProviderProfile] Blocked profile "${name}": model is not allowed by the organization allow-list`, + ) + return undefined + } + try { return await this.enqueueProviderProfileMutation(async (signal) => { // TODO: Do we need to be calling `activateProfile`? It's not diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index a41fda1ce1..7ebd125a7e 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -24,6 +24,7 @@ import { getCompletionCheckpoint, providerIdentifiers, retiredProviderIdentifiers, + ORGANIZATION_ALLOW_ALL, LmStudioModelsMessageType, OllamaModelsMessageType, OpenAiModelsMessageType, @@ -57,6 +58,7 @@ import { handleOpenRuleFile, handleOpenRulesDirectory, } from "./rulesMessageHandler" +import { ProfileValidator } from "../../shared/ProfileValidator" import { changeLanguage, t } from "../../i18n" import { Package } from "../../shared/package" import { type RouterName, toRouterName } from "../../shared/api" @@ -1475,7 +1477,14 @@ export const webviewMessageHandler = async ( message?.values?.openAiHeaders, ) - await provider.postMessageToWebview({ type: OpenAiModelsMessageType.openAiModels, openAiModels }) + // Echo the caller's request id so the webview can correlate the + // response with the request it issued and drop stale replies + // that arrive after a provider/profile switch. + await provider.postMessageToWebview({ + type: OpenAiModelsMessageType.openAiModels, + openAiModels, + requestId: message.requestId, + }) } break @@ -2280,6 +2289,25 @@ export const webviewMessageHandler = async ( break case "upsertApiConfiguration": if (message.text && message.apiConfiguration) { + const { organizationAllowList } = await provider.getState() + + // The webview is not a trusted boundary: re-validate the + // organization model allow-list before persisting and + // activating, so a forged custom model id cannot bypass the + // selector's UI gating. + if ( + !ProfileValidator.isProfileAllowed( + message.apiConfiguration, + organizationAllowList ?? ORGANIZATION_ALLOW_ALL, + ) + ) { + provider.log( + `Blocked upsertApiConfiguration "${message.text}": model is not allowed by the organization allow-list`, + ) + vscode.window.showErrorMessage(t("common:errors.violated_organization_allowlist")) + break + } + await provider.upsertProviderProfile(message.text, message.apiConfiguration) } break diff --git a/webview-ui/src/components/chat/ChatModelSelector.tsx b/webview-ui/src/components/chat/ChatModelSelector.tsx index 7cd3450e01..0bbf70d1fe 100644 --- a/webview-ui/src/components/chat/ChatModelSelector.tsx +++ b/webview-ui/src/components/chat/ChatModelSelector.tsx @@ -40,14 +40,47 @@ export const ChatModelSelector = ({ disabled = false, title, triggerClassName = return available }, [models, provider, organizationAllowList, selectedModelId]) + // Gate arbitrary model ids against the organization allow-list. The webview + // is not a security boundary, but this keeps the UI from offering a custom + // model that the extension host would reject on save. + const isModelAllowed = useCallback( + (modelId: string): boolean => { + if (!organizationAllowList || organizationAllowList.allowAll) { + return true + } + if (!provider) { + return false + } + const providerConfig = organizationAllowList.providers[provider] + if (!providerConfig) { + return false + } + if (providerConfig.allowAll) { + return true + } + return providerConfig.models?.includes(modelId) ?? false + }, + [organizationAllowList, provider], + ) + + // Only offer a custom (non-listed) model when the allow-list permits that + // exact model id for the active provider. + const customModelAllowed = useMemo( + () => (searchValue ? isModelAllowed(searchValue) : false), + [searchValue, isModelAllowed], + ) + // Resolve the display value (custom transform for compound config values like VSCode LM). const displayValue = useMemo(() => { if (displayTransform && modelIdKey) { const storedValue = apiConfiguration?.[modelIdKey] return storedValue ? displayTransform(storedValue) : undefined } - return selectedModelId || (searchValue ? searchValue : undefined) - }, [displayTransform, modelIdKey, apiConfiguration, selectedModelId, searchValue]) + // Only reflect the saved model in the trigger label (and the row + // highlight); fall back to undefined so the default/placeholder shows + // instead of the in-progress search text. + return selectedModelId || undefined + }, [displayTransform, modelIdKey, apiConfiguration, selectedModelId]) const filteredModelIds = useMemo(() => { if (!searchValue) return modelIds @@ -61,6 +94,12 @@ export const ChatModelSelector = ({ disabled = false, title, triggerClassName = return } + // Defense in depth: never persist a model the allow-list forbids, + // even if a caller bypasses the rendered list (e.g. custom search). + if (!isModelAllowed(modelId)) { + return + } + setOpen(false) setSearchValue("") @@ -79,7 +118,7 @@ export const ChatModelSelector = ({ disabled = false, title, triggerClassName = }, }) }, - [modelIdKey, apiConfiguration, valueTransform, currentApiConfigName], + [modelIdKey, apiConfiguration, valueTransform, currentApiConfigName, isModelAllowed], ) const onClearSearch = useCallback(() => { @@ -123,9 +162,12 @@ export const ChatModelSelector = ({ disabled = false, title, triggerClassName = /> {searchValue.length > 0 && (

)} @@ -138,12 +180,14 @@ export const ChatModelSelector = ({ disabled = false, title, triggerClassName = {modelIds.length > 0 && (
{filteredModelIds.map((modelId) => ( -
onSelect(modelId)} data-testid={`chat-model-option-${modelId}`} className={cn( - "px-3 py-1.5 text-sm cursor-pointer flex items-center gap-2", + "w-full text-left bg-transparent border-none px-3 py-1.5 text-sm cursor-pointer flex items-center gap-2", + "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset", "hover:bg-vscode-list-hoverBackground", modelId === displayValue && "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground", @@ -152,18 +196,19 @@ export const ChatModelSelector = ({ disabled = false, title, triggerClassName = {modelId} {modelId === displayValue && } -
+ ))}
)} - {searchValue && !modelIds.includes(searchValue) && ( -
onSelect(searchValue)} - className="px-3 py-1.5 text-sm cursor-pointer hover:bg-vscode-list-hoverBackground border-t border-vscode-input-border"> + className="w-full text-left bg-transparent border-none px-3 py-1.5 text-sm cursor-pointer hover:bg-vscode-list-hoverBackground border-t border-vscode-input-border focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset"> {t("chat:useCustomModel", { modelId: searchValue })} -
+ )}
diff --git a/webview-ui/src/components/chat/hooks/useChatModelSelector.ts b/webview-ui/src/components/chat/hooks/useChatModelSelector.ts index 07cfbbe77f..7ab0dc2b9d 100644 --- a/webview-ui/src/components/chat/hooks/useChatModelSelector.ts +++ b/webview-ui/src/components/chat/hooks/useChatModelSelector.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from "react" +import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { useEvent } from "react-use" import { @@ -141,7 +141,7 @@ export interface ChatModelSelectorData { * - Static providers: `getStaticModelsForProvider`. */ export const useChatModelSelector = (): ChatModelSelectorData => { - const { apiConfiguration, routerModels: stateRouterModels } = useExtensionState() + const { apiConfiguration, routerModels: stateRouterModels, currentApiConfigName } = useExtensionState() const provider = (apiConfiguration?.apiProvider || providerIdentifiers.openrouter) as ProviderName const activeProvider = isRetiredProvider(provider) ? undefined : provider @@ -159,10 +159,32 @@ export const useChatModelSelector = (): ChatModelSelectorData => { const [lmStudioModels, setLmStudioModels] = useState({}) const [vsCodeLmModels, setVsCodeLmModels] = useState([]) + // Identity of the in-flight OpenAI-compatible model request. Responses that + // do not match the current request are ignored so a late reply from a + // previous provider/profile cannot overwrite the active list. + const openAiRequestIdRef = useRef(null) + + // Drop any previously fetched message-based model list when the provider or + // API profile changes; the lists are provider/profile scoped and a stale + // entry would otherwise leak across the switch. + useEffect(() => { + setOpenAiModels([]) + setOllamaModels({}) + setLmStudioModels({}) + setVsCodeLmModels([]) + openAiRequestIdRef.current = null + }, [activeProvider, currentApiConfigName]) + const onMessage = useCallback((event: MessageEvent) => { const message: ExtensionMessage = event.data switch (message.type) { case "openAiModels": + // Only accept a response that belongs to the request we most + // recently issued; ignore replies without an identity (other + // callers) or from a superseded request. + if (message.requestId && message.requestId !== openAiRequestIdRef.current) { + break + } setOpenAiModels(message.openAiModels ?? []) break case "ollamaModels": @@ -188,8 +210,13 @@ export const useChatModelSelector = (): ChatModelSelectorData => { switch (activeProvider) { case providerIdentifiers.openai: if (apiConfiguration?.openAiBaseUrl && apiConfiguration?.openAiApiKey) { + // Tag the request so the matching response can be identified + // and stale replies discarded (see `onMessage`). + const requestId = `${Date.now()}-${Math.random().toString(36).slice(2)}` + openAiRequestIdRef.current = requestId vscode.postMessage({ type: "requestOpenAiModels", + requestId, values: { baseUrl: apiConfiguration.openAiBaseUrl, apiKey: apiConfiguration.openAiApiKey, @@ -211,6 +238,9 @@ export const useChatModelSelector = (): ChatModelSelectorData => { } }, [ activeProvider, + // Re-request after a provider/profile switch so the list matches the + // newly active profile (the reset effect clears the previous list). + currentApiConfigName, apiConfiguration?.openAiBaseUrl, apiConfiguration?.openAiApiKey, apiConfiguration?.openAiHeaders, @@ -332,7 +362,11 @@ export const useChatModelSelector = (): ChatModelSelectorData => { break default: // Static models providers (anthropic, bedrock, gemini, etc.). - models = MODELS_BY_PROVIDER[activeProvider] ? getStaticModelsForProvider(activeProvider) : null + // Pass the configuration so Z.ai resolves models for the same + // API line (China vs international) as `defaultModelId`. + models = MODELS_BY_PROVIDER[activeProvider] + ? getStaticModelsForProvider(activeProvider, undefined, apiConfiguration) + : null modelIdKey = "apiModelId" } From 4d292444c35a52f95629cc617eea097dd1ff661c Mon Sep 17 00:00:00 2001 From: seeones Date: Tue, 29 Sep 2026 11:38:04 +0800 Subject: [PATCH 05/12] test(chat): strengthen inline model selector coverage - Cover allow-list gating, keyboard interaction, race handling and trigger display - Align assertions with the div -> button markup change --- .../chat/__tests__/ChatModelSelector.spec.tsx | 270 +++++++++++++++++- .../__tests__/useChatModelSelector.spec.tsx | 239 ++++++++++++++-- 2 files changed, 475 insertions(+), 34 deletions(-) diff --git a/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx index 65642a1715..810298e6e0 100644 --- a/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatModelSelector.spec.tsx @@ -88,11 +88,31 @@ const anthropicModels = { }, } +// Minimal ModelInfo factory for ad-hoc fixtures. +const modelInfo = (overrides: Record = {}) => ({ + maxTokens: 32000, + contextWindow: 200000, + supportsPromptCache: true, + ...overrides, +}) + describe("ChatModelSelector", () => { const defaultProps = { title: "Select model", } + /** Helper: render the selector and open the popover so options are visible. */ + const renderOpen = (disabled = false) => { + render() + fireEvent.click(screen.getByTestId("chat-model-selector-trigger")) + } + + /** Helper: read the visible option ids in DOM order. */ + const visibleOptionIds = () => + screen + .getAllByTestId(/^chat-model-option-/) + .map((element) => element.getAttribute("data-testid")!.replace("chat-model-option-", "")) + beforeEach(() => { vi.clearAllMocks() mockUseExtensionState.mockReturnValue({ @@ -199,33 +219,72 @@ describe("ChatModelSelector", () => { expect((screen.getByPlaceholderText("chat:searchModel") as HTMLInputElement).value).toBe("") }) - test("does not post a message when selecting with no apiConfiguration", () => { + test("filters model ids case-insensitively", () => { mockUseChatModelSelector.mockReturnValue({ provider: providerIdentifiers.anthropic, - models: anthropicModels, + models: { + "Claude-Opus-4": modelInfo(), + "Claude-Sonnet-4": modelInfo(), + }, modelIdKey: "apiModelId", - defaultModelId: "claude-opus-4-20250514", + defaultModelId: "Claude-Opus-4", isLoading: false, }) - // Simulate a missing apiConfiguration so onSelect takes its early-return branch - // (the mock of useExtensionState returns an apiConfiguration; the real early - // return also triggers when modelIdKey is unset, which we cover via a hook mock - // without a modelIdKey below). - mockUseChatModelSelector.mockReturnValueOnce({ + + renderOpen() + + // Uppercase query must match the lowercase portion of the model id. + fireEvent.change(screen.getByPlaceholderText("chat:searchModel"), { target: { value: "SONNET" } }) + + expect(screen.getByTestId("chat-model-option-Claude-Sonnet-4")).toBeInTheDocument() + expect(screen.queryByTestId("chat-model-option-Claude-Opus-4")).not.toBeInTheDocument() + }) + + test("renders the empty state when no models are available", () => { + mockUseChatModelSelector.mockReturnValue({ provider: providerIdentifiers.anthropic, - models: anthropicModels, - modelIdKey: undefined, - defaultModelId: undefined, + models: {}, + modelIdKey: "apiModelId", + defaultModelId: "claude-opus-4-20250514", isLoading: false, }) + renderOpen() + + expect(screen.getByText("chat:modelListEmpty")).toBeInTheDocument() + expect(screen.queryByTestId(/^chat-model-option-/)).not.toBeInTheDocument() + }) + + test("shows a loading label and hides the empty state while loading", () => { + mockUseChatModelSelector.mockReturnValue({ + provider: providerIdentifiers.anthropic, + models: null, + modelIdKey: "apiModelId", + defaultModelId: "claude-opus-4-20250514", + isLoading: true, + }) + render() - const trigger = screen.getByTestId("chat-model-selector-trigger") - fireEvent.click(trigger) + // The trigger collapses to an ellipsis while loading, regardless of the + // resolved display/default value. + expect(screen.getByTestId("chat-model-selector-trigger")).toHaveTextContent("…") + // The empty-state message must not appear while a load is in flight. + expect(screen.queryByText("chat:modelListEmpty")).not.toBeInTheDocument() + }) - const option = screen.getByTestId("chat-model-option-claude-opus-4-20250514") - fireEvent.click(option) + test("does not post a message when selecting with no apiConfiguration", () => { + // Keep modelIdKey defined so the guard under test is the `!apiConfiguration` + // early return, not the `!modelIdKey` guard. + mockUseExtensionState.mockReturnValue({ + apiConfiguration: undefined, + organizationAllowList: { allowAll: true, providers: {} }, + currentApiConfigName: "default", + }) + + renderOpen() + + fireEvent.click(screen.getByTestId("chat-model-option-claude-opus-4-20250514")) expect(vscode.postMessage).not.toHaveBeenCalled() }) @@ -257,4 +316,185 @@ describe("ChatModelSelector", () => { const trigger = screen.getByTestId("chat-model-selector-trigger") expect(trigger).toHaveTextContent("copilot/gpt-4o") }) + + describe("organization allow-list", () => { + test("filters models down to the provider allow-list", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.anthropic, + apiModelId: "claude-opus-4-20250514", + }, + organizationAllowList: { + allowAll: false, + providers: { + [providerIdentifiers.anthropic]: { + allowAll: false, + models: ["claude-sonnet-4-20250514"], + }, + }, + }, + currentApiConfigName: "default", + }) + + renderOpen() + + expect(screen.getByTestId("chat-model-option-claude-sonnet-4-20250514")).toBeInTheDocument() + expect(screen.queryByTestId("chat-model-option-claude-opus-4-20250514")).not.toBeInTheDocument() + }) + + test("shows the empty state when the provider is absent from the allow-list", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.anthropic, + apiModelId: "claude-opus-4-20250514", + }, + organizationAllowList: { allowAll: false, providers: {} }, + currentApiConfigName: "default", + }) + + renderOpen() + + expect(screen.queryByTestId(/^chat-model-option-/)).not.toBeInTheDocument() + expect(screen.getByText("chat:modelListEmpty")).toBeInTheDocument() + }) + + test("hides the custom-model row when the allow-list forbids the typed id", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.anthropic, + apiModelId: "claude-opus-4-20250514", + }, + organizationAllowList: { + allowAll: false, + providers: { + [providerIdentifiers.anthropic]: { + allowAll: false, + models: ["claude-opus-4-20250514"], + }, + }, + }, + currentApiConfigName: "default", + }) + + renderOpen() + + fireEvent.change(screen.getByPlaceholderText("chat:searchModel"), { + target: { value: "unauthorized-model" }, + }) + + expect(screen.queryByTestId("chat-model-use-custom")).not.toBeInTheDocument() + }) + + test("offers and posts the custom-model row when the allow-list permits it", () => { + mockUseExtensionState.mockReturnValue({ + apiConfiguration: { + apiProvider: providerIdentifiers.anthropic, + apiModelId: "claude-opus-4-20250514", + }, + organizationAllowList: { + allowAll: false, + providers: { + [providerIdentifiers.anthropic]: { + allowAll: false, + models: ["claude-opus-4-20250514", "approved-custom"], + }, + }, + }, + currentApiConfigName: "default", + }) + mockUseChatModelSelector.mockReturnValue({ + provider: providerIdentifiers.anthropic, + models: { "claude-opus-4-20250514": modelInfo() }, + modelIdKey: "apiModelId", + defaultModelId: "claude-opus-4-20250514", + isLoading: false, + }) + + renderOpen() + + fireEvent.change(screen.getByPlaceholderText("chat:searchModel"), { + target: { value: "approved-custom" }, + }) + + fireEvent.click(screen.getByTestId("chat-model-use-custom")) + + expect(vscode.postMessage).toHaveBeenCalledWith({ + type: "upsertApiConfiguration", + text: "default", + apiConfiguration: { + apiProvider: providerIdentifiers.anthropic, + apiModelId: "approved-custom", + }, + }) + }) + }) + + test("guards the visible list: sorts ids, drops unselected deprecated models and keeps the selected one", () => { + mockUseChatModelSelector.mockReturnValue({ + provider: providerIdentifiers.anthropic, + // Intentionally unsorted, with one deprecated model that is also the + // selected one (must survive) and one deprecated model that is not. + models: { + "zeta-model": modelInfo(), + "claude-opus-4-20250514": modelInfo({ deprecated: true }), + "legacy-model": modelInfo({ deprecated: true }), + "alpha-model": modelInfo(), + "middle-model": modelInfo(), + }, + modelIdKey: "apiModelId", + defaultModelId: "claude-opus-4-20250514", + isLoading: false, + }) + + renderOpen() + + // Rendered ids are sorted alphabetically and exclude the unselected deprecated model. + expect(visibleOptionIds()).toEqual(["alpha-model", "claude-opus-4-20250514", "middle-model", "zeta-model"]) + expect(screen.queryByTestId("chat-model-option-legacy-model")).not.toBeInTheDocument() + expect(screen.getByTestId("chat-model-option-claude-opus-4-20250514")).toBeInTheDocument() + }) + + test("disables the trigger when there is no modelIdKey", () => { + mockUseChatModelSelector.mockReturnValue({ + provider: undefined, + models: null, + modelIdKey: undefined, + defaultModelId: "", + isLoading: false, + }) + + render() + + const trigger = screen.getByTestId("chat-model-selector-trigger") + expect(trigger).toBeDisabled() + expect(trigger).toHaveClass("cursor-not-allowed") + }) + + test("exposes model options as keyboard-activatable buttons", () => { + renderOpen() + + const option = screen.getByTestId("chat-model-option-claude-sonnet-4-20250514") + // A native