MCPcopy Create free account
hub / github.com/codehamr/codehamr / Pack

Function Pack

internal/ctx/ctx.go:140–188  ·  view source on GitHub ↗

Pack keeps whole messages newest-first until the budget is full, then returns them chronologically. The newest message is always kept, even if it alone exceeds the budget. Two clean-up passes then keep the wire well-formed: dropDanglingToolCalls drops an assistant whose tool_calls weren't all answer

(history []Message, budget int)

Source from the content-addressed store, hash-verified

138// Pack keeps whole messages newest-first until the budget is full, then
139// returns them chronologically. The newest message is always kept, even if it
140// alone exceeds the budget. Two clean-up passes then keep the wire well-formed:
141// dropDanglingToolCalls drops an assistant whose tool_calls weren't all
142// answered (the cancel-mid-tool case), and dropOrphanTools drops tool messages
143// whose assistant.tool_calls ancestor got trimmed off the top. Both directions
144// 400 every OpenAI-compatible backend, so both are stripped before the wire.
145// A final anchorUserMessage pass guarantees the window is never userless: the
146// third shape that 400s every backend, and the one a long single turn reaches
147// when the budget walk evicts the sole user task. demoteSystemMessages then runs
148// last, rewriting any surviving system note to a user message: the wire is always
149// prefixed by the embedded system prompt, so a fourth shape (a second, non-leading
150// system message) 400s strict backends, and that note is only ever a soft-nudge.
151func Pack(history []Message, budget int) PackResult {
152 kept := make([]Message, 0, len(history))
153 used := 0
154 // walk newest to oldest
155 for i := len(history) - 1; i >= 0; i-- {
156 cost := history[i].Tokens()
157 if len(kept) > 0 && used+cost > budget {
158 break
159 }
160 kept = append(kept, history[i])
161 used += cost
162 }
163 slices.Reverse(kept)
164 // Dangling assistant first: dropping it can orphan its partial tool results,
165 // which the following dropOrphanTools pass then cleans up.
166 kept = dropDanglingToolCalls(kept)
167 kept = dropOrphanTools(kept)
168 // The cleanup passes can lose the current turn's tool exchange when the
169 // newest tool result's owning assistant fell just past the budget cut: the
170 // budget walk keeps the lone tool result (plus any trailing system nudge),
171 // then the orphan drop removes it, so the next request would silently lose
172 // the whole conversation mid-turn (reachable on small-ctx profiles after a
173 // big tool output). Keyed on "nothing substantive survived", NOT on
174 // len(kept)==0: a failure/runaway nudge (or the empty assistant reply the
175 // empty-reply nudge answers) survives the cleanup as the sole keeper and
176 // would otherwise mask exactly this loss - and nothing substantive
177 // surviving already implies the newest tool result didn't. A surviving
178 // user message or real assistant reply instead means the conversation
179 // moved past the exchange, ordinary budget trimming, no over-budget
180 // resurrection. Recover the newest assistant+tool-results group whole,
181 // over budget if need be, with the same deliberately-over-budget
182 // guarantee a newest user message already gets.
183 if i := newestToolIndex(history); i >= 0 && onlyNonSubstantive(kept) {
184 // Recover the group over budget, then re-run the same two passes the
185 // normal path uses: a partially-answered parallel set (owner issued c1,c2
186 // but only c1 came back before an abort) would otherwise reach the wire as
187 // a dangling assistant and 400 every backend. Fully-answered groups pass
188 // through untouched; an unpairable partial empties to nothing. Survivors
189 // in kept are all newer than the recovered group (the budget walk keeps a
190 // suffix), so prepending keeps the order chronological.
191 group := newestToolGroup(history[:i+1])

Calls 8

dropDanglingToolCallsFunction · 0.85
dropOrphanToolsFunction · 0.85
newestToolIndexFunction · 0.85
onlyNonSubstantiveFunction · 0.85
newestToolGroupFunction · 0.85
anchorUserMessageFunction · 0.85
demoteSystemMessagesFunction · 0.85
TokensMethod · 0.80