Merge pull request #5333 from vastxie/zai-coding-cn-provider

feat(ai): add ZAI Coding Plan China provider
This commit is contained in:
Mario Zechner
2026-06-03 01:07:01 +02:00
committed by GitHub
Unverified
13 changed files with 155 additions and 28 deletions
+2
View File
@@ -67,6 +67,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
- **xAI**
- **OpenRouter**
- **Vercel AI Gateway**
- **ZAI** (with separate Coding Plan China provider)
- **MiniMax**
- **Together AI**
- **GitHub Copilot** (requires OAuth, see below)
@@ -1119,6 +1120,7 @@ In Node.js environments, you can set environment variables to avoid passing API
| OpenRouter | `OPENROUTER_API_KEY` |
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` |
| zAI | `ZAI_API_KEY` |
| ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` |
| MiniMax | `MINIMAX_API_KEY` |
| OpenCode Zen / OpenCode Go | `OPENCODE_API_KEY` |
| Kimi For Coding | `KIMI_API_KEY` |
+34 -27
View File
@@ -788,34 +788,41 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
}
// Process zAi models
if (data["zai-coding-plan"]?.models) {
for (const [modelId, model] of Object.entries(data["zai-coding-plan"].models)) {
const m = model as ModelsDevModel;
if (m.tool_call !== true) continue;
const supportsImage = m.modalities?.input?.includes("image");
const zaiCodingPlanVariants = [
{ provider: "zai", baseUrl: "https://api.z.ai/api/coding/paas/v4" },
{ provider: "zai-coding-cn", baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4" },
] as const;
models.push({
id: modelId,
name: m.name || modelId,
api: "openai-completions",
provider: "zai",
baseUrl: "https://api.z.ai/api/coding/paas/v4",
reasoning: m.reasoning === true,
input: supportsImage ? ["text", "image"] : ["text"],
cost: {
input: m.cost?.input || 0,
output: m.cost?.output || 0,
cacheRead: m.cost?.cache_read || 0,
cacheWrite: m.cost?.cache_write || 0,
},
compat: {
supportsDeveloperRole: false,
thinkingFormat: "zai",
...(!ZAI_TOOL_STREAM_UNSUPPORTED_MODELS.has(modelId) ? { zaiToolStream: true } : {}),
},
contextWindow: m.limit?.context || 4096,
maxTokens: m.limit?.output || 4096,
});
if (data["zai-coding-plan"]?.models) {
for (const { provider, baseUrl } of zaiCodingPlanVariants) {
for (const [modelId, model] of Object.entries(data["zai-coding-plan"].models)) {
const m = model as ModelsDevModel;
if (m.tool_call !== true) continue;
const supportsImage = m.modalities?.input?.includes("image");
models.push({
id: modelId,
name: m.name || modelId,
api: "openai-completions",
provider,
baseUrl,
reasoning: m.reasoning === true,
input: supportsImage ? ["text", "image"] : ["text"],
cost: {
input: m.cost?.input || 0,
output: m.cost?.output || 0,
cacheRead: m.cost?.cache_read || 0,
cacheWrite: m.cost?.cache_write || 0,
},
compat: {
supportsDeveloperRole: false,
thinkingFormat: "zai",
...(!ZAI_TOOL_STREAM_UNSUPPORTED_MODELS.has(modelId) ? { zaiToolStream: true } : {}),
},
contextWindow: m.limit?.context || 4096,
maxTokens: m.limit?.output || 4096,
});
}
}
}
+1
View File
@@ -112,6 +112,7 @@ function getApiKeyEnvVars(provider: string): readonly string[] | undefined {
openrouter: "OPENROUTER_API_KEY",
"vercel-ai-gateway": "AI_GATEWAY_API_KEY",
zai: "ZAI_API_KEY",
"zai-coding-cn": "ZAI_CODING_CN_API_KEY",
mistral: "MISTRAL_API_KEY",
minimax: "MINIMAX_API_KEY",
"minimax-cn": "MINIMAX_CN_API_KEY",
+92
View File
@@ -16776,4 +16776,96 @@ export const MODELS = {
maxTokens: 131072,
} satisfies Model<"openai-completions">,
},
"zai-coding-cn": {
"glm-4.5-air": {
id: "glm-4.5-air",
name: "GLM-4.5-Air",
api: "openai-completions",
provider: "zai-coding-cn",
baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
compat: {"supportsDeveloperRole":false,"thinkingFormat":"zai"},
reasoning: true,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
maxTokens: 98304,
} satisfies Model<"openai-completions">,
"glm-4.7": {
id: "glm-4.7",
name: "GLM-4.7",
api: "openai-completions",
provider: "zai-coding-cn",
baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
compat: {"supportsDeveloperRole":false,"thinkingFormat":"zai","zaiToolStream":true},
reasoning: true,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 204800,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"glm-5-turbo": {
id: "glm-5-turbo",
name: "GLM-5-Turbo",
api: "openai-completions",
provider: "zai-coding-cn",
baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
compat: {"supportsDeveloperRole":false,"thinkingFormat":"zai","zaiToolStream":true},
reasoning: true,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 200000,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"glm-5.1": {
id: "glm-5.1",
name: "GLM-5.1",
api: "openai-completions",
provider: "zai-coding-cn",
baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
compat: {"supportsDeveloperRole":false,"thinkingFormat":"zai","zaiToolStream":true},
reasoning: true,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 200000,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"glm-5v-turbo": {
id: "glm-5v-turbo",
name: "GLM-5V-Turbo",
api: "openai-completions",
provider: "zai-coding-cn",
baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
compat: {"supportsDeveloperRole":false,"thinkingFormat":"zai","zaiToolStream":true},
reasoning: true,
input: ["text", "image"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 200000,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
},
} as const;
@@ -1076,7 +1076,11 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet
const provider = model.provider;
const baseUrl = model.baseUrl;
const isZai = provider === "zai" || baseUrl.includes("api.z.ai");
const isZai =
provider === "zai" ||
provider === "zai-coding-cn" ||
baseUrl.includes("api.z.ai") ||
baseUrl.includes("open.bigmodel.cn");
const isTogether =
provider === "together" || baseUrl.includes("api.together.ai") || baseUrl.includes("api.together.xyz");
const isMoonshot = provider === "moonshotai" || provider === "moonshotai-cn" || baseUrl.includes("api.moonshot.");
+1
View File
@@ -38,6 +38,7 @@ export type KnownProvider =
| "openrouter"
| "vercel-ai-gateway"
| "zai"
| "zai-coding-cn"
| "mistral"
| "minimax"
| "minimax-cn"
+14
View File
@@ -4,6 +4,7 @@ import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.ts";
const originalCopilotGitHubToken = process.env.COPILOT_GITHUB_TOKEN;
const originalGhToken = process.env.GH_TOKEN;
const originalGitHubToken = process.env.GITHUB_TOKEN;
const originalZaiCodingCnApiKey = process.env.ZAI_CODING_CN_API_KEY;
afterEach(() => {
if (originalCopilotGitHubToken === undefined) {
@@ -23,6 +24,12 @@ afterEach(() => {
} else {
process.env.GITHUB_TOKEN = originalGitHubToken;
}
if (originalZaiCodingCnApiKey === undefined) {
delete process.env.ZAI_CODING_CN_API_KEY;
} else {
process.env.ZAI_CODING_CN_API_KEY = originalZaiCodingCnApiKey;
}
});
describe("environment API keys", () => {
@@ -43,4 +50,11 @@ describe("environment API keys", () => {
expect(findEnvKeys("github-copilot")).toEqual(["COPILOT_GITHUB_TOKEN"]);
expect(getEnvApiKey("github-copilot")).toBe("copilot-token");
});
it("resolves ZAI China Coding Plan credentials from ZAI_CODING_CN_API_KEY", () => {
process.env.ZAI_CODING_CN_API_KEY = "zai-coding-cn-token";
expect(findEnvKeys("zai-coding-cn")).toEqual(["ZAI_CODING_CN_API_KEY"]);
expect(getEnvApiKey("zai-coding-cn")).toBe("zai-coding-cn-token");
});
});
+1
View File
@@ -127,6 +127,7 @@ For each built-in provider, pi maintains a list of tool-capable models, updated
- OpenRouter
- Vercel AI Gateway
- ZAI
- ZAI Coding Plan (China)
- OpenCode Zen
- OpenCode Go
- Hugging Face
+1
View File
@@ -64,6 +64,7 @@ pi
| OpenRouter | `OPENROUTER_API_KEY` | `openrouter` |
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway` |
| ZAI | `ZAI_API_KEY` | `zai` |
| ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` | `zai-coding-cn` |
| OpenCode Zen | `OPENCODE_API_KEY` | `opencode` |
| OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` |
| Hugging Face | `HF_TOKEN` | `huggingface` |
+1
View File
@@ -346,6 +346,7 @@ ${chalk.bold("Environment Variables:")}
OPENROUTER_API_KEY - OpenRouter API key
AI_GATEWAY_API_KEY - Vercel AI Gateway API key
ZAI_API_KEY - ZAI API key
ZAI_CODING_CN_API_KEY - ZAI Coding Plan API key (China)
MISTRAL_API_KEY - Mistral API key
MINIMAX_API_KEY - MiniMax API key
MOONSHOT_API_KEY - Moonshot AI API key
@@ -29,6 +29,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
groq: "openai/gpt-oss-120b",
cerebras: "zai-glm-4.7",
zai: "glm-5.1",
"zai-coding-cn": "glm-5.1",
mistral: "devstral-medium-latest",
minimax: "MiniMax-M2.7",
"minimax-cn": "MiniMax-M2.7",
@@ -27,6 +27,7 @@ export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record<string, string> = {
"vercel-ai-gateway": "Vercel AI Gateway",
xai: "xAI",
zai: "ZAI",
"zai-coding-cn": "ZAI Coding Plan (China)",
xiaomi: "Xiaomi MiMo",
"xiaomi-token-plan-cn": "Xiaomi MiMo Token Plan (China)",
"xiaomi-token-plan-ams": "Xiaomi MiMo Token Plan (Amsterdam)",
+1
View File
@@ -35,6 +35,7 @@ unset CEREBRAS_API_KEY
unset XAI_API_KEY
unset OPENROUTER_API_KEY
unset ZAI_API_KEY
unset ZAI_CODING_CN_API_KEY
unset MISTRAL_API_KEY
unset MINIMAX_API_KEY
unset MINIMAX_CN_API_KEY