diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b1db3aa6..7d0160a4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 2026.1.10 +### New Features and Changes +- Onboarding/Models: add first-class Z.AI (GLM) auth choice (`zai-api-key`) + `--zai-api-key` flag. + ### Fixes - Agents/OpenAI: fix Responses tool-only → follow-up turn handling (avoid standalone `reasoning` items that trigger 400 “required following item”). - Auth: update Claude Code keychain credentials in-place during refresh sync; extract CLI sync helpers + coverage. diff --git a/docs/cli/index.md b/docs/cli/index.md index 177234287..d73979756 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -178,7 +178,7 @@ Options: - `--workspace ` - `--non-interactive` - `--mode ` -- `--auth-choice ` +- `--auth-choice ` - `--token-provider ` (non-interactive; used with `--auth-choice token`) - `--token ` (non-interactive; used with `--auth-choice token`) - `--token-profile-id ` (non-interactive; default: `:manual`) @@ -186,6 +186,7 @@ Options: - `--anthropic-api-key ` - `--openai-api-key ` - `--gemini-api-key ` +- `--zai-api-key ` - `--minimax-api-key ` - `--opencode-zen-api-key ` - `--gateway-port ` diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index 508057782..7e97a3273 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -1491,6 +1491,8 @@ Notes: Z.AI models are available via the built-in `zai` provider. Set `ZAI_API_KEY` in your environment and reference the model by provider/model. +Shortcut: `clawdbot onboard --auth-choice zai-api-key`. + ```json5 { agents: { diff --git a/docs/start/wizard.md b/docs/start/wizard.md index 9594c6e7d..25b60cc46 100644 --- a/docs/start/wizard.md +++ b/docs/start/wizard.md @@ -186,6 +186,17 @@ clawdbot onboard --non-interactive \ --gateway-bind loopback ``` +Z.AI example: + +```bash +clawdbot onboard --non-interactive \ + --mode local \ + --auth-choice zai-api-key \ + --zai-api-key "$ZAI_API_KEY" \ + --gateway-port 18789 \ + --gateway-bind loopback +``` + OpenCode Zen example: ```bash diff --git a/src/cli/program.test.ts b/src/cli/program.test.ts index e69655f37..ff7f9c095 100644 --- a/src/cli/program.test.ts +++ b/src/cli/program.test.ts @@ -156,6 +156,29 @@ describe("cli program", () => { ); }); + it("passes zai api key to onboard", async () => { + const program = buildProgram(); + await program.parseAsync( + [ + "onboard", + "--non-interactive", + "--auth-choice", + "zai-api-key", + "--zai-api-key", + "sk-zai-test", + ], + { from: "user" }, + ); + expect(onboardCommand).toHaveBeenCalledWith( + expect.objectContaining({ + nonInteractive: true, + authChoice: "zai-api-key", + zaiApiKey: "sk-zai-test", + }), + runtime, + ); + }); + it("runs providers login", async () => { const program = buildProgram(); await program.parseAsync(["providers", "login", "--account", "work"], { diff --git a/src/cli/program.ts b/src/cli/program.ts index 80ae5c7d5..f0afdcc25 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -245,7 +245,7 @@ export function buildProgram() { .option("--mode ", "Wizard mode: local|remote") .option( "--auth-choice ", - "Auth: setup-token|claude-cli|token|openai-codex|openai-api-key|codex-cli|antigravity|gemini-api-key|apiKey|minimax-cloud|minimax-api|minimax|opencode-zen|skip", + "Auth: setup-token|claude-cli|token|openai-codex|openai-api-key|codex-cli|antigravity|gemini-api-key|zai-api-key|apiKey|minimax-cloud|minimax-api|minimax|opencode-zen|skip", ) .option( "--token-provider ", @@ -266,6 +266,7 @@ export function buildProgram() { .option("--anthropic-api-key ", "Anthropic API key") .option("--openai-api-key ", "OpenAI API key") .option("--gemini-api-key ", "Gemini API key") + .option("--zai-api-key ", "Z.AI API key") .option("--minimax-api-key ", "MiniMax API key") .option("--opencode-zen-api-key ", "OpenCode Zen API key") .option("--gateway-port ", "Gateway port") @@ -313,6 +314,7 @@ export function buildProgram() { | "codex-cli" | "antigravity" | "gemini-api-key" + | "zai-api-key" | "apiKey" | "minimax-cloud" | "minimax-api" @@ -327,6 +329,7 @@ export function buildProgram() { anthropicApiKey: opts.anthropicApiKey as string | undefined, openaiApiKey: opts.openaiApiKey as string | undefined, geminiApiKey: opts.geminiApiKey as string | undefined, + zaiApiKey: opts.zaiApiKey as string | undefined, minimaxApiKey: opts.minimaxApiKey as string | undefined, opencodeZenApiKey: opts.opencodeZenApiKey as string | undefined, gatewayPort: diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index c154ef4a4..50115073d 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -97,6 +97,7 @@ export function buildAuthChoiceOptions(params: { label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)", }); options.push({ value: "gemini-api-key", label: "Google Gemini API key" }); + options.push({ value: "zai-api-key", label: "Z.AI (GLM) API key" }); options.push({ value: "apiKey", label: "Anthropic API key" }); // Token flow is currently Anthropic-only; use CLI for advanced providers. options.push({ diff --git a/src/commands/auth-choice.ts b/src/commands/auth-choice.ts index 5f292bdd6..1d09e220c 100644 --- a/src/commands/auth-choice.ts +++ b/src/commands/auth-choice.ts @@ -44,12 +44,15 @@ import { applyMinimaxProviderConfig, applyOpencodeZenConfig, applyOpencodeZenProviderConfig, + applyZaiConfig, MINIMAX_HOSTED_MODEL_REF, setAnthropicApiKey, setGeminiApiKey, setMinimaxApiKey, setOpencodeZenApiKey, + setZaiApiKey, writeOAuthCredentials, + ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.js"; import { openUrl } from "./onboard-helpers.js"; import type { AuthChoice } from "./onboard-types.js"; @@ -598,6 +601,45 @@ export async function applyAuthChoice(params: { agentModelOverride = GOOGLE_GEMINI_DEFAULT_MODEL; await noteAgentModel(GOOGLE_GEMINI_DEFAULT_MODEL); } + } else if (params.authChoice === "zai-api-key") { + const key = await params.prompter.text({ + message: "Enter Z.AI API key", + validate: (value) => (value?.trim() ? undefined : "Required"), + }); + await setZaiApiKey(String(key).trim(), params.agentDir); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "zai:default", + provider: "zai", + mode: "api_key", + }); + if (params.setDefaultModel) { + nextConfig = applyZaiConfig(nextConfig); + await params.prompter.note( + `Default model set to ${ZAI_DEFAULT_MODEL_REF}`, + "Model configured", + ); + } else { + nextConfig = { + ...nextConfig, + agents: { + ...nextConfig.agents, + defaults: { + ...nextConfig.agents?.defaults, + models: { + ...nextConfig.agents?.defaults?.models, + [ZAI_DEFAULT_MODEL_REF]: { + ...nextConfig.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF], + alias: + nextConfig.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF] + ?.alias ?? "GLM", + }, + }, + }, + }, + }; + agentModelOverride = ZAI_DEFAULT_MODEL_REF; + await noteAgentModel(ZAI_DEFAULT_MODEL_REF); + } } else if (params.authChoice === "apiKey") { const key = await params.prompter.text({ message: "Enter Anthropic API key", diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index 90406d145..65af6913c 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -134,6 +134,51 @@ export async function setMinimaxApiKey(key: string, agentDir?: string) { }); } +export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7"; + +export async function setZaiApiKey(key: string, agentDir?: string) { + // Write to the multi-agent path so gateway finds credentials on startup + upsertAuthProfile({ + profileId: "zai:default", + credential: { + type: "api_key", + provider: "zai", + key, + }, + agentDir: agentDir ?? resolveDefaultAgentDir(), + }); +} + +export function applyZaiConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[ZAI_DEFAULT_MODEL_REF] = { + ...models[ZAI_DEFAULT_MODEL_REF], + alias: models[ZAI_DEFAULT_MODEL_REF]?.alias ?? "GLM", + }; + + const existingModel = cfg.agents?.defaults?.model; + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + model: { + ...(existingModel && + "fallbacks" in (existingModel as Record) + ? { + fallbacks: (existingModel as { fallbacks?: string[] }) + .fallbacks, + } + : undefined), + primary: ZAI_DEFAULT_MODEL_REF, + }, + }, + }, + }; +} + export function applyAuthProfileConfig( cfg: ClawdbotConfig, params: { diff --git a/src/commands/onboard-non-interactive.ts b/src/commands/onboard-non-interactive.ts index 0b54de492..de6293296 100644 --- a/src/commands/onboard-non-interactive.ts +++ b/src/commands/onboard-non-interactive.ts @@ -36,10 +36,12 @@ import { applyMinimaxConfig, applyMinimaxHostedConfig, applyOpencodeZenConfig, + applyZaiConfig, setAnthropicApiKey, setGeminiApiKey, setMinimaxApiKey, setOpencodeZenApiKey, + setZaiApiKey, } from "./onboard-auth.js"; import { applyWizardMetadata, @@ -225,6 +227,25 @@ export async function runNonInteractiveOnboarding( mode: "api_key", }); nextConfig = applyGoogleGeminiModelDefault(nextConfig).next; + } else if (authChoice === "zai-api-key") { + const resolved = await resolveNonInteractiveApiKey({ + provider: "zai", + cfg: baseConfig, + flagValue: opts.zaiApiKey, + flagName: "--zai-api-key", + envVar: "ZAI_API_KEY", + runtime, + }); + if (!resolved) return; + if (resolved.source !== "profile") { + await setZaiApiKey(resolved.key); + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "zai:default", + provider: "zai", + mode: "api_key", + }); + nextConfig = applyZaiConfig(nextConfig); } else if (authChoice === "openai-api-key") { const resolved = await resolveNonInteractiveApiKey({ provider: "openai", diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index 09375bcb7..9f8b92d50 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -14,6 +14,7 @@ export type AuthChoice = | "antigravity" | "apiKey" | "gemini-api-key" + | "zai-api-key" | "minimax-cloud" | "minimax" | "minimax-api" @@ -43,6 +44,7 @@ export type OnboardOptions = { anthropicApiKey?: string; openaiApiKey?: string; geminiApiKey?: string; + zaiApiKey?: string; minimaxApiKey?: string; opencodeZenApiKey?: string; gatewayPort?: number;