Merge pull request #358 from default-anton/migrate-glm-4.7-to-openai

Migrate zai provider from Anthropic to OpenAI-compatible API
This commit is contained in:
Mario Zechner 2025-12-30 13:44:24 +01:00 committed by GitHub
commit 04a764742e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 34 additions and 29 deletions

View file

@ -258,7 +258,7 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
} }
} }
// Process xAi models // Process zAi models
if (data.zai?.models) { if (data.zai?.models) {
for (const [modelId, model] of Object.entries(data.zai.models)) { for (const [modelId, model] of Object.entries(data.zai.models)) {
const m = model as ModelsDevModel; const m = model as ModelsDevModel;
@ -268,9 +268,9 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
models.push({ models.push({
id: modelId, id: modelId,
name: m.name || modelId, name: m.name || modelId,
api: supportsImage ? "openai-completions" : "anthropic-messages", api: "openai-completions",
provider: "zai", provider: "zai",
baseUrl: supportsImage ? "https://api.z.ai/api/coding/paas/v4" : "https://api.z.ai/api/anthropic", baseUrl: "https://api.z.ai/api/coding/paas/v4",
reasoning: m.reasoning === true, reasoning: m.reasoning === true,
input: supportsImage ? ["text", "image"] : ["text"], input: supportsImage ? ["text", "image"] : ["text"],
cost: { cost: {
@ -279,11 +279,9 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
cacheRead: m.cost?.cache_read || 0, cacheRead: m.cost?.cache_read || 0,
cacheWrite: m.cost?.cache_write || 0, cacheWrite: m.cost?.cache_write || 0,
}, },
...(supportsImage ? { compat: {
compat: { supportsDeveloperRole: false,
supportsDeveloperRole: false, },
},
} : {}),
contextWindow: m.limit?.context || 4096, contextWindow: m.limit?.context || 4096,
maxTokens: m.limit?.output || 4096, maxTokens: m.limit?.output || 4096,
}); });

View file

@ -6978,9 +6978,10 @@ export const MODELS = {
"glm-4.5": { "glm-4.5": {
id: "glm-4.5", id: "glm-4.5",
name: "GLM-4.5", name: "GLM-4.5",
api: "anthropic-messages", api: "openai-completions",
provider: "zai", provider: "zai",
baseUrl: "https://api.z.ai/api/anthropic", baseUrl: "https://api.z.ai/api/coding/paas/v4",
compat: {"supportsDeveloperRole":false},
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -6991,13 +6992,14 @@ export const MODELS = {
}, },
contextWindow: 131072, contextWindow: 131072,
maxTokens: 98304, maxTokens: 98304,
} satisfies Model<"anthropic-messages">, } satisfies Model<"openai-completions">,
"glm-4.5-air": { "glm-4.5-air": {
id: "glm-4.5-air", id: "glm-4.5-air",
name: "GLM-4.5-Air", name: "GLM-4.5-Air",
api: "anthropic-messages", api: "openai-completions",
provider: "zai", provider: "zai",
baseUrl: "https://api.z.ai/api/anthropic", baseUrl: "https://api.z.ai/api/coding/paas/v4",
compat: {"supportsDeveloperRole":false},
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -7008,13 +7010,14 @@ export const MODELS = {
}, },
contextWindow: 131072, contextWindow: 131072,
maxTokens: 98304, maxTokens: 98304,
} satisfies Model<"anthropic-messages">, } satisfies Model<"openai-completions">,
"glm-4.5-flash": { "glm-4.5-flash": {
id: "glm-4.5-flash", id: "glm-4.5-flash",
name: "GLM-4.5-Flash", name: "GLM-4.5-Flash",
api: "anthropic-messages", api: "openai-completions",
provider: "zai", provider: "zai",
baseUrl: "https://api.z.ai/api/anthropic", baseUrl: "https://api.z.ai/api/coding/paas/v4",
compat: {"supportsDeveloperRole":false},
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -7025,7 +7028,7 @@ export const MODELS = {
}, },
contextWindow: 131072, contextWindow: 131072,
maxTokens: 98304, maxTokens: 98304,
} satisfies Model<"anthropic-messages">, } satisfies Model<"openai-completions">,
"glm-4.5v": { "glm-4.5v": {
id: "glm-4.5v", id: "glm-4.5v",
name: "GLM-4.5V", name: "GLM-4.5V",
@ -7047,9 +7050,10 @@ export const MODELS = {
"glm-4.6": { "glm-4.6": {
id: "glm-4.6", id: "glm-4.6",
name: "GLM-4.6", name: "GLM-4.6",
api: "anthropic-messages", api: "openai-completions",
provider: "zai", provider: "zai",
baseUrl: "https://api.z.ai/api/anthropic", baseUrl: "https://api.z.ai/api/coding/paas/v4",
compat: {"supportsDeveloperRole":false},
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -7060,7 +7064,7 @@ export const MODELS = {
}, },
contextWindow: 204800, contextWindow: 204800,
maxTokens: 131072, maxTokens: 131072,
} satisfies Model<"anthropic-messages">, } satisfies Model<"openai-completions">,
"glm-4.6v": { "glm-4.6v": {
id: "glm-4.6v", id: "glm-4.6v",
name: "GLM-4.6V", name: "GLM-4.6V",
@ -7082,9 +7086,10 @@ export const MODELS = {
"glm-4.7": { "glm-4.7": {
id: "glm-4.7", id: "glm-4.7",
name: "GLM-4.7", name: "GLM-4.7",
api: "anthropic-messages", api: "openai-completions",
provider: "zai", provider: "zai",
baseUrl: "https://api.z.ai/api/anthropic", baseUrl: "https://api.z.ai/api/coding/paas/v4",
compat: {"supportsDeveloperRole":false},
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -7095,6 +7100,6 @@ export const MODELS = {
}, },
contextWindow: 204800, contextWindow: 204800,
maxTokens: 131072, maxTokens: 131072,
} satisfies Model<"anthropic-messages">, } satisfies Model<"openai-completions">,
}, },
} as const; } as const;

View file

@ -460,13 +460,15 @@ function convertMessages(
}; };
const textBlocks = msg.content.filter((b) => b.type === "text") as TextContent[]; const textBlocks = msg.content.filter((b) => b.type === "text") as TextContent[];
if (textBlocks.length > 0) { // Filter out empty text blocks to avoid API validation errors
const nonEmptyTextBlocks = textBlocks.filter((b) => b.text && b.text.trim().length > 0);
if (nonEmptyTextBlocks.length > 0) {
// GitHub Copilot requires assistant content as a string, not an array. // GitHub Copilot requires assistant content as a string, not an array.
// Sending as array causes Claude models to re-answer all previous prompts. // Sending as array causes Claude models to re-answer all previous prompts.
if (model.provider === "github-copilot") { if (model.provider === "github-copilot") {
assistantMsg.content = textBlocks.map((b) => sanitizeSurrogates(b.text)).join(""); assistantMsg.content = nonEmptyTextBlocks.map((b) => sanitizeSurrogates(b.text)).join("");
} else { } else {
assistantMsg.content = textBlocks.map((b) => { assistantMsg.content = nonEmptyTextBlocks.map((b) => {
return { type: "text", text: sanitizeSurrogates(b.text) }; return { type: "text", text: sanitizeSurrogates(b.text) };
}); });
} }

View file

@ -556,7 +556,7 @@ describe("Generate E2E Tests", () => {
}); });
}); });
describe.skipIf(!process.env.ZAI_API_KEY)("zAI Provider (glm-4.5-air via Anthropic Messages)", () => { describe.skipIf(!process.env.ZAI_API_KEY)("zAI Provider (glm-4.5-air via OpenAI Completions)", () => {
const llm = getModel("zai", "glm-4.5-air"); const llm = getModel("zai", "glm-4.5-air");
it("should complete basic text generation", { retry: 3 }, async () => { it("should complete basic text generation", { retry: 3 }, async () => {
@ -572,11 +572,11 @@ describe("Generate E2E Tests", () => {
}); });
it.skip("should handle thinking mode", { retry: 3 }, async () => { it.skip("should handle thinking mode", { retry: 3 }, async () => {
await handleThinking(llm, { thinkingEnabled: true, thinkingBudgetTokens: 2048 }); await handleThinking(llm, { reasoningEffort: "medium" });
}); });
it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => { it("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
await multiTurn(llm, { thinkingEnabled: true, thinkingBudgetTokens: 2048 }); await multiTurn(llm, { reasoningEffort: "medium" });
}); });
}); });