package cli import ( "bufio" "context" "io" "strings" "time" "iop/apps/node/internal/runtime" ) // lineEmitter parses one stdout line (already trimmed of trailing newline) and // returns the resulting RuntimeEvents to push to the sink. Empty events are // skipped. Returning a non-nil error aborts the run. type lineEmitter interface { Name() string Emit(line string) ([]runtime.RuntimeEvent, error) } // registeredEmitter wraps a lineEmitter with its maximum scanner buffer size. type registeredEmitter struct { emitter lineEmitter scanBufMax int } // jsonEmitters maps OutputFormat names to their registered emitter + buffer config. var jsonEmitters = map[string]registeredEmitter{ "stream-json": {emitter: streamJSONEmitter{}, scanBufMax: 4 * 1024 * 1024}, "claude-json": {emitter: claudeJSONEmitter{}, scanBufMax: 8 * 1024 * 1024}, "codex-json": {emitter: codexJSONEmitter{}, scanBufMax: 4 * 1024 * 1024}, "opencode-json": {emitter: opencodeJSONEmitter{}, scanBufMax: 4 * 1024 * 1024}, "cline-json": {emitter: clineJSONEmitter{}, scanBufMax: 8 * 1024 * 1024}, } // driveJSONLines runs a shared scanner loop over stdout, accumulates raw output // into outBuf, dispatches each line through the emitter, and forwards events // to sink. Returns total OutputTokens approximation. func driveJSONLines( ctx context.Context, stdout io.Reader, sink runtime.EventSink, runID string, outBuf *strings.Builder, emitter lineEmitter, scanBufMax int, ) (int, error) { scanner := bufio.NewScanner(stdout) scanner.Buffer(make([]byte, 64*1024), scanBufMax) outputTokens := 0 for scanner.Scan() { line := scanner.Bytes() outBuf.Write(line) outBuf.WriteByte('\n') trimmed := strings.TrimSpace(string(line)) if trimmed == "" || trimmed[0] != '{' { continue } events, err := emitter.Emit(trimmed) if err != nil { return outputTokens, err } for _, ev := range events { ev.RunID = runID if ev.Timestamp.IsZero() { ev.Timestamp = time.Now() } if ev.Type == runtime.EventTypeDelta { outputTokens += len(strings.Fields(ev.Delta)) } _ = sink.Emit(ctx, ev) } } if err := scanner.Err(); err != nil { return outputTokens, err } return outputTokens, nil }