Skip to content

Commit 7f52b77

Browse files
committed
feat: add qwen-code provider support with complete translations
- Add QwenCode provider type definition and models in packages/types/src/providers/qwen-code.ts - Implement OAuth-based provider in src/api/providers/qwen-code.ts with proper error handling - Add comprehensive test coverage in src/api/providers/__tests__/qwen-code.spec.ts - Create QwenCode settings UI component in webview-ui/src/components/settings/providers/QwenCode.tsx - Add qwenCodeOAuthPath field to provider settings schema - Add qwenCode error translations to all backend common.json files (17 languages) - Add qwenCode provider settings to all frontend settings.json files (17 languages) - Include OAuth path configuration and setup instructions for all locales Supported languages: ca, de, es, fr, hi, id, it, ja, ko, nl, pl, pt-BR, ru, tr, vi, zh-CN, zh-TW
1 parent fc70012 commit 7f52b77

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+666
-5
lines changed

packages/types/src/provider-settings.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export const providerNames = [
5050
"doubao",
5151
"unbound",
5252
"requesty",
53+
"qwen-code",
5354
"human-relay",
5455
"fake-ai",
5556
"xai",
@@ -311,6 +312,10 @@ const ioIntelligenceSchema = apiModelIdProviderModelSchema.extend({
311312
ioIntelligenceApiKey: z.string().optional(),
312313
})
313314

315+
const qwenCodeSchema = apiModelIdProviderModelSchema.extend({
316+
qwenCodeOAuthPath: z.string().optional(),
317+
})
318+
314319
const rooSchema = apiModelIdProviderModelSchema.extend({
315320
// No additional fields needed - uses cloud authentication
316321
})
@@ -352,6 +357,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
352357
fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })),
353358
featherlessSchema.merge(z.object({ apiProvider: z.literal("featherless") })),
354359
ioIntelligenceSchema.merge(z.object({ apiProvider: z.literal("io-intelligence") })),
360+
qwenCodeSchema.merge(z.object({ apiProvider: z.literal("qwen-code") })),
355361
rooSchema.merge(z.object({ apiProvider: z.literal("roo") })),
356362
defaultSchema,
357363
])
@@ -390,6 +396,7 @@ export const providerSettingsSchema = z.object({
390396
...fireworksSchema.shape,
391397
...featherlessSchema.shape,
392398
...ioIntelligenceSchema.shape,
399+
...qwenCodeSchema.shape,
393400
...rooSchema.shape,
394401
...codebaseIndexProviderSchema.shape,
395402
})
@@ -440,7 +447,7 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str
440447
}
441448

