Skip to content

Commit 43eaecd

Browse files
authored
Merge branch 'main' into fix/rename-roo-to-zoo-user-strings
2 parents fe9c12e + 2db2af0 commit 43eaecd

7 files changed

Lines changed: 435 additions & 162 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { ProviderName } from "../provider-settings.js"
2+
3+
vi.mock("../provider-identifiers.js", async (importOriginal) => {
4+
const actual = await importOriginal<typeof import("../provider-identifiers.js")>()
5+
6+
return {
7+
...actual,
8+
providerIdentifiers: {
9+
...actual.providerIdentifiers,
10+
openrouter: "canonical-openrouter-test-value",
11+
},
12+
}
13+
})
14+
15+
import { providerIdentifiers } from "../provider-identifiers.js"
16+
import {
17+
anthropicDefaultModelId,
18+
getProviderDefaultModelId,
19+
internationalZAiDefaultModelId,
20+
kimiCodeDefaultModelId,
21+
mainlandZAiDefaultModelId,
22+
openRouterDefaultModelId,
23+
vscodeLlmDefaultModelId,
24+
zooGatewayDefaultModelId,
25+
} from "../providers/index.js"
26+
27+
describe("getProviderDefaultModelId", () => {
28+
it("selects a static default through the canonical provider identifier", () => {
29+
expect(getProviderDefaultModelId(providerIdentifiers.openrouter as ProviderName)).toBe(openRouterDefaultModelId)
30+
})
31+
32+
it("triangulates static selection with another provider category", () => {
33+
expect(getProviderDefaultModelId(providerIdentifiers.vscodeLm)).toBe(vscodeLlmDefaultModelId)
34+
})
35+
36+
it.each([
37+
[providerIdentifiers.kimiCode, kimiCodeDefaultModelId],
38+
[providerIdentifiers.zooGateway, zooGatewayDefaultModelId],
39+
])("preserves the %s default added on main", (provider, expectedModelId) => {
40+
expect(getProviderDefaultModelId(provider)).toBe(expectedModelId)
41+
})
42+
43+
it("preserves region-dependent defaults", () => {
44+
// These defaults currently share the same model ID, so the assertions document
45+
// both branches but cannot detect swapped ternary arms until the IDs diverge.
46+
expect(getProviderDefaultModelId(providerIdentifiers.zai, { isChina: true })).toBe(mainlandZAiDefaultModelId)
47+
expect(getProviderDefaultModelId(providerIdentifiers.zai)).toBe(internationalZAiDefaultModelId)
48+
})
49+
50+
it.each([providerIdentifiers.openai, providerIdentifiers.ollama, providerIdentifiers.lmstudio])(
51+
"returns an empty default for custom or locally selected models from %s",
52+
(provider) => {
53+
expect(getProviderDefaultModelId(provider)).toBe("")
54+
},
55+
)
56+
57+
it.each([providerIdentifiers.anthropic, providerIdentifiers.geminiCli, providerIdentifiers.fakeAi])(
58+
"preserves the Anthropic fallback for %s",
59+
(provider) => {
60+
expect(getProviderDefaultModelId(provider)).toBe(anthropicDefaultModelId)
61+
},
62+
)
63+
})

packages/types/src/providers/index.ts

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ import { zooGatewayDefaultModelId } from "./zoo-gateway.js"
6262

6363
// Import the ProviderName type from provider-settings to avoid duplication
6464
import type { ProviderName } from "../provider-settings.js"
65+
import { providerIdentifiers } from "../provider-identifiers.js"
66+
67+
const NO_DEFAULT_MODEL_ID = ""
6568

