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 <prompt>`, 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.
This commit is contained in:
Joey-nexu
2026-05-19 14:15:08 +08:00
parent 4f116d9eaf
commit 35418feed2
2 changed files with 78 additions and 0 deletions
@@ -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 <PROMPT>` 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;
+2
View File
@@ -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,