fix(cli): match Python trace limits with O(n) text checks
Count Unicode characters so valid Python traces stay visible. Decoding stays O(n), with character checks adding 1-3% in the same microbenchmarks.
4 unmodified lines567891011121346 unmodified lines6061626363646566676869706971727374757476777879788081828384152 unmodified linesExpand all4 unmodified lines "errors" "fmt" "strings" "unicode" "unicode/utf8")
// ToolActivity records display-only execution evidence, not model-facing tool output.type ToolActivity struct {46 unmodified lines case "read", "search", "list", "run": default: return nil } if len(act.Target) > 1000 { // Python bounds these strings by Unicode characters, not UTF-8 bytes. if utf8.RuneCountInString(act.Target) > 1000 { return nil } }
for _, ch := range trace.Changes { if len(ch.Path) > 1000 { if utf8.RuneCountInString(ch.Path) > 1000 { return nil } switch ch.Kind { case "unavailable": if len(ch.Reason) > 1000 { if utf8.RuneCountInString(ch.Reason) > 1000 { return nil } case "diff": if len(ch.Diff) > 16000 || ch.Added < 0 || ch.Removed < 0 { if utf8.RuneCountInString(ch.Diff) > 16000 || ch.Added < 0 || ch.Removed < 0 { return nil } default: return nil152 unmodified linesExpand all361 unmodified linesExpand all3623633643653663673683693703713723733743753763773783795 unmodified lines38538638738838939039139239326 unmodified lines420421422423413424425426427428429430431432361 unmodified linesExpand all }}
func TestStreamLoadsHistoryDespiteInvalidDisplayTraces(t *testing.T) { unicodeTrace, err := json.Marshal(ToolTrace{ Activities: []ToolActivity{{Kind: "read", Target: strings.Repeat("界", 1000)}}, Changes: []FileChange{ {Kind: "diff", Path: strings.Repeat("界", 1000), Diff: strings.Repeat("🙂", 16000)}, {Kind: "unavailable", Path: "binary", Reason: strings.Repeat("界", 1000)}, }, }) if err != nil { t.Fatal(err) } cases := []struct { name string trace string wantTrace bool5 unmodified lines {"unknown activity", `{"activities":[{"kind":"unknown","target":"file"}]}`, false}, {"oversized target", `{"activities":[{"kind":"read","target":"` + strings.Repeat("界", 1001) + `"}]}`, false}, {"oversized diff", `{"changes":[{"kind":"diff","path":"file","diff":"` + strings.Repeat("🙂", 16001) + `"}]}`, false}, {"negative line count", `{"changes":[{"kind":"diff","path":"file","added":-1}]}`, false}, {"unicode at limits", string(unicodeTrace), true}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { traceField := ""26 unmodified lines t.Fatalf("history did not load completely: consumed=%d events=%+v", consumed, events) } if got := events[1].ToolTrace; (got != nil) != tc.wantTrace { t.Fatalf("unexpected display trace: %+v", got)
} else if tc.wantTrace { gotJSON, err := json.Marshal(got) if err != nil || string(gotJSON) != string(unicodeTrace) { t.Fatalf("unicode trace changed: %s, %v", gotJSON, err) } } }) }}