Skip to content

Commit 562869a

Browse files
WebMadrrewll
authored andcommitted
refactor(api): use canonical provider identifiers (Zoo-Code-Org#1012)
* refactor(api): use canonical provider identifiers * test(api): cover canonical provider routing * test(api): enforce exhaustive provider routing * test(api): avoid concrete Vertex model IDs
1 parent fe9c12e commit 562869a

2 files changed

Lines changed: 177 additions & 45 deletions

File tree

src/api/__tests__/index.spec.ts

Lines changed: 136 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,147 @@ vitest.mock("vscode", () => ({
99
},
1010
}))
1111

12-
import type { ProviderSettings } from "@roo-code/types"
12+
// Handler constructors can require credentials or initialize SDK clients. Replace them
13+
// with inert classes so these tests exercise only the factory's routing behavior.
14+
vitest.mock("../providers", async () => {
15+
const providers = await vitest.importActual<Record<string, unknown>>("../providers")
16+
17+
return Object.fromEntries(Object.keys(providers).map((name) => [name, class {}]))
18+
})
19+
20+
vitest.mock("../providers/native-ollama", () => ({
21+
NativeOllamaHandler: class {},
22+
}))
23+
24+
import {
25+
providerIdentifiers,
26+
retiredProviderIdentifiers,
27+
type ProviderName,
28+
type ProviderNameWithRetired,
29+
} from "@roo-code/types"
1330

1431
import { buildApiHandler } from "../index"
15-
import { KenariHandler } from "../providers/kenari"
32+
import {
33+
AnthropicHandler,
34+
AnthropicVertexHandler,
35+
AwsBedrockHandler,
36+
BasetenHandler,
37+
DeepSeekHandler,
38+
FakeAIHandler,
39+
FireworksHandler,
40+
FriendliHandler,
41+
GeminiHandler,
42+
KenariHandler,
43+
KimiCodeHandler,
44+
LiteLLMHandler,
45+
LmStudioHandler,
46+
MiniMaxHandler,
47+
MimoHandler,
48+
MistralHandler,
49+
MoonshotHandler,
50+
OpenAiCodexHandler,
51+
OpenAiHandler,
52+
OpenAiNativeHandler,
53+
OpencodeGoHandler,
54+
OpenRouterHandler,
55+
PoeHandler,
56+
QwenCodeHandler,
57+
RequestyHandler,
58+
SambaNovaHandler,
59+
UnboundHandler,
60+
VercelAiGatewayHandler,
61+
VertexHandler,
62+
VsCodeLmHandler,
63+
XAIHandler,
64+
ZAiHandler,
65+
ZooGatewayHandler,
66+
} from "../providers"
67+
import { NativeOllamaHandler } from "../providers/native-ollama"
68+
69+
type HandlerConstructor = new (...args: never[]) => object
70+
71+
const expectedHandlers = {
72+
[providerIdentifiers.anthropic]: AnthropicHandler,
73+
[providerIdentifiers.openrouter]: OpenRouterHandler,
74+
[providerIdentifiers.bedrock]: AwsBedrockHandler,
75+
[providerIdentifiers.openai]: OpenAiHandler,
76+
[providerIdentifiers.ollama]: NativeOllamaHandler,
77+
[providerIdentifiers.lmstudio]: LmStudioHandler,
78+
[providerIdentifiers.gemini]: GeminiHandler,
79+
// Gemini CLI currently relies on the factory's default Anthropic handler.
80+
[providerIdentifiers.geminiCli]: AnthropicHandler,
81+
[providerIdentifiers.openaiCodex]: OpenAiCodexHandler,
82+
[providerIdentifiers.openaiNative]: OpenAiNativeHandler,
83+
[providerIdentifiers.deepseek]: DeepSeekHandler,
84+
[providerIdentifiers.qwenCode]: QwenCodeHandler,
85+
[providerIdentifiers.moonshot]: MoonshotHandler,
86+
[providerIdentifiers.kimiCode]: KimiCodeHandler,
87+
[providerIdentifiers.vscodeLm]: VsCodeLmHandler,
88+
[providerIdentifiers.mistral]: MistralHandler,
89+
[providerIdentifiers.requesty]: RequestyHandler,
90+
[providerIdentifiers.unbound]: UnboundHandler,
91+
[providerIdentifiers.fakeAi]: FakeAIHandler,
92+
[providerIdentifiers.xai]: XAIHandler,
93+
[providerIdentifiers.litellm]: LiteLLMHandler,
94+
[providerIdentifiers.sambanova]: SambaNovaHandler,
95+
[providerIdentifiers.mimo]: MimoHandler,
96+
[providerIdentifiers.zai]: ZAiHandler,
97+
[providerIdentifiers.fireworks]: FireworksHandler,
98+
[providerIdentifiers.friendli]: FriendliHandler,
99+
[providerIdentifiers.vercelAiGateway]: VercelAiGatewayHandler,
100+
[providerIdentifiers.opencodeGo]: OpencodeGoHandler,
101+
[providerIdentifiers.kenari]: KenariHandler,
102+
[providerIdentifiers.zooGateway]: ZooGatewayHandler,
103+
[providerIdentifiers.minimax]: MiniMaxHandler,
104+
[providerIdentifiers.baseten]: BasetenHandler,
105+
[providerIdentifiers.poe]: PoeHandler,
106+
} satisfies Record<Exclude<ProviderName, typeof providerIdentifiers.vertex>, HandlerConstructor>
107+
108+
const expectedHandlerEntries = Object.entries(expectedHandlers) as Array<
109+
[Exclude<ProviderName, typeof providerIdentifiers.vertex>, HandlerConstructor]
110+
>
16111

