@@ -18,6 +18,11 @@ const (
1818 // minBase64RunBytes:连续 base64 字符达到这个长度即判定为二进制 blob(截图 / 附件等),
1919 // 整段替换为占位符。正常文本 / 代码不会出现这么长且不含空白的连续串。
2020 minBase64RunBytes = 4096
21+ // maxToolOutputBytesPerTurn:单个 assistant turn 内,所有工具结果写入历史的「合计」字节上限。
22+ // 单条已被 clampToolOutput 限到 maxToolOutputBytes;但一轮可并发多个 tool call,合计无人约束 ——
23+ // K×96KB 仍能一轮把上下文顶爆(issue #135 的遗留缺口)。超预算后本轮后续结果整体替换为简短占位。
24+ // 256KB ≈ 容纳 2~3 条满载结果,够正常一轮多工具用,又远小于上下文窗口。
25+ maxToolOutputBytesPerTurn = 256 * 1024
2126)
2227
2328// clampToolOutput 把工具结果压到可安全入历史的大小:
@@ -34,11 +39,39 @@ func clampToolOutput(name, out string) string {
3439 b = b [:len (b )- 1 ]
3540 }
3641 return string (b ) + fmt .Sprintf (
37- "\n \n […%s 返回 %d 字节,已截断至 %d 字节,防止撑爆上下文(issue #135) 。" +
42+ "\n \n […%s 返回 %d 字节,已截断至 %d 字节,防止撑爆上下文。" +
3843 "请缩小范围重试:读文件用 offset/limit 分页、grep 收窄匹配、命令只取必要输出。]" ,
3944 name , len (out ), len (b ))
4045}
4146
47+ // clampTurnToolOutput 在 clampToolOutput(单条上限)之上再加一道「本轮合计上限」。
48+ // spent 指向本轮已计入历史的工具结果字节数,每个 assistant turn 开始时由调用方置 0。
49+ // 预算够:截到剩余预算内(UTF-8 边界)并累加 spent;预算用尽:整条替换为简短占位,
50+ // 提示模型减少单轮并发工具调用 / 分多轮获取,避免一轮 K 条结果合计撑爆上下文(issue #135)。
51+ func clampTurnToolOutput (name , out string , spent * int ) string {
52+ remaining := maxToolOutputBytesPerTurn - * spent
53+ if remaining <= 0 {
54+ return fmt .Sprintf (
55+ "[本轮工具结果合计已达 %dKB 上限,%s 的结果未计入上下文。" +
56+ "请减少单轮并发的工具调用、分多轮获取,或用更聚焦的查询缩小输出。]" ,
57+ maxToolOutputBytesPerTurn / 1024 , name )
58+ }
59+ if len (out ) <= remaining {
60+ * spent += len (out )
61+ return out
62+ }
63+ b := []byte (out )[:remaining ]
64+ // 回退到合法 UTF-8 边界,避免截出半个多字节字符(发给 API 会乱码 / 被拒)。
65+ for len (b ) > 0 && ! utf8 .Valid (b ) {
66+ b = b [:len (b )- 1 ]
67+ }
68+ * spent += len (b )
69+ return string (b ) + fmt .Sprintf (
70+ "\n \n […本轮工具结果合计接近 %dKB 上限,%s 的结果被进一步截断。" +
71+ "请分多轮获取或缩小范围重试。]" ,
72+ maxToolOutputBytesPerTurn / 1024 , name )
73+ }
74+
4275// stripBase64Blobs 把每一段足够长的连续 base64 字符串替换为简短占位符。
4376// 用途:real-browser-mcp 的 browser_screenshot 等工具会把截图编成单行 base64 直接塞进文本结果,
4477// 对非视觉模型纯属上下文垃圾。按字节扫描(base64 字符全是 ASCII,非 ASCII 字节天然断开,保证不破坏 UTF-8 文本)。
@@ -55,7 +88,7 @@ func stripBase64Blobs(s string) string {
5588 j ++
5689 }
5790 if j - i >= minBase64RunBytes {
58- fmt .Fprintf (& b , "[…%d 字节 base64 二进制数据已省略(截图 / 附件不入上下文,issue #135 )]" , j - i )
91+ fmt .Fprintf (& b , "[…%d 字节 base64 二进制数据已省略(截图 / 附件不入上下文)]" , j - i )
5992 i = j
6093 continue
6194 }
0 commit comments