Why: Old tool results were filling the window, compaction could split a tool group or commit a stale summary, and reminders arrived as extra user messages. Evidence: Compaction retries a conflict and opens the circuit after three failures; clearing keeps the last five tool results; a stale file write is rejected; RPC reports started, queued, or handled. Impact: Requests over the context window replace older tool results with a placeholder. Rewriting an existing file now requires a read in the same run. Subagent text over 32000 characters is capped. Refs: feature/context-engineering Co-authored-by: Cursor <cursoragent@cursor.com>
71 lines
2.0 KiB
Go
71 lines
2.0 KiB
Go
package session
|
||
|
||
import "agent-native/runtime/llm"
|
||
|
||
const (
|
||
// DefaultKeepToolResults 是运行内清理时保留的最近工具结果条数。
|
||
DefaultKeepToolResults = 5
|
||
// DefaultContextWindow 是未配置模型窗口时的保守字符数。
|
||
DefaultContextWindow = 32000
|
||
// DefaultOutputReserve 是留给模型输出的字符预留。
|
||
DefaultOutputReserve = 4096
|
||
// DefaultContextBuffer 是窗口估算的余量,避免贴着上限才清理。
|
||
DefaultContextBuffer = 1024
|
||
)
|
||
|
||
// EstimateChars 用字符数近似这次请求的体积。
|
||
func EstimateChars(msgs []llm.Message) int {
|
||
n := 0
|
||
for _, m := range msgs {
|
||
n += len([]rune(m.Content))
|
||
for _, c := range m.ToolCalls {
|
||
n += len([]rune(c.Name)) + len([]rune(c.Arguments))
|
||
}
|
||
}
|
||
return n
|
||
}
|
||
|
||
// NeedsClear 在估算超过「窗口 − 输出预留 − 缓冲」时返回真。
|
||
// window ≤ 0 时用保守默认值。
|
||
func NeedsClear(estimate, window, outputReserve, buffer int) bool {
|
||
if window <= 0 {
|
||
window = DefaultContextWindow
|
||
}
|
||
room := window - outputReserve - buffer
|
||
if room < 1 {
|
||
room = 1
|
||
}
|
||
return estimate > room
|
||
}
|
||
|
||
// ClearOldToolResults 把最近 keep 条之前的工具结果换成占位。
|
||
// 消息条数、顺序和 tool_call 配对都不变;占位写明完整结果仍在会话记录里。
|
||
// keep ≤ 0 时保留默认的 5 条。返回的切片不与输入共享被改写的消息。
|
||
func ClearOldToolResults(msgs []llm.Message, keep int) []llm.Message {
|
||
if keep <= 0 {
|
||
keep = DefaultKeepToolResults
|
||
}
|
||
out := append([]llm.Message{}, msgs...)
|
||
var toolIdx []int
|
||
for i, m := range out {
|
||
if m.Role == llm.RoleTool {
|
||
toolIdx = append(toolIdx, i)
|
||
}
|
||
}
|
||
if len(toolIdx) <= keep {
|
||
return out
|
||
}
|
||
for _, i := range toolIdx[:len(toolIdx)-keep] {
|
||
name := out[i].Name
|
||
if name == "" {
|
||
name = "工具"
|
||
}
|
||
loc := "会话记录"
|
||
if out[i].ToolCallID != "" {
|
||
loc = "会话记录中的调用 " + out[i].ToolCallID
|
||
}
|
||
out[i].Content = "旧工具结果已清理。完整内容见" + loc + "(" + name + ")。"
|
||
}
|
||
return out
|
||
}
|