Merge pull request #5333 from vastxie/zai-coding-cn-provider
feat(ai): add ZAI Coding Plan China provider
This commit is contained in:
@@ -67,6 +67,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
|
|||||||
- **xAI**
|
- **xAI**
|
||||||
- **OpenRouter**
|
- **OpenRouter**
|
||||||
- **Vercel AI Gateway**
|
- **Vercel AI Gateway**
|
||||||
|
- **ZAI** (with separate Coding Plan China provider)
|
||||||
- **MiniMax**
|
- **MiniMax**
|
||||||
- **Together AI**
|
- **Together AI**
|
||||||
- **GitHub Copilot** (requires OAuth, see below)
|
- **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` |
|
| OpenRouter | `OPENROUTER_API_KEY` |
|
||||||
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` |
|
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` |
|
||||||
| zAI | `ZAI_API_KEY` |
|
| zAI | `ZAI_API_KEY` |
|
||||||
|
| ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` |
|
||||||
| MiniMax | `MINIMAX_API_KEY` |
|
| MiniMax | `MINIMAX_API_KEY` |
|
||||||
| OpenCode Zen / OpenCode Go | `OPENCODE_API_KEY` |
|
| OpenCode Zen / OpenCode Go | `OPENCODE_API_KEY` |
|
||||||
| Kimi For Coding | `KIMI_API_KEY` |
|
| Kimi For Coding | `KIMI_API_KEY` |
|
||||||
|
|||||||
@@ -788,34 +788,41 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process zAi models
|
// Process zAi models
|
||||||
if (data["zai-coding-plan"]?.models) {
|
const zaiCodingPlanVariants = [
|
||||||
for (const [modelId, model] of Object.entries(data["zai-coding-plan"].models)) {
|
{ provider: "zai", baseUrl: "https://api.z.ai/api/coding/paas/v4" },
|
||||||
const m = model as ModelsDevModel;
|
{ provider: "zai-coding-cn", baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4" },
|
||||||
if (m.tool_call !== true) continue;
|
] as const;
|
||||||
const supportsImage = m.modalities?.input?.includes("image");
|
|
||||||
|
|
||||||
models.push({
|
if (data["zai-coding-plan"]?.models) {
|
||||||
id: modelId,
|
for (const { provider, baseUrl } of zaiCodingPlanVariants) {
|
||||||
name: m.name || modelId,
|
for (const [modelId, model] of Object.entries(data["zai-coding-plan"].models)) {
|
||||||
api: "openai-completions",
|
const m = model as ModelsDevModel;
|
||||||
provider: "zai",
|
if (m.tool_call !== true) continue;
|
||||||
baseUrl: "https://api.z.ai/api/coding/paas/v4",
|
const supportsImage = m.modalities?.input?.includes("image");
|
||||||
reasoning: m.reasoning === true,
|
|
||||||
input: supportsImage ? ["text", "image"] : ["text"],
|
models.push({
|
||||||
cost: {
|
id: modelId,
|
||||||
input: m.cost?.input || 0,
|
name: m.name || modelId,
|
||||||
output: m.cost?.output || 0,
|
api: "openai-completions",
|
||||||
cacheRead: m.cost?.cache_read || 0,
|
provider,
|
||||||
cacheWrite: m.cost?.cache_write || 0,
|
baseUrl,
|
||||||
},
|
reasoning: m.reasoning === true,
|
||||||
compat: {
|
input: supportsImage ? ["text", "image"] : ["text"],
|
||||||
supportsDeveloperRole: false,
|
cost: {
|
||||||
thinkingFormat: "zai",
|
input: m.cost?.input || 0,
|
||||||
...(!ZAI_TOOL_STREAM_UNSUPPORTED_MODELS.has(modelId) ? { zaiToolStream: true } : {}),
|
output: m.cost?.output || 0,
|
||||||
},
|
cacheRead: m.cost?.cache_read || 0,
|
||||||
contextWindow: m.limit?.context || 4096,
|
cacheWrite: m.cost?.cache_write || 0,
|
||||||
maxTokens: m.limit?.output || 4096,
|
},
|
||||||
});
|
compat: {
|
||||||
|
supportsDeveloperRole: false,
|
||||||
|
thinkingFormat: "zai",
|
||||||
|
...(!ZAI_TOOL_STREAM_UNSUPPORTED_MODELS.has(modelId) ? { zaiToolStream: true } : {}),
|
||||||
|
},
|
||||||
|
contextWindow: m.limit?.context || 4096,
|
||||||
|
maxTokens: m.limit?.output || 4096,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ function getApiKeyEnvVars(provider: string): readonly string[] | undefined {
|
|||||||
openrouter: "OPENROUTER_API_KEY",
|
openrouter: "OPENROUTER_API_KEY",
|
||||||
"vercel-ai-gateway": "AI_GATEWAY_API_KEY",
|
"vercel-ai-gateway": "AI_GATEWAY_API_KEY",
|
||||||
zai: "ZAI_API_KEY",
|
zai: "ZAI_API_KEY",
|
||||||
|
"zai-coding-cn": "ZAI_CODING_CN_API_KEY",
|
||||||
mistral: "MISTRAL_API_KEY",
|
mistral: "MISTRAL_API_KEY",
|
||||||
minimax: "MINIMAX_API_KEY",
|
minimax: "MINIMAX_API_KEY",
|
||||||
"minimax-cn": "MINIMAX_CN_API_KEY",
|
"minimax-cn": "MINIMAX_CN_API_KEY",
|
||||||
|
|||||||
@@ -16776,4 +16776,96 @@ export const MODELS = {
|
|||||||
maxTokens: 131072,
|
maxTokens: 131072,
|
||||||
} satisfies Model<"openai-completions">,
|
} 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;
|
} as const;
|
||||||
|
|||||||
@@ -1076,7 +1076,11 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet
|
|||||||
const provider = model.provider;
|
const provider = model.provider;
|
||||||
const baseUrl = model.baseUrl;
|
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 =
|
const isTogether =
|
||||||
provider === "together" || baseUrl.includes("api.together.ai") || baseUrl.includes("api.together.xyz");
|
provider === "together" || baseUrl.includes("api.together.ai") || baseUrl.includes("api.together.xyz");
|
||||||
const isMoonshot = provider === "moonshotai" || provider === "moonshotai-cn" || baseUrl.includes("api.moonshot.");
|
const isMoonshot = provider === "moonshotai" || provider === "moonshotai-cn" || baseUrl.includes("api.moonshot.");
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export type KnownProvider =
|
|||||||
| "openrouter"
|
| "openrouter"
|
||||||
| "vercel-ai-gateway"
|
| "vercel-ai-gateway"
|
||||||
| "zai"
|
| "zai"
|
||||||
|
| "zai-coding-cn"
|
||||||
| "mistral"
|
| "mistral"
|
||||||
| "minimax"
|
| "minimax"
|
||||||
| "minimax-cn"
|
| "minimax-cn"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.ts";
|
|||||||
const originalCopilotGitHubToken = process.env.COPILOT_GITHUB_TOKEN;
|
const originalCopilotGitHubToken = process.env.COPILOT_GITHUB_TOKEN;
|
||||||
const originalGhToken = process.env.GH_TOKEN;
|
const originalGhToken = process.env.GH_TOKEN;
|
||||||
const originalGitHubToken = process.env.GITHUB_TOKEN;
|
const originalGitHubToken = process.env.GITHUB_TOKEN;
|
||||||
|
const originalZaiCodingCnApiKey = process.env.ZAI_CODING_CN_API_KEY;
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
if (originalCopilotGitHubToken === undefined) {
|
if (originalCopilotGitHubToken === undefined) {
|
||||||
@@ -23,6 +24,12 @@ afterEach(() => {
|
|||||||
} else {
|
} else {
|
||||||
process.env.GITHUB_TOKEN = originalGitHubToken;
|
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", () => {
|
describe("environment API keys", () => {
|
||||||
@@ -43,4 +50,11 @@ describe("environment API keys", () => {
|
|||||||
expect(findEnvKeys("github-copilot")).toEqual(["COPILOT_GITHUB_TOKEN"]);
|
expect(findEnvKeys("github-copilot")).toEqual(["COPILOT_GITHUB_TOKEN"]);
|
||||||
expect(getEnvApiKey("github-copilot")).toBe("copilot-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");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ For each built-in provider, pi maintains a list of tool-capable models, updated
|
|||||||
- OpenRouter
|
- OpenRouter
|
||||||
- Vercel AI Gateway
|
- Vercel AI Gateway
|
||||||
- ZAI
|
- ZAI
|
||||||
|
- ZAI Coding Plan (China)
|
||||||
- OpenCode Zen
|
- OpenCode Zen
|
||||||
- OpenCode Go
|
- OpenCode Go
|
||||||
- Hugging Face
|
- Hugging Face
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ pi
|
|||||||
| OpenRouter | `OPENROUTER_API_KEY` | `openrouter` |
|
| OpenRouter | `OPENROUTER_API_KEY` | `openrouter` |
|
||||||
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway` |
|
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway` |
|
||||||
| ZAI | `ZAI_API_KEY` | `zai` |
|
| ZAI | `ZAI_API_KEY` | `zai` |
|
||||||
|
| ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` | `zai-coding-cn` |
|
||||||
| OpenCode Zen | `OPENCODE_API_KEY` | `opencode` |
|
| OpenCode Zen | `OPENCODE_API_KEY` | `opencode` |
|
||||||
| OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` |
|
| OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` |
|
||||||
| Hugging Face | `HF_TOKEN` | `huggingface` |
|
| Hugging Face | `HF_TOKEN` | `huggingface` |
|
||||||
|
|||||||
@@ -346,6 +346,7 @@ ${chalk.bold("Environment Variables:")}
|
|||||||
OPENROUTER_API_KEY - OpenRouter API key
|
OPENROUTER_API_KEY - OpenRouter API key
|
||||||
AI_GATEWAY_API_KEY - Vercel AI Gateway API key
|
AI_GATEWAY_API_KEY - Vercel AI Gateway API key
|
||||||
ZAI_API_KEY - ZAI 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
|
MISTRAL_API_KEY - Mistral API key
|
||||||
MINIMAX_API_KEY - MiniMax API key
|
MINIMAX_API_KEY - MiniMax API key
|
||||||
MOONSHOT_API_KEY - Moonshot AI API key
|
MOONSHOT_API_KEY - Moonshot AI API key
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
|
|||||||
groq: "openai/gpt-oss-120b",
|
groq: "openai/gpt-oss-120b",
|
||||||
cerebras: "zai-glm-4.7",
|
cerebras: "zai-glm-4.7",
|
||||||
zai: "glm-5.1",
|
zai: "glm-5.1",
|
||||||
|
"zai-coding-cn": "glm-5.1",
|
||||||
mistral: "devstral-medium-latest",
|
mistral: "devstral-medium-latest",
|
||||||
minimax: "MiniMax-M2.7",
|
minimax: "MiniMax-M2.7",
|
||||||
"minimax-cn": "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",
|
"vercel-ai-gateway": "Vercel AI Gateway",
|
||||||
xai: "xAI",
|
xai: "xAI",
|
||||||
zai: "ZAI",
|
zai: "ZAI",
|
||||||
|
"zai-coding-cn": "ZAI Coding Plan (China)",
|
||||||
xiaomi: "Xiaomi MiMo",
|
xiaomi: "Xiaomi MiMo",
|
||||||
"xiaomi-token-plan-cn": "Xiaomi MiMo Token Plan (China)",
|
"xiaomi-token-plan-cn": "Xiaomi MiMo Token Plan (China)",
|
||||||
"xiaomi-token-plan-ams": "Xiaomi MiMo Token Plan (Amsterdam)",
|
"xiaomi-token-plan-ams": "Xiaomi MiMo Token Plan (Amsterdam)",
|
||||||
|
|||||||
1
test.sh
1
test.sh
@@ -35,6 +35,7 @@ unset CEREBRAS_API_KEY
|
|||||||
unset XAI_API_KEY
|
unset XAI_API_KEY
|
||||||
unset OPENROUTER_API_KEY
|
unset OPENROUTER_API_KEY
|
||||||
unset ZAI_API_KEY
|
unset ZAI_API_KEY
|
||||||
|
unset ZAI_CODING_CN_API_KEY
|
||||||
unset MISTRAL_API_KEY
|
unset MISTRAL_API_KEY
|
||||||
unset MINIMAX_API_KEY
|
unset MINIMAX_API_KEY
|
||||||
unset MINIMAX_CN_API_KEY
|
unset MINIMAX_CN_API_KEY
|
||||||
|
|||||||
Reference in New Issue
Block a user