6669
/**
6770
* Get the default model ID for a given provider.
@@ -73,71 +76,70 @@ export function getProviderDefaultModelId(
7376
options: { isChina?: boolean } = { isChina: false },
7477
): string {
7578
switch (provider) {
76-
case "openrouter":
79+
case providerIdentifiers.openrouter:
7780
return openRouterDefaultModelId
78-
case "requesty":
81+
case providerIdentifiers.requesty:
7982
return requestyDefaultModelId
80-
case "litellm":
83+
case providerIdentifiers.litellm:
8184
return litellmDefaultModelId
82-
case "xai":
85+
case providerIdentifiers.xai:
8386
return xaiDefaultModelId
84-
case "baseten":
87+
case providerIdentifiers.baseten:
8588
return basetenDefaultModelId
86-
case "bedrock":
89+
case providerIdentifiers.bedrock:
8790
return bedrockDefaultModelId
88-
case "vertex":
91+
case providerIdentifiers.vertex:
8992
return vertexDefaultModelId
90-
case "gemini":
93+
case providerIdentifiers.gemini:
9194
return geminiDefaultModelId
92-
case "deepseek":
95+
case providerIdentifiers.deepseek:
9396
return deepSeekDefaultModelId
94-
case "moonshot":
97+
case providerIdentifiers.moonshot:
9598
return moonshotDefaultModelId
96-
case "minimax":
99+
case providerIdentifiers.minimax:
97100
return minimaxDefaultModelId
98-
case "mimo":
101+
case providerIdentifiers.mimo:
99102
return mimoDefaultModelId
100-
case "zai":
103+
case providerIdentifiers.zai:
101104
return options?.isChina ? mainlandZAiDefaultModelId : internationalZAiDefaultModelId
102-
case "openai-native":
105+
case providerIdentifiers.openaiNative:
106+
// TODO(#992): Replace this stale fallback with openAiNativeDefaultModelId.
103107
return "gpt-4o" // Based on openai-native patterns
104-
case "openai-codex":
108+
case providerIdentifiers.openaiCodex:
105109
return openAiCodexDefaultModelId
106-
case "mistral":
110+
case providerIdentifiers.mistral:
107111
return mistralDefaultModelId
108-
case "openai":
109-
return "" // OpenAI provider uses custom model configuration
110-
case "ollama":
111-
return "" // Ollama uses dynamic model selection
112-
case "lmstudio":
113-
return "" // LMStudio uses dynamic model selection
114-
case "vscode-lm":
112+
case providerIdentifiers.openai:
113+
case providerIdentifiers.ollama:
114+
case providerIdentifiers.lmstudio:
115+
return NO_DEFAULT_MODEL_ID
116+
case providerIdentifiers.vscodeLm:
115117
return vscodeLlmDefaultModelId
116-
case "sambanova":
118+
case providerIdentifiers.sambanova:
117119
return sambaNovaDefaultModelId
118-
case "fireworks":
120+
case providerIdentifiers.fireworks:
119121
return fireworksDefaultModelId
120-
case "friendli":
122+
case providerIdentifiers.friendli:
121123
return friendliDefaultModelId
122-
case "qwen-code":
124+
case providerIdentifiers.qwenCode:
123125
return qwenCodeDefaultModelId
124-
case "poe":
126+
case providerIdentifiers.poe:
125127
return poeDefaultModelId
126-
case "unbound":
128+
case providerIdentifiers.unbound:
127129
return unboundDefaultModelId
128-
case "vercel-ai-gateway":
130+
case providerIdentifiers.vercelAiGateway:
129131
return vercelAiGatewayDefaultModelId
130-
case "opencode-go":
132+
case providerIdentifiers.opencodeGo:
131133
return opencodeGoDefaultModelId
132-
case "kenari":
134+
case providerIdentifiers.kenari:
133135
return kenariDefaultModelId
134-
case "kimi-code":
136+
case providerIdentifiers.kimiCode:
135137
return kimiCodeDefaultModelId
136-
case "zoo-gateway":
138+
case providerIdentifiers.zooGateway:
137139
return zooGatewayDefaultModelId
138-
case "anthropic":
139-
case "gemini-cli":
140-
case "fake-ai":
140+
case providerIdentifiers.anthropic:
141+
case providerIdentifiers.geminiCli:
142+
case providerIdentifiers.fakeAi:
141143
default:
142144
return anthropicDefaultModelId
143145
}

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
})

0 commit comments

Comments
 (0)