返回 DeepSeek-Reasonix
incomplete_read_truncate.go
根目录 / internal / agent / incomplete_read_truncate.go
1 package agent
2
3 import (
4 "fmt"
5 "strings"
6 "unicode/utf8"
7
8 "reasonix/internal/i18n"
9 )
10
11 // truncateReadFileOutput returns a contiguous prefix on a complete rendered
12 // line when possible. Its recovery cursor is exactly the first unseen byte, so
13 // paging can reconstruct the original without a gap or duplicate.
14 func truncateReadFileOutput(s, toolName, toolCallID string) (string, string) {
15 resultRef := toolResultRef(toolCallID, s)
16 headKeep := maxToolOutputBytes - 1024
17 if headKeep < 1024 {
18 headKeep = maxToolOutputBytes / 2
19 }
20 head := snapToRuneBoundary(s, 0, min(headKeep, len(s)))
21 if newline := strings.LastIndexByte(head, '\n'); newline >= 1024 {
22 head = head[:newline+1]
23 }
24 for range 4 {
25 marker := toolOutputRecoveryMarkerAt(toolName, toolCallID, resultRef, len(s), len(head), len(head))
26 if len(head)+len(marker) <= maxToolOutputBytes {
27 notice := fmt.Sprintf(i18n.M.ToolOutputTruncatedFmt, len(s)-len(head), len(s))
28 return head + marker, notice
29 }
30 trimTo := len(head) - (len(head) + len(marker) - maxToolOutputBytes)
31 if trimTo <= 0 {
32 head = ""
33 continue
34 }
35 head = snapToRuneBoundary(head, 0, trimTo)
36 if newline := strings.LastIndexByte(head, '\n'); newline >= 0 {
37 head = head[:newline+1]
38 }
39 }
40 marker := toolOutputRecoveryMarkerAt(toolName, toolCallID, resultRef, len(s), len(head), len(head))
41 if len(marker) > maxToolOutputBytes {
42 marker = snapToRuneBoundary(marker, 0, maxToolOutputBytes)
43 }
44 notice := fmt.Sprintf(i18n.M.ToolOutputTruncatedFmt, len(s)-len(head), len(s))
45 return head + marker, notice
46 }
47
48 func snapToRuneBoundary(s string, lo, hi int) string {
49 for lo > 0 && !utf8.RuneStart(s[lo]) {
50 lo--
51 }
52 for hi < len(s) && !utf8.RuneStart(s[hi]) {
53 hi++
54 }
55 return s[lo:hi]
56 }
57
57 lines GO