diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index f13eef365..3a323a3af 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -22,7 +22,8 @@ export type AuthChoiceGroupId = | "minimax" | "synthetic" | "venice" - | "qwen"; + | "qwen" + | "xai"; export type AuthChoiceGroup = { value: AuthChoiceGroupId; @@ -115,6 +116,12 @@ const AUTH_CHOICE_GROUP_DEFS: { hint: "API key", choices: ["opencode-zen"], }, + { + value: "xai", + label: "xAI", + hint: "Grok models (API key)", + choices: ["xai-api-key"], + }, ]; function formatOAuthHint(expires?: number, opts?: { allowStale?: boolean }): string { @@ -219,6 +226,7 @@ export function buildAuthChoiceOptions(params: { 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: "xai-api-key", label: "xAI API key" }); options.push({ value: "qwen-portal", label: "Qwen OAuth" }); options.push({ value: "copilot-proxy", diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index 8be02008b..edbcf8d63 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -27,6 +27,8 @@ import { applyVeniceProviderConfig, applyVercelAiGatewayConfig, applyVercelAiGatewayProviderConfig, + applyXaiConfig, + applyXaiProviderConfig, applyZaiConfig, KIMI_CODE_MODEL_REF, MOONSHOT_DEFAULT_MODEL_REF, @@ -42,7 +44,9 @@ import { setSyntheticApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, + setXaiApiKey, setZaiApiKey, + XAI_DEFAULT_MODEL_REF, ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.js"; import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js"; @@ -85,6 +89,8 @@ export async function applyAuthChoiceApiProviders( authChoice = "venice-api-key"; } else if (params.opts.tokenProvider === "opencode") { authChoice = "opencode-zen"; + } else if (params.opts.tokenProvider === "xai") { + authChoice = "xai-api-key"; } } @@ -579,5 +585,53 @@ export async function applyAuthChoiceApiProviders( return { config: nextConfig, agentModelOverride }; } + if (authChoice === "xai-api-key") { + let hasCredential = false; + + if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "xai") { + await setXaiApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); + hasCredential = true; + } + + const envKey = resolveEnvApiKey("xai"); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing XAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await setXaiApiKey(envKey.apiKey, params.agentDir); + hasCredential = true; + } + } + if (!hasCredential) { + const key = await params.prompter.text({ + message: "Enter xAI API key", + validate: validateApiKeyInput, + }); + await setXaiApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "xai:default", + provider: "xai", + mode: "api_key", + }); + { + const applied = await applyDefaultModelChoice({ + config: nextConfig, + setDefaultModel: params.setDefaultModel, + defaultModel: XAI_DEFAULT_MODEL_REF, + applyDefaultConfig: applyXaiConfig, + applyProviderConfig: applyXaiProviderConfig, + noteDefault: XAI_DEFAULT_MODEL_REF, + noteAgentModel, + prompter: params.prompter, + }); + nextConfig = applied.config; + agentModelOverride = applied.agentModelOverride ?? agentModelOverride; + } + return { config: nextConfig, agentModelOverride }; + } + return null; } diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 8e324113e..278fca010 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -14,6 +14,7 @@ import type { ClawdbotConfig } from "../config/config.js"; import { OPENROUTER_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, + XAI_DEFAULT_MODEL_REF, ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.credentials.js"; import { @@ -55,6 +56,47 @@ export function applyZaiConfig(cfg: ClawdbotConfig): ClawdbotConfig { }; } +export function applyXaiProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[XAI_DEFAULT_MODEL_REF] = { + ...models[XAI_DEFAULT_MODEL_REF], + alias: models[XAI_DEFAULT_MODEL_REF]?.alias ?? "Grok", + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + }; +} + +export function applyXaiConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const next = applyXaiProviderConfig(cfg); + const existingModel = next.agents?.defaults?.model; + return { + ...next, + agents: { + ...next.agents, + defaults: { + ...next.agents?.defaults, + model: { + ...(existingModel && "fallbacks" in (existingModel as Record) + ? { + fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks, + } + : undefined), + primary: XAI_DEFAULT_MODEL_REF, + }, + }, + }, + }; +} + export function applyOpenrouterProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { const models = { ...cfg.agents?.defaults?.models }; models[OPENROUTER_DEFAULT_MODEL_REF] = { diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index 0c7dff409..ba35a1ddf 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -113,6 +113,7 @@ export async function setVeniceApiKey(key: string, agentDir?: string) { } export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7"; +export const XAI_DEFAULT_MODEL_REF = "xai/grok-4.1"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5"; @@ -164,3 +165,15 @@ export async function setOpencodeZenApiKey(key: string, agentDir?: string) { agentDir: resolveAuthAgentDir(agentDir), }); } + +export async function setXaiApiKey(key: string, agentDir?: string) { + upsertAuthProfile({ + profileId: "xai:default", + credential: { + type: "api_key", + provider: "xai", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index 3ddeca48c..bb1f4ec00 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -20,6 +20,8 @@ export { applyVeniceProviderConfig, applyVercelAiGatewayConfig, applyVercelAiGatewayProviderConfig, + applyXaiConfig, + applyXaiProviderConfig, applyZaiConfig, } from "./onboard-auth.config-core.js"; export { @@ -47,9 +49,11 @@ export { setSyntheticApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, + setXaiApiKey, setZaiApiKey, writeOAuthCredentials, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, + XAI_DEFAULT_MODEL_REF, ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.credentials.js"; export { diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index 84c15afc4..0700d2a65 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -31,6 +31,7 @@ export type AuthChoice = | "github-copilot" | "copilot-proxy" | "qwen-portal" + | "xai-api-key" | "skip"; export type GatewayAuthChoice = "off" | "token" | "password"; export type ResetScope = "config" | "config+creds+sessions" | "full"; @@ -71,6 +72,7 @@ export type OnboardOptions = { syntheticApiKey?: string; veniceApiKey?: string; opencodeZenApiKey?: string; + xaiApiKey?: string; gatewayPort?: number; gatewayBind?: GatewayBind; gatewayAuth?: GatewayAuthChoice;