Skip to content

fix(proxy): enable Gemini SWITCH bounded-cost handover - #757

Open
rohith500 wants to merge 5 commits into
workweave:mainfrom
rohith500:fix/gemini-handover-summarizer-ingest
Open

fix(proxy): enable Gemini SWITCH bounded-cost handover#757
rohith500 wants to merge 5 commits into
workweave:mainfrom
rohith500:fix/gemini-handover-summarizer-ingest

Conversation

@rohith500

@rohith500 rohith500 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Bug: On a planner SWITCH for a native Gemini request (ProxyGeminiGenerateContent), the stock ProviderSummarizer always failed before any upstream call. It builds its request via env.PrepareAnthropic(...), which had no FormatGemini case and returned unsupported 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 prior contents at the new model's input rate, plus warn-level log noise on every switch.

Latent sibling (required in the same change set): rewriteGeminiForHandover kept the latest user turn verbatim. Anthropic's sibling strips orphan tool_result blocks. Once summarizer ingest succeeded, a mid-tool Gemini SWITCH that rewrote successfully would forward an orphan functionResponse after a text-only summary → Google 400 INVALID_ARGUMENT.

Fix:

  1. Teach PrepareAnthropic to accept FormatGemini via buildAnthropicFromGemini (systemInstruction + contents → Anthropic system/messages; functionCall/functionResponsetool_use/tool_result with 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.
  2. Strip orphan functionResponse parts in rewriteGeminiForHandover (drop the user turn if nothing remains), matching Anthropic.
  3. Strip orphan functionResponse parts in trimGeminiLastN with order-aware pairing (consume unmatched functionCall counts 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

File Change
internal/translate/emit_anthropic.go Add FormatGemini case in PrepareAnthropic
internal/translate/emit_anthropic_from_gemini.go New Gemini→Anthropic request builder for summarizer ingest
internal/translate/handover.go Orphan functionResponse strip on RewriteEnvelope + order-aware TrimLastN pairing
internal/proxy/turnloop.go Comment: RewriteEnvelope strips tool_results / functionResponse (Anthropic / Gemini)
internal/proxy/gemini_handover_test.go E2E Gemini SWITCH coverage (bounds, mid-tool, real summarizer, failure/empty, race)
internal/proxy/handover_internal_test.go ProviderSummarizer Summarize + SummarizeForCompaction accept Gemini
internal/router/handover/summarizer_test.go RewriteEnvelope + TrimLastN Gemini orphan / stale same-name cases
internal/translate/crossformat_request_test.go Gemini→Anthropic text+tools pairing
internal/translate/gemini_to_anthropic_edge_test.go Edge convert matrix

Expected vs Actual (pre-fix)

Anthropic / OpenAI SWITCH Gemini SWITCH (before) Gemini SWITCH (after)
Summarizer prepare succeeds unsupported source format succeeds
Upstream summarizer call yes no (call count 0) yes
Forwarded body [summary, latestUser] full prior contents [summary, latestUser]
Telemetry handover_fallback_to_full_history=false =true =false
Mid-tool rewrite strips orphan tool_result N/A (rewrite never ran) strips orphan functionResponse

Out of scope (called out in #755)

  • Wiring maybeCompact into ProxyGeminiGenerateContent (still Anthropic/OpenAI only).
  • Full product Gemini→non-Google request translation (still 501 at proxy).

Verification

Local (author):

  • make check — all checks passed (on e1f07d3)
  • Counterfactual on pre-fix parent: ProviderSummarizer.Summarize on Gemini fails with unsupported source format for Anthropic emit
  • Race-covered Gemini SWITCH / convert / TrimLastN suites
  • Bugbot finding addressed: order-aware same-name FR pairing (e1f07d3)

GitHub Actions (fork PR):

  • Cursor Bugbot — pass on e1f07d3 (prior medium finding fixed + thread resolved)
  • Test / Comment Length / Cluster Routing — currently 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)

rohith500 and others added 4 commits July 16, 2026 16:06
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>
@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown

PR author is not in the allowed authors list.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread internal/translate/handover.go Outdated
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>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

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 functionResponse stripping) is a great improvement, and the Gemini→Anthropic request translation it needed is done carefully. I reviewed it against the repo conventions (AGENTS.md) and it conforms — no changes needed:

  • Layering — the new conversion lives in internal/translate as pure, no-I/O functions (buildAnthropicFromGemini and friends), which is exactly where cross-format wire conversion belongs per the layer model; the proxy orchestration stays in internal/proxy.
  • Godoc on exported/behavioral symbols — the new helpers carry concise doc comments describing what they do (synthetic tool-use id pairing by name order, orphan stripping), and comments explain the non-obvious why.
  • No magic provider strings — fixtures use providers.ProviderGoogle; model names as literals follow the established internal/proxy test convention.
  • Tests — thorough and non-tautological: parallel-tool id pairing, snake_case aliases, orphan/stale functionResponse handling, stream-flag preservation, and the previously-"unsupported" negative test is correctly updated to assert the now-supported behavior.

Approving from a conventions standpoint — thank you for the thorough edge-case coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gemini SWITCH never gets bounded-cost handover — ProviderSummarizer rejects FormatGemini

1 participant