mirror of
https://github.com/earendil-works/pi.git
synced 2026-06-18 15:54:04 +08:00
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**
|
||||
- **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` |
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -38,6 +38,7 @@ export type KnownProvider =
|
||||
| "openrouter"
|
||||
| "vercel-ai-gateway"
|
||||
| "zai"
|
||||
| "zai-coding-cn"
|
||||
| "mistral"
|
||||
| "minimax"
|
||||
| "minimax-cn"
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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)",
|
||||
|
||||
Reference in New Issue
Block a user