feat: show model ID in API configuration dropdown (#7423)

This commit is contained in:
Daniel 2025-08-27 12:19:30 -05:00 committed by GitHub
parent 46b6fdd21e
commit faf5734bdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 113 additions and 55 deletions

View file

@ -81,6 +81,7 @@ export const providerSettingsEntrySchema = z.object({
id: z.string(),
name: z.string(),
apiProvider: providerNamesSchema.optional(),
modelId: z.string().optional(),
})
export type ProviderSettingsEntry = z.infer<typeof providerSettingsEntrySchema>

View file

@ -9,6 +9,7 @@ import {
isSecretStateKey,
ProviderSettingsEntry,
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
getModelId,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
@ -274,6 +275,20 @@ export class ProviderSettingsManager {
}
}
/**
* Clean model ID by removing prefix before "/"
*/
private cleanModelId(modelId: string | undefined): string | undefined {
if (!modelId) return undefined
// Check for "/" and take the part after it
if (modelId.includes("/")) {
return modelId.split("/").pop()
}
return modelId
}
/**
* List all available configs with metadata.
*/
@ -286,6 +301,7 @@ export class ProviderSettingsManager {
name,
id: apiConfig.id || "",
apiProvider: apiConfig.apiProvider,
modelId: this.cleanModelId(getModelId(apiConfig)),
}))
})
} catch (error) {

View file

@ -18,7 +18,7 @@ interface ApiConfigSelectorProps {
title: string
onChange: (value: string) => void
triggerClassName?: string
listApiConfigMeta: Array<{ id: string; name: string }>
listApiConfigMeta: Array<{ id: string; name: string; modelId?: string }>
pinnedApiConfigs?: Record<string, boolean>
togglePinnedApiConfig: (id: string) => void
}
@ -87,7 +87,7 @@ export const ApiConfigSelector = ({
}, [])
const renderConfigItem = useCallback(
(config: { id: string; name: string }, isPinned: boolean) => {
(config: { id: string; name: string; modelId?: string }, isPinned: boolean) => {
const isCurrentConfig = config.id === value
return (
@ -100,7 +100,19 @@ export const ApiConfigSelector = ({
isCurrentConfig &&
"bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground",
)}>
<span className="flex-1 truncate">{config.name}</span>
<div className="flex-1 min-w-0 flex items-center gap-1 overflow-hidden">
<span className="flex-shrink-0">{config.name}</span>
{config.modelId && (
<>
<span className="text-vscode-descriptionForeground opacity-70 flex-shrink-0">·</span>
<span
className="text-vscode-descriptionForeground opacity-70 min-w-0 overflow-hidden"
style={{ direction: "rtl", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{config.modelId}
</span>
</>
)}
</div>
<div className="flex items-center gap-1">
{isCurrentConfig && (
<div className="size-5 p-1 flex items-center justify-center">

View file

@ -21,6 +21,21 @@ vi.mock("@/components/ui/hooks/useRooPortal", () => ({
useRooPortal: () => document.body,
}))
// Mock the ExtensionStateContext
vi.mock("@/context/ExtensionStateContext", () => ({
useExtensionState: () => ({
apiConfiguration: {
apiProvider: "anthropic",
apiModelId: "claude-3-opus-20240229",
},
}),
}))
// Mock the getModelId function from @roo-code/types
vi.mock("@roo-code/types", () => ({
getModelId: (config: any) => config?.apiModelId || undefined,
}))
// Mock Popover components to be testable
vi.mock("@/components/ui", () => ({
Popover: ({ children, open }: any) => (
@ -52,9 +67,9 @@ describe("ApiConfigSelector", () => {
title: "API Config",
onChange: mockOnChange,
listApiConfigMeta: [
{ id: "config1", name: "Config 1" },
{ id: "config2", name: "Config 2" },
{ id: "config3", name: "Config 3" },
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
],
pinnedApiConfigs: { config1: true },
togglePinnedApiConfig: mockTogglePinnedApiConfig,
@ -120,13 +135,13 @@ describe("ApiConfigSelector", () => {
const props = {
...defaultProps,
listApiConfigMeta: [
{ id: "config1", name: "Config 1" },
{ id: "config2", name: "Config 2" },
{ id: "config3", name: "Config 3" },
{ id: "config4", name: "Config 4" },
{ id: "config5", name: "Config 5" },
{ id: "config6", name: "Config 6" },
{ id: "config7", name: "Config 7" },
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
],
}
render(<ApiConfigSelector {...props} />)
@ -154,13 +169,13 @@ describe("ApiConfigSelector", () => {
const props = {
...defaultProps,
listApiConfigMeta: [
{ id: "config1", name: "Config 1" },
{ id: "config2", name: "Config 2" },
{ id: "config3", name: "Config 3" },
{ id: "config4", name: "Config 4" },
{ id: "config5", name: "Config 5" },
{ id: "config6", name: "Config 6" },
{ id: "config7", name: "Config 7" },
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
],
}
render(<ApiConfigSelector {...props} />)
@ -184,13 +199,13 @@ describe("ApiConfigSelector", () => {
const props = {
...defaultProps,
listApiConfigMeta: [
{ id: "config1", name: "Config 1" },
{ id: "config2", name: "Config 2" },
{ id: "config3", name: "Config 3" },
{ id: "config4", name: "Config 4" },
{ id: "config5", name: "Config 5" },
{ id: "config6", name: "Config 6" },
{ id: "config7", name: "Config 7" },
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
],
}
render(<ApiConfigSelector {...props} />)
@ -210,13 +225,13 @@ describe("ApiConfigSelector", () => {
const props = {
...defaultProps,
listApiConfigMeta: [
{ id: "config1", name: "Config 1" },
{ id: "config2", name: "Config 2" },
{ id: "config3", name: "Config 3" },
{ id: "config4", name: "Config 4" },
{ id: "config5", name: "Config 5" },
{ id: "config6", name: "Config 6" },
{ id: "config7", name: "Config 7" },
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
],
}
render(<ApiConfigSelector {...props} />)
@ -263,7 +278,8 @@ describe("ApiConfigSelector", () => {
const config1Elements = screen.getAllByText("Config 1")
// Find the one that's in the dropdown content (not the trigger)
const configInDropdown = config1Elements.find((el) => el.closest('[data-testid="popover-content"]'))
const selectedConfigRow = configInDropdown?.closest("div")
// Navigate up to find the parent row that contains both the text and the check icon
const selectedConfigRow = configInDropdown?.closest(".group")
const checkIcon = selectedConfigRow?.querySelector(".codicon-check")
expect(checkIcon).toBeInTheDocument()
})
@ -280,13 +296,24 @@ describe("ApiConfigSelector", () => {
fireEvent.click(trigger)
const content = screen.getByTestId("popover-content")
const configTexts = content.querySelectorAll(".truncate")
// Get all config items by looking for the group class
const configRows = content.querySelectorAll(".group")
// Extract the config names from each row
const configNames: string[] = []
configRows.forEach((row) => {
// Find the first span that's flex-shrink-0 (the profile name)
const nameElement = row.querySelector(".flex-1 span.flex-shrink-0")
if (nameElement?.textContent) {
configNames.push(nameElement.textContent)
}
})
// Pinned configs should appear first
expect(configTexts[0]).toHaveTextContent("Config 1")
expect(configTexts[1]).toHaveTextContent("Config 3")
expect(configNames[0]).toBe("Config 1")
expect(configNames[1]).toBe("Config 3")
// Unpinned config should appear after separator
expect(configTexts[2]).toHaveTextContent("Config 2")
expect(configNames[2]).toBe("Config 2")
})
test("toggles pin status when pin button is clicked", () => {
@ -296,8 +323,10 @@ describe("ApiConfigSelector", () => {
fireEvent.click(trigger)
// Find the pin button for Config 2 (unpinned)
const config2Row = screen.getByText("Config 2").closest("div")
const pinButton = config2Row?.querySelector("button")
const config2Row = screen.getByText("Config 2").closest(".group")
// Find the button with the pin icon (it's the second button, first is the row itself)
const buttons = config2Row?.querySelectorAll("button")
const pinButton = Array.from(buttons || []).find((btn) => btn.querySelector(".codicon-pin"))
if (pinButton) {
fireEvent.click(pinButton)
@ -332,13 +361,13 @@ describe("ApiConfigSelector", () => {
const props = {
...defaultProps,
listApiConfigMeta: [
{ id: "config1", name: "Config 1" },
{ id: "config2", name: "Config 2" },
{ id: "config3", name: "Config 3" },
{ id: "config4", name: "Config 4" },
{ id: "config5", name: "Config 5" },
{ id: "config6", name: "Config 6" },
{ id: "config7", name: "Config 7" },
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
],
}
render(<ApiConfigSelector {...props} />)
@ -389,13 +418,13 @@ describe("ApiConfigSelector", () => {
const props = {
...defaultProps,
listApiConfigMeta: [
{ id: "config1", name: "Config 1" },
{ id: "config2", name: "Config 2" },
{ id: "config3", name: "Config 3" },
{ id: "config4", name: "Config 4" },
{ id: "config5", name: "Config 5" },
{ id: "config6", name: "Config 6" },
{ id: "config7", name: "Config 7" },
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
],
}
render(<ApiConfigSelector {...props} />)