fix(router): port compaction handover to OpenAI path - #792
Open
rohith500 wants to merge 2 commits into
Open
Conversation
Signed-off-by: N Rohith Reddy <rohithreddy2202@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Mirrors ProxyMessages' PrefixTrimmed consumer: ProxyOpenAIChatCompletion now calls runCompactionHandover when staying on a non-Anthropic model after a client-side history trim, matching the protection PR workweave#298 added for Messages. Without this, an OpenAI-wire session pinned to a non-Anthropic model that compacts its own history got no 'prior work' summary injected, risking the same duplicate-edit failure workweave#298 fixed for Messages. Also wires the corresponding _compaction_summary billing, matching the Messages path. Fixes workweave#791 Signed-off-by: N Rohith Reddy <rohithreddy2202@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
PR author is not in the allowed authors list. |
Contributor
|
Thanks for this — reviewed against the repo's
Nice parity fix — appreciated! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #791.
Summary
ProxyOpenAIChatCompletionnever calledrunCompactionHandover, eventhough the shared
runTurnLoopalready detects the exact trigger condition(
PrefixTrimmed) thatProxyMessagesuses to fire it. This PR ports thesame consumer, plus its billing, to the OpenAI path.
Background / root cause
runTurnLoop'scompactionTracker.checkAndRecord(
internal/proxy/no_progress.go) detects two shapes of client-side historytrimming by comparing each turn's message count and tool-call count to the
last observation for the same session: full compaction (message count drops
sharply from a substantial conversation, gated at
compactionMinHistoryMessages = 8) and rolling-window trimming (messagecount flat, tool-call count shrinks). Either sets
routeRes.PrefixTrimmed,computed pre-routing and shared by both
ProxyMessagesandProxyOpenAIChatCompletion— both already read it for planner cold-pin /free-switch pricing.
Only
ProxyMessages(pre-fix) consumed it post-routing to protectcorrectness, not just cost. PR #298 introduced this after a real production
incident: Claude Code's context compaction dropped a completed Edit and its
tool_resultfrom history, and the non-Anthropic model serving thatsession (DeepSeek) had no record the edit had happened — it re-applied the
same edit.
runCompactionHandoverrewrites the envelope with a summary ofthe elided work before dispatch, specifically for the STAY-on-non-Anthropic
case (a SWITCH turn gets its own handover summarizer via the turn loop
already).
ProxyOpenAIChatCompletionhad zero references toPrefixTrimmedorrunCompactionHandoverpost-routing. It gotmaybeCompact(the separate,proactive, pre-routing feature that prevents context-window overflow) via
PR #644 — which is a different mechanism entirely and doesn't address this.
For an OpenAI-wire session pinned to a non-Anthropic model that compacts
its own history mid-session, the router forwarded the already-trimmed
history raw, with no injected "prior work" summary — the same failure
shape #298 fixed for Messages, just unaddressed on this sibling surface.
What changed
In
ProxyOpenAIChatCompletion(internal/proxy/service.go):ProxyMessagesexactly, with one addition:The
!responsesPassthroughclause is new relative to the Messages guard —Codex Responses passthrough bodies are forwarded verbatim by design
elsewhere in this function, and should stay that way rather than getting
rewritten with a handover summary.
cacheEligibleupdated to excludecompactionHandoverRan, matchingthe existing Messages-path comment/rationale: a handover rewrite means
the routing decision's embedding predates the rewrite, so caching under
that embedding would be wrong.
Billing wired, verified as a structural match (not just an
equivalent outcome) against the Messages-path block at
service.go:3023— sameDebitInferenceParamsconstruction, same_compaction_summaryrequest-ID suffix, same guard shape:runCompactionHandoveritself is untouched. ProxyOpenAIChatCompletion never calls runCompactionHandover after PrefixTrimmed — OpenAI client-trim stays lose prior-work summary #791's investigationconfirmed the summarizer already accepts OpenAI-format envelopes end to
end:
buildSummaryRequestBody→env.PrepareAnthropic(handover.go:221-222)PrepareAnthropichandlesFormatOpenAIviabuildAnthropicFromOpenAI(
emit_anthropic.go:21-25)RewriteForHandoveralready has an OpenAI case (translate/handover.go)This confirmed it as a call-site wiring gap, not a missing translation
layer — so this PR is scoped to
service.goonly.Commits
5b705ed—TestService_CompactionHandover_OpenAIParityWithMessages,asserting the desired post-fix behavior (summarizer invoked on OpenAI,
matching Messages). Confirmed failing against pre-fix code for the right
reason: the trim was correctly detected
(
"turnloop detected client history trim"in logs) but the summarizerwas never called (
sz.calls == 0).9c5f0c7— the fix described above. Test passes after.Testing
session: same API key, same first user message, per
DeriveSessionKey)through both
ProxyMessagesandProxyOpenAIChatCompletionagainst areal
*Servicewith stub providers and a counting stub summarizer (nonetwork calls). Turn 1 (first observation, nothing to compare against)
correctly shows zero invocations on both surfaces — confirms the trigger
is genuinely the turn-2 count drop, not incidental to message shape or
turn count alone. Turn 2 now shows
sz.calls >= 1on both surfaces.go test ./internal/proxy/... -count=1— clean, including all existingOpenAI-path and compaction-related tests (no regressions).
make check— clean.Known, deliberately unaddressed
wiring-only change: Gemini SWITCH never gets bounded-cost handover — ProviderSummarizer rejects FormatGemini #755 already tracks that
ProviderSummarizer/PrepareAnthropiccan't ingest Gemini-format envelopes at all yet — atranslation-layer gap, not just a missing call site. Gemini's compaction
handover should wait until Gemini SWITCH never gets bounded-cost handover — ProviderSummarizer rejects FormatGemini #755 lands; happy to pick it up at that point.
compactionTracker.checkAndRecord)or the proactive
maybeCompactpath — both already work identically onOpenAI (ported by feat(proxy): proactively compact over-window sessions instead of failing #644) and Messages. Only the reactive, post-routing
consumer was missing.
Related (from #791)
runCompactionHandover, Messages-only at the time,after the DeepSeek duplicate-edit production incident this PR closes the
parity gap on.
maybeCompactfeature to OpenAI; didn'tattempt this consumer (different feature, not an interrupted port of the
same one).
noProgressTrackerandcompactionTracker: non-atomic LRU check-then-set races delay loop detection and can falsely trigger context-rewrite handover #471 — an LRU race in the compaction/no-progress tracker itself,unrelated to this missing call site.