Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof zaiApiLineSchema>

Expand Down
8 changes: 5 additions & 3 deletions packages/types/src/providers/zai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ZaiApiLine, { name: string; baseUrl: string; isChina: boolean }>
30 changes: 15 additions & 15 deletions src/api/providers/__tests__/zai.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }))
})

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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 }))
})

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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 () => {
Expand Down Expand Up @@ -231,7 +231,7 @@ describe("ZAiHandler", () => {
const handlerWithModel = new ZAiHandler({
apiModelId: modelId,
zaiApiKey: "test-zai-api-key",
zaiApiLine: "international",
zaiApiLine: "international_coding",
})

mockCreate.mockImplementationOnce(() => {
Expand Down
59 changes: 58 additions & 1 deletion src/core/config/ProviderSettingsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})
Expand All @@ -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
},
}

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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<string, ProviderSettingsWithId>,
Expand Down Expand Up @@ -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<boolean> {
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>): string {
if (!existingNames.has(baseName)) {
return baseName
Expand Down
2 changes: 1 addition & 1 deletion webview-ui/src/components/settings/ApiOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ const ApiOptions = ({
zai: {
field: "apiModelId",
default:
apiConfiguration.zaiApiLine === "china"
apiConfiguration.zaiApiLine === "china_coding"
? mainlandZAiDefaultModelId
: internationalZAiDefaultModelId,
},
Expand Down
2 changes: 1 addition & 1 deletion webview-ui/src/components/ui/hooks/useSelectedModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down