Repository navigation
fix(gemini): never send a functionCall-opening turn with no preceding user turn - #13573
Merged
diegosouzapw merged 2 commits intoSep 16, 2026
Conversation
… user turn Gemini rejects contents[] that opens with a functionCall-bearing "model" turn: 400 INVALID_ARGUMENT "Please ensure that function call turn comes immediately after a user turn or after a function response turn." mergeConsecutiveSameRoleContents (9router#2191) already guarantees strict user/model alternation for every index >= 1, so the only remaining way to violate this rule is history that OPENS with such a turn -- observed live when the true leading user turn is missing from reconstructed/compressed history while a mid-conversation assistant tool-call turn survives. ensureHistoryDoesNotOpenWithFunctionCall prepends a minimal synthetic user turn in that case, applied to both the OpenAI->Gemini and Claude->Gemini request paths right after the existing same-role merge. Regression test reproduces the exact reported live failure shape (history starting on an assistant tool-call turn) through the full translator and asserts contents[0].role is always "user".
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 13, 2026
…pening turn with no preceding user turn) into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 13, 2026
…pening turn with no preceding user turn) into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 14, 2026
…pening turn with no preceding user turn) into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 14, 2026
…pening turn with no preceding user turn) into dev/omniroute-dev-combined
Owner
|
Ran your new tests locally at the PR head — 27/27 pass, including the full-translator |
Contributor
Author
|
Added the changelog fragment — |
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 15, 2026
…pening turn with no preceding user turn) into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 15, 2026
…pening turn with no preceding user turn) into dev/omniroute-dev-combined
diegosouzapw
merged commit Sep 16, 2026
3df5d50
into
diegosouzapw:release/v3.8.51
11 of 16 checks passed
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
… user turn (diegosouzapw#13573) Merged. Gemini rejecting a turn that opens with a `functionCall` and no preceding user turn is a contract we have to honour on our side; building the turn correctly is cheaper than reading the upstream 400 that comes back. Validated as a combined board first (this PR merged with the 21 siblings of the same wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-counts, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 176 passing / 0 failing focused node:test cases across the 25 test files the wave touches and the dashboard test under Vitest (2/0). Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run. Thank you.
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.
Problem
Gemini rejects any
contents[]that opens with a functionCall-bearing"model"turn:Observed live on the
defaultcombo: a request retried this identical error against every one of 3-5 Gemini variants tried (each one rejects the same corrupted history identically), before the new identical-error circuit breaker (#13338) correctly stopped it fast instead of exhausting all 11 fallback targets over ~13 minutes.Root cause
mergeConsecutiveSameRoleContents(9router#2191) already guarantees strictuser/modelalternation for everycontents[]index>= 1(Gemini only ever uses those two roles incontents, and the merge collapses any adjacent same-role pair). That leaves exactly one way to violate Gemini's rule: history that opens with a functionCall-bearing turn, with nothing before it at all.This is reachable whenever the true leading user turn goes missing from a request's history while a mid-conversation assistant tool-call turn survives — e.g. continuation reconstruction, context compression, or a truncated client history. I was not able to pin down the exact upstream trigger with certainty from static analysis alone (flagged as a known gap in #13338); this fix closes the resulting Gemini-side failure regardless of which upstream path produces it.
Fix
ensureHistoryDoesNotOpenWithFunctionCall(openai-to-gemini/helpers.ts): ifcontents[0]is a"model"turn containing afunctionCallpart, prepend a minimal synthetic"user"turn. Applied right after the existing same-role merge in both the OpenAI→Gemini and Claude→Gemini request paths (siblings that share the same helper).Testing
translator-gemini-consecutive-role-2191.test.ts: the helper directly (prepends when needed, leaves valid/empty input alone), plus a full-translator test reproducing the exact reported failure shape (history opening on an assistant tool-call turn) and assertingcontents[0].roleis always"user".TypeError: ... is not a function/ missing leading-role assertion).translator-claude-to-gemini.test.tsfixtures intentionally started theirmessages[]on an assistant tool_use with no leading user turn (unit-testing field mapping in isolation, never exercised against real Gemini) — updated their content-index assertions to account for the now-correct synthetic leading turn; no behavior change to what they're actually testing.openai-to-gemini/claude-to-geminitest file) passes.tsc -p tsconfig.typecheck-core.jsonclean.🤖 Generated with Claude Code