From efa91cfc6f5f427ad200bc9166813a2ef8a6cdc8 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 16 Oct 2025 19:33:46 -0600 Subject: [PATCH 1/2] feat: Z AI provider: restrict to coding endpoints and migrate settings (#8687) --- packages/types/src/provider-settings.ts | 2 +- packages/types/src/providers/zai.ts | 8 ++- src/api/providers/__tests__/zai.spec.ts | 30 +++++----- src/core/config/ProviderSettingsManager.ts | 59 ++++++++++++++++++- .../src/components/settings/ApiOptions.tsx | 2 +- .../components/ui/hooks/useSelectedModel.ts | 2 +- 6 files changed, 81 insertions(+), 22 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index a66aae08a24..5262e7602d6 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -377,7 +377,7 @@ const sambaNovaSchema = apiModelIdProviderModelSchema.extend({ sambaNovaApiKey: z.string().optional(), }) -export const zaiApiLineSchema = z.enum(["international_coding", "international", "china_coding", "china"]) +export const zaiApiLineSchema = z.enum(["international_coding", "china_coding"]) export type ZaiApiLine = z.infer diff --git a/packages/types/src/providers/zai.ts b/packages/types/src/providers/zai.ts index de8c6cd4e49..b4049b81228 100644 --- a/packages/types/src/providers/zai.ts +++ b/packages/types/src/providers/zai.ts @@ -161,7 +161,9 @@ export const zaiApiLineConfigs = { baseUrl: "https://api.z.ai/api/coding/paas/v4", isChina: false, }, - international: { name: "International Standard", baseUrl: "https://api.z.ai/api/paas/v4", isChina: false }, - china_coding: { name: "China Coding Plan", baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4", isChina: true }, - china: { name: "China Standard", baseUrl: "https://open.bigmodel.cn/api/paas/v4", isChina: true }, + china_coding: { + name: "China Coding Plan", + baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4", + isChina: true, + }, } satisfies Record diff --git a/src/api/providers/__tests__/zai.spec.ts b/src/api/providers/__tests__/zai.spec.ts index 383394b703c..bb892960889 100644 --- a/src/api/providers/__tests__/zai.spec.ts +++ b/src/api/providers/__tests__/zai.spec.ts @@ -36,21 +36,21 @@ describe("ZAiHandler", () => { describe("International Z AI", () => { beforeEach(() => { - handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international" }) + handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international_coding" }) }) it("should use the correct international Z AI base URL", () => { - new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international" }) + new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international_coding" }) expect(OpenAI).toHaveBeenCalledWith( expect.objectContaining({ - baseURL: "https://api.z.ai/api/paas/v4", + baseURL: "https://api.z.ai/api/coding/paas/v4", }), ) }) it("should use the provided API key for international", () => { const zaiApiKey = "test-zai-api-key" - new ZAiHandler({ zaiApiKey, zaiApiLine: "international" }) + new ZAiHandler({ zaiApiKey, zaiApiLine: "international_coding" }) expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: zaiApiKey })) }) @@ -65,7 +65,7 @@ describe("ZAiHandler", () => { const handlerWithModel = new ZAiHandler({ apiModelId: testModelId, zaiApiKey: "test-zai-api-key", - zaiApiLine: "international", + zaiApiLine: "international_coding", }) const model = handlerWithModel.getModel() expect(model.id).toBe(testModelId) @@ -77,7 +77,7 @@ describe("ZAiHandler", () => { const handlerWithModel = new ZAiHandler({ apiModelId: testModelId, zaiApiKey: "test-zai-api-key", - zaiApiLine: "international", + zaiApiLine: "international_coding", }) const model = handlerWithModel.getModel() expect(model.id).toBe(testModelId) @@ -88,19 +88,19 @@ describe("ZAiHandler", () => { describe("China Z AI", () => { beforeEach(() => { - handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "china" }) + handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "china_coding" }) }) it("should use the correct China Z AI base URL", () => { - new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "china" }) + new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "china_coding" }) expect(OpenAI).toHaveBeenCalledWith( - expect.objectContaining({ baseURL: "https://open.bigmodel.cn/api/paas/v4" }), + expect.objectContaining({ baseURL: "https://open.bigmodel.cn/api/coding/paas/v4" }), ) }) it("should use the provided API key for China", () => { const zaiApiKey = "test-zai-api-key" - new ZAiHandler({ zaiApiKey, zaiApiLine: "china" }) + new ZAiHandler({ zaiApiKey, zaiApiLine: "china_coding" }) expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: zaiApiKey })) }) @@ -115,7 +115,7 @@ describe("ZAiHandler", () => { const handlerWithModel = new ZAiHandler({ apiModelId: testModelId, zaiApiKey: "test-zai-api-key", - zaiApiLine: "china", + zaiApiLine: "china_coding", }) const model = handlerWithModel.getModel() expect(model.id).toBe(testModelId) @@ -127,7 +127,7 @@ describe("ZAiHandler", () => { const handlerWithModel = new ZAiHandler({ apiModelId: testModelId, zaiApiKey: "test-zai-api-key", - zaiApiLine: "china", + zaiApiLine: "china_coding", }) const model = handlerWithModel.getModel() expect(model.id).toBe(testModelId) @@ -151,14 +151,14 @@ describe("ZAiHandler", () => { }) it("should use 'not-provided' as default API key when none is specified", () => { - new ZAiHandler({ zaiApiLine: "international" }) + new ZAiHandler({ zaiApiLine: "international_coding" }) expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: "not-provided" })) }) }) describe("API Methods", () => { beforeEach(() => { - handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international" }) + handler = new ZAiHandler({ zaiApiKey: "test-zai-api-key", zaiApiLine: "international_coding" }) }) it("completePrompt method should return text from Z AI API", async () => { @@ -231,7 +231,7 @@ describe("ZAiHandler", () => { const handlerWithModel = new ZAiHandler({ apiModelId: modelId, zaiApiKey: "test-zai-api-key", - zaiApiLine: "international", + zaiApiLine: "international_coding", }) mockCreate.mockImplementationOnce(() => { diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index 357a04b33a4..51300803008 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -46,6 +46,7 @@ export const providerProfilesSchema = z.object({ openAiHeadersMigrated: z.boolean().optional(), consecutiveMistakeLimitMigrated: z.boolean().optional(), todoListEnabledMigrated: z.boolean().optional(), + zaiApiLineCodingOnlyMigrated: z.boolean().optional(), }) .optional(), }) @@ -70,6 +71,7 @@ export class ProviderSettingsManager { openAiHeadersMigrated: true, // Mark as migrated on fresh installs consecutiveMistakeLimitMigrated: true, // Mark as migrated on fresh installs todoListEnabledMigrated: true, // Mark as migrated on fresh installs + zaiApiLineCodingOnlyMigrated: true, // Mark as migrated on fresh installs }, } @@ -176,6 +178,15 @@ export class ProviderSettingsManager { isDirty = true } + // Migrate Z AI api line to coding-only options + if (!providerProfiles.migrations.zaiApiLineCodingOnlyMigrated) { + const changed = await this.migrateZaiApiLineCodingOnly(providerProfiles) + if (changed) { + providerProfiles.migrations.zaiApiLineCodingOnlyMigrated = true + isDirty = true + } + } + if (isDirty) { await this.store(providerProfiles) } @@ -573,7 +584,9 @@ export class ProviderSettingsManager { const apiConfigs = Object.entries(providerProfiles.apiConfigs).reduce( (acc, [key, apiConfig]) => { - const result = providerSettingsWithIdSchema.safeParse(apiConfig) + // Preprocess legacy settings before validation (e.g., Z AI api line) + const processed = this.preprocessLegacySettings(apiConfig) + const result = providerSettingsWithIdSchema.safeParse(processed) return result.success ? { ...acc, [key]: result.data } : acc }, {} as Record, @@ -605,6 +618,50 @@ export class ProviderSettingsManager { } } + /** + * Preprocess legacy settings objects before schema validation. + * For example, coerce deprecated Z AI api lines to coding-only variants. + */ + private preprocessLegacySettings(apiConfig: any): any { + try { + if (apiConfig && apiConfig.apiProvider === "zai") { + const line = apiConfig.zaiApiLine + if (line === "international" || line === "intl_standard") { + apiConfig.zaiApiLine = "international_coding" + } else if (line === "china" || line === "china_standard") { + apiConfig.zaiApiLine = "china_coding" + } + } + } catch { + // no-op + } + return apiConfig + } + + /** + * Migrate Z AI api line values to coding-only options across all profiles. + */ + private async migrateZaiApiLineCodingOnly(providerProfiles: ProviderProfiles): Promise { + let changed = false + try { + for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) { + if ((apiConfig as any)?.apiProvider === "zai") { + const cfg = apiConfig as any + if (cfg.zaiApiLine === "international" || cfg.zaiApiLine === "intl_standard") { + cfg.zaiApiLine = "international_coding" + changed = true + } else if (cfg.zaiApiLine === "china" || cfg.zaiApiLine === "china_standard") { + cfg.zaiApiLine = "china_coding" + changed = true + } + } + } + } catch (error) { + console.error(`[MigrateZaiApiLine] Failed to migrate Z AI api line settings:`, error) + } + return changed + } + private findUniqueProfileName(baseName: string, existingNames: Set): string { if (!existingNames.has(baseName)) { return baseName diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 4142796b668..37c1c286b98 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -345,7 +345,7 @@ const ApiOptions = ({ zai: { field: "apiModelId", default: - apiConfiguration.zaiApiLine === "china" + apiConfiguration.zaiApiLine === "china_coding" ? mainlandZAiDefaultModelId : internationalZAiDefaultModelId, }, diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 0d0514b4d66..a3ce1e63e4e 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -238,7 +238,7 @@ function getSelectedModel({ return { id, info } } case "zai": { - const isChina = apiConfiguration.zaiApiLine === "china" + const isChina = apiConfiguration.zaiApiLine === "china_coding" const models = isChina ? mainlandZAiModels : internationalZAiModels const defaultModelId = isChina ? mainlandZAiDefaultModelId : internationalZAiDefaultModelId const id = apiConfiguration.apiModelId ?? defaultModelId From 94f6abe978ade89a725455ab3ebbc4017a93eb11 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 16 Oct 2025 19:41:26 -0600 Subject: [PATCH 2/2] chore: remove Z AI migration per feedback (#8687) --- src/core/config/ProviderSettingsManager.ts | 59 +--------------------- 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index 51300803008..357a04b33a4 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -46,7 +46,6 @@ export const providerProfilesSchema = z.object({ openAiHeadersMigrated: z.boolean().optional(), consecutiveMistakeLimitMigrated: z.boolean().optional(), todoListEnabledMigrated: z.boolean().optional(), - zaiApiLineCodingOnlyMigrated: z.boolean().optional(), }) .optional(), }) @@ -71,7 +70,6 @@ export class ProviderSettingsManager { openAiHeadersMigrated: true, // Mark as migrated on fresh installs consecutiveMistakeLimitMigrated: true, // Mark as migrated on fresh installs todoListEnabledMigrated: true, // Mark as migrated on fresh installs - zaiApiLineCodingOnlyMigrated: true, // Mark as migrated on fresh installs }, } @@ -178,15 +176,6 @@ export class ProviderSettingsManager { isDirty = true } - // Migrate Z AI api line to coding-only options - if (!providerProfiles.migrations.zaiApiLineCodingOnlyMigrated) { - const changed = await this.migrateZaiApiLineCodingOnly(providerProfiles) - if (changed) { - providerProfiles.migrations.zaiApiLineCodingOnlyMigrated = true - isDirty = true - } - } - if (isDirty) { await this.store(providerProfiles) } @@ -584,9 +573,7 @@ export class ProviderSettingsManager { const apiConfigs = Object.entries(providerProfiles.apiConfigs).reduce( (acc, [key, apiConfig]) => { - // Preprocess legacy settings before validation (e.g., Z AI api line) - const processed = this.preprocessLegacySettings(apiConfig) - const result = providerSettingsWithIdSchema.safeParse(processed) + const result = providerSettingsWithIdSchema.safeParse(apiConfig) return result.success ? { ...acc, [key]: result.data } : acc }, {} as Record, @@ -618,50 +605,6 @@ export class ProviderSettingsManager { } } - /** - * Preprocess legacy settings objects before schema validation. - * For example, coerce deprecated Z AI api lines to coding-only variants. - */ - private preprocessLegacySettings(apiConfig: any): any { - try { - if (apiConfig && apiConfig.apiProvider === "zai") { - const line = apiConfig.zaiApiLine - if (line === "international" || line === "intl_standard") { - apiConfig.zaiApiLine = "international_coding" - } else if (line === "china" || line === "china_standard") { - apiConfig.zaiApiLine = "china_coding" - } - } - } catch { - // no-op - } - return apiConfig - } - - /** - * Migrate Z AI api line values to coding-only options across all profiles. - */ - private async migrateZaiApiLineCodingOnly(providerProfiles: ProviderProfiles): Promise { - let changed = false - try { - for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) { - if ((apiConfig as any)?.apiProvider === "zai") { - const cfg = apiConfig as any - if (cfg.zaiApiLine === "international" || cfg.zaiApiLine === "intl_standard") { - cfg.zaiApiLine = "international_coding" - changed = true - } else if (cfg.zaiApiLine === "china" || cfg.zaiApiLine === "china_standard") { - cfg.zaiApiLine = "china_coding" - changed = true - } - } - } - } catch (error) { - console.error(`[MigrateZaiApiLine] Failed to migrate Z AI api line settings:`, error) - } - return changed - } - private findUniqueProfileName(baseName: string, existingNames: Set): string { if (!existingNames.has(baseName)) { return baseName