feat: add ZAI auth choice

This commit is contained in:
Peter Steinberger
2026-01-10 16:32:21 +01:00
parent 8b47368167
commit 41c8bdfada
11 changed files with 156 additions and 2 deletions

View File

@@ -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.

View File

@@ -178,7 +178,7 @@ Options:
- `--workspace <dir>`
- `--non-interactive`
- `--mode <local|remote>`
- `--auth-choice <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-choice <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>`
- `--token-provider <id>` (non-interactive; used with `--auth-choice token`)
- `--token <token>` (non-interactive; used with `--auth-choice token`)
- `--token-profile-id <id>` (non-interactive; default: `<provider>:manual`)
@@ -186,6 +186,7 @@ Options:
- `--anthropic-api-key <key>`
- `--openai-api-key <key>`
- `--gemini-api-key <key>`
- `--zai-api-key <key>`
- `--minimax-api-key <key>`
- `--opencode-zen-api-key <key>`
- `--gateway-port <port>`

View File

@@ -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: {

View File

@@ -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

View File

@@ -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"], {

View File

@@ -245,7 +245,7 @@ export function buildProgram() {
.option("--mode <mode>", "Wizard mode: local|remote")
.option(
"--auth-choice <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 <id>",
@@ -266,6 +266,7 @@ export function buildProgram() {
.option("--anthropic-api-key <key>", "Anthropic API key")
.option("--openai-api-key <key>", "OpenAI API key")
.option("--gemini-api-key <key>", "Gemini API key")
.option("--zai-api-key <key>", "Z.AI API key")
.option("--minimax-api-key <key>", "MiniMax API key")
.option("--opencode-zen-api-key <key>", "OpenCode Zen API key")
.option("--gateway-port <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:

View File

@@ -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({

View File

@@ -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",

View File

@@ -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<string, unknown>)
? {
fallbacks: (existingModel as { fallbacks?: string[] })
.fallbacks,
}
: undefined),
primary: ZAI_DEFAULT_MODEL_REF,
},
},
},
};
}
export function applyAuthProfileConfig(
cfg: ClawdbotConfig,
params: {

View File

@@ -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",

View File

@@ -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;