feat: add ZAI auth choice
This commit is contained in:
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
## 2026.1.10
|
## 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
|
### Fixes
|
||||||
- Agents/OpenAI: fix Responses tool-only → follow-up turn handling (avoid standalone `reasoning` items that trigger 400 “required following item”).
|
- 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.
|
- Auth: update Claude Code keychain credentials in-place during refresh sync; extract CLI sync helpers + coverage.
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ Options:
|
|||||||
- `--workspace <dir>`
|
- `--workspace <dir>`
|
||||||
- `--non-interactive`
|
- `--non-interactive`
|
||||||
- `--mode <local|remote>`
|
- `--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-provider <id>` (non-interactive; used with `--auth-choice token`)
|
||||||
- `--token <token>` (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`)
|
- `--token-profile-id <id>` (non-interactive; default: `<provider>:manual`)
|
||||||
@@ -186,6 +186,7 @@ Options:
|
|||||||
- `--anthropic-api-key <key>`
|
- `--anthropic-api-key <key>`
|
||||||
- `--openai-api-key <key>`
|
- `--openai-api-key <key>`
|
||||||
- `--gemini-api-key <key>`
|
- `--gemini-api-key <key>`
|
||||||
|
- `--zai-api-key <key>`
|
||||||
- `--minimax-api-key <key>`
|
- `--minimax-api-key <key>`
|
||||||
- `--opencode-zen-api-key <key>`
|
- `--opencode-zen-api-key <key>`
|
||||||
- `--gateway-port <port>`
|
- `--gateway-port <port>`
|
||||||
|
|||||||
@@ -1491,6 +1491,8 @@ Notes:
|
|||||||
Z.AI models are available via the built-in `zai` provider. Set `ZAI_API_KEY`
|
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.
|
in your environment and reference the model by provider/model.
|
||||||
|
|
||||||
|
Shortcut: `clawdbot onboard --auth-choice zai-api-key`.
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
agents: {
|
agents: {
|
||||||
|
|||||||
@@ -186,6 +186,17 @@ clawdbot onboard --non-interactive \
|
|||||||
--gateway-bind loopback
|
--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:
|
OpenCode Zen example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -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 () => {
|
it("runs providers login", async () => {
|
||||||
const program = buildProgram();
|
const program = buildProgram();
|
||||||
await program.parseAsync(["providers", "login", "--account", "work"], {
|
await program.parseAsync(["providers", "login", "--account", "work"], {
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ export function buildProgram() {
|
|||||||
.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|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(
|
.option(
|
||||||
"--token-provider <id>",
|
"--token-provider <id>",
|
||||||
@@ -266,6 +266,7 @@ export function buildProgram() {
|
|||||||
.option("--anthropic-api-key <key>", "Anthropic API key")
|
.option("--anthropic-api-key <key>", "Anthropic API key")
|
||||||
.option("--openai-api-key <key>", "OpenAI API key")
|
.option("--openai-api-key <key>", "OpenAI 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("--minimax-api-key <key>", "MiniMax API key")
|
.option("--minimax-api-key <key>", "MiniMax API key")
|
||||||
.option("--opencode-zen-api-key <key>", "OpenCode Zen API key")
|
.option("--opencode-zen-api-key <key>", "OpenCode Zen API key")
|
||||||
.option("--gateway-port <port>", "Gateway port")
|
.option("--gateway-port <port>", "Gateway port")
|
||||||
@@ -313,6 +314,7 @@ export function buildProgram() {
|
|||||||
| "codex-cli"
|
| "codex-cli"
|
||||||
| "antigravity"
|
| "antigravity"
|
||||||
| "gemini-api-key"
|
| "gemini-api-key"
|
||||||
|
| "zai-api-key"
|
||||||
| "apiKey"
|
| "apiKey"
|
||||||
| "minimax-cloud"
|
| "minimax-cloud"
|
||||||
| "minimax-api"
|
| "minimax-api"
|
||||||
@@ -327,6 +329,7 @@ export function buildProgram() {
|
|||||||
anthropicApiKey: opts.anthropicApiKey as string | undefined,
|
anthropicApiKey: opts.anthropicApiKey as string | undefined,
|
||||||
openaiApiKey: opts.openaiApiKey as string | undefined,
|
openaiApiKey: opts.openaiApiKey as string | undefined,
|
||||||
geminiApiKey: opts.geminiApiKey as string | undefined,
|
geminiApiKey: opts.geminiApiKey as string | undefined,
|
||||||
|
zaiApiKey: opts.zaiApiKey as string | undefined,
|
||||||
minimaxApiKey: opts.minimaxApiKey as string | undefined,
|
minimaxApiKey: opts.minimaxApiKey as string | undefined,
|
||||||
opencodeZenApiKey: opts.opencodeZenApiKey as string | undefined,
|
opencodeZenApiKey: opts.opencodeZenApiKey as string | undefined,
|
||||||
gatewayPort:
|
gatewayPort:
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ export function buildAuthChoiceOptions(params: {
|
|||||||
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",
|
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",
|
||||||
});
|
});
|
||||||
options.push({ value: "gemini-api-key", label: "Google Gemini API key" });
|
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" });
|
options.push({ value: "apiKey", label: "Anthropic API key" });
|
||||||
// Token flow is currently Anthropic-only; use CLI for advanced providers.
|
// Token flow is currently Anthropic-only; use CLI for advanced providers.
|
||||||
options.push({
|
options.push({
|
||||||
|
|||||||
@@ -44,12 +44,15 @@ import {
|
|||||||
applyMinimaxProviderConfig,
|
applyMinimaxProviderConfig,
|
||||||
applyOpencodeZenConfig,
|
applyOpencodeZenConfig,
|
||||||
applyOpencodeZenProviderConfig,
|
applyOpencodeZenProviderConfig,
|
||||||
|
applyZaiConfig,
|
||||||
MINIMAX_HOSTED_MODEL_REF,
|
MINIMAX_HOSTED_MODEL_REF,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setGeminiApiKey,
|
setGeminiApiKey,
|
||||||
setMinimaxApiKey,
|
setMinimaxApiKey,
|
||||||
setOpencodeZenApiKey,
|
setOpencodeZenApiKey,
|
||||||
|
setZaiApiKey,
|
||||||
writeOAuthCredentials,
|
writeOAuthCredentials,
|
||||||
|
ZAI_DEFAULT_MODEL_REF,
|
||||||
} from "./onboard-auth.js";
|
} from "./onboard-auth.js";
|
||||||
import { openUrl } from "./onboard-helpers.js";
|
import { openUrl } from "./onboard-helpers.js";
|
||||||
import type { AuthChoice } from "./onboard-types.js";
|
import type { AuthChoice } from "./onboard-types.js";
|
||||||
@@ -598,6 +601,45 @@ export async function applyAuthChoice(params: {
|
|||||||
agentModelOverride = GOOGLE_GEMINI_DEFAULT_MODEL;
|
agentModelOverride = GOOGLE_GEMINI_DEFAULT_MODEL;
|
||||||
await noteAgentModel(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") {
|
} else if (params.authChoice === "apiKey") {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter Anthropic API key",
|
message: "Enter Anthropic API key",
|
||||||
|
|||||||
@@ -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(
|
export function applyAuthProfileConfig(
|
||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -36,10 +36,12 @@ import {
|
|||||||
applyMinimaxConfig,
|
applyMinimaxConfig,
|
||||||
applyMinimaxHostedConfig,
|
applyMinimaxHostedConfig,
|
||||||
applyOpencodeZenConfig,
|
applyOpencodeZenConfig,
|
||||||
|
applyZaiConfig,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setGeminiApiKey,
|
setGeminiApiKey,
|
||||||
setMinimaxApiKey,
|
setMinimaxApiKey,
|
||||||
setOpencodeZenApiKey,
|
setOpencodeZenApiKey,
|
||||||
|
setZaiApiKey,
|
||||||
} from "./onboard-auth.js";
|
} from "./onboard-auth.js";
|
||||||
import {
|
import {
|
||||||
applyWizardMetadata,
|
applyWizardMetadata,
|
||||||
@@ -225,6 +227,25 @@ export async function runNonInteractiveOnboarding(
|
|||||||
mode: "api_key",
|
mode: "api_key",
|
||||||
});
|
});
|
||||||
nextConfig = applyGoogleGeminiModelDefault(nextConfig).next;
|
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") {
|
} else if (authChoice === "openai-api-key") {
|
||||||
const resolved = await resolveNonInteractiveApiKey({
|
const resolved = await resolveNonInteractiveApiKey({
|
||||||
provider: "openai",
|
provider: "openai",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type AuthChoice =
|
|||||||
| "antigravity"
|
| "antigravity"
|
||||||
| "apiKey"
|
| "apiKey"
|
||||||
| "gemini-api-key"
|
| "gemini-api-key"
|
||||||
|
| "zai-api-key"
|
||||||
| "minimax-cloud"
|
| "minimax-cloud"
|
||||||
| "minimax"
|
| "minimax"
|
||||||
| "minimax-api"
|
| "minimax-api"
|
||||||
@@ -43,6 +44,7 @@ export type OnboardOptions = {
|
|||||||
anthropicApiKey?: string;
|
anthropicApiKey?: string;
|
||||||
openaiApiKey?: string;
|
openaiApiKey?: string;
|
||||||
geminiApiKey?: string;
|
geminiApiKey?: string;
|
||||||
|
zaiApiKey?: string;
|
||||||
minimaxApiKey?: string;
|
minimaxApiKey?: string;
|
||||||
opencodeZenApiKey?: string;
|
opencodeZenApiKey?: string;
|
||||||
gatewayPort?: number;
|
gatewayPort?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user