iop/apps/node/internal/adapters/cli/emitters.go

83 lines
2.2 KiB
Go

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
}