Skip to content

Commit 5b1c51a

Browse files
committed
refactor(webview): use canonical provider identifiers
1 parent e479b7e commit 5b1c51a

4 files changed

Lines changed: 105 additions & 57 deletions

File tree

webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
vscodeLlmDefaultModelId,
2323
moonshotDefaultModelId,
2424
moonshotModels,
25+
providerIdentifiers,
2526
} from "@roo-code/types"
2627

2728
import { useSelectedModel } from "../useSelectedModel"
@@ -831,6 +832,28 @@ describe("useSelectedModel", () => {
831832
expect(result.current.info).toEqual(openAiModelInfoSaneDefaults)
832833
})
833834

835+
it("uses the canonical OpenAI identifier to select configured model metadata", () => {
836+
const identifiers = providerIdentifiers as Record<string, string>
837+
const originalIdentifier = identifiers.openai
838+
839+
try {
840+
identifiers.openai = "canonical-openai"
841+
842+
const apiConfiguration = {
843+
apiProvider: identifiers.openai,
844+
openAiModelId: "gpt-4o",
845+
} as ProviderSettings
846+
847+
const wrapper = createWrapper()
848+
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
849+
850+
expect(result.current.id).toBe("gpt-4o")
851+
expect(result.current.info).toEqual(openAiModelInfoSaneDefaults)
852+
} finally {
853+
identifiers.openai = originalIdentifier
854+
}
855+
})
856+
834857
it("should return custom model info when provided", () => {
835858
const customModelInfo: ModelInfo = {
836859
maxTokens: 16384,

webview-ui/src/components/ui/hooks/useSelectedModel.ts

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
isDynamicProvider,
3737
isRetiredProvider,
3838
getProviderDefaultModelId,
39+
providerIdentifiers,
3940
} from "@roo-code/types"
4041

4142
import { useRouterModels } from "./useRouterModels"
@@ -148,7 +149,7 @@ function getSelectedModel({
148149
// this gives a better UX than showing the default model
149150
const defaultModelId = getProviderDefaultModelId(provider)
150151
switch (provider) {
151-
case "openrouter": {
152+
case providerIdentifiers.openrouter: {
152153
const id = getValidatedModelId(apiConfiguration.openRouterModelId, routerModels.openrouter, defaultModelId)
153154
let info = routerModels.openrouter?.[id]
154155
const specificProvider = apiConfiguration.openRouterSpecificProvider
@@ -164,17 +165,17 @@ function getSelectedModel({
164165

165166
return { id, info }
166167
}
167-
case "requesty": {
168+
case providerIdentifiers.requesty: {
168169
const id = getValidatedModelId(apiConfiguration.requestyModelId, routerModels.requesty, defaultModelId)
169170
const routerInfo = routerModels.requesty?.[id]
170171
return { id, info: routerInfo }
171172
}
172-
case "unbound": {
173+
case providerIdentifiers.unbound: {
173174
const id = getValidatedModelId(apiConfiguration.unboundModelId, routerModels.unbound, defaultModelId)
174175
const routerInfo = routerModels.unbound?.[id]
175176
return { id, info: routerInfo }
176177
}
177-
case "litellm": {
178+
case providerIdentifiers.litellm: {
178179
// When the model list is empty (not yet loaded or still loading),
179180
// preserve the configured model ID. LiteLLM is a proxy with no inherent
180181
// default model, so we never substitute a hardcoded default here -- when
@@ -187,17 +188,17 @@ function getSelectedModel({
187188
const routerInfo = routerModels.litellm?.[id]
188189
return { id, info: routerInfo ?? litellmDefaultModelInfo }
189190
}
190-
case "xai": {
191+
case providerIdentifiers.xai: {
191192
const id = apiConfiguration.apiModelId ?? defaultModelId
192193
const info = xaiModels[id as keyof typeof xaiModels]
193194
return info ? { id, info } : { id, info: undefined }
194195
}
195-
case "baseten": {
196+
case providerIdentifiers.baseten: {
196197
const id = apiConfiguration.apiModelId ?? defaultModelId
197198
const info = basetenModels[id as keyof typeof basetenModels]
198199
return { id, info }
199200
}
200-
case "bedrock": {
201+
case providerIdentifiers.bedrock: {
201202
const id = apiConfiguration.apiModelId ?? defaultModelId
202203
const baseInfo = bedrockModels[id as keyof typeof bedrockModels]
203204

@@ -221,7 +222,7 @@ function getSelectedModel({
221222

222223
return { id, info: baseInfo }
223224
}
224-
case "vertex": {
225+
case providerIdentifiers.vertex: {
225226
const id = apiConfiguration.apiModelId ?? defaultModelId
226227
const baseInfo = vertexModels[id as keyof typeof vertexModels]
227228

@@ -244,12 +245,12 @@ function getSelectedModel({
244245

245246
return { id, info: baseInfo }
246247
}
247-
case "gemini": {
248+
case providerIdentifiers.gemini: {
248249
const id = apiConfiguration.apiModelId ?? defaultModelId
249250
const info = geminiModels[id as keyof typeof geminiModels]
250251
return { id, info }
251252
}
252-
case "deepseek": {
253+
case providerIdentifiers.deepseek: {
253254
const availableModels = routerModels.deepseek
254255
? { ...deepSeekModels, ...routerModels.deepseek }
255256
: deepSeekModels
@@ -258,7 +259,7 @@ function getSelectedModel({
258259
const staticInfo = deepSeekModels[id as keyof typeof deepSeekModels]
259260
return { id, info: routerInfo ?? staticInfo }
260261
}
261-
case "moonshot": {
262+
case providerIdentifiers.moonshot: {
262263
const availableModels = routerModels.moonshot
263264
? { ...moonshotModels, ...routerModels.moonshot }
264265
: moonshotModels
@@ -267,47 +268,47 @@ function getSelectedModel({
267268
const staticInfo = moonshotModels[id as keyof typeof moonshotModels]
268269
return { id, info: routerInfo ?? staticInfo }
269270
}
270-
case "kimi-code": {
271+
case providerIdentifiers.kimiCode: {
271272
const configuredId = apiConfiguration.apiModelId
272273
const availableModels = routerModels["kimi-code"]
273274
const id = configuredId || defaultModelId
274275
return { id, info: availableModels?.[id] ?? kimiCodeDefaultModelInfo }
275276
}
276-
case "minimax": {
277+
case providerIdentifiers.minimax: {
277278
const id = apiConfiguration.apiModelId ?? defaultModelId
278279
const info = minimaxModels[id as keyof typeof minimaxModels]
279280
return { id, info }
280281
}
281-
case "mimo": {
282+
case providerIdentifiers.mimo: {
282283
const id = apiConfiguration.apiModelId ?? defaultModelId
283284
const info = mimoModels[id as keyof typeof mimoModels] ?? mimoModels["mimo-v2.5-pro"]
284285
return { id, info }
285286
}
286-
case "zai": {
287+
case providerIdentifiers.zai: {
287288
const isChina = apiConfiguration.zaiApiLine === "china_coding"
288289
const models = isChina ? mainlandZAiModels : internationalZAiModels
289290
const defaultModelId = getProviderDefaultModelId(provider, { isChina })
290291
const id = apiConfiguration.apiModelId ?? defaultModelId
291292
const info = models[id as keyof typeof models]
292293
return { id, info }
293294
}
294-
case "openai-native": {
295+
case providerIdentifiers.openaiNative: {
295296
const id = apiConfiguration.apiModelId ?? defaultModelId
296297
const info = openAiNativeModels[id as keyof typeof openAiNativeModels]
297298
return { id, info }
298299
}
299-
case "mistral": {
300+
case providerIdentifiers.mistral: {
300301
const id = apiConfiguration.apiModelId ?? defaultModelId
301302
const info = mistralModels[id as keyof typeof mistralModels]
302303
return { id, info }
303304
}
304-
case "openai": {
305+
case providerIdentifiers.openai: {
305306
const id = apiConfiguration.openAiModelId ?? ""
306307
const customInfo = apiConfiguration?.openAiCustomModelInfo
307308
const info = customInfo ?? openAiModelInfoSaneDefaults
308309
return { id, info }
309310
}
310-
case "ollama": {
311+
case providerIdentifiers.ollama: {
311312
const id = apiConfiguration.ollamaModelId ?? ""
312313
const info = ollamaModels && ollamaModels[apiConfiguration.ollamaModelId!]
313314

@@ -323,15 +324,15 @@ function getSelectedModel({
323324
info: adjustedInfo || undefined,
324325
}
325326
}
326-
case "lmstudio": {
327+
case providerIdentifiers.lmstudio: {
327328
const id = apiConfiguration.lmStudioModelId ?? ""
328329
const modelInfo = lmStudioModels && lmStudioModels[apiConfiguration.lmStudioModelId!]
329330
return {
330331
id,
331332
info: modelInfo ? { ...lMStudioDefaultModelInfo, ...modelInfo } : undefined,
332333
}
333334
}
334-
case "vscode-lm": {
335+
case providerIdentifiers.vscodeLm: {
335336
const id = apiConfiguration?.vsCodeLmModelSelector
336337
? `${apiConfiguration.vsCodeLmModelSelector.vendor}/${apiConfiguration.vsCodeLmModelSelector.family}`
337338
: vscodeLlmDefaultModelId
@@ -351,37 +352,37 @@ function getSelectedModel({
351352
}
352353
return { id, info }
353354
}
354-
case "sambanova": {
355+
case providerIdentifiers.sambanova: {
355356
const id = apiConfiguration.apiModelId ?? defaultModelId
356357
const info = sambaNovaModels[id as keyof typeof sambaNovaModels]
357358
return { id, info }
358359
}
359-
case "fireworks": {
360+
case providerIdentifiers.fireworks: {
360361
const id = apiConfiguration.apiModelId ?? defaultModelId
361362
const info = fireworksModels[id as keyof typeof fireworksModels]
362363
return { id, info }
363364
}
364-
case "friendli": {
365+
case providerIdentifiers.friendli: {
365366
const id = apiConfiguration.apiModelId ?? defaultModelId
366367
const info = friendliModels[id as keyof typeof friendliModels]
367368
return { id, info }
368369
}
369-
case "poe": {
370+
case providerIdentifiers.poe: {
370371
const id = apiConfiguration.apiModelId ?? defaultModelId
371372
const info = routerModels.poe?.[id]
372373
return { id, info }
373374
}
374-
case "qwen-code": {
375+
case providerIdentifiers.qwenCode: {
375376
const id = apiConfiguration.apiModelId ?? defaultModelId
376377
const info = qwenCodeModels[id as keyof typeof qwenCodeModels]
377378
return { id, info }
378379
}
379-
case "openai-codex": {
380+
case providerIdentifiers.openaiCodex: {
380381
const id = apiConfiguration.apiModelId ?? defaultModelId
381382
const info = openAiCodexModels[id as keyof typeof openAiCodexModels]
382383
return { id, info }
383384
}
384-
case "vercel-ai-gateway": {
385+
case providerIdentifiers.vercelAiGateway: {
385386
const id = getValidatedModelId(
386387
apiConfiguration.vercelAiGatewayModelId,
387388
routerModels["vercel-ai-gateway"],
@@ -390,7 +391,7 @@ function getSelectedModel({
390391
const info = routerModels["vercel-ai-gateway"]?.[id]
391392
return { id, info }
392393
}
393-
case "opencode-go": {
394+
case providerIdentifiers.opencodeGo: {
394395
const id = getValidatedModelId(
395396
apiConfiguration.opencodeGoModelId,
396397
routerModels["opencode-go"],
@@ -401,14 +402,14 @@ function getSelectedModel({
401402
const info = routerModels["opencode-go"]?.[id] ?? opencodeGoDefaultModelInfo
402403
return { id, info }
403404
}
404-
case "kenari": {
405+
case providerIdentifiers.kenari: {
405406
const id = getValidatedModelId(apiConfiguration.kenariModelId, routerModels["kenari"], defaultModelId)
406407
// Fall back to the provider's default ModelInfo so capability-driven UI
407408
// keeps working when the /models list is empty or unavailable.
408409
const info = routerModels["kenari"]?.[id] ?? kenariDefaultModelInfo
409410
return { id, info }
410411
}
411-
case "zoo-gateway": {
412+
case providerIdentifiers.zooGateway: {
412413
const id = getValidatedModelId(
413414
apiConfiguration.zooGatewayModelId,
414415
routerModels["zoo-gateway"],
@@ -417,8 +418,8 @@ function getSelectedModel({
417418
const info = routerModels["zoo-gateway"]?.[id]
418419
return { id, info }
419420
}
420-
// case "anthropic":
421-
// case "fake-ai":
421+
// case providerIdentifiers.anthropic:
422+
// case providerIdentifiers.fakeAi:
422423
default: {
423424
provider satisfies "anthropic" | "gemini-cli" | "fake-ai"
424425
const id = apiConfiguration.apiModelId ?? defaultModelId

webview-ui/src/utils/__tests__/validate.spec.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { ProviderSettings, OrganizationAllowList, RouterModels } from "@roo-code/types"
1+
import {
2+
providerIdentifiers,
3+
type ProviderSettings,
4+
type OrganizationAllowList,
5+
type RouterModels,
6+
} from "@roo-code/types"
27

38
// Mock i18next to return translation keys with interpolated values
49
vi.mock("i18next", () => ({
@@ -136,6 +141,24 @@ describe("Model Validation Functions", () => {
136141
})
137142

138143
describe("validateApiConfigurationExcludingModelErrors", () => {
144+
it("uses the canonical OpenRouter identifier for credential validation", () => {
145+
const identifiers = providerIdentifiers as Record<string, string>
146+
const originalIdentifier = identifiers.openrouter
147+
148+
try {
149+
identifiers.openrouter = "canonical-openrouter"
150+
151+
const config = {
152+
apiProvider: identifiers.openrouter,
153+
openRouterModelId: "valid-model",
154+
} as ProviderSettings
155+
156+
expect(validateApiConfigurationExcludingModelErrors(config)).toBe("settings:validation.apiKey")
157+
} finally {
158+
identifiers.openrouter = originalIdentifier
159+
}
160+
})
161+
139162
it("returns undefined when configuration is valid", () => {
140163
const config: ProviderSettings = {
141164
apiProvider: "openrouter",

0 commit comments

Comments
 (0)