diff --git a/src/commands/auth-choice.apply.xai.ts b/src/commands/auth-choice.apply.xai.ts index 197fcae86..0a3192080 100644 --- a/src/commands/auth-choice.apply.xai.ts +++ b/src/commands/auth-choice.apply.xai.ts @@ -36,7 +36,7 @@ export async function applyAuthChoiceXAI( let hasCredential = false; const optsKey = params.opts?.xaiApiKey?.trim(); if (optsKey) { - await setXaiApiKey(normalizeApiKeyInput(optsKey), params.agentDir); + setXaiApiKey(normalizeApiKeyInput(optsKey), params.agentDir); hasCredential = true; } @@ -48,7 +48,7 @@ export async function applyAuthChoiceXAI( initialValue: true, }); if (useExisting) { - await setXaiApiKey(envKey.apiKey, params.agentDir); + setXaiApiKey(envKey.apiKey, params.agentDir); hasCredential = true; } } @@ -59,7 +59,7 @@ export async function applyAuthChoiceXAI( message: "Enter xAI API key", validate: validateApiKeyInput, }); - await setXaiApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + setXaiApiKey(normalizeApiKeyInput(String(key)), params.agentDir); } nextConfig = applyAuthProfileConfig(nextConfig, { diff --git a/src/commands/auth-choice.test.ts b/src/commands/auth-choice.test.ts index 6079531ca..545525d9f 100644 --- a/src/commands/auth-choice.test.ts +++ b/src/commands/auth-choice.test.ts @@ -237,7 +237,7 @@ describe("applyAuthChoice", () => { mode: "api_key", }); expect(result.config.agents?.defaults?.model?.primary).toBe("openai/gpt-4o-mini"); - expect(result.agentModelOverride).toBe("xai/grok-2-latest"); + expect(result.agentModelOverride).toBe("xai/grok-4"); const authProfilePath = authProfilePathFor(requireAgentDir()); const raw = await fs.readFile(authProfilePath, "utf8"); diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index 93c819239..c8992efff 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -205,7 +205,7 @@ export async function setOpencodeZenApiKey(key: string, agentDir?: string) { }); } -export async function setXaiApiKey(key: string, agentDir?: string) { +export function setXaiApiKey(key: string, agentDir?: string) { upsertAuthProfile({ profileId: "xai:default", credential: { diff --git a/src/commands/onboard-auth.models.ts b/src/commands/onboard-auth.models.ts index 043ba93e7..3873a877c 100644 --- a/src/commands/onboard-auth.models.ts +++ b/src/commands/onboard-auth.models.ts @@ -94,7 +94,7 @@ export function buildMoonshotModelDefinition(): ModelDefinitionConfig { } export const XAI_BASE_URL = "https://api.x.ai/v1"; -export const XAI_DEFAULT_MODEL_ID = "grok-2-latest"; +export const XAI_DEFAULT_MODEL_ID = "grok-4"; export const XAI_DEFAULT_MODEL_REF = `xai/${XAI_DEFAULT_MODEL_ID}`; export const XAI_DEFAULT_CONTEXT_WINDOW = 131072; export const XAI_DEFAULT_MAX_TOKENS = 8192; @@ -108,7 +108,7 @@ export const XAI_DEFAULT_COST = { export function buildXaiModelDefinition(): ModelDefinitionConfig { return { id: XAI_DEFAULT_MODEL_ID, - name: "Grok 2", + name: "Grok 4", reasoning: false, input: ["text"], cost: XAI_DEFAULT_COST, diff --git a/src/commands/onboard-auth.test.ts b/src/commands/onboard-auth.test.ts index 096e6f086..0da6e1d3f 100644 --- a/src/commands/onboard-auth.test.ts +++ b/src/commands/onboard-auth.test.ts @@ -13,11 +13,14 @@ import { applyOpenrouterProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, + applyXaiConfig, + applyXaiProviderConfig, applyXiaomiConfig, applyXiaomiProviderConfig, OPENROUTER_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_ID, SYNTHETIC_DEFAULT_MODEL_REF, + XAI_DEFAULT_MODEL_REF, setMinimaxApiKey, writeOAuthCredentials, } from "./onboard-auth.js"; @@ -389,6 +392,65 @@ describe("applyXiaomiConfig", () => { }); }); +describe("applyXaiConfig", () => { + it("adds xAI provider with correct settings", () => { + const cfg = applyXaiConfig({}); + expect(cfg.models?.providers?.xai).toMatchObject({ + baseUrl: "https://api.x.ai/v1", + api: "openai-completions", + }); + expect(cfg.agents?.defaults?.model?.primary).toBe(XAI_DEFAULT_MODEL_REF); + }); + + it("preserves existing model fallbacks", () => { + const cfg = applyXaiConfig({ + agents: { + defaults: { + model: { fallbacks: ["anthropic/claude-opus-4-5"] }, + }, + }, + }); + expect(cfg.agents?.defaults?.model?.fallbacks).toEqual(["anthropic/claude-opus-4-5"]); + }); +}); + +describe("applyXaiProviderConfig", () => { + it("adds model alias", () => { + const cfg = applyXaiProviderConfig({}); + expect(cfg.agents?.defaults?.models?.[XAI_DEFAULT_MODEL_REF]?.alias).toBe("Grok"); + }); + + it("merges xAI models and keeps existing provider overrides", () => { + const cfg = applyXaiProviderConfig({ + models: { + providers: { + xai: { + baseUrl: "https://old.example.com", + apiKey: "old-key", + api: "anthropic-messages", + models: [ + { + id: "custom-model", + name: "Custom", + reasoning: false, + input: ["text"], + cost: { input: 1, output: 2, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1000, + maxTokens: 100, + }, + ], + }, + }, + }, + }); + + expect(cfg.models?.providers?.xai?.baseUrl).toBe("https://api.x.ai/v1"); + expect(cfg.models?.providers?.xai?.api).toBe("openai-completions"); + expect(cfg.models?.providers?.xai?.apiKey).toBe("old-key"); + expect(cfg.models?.providers?.xai?.models.map((m) => m.id)).toEqual(["custom-model", "grok-4"]); + }); +}); + describe("applyOpencodeZenProviderConfig", () => { it("adds allowlist entry for the default model", () => { const cfg = applyOpencodeZenProviderConfig({}); diff --git a/src/commands/onboard-non-interactive.xai.test.ts b/src/commands/onboard-non-interactive.xai.test.ts index bb34fb064..1c4d2dda7 100644 --- a/src/commands/onboard-non-interactive.xai.test.ts +++ b/src/commands/onboard-non-interactive.xai.test.ts @@ -65,7 +65,7 @@ describe("onboard (non-interactive): xAI", () => { expect(cfg.auth?.profiles?.["xai:default"]?.provider).toBe("xai"); expect(cfg.auth?.profiles?.["xai:default"]?.mode).toBe("api_key"); - expect(cfg.agents?.defaults?.model?.primary).toBe("xai/grok-2-latest"); + expect(cfg.agents?.defaults?.model?.primary).toBe("xai/grok-4"); const { ensureAuthProfileStore } = await import("../agents/auth-profiles.js"); const store = ensureAuthProfileStore(); diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index e1cd61ab1..adc20f1c3 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -233,7 +233,7 @@ export async function applyNonInteractiveAuthChoice(params: { return null; } if (resolved.source !== "profile") { - await setXaiApiKey(resolved.key); + setXaiApiKey(resolved.key); } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "xai:default",