83 lines
2.2 KiB
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
|
|
}
|