跪拜 Guibai
← All articles
Frontend Framework · Frontend · Design Patterns

A Coding Agent Gets a Scriptable JSONL Interface and a Real CLI Binary

By 东方小月 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A CLI that only prints final text is a dead end for automation. JSONL output with per-line versioning gives scripts, CI pipelines, and monitoring tools a stable machine-readable contract they can consume incrementally, without fragile screen-scraping or waiting for completion.

Summary

The di-code coding agent now speaks JSON Lines. A new `runJsonMode` function subscribes to every `AgentEvent`—start, turn, message update, end—and writes each as a versioned JSON record to stdout. Scripts can parse the stream line by line without waiting for the agent to finish, and every record carries a protocol version so consumers stay resilient to future schema changes. Structured failures keep their event history on stdout while routing diagnostics to stderr, and a `finally` block guarantees the listener is always cleaned up.

Behind the scenes, `runMain` now dispatches to either print or JSON mode from a single Agent instance, avoiding duplicated CLI parsing. A thin `entry.ts` file wires `process.argv`, real stdout/stderr, and the package version into the application, and the package.json `bin` field points to the built `dist/entry.js` so `di-code` works as a global command.

Subprocess tests spawn the actual binary to verify help text, version output, deterministic print responses, versioned JSON event streams, and proper separation of usage errors onto stderr. The whole pipeline—argument parsing, mode selection, event streaming, and process exit codes—now runs deterministically without touching the network, using a faux provider that returns fixed responses.

Takeaways
JSONL mode writes one versioned JSON record per AgentEvent to stdout, letting consumers parse the stream line by line as events occur.
Every record carries a `version` field so downstream tools can handle future schema changes without breaking.
Structured failures (error/aborted stop reasons) preserve already-emitted JSONL on stdout and write diagnostics only to stderr, returning exit code 1.
An unsubscribe call in a `finally` block prevents listener leaks across success, failure, and rejection paths.
`runMain` creates one Agent instance and dispatches to either print or JSON mode, keeping CLI parsing logic in a single place.
A thin `entry.ts` connects `process.argv`, real stdout/stderr, and package version to the application; the `bin` field points to the built `dist/entry.js`.
Subprocess tests using `spawn()` verify the full process boundary: help text, version, print output, JSON event streams, and stderr error routing.
Conclusions

Putting a protocol version on every JSONL line, not just the header, is a defensive design choice that pays off when logs are truncated, sharded, or tailed from the middle—a lesson applicable to any streaming CLI contract.

Keeping the entry point thin and deferring all logic to testable functions means the entire application can be verified without spawning processes, while still having integration tests that catch real-world wiring bugs.

Using a faux provider that returns fixed responses turns the CLI into a deterministic test harness; the entire event pipeline can be validated without network calls, API keys, or model variability.

Concepts & terms
JSONL (JSON Lines)
A text format where each line is a complete, independent JSON object, separated by newlines. Unlike a single JSON array, consumers can parse one line at a time without loading the entire document into memory.
Faux Provider
A mock AI provider that returns fixed, deterministic responses without making network calls or reading API keys. Used during development to test agent logic and CLI pipelines in isolation from real models.
AgentEvent
A typed event emitted during an agent's lifecycle, such as agent_start, turn_start, message_update, and agent_end. Subscribing to these events lets external code observe the agent's internal progress.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