17112
describe("buildApiHandler", () => {
18-
it("returns a KenariHandler for the kenari provider", () => {
19-
const configuration: ProviderSettings = {
20-
apiProvider: "kenari",
21-
kenariApiKey: "test-key",
22-
kenariModelId: "glm-5-2",
23-
}
113+
it.each(expectedHandlerEntries)("returns the expected handler for %s", (apiProvider, Handler) => {
114+
const handler = buildApiHandler({ apiProvider })
115+
116+
expect(handler).toBeInstanceOf(Handler)
117+
})
118+
119+
it.each([
120+
["an unspecified model", undefined, VertexHandler],
121+
["non-Claude models", "non-claude-test-model", VertexHandler],
122+
["Claude models", "claude-test-model", AnthropicVertexHandler],
123+
] as const)("returns the expected Vertex handler for %s", (_description, apiModelId, Handler) => {
124+
const handler = buildApiHandler({
125+
apiProvider: providerIdentifiers.vertex,
126+
apiModelId,
127+
})
128+
129+
expect(handler).toBeInstanceOf(Handler)
130+
})
131+
132+
it("preserves the dedicated removal error for the retired Roo provider", () => {
133+
expect(() =>
134+
buildApiHandler({
135+
apiProvider: retiredProviderIdentifiers.roo,
136+
}),
137+
).toThrow("Roo Code Router has been removed")
138+
})
139+
140+
it("rejects other retired providers", () => {
141+
expect(() =>
142+
buildApiHandler({
143+
apiProvider: retiredProviderIdentifiers.cerebras,
144+
}),
145+
).toThrow("this provider is no longer supported")
146+
})
24147

25-
const handler = buildApiHandler(configuration)
148+
it("falls back to Anthropic for an unsupported provider value", () => {
149+
const handler = buildApiHandler({
150+
apiProvider: "unsupported-provider" as ProviderNameWithRetired,
151+
})
26152

27-
expect(handler).toBeInstanceOf(KenariHandler)
153+
expect(handler).toBeInstanceOf(AnthropicHandler)
28154
})
29155
})

src/api/index.ts

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { Anthropic } from "@anthropic-ai/sdk"
22
import OpenAI from "openai"
33

4-
import { isRetiredProvider, type ProviderSettings, type ModelInfo } from "@roo-code/types"
4+
import {
5+
isRetiredProvider,
6+
providerIdentifiers,
7+
retiredProviderIdentifiers,
8+
type ProviderSettings,
9+
type ModelInfo,
10+
} from "@roo-code/types"
511

612
import { getRouterRemovalMessage } from "../core/config/routerRemoval"
713
import { ApiStream } from "./transform/stream"
@@ -140,7 +146,7 @@ export interface ApiHandler {
140146
export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
141147
const { apiProvider, ...options } = configuration
142148

143-
if (apiProvider === "roo") {
149+
if (apiProvider === retiredProviderIdentifiers.roo) {
144150
throw new Error(getRouterRemovalMessage())
145151
}
146152

@@ -151,73 +157,73 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
151157
}
152158

153159
switch (apiProvider) {
154-
case "anthropic":
160+
case providerIdentifiers.anthropic:
155161
return new AnthropicHandler(options)
156-
case "openrouter":
162+
case providerIdentifiers.openrouter:
157163
return new OpenRouterHandler(options)
158-
case "bedrock":
164+
case providerIdentifiers.bedrock:
159165
return new AwsBedrockHandler(options)
160-
case "vertex":
166+
case providerIdentifiers.vertex:
161167
return options.apiModelId?.startsWith("claude")
162168
? new AnthropicVertexHandler(options)
163169
: new VertexHandler(options)
164-
case "openai":
170+
case providerIdentifiers.openai:
165171
return new OpenAiHandler(options)
166-
case "ollama":
172+
case providerIdentifiers.ollama:
167173
return new NativeOllamaHandler(options)
168-
case "lmstudio":
174+
case providerIdentifiers.lmstudio:
169175
return new LmStudioHandler(options)
170-
case "gemini":
176+
case providerIdentifiers.gemini:
171177
return new GeminiHandler(options)
172-
case "openai-codex":
178+
case providerIdentifiers.openaiCodex:
173179
return new OpenAiCodexHandler(options)
174-
case "openai-native":
180+
case providerIdentifiers.openaiNative:
175181
return new OpenAiNativeHandler(options)
176-
case "deepseek":
182+
case providerIdentifiers.deepseek:
177183
return new DeepSeekHandler(options)
178-
case "qwen-code":
184+
case providerIdentifiers.qwenCode:
179185
return new QwenCodeHandler(options)
180-
case "moonshot":
186+
case providerIdentifiers.moonshot:
181187
return new MoonshotHandler(options)
182-
case "kimi-code":
188+
case providerIdentifiers.kimiCode:
183189
return new KimiCodeHandler(options)
184-
case "vscode-lm":
190+
case providerIdentifiers.vscodeLm:
185191
return new VsCodeLmHandler(options)
186-
case "mistral":
192+
case providerIdentifiers.mistral:
187193
return new MistralHandler(options)
188-
case "requesty":
194+
case providerIdentifiers.requesty:
189195
return new RequestyHandler(options)
190-
case "unbound":
196+
case providerIdentifiers.unbound:
191197
return new UnboundHandler(options)
192-
case "fake-ai":
198+
case providerIdentifiers.fakeAi:
193199
return new FakeAIHandler(options)
194-
case "xai":
200+
case providerIdentifiers.xai:
195201
return new XAIHandler(options)
196-
case "litellm":
202+
case providerIdentifiers.litellm:
197203
return new LiteLLMHandler(options)
198-
case "sambanova":
204+
case providerIdentifiers.sambanova:
199205
return new SambaNovaHandler(options)
200-
case "mimo":
206+
case providerIdentifiers.mimo:
201207
return new MimoHandler(options)
202-
case "zai":
208+
case providerIdentifiers.zai:
203209
return new ZAiHandler(options)
204-
case "fireworks":
210+
case providerIdentifiers.fireworks:
205211
return new FireworksHandler(options)
206-
case "friendli":
212+
case providerIdentifiers.friendli:
207213
return new FriendliHandler(options)
208-
case "vercel-ai-gateway":
214+
case providerIdentifiers.vercelAiGateway:
209215
return new VercelAiGatewayHandler(options)
210-
case "opencode-go":
216+
case providerIdentifiers.opencodeGo:
211217
return new OpencodeGoHandler(options)
212-
case "kenari":
218+
case providerIdentifiers.kenari:
213219
return new KenariHandler(options)
214-
case "zoo-gateway":
220+
case providerIdentifiers.zooGateway:
215221
return new ZooGatewayHandler(options)
216-
case "minimax":
222+
case providerIdentifiers.minimax:
217223
return new MiniMaxHandler(options)
218-
case "baseten":
224+
case providerIdentifiers.baseten:
219225
return new BasetenHandler(options)
220-
case "poe":
226+
case providerIdentifiers.poe:
221227
return new PoeHandler(options)
222228
default:
223229
return new AnthropicHandler(options)

0 commit comments

Comments
 (0)