Merge 60c26c4604 into 09be5d45d5
This commit is contained in:
commit
3db70acfe2
@ -4,18 +4,33 @@ read_when:
|
|||||||
- You want GLM models in OpenClaw
|
- You want GLM models in OpenClaw
|
||||||
- You need the model naming convention and setup
|
- You need the model naming convention and setup
|
||||||
---
|
---
|
||||||
# GLM models
|
# GLM Models
|
||||||
|
|
||||||
GLM is a **model family** (not a company) available through the Z.AI platform. In OpenClaw, GLM
|
GLM is a **model family** developed by Zhipu AI. GLM models are available through two platforms:
|
||||||
models are accessed via the `zai` provider and model IDs like `zai/glm-4.7`.
|
|
||||||
|
|
||||||
## CLI setup
|
- **Z.AI** (api.z.ai) - International platform
|
||||||
|
- **Zhipu AI** (bigmodel.cn) - China mainland platform
|
||||||
|
|
||||||
|
## Provider Options
|
||||||
|
|
||||||
|
| Provider | Platform | Use Case |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| `zai` | Z.AI (International) | Pay-as-you-go |
|
||||||
|
| `zai-coding` | Z.AI (International) | Coding Plan subscription |
|
||||||
|
| `zhipu` | Zhipu AI (China) | Pay-as-you-go |
|
||||||
|
| `zhipu-coding` | Zhipu AI (China) | Coding Plan subscription |
|
||||||
|
|
||||||
|
## CLI Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# International users
|
||||||
openclaw onboard --auth-choice zai-api-key
|
openclaw onboard --auth-choice zai-api-key
|
||||||
|
|
||||||
|
# China users
|
||||||
|
openclaw onboard --auth-choice zhipu-api-key
|
||||||
```
|
```
|
||||||
|
|
||||||
## Config snippet
|
## Config Snippet
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
@ -24,8 +39,18 @@ openclaw onboard --auth-choice zai-api-key
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Available Models
|
||||||
|
|
||||||
|
- `glm-4.7` - Latest flagship model (205K context)
|
||||||
|
- `glm-4.6` - Previous generation (205K context)
|
||||||
|
- `glm-4.6v` - Vision model (128K context)
|
||||||
|
- `glm-4.5` - Balanced performance (131K context)
|
||||||
|
- `glm-4.5-air` - Lighter variant (131K context)
|
||||||
|
- `glm-4.5-flash` - Faster variant (131K context)
|
||||||
|
|
||||||
|
Model availability may vary by region; check the platform docs for the latest.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- GLM versions and availability can change; check Z.AI's docs for the latest.
|
- Model IDs follow the pattern `{provider}/glm-{version}` (e.g., `zai/glm-4.7`, `zhipu/glm-4.7`)
|
||||||
- Example model IDs include `glm-4.7` and `glm-4.6`.
|
- For detailed provider setup, see [/providers/zai](/providers/zai)
|
||||||
- For provider details, see [/providers/zai](/providers/zai).
|
|
||||||
|
|||||||
@ -41,9 +41,9 @@ See [Venice AI](/providers/venice).
|
|||||||
- [Moonshot AI (Kimi + Kimi Code)](/providers/moonshot)
|
- [Moonshot AI (Kimi + Kimi Code)](/providers/moonshot)
|
||||||
- [OpenCode Zen](/providers/opencode)
|
- [OpenCode Zen](/providers/opencode)
|
||||||
- [Amazon Bedrock](/bedrock)
|
- [Amazon Bedrock](/bedrock)
|
||||||
- [Z.AI](/providers/zai)
|
- [Z.AI / Zhipu AI (GLM models)](/providers/zai) - International + China, pay-as-you-go + Coding Plan
|
||||||
|
- [GLM models](/providers/glm) - Model family overview
|
||||||
- [Xiaomi](/providers/xiaomi)
|
- [Xiaomi](/providers/xiaomi)
|
||||||
- [GLM models](/providers/glm)
|
|
||||||
- [MiniMax](/providers/minimax)
|
- [MiniMax](/providers/minimax)
|
||||||
- [Venius (Venice AI, privacy-focused)](/providers/venice)
|
- [Venius (Venice AI, privacy-focused)](/providers/venice)
|
||||||
- [Ollama (local models)](/providers/ollama)
|
- [Ollama (local models)](/providers/ollama)
|
||||||
|
|||||||
@ -1,24 +1,81 @@
|
|||||||
---
|
---
|
||||||
summary: "Use Z.AI (GLM models) with OpenClaw"
|
summary: "Use Z.AI / Zhipu AI (GLM models) with OpenClaw"
|
||||||
read_when:
|
read_when:
|
||||||
- You want Z.AI / GLM models in OpenClaw
|
- You want Z.AI / GLM models in OpenClaw
|
||||||
- You need a simple ZAI_API_KEY setup
|
- You need to choose between international and China endpoints
|
||||||
|
- You have a Coding Plan subscription
|
||||||
---
|
---
|
||||||
# Z.AI
|
# Z.AI / Zhipu AI (GLM Models)
|
||||||
|
|
||||||
Z.AI is the API platform for **GLM** models. It provides REST APIs for GLM and uses API keys
|
Z.AI and Zhipu AI provide access to **GLM** models. There are four provider configurations
|
||||||
for authentication. Create your API key in the Z.AI console. OpenClaw uses the `zai` provider
|
depending on your region and subscription type.
|
||||||
with a Z.AI API key.
|
|
||||||
|
|
||||||
## CLI setup
|
## Provider Variants
|
||||||
|
|
||||||
|
| Provider | Region | Plan Type | Base URL |
|
||||||
|
|----------|--------|-----------|----------|
|
||||||
|
| `zai` | International | Pay-as-you-go | api.z.ai |
|
||||||
|
| `zai-coding` | International | Coding Plan | api.z.ai (coding endpoint) |
|
||||||
|
| `zhipu` | China | Pay-as-you-go | bigmodel.cn |
|
||||||
|
| `zhipu-coding` | China | Coding Plan | bigmodel.cn (coding endpoint) |
|
||||||
|
|
||||||
|
## Which should I use?
|
||||||
|
|
||||||
|
- **International users with pay-as-you-go**: Use `zai`
|
||||||
|
- **International users with Coding Plan subscription ($3-15/mo)**: Use `zai-coding`
|
||||||
|
- **China mainland users with pay-as-you-go**: Use `zhipu`
|
||||||
|
- **China mainland users with Coding Plan**: Use `zhipu-coding`
|
||||||
|
|
||||||
|
The Coding Plan endpoints are optimized for coding tools and have better tool-calling
|
||||||
|
performance. They use subscription-based billing rather than per-token billing.
|
||||||
|
|
||||||
|
## CLI Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# International (pay-as-you-go)
|
||||||
openclaw onboard --auth-choice zai-api-key
|
openclaw onboard --auth-choice zai-api-key
|
||||||
# or non-interactive
|
|
||||||
openclaw onboard --zai-api-key "$ZAI_API_KEY"
|
# International (Coding Plan)
|
||||||
|
openclaw onboard --auth-choice zai-coding-api-key
|
||||||
|
|
||||||
|
# China (pay-as-you-go)
|
||||||
|
openclaw onboard --auth-choice zhipu-api-key
|
||||||
|
|
||||||
|
# China (Coding Plan)
|
||||||
|
openclaw onboard --auth-choice zhipu-coding-api-key
|
||||||
```
|
```
|
||||||
|
|
||||||
## Config snippet
|
### Non-interactive
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# International (pay-as-you-go)
|
||||||
|
openclaw onboard --non-interactive --auth-choice zai-api-key --zai-api-key "$ZAI_API_KEY"
|
||||||
|
|
||||||
|
# International (Coding Plan)
|
||||||
|
openclaw onboard --non-interactive --auth-choice zai-coding-api-key --zai-coding-api-key "$ZAI_API_KEY"
|
||||||
|
|
||||||
|
# China (pay-as-you-go)
|
||||||
|
openclaw onboard --non-interactive --auth-choice zhipu-api-key --zhipu-api-key "$ZHIPU_API_KEY"
|
||||||
|
|
||||||
|
# China (Coding Plan)
|
||||||
|
openclaw onboard --non-interactive --auth-choice zhipu-coding-api-key --zhipu-coding-api-key "$ZHIPU_API_KEY"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Provider | Primary Env Var | Fallback Chain |
|
||||||
|
|----------|-----------------|----------------|
|
||||||
|
| `zai` | `ZAI_API_KEY` | `Z_AI_API_KEY` |
|
||||||
|
| `zai-coding` | `ZAI_CODING_API_KEY` | `ZAI_API_KEY` → `Z_AI_API_KEY` |
|
||||||
|
| `zhipu` | `ZHIPU_API_KEY` | (none) |
|
||||||
|
| `zhipu-coding` | `ZHIPU_CODING_API_KEY` | `ZHIPU_API_KEY` |
|
||||||
|
|
||||||
|
The coding providers fall back to their respective general provider's env var, so you can
|
||||||
|
use a single API key for both if desired.
|
||||||
|
|
||||||
|
## Config Snippets
|
||||||
|
|
||||||
|
### International (pay-as-you-go)
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
@ -27,8 +84,47 @@ openclaw onboard --zai-api-key "$ZAI_API_KEY"
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
### International (Coding Plan)
|
||||||
|
|
||||||
- GLM models are available as `zai/<model>` (example: `zai/glm-4.7`).
|
```json5
|
||||||
- See [/providers/glm](/providers/glm) for the model family overview.
|
{
|
||||||
- Z.AI uses Bearer auth with your API key.
|
env: { ZAI_API_KEY: "sk-..." },
|
||||||
|
agents: { defaults: { model: { primary: "zai-coding/glm-4.7" } } }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### China (pay-as-you-go)
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
env: { ZHIPU_API_KEY: "sk-..." },
|
||||||
|
agents: { defaults: { model: { primary: "zhipu/glm-4.7" } } }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### China (Coding Plan)
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
env: { ZHIPU_API_KEY: "sk-..." },
|
||||||
|
agents: { defaults: { model: { primary: "zhipu-coding/glm-4.7" } } }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- **Keys are subscription-gated**: Your API key only works on the endpoint matching
|
||||||
|
your active subscription. A Coding Plan key will return error 1113 ("Insufficient
|
||||||
|
balance") on the general pay-as-you-go endpoint, and vice versa. Choose the provider
|
||||||
|
variant that matches your subscription type.
|
||||||
|
|
||||||
|
- **Regional keys are not interchangeable**: Keys from z.ai don't work on bigmodel.cn
|
||||||
|
and vice versa. Create your key on the platform for your region.
|
||||||
|
|
||||||
|
- **Coding endpoint optimized for tools**: The coding endpoints have better
|
||||||
|
tool-calling performance and are recommended for use with coding assistants.
|
||||||
|
|
||||||
|
- **GLM models**: Models are available as `{provider}/glm-4.7`, `{provider}/glm-4.6`, etc.
|
||||||
|
See [/providers/glm](/providers/glm) for the model family overview.
|
||||||
|
|
||||||
|
- **Authentication**: All variants use Bearer token auth with your API key.
|
||||||
|
|||||||
@ -233,6 +233,132 @@ describe("getApiKeyForModel", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("resolves zai-coding API key with fallback to ZAI_API_KEY", async () => {
|
||||||
|
const previous = {
|
||||||
|
coding: process.env.ZAI_CODING_API_KEY,
|
||||||
|
zai: process.env.ZAI_API_KEY,
|
||||||
|
legacy: process.env.Z_AI_API_KEY,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
delete process.env.ZAI_CODING_API_KEY;
|
||||||
|
process.env.ZAI_API_KEY = "zai-fallback-key";
|
||||||
|
delete process.env.Z_AI_API_KEY;
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
const { resolveApiKeyForProvider } = await import("./model-auth.js");
|
||||||
|
|
||||||
|
const resolved = await resolveApiKeyForProvider({
|
||||||
|
provider: "zai-coding",
|
||||||
|
store: { version: 1, profiles: {} },
|
||||||
|
});
|
||||||
|
expect(resolved.apiKey).toBe("zai-fallback-key");
|
||||||
|
expect(resolved.source).toContain("ZAI_API_KEY");
|
||||||
|
} finally {
|
||||||
|
if (previous.coding === undefined) {
|
||||||
|
delete process.env.ZAI_CODING_API_KEY;
|
||||||
|
} else {
|
||||||
|
process.env.ZAI_CODING_API_KEY = previous.coding;
|
||||||
|
}
|
||||||
|
if (previous.zai === undefined) {
|
||||||
|
delete process.env.ZAI_API_KEY;
|
||||||
|
} else {
|
||||||
|
process.env.ZAI_API_KEY = previous.zai;
|
||||||
|
}
|
||||||
|
if (previous.legacy === undefined) {
|
||||||
|
delete process.env.Z_AI_API_KEY;
|
||||||
|
} else {
|
||||||
|
process.env.Z_AI_API_KEY = previous.legacy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when zhipu API key is missing", async () => {
|
||||||
|
const previous = process.env.ZHIPU_API_KEY;
|
||||||
|
|
||||||
|
try {
|
||||||
|
delete process.env.ZHIPU_API_KEY;
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
const { resolveApiKeyForProvider } = await import("./model-auth.js");
|
||||||
|
|
||||||
|
let error: unknown = null;
|
||||||
|
try {
|
||||||
|
await resolveApiKeyForProvider({
|
||||||
|
provider: "zhipu",
|
||||||
|
store: { version: 1, profiles: {} },
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(String(error)).toContain('No API key found for provider "zhipu".');
|
||||||
|
} finally {
|
||||||
|
if (previous === undefined) {
|
||||||
|
delete process.env.ZHIPU_API_KEY;
|
||||||
|
} else {
|
||||||
|
process.env.ZHIPU_API_KEY = previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves zhipu API key from ZHIPU_API_KEY", async () => {
|
||||||
|
const previous = process.env.ZHIPU_API_KEY;
|
||||||
|
|
||||||
|
try {
|
||||||
|
process.env.ZHIPU_API_KEY = "zhipu-test-key";
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
const { resolveApiKeyForProvider } = await import("./model-auth.js");
|
||||||
|
|
||||||
|
const resolved = await resolveApiKeyForProvider({
|
||||||
|
provider: "zhipu",
|
||||||
|
store: { version: 1, profiles: {} },
|
||||||
|
});
|
||||||
|
expect(resolved.apiKey).toBe("zhipu-test-key");
|
||||||
|
expect(resolved.source).toContain("ZHIPU_API_KEY");
|
||||||
|
} finally {
|
||||||
|
if (previous === undefined) {
|
||||||
|
delete process.env.ZHIPU_API_KEY;
|
||||||
|
} else {
|
||||||
|
process.env.ZHIPU_API_KEY = previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves zhipu-coding API key with fallback to ZHIPU_API_KEY", async () => {
|
||||||
|
const previous = {
|
||||||
|
coding: process.env.ZHIPU_CODING_API_KEY,
|
||||||
|
zhipu: process.env.ZHIPU_API_KEY,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
delete process.env.ZHIPU_CODING_API_KEY;
|
||||||
|
process.env.ZHIPU_API_KEY = "zhipu-fallback-key";
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
const { resolveApiKeyForProvider } = await import("./model-auth.js");
|
||||||
|
|
||||||
|
const resolved = await resolveApiKeyForProvider({
|
||||||
|
provider: "zhipu-coding",
|
||||||
|
store: { version: 1, profiles: {} },
|
||||||
|
});
|
||||||
|
expect(resolved.apiKey).toBe("zhipu-fallback-key");
|
||||||
|
expect(resolved.source).toContain("ZHIPU_API_KEY");
|
||||||
|
} finally {
|
||||||
|
if (previous.coding === undefined) {
|
||||||
|
delete process.env.ZHIPU_CODING_API_KEY;
|
||||||
|
} else {
|
||||||
|
process.env.ZHIPU_CODING_API_KEY = previous.coding;
|
||||||
|
}
|
||||||
|
if (previous.zhipu === undefined) {
|
||||||
|
delete process.env.ZHIPU_API_KEY;
|
||||||
|
} else {
|
||||||
|
process.env.ZHIPU_API_KEY = previous.zhipu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("resolves Synthetic API key from env", async () => {
|
it("resolves Synthetic API key from env", async () => {
|
||||||
const previousSynthetic = process.env.SYNTHETIC_API_KEY;
|
const previousSynthetic = process.env.SYNTHETIC_API_KEY;
|
||||||
|
|
||||||
|
|||||||
@ -255,6 +255,20 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
|
|||||||
return pick("ZAI_API_KEY") ?? pick("Z_AI_API_KEY");
|
return pick("ZAI_API_KEY") ?? pick("Z_AI_API_KEY");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (normalized === "zai-coding") {
|
||||||
|
// Coding plan can use same key as general, just different endpoint
|
||||||
|
return pick("ZAI_CODING_API_KEY") ?? pick("ZAI_API_KEY") ?? pick("Z_AI_API_KEY");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === "zhipu") {
|
||||||
|
return pick("ZHIPU_API_KEY");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === "zhipu-coding") {
|
||||||
|
// Coding plan can use same key as general, just different endpoint
|
||||||
|
return pick("ZHIPU_CODING_API_KEY") ?? pick("ZHIPU_API_KEY");
|
||||||
|
}
|
||||||
|
|
||||||
if (normalized === "google-vertex") {
|
if (normalized === "google-vertex") {
|
||||||
const envKey = getEnvApiKey(normalized);
|
const envKey = getEnvApiKey(normalized);
|
||||||
if (!envKey) return null;
|
if (!envKey) return null;
|
||||||
|
|||||||
@ -6,8 +6,15 @@ function isOpenAiCompletionsModel(model: Model<Api>): model is Model<"openai-com
|
|||||||
|
|
||||||
export function normalizeModelCompat(model: Model<Api>): Model<Api> {
|
export function normalizeModelCompat(model: Model<Api>): Model<Api> {
|
||||||
const baseUrl = model.baseUrl ?? "";
|
const baseUrl = model.baseUrl ?? "";
|
||||||
const isZai = model.provider === "zai" || baseUrl.includes("api.z.ai");
|
// All GLM providers (Z.AI international and Zhipu AI China) share the same compatibility
|
||||||
if (!isZai || !isOpenAiCompletionsModel(model)) return model;
|
const isGlm =
|
||||||
|
model.provider === "zai" ||
|
||||||
|
model.provider === "zai-coding" ||
|
||||||
|
model.provider === "zhipu" ||
|
||||||
|
model.provider === "zhipu-coding" ||
|
||||||
|
baseUrl.includes("api.z.ai") ||
|
||||||
|
baseUrl.includes("bigmodel.cn");
|
||||||
|
if (!isGlm || !isOpenAiCompletionsModel(model)) return model;
|
||||||
|
|
||||||
const openaiModel = model as Model<"openai-completions">;
|
const openaiModel = model as Model<"openai-completions">;
|
||||||
const compat = openaiModel.compat ?? undefined;
|
const compat = openaiModel.compat ?? undefined;
|
||||||
|
|||||||
@ -26,7 +26,12 @@ export function modelKey(provider: string, model: string) {
|
|||||||
|
|
||||||
export function normalizeProviderId(provider: string): string {
|
export function normalizeProviderId(provider: string): string {
|
||||||
const normalized = provider.trim().toLowerCase();
|
const normalized = provider.trim().toLowerCase();
|
||||||
|
// Z.AI international variants
|
||||||
if (normalized === "z.ai" || normalized === "z-ai") return "zai";
|
if (normalized === "z.ai" || normalized === "z-ai") return "zai";
|
||||||
|
if (normalized === "z.ai-coding" || normalized === "z-ai-coding") return "zai-coding";
|
||||||
|
// Zhipu AI (China) variants
|
||||||
|
if (normalized === "zhipuai" || normalized === "zhipu-ai") return "zhipu";
|
||||||
|
if (normalized === "zhipuai-coding" || normalized === "zhipu-ai-coding") return "zhipu-coding";
|
||||||
if (normalized === "opencode-zen") return "opencode";
|
if (normalized === "opencode-zen") return "opencode";
|
||||||
if (normalized === "qwen") return "qwen-portal";
|
if (normalized === "qwen") return "qwen-portal";
|
||||||
return normalized;
|
return normalized;
|
||||||
|
|||||||
@ -182,6 +182,24 @@ describe("cli program (smoke)", () => {
|
|||||||
key: "sk-zai-test",
|
key: "sk-zai-test",
|
||||||
field: "zaiApiKey",
|
field: "zaiApiKey",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
authChoice: "zai-coding-api-key",
|
||||||
|
flag: "--zai-coding-api-key",
|
||||||
|
key: "sk-zai-coding-test",
|
||||||
|
field: "zaiCodingApiKey",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authChoice: "zhipu-api-key",
|
||||||
|
flag: "--zhipu-api-key",
|
||||||
|
key: "sk-zhipu-test",
|
||||||
|
field: "zhipuApiKey",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authChoice: "zhipu-coding-api-key",
|
||||||
|
flag: "--zhipu-coding-api-key",
|
||||||
|
key: "sk-zhipu-coding-test",
|
||||||
|
field: "zhipuCodingApiKey",
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
for (const entry of cases) {
|
for (const entry of cases) {
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export function registerOnboardCommand(program: Command) {
|
|||||||
.option("--mode <mode>", "Wizard mode: local|remote")
|
.option("--mode <mode>", "Wizard mode: local|remote")
|
||||||
.option(
|
.option(
|
||||||
"--auth-choice <choice>",
|
"--auth-choice <choice>",
|
||||||
"Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|xiaomi-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip",
|
"Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|zai-coding-api-key|zhipu-api-key|zhipu-coding-api-key|xiaomi-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip",
|
||||||
)
|
)
|
||||||
.option(
|
.option(
|
||||||
"--token-provider <id>",
|
"--token-provider <id>",
|
||||||
@ -72,6 +72,9 @@ export function registerOnboardCommand(program: Command) {
|
|||||||
.option("--kimi-code-api-key <key>", "Kimi Code API key")
|
.option("--kimi-code-api-key <key>", "Kimi Code API key")
|
||||||
.option("--gemini-api-key <key>", "Gemini API key")
|
.option("--gemini-api-key <key>", "Gemini API key")
|
||||||
.option("--zai-api-key <key>", "Z.AI API key")
|
.option("--zai-api-key <key>", "Z.AI API key")
|
||||||
|
.option("--zai-coding-api-key <key>", "Z.AI Coding Plan API key")
|
||||||
|
.option("--zhipu-api-key <key>", "Zhipu AI API key")
|
||||||
|
.option("--zhipu-coding-api-key <key>", "Zhipu AI Coding Plan API key")
|
||||||
.option("--xiaomi-api-key <key>", "Xiaomi API key")
|
.option("--xiaomi-api-key <key>", "Xiaomi API key")
|
||||||
.option("--minimax-api-key <key>", "MiniMax API key")
|
.option("--minimax-api-key <key>", "MiniMax API key")
|
||||||
.option("--synthetic-api-key <key>", "Synthetic API key")
|
.option("--synthetic-api-key <key>", "Synthetic API key")
|
||||||
@ -123,6 +126,9 @@ export function registerOnboardCommand(program: Command) {
|
|||||||
kimiCodeApiKey: opts.kimiCodeApiKey as string | undefined,
|
kimiCodeApiKey: opts.kimiCodeApiKey as string | undefined,
|
||||||
geminiApiKey: opts.geminiApiKey as string | undefined,
|
geminiApiKey: opts.geminiApiKey as string | undefined,
|
||||||
zaiApiKey: opts.zaiApiKey as string | undefined,
|
zaiApiKey: opts.zaiApiKey as string | undefined,
|
||||||
|
zaiCodingApiKey: opts.zaiCodingApiKey as string | undefined,
|
||||||
|
zhipuApiKey: opts.zhipuApiKey as string | undefined,
|
||||||
|
zhipuCodingApiKey: opts.zhipuCodingApiKey as string | undefined,
|
||||||
xiaomiApiKey: opts.xiaomiApiKey as string | undefined,
|
xiaomiApiKey: opts.xiaomiApiKey as string | undefined,
|
||||||
minimaxApiKey: opts.minimaxApiKey as string | undefined,
|
minimaxApiKey: opts.minimaxApiKey as string | undefined,
|
||||||
syntheticApiKey: opts.syntheticApiKey as string | undefined,
|
syntheticApiKey: opts.syntheticApiKey as string | undefined,
|
||||||
|
|||||||
@ -23,14 +23,19 @@ describe("buildAuthChoiceOptions", () => {
|
|||||||
expect(options.some((opt) => opt.value === "token")).toBe(true);
|
expect(options.some((opt) => opt.value === "token")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes Z.AI (GLM) auth choice", () => {
|
it("includes Z.AI / Zhipu (GLM) auth choices", () => {
|
||||||
const store: AuthProfileStore = { version: 1, profiles: {} };
|
const store: AuthProfileStore = { version: 1, profiles: {} };
|
||||||
const options = buildAuthChoiceOptions({
|
const options = buildAuthChoiceOptions({
|
||||||
store,
|
store,
|
||||||
includeSkip: false,
|
includeSkip: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// International variants (api.z.ai)
|
||||||
expect(options.some((opt) => opt.value === "zai-api-key")).toBe(true);
|
expect(options.some((opt) => opt.value === "zai-api-key")).toBe(true);
|
||||||
|
expect(options.some((opt) => opt.value === "zai-coding-api-key")).toBe(true);
|
||||||
|
// China variants (bigmodel.cn)
|
||||||
|
expect(options.some((opt) => opt.value === "zhipu-api-key")).toBe(true);
|
||||||
|
expect(options.some((opt) => opt.value === "zhipu-coding-api-key")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes Xiaomi auth choice", () => {
|
it("includes Xiaomi auth choice", () => {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export type AuthChoiceGroupId =
|
|||||||
| "ai-gateway"
|
| "ai-gateway"
|
||||||
| "moonshot"
|
| "moonshot"
|
||||||
| "zai"
|
| "zai"
|
||||||
|
| "zhipu"
|
||||||
| "xiaomi"
|
| "xiaomi"
|
||||||
| "opencode-zen"
|
| "opencode-zen"
|
||||||
| "minimax"
|
| "minimax"
|
||||||
@ -104,9 +105,15 @@ const AUTH_CHOICE_GROUP_DEFS: {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "zai",
|
value: "zai",
|
||||||
label: "Z.AI (GLM 4.7)",
|
label: "Z.AI (International)",
|
||||||
hint: "API key",
|
hint: "GLM models via api.z.ai",
|
||||||
choices: ["zai-api-key"],
|
choices: ["zai-api-key", "zai-coding-api-key"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "zhipu",
|
||||||
|
label: "Zhipu AI (China)",
|
||||||
|
hint: "GLM models via bigmodel.cn",
|
||||||
|
choices: ["zhipu-api-key", "zhipu-coding-api-key"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "xiaomi",
|
value: "xiaomi",
|
||||||
@ -170,7 +177,18 @@ export function buildAuthChoiceOptions(params: {
|
|||||||
label: "Google Gemini CLI OAuth",
|
label: "Google Gemini CLI OAuth",
|
||||||
hint: "Uses the bundled Gemini CLI auth plugin",
|
hint: "Uses the bundled Gemini CLI auth plugin",
|
||||||
});
|
});
|
||||||
options.push({ value: "zai-api-key", label: "Z.AI (GLM 4.7) API key" });
|
options.push({ value: "zai-api-key", label: "Z.AI API key (pay-as-you-go)" });
|
||||||
|
options.push({
|
||||||
|
value: "zai-coding-api-key",
|
||||||
|
label: "Z.AI Coding Plan API key",
|
||||||
|
hint: "Subscription-based, optimized for coding tools",
|
||||||
|
});
|
||||||
|
options.push({ value: "zhipu-api-key", label: "Zhipu AI API key (pay-as-you-go)" });
|
||||||
|
options.push({
|
||||||
|
value: "zhipu-coding-api-key",
|
||||||
|
label: "Zhipu AI Coding Plan API key",
|
||||||
|
hint: "China mainland, subscription-based",
|
||||||
|
});
|
||||||
options.push({
|
options.push({
|
||||||
value: "xiaomi-api-key",
|
value: "xiaomi-api-key",
|
||||||
label: "Xiaomi API key",
|
label: "Xiaomi API key",
|
||||||
|
|||||||
@ -29,7 +29,14 @@ import {
|
|||||||
applyVercelAiGatewayProviderConfig,
|
applyVercelAiGatewayProviderConfig,
|
||||||
applyXiaomiConfig,
|
applyXiaomiConfig,
|
||||||
applyXiaomiProviderConfig,
|
applyXiaomiProviderConfig,
|
||||||
|
applyZaiCodingConfig,
|
||||||
|
applyZaiCodingProviderConfig,
|
||||||
applyZaiConfig,
|
applyZaiConfig,
|
||||||
|
applyZaiProviderConfig,
|
||||||
|
applyZhipuCodingConfig,
|
||||||
|
applyZhipuCodingProviderConfig,
|
||||||
|
applyZhipuConfig,
|
||||||
|
applyZhipuProviderConfig,
|
||||||
KIMI_CODE_MODEL_REF,
|
KIMI_CODE_MODEL_REF,
|
||||||
MOONSHOT_DEFAULT_MODEL_REF,
|
MOONSHOT_DEFAULT_MODEL_REF,
|
||||||
OPENROUTER_DEFAULT_MODEL_REF,
|
OPENROUTER_DEFAULT_MODEL_REF,
|
||||||
@ -47,7 +54,13 @@ import {
|
|||||||
setVercelAiGatewayApiKey,
|
setVercelAiGatewayApiKey,
|
||||||
setXiaomiApiKey,
|
setXiaomiApiKey,
|
||||||
setZaiApiKey,
|
setZaiApiKey,
|
||||||
|
setZaiCodingApiKey,
|
||||||
|
setZhipuApiKey,
|
||||||
|
setZhipuCodingApiKey,
|
||||||
|
ZAI_CODING_DEFAULT_MODEL_REF,
|
||||||
ZAI_DEFAULT_MODEL_REF,
|
ZAI_DEFAULT_MODEL_REF,
|
||||||
|
ZHIPU_CODING_DEFAULT_MODEL_REF,
|
||||||
|
ZHIPU_DEFAULT_MODEL_REF,
|
||||||
} from "./onboard-auth.js";
|
} from "./onboard-auth.js";
|
||||||
import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js";
|
import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js";
|
||||||
|
|
||||||
@ -83,6 +96,12 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
authChoice = "gemini-api-key";
|
authChoice = "gemini-api-key";
|
||||||
} else if (params.opts.tokenProvider === "zai") {
|
} else if (params.opts.tokenProvider === "zai") {
|
||||||
authChoice = "zai-api-key";
|
authChoice = "zai-api-key";
|
||||||
|
} else if (params.opts.tokenProvider === "zai-coding") {
|
||||||
|
authChoice = "zai-coding-api-key";
|
||||||
|
} else if (params.opts.tokenProvider === "zhipu") {
|
||||||
|
authChoice = "zhipu-api-key";
|
||||||
|
} else if (params.opts.tokenProvider === "zhipu-coding") {
|
||||||
|
authChoice = "zhipu-coding-api-key";
|
||||||
} else if (params.opts.tokenProvider === "xiaomi") {
|
} else if (params.opts.tokenProvider === "xiaomi") {
|
||||||
authChoice = "xiaomi-api-key";
|
authChoice = "xiaomi-api-key";
|
||||||
} else if (params.opts.tokenProvider === "synthetic") {
|
} else if (params.opts.tokenProvider === "synthetic") {
|
||||||
@ -184,15 +203,17 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("vercel-ai-gateway");
|
if (!hasCredential) {
|
||||||
if (envKey) {
|
const envKey = resolveEnvApiKey("vercel-ai-gateway");
|
||||||
const useExisting = await params.prompter.confirm({
|
if (envKey) {
|
||||||
message: `Use existing AI_GATEWAY_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
const useExisting = await params.prompter.confirm({
|
||||||
initialValue: true,
|
message: `Use existing AI_GATEWAY_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
});
|
initialValue: true,
|
||||||
if (useExisting) {
|
});
|
||||||
await setVercelAiGatewayApiKey(envKey.apiKey, params.agentDir);
|
if (useExisting) {
|
||||||
hasCredential = true;
|
await setVercelAiGatewayApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
@ -232,15 +253,17 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("moonshot");
|
if (!hasCredential) {
|
||||||
if (envKey) {
|
const envKey = resolveEnvApiKey("moonshot");
|
||||||
const useExisting = await params.prompter.confirm({
|
if (envKey) {
|
||||||
message: `Use existing MOONSHOT_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
const useExisting = await params.prompter.confirm({
|
||||||
initialValue: true,
|
message: `Use existing MOONSHOT_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
});
|
initialValue: true,
|
||||||
if (useExisting) {
|
});
|
||||||
await setMoonshotApiKey(envKey.apiKey, params.agentDir);
|
if (useExisting) {
|
||||||
hasCredential = true;
|
await setMoonshotApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
@ -287,15 +310,17 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
"Kimi Code",
|
"Kimi Code",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const envKey = resolveEnvApiKey("kimi-code");
|
if (!hasCredential) {
|
||||||
if (envKey) {
|
const envKey = resolveEnvApiKey("kimi-code");
|
||||||
const useExisting = await params.prompter.confirm({
|
if (envKey) {
|
||||||
message: `Use existing KIMICODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
const useExisting = await params.prompter.confirm({
|
||||||
initialValue: true,
|
message: `Use existing KIMICODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
});
|
initialValue: true,
|
||||||
if (useExisting) {
|
});
|
||||||
await setKimiCodeApiKey(envKey.apiKey, params.agentDir);
|
if (useExisting) {
|
||||||
hasCredential = true;
|
await setKimiCodeApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
@ -335,15 +360,17 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("google");
|
if (!hasCredential) {
|
||||||
if (envKey) {
|
const envKey = resolveEnvApiKey("google");
|
||||||
const useExisting = await params.prompter.confirm({
|
if (envKey) {
|
||||||
message: `Use existing GEMINI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
const useExisting = await params.prompter.confirm({
|
||||||
initialValue: true,
|
message: `Use existing GEMINI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
});
|
initialValue: true,
|
||||||
if (useExisting) {
|
});
|
||||||
await setGeminiApiKey(envKey.apiKey, params.agentDir);
|
if (useExisting) {
|
||||||
hasCredential = true;
|
await setGeminiApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
@ -382,15 +409,17 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("zai");
|
if (!hasCredential) {
|
||||||
if (envKey) {
|
const envKey = resolveEnvApiKey("zai");
|
||||||
const useExisting = await params.prompter.confirm({
|
if (envKey) {
|
||||||
message: `Use existing ZAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
const useExisting = await params.prompter.confirm({
|
||||||
initialValue: true,
|
message: `Use existing ZAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
});
|
initialValue: true,
|
||||||
if (useExisting) {
|
});
|
||||||
await setZaiApiKey(envKey.apiKey, params.agentDir);
|
if (useExisting) {
|
||||||
hasCredential = true;
|
await setZaiApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
@ -411,22 +440,7 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
setDefaultModel: params.setDefaultModel,
|
setDefaultModel: params.setDefaultModel,
|
||||||
defaultModel: ZAI_DEFAULT_MODEL_REF,
|
defaultModel: ZAI_DEFAULT_MODEL_REF,
|
||||||
applyDefaultConfig: applyZaiConfig,
|
applyDefaultConfig: applyZaiConfig,
|
||||||
applyProviderConfig: (config) => ({
|
applyProviderConfig: applyZaiProviderConfig,
|
||||||
...config,
|
|
||||||
agents: {
|
|
||||||
...config.agents,
|
|
||||||
defaults: {
|
|
||||||
...config.agents?.defaults,
|
|
||||||
models: {
|
|
||||||
...config.agents?.defaults?.models,
|
|
||||||
[ZAI_DEFAULT_MODEL_REF]: {
|
|
||||||
...config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF],
|
|
||||||
alias: config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF]?.alias ?? "GLM",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
noteDefault: ZAI_DEFAULT_MODEL_REF,
|
noteDefault: ZAI_DEFAULT_MODEL_REF,
|
||||||
noteAgentModel,
|
noteAgentModel,
|
||||||
prompter: params.prompter,
|
prompter: params.prompter,
|
||||||
@ -437,6 +451,156 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
return { config: nextConfig, agentModelOverride };
|
return { config: nextConfig, agentModelOverride };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authChoice === "zai-coding-api-key") {
|
||||||
|
let hasCredential = false;
|
||||||
|
|
||||||
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "zai-coding") {
|
||||||
|
await setZaiCodingApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCredential) {
|
||||||
|
const envKey = resolveEnvApiKey("zai-coding");
|
||||||
|
if (envKey) {
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing API key (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
await setZaiCodingApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasCredential) {
|
||||||
|
const key = await params.prompter.text({
|
||||||
|
message: "Enter Z.AI Coding Plan API key",
|
||||||
|
validate: validateApiKeyInput,
|
||||||
|
});
|
||||||
|
await setZaiCodingApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||||
|
}
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "zai-coding:default",
|
||||||
|
provider: "zai-coding",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
{
|
||||||
|
const applied = await applyDefaultModelChoice({
|
||||||
|
config: nextConfig,
|
||||||
|
setDefaultModel: params.setDefaultModel,
|
||||||
|
defaultModel: ZAI_CODING_DEFAULT_MODEL_REF,
|
||||||
|
applyDefaultConfig: applyZaiCodingConfig,
|
||||||
|
applyProviderConfig: applyZaiCodingProviderConfig,
|
||||||
|
noteDefault: ZAI_CODING_DEFAULT_MODEL_REF,
|
||||||
|
noteAgentModel,
|
||||||
|
prompter: params.prompter,
|
||||||
|
});
|
||||||
|
nextConfig = applied.config;
|
||||||
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
||||||
|
}
|
||||||
|
return { config: nextConfig, agentModelOverride };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authChoice === "zhipu-api-key") {
|
||||||
|
let hasCredential = false;
|
||||||
|
|
||||||
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "zhipu") {
|
||||||
|
await setZhipuApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCredential) {
|
||||||
|
const envKey = resolveEnvApiKey("zhipu");
|
||||||
|
if (envKey) {
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing ZHIPU_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
await setZhipuApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasCredential) {
|
||||||
|
const key = await params.prompter.text({
|
||||||
|
message: "Enter Zhipu AI API key",
|
||||||
|
validate: validateApiKeyInput,
|
||||||
|
});
|
||||||
|
await setZhipuApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||||
|
}
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "zhipu:default",
|
||||||
|
provider: "zhipu",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
{
|
||||||
|
const applied = await applyDefaultModelChoice({
|
||||||
|
config: nextConfig,
|
||||||
|
setDefaultModel: params.setDefaultModel,
|
||||||
|
defaultModel: ZHIPU_DEFAULT_MODEL_REF,
|
||||||
|
applyDefaultConfig: applyZhipuConfig,
|
||||||
|
applyProviderConfig: applyZhipuProviderConfig,
|
||||||
|
noteDefault: ZHIPU_DEFAULT_MODEL_REF,
|
||||||
|
noteAgentModel,
|
||||||
|
prompter: params.prompter,
|
||||||
|
});
|
||||||
|
nextConfig = applied.config;
|
||||||
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
||||||
|
}
|
||||||
|
return { config: nextConfig, agentModelOverride };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authChoice === "zhipu-coding-api-key") {
|
||||||
|
let hasCredential = false;
|
||||||
|
|
||||||
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "zhipu-coding") {
|
||||||
|
await setZhipuCodingApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCredential) {
|
||||||
|
const envKey = resolveEnvApiKey("zhipu-coding");
|
||||||
|
if (envKey) {
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing API key (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
await setZhipuCodingApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasCredential) {
|
||||||
|
const key = await params.prompter.text({
|
||||||
|
message: "Enter Zhipu AI Coding Plan API key",
|
||||||
|
validate: validateApiKeyInput,
|
||||||
|
});
|
||||||
|
await setZhipuCodingApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||||
|
}
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "zhipu-coding:default",
|
||||||
|
provider: "zhipu-coding",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
{
|
||||||
|
const applied = await applyDefaultModelChoice({
|
||||||
|
config: nextConfig,
|
||||||
|
setDefaultModel: params.setDefaultModel,
|
||||||
|
defaultModel: ZHIPU_CODING_DEFAULT_MODEL_REF,
|
||||||
|
applyDefaultConfig: applyZhipuCodingConfig,
|
||||||
|
applyProviderConfig: applyZhipuCodingProviderConfig,
|
||||||
|
noteDefault: ZHIPU_CODING_DEFAULT_MODEL_REF,
|
||||||
|
noteAgentModel,
|
||||||
|
prompter: params.prompter,
|
||||||
|
});
|
||||||
|
nextConfig = applied.config;
|
||||||
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
||||||
|
}
|
||||||
|
return { config: nextConfig, agentModelOverride };
|
||||||
|
}
|
||||||
|
|
||||||
if (authChoice === "xiaomi-api-key") {
|
if (authChoice === "xiaomi-api-key") {
|
||||||
let hasCredential = false;
|
let hasCredential = false;
|
||||||
|
|
||||||
@ -536,15 +700,17 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("venice");
|
if (!hasCredential) {
|
||||||
if (envKey) {
|
const envKey = resolveEnvApiKey("venice");
|
||||||
const useExisting = await params.prompter.confirm({
|
if (envKey) {
|
||||||
message: `Use existing VENICE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
const useExisting = await params.prompter.confirm({
|
||||||
initialValue: true,
|
message: `Use existing VENICE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
});
|
initialValue: true,
|
||||||
if (useExisting) {
|
});
|
||||||
await setVeniceApiKey(envKey.apiKey, params.agentDir);
|
if (useExisting) {
|
||||||
hasCredential = true;
|
await setVeniceApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
@ -593,15 +759,17 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
"OpenCode Zen",
|
"OpenCode Zen",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const envKey = resolveEnvApiKey("opencode");
|
if (!hasCredential) {
|
||||||
if (envKey) {
|
const envKey = resolveEnvApiKey("opencode");
|
||||||
const useExisting = await params.prompter.confirm({
|
if (envKey) {
|
||||||
message: `Use existing OPENCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
const useExisting = await params.prompter.confirm({
|
||||||
initialValue: true,
|
message: `Use existing OPENCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
});
|
initialValue: true,
|
||||||
if (useExisting) {
|
});
|
||||||
await setOpencodeZenApiKey(envKey.apiKey, params.agentDir);
|
if (useExisting) {
|
||||||
hasCredential = true;
|
await setOpencodeZenApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
|
|||||||
@ -16,11 +16,22 @@ import {
|
|||||||
OPENROUTER_DEFAULT_MODEL_REF,
|
OPENROUTER_DEFAULT_MODEL_REF,
|
||||||
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
||||||
XIAOMI_DEFAULT_MODEL_REF,
|
XIAOMI_DEFAULT_MODEL_REF,
|
||||||
|
ZAI_CODING_DEFAULT_MODEL_REF,
|
||||||
ZAI_DEFAULT_MODEL_REF,
|
ZAI_DEFAULT_MODEL_REF,
|
||||||
|
ZHIPU_CODING_DEFAULT_MODEL_REF,
|
||||||
|
ZHIPU_DEFAULT_MODEL_REF,
|
||||||
} from "./onboard-auth.credentials.js";
|
} from "./onboard-auth.credentials.js";
|
||||||
|
|
||||||
|
// Z.AI / Zhipu AI base URLs
|
||||||
|
export const ZAI_BASE_URL = "https://api.z.ai/api/paas/v4";
|
||||||
|
export const ZAI_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
|
||||||
|
export const ZHIPU_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
||||||
|
export const ZHIPU_CODING_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
|
||||||
import {
|
import {
|
||||||
|
buildGlmModelDefinition,
|
||||||
buildKimiCodeModelDefinition,
|
buildKimiCodeModelDefinition,
|
||||||
buildMoonshotModelDefinition,
|
buildMoonshotModelDefinition,
|
||||||
|
GLM_DEFAULT_MODEL_ID,
|
||||||
KIMI_CODE_BASE_URL,
|
KIMI_CODE_BASE_URL,
|
||||||
KIMI_CODE_MODEL_ID,
|
KIMI_CODE_MODEL_ID,
|
||||||
KIMI_CODE_MODEL_REF,
|
KIMI_CODE_MODEL_REF,
|
||||||
@ -29,14 +40,33 @@ import {
|
|||||||
MOONSHOT_DEFAULT_MODEL_REF,
|
MOONSHOT_DEFAULT_MODEL_REF,
|
||||||
} from "./onboard-auth.models.js";
|
} from "./onboard-auth.models.js";
|
||||||
|
|
||||||
export function applyZaiConfig(cfg: OpenClawConfig): OpenClawConfig {
|
export function applyZaiProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||||
const models = { ...cfg.agents?.defaults?.models };
|
const models = { ...cfg.agents?.defaults?.models };
|
||||||
models[ZAI_DEFAULT_MODEL_REF] = {
|
models[ZAI_DEFAULT_MODEL_REF] = {
|
||||||
...models[ZAI_DEFAULT_MODEL_REF],
|
...models[ZAI_DEFAULT_MODEL_REF],
|
||||||
alias: models[ZAI_DEFAULT_MODEL_REF]?.alias ?? "GLM",
|
alias: models[ZAI_DEFAULT_MODEL_REF]?.alias ?? "GLM",
|
||||||
};
|
};
|
||||||
|
|
||||||
const existingModel = cfg.agents?.defaults?.model;
|
const providers = { ...cfg.models?.providers };
|
||||||
|
const existingProvider = providers.zai;
|
||||||
|
const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : [];
|
||||||
|
const defaultModel = buildGlmModelDefinition();
|
||||||
|
const hasDefaultModel = existingModels.some((model) => model.id === GLM_DEFAULT_MODEL_ID);
|
||||||
|
const mergedModels = hasDefaultModel ? existingModels : [...existingModels, defaultModel];
|
||||||
|
const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> as { apiKey?: string };
|
||||||
|
const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined;
|
||||||
|
const normalizedApiKey = resolvedApiKey?.trim();
|
||||||
|
providers.zai = {
|
||||||
|
...existingProviderRest,
|
||||||
|
baseUrl: ZAI_BASE_URL,
|
||||||
|
api: "openai-completions",
|
||||||
|
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
|
||||||
|
models: mergedModels.length > 0 ? mergedModels : [defaultModel],
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...cfg,
|
...cfg,
|
||||||
agents: {
|
agents: {
|
||||||
@ -44,6 +74,24 @@ export function applyZaiConfig(cfg: OpenClawConfig): OpenClawConfig {
|
|||||||
defaults: {
|
defaults: {
|
||||||
...cfg.agents?.defaults,
|
...cfg.agents?.defaults,
|
||||||
models,
|
models,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
mode: cfg.models?.mode ?? "merge",
|
||||||
|
providers,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyZaiConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||||
|
const next = applyZaiProviderConfig(cfg);
|
||||||
|
const existingModel = next.agents?.defaults?.model;
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
agents: {
|
||||||
|
...next.agents,
|
||||||
|
defaults: {
|
||||||
|
...next.agents?.defaults,
|
||||||
model: {
|
model: {
|
||||||
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
|
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
|
||||||
? {
|
? {
|
||||||
@ -57,6 +105,201 @@ export function applyZaiConfig(cfg: OpenClawConfig): OpenClawConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyZaiCodingProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||||
|
const models = { ...cfg.agents?.defaults?.models };
|
||||||
|
models[ZAI_CODING_DEFAULT_MODEL_REF] = {
|
||||||
|
...models[ZAI_CODING_DEFAULT_MODEL_REF],
|
||||||
|
alias: models[ZAI_CODING_DEFAULT_MODEL_REF]?.alias ?? "GLM Coding",
|
||||||
|
};
|
||||||
|
|
||||||
|
const providers = { ...cfg.models?.providers };
|
||||||
|
const existingProvider = providers["zai-coding"];
|
||||||
|
const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : [];
|
||||||
|
const defaultModel = buildGlmModelDefinition();
|
||||||
|
const hasDefaultModel = existingModels.some((model) => model.id === GLM_DEFAULT_MODEL_ID);
|
||||||
|
const mergedModels = hasDefaultModel ? existingModels : [...existingModels, defaultModel];
|
||||||
|
const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> as { apiKey?: string };
|
||||||
|
const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined;
|
||||||
|
const normalizedApiKey = resolvedApiKey?.trim();
|
||||||
|
providers["zai-coding"] = {
|
||||||
|
...existingProviderRest,
|
||||||
|
baseUrl: ZAI_CODING_BASE_URL,
|
||||||
|
api: "openai-completions",
|
||||||
|
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
|
||||||
|
models: mergedModels.length > 0 ? mergedModels : [defaultModel],
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
agents: {
|
||||||
|
...cfg.agents,
|
||||||
|
defaults: {
|
||||||
|
...cfg.agents?.defaults,
|
||||||
|
models,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
mode: cfg.models?.mode ?? "merge",
|
||||||
|
providers,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyZaiCodingConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||||
|
const next = applyZaiCodingProviderConfig(cfg);
|
||||||
|
const existingModel = next.agents?.defaults?.model;
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
agents: {
|
||||||
|
...next.agents,
|
||||||
|
defaults: {
|
||||||
|
...next.agents?.defaults,
|
||||||
|
model: {
|
||||||
|
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
|
||||||
|
? {
|
||||||
|
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
|
||||||
|
}
|
||||||
|
: undefined),
|
||||||
|
primary: ZAI_CODING_DEFAULT_MODEL_REF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyZhipuProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||||
|
const models = { ...cfg.agents?.defaults?.models };
|
||||||
|
models[ZHIPU_DEFAULT_MODEL_REF] = {
|
||||||
|
...models[ZHIPU_DEFAULT_MODEL_REF],
|
||||||
|
alias: models[ZHIPU_DEFAULT_MODEL_REF]?.alias ?? "GLM",
|
||||||
|
};
|
||||||
|
|
||||||
|
const providers = { ...cfg.models?.providers };
|
||||||
|
const existingProvider = providers.zhipu;
|
||||||
|
const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : [];
|
||||||
|
const defaultModel = buildGlmModelDefinition();
|
||||||
|
const hasDefaultModel = existingModels.some((model) => model.id === GLM_DEFAULT_MODEL_ID);
|
||||||
|
const mergedModels = hasDefaultModel ? existingModels : [...existingModels, defaultModel];
|
||||||
|
const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> as { apiKey?: string };
|
||||||
|
const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined;
|
||||||
|
const normalizedApiKey = resolvedApiKey?.trim();
|
||||||
|
providers.zhipu = {
|
||||||
|
...existingProviderRest,
|
||||||
|
baseUrl: ZHIPU_BASE_URL,
|
||||||
|
api: "openai-completions",
|
||||||
|
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
|
||||||
|
models: mergedModels.length > 0 ? mergedModels : [defaultModel],
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
agents: {
|
||||||
|
...cfg.agents,
|
||||||
|
defaults: {
|
||||||
|
...cfg.agents?.defaults,
|
||||||
|
models,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
mode: cfg.models?.mode ?? "merge",
|
||||||
|
providers,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyZhipuConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||||
|
const next = applyZhipuProviderConfig(cfg);
|
||||||
|
const existingModel = next.agents?.defaults?.model;
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
agents: {
|
||||||
|
...next.agents,
|
||||||
|
defaults: {
|
||||||
|
...next.agents?.defaults,
|
||||||
|
model: {
|
||||||
|
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
|
||||||
|
? {
|
||||||
|
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
|
||||||
|
}
|
||||||
|
: undefined),
|
||||||
|
primary: ZHIPU_DEFAULT_MODEL_REF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyZhipuCodingProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||||
|
const models = { ...cfg.agents?.defaults?.models };
|
||||||
|
models[ZHIPU_CODING_DEFAULT_MODEL_REF] = {
|
||||||
|
...models[ZHIPU_CODING_DEFAULT_MODEL_REF],
|
||||||
|
alias: models[ZHIPU_CODING_DEFAULT_MODEL_REF]?.alias ?? "GLM Coding",
|
||||||
|
};
|
||||||
|
|
||||||
|
const providers = { ...cfg.models?.providers };
|
||||||
|
const existingProvider = providers["zhipu-coding"];
|
||||||
|
const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : [];
|
||||||
|
const defaultModel = buildGlmModelDefinition();
|
||||||
|
const hasDefaultModel = existingModels.some((model) => model.id === GLM_DEFAULT_MODEL_ID);
|
||||||
|
const mergedModels = hasDefaultModel ? existingModels : [...existingModels, defaultModel];
|
||||||
|
const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> as { apiKey?: string };
|
||||||
|
const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined;
|
||||||
|
const normalizedApiKey = resolvedApiKey?.trim();
|
||||||
|
providers["zhipu-coding"] = {
|
||||||
|
...existingProviderRest,
|
||||||
|
baseUrl: ZHIPU_CODING_BASE_URL,
|
||||||
|
api: "openai-completions",
|
||||||
|
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
|
||||||
|
models: mergedModels.length > 0 ? mergedModels : [defaultModel],
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
agents: {
|
||||||
|
...cfg.agents,
|
||||||
|
defaults: {
|
||||||
|
...cfg.agents?.defaults,
|
||||||
|
models,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
mode: cfg.models?.mode ?? "merge",
|
||||||
|
providers,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyZhipuCodingConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||||
|
const next = applyZhipuCodingProviderConfig(cfg);
|
||||||
|
const existingModel = next.agents?.defaults?.model;
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
agents: {
|
||||||
|
...next.agents,
|
||||||
|
defaults: {
|
||||||
|
...next.agents?.defaults,
|
||||||
|
model: {
|
||||||
|
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
|
||||||
|
? {
|
||||||
|
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
|
||||||
|
}
|
||||||
|
: undefined),
|
||||||
|
primary: ZHIPU_CODING_DEFAULT_MODEL_REF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function applyOpenrouterProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
|
export function applyOpenrouterProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||||
const models = { ...cfg.agents?.defaults?.models };
|
const models = { ...cfg.agents?.defaults?.models };
|
||||||
models[OPENROUTER_DEFAULT_MODEL_REF] = {
|
models[OPENROUTER_DEFAULT_MODEL_REF] = {
|
||||||
|
|||||||
@ -113,6 +113,9 @@ export async function setVeniceApiKey(key: string, agentDir?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
|
export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
|
||||||
|
export const ZAI_CODING_DEFAULT_MODEL_REF = "zai-coding/glm-4.7";
|
||||||
|
export const ZHIPU_DEFAULT_MODEL_REF = "zhipu/glm-4.7";
|
||||||
|
export const ZHIPU_CODING_DEFAULT_MODEL_REF = "zhipu-coding/glm-4.7";
|
||||||
export const XIAOMI_DEFAULT_MODEL_REF = "xiaomi/mimo-v2-flash";
|
export const XIAOMI_DEFAULT_MODEL_REF = "xiaomi/mimo-v2-flash";
|
||||||
export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";
|
export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";
|
||||||
export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5";
|
export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5";
|
||||||
@ -130,6 +133,45 @@ export async function setZaiApiKey(key: string, agentDir?: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setZaiCodingApiKey(key: string, agentDir?: string) {
|
||||||
|
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||||
|
upsertAuthProfile({
|
||||||
|
profileId: "zai-coding:default",
|
||||||
|
credential: {
|
||||||
|
type: "api_key",
|
||||||
|
provider: "zai-coding",
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setZhipuApiKey(key: string, agentDir?: string) {
|
||||||
|
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||||
|
upsertAuthProfile({
|
||||||
|
profileId: "zhipu:default",
|
||||||
|
credential: {
|
||||||
|
type: "api_key",
|
||||||
|
provider: "zhipu",
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setZhipuCodingApiKey(key: string, agentDir?: string) {
|
||||||
|
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||||
|
upsertAuthProfile({
|
||||||
|
profileId: "zhipu-coding:default",
|
||||||
|
credential: {
|
||||||
|
type: "api_key",
|
||||||
|
provider: "zhipu-coding",
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function setXiaomiApiKey(key: string, agentDir?: string) {
|
export async function setXiaomiApiKey(key: string, agentDir?: string) {
|
||||||
upsertAuthProfile({
|
upsertAuthProfile({
|
||||||
profileId: "xiaomi:default",
|
profileId: "xiaomi:default",
|
||||||
|
|||||||
@ -20,6 +20,11 @@ export const KIMI_CODE_MAX_TOKENS = 32768;
|
|||||||
export const KIMI_CODE_HEADERS = { "User-Agent": "KimiCLI/0.77" } as const;
|
export const KIMI_CODE_HEADERS = { "User-Agent": "KimiCLI/0.77" } as const;
|
||||||
export const KIMI_CODE_COMPAT = { supportsDeveloperRole: false } as const;
|
export const KIMI_CODE_COMPAT = { supportsDeveloperRole: false } as const;
|
||||||
|
|
||||||
|
// GLM (Z.AI / Zhipu AI) models
|
||||||
|
export const GLM_DEFAULT_MODEL_ID = "glm-4.7";
|
||||||
|
export const GLM_DEFAULT_CONTEXT_WINDOW = 205000;
|
||||||
|
export const GLM_DEFAULT_MAX_TOKENS = 8192;
|
||||||
|
|
||||||
// Pricing: MiniMax doesn't publish public rates. Override in models.json for accurate costs.
|
// Pricing: MiniMax doesn't publish public rates. Override in models.json for accurate costs.
|
||||||
export const MINIMAX_API_COST = {
|
export const MINIMAX_API_COST = {
|
||||||
input: 15,
|
input: 15,
|
||||||
@ -51,6 +56,12 @@ export const KIMI_CODE_DEFAULT_COST = {
|
|||||||
cacheRead: 0,
|
cacheRead: 0,
|
||||||
cacheWrite: 0,
|
cacheWrite: 0,
|
||||||
};
|
};
|
||||||
|
export const GLM_DEFAULT_COST = {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
};
|
||||||
|
|
||||||
const MINIMAX_MODEL_CATALOG = {
|
const MINIMAX_MODEL_CATALOG = {
|
||||||
"MiniMax-M2.1": { name: "MiniMax M2.1", reasoning: false },
|
"MiniMax-M2.1": { name: "MiniMax M2.1", reasoning: false },
|
||||||
@ -116,3 +127,15 @@ export function buildKimiCodeModelDefinition(): ModelDefinitionConfig {
|
|||||||
compat: KIMI_CODE_COMPAT,
|
compat: KIMI_CODE_COMPAT,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildGlmModelDefinition(): ModelDefinitionConfig {
|
||||||
|
return {
|
||||||
|
id: GLM_DEFAULT_MODEL_ID,
|
||||||
|
name: "GLM 4.7",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: GLM_DEFAULT_COST,
|
||||||
|
contextWindow: GLM_DEFAULT_CONTEXT_WINDOW,
|
||||||
|
maxTokens: GLM_DEFAULT_MAX_TOKENS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -19,7 +19,14 @@ export {
|
|||||||
applyVercelAiGatewayProviderConfig,
|
applyVercelAiGatewayProviderConfig,
|
||||||
applyXiaomiConfig,
|
applyXiaomiConfig,
|
||||||
applyXiaomiProviderConfig,
|
applyXiaomiProviderConfig,
|
||||||
|
applyZaiCodingConfig,
|
||||||
|
applyZaiCodingProviderConfig,
|
||||||
applyZaiConfig,
|
applyZaiConfig,
|
||||||
|
applyZaiProviderConfig,
|
||||||
|
applyZhipuCodingConfig,
|
||||||
|
applyZhipuCodingProviderConfig,
|
||||||
|
applyZhipuConfig,
|
||||||
|
applyZhipuProviderConfig,
|
||||||
} from "./onboard-auth.config-core.js";
|
} from "./onboard-auth.config-core.js";
|
||||||
export {
|
export {
|
||||||
applyMinimaxApiConfig,
|
applyMinimaxApiConfig,
|
||||||
@ -48,10 +55,16 @@ export {
|
|||||||
setVercelAiGatewayApiKey,
|
setVercelAiGatewayApiKey,
|
||||||
setXiaomiApiKey,
|
setXiaomiApiKey,
|
||||||
setZaiApiKey,
|
setZaiApiKey,
|
||||||
|
setZaiCodingApiKey,
|
||||||
|
setZhipuApiKey,
|
||||||
|
setZhipuCodingApiKey,
|
||||||
writeOAuthCredentials,
|
writeOAuthCredentials,
|
||||||
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
||||||
XIAOMI_DEFAULT_MODEL_REF,
|
XIAOMI_DEFAULT_MODEL_REF,
|
||||||
|
ZAI_CODING_DEFAULT_MODEL_REF,
|
||||||
ZAI_DEFAULT_MODEL_REF,
|
ZAI_DEFAULT_MODEL_REF,
|
||||||
|
ZHIPU_CODING_DEFAULT_MODEL_REF,
|
||||||
|
ZHIPU_DEFAULT_MODEL_REF,
|
||||||
} from "./onboard-auth.credentials.js";
|
} from "./onboard-auth.credentials.js";
|
||||||
export {
|
export {
|
||||||
buildKimiCodeModelDefinition,
|
buildKimiCodeModelDefinition,
|
||||||
|
|||||||
@ -18,7 +18,10 @@ import {
|
|||||||
applyVeniceConfig,
|
applyVeniceConfig,
|
||||||
applyVercelAiGatewayConfig,
|
applyVercelAiGatewayConfig,
|
||||||
applyXiaomiConfig,
|
applyXiaomiConfig,
|
||||||
|
applyZaiCodingConfig,
|
||||||
applyZaiConfig,
|
applyZaiConfig,
|
||||||
|
applyZhipuCodingConfig,
|
||||||
|
applyZhipuConfig,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setGeminiApiKey,
|
setGeminiApiKey,
|
||||||
setKimiCodeApiKey,
|
setKimiCodeApiKey,
|
||||||
@ -31,6 +34,9 @@ import {
|
|||||||
setVercelAiGatewayApiKey,
|
setVercelAiGatewayApiKey,
|
||||||
setXiaomiApiKey,
|
setXiaomiApiKey,
|
||||||
setZaiApiKey,
|
setZaiApiKey,
|
||||||
|
setZaiCodingApiKey,
|
||||||
|
setZhipuApiKey,
|
||||||
|
setZhipuCodingApiKey,
|
||||||
} from "../../onboard-auth.js";
|
} from "../../onboard-auth.js";
|
||||||
import type { AuthChoice, OnboardOptions } from "../../onboard-types.js";
|
import type { AuthChoice, OnboardOptions } from "../../onboard-types.js";
|
||||||
import { resolveNonInteractiveApiKey } from "../api-keys.js";
|
import { resolveNonInteractiveApiKey } from "../api-keys.js";
|
||||||
@ -179,6 +185,63 @@ export async function applyNonInteractiveAuthChoice(params: {
|
|||||||
return applyZaiConfig(nextConfig);
|
return applyZaiConfig(nextConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authChoice === "zai-coding-api-key") {
|
||||||
|
const resolved = await resolveNonInteractiveApiKey({
|
||||||
|
provider: "zai-coding",
|
||||||
|
cfg: baseConfig,
|
||||||
|
flagValue: opts.zaiCodingApiKey ?? opts.zaiApiKey,
|
||||||
|
flagName: "--zai-coding-api-key",
|
||||||
|
envVar: "ZAI_CODING_API_KEY (or ZAI_API_KEY)",
|
||||||
|
runtime,
|
||||||
|
});
|
||||||
|
if (!resolved) return null;
|
||||||
|
if (resolved.source !== "profile") await setZaiCodingApiKey(resolved.key);
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "zai-coding:default",
|
||||||
|
provider: "zai-coding",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
return applyZaiCodingConfig(nextConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authChoice === "zhipu-api-key") {
|
||||||
|
const resolved = await resolveNonInteractiveApiKey({
|
||||||
|
provider: "zhipu",
|
||||||
|
cfg: baseConfig,
|
||||||
|
flagValue: opts.zhipuApiKey,
|
||||||
|
flagName: "--zhipu-api-key",
|
||||||
|
envVar: "ZHIPU_API_KEY",
|
||||||
|
runtime,
|
||||||
|
});
|
||||||
|
if (!resolved) return null;
|
||||||
|
if (resolved.source !== "profile") await setZhipuApiKey(resolved.key);
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "zhipu:default",
|
||||||
|
provider: "zhipu",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
return applyZhipuConfig(nextConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authChoice === "zhipu-coding-api-key") {
|
||||||
|
const resolved = await resolveNonInteractiveApiKey({
|
||||||
|
provider: "zhipu-coding",
|
||||||
|
cfg: baseConfig,
|
||||||
|
flagValue: opts.zhipuCodingApiKey ?? opts.zhipuApiKey,
|
||||||
|
flagName: "--zhipu-coding-api-key",
|
||||||
|
envVar: "ZHIPU_CODING_API_KEY (or ZHIPU_API_KEY)",
|
||||||
|
runtime,
|
||||||
|
});
|
||||||
|
if (!resolved) return null;
|
||||||
|
if (resolved.source !== "profile") await setZhipuCodingApiKey(resolved.key);
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "zhipu-coding:default",
|
||||||
|
provider: "zhipu-coding",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
return applyZhipuCodingConfig(nextConfig);
|
||||||
|
}
|
||||||
|
|
||||||
if (authChoice === "xiaomi-api-key") {
|
if (authChoice === "xiaomi-api-key") {
|
||||||
const resolved = await resolveNonInteractiveApiKey({
|
const resolved = await resolveNonInteractiveApiKey({
|
||||||
provider: "xiaomi",
|
provider: "xiaomi",
|
||||||
@ -380,7 +443,11 @@ export async function applyNonInteractiveAuthChoice(params: {
|
|||||||
authChoice === "oauth" ||
|
authChoice === "oauth" ||
|
||||||
authChoice === "chutes" ||
|
authChoice === "chutes" ||
|
||||||
authChoice === "openai-codex" ||
|
authChoice === "openai-codex" ||
|
||||||
authChoice === "qwen-portal"
|
authChoice === "qwen-portal" ||
|
||||||
|
authChoice === "github-copilot" ||
|
||||||
|
authChoice === "google-gemini-cli" ||
|
||||||
|
authChoice === "google-antigravity" ||
|
||||||
|
authChoice === "copilot-proxy"
|
||||||
) {
|
) {
|
||||||
runtime.error("OAuth requires interactive mode.");
|
runtime.error("OAuth requires interactive mode.");
|
||||||
runtime.exit(1);
|
runtime.exit(1);
|
||||||
|
|||||||
@ -23,6 +23,9 @@ export type AuthChoice =
|
|||||||
| "google-antigravity"
|
| "google-antigravity"
|
||||||
| "google-gemini-cli"
|
| "google-gemini-cli"
|
||||||
| "zai-api-key"
|
| "zai-api-key"
|
||||||
|
| "zai-coding-api-key"
|
||||||
|
| "zhipu-api-key"
|
||||||
|
| "zhipu-coding-api-key"
|
||||||
| "xiaomi-api-key"
|
| "xiaomi-api-key"
|
||||||
| "minimax-cloud"
|
| "minimax-cloud"
|
||||||
| "minimax"
|
| "minimax"
|
||||||
@ -68,6 +71,9 @@ export type OnboardOptions = {
|
|||||||
kimiCodeApiKey?: string;
|
kimiCodeApiKey?: string;
|
||||||
geminiApiKey?: string;
|
geminiApiKey?: string;
|
||||||
zaiApiKey?: string;
|
zaiApiKey?: string;
|
||||||
|
zaiCodingApiKey?: string;
|
||||||
|
zhipuApiKey?: string;
|
||||||
|
zhipuCodingApiKey?: string;
|
||||||
xiaomiApiKey?: string;
|
xiaomiApiKey?: string;
|
||||||
minimaxApiKey?: string;
|
minimaxApiKey?: string;
|
||||||
syntheticApiKey?: string;
|
syntheticApiKey?: string;
|
||||||
|
|||||||
@ -4,4 +4,4 @@ export { fetchCodexUsage } from "./provider-usage.fetch.codex.js";
|
|||||||
export { fetchCopilotUsage } from "./provider-usage.fetch.copilot.js";
|
export { fetchCopilotUsage } from "./provider-usage.fetch.copilot.js";
|
||||||
export { fetchGeminiUsage } from "./provider-usage.fetch.gemini.js";
|
export { fetchGeminiUsage } from "./provider-usage.fetch.gemini.js";
|
||||||
export { fetchMinimaxUsage } from "./provider-usage.fetch.minimax.js";
|
export { fetchMinimaxUsage } from "./provider-usage.fetch.minimax.js";
|
||||||
export { fetchZaiUsage } from "./provider-usage.fetch.zai.js";
|
export { fetchGlmUsage, fetchZaiUsage } from "./provider-usage.fetch.zai.js";
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { fetchJson } from "./provider-usage.fetch.shared.js";
|
import { fetchJson } from "./provider-usage.fetch.shared.js";
|
||||||
import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
|
import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
|
||||||
import type { ProviderUsageSnapshot, UsageWindow } from "./provider-usage.types.js";
|
import type {
|
||||||
|
ProviderUsageSnapshot,
|
||||||
|
UsageProviderId,
|
||||||
|
UsageWindow,
|
||||||
|
} from "./provider-usage.types.js";
|
||||||
|
|
||||||
type ZaiUsageResponse = {
|
type ZaiUsageResponse = {
|
||||||
success?: boolean;
|
success?: boolean;
|
||||||
@ -19,13 +23,24 @@ type ZaiUsageResponse = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function fetchZaiUsage(
|
type GlmUsageProviderId = "zai" | "zai-coding" | "zhipu" | "zhipu-coding";
|
||||||
|
|
||||||
|
const GLM_USAGE_URLS: Record<GlmUsageProviderId, string> = {
|
||||||
|
zai: "https://api.z.ai/api/monitor/usage/quota/limit",
|
||||||
|
"zai-coding": "https://api.z.ai/api/monitor/usage/quota/limit",
|
||||||
|
zhipu: "https://open.bigmodel.cn/api/monitor/usage/quota/limit",
|
||||||
|
"zhipu-coding": "https://open.bigmodel.cn/api/monitor/usage/quota/limit",
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function fetchGlmUsage(
|
||||||
|
provider: GlmUsageProviderId,
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
timeoutMs: number,
|
timeoutMs: number,
|
||||||
fetchFn: typeof fetch,
|
fetchFn: typeof fetch,
|
||||||
): Promise<ProviderUsageSnapshot> {
|
): Promise<ProviderUsageSnapshot> {
|
||||||
|
const url = GLM_USAGE_URLS[provider];
|
||||||
const res = await fetchJson(
|
const res = await fetchJson(
|
||||||
"https://api.z.ai/api/monitor/usage/quota/limit",
|
url,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@ -39,8 +54,8 @@ export async function fetchZaiUsage(
|
|||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
return {
|
return {
|
||||||
provider: "zai",
|
provider: provider as UsageProviderId,
|
||||||
displayName: PROVIDER_LABELS.zai,
|
displayName: PROVIDER_LABELS[provider],
|
||||||
windows: [],
|
windows: [],
|
||||||
error: `HTTP ${res.status}`,
|
error: `HTTP ${res.status}`,
|
||||||
};
|
};
|
||||||
@ -49,8 +64,8 @@ export async function fetchZaiUsage(
|
|||||||
const data = (await res.json()) as ZaiUsageResponse;
|
const data = (await res.json()) as ZaiUsageResponse;
|
||||||
if (!data.success || data.code !== 200) {
|
if (!data.success || data.code !== 200) {
|
||||||
return {
|
return {
|
||||||
provider: "zai",
|
provider: provider as UsageProviderId,
|
||||||
displayName: PROVIDER_LABELS.zai,
|
displayName: PROVIDER_LABELS[provider],
|
||||||
windows: [],
|
windows: [],
|
||||||
error: data.msg || "API error",
|
error: data.msg || "API error",
|
||||||
};
|
};
|
||||||
@ -84,9 +99,18 @@ export async function fetchZaiUsage(
|
|||||||
|
|
||||||
const planName = data.data?.planName || data.data?.plan || undefined;
|
const planName = data.data?.planName || data.data?.plan || undefined;
|
||||||
return {
|
return {
|
||||||
provider: "zai",
|
provider: provider as UsageProviderId,
|
||||||
displayName: PROVIDER_LABELS.zai,
|
displayName: PROVIDER_LABELS[provider],
|
||||||
windows,
|
windows,
|
||||||
plan: planName,
|
plan: planName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated Use fetchGlmUsage instead */
|
||||||
|
export async function fetchZaiUsage(
|
||||||
|
apiKey: string,
|
||||||
|
timeoutMs: number,
|
||||||
|
fetchFn: typeof fetch,
|
||||||
|
): Promise<ProviderUsageSnapshot> {
|
||||||
|
return fetchGlmUsage("zai", apiKey, timeoutMs, fetchFn);
|
||||||
|
}
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import {
|
|||||||
fetchCodexUsage,
|
fetchCodexUsage,
|
||||||
fetchCopilotUsage,
|
fetchCopilotUsage,
|
||||||
fetchGeminiUsage,
|
fetchGeminiUsage,
|
||||||
|
fetchGlmUsage,
|
||||||
fetchMinimaxUsage,
|
fetchMinimaxUsage,
|
||||||
fetchZaiUsage,
|
|
||||||
} from "./provider-usage.fetch.js";
|
} from "./provider-usage.fetch.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_TIMEOUT_MS,
|
DEFAULT_TIMEOUT_MS,
|
||||||
@ -73,7 +73,10 @@ export async function loadProviderUsageSummary(
|
|||||||
windows: [],
|
windows: [],
|
||||||
};
|
};
|
||||||
case "zai":
|
case "zai":
|
||||||
return await fetchZaiUsage(auth.token, timeoutMs, fetchFn);
|
case "zai-coding":
|
||||||
|
case "zhipu":
|
||||||
|
case "zhipu-coding":
|
||||||
|
return await fetchGlmUsage(auth.provider, auth.token, timeoutMs, fetchFn);
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
provider: auth.provider,
|
provider: auth.provider,
|
||||||
|
|||||||
@ -11,7 +11,10 @@ export const PROVIDER_LABELS: Record<UsageProviderId, string> = {
|
|||||||
minimax: "MiniMax",
|
minimax: "MiniMax",
|
||||||
"openai-codex": "Codex",
|
"openai-codex": "Codex",
|
||||||
xiaomi: "Xiaomi",
|
xiaomi: "Xiaomi",
|
||||||
zai: "z.ai",
|
zai: "Z.AI",
|
||||||
|
"zai-coding": "Z.AI Coding",
|
||||||
|
zhipu: "Zhipu AI",
|
||||||
|
"zhipu-coding": "Zhipu AI Coding",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const usageProviders: UsageProviderId[] = [
|
export const usageProviders: UsageProviderId[] = [
|
||||||
@ -23,6 +26,9 @@ export const usageProviders: UsageProviderId[] = [
|
|||||||
"openai-codex",
|
"openai-codex",
|
||||||
"xiaomi",
|
"xiaomi",
|
||||||
"zai",
|
"zai",
|
||||||
|
"zai-coding",
|
||||||
|
"zhipu",
|
||||||
|
"zhipu-coding",
|
||||||
];
|
];
|
||||||
|
|
||||||
export function resolveUsageProviderId(provider?: string | null): UsageProviderId | undefined {
|
export function resolveUsageProviderId(provider?: string | null): UsageProviderId | undefined {
|
||||||
|
|||||||
@ -137,6 +137,206 @@ describe("provider usage loading", () => {
|
|||||||
expect(mockFetch).toHaveBeenCalled();
|
expect(mockFetch).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("loads zhipu usage from bigmodel.cn endpoint", async () => {
|
||||||
|
const makeResponse = (status: number, body: unknown): Response => {
|
||||||
|
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
||||||
|
const headers = typeof body === "string" ? undefined : { "Content-Type": "application/json" };
|
||||||
|
return new Response(payload, { status, headers });
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockFetch = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>(async (input) => {
|
||||||
|
const url =
|
||||||
|
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||||
|
if (url.includes("open.bigmodel.cn")) {
|
||||||
|
return makeResponse(200, {
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
planName: "Basic",
|
||||||
|
limits: [
|
||||||
|
{
|
||||||
|
type: "TOKENS_LIMIT",
|
||||||
|
percentage: 30,
|
||||||
|
unit: 3,
|
||||||
|
number: 6,
|
||||||
|
nextResetTime: "2026-01-07T06:00:00Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return makeResponse(404, "not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
const summary = await loadProviderUsageSummary({
|
||||||
|
now: Date.UTC(2026, 0, 7, 0, 0, 0),
|
||||||
|
auth: [{ provider: "zhipu", token: "token-1" }],
|
||||||
|
fetch: mockFetch,
|
||||||
|
});
|
||||||
|
|
||||||
|
const zhipu = summary.providers.find((p) => p.provider === "zhipu");
|
||||||
|
expect(zhipu?.plan).toBe("Basic");
|
||||||
|
expect(zhipu?.windows[0]?.usedPercent).toBe(30);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns error snapshot when GLM API returns HTTP error", async () => {
|
||||||
|
const makeResponse = (status: number, body: unknown): Response => {
|
||||||
|
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
||||||
|
const headers = typeof body === "string" ? undefined : { "Content-Type": "application/json" };
|
||||||
|
return new Response(payload, { status, headers });
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockFetch = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>(async (input) => {
|
||||||
|
const url =
|
||||||
|
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||||
|
if (url.includes("api.z.ai")) {
|
||||||
|
return makeResponse(500, "Internal Server Error");
|
||||||
|
}
|
||||||
|
return makeResponse(404, "not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
const summary = await loadProviderUsageSummary({
|
||||||
|
now: Date.UTC(2026, 0, 7, 0, 0, 0),
|
||||||
|
auth: [{ provider: "zai", token: "token-1" }],
|
||||||
|
fetch: mockFetch,
|
||||||
|
});
|
||||||
|
|
||||||
|
const zai = summary.providers.find((p) => p.provider === "zai");
|
||||||
|
expect(zai?.error).toBe("HTTP 500");
|
||||||
|
expect(zai?.windows).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns error snapshot when GLM API returns non-200 code", async () => {
|
||||||
|
const makeResponse = (status: number, body: unknown): Response => {
|
||||||
|
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
||||||
|
const headers = typeof body === "string" ? undefined : { "Content-Type": "application/json" };
|
||||||
|
return new Response(payload, { status, headers });
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockFetch = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>(async (input) => {
|
||||||
|
const url =
|
||||||
|
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||||
|
if (url.includes("api.z.ai")) {
|
||||||
|
return makeResponse(200, {
|
||||||
|
success: false,
|
||||||
|
code: 401,
|
||||||
|
msg: "Invalid API key",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return makeResponse(404, "not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
const summary = await loadProviderUsageSummary({
|
||||||
|
now: Date.UTC(2026, 0, 7, 0, 0, 0),
|
||||||
|
auth: [{ provider: "zai", token: "invalid-token" }],
|
||||||
|
fetch: mockFetch,
|
||||||
|
});
|
||||||
|
|
||||||
|
const zai = summary.providers.find((p) => p.provider === "zai");
|
||||||
|
expect(zai?.error).toBe("Invalid API key");
|
||||||
|
expect(zai?.windows).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles GLM TIME_LIMIT as Monthly window", async () => {
|
||||||
|
const makeResponse = (status: number, body: unknown): Response => {
|
||||||
|
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
||||||
|
const headers = typeof body === "string" ? undefined : { "Content-Type": "application/json" };
|
||||||
|
return new Response(payload, { status, headers });
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockFetch = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>(async (input) => {
|
||||||
|
const url =
|
||||||
|
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||||
|
if (url.includes("api.z.ai")) {
|
||||||
|
return makeResponse(200, {
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
planName: "Enterprise",
|
||||||
|
limits: [
|
||||||
|
{
|
||||||
|
type: "TIME_LIMIT",
|
||||||
|
percentage: 45,
|
||||||
|
unit: 1,
|
||||||
|
number: 30,
|
||||||
|
nextResetTime: "2026-02-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return makeResponse(404, "not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
const summary = await loadProviderUsageSummary({
|
||||||
|
now: Date.UTC(2026, 0, 7, 0, 0, 0),
|
||||||
|
auth: [{ provider: "zai", token: "token-1" }],
|
||||||
|
fetch: mockFetch,
|
||||||
|
});
|
||||||
|
|
||||||
|
const zai = summary.providers.find((p) => p.provider === "zai");
|
||||||
|
expect(zai?.plan).toBe("Enterprise");
|
||||||
|
expect(zai?.windows).toHaveLength(1);
|
||||||
|
expect(zai?.windows[0]?.label).toBe("Monthly");
|
||||||
|
expect(zai?.windows[0]?.usedPercent).toBe(45);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles GLM response with both TOKENS_LIMIT and TIME_LIMIT", async () => {
|
||||||
|
const makeResponse = (status: number, body: unknown): Response => {
|
||||||
|
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
||||||
|
const headers = typeof body === "string" ? undefined : { "Content-Type": "application/json" };
|
||||||
|
return new Response(payload, { status, headers });
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockFetch = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>(async (input) => {
|
||||||
|
const url =
|
||||||
|
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||||
|
if (url.includes("api.z.ai")) {
|
||||||
|
return makeResponse(200, {
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
planName: "Pro",
|
||||||
|
limits: [
|
||||||
|
{
|
||||||
|
type: "TOKENS_LIMIT",
|
||||||
|
percentage: 25,
|
||||||
|
unit: 3,
|
||||||
|
number: 24,
|
||||||
|
nextResetTime: "2026-01-08T00:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "TIME_LIMIT",
|
||||||
|
percentage: 10,
|
||||||
|
unit: 1,
|
||||||
|
number: 30,
|
||||||
|
nextResetTime: "2026-02-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return makeResponse(404, "not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
const summary = await loadProviderUsageSummary({
|
||||||
|
now: Date.UTC(2026, 0, 7, 0, 0, 0),
|
||||||
|
auth: [{ provider: "zai", token: "token-1" }],
|
||||||
|
fetch: mockFetch,
|
||||||
|
});
|
||||||
|
|
||||||
|
const zai = summary.providers.find((p) => p.provider === "zai");
|
||||||
|
expect(zai?.plan).toBe("Pro");
|
||||||
|
expect(zai?.windows).toHaveLength(2);
|
||||||
|
|
||||||
|
const tokensWindow = zai?.windows.find((w) => w.label.startsWith("Tokens"));
|
||||||
|
expect(tokensWindow?.label).toBe("Tokens (24h)");
|
||||||
|
expect(tokensWindow?.usedPercent).toBe(25);
|
||||||
|
|
||||||
|
const monthlyWindow = zai?.windows.find((w) => w.label === "Monthly");
|
||||||
|
expect(monthlyWindow?.usedPercent).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
it("handles nested MiniMax usage payloads", async () => {
|
it("handles nested MiniMax usage payloads", async () => {
|
||||||
const makeResponse = (status: number, body: unknown): Response => {
|
const makeResponse = (status: number, body: unknown): Response => {
|
||||||
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
||||||
|
|||||||
@ -25,4 +25,7 @@ export type UsageProviderId =
|
|||||||
| "minimax"
|
| "minimax"
|
||||||
| "openai-codex"
|
| "openai-codex"
|
||||||
| "xiaomi"
|
| "xiaomi"
|
||||||
| "zai";
|
| "zai"
|
||||||
|
| "zai-coding"
|
||||||
|
| "zhipu"
|
||||||
|
| "zhipu-coding";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user