From 444e33ea60e76096e27bf64d00caa558ca24f6c8 Mon Sep 17 00:00:00 2001 From: Joey-nexu Date: Tue, 19 May 2026 14:55:28 +0800 Subject: [PATCH] feat(daemon): add Grok Build (xAI's first-party CLI) as an agent runtime (#2200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(daemon): add Grok Build (xAI's first-party CLI) as agent runtime xAI shipped Grok Build (https://x.ai/cli) on 2026-05-19 โ€” a first- party CLI agent that boxes their SuperGrok subscription into a Claude-Code-shaped headless mode (`grok -p `, optional `--output-format streaming-json`, `--max-turns`, native subagent / plan / mcp subcommands). The OAuth flow it ships uses the same `auth.x.ai` issuer + `b1a00492-073a-47ea-816f-4c329264a828` client_id + `grok-cli:access api:access` scope that the existing OD xAI Settings panel already speaks, so once a user runs the install script and `grok login --oauth`, Grok Build is fully authenticated without OD doing anything. This runtime def lets OD detect a `grok` binary on PATH and surface it in the agent picker the same way Hermes / Claude Code / Codex are surfaced today. Headless invocations get: - `grok -p` (prompt via stdin to avoid Windows ENAMETOOLONG / Linux E2BIG on large composed prompts), with `--model` / `--effort` pass-through when the user picks one in the picker. - `grok models` for live model discovery (parsed line-by-line, matching the existing `parseLineSeparatedModels` helper opencode / cursor-agent already use). Falls back to a static list of grok-build / grok-4.3 / grok-4.20-{reasoning,non-reasoning, multi-agent} when probing fails (no Heavy entitlement, network blip, etc.). - `--effort` exposed as the standard reasoning-options dropdown. streamFormat is intentionally `plain` for this first cut. Grok Build's `streaming-json` output is a real NDJSON event stream, but the schema is xAI-specific and OD does not yet have a parser for it โ€” shipping with `plain` now means the user can chat single-turn, and a follow-up `grok-stream-json` adapter can light up tool_use / plan-mode streaming once we've validated the shape against a live Heavy account. No test added for the def itself; the existing `tests/runtimes/registry-and-args.test.ts` and `agent-args.test.ts` iterate over `AGENT_DEFS` and now cover this entry by construction. All 126 runtime tests pass; daemon tsc + tsconfig.tests.json clean; pnpm guard clean. * feat(web): add grok-build agent icon Pairs with the new daemon runtime def in 35418fe so the Settings โ†’ Agent Runtime row stops falling back to the "G" initial. The SVG is a hand-drawn geometric "X" silhouette (Unicode ๐• / U+1D54F shape), painted via AgentIcon's mono mask path so it picks up the active theme color the way hermes / mimo / pi / kilo / cursor-agent already do. xAI does not publish a downloadable brand SVG (favicon paths on x.ai 404), so this is intentionally a placeholder rather than a copy of any licensed mark โ€” happy to swap for an official asset if maintainers have one to hand. Wires the id into both `ICON_EXT` and the `MONO_ICONS` set in AgentIcon.tsx. No other behaviour changes. --- apps/daemon/src/runtimes/defs/grok-build.ts | 76 +++++++++++++++++++++ apps/daemon/src/runtimes/registry.ts | 2 + apps/web/public/agent-icons/grok-build.svg | 15 ++++ apps/web/src/components/AgentIcon.tsx | 2 + 4 files changed, 95 insertions(+) create mode 100644 apps/daemon/src/runtimes/defs/grok-build.ts create mode 100644 apps/web/public/agent-icons/grok-build.svg diff --git a/apps/daemon/src/runtimes/defs/grok-build.ts b/apps/daemon/src/runtimes/defs/grok-build.ts new file mode 100644 index 0000000000..404cbf7f90 --- /dev/null +++ b/apps/daemon/src/runtimes/defs/grok-build.ts @@ -0,0 +1,76 @@ +import { parseLineSeparatedModels, DEFAULT_MODEL_OPTION } from './shared.js'; +import type { RuntimeAgentDef } from '../types.js'; + +// xAI's first-party CLI agent โ€” https://x.ai/cli โ€” distributed as the +// `grok` binary. Installed via `curl -fsSL https://x.ai/cli/install.sh | bash`, +// which symlinks `~/.grok/bin/grok` into PATH. +// +// `grok` ships its own SuperGrok OAuth dance (same `auth.x.ai` issuer + +// loopback-redirect shape Open Design's xAI Settings panel uses), so it's +// already authenticated by the time OD detects the binary; OD does not +// need to inject credentials. Users authenticate once with `grok login +// --oauth` and the resulting `~/.grok/auth.json` is what every spawned +// invocation reads. +// +// Headless mode follows Claude Code's pattern (`-p ` for single- +// turn, `--output-format streaming-json` for structured streaming), but +// the streaming-json schema is xAI-specific and we do not yet have a +// daemon-side parser for it. To ship the runtime now and let users at +// least chat with grok inside OD, this defaults to `plain` streamFormat +// (single-turn text reply, no tool_use streaming). Upgrading to a +// `grok-stream-json` event parser is follow-up work once the format is +// stable enough to lock in. +export const grokBuildAgentDef = { + id: 'grok-build', + name: 'Grok Build', + bin: 'grok', + versionArgs: ['--version'], + helpArgs: ['-p', '--help'], + // `grok models` prints one model id per line, plus a `Default model:` + // header line that parseLineSeparatedModels strips because it isn't + // an id token. Falls back to the static list below when probing fails + // (no SuperGrok Heavy entitlement on this machine, network blip, etc.). + listModels: { + args: ['models'], + timeoutMs: 10_000, + parse: parseLineSeparatedModels, + }, + fallbackModels: [ + DEFAULT_MODEL_OPTION, + { id: 'grok-build', label: 'grok-build (xAI ยท default)' }, + { id: 'grok-4.3', label: 'grok-4.3 (xAI)' }, + { id: 'grok-4.20-reasoning', label: 'grok-4.20-reasoning (xAI ยท deep)' }, + { + id: 'grok-4.20-non-reasoning', + label: 'grok-4.20-non-reasoning (xAI ยท fast)', + }, + { + id: 'grok-4.20-multi-agent', + label: 'grok-4.20-multi-agent (xAI ยท orchestration)', + }, + ], + // Prompt delivered via stdin so Windows `spawn ENAMETOOLONG` and Linux + // `spawn E2BIG` can't truncate large composed prompts. `grok -p` with + // no positional argument reads from piped stdin. + buildArgs: (_prompt, _imagePaths, _extra = [], options = {}) => { + const args = ['-p']; + if (options.model && options.model !== DEFAULT_MODEL_OPTION.id) { + args.push('--model', options.model); + } + if (options.reasoning) { + args.push('--effort', options.reasoning); + } + return args; + }, + reasoningOptions: [ + { id: 'low', label: 'low' }, + { id: 'medium', label: 'medium' }, + { id: 'high', label: 'high' }, + { id: 'xhigh', label: 'xhigh' }, + { id: 'max', label: 'max' }, + ], + promptViaStdin: true, + streamFormat: 'plain', + installUrl: 'https://x.ai/cli', + docsUrl: 'https://x.ai/cli', +} satisfies RuntimeAgentDef; diff --git a/apps/daemon/src/runtimes/registry.ts b/apps/daemon/src/runtimes/registry.ts index 200e80e3cb..72483c3fb8 100644 --- a/apps/daemon/src/runtimes/registry.ts +++ b/apps/daemon/src/runtimes/registry.ts @@ -4,6 +4,7 @@ import { devinAgentDef } from './defs/devin.js'; import { geminiAgentDef } from './defs/gemini.js'; import { opencodeAgentDef } from './defs/opencode.js'; import { hermesAgentDef } from './defs/hermes.js'; +import { grokBuildAgentDef } from './defs/grok-build.js'; import { kimiAgentDef } from './defs/kimi.js'; import { cursorAgentDef } from './defs/cursor-agent.js'; import { qwenAgentDef } from './defs/qwen.js'; @@ -24,6 +25,7 @@ const BASE_AGENT_DEFS: RuntimeAgentDef[] = [ geminiAgentDef, opencodeAgentDef, hermesAgentDef, + grokBuildAgentDef, kimiAgentDef, cursorAgentDef, qwenAgentDef, diff --git a/apps/web/public/agent-icons/grok-build.svg b/apps/web/public/agent-icons/grok-build.svg new file mode 100644 index 0000000000..f26b8d81bc --- /dev/null +++ b/apps/web/public/agent-icons/grok-build.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/apps/web/src/components/AgentIcon.tsx b/apps/web/src/components/AgentIcon.tsx index bdc1f9c05e..b2e8328df6 100644 --- a/apps/web/src/components/AgentIcon.tsx +++ b/apps/web/src/components/AgentIcon.tsx @@ -23,6 +23,7 @@ const ICON_EXT: Record = { deepseek: 'svg', mimo: 'svg', hermes: 'svg', + 'grok-build': 'svg', kimi: 'svg', pi: 'svg', kiro: 'svg', @@ -46,6 +47,7 @@ const MONO_ICONS = new Set([ 'mimo', 'pi', 'kilo', + 'grok-build', ]); export function AgentIcon({ id, size = 36, className }: Props) {