fix(proxy): enable Gemini SWITCH bounded-cost handover - #757
Conversation
ProviderSummarizer rejected FormatGemini at PrepareAnthropic, so every Gemini SWITCH fell back to full history. Add Gemini→Anthropic summarizer ingest and strip orphan functionResponse on RewriteEnvelope so mid-tool switches stay Google-legal. Fixes workweave#755 Signed-off-by: N Rohith Reddy <rohithreddy2202@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Add e2e cases that exercise ProviderSummarizer ingest on Gemini SWITCH and assert full-history fallback still runs when summarization fails. Signed-off-by: N Rohith Reddy <rohithreddy2202@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Add edge-case Gemini→Anthropic convert tests, empty-summary/race SWITCH cases, and compaction ingest coverage. Also strip orphan functionResponse parts in trimGeminiLastN so TrimLastN matches Anthropic/OpenAI parity. Signed-off-by: N Rohith Reddy <rohithreddy2202@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Drop caller/task-flavored narration so comments match the one-line asserted-behavior convention used elsewhere in these packages. 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. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2fabf40. Configure here.
Name-only matching let a stale same-name functionResponse survive when a later kept functionCall reused the tool name. Walk the kept window in order and consume unmatched calls so each response needs a preceding call. Signed-off-by: N Rohith Reddy <rohithreddy2202@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks for this, @rohith500 — this is a meaty, well-executed change. 🙏 Wiring the Gemini SWITCH path through the bounded-cost handover (summary + latest user turn, with orphan
Approving from a conventions standpoint — thank you for the thorough edge-case coverage. |

Summary
Bug: On a planner SWITCH for a native Gemini request (
ProxyGeminiGenerateContent), the stockProviderSummarizeralways failed before any upstream call. It builds its request viaenv.PrepareAnthropic(...), which had noFormatGeminicase and returnedunsupported source format for Anthropic emit. The orchestrator correctly fell back to full history (handover_fallback_to_full_history=true), so the turn stayed safe — but the bounded-cost rewrite ([summary, latestUser]) that Anthropic and OpenAI SWITCH turns already get never ran. Every Gemini SWITCH therefore paid the full priorcontentsat the new model's input rate, plus warn-level log noise on every switch.Latent sibling (required in the same change set):
rewriteGeminiForHandoverkept the latest user turn verbatim. Anthropic's sibling strips orphantool_resultblocks. Once summarizer ingest succeeded, a mid-tool Gemini SWITCH that rewrote successfully would forward an orphanfunctionResponseafter a text-only summary → Google400 INVALID_ARGUMENT.Fix:
PrepareAnthropicto acceptFormatGeminiviabuildAnthropicFromGemini(systemInstruction + contents → Anthropic system/messages;functionCall/functionResponse→tool_use/tool_resultwith synthetic ids paired by tool name). Product Gemini→non-Google dispatch remains deferred at the proxy layer (ErrGeminiCrossFormatUnsupported→ 501); this path is for summarizer/compaction ingest only.functionResponseparts inrewriteGeminiForHandover(drop the user turn if nothing remains), matching Anthropic.functionResponseparts intrimGeminiLastNwith order-aware pairing (consume unmatchedfunctionCallcounts while walking the kept window) so a stale same-name response from a trimmed turn cannot hitch a ride on a later kept call.Fixes #755
What changed
internal/translate/emit_anthropic.goFormatGeminicase inPrepareAnthropicinternal/translate/emit_anthropic_from_gemini.gointernal/translate/handover.gofunctionResponsestrip on RewriteEnvelope + order-aware TrimLastN pairinginternal/proxy/turnloop.gointernal/proxy/gemini_handover_test.gointernal/proxy/handover_internal_test.gointernal/router/handover/summarizer_test.gointernal/translate/crossformat_request_test.gointernal/translate/gemini_to_anthropic_edge_test.goExpected vs Actual (pre-fix)
unsupported source format[summary, latestUser]contents[summary, latestUser]handover_fallback_to_full_history=false=true=falseOut of scope (called out in #755)
maybeCompactintoProxyGeminiGenerateContent(still Anthropic/OpenAI only).Verification
Local (author):
make check— all checks passed (one1f07d3)ProviderSummarizer.Summarizeon Gemini fails withunsupported source format for Anthropic emite1f07d3)GitHub Actions (fork PR):
e1f07d3(prior medium finding fixed + thread resolved)action_required(first-time contributor workflow approval). A maintainer needs to click Approve and run workflows on the PR checks tab before these can go green.Checklist (CONTRIBUTING.md / AGENTS.md)
main)Signed-off-byon every committranslate; orchestration stays inproxy)