Files
codegirl-games/agentbox/internal/trace/reader.go
T

98 lines
2.0 KiB
Go

package trace
import (
"bufio"
"encoding/json"
"errors"
"os"
"path/filepath"
"sort"
)
func List(projectRoot string) ([]Run, error) {
runsDirectory := filepath.Join(projectRoot, ".agentbox", "runs")
directoryEntries, err := os.ReadDir(runsDirectory)
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
if err != nil {
return nil, err
}
var runs []Run
for _, entry := range directoryEntries {
if !entry.IsDir() {
continue
}
run, err := Read(projectRoot, entry.Name())
// Old or malformed trace directories are intentionally hidden rather
// than making the entire run listing fail.
if err == nil && run.SchemaVersion == SchemaVersion {
runs = append(runs, run)
}
}
sort.Slice(runs, func(left, right int) bool {
return runs[left].StartedAt.After(runs[right].StartedAt)
})
return runs, nil
}
func Read(projectRoot, runID string) (Run, error) {
if err := validateRunID(runID); err != nil {
return Run{}, err
}
runPath := filepath.Join(
projectRoot,
".agentbox",
"runs",
runID,
"run.json",
)
data, err := os.ReadFile(runPath)
if err != nil {
return Run{}, err
}
var run Run
if err := json.Unmarshal(data, &run); err != nil {
return Run{}, err
}
return run, nil
}
func ReadSteps(projectRoot, runID string) ([]StepRecord, error) {
if err := validateRunID(runID); err != nil {
return nil, err
}
stepsPath := filepath.Join(
projectRoot,
".agentbox",
"runs",
runID,
"steps.jsonl",
)
stepsFile, err := os.Open(stepsPath)
if err != nil {
return nil, err
}
defer stepsFile.Close()
var steps []StepRecord
scanner := bufio.NewScanner(stepsFile)
for scanner.Scan() {
var step StepRecord
if err := json.Unmarshal(scanner.Bytes(), &step); err != nil {
return nil, err
}
steps = append(steps, step)
}
return steps, scanner.Err()
}
func validateRunID(runID string) error {
// Run IDs become path components, so reject separators and traversal.
if filepath.Base(runID) != runID {
return errors.New("invalid run ID")
}
return nil
}