442449
export const MODELS_BY_PROVIDER: Record<
443-
Exclude<ProviderName, "fake-ai" | "human-relay" | "gemini-cli" | "lmstudio" | "openai" | "ollama">,
450+
Exclude<ProviderName, "fake-ai" | "human-relay" | "gemini-cli" | "lmstudio" | "openai" | "ollama" | "qwen-code">,
444451
{ id: ProviderName; label: string; models: string[] }
445452
> = {
446453
anthropic: {

packages/types/src/providers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export * from "./moonshot.js"
1919
export * from "./ollama.js"
2020
export * from "./openai.js"
2121
export * from "./openrouter.js"
22+
export * from "./qwen-code.js"
2223
export * from "./requesty.js"
2324
export * from "./roo.js"
2425
export * from "./sambanova.js"
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { ModelInfo } from "../model.js"
2+
import type { ProviderName } from "../provider-settings.js"
3+
4+
export const qwenCodeModels = {
5+
"qwen3-coder-plus": {
6+
id: "qwen3-coder-plus",
7+
name: "Qwen3 Coder Plus",
8+
provider: "qwen-code" as ProviderName,
9+
contextWindow: 1000000,
10+
maxTokens: 65536,
11+
supportsPromptCache: false,
12+
},
13+
"qwen3-coder-flash": {
14+
id: "qwen3-coder-flash",
15+
name: "Qwen3 Coder Flash",
16+
provider: "qwen-code" as ProviderName,
17+
contextWindow: 1000000,
18+
maxTokens: 65536,
19+
supportsPromptCache: false,
20+
},
21+
} as const
22+
23+
export type QwenCodeModelId = keyof typeof qwenCodeModels
24+
25+
export const qwenCodeDefaultModelId: QwenCodeModelId = "qwen3-coder-plus"
26+
27+
export const isQwenCodeModel = (modelId: string): modelId is QwenCodeModelId => {
28+
return modelId in qwenCodeModels
29+
}
30+
31+
export const getQwenCodeModelInfo = (modelId: string): ModelInfo => {
32+
if (isQwenCodeModel(modelId)) {
33+
return qwenCodeModels[modelId]
34+
}
35+
// Fallback to a default or throw an error
36+
return qwenCodeModels[qwenCodeDefaultModelId]
37+
}
38+
39+
export type QwenCodeProvider = {
40+
id: "qwen-code"
41+
apiKey?: string
42+
baseUrl?: string
43+
model: QwenCodeModelId
44+
}

src/api/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
OpenAiHandler,
1616
LmStudioHandler,
1717
GeminiHandler,
18+
QwenCodeHandler,
1819
OpenAiNativeHandler,
1920
DeepSeekHandler,
2021
MoonshotHandler,
@@ -148,6 +149,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
148149
return new RooHandler(options)
149150
case "featherless":
150151
return new FeatherlessHandler(options)
152+
case "qwen-code":
153+
return new QwenCodeHandler(options)
151154
default:
152155
apiProvider satisfies "gemini-cli" | undefined
153156
return new AnthropicHandler(options)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { describe, it, expect, vi } from "vitest"
2+
import { QwenCodeHandler } from "../qwen-code"
3+
import { ApiHandlerOptions } from "../../../shared/api"
4+
5+
// Mock fs
6+
vi.mock("fs", () => ({
7+
existsSync: vi.fn(),
8+
readFileSync: vi.fn(),
9+
}))
10+
11+
// Mock os
12+
vi.mock("os", () => ({
13+
homedir: () => "/home/user",
14+
}))
15+
16+
// Mock path
17+
vi.mock("path", () => ({
18+
resolve: vi.fn((...args) => args.join("/")),
19+
join: vi.fn((...args) => args.join("/")),
20+
}))
21+
22+
describe("QwenCodeHandler", () => {
23+
it("should initialize with correct model configuration", () => {
24+
const options: ApiHandlerOptions = {
25+
apiModelId: "qwen3-coder-plus",
26+
}
27+
const handler = new QwenCodeHandler(options)
28+
29+
const model = handler.getModel()
30+
expect(model.id).toBe("qwen3-coder-plus")
31+
expect(model.info).toBeDefined()
32+
expect(model.info?.supportsPromptCache).toBe(false)
33+
})
34+
35+
it("should use default model when none specified", () => {
36+
const options: ApiHandlerOptions = {}
37+
const handler = new QwenCodeHandler(options)
38+
39+
const model = handler.getModel()
40+
expect(model.id).toBe("qwen3-coder-plus") // default model
41+
expect(model.info).toBeDefined()
42+
})
43+
44+
it("should use custom oauth path when provided", () => {
45+
const customPath = "/custom/path/oauth.json"
46+
const options: ApiHandlerOptions = {
47+
qwenCodeOAuthPath: customPath,
48+
}
49+
const handler = new QwenCodeHandler(options)
50+
51+
// Handler should initialize without throwing
52+
expect(handler).toBeDefined()
53+
})
54+
})

src/api/providers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export { OllamaHandler } from "./ollama"
2121
export { OpenAiNativeHandler } from "./openai-native"
2222
export { OpenAiHandler } from "./openai"
2323
export { OpenRouterHandler } from "./openrouter"
24+
export { QwenCodeHandler } from "./qwen-code"
2425
export { RequestyHandler } from "./requesty"
2526
export { SambaNovaHandler } from "./sambanova"
2627
export { UnboundHandler } from "./unbound"

0 commit comments

Comments
 (0)