Skip to content

Commit efa91cf

Browse files
committed
feat: Z AI provider: restrict to coding endpoints and migrate settings (#8687)
1 parent 9aee31f commit efa91cf

File tree

6 files changed

+81
-22
lines changed

6 files changed

+81
-22
lines changed

packages/types/src/provider-settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ const sambaNovaSchema = apiModelIdProviderModelSchema.extend({
377377
sambaNovaApiKey: z.string().optional(),
378378
})
379379

380-
export const zaiApiLineSchema = z.enum(["international_coding", "international", "china_coding", "china"])
380+
export const zaiApiLineSchema = z.enum(["international_coding", "china_coding"])
381381

382382
export type ZaiApiLine = z.infer<typeof zaiApiLineSchema>
383383

packages/types/src/providers/zai.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ export const zaiApiLineConfigs = {
161161
baseUrl: "https://api.z.ai/api/coding/paas/v4",
162162
isChina: false,
163163
},
164-
international: { name: "International Standard", baseUrl: "https://api.z.ai/api/paas/v4", isChina: false },
165-
china_coding: { name: "China Coding Plan", baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4", isChina: true },
166-
china: { name: "China Standard", baseUrl: "https://open.bigmodel.cn/api/paas/v4", isChina: true },
164+
china_coding: {
165+
name: "China Coding Plan",
166+
baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
167+
isChina: true,
168+
},
167169
} satisfies Record<ZaiApiLine, { name: string; baseUrl: string; isChina: boolean }>

src/api/providers/__tests__/zai.spec.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,21 @@ describe("ZAiHandler", () => {
3636

3737
describe("International Z AI", () => {
3838
beforeEach(() => {
39-
handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international" })
39+
handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international_coding" })
4040
})
4141

4242
it("should use the correct international Z AI base URL", () => {
43-
new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international" })
43+
new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international_coding" })
4444
expect(OpenAI).toHaveBeenCalledWith(
4545
expect.objectContaining({
46-
baseURL: "https://api.z.ai/api/paas/v4",
46+
baseURL: "https://api.z.ai/api/coding/paas/v4",
4747
}),
4848
)
4949
})
5050

5151
it("should use the provided API key for international", () => {
5252
const zaiApiKey = "test-zai-api-key"
53-
new ZAiHandler({ zaiApiKey, zaiApiLine: "international" })
53+
new ZAiHandler({ zaiApiKey, zaiApiLine: "international_coding" })
5454
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: zaiApiKey }))
5555
})
5656

@@ -65,7 +65,7 @@ describe("ZAiHandler", () => {
6565
const handlerWithModel = new ZAiHandler({
6666
apiModelId: testModelId,
6767
zaiApiKey: "test-zai-api-key",
68-
zaiApiLine: "international",
68+
zaiApiLine: "international_coding",
6969
})
7070
const model = handlerWithModel.getModel()
7171
expect(model.id).toBe(testModelId)
@@ -77,7 +77,7 @@ describe("ZAiHandler", () => {
7777
const handlerWithModel = new ZAiHandler({
7878
apiModelId: testModelId,
7979
zaiApiKey: "test-zai-api-key",
80-
zaiApiLine: "international",
80+
zaiApiLine: "international_coding",
8181
})
8282
const model = handlerWithModel.getModel()
8383
expect(model.id).toBe(testModelId)
@@ -88,19 +88,19 @@ describe("ZAiHandler", () => {
8888

8989
describe("China Z AI", () => {
9090
beforeEach(() => {
91-
handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "china" })
91+
handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "china_coding" })
9292
})
9393

9494
it("should use the correct China Z AI base URL", () => {
95-
new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "china" })
95+
new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "china_coding" })
9696
expect(OpenAI).toHaveBeenCalledWith(
97-
expect.objectContaining({ baseURL: "https://open.bigmodel.cn/api/paas/v4" }),
97+
expect.objectContaining({ baseURL: "https://open.bigmodel.cn/api/coding/paas/v4" }),
9898
)
9999
})
100100

101101
it("should use the provided API key for China", () => {
102102
const zaiApiKey = "test-zai-api-key"
103-
new ZAiHandler({ zaiApiKey, zaiApiLine: "china" })
103+
new ZAiHandler({ zaiApiKey, zaiApiLine: "china_coding" })
104104
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: zaiApiKey }))
105105
})
106106

@@ -115,7 +115,7 @@ describe("ZAiHandler", () => {
115115
const handlerWithModel = new ZAiHandler({
116116
apiModelId: testModelId,
117117
zaiApiKey: "test-zai-api-key",
118-
zaiApiLine: "china",
118+
zaiApiLine: "china_coding",
119119
})
120120
const model = handlerWithModel.getModel()
121121
expect(model.id).toBe(testModelId)
@@ -127,7 +127,7 @@ describe("ZAiHandler", () => {
127127
const handlerWithModel = new ZAiHandler({
128128
apiModelId: testModelId,
129129
zaiApiKey: "test-zai-api-key",
130-
zaiApiLine: "china",
130+
zaiApiLine: "china_coding",
131131
})
132132
const model = handlerWithModel.getModel()
133133
expect(model.id).toBe(testModelId)
@@ -151,14 +151,14 @@ describe("ZAiHandler", () => {
151151
})
152152

153153
it("should use 'not-provided' as default API key when none is specified", () => {
154-
new ZAiHandler({ zaiApiLine: "international" })
154+
new ZAiHandler({ zaiApiLine: "international_coding" })
155155
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: "not-provided" }))
156156
})
157157
})
158158

159159
describe("API Methods", () => {
160160
beforeEach(() => {
161-
handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international" })
161+
handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international_coding" })
162162
})
163163

164164
it("completePrompt method should return text from Z AI API", async () => {
@@ -231,7 +231,7 @@ describe("ZAiHandler", () => {
231231
const handlerWithModel = new ZAiHandler({
232232
apiModelId: modelId,
233233
zaiApiKey: "test-zai-api-key",
234-
zaiApiLine: "international",
234+
zaiApiLine: "international_coding",
235235
})
236236

237237
mockCreate.mockImplementationOnce(() => {

src/core/config/ProviderSettingsManager.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export const providerProfilesSchema = z.object({
4646
openAiHeadersMigrated: z.boolean().optional(),
4747
consecutiveMistakeLimitMigrated: z.boolean().optional(),
4848
todoListEnabledMigrated: z.boolean().optional(),
49+
zaiApiLineCodingOnlyMigrated: z.boolean().optional(),
4950
})
5051
.optional(),
5152
})
@@ -70,6 +71,7 @@ export class ProviderSettingsManager {
7071
openAiHeadersMigrated: true, // Mark as migrated on fresh installs
7172
consecutiveMistakeLimitMigrated: true, // Mark as migrated on fresh installs
7273
todoListEnabledMigrated: true, // Mark as migrated on fresh installs
74+
zaiApiLineCodingOnlyMigrated: true, // Mark as migrated on fresh installs
7375
},
7476
}
7577

@@ -176,6 +178,15 @@ export class ProviderSettingsManager {
176178
isDirty = true
177179
}
178180

181+
// Migrate Z AI api line to coding-only options
182+
if (!providerProfiles.migrations.zaiApiLineCodingOnlyMigrated) {
183+
const changed = await this.migrateZaiApiLineCodingOnly(providerProfiles)
184+
if (changed) {
185+
providerProfiles.migrations.zaiApiLineCodingOnlyMigrated = true
186+
isDirty = true
187+
}
188+
}
189+
179190
if (isDirty) {
180191
await this.store(providerProfiles)
181192
}
@@ -573,7 +584,9 @@ export class ProviderSettingsManager {
573584

574585
const apiConfigs = Object.entries(providerProfiles.apiConfigs).reduce(
575586
(acc, [key, apiConfig]) => {
576-
const result = providerSettingsWithIdSchema.safeParse(apiConfig)
587+
// Preprocess legacy settings before validation (e.g., Z AI api line)
588+
const processed = this.preprocessLegacySettings(apiConfig)
589+
const result = providerSettingsWithIdSchema.safeParse(processed)
577590
return result.success ? { ...acc, [key]: result.data } : acc
578591
},
579592
{} as Record<string, ProviderSettingsWithId>,
@@ -605,6 +618,50 @@ export class ProviderSettingsManager {
605618
}
606619
}
607620

621+
/**
622+
* Preprocess legacy settings objects before schema validation.
623+
* For example, coerce deprecated Z AI api lines to coding-only variants.
624+
*/
625+
private preprocessLegacySettings(apiConfig: any): any {
626+
try {
627+
if (apiConfig && apiConfig.apiProvider === "zai") {
628+
const line = apiConfig.zaiApiLine
629+
if (line === "international" || line === "intl_standard") {
630+
apiConfig.zaiApiLine = "international_coding"
631+
} else if (line === "china" || line === "china_standard") {
632+
apiConfig.zaiApiLine = "china_coding"
633+
}
634+
}
635+
} catch {
636+
// no-op
637+
}
638+
return apiConfig
639+
}
640+
641+
/**
642+
* Migrate Z AI api line values to coding-only options across all profiles.
643+
*/
644+
private async migrateZaiApiLineCodingOnly(providerProfiles: ProviderProfiles): Promise<boolean> {
645+
let changed = false
646+
try {
647+
for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) {
648+
if ((apiConfig as any)?.apiProvider === "zai") {
649+
const cfg = apiConfig as any
650+
if (cfg.zaiApiLine === "international" || cfg.zaiApiLine === "intl_standard") {
651+
cfg.zaiApiLine = "international_coding"
652+
changed = true
653+
} else if (cfg.zaiApiLine === "china" || cfg.zaiApiLine === "china_standard") {
654+
cfg.zaiApiLine = "china_coding"
655+
changed = true
656+
}
657+
}
658+
}
659+
} catch (error) {
660+
console.error(`[MigrateZaiApiLine] Failed to migrate Z AI api line settings:`, error)
661+
}
662+
return changed
663+
}
664+
608665
private findUniqueProfileName(baseName: string, existingNames: Set<string>): string {
609666
if (!existingNames.has(baseName)) {
610667
return baseName

webview-ui/src/components/settings/ApiOptions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ const ApiOptions = ({
345345
zai: {
346346
field: "apiModelId",
347347
default:
348-
apiConfiguration.zaiApiLine === "china"
348+
apiConfiguration.zaiApiLine === "china_coding"
349349
? mainlandZAiDefaultModelId
350350
: internationalZAiDefaultModelId,
351351
},

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ function getSelectedModel({
238238
return { id, info }
239239
}
240240
case "zai": {
241-
const isChina = apiConfiguration.zaiApiLine === "china"
241+
const isChina = apiConfiguration.zaiApiLine === "china_coding"
242242
const models = isChina ? mainlandZAiModels : internationalZAiModels
243243
const defaultModelId = isChina ? mainlandZAiDefaultModelId : internationalZAiDefaultModelId
244244
const id = apiConfiguration.apiModelId ?? defaultModelId

0 commit comments

Comments
 (0)