clijson

package
v0.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package clijson frames a machine-readable JSON payload so it survives a transport that also carries human-readable diagnostics.

Why this exists

The Web UI runs `cloop <subcommand> --json` through pkg/executor and parses the result. Every driver hands back one merged stream: localprocess tags its output executor.StreamCombined, and a container or remote agent reads a single log pipe it cannot split either. So stdout and stderr arrive interleaved in one buffer, and "the whole buffer is the JSON document" is a contract the sub-binary cannot actually keep — anything that writes a diagnostic breaks the feature, however unrelated it is.

That is not hypothetical. It has broken the suggest panel twice: once when the command printed a coloured banner before the payload (Task 20133), and again when a schema-divergence warning from statedb landed on stderr during startup, so the UI reported

could not parse suggestions: invalid character 'w' looking for beginning of value

where the 'w' was the first letter of "warning:" (Task 20325). Silencing each writer in turn treats the symptom: the diagnostics are legitimate, and the next one to be added reopens the bug. Framing the payload fixes the class — the reader can find the document no matter what shares the stream.

The frame

Emit brackets the JSON with sentinels on their own lines:

<<<cloop-json:begin>>>
{"summary":"...","suggestions":[...]}
<<<cloop-json:end>>>

Extract recovers the payload from between them and ignores everything else. The markers are deliberately not valid JSON and not plausible prose, so a diagnostic cannot be mistaken for a frame.

Index

Constants

View Source
const (
	BeginMarker = "<<<cloop-json:begin>>>"
	EndMarker   = "<<<cloop-json:end>>>"
)

Frame markers. Each occupies its own line so a reader scanning text can see them, and neither is valid JSON so neither can be confused with a payload.

Changing these is a wire-format break between a hub and any differently versioned cloop binary it invokes — see Extract, which falls back to the unframed heuristic so the break degrades instead of failing.

View Source
const ChunkMarker = "<<<cloop-json:chunk>>>"

ChunkMarker opens a line carrying one piece of a result the command streams while it still runs (Task 20442): the plan assistant's answer, token by token, from a model call made on the project's executor. The frame above carries the finished result; a chunk only lets the reader show it sooner.

A chunk is one line, so the reader can relay it the moment the line is complete. Its payload is compact JSON, which escapes the newlines of the text it carries — and, encoding/json escaping '<' and '>', can never contain the marker itself.

Variables

View Source
var ErrNoPayload = errors.New("clijson: no JSON payload in output")

ErrNoPayload reports that buf held no JSON document: neither a frame nor anything the unframed fallback could recognise. Callers use errors.Is to tell "the command produced no result" from "the result would not parse".

Functions

func ChunkPayload added in v0.0.7

func ChunkPayload(line []byte) ([]byte, bool)

ChunkPayload returns the JSON a line written by EmitChunk carries, and whether line is one. The marker may follow a diagnostic that left its line unfinished, so it is looked for anywhere in the line — the last occurrence, since the payload cannot contain it and a diagnostic quoting it can only come first.

func Emit

func Emit(w io.Writer, v any) error

Emit writes v to w as a framed JSON document.

The payload is marshalled fully before anything is written, then handed to w in a single Write. That matters because stdout and stderr are usually the same pipe by the time this runs: a multi-call encoder can have a concurrent diagnostic interleaved between its writes, landing inside the document. One write cannot be split that way for payloads up to PIPE_BUF, and is materially harder to split above it.

func EmitChunk added in v0.0.7

func EmitChunk(w io.Writer, v any) error

EmitChunk writes v to w as one streamed chunk, in a single Write for the reason Emit uses one.

func Extract

func Extract(buf []byte) ([]byte, error)

Extract recovers the JSON document from buf, which may also contain diagnostics written to a stream merged with the payload's.

A framed payload is preferred. When no frame is present Extract falls back to the pre-framing heuristic — the span from the first '{' to the last '}' — so a hub still reads a result from an older binary that predates Emit. The fallback is the fragile behaviour this package exists to replace, and is only ever reached when the frame is genuinely absent.

On failure the error quotes the leading output, because the operator's next question is always "then what did it print instead?" and the raw decoder error ("invalid character 'w'") does not answer it.

func Unmarshal

func Unmarshal(buf []byte, v any) error

Unmarshal extracts the payload from buf and decodes it into v.

The decode error is wrapped with the leading output for the same reason Extract's is: a bare json error names a byte offset into a buffer the operator cannot see.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL