Align the Grok CLI runtime plugin adapter with the real xAI grok CLI stream contract so tool responses are no longer silently dropped, and update tests/docs to match. - Rework stream-parser.ts to parse the actual grok CLI event/message shape instead of the previously assumed schema - Trim runtime-adapter.ts and types.ts down to the fields the real CLI contract emits, removing speculative/unsupported fields - Update cli-stream.ts to match the corrected event handling - Rewrite runtime-adapter, stream-parser, and cli-stream test suites to exercise the real CLI contract end-to-end - Update docs/grok-cli-contract.md and plugin README to document the verified contract - Add changeset for the grok-runtime plugin fix Files changed: .changeset/fn-7790-grok-cli-real-contract.md | 7 + docs/grok-cli-contract.md | 432 +++++++-------------- plugins/fusion-plugin-grok-runtime/README.md | 149 ++----- .../src/__tests__/cli-stream.test.ts | 22 +- .../src/__tests__/runtime-adapter.test.ts | 250 ++++-------- .../src/__tests__/stream-parser.test.ts | 108 ++---- .../fusion-plugin-grok-runtime/src/cli-stream.ts | 22 +- .../src/runtime-adapter.ts | 109 ++---- .../src/stream-parser.ts | 27 +- plugins/fusion-plugin-grok-runtime/src/types.ts | 104 +---- 10 files changed, 338 insertions(+), 892 deletions(-) Fusion-Task-Id: FN-7790 Fusion-Task-Lineage: 377e86c4-c005-46a5-9eb4-69e858c38b79 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
6.5 KiB
Grok CLI Contract (FN-7790)
Date: 2026-07-10
Ground truth
Fusion shells out to an operator-installed grok binary. The binary is not downloaded or bundled by Fusion, so the authoritative contract is the installed xAI CLI's own help/version output plus live execution on an authenticated machine.
External integration evidence:
- Canonical upstream: xAI official Grok CLI / Grok Build TUI, surfaced by the installed binary as
grok 0.2.93 (f00f96316d4b). - Docs/homepage: https://grok.com/, https://docs.x.ai/, and
grok --help/grok agent --helpfor exact flags. - Release/download: operator-installed; Fusion resolves
grokfrom PATH orgrokCliBinaryPathand does not bundle a release artifact. - Binary name:
grok. - Checksum:
upstream-pending-verificationbecause Fusion does not pin or download the operator's binary.
The previously documented https://github.com/superagent-ai/grok-cli contract is a different product that happens to use the same binary name. Its grok --prompt <text> --format json invocation is not accepted by xAI's CLI.
Failure that caused FN-7790
The old adapter invocation fails against the real xAI binary:
grok --prompt "say hello" --format json
Observed result:
exit 2
stdout: <empty>
stderr:
error: unexpected argument '--prompt' found
tip: a similar argument exists: '--prompt-file'
Usage: grok --prompt-file <PATH> [PROMPT]
Because no NDJSON text event is produced, Fusion surfaced a blank/no-message assistant response.
Confirmed non-interactive invocation
Use xAI Grok Build TUI's single-turn prompt mode with streaming JSON:
grok -p "<text>" --output-format streaming-json
# equivalent long prompt flag:
grok --single "<text>" --output-format streaming-json
Supported companion flags used by Fusion:
-p, --single <PROMPT>— run a single prompt, print the response, and exit. This does not require interactive stdin.--output-format <plain|json|streaming-json>— streaming adapter usesstreaming-json.-m, --model <MODEL>— optional concrete model id. Fusion omits this for the model-lessgrok/defaultRuntime-mode path.--cwd <CWD>— optional working directory. This replaces the wrong-product--directoryflag.
Other observed flags include --prompt-file <PATH>, --prompt-json <JSON>, -s/--session-id <UUID>, --sandbox <PROFILE>, --system-prompt-override <PROMPT>, and --max-turns <N>, but Fusion's adapter does not currently use them.
Streaming JSON event schema
--output-format streaming-json emits one JSON object per line:
type GrokStreamingJsonEvent =
| { type: "thought"; data: string }
| { type: "text"; data: string }
| { type: "end"; stopReason?: string; sessionId?: string; requestId?: string };
Mapping in Fusion:
thought.data→onThinking(thought.data).text.data→onText(text.data)and accumulated assistant content.end.sessionId→session.sessionIdwhen present.endpre-signals terminal output, but subprocesscloseremains the authoritative promise resolution point because it carries exit status/stderr diagnostics.
Real captured tail:
{"type":"thought","data":" one"}
{"type":"thought","data":"-"}
{"type":"thought","data":"word"}
{"type":"thought","data":" greeting"}
{"type":"thought","data":"."}
{"type":"text","data":"Hello"}
{"type":"text","data":"!"}
{"type":"end","stopReason":"EndTurn","sessionId":"019f4d1e-2582-70e0-a174-c8774782ab01","requestId":"2233f1dc-e9ad-4ae4-8221-caa6afade07f"}
A successful run exits 0 with empty stderr.
Non-streaming formats
--output-format plain prints renderable response text.
--output-format json emits one final JSON object rather than an NDJSON stream. Observed shape:
{
"text": "hi",
"stopReason": "EndTurn",
"sessionId": "019f4d18-875b-7662-9bc5-9b71fa0aa6b0",
"requestId": "0e8ef53f-5a5f-4564-a8fd-0200ef96440e",
"thought": "The user wants me to say hi in one word..."
}
Fusion uses streaming-json for live onText/onThinking callbacks.
Model discovery
grok models is plain text, not JSON. Observed shape:
You are logged in with grok.com.
Default model: grok-4.5
Available models:
* grok-4.5 (default)
- grok-composer-2.5-fast
Fusion parses the bullet list conservatively and exposes ids under provider grok-cli when the useGrokCli toggle is enabled.
Auth and readiness
The CLI owns authentication for CLI-routed execution. Fusion's readiness probe uses grok --version; a passing probe proves only that a compatible-looking binary exists, not that the prompt path is authenticated or serviceable. The prompt path is proven by a real grok -p ... --output-format streaming-json run.
Fusion-visible GROK_API_KEY remains relevant for the direct xAI OpenAI-compatible endpoint. For CLI-routed sessions, Fusion does not need to see a key as long as the operator-installed CLI is authenticated by its own supported mechanism.
Runtime routing
The Grok runtime adapter is reached when:
- an agent explicitly sets
runtimeConfig.runtimeHint === "grok"; or - the FN-7753/FN-7758 no-visible-key fallback derives the same runtime hint for a
grok-cli/*default/fallback provider selection and the bundled Grok Runtime plugin is registered.
The selected grok-cli/<id> or grok/<id> model is normalized to <id> and passed to the CLI as -m <id>. The explicit no-model Runtime-mode path keeps grok/default and omits -m.
Diagnostics and empty-output invariant
The adapter preserves the resolve-never-reject runtime contract while surfacing concrete diagnostics:
- spawn failure →
session.state.errorMessageand diagnosticonText. - non-zero subprocess close with no text → stderr/exit diagnostic.
- code-0 close with zero parsed NDJSON → wrong-binary/interactive-EOF diagnostic.
- parsed
endwith no accumulated assistant text → legitimate silent response, not a diagnostic. - text emitted before a noisy/non-zero close → keep the assistant text and avoid replacing it with an error.
This invariant prevents the original blank/no-message symptom while still allowing genuinely empty model turns.