Bridges Grok CLI tool execution events (tool_use start/result) from the NDJSON stream into the runtime adapter's onToolStart/onToolEnd callbacks, alongside existing text bridging. - GrokRuntimeAdapter.promptWithFallback now parses and bridges tool_use NDJSON events into onToolStart/onToolEnd callbacks - Tool name/args/result pass through unchanged (no Grok→pi tool-name mapping, since the verified contract doesn't pin a vocabulary) - step_finish/error remain non-terminal per-step events, not bridged to any callback; only subprocess close/error finalizes (unchanged from FN-7722) - Extended stream-parser.ts to recognize tool_use event shapes - Added new types for tool event payloads in types.ts - Updated docs/grok-cli-contract.md and plugin README to document tool event bridging - Added changeset for @runfusion/fusion (minor) - Added/extended tests in runtime-adapter.test.ts and stream-parser.test.ts (fixture-based, no live binary) Files changed: .changeset/fn-7724-grok-cli-tool-bridging.md | 7 ++ docs/grok-cli-contract.md | 16 ++- plugins/fusion-plugin-grok-runtime/README.md | 12 +++ .../src/__tests__/runtime-adapter.test.ts | 120 +++++++++++++++++++++ .../src/__tests__/stream-parser.test.ts | 33 ++++++ .../src/runtime-adapter.ts | 83 +++++++++++--- .../src/stream-parser.ts | 10 ++ plugins/fusion-plugin-grok-runtime/src/types.ts | 23 ++++ 8 files changed, 287 insertions(+), 17 deletions(-) Fusion-Task-Id: FN-7724 Fusion-Task-Lineage: 73abbf2a-6dcd-44fb-86be-71d4788c92d2 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
65 lines
2.3 KiB
TypeScript
65 lines
2.3 KiB
TypeScript
import type { GrokNdjsonEvent } from "./types.js";
|
|
|
|
/*
|
|
FNXC:GrokCli 2026-07-09-00:00:
|
|
FN-7722: `grok --prompt <text> --format json` emits newline-delimited JSON
|
|
(one JSON object per line) per the verified upstream contract captured in
|
|
docs/grok-cli-contract.md (source: src/headless/output.ts's
|
|
`createHeadlessJsonlEmitter` / `HeadlessJsonEvent`). This parser mirrors the
|
|
Droid plugin's `stream-parser.ts` shape and resilience contract: it never
|
|
throws. Debug noise, empty lines, and malformed/unrecognized JSON all return
|
|
null so the streaming pipeline can safely skip them and continue.
|
|
*/
|
|
|
|
/*
|
|
FNXC:GrokCli 2026-07-09-00:10:
|
|
FN-7724: confirmed at execution time — FN-7722 already typed `tool_use` /
|
|
`step_finish` / `error` into the `GrokNdjsonEvent` union (types.ts) and this
|
|
parser already accepts them via KNOWN_EVENT_TYPES below, so no parser change
|
|
was needed to "surface" them; only runtime-adapter.ts's bridge (previously
|
|
intentionally dropping tool_use/step_finish/error, see FN-7722 comment
|
|
above) needed extending. See docs/grok-cli-contract.md for the verified
|
|
schema this parser accepts unmodified.
|
|
*/
|
|
const KNOWN_EVENT_TYPES = new Set(["step_start", "text", "tool_use", "step_finish", "error"]);
|
|
|
|
/**
|
|
* Parse a single NDJSON line from `grok --prompt --format json` stdout into a
|
|
* typed event, or null when the line should be skipped (empty, non-JSON
|
|
* debug noise, malformed JSON, or a JSON object whose `type` isn't one of
|
|
* the five verified event types).
|
|
*/
|
|
export function parseLine(line: string): GrokNdjsonEvent | null {
|
|
const trimmed = line.trim();
|
|
|
|
// Skip empty lines
|
|
if (!trimmed) {
|
|
return null;
|
|
}
|
|
|
|
// Skip non-JSON lines (e.g. any stray debug/log output not part of the JSONL stream)
|
|
if (!trimmed.startsWith("{")) {
|
|
return null;
|
|
}
|
|
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = JSON.parse(trimmed);
|
|
} catch {
|
|
console.error("Failed to parse Grok CLI NDJSON line:", trimmed);
|
|
return null;
|
|
}
|
|
|
|
// Validate that the parsed result is a non-null object (not array, not primitive)
|
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
return null;
|
|
}
|
|
|
|
const candidate = parsed as { type?: unknown };
|
|
if (typeof candidate.type !== "string" || !KNOWN_EVENT_TYPES.has(candidate.type)) {
|
|
return null;
|
|
}
|
|
|
|
return parsed as GrokNdjsonEvent;
|
|
}
|