Skip to content

fix(mcp): persist and re-attach Gemini thoughtSignature on the direct Claude<->Gemini path - #9448

Merged
diegosouzapw merged 4 commits into
diegosouzapw:release/v3.8.50from
Sam280903:fix/claude-to-gemini-thought-signature
Aug 13, 2026
Merged

diegosouzapw merged 4 commits into
diegosouzapw:release/v3.8.50from
Sam280903:fix/claude-to-gemini-thought-signature

Conversation

@Sam280903

Copy link
Copy Markdown
Contributor

Summary

  • The direct Claude<->Gemini translator (claude-to-gemini.ts / gemini-to-claude.ts) never persisted the thoughtSignature Gemini returns on functionCall parts, and never re-attached one on the next turn.
  • Gemini 3+/2.5 strictly reject a native functionCall part with no signature (400). This surfaces in practice whenever a fallback combo lands on a Gemini model mid-conversation — the prior tool_use never went through Gemini, so no real signature was ever stored for it.
  • gemini-to-claude.ts now stores the signature (keyed by tool_use id + connection namespace) when Gemini's response carries one, mirroring the existing gemini-to-openai.ts hub-path behavior.
  • claude-to-gemini.ts now resolves a stored signature for historical tool_use blocks; when none exists and the target model requires one, the tool_use/tool_result pair is downgraded to inert text instead of being sent as a signature-less native part — matching the "context" fallback the OpenAI hub path already uses for the same situation (fix(gemini): resolve truncation/suppression of false positive textual tool call markers in backticks #3358), rather than the removed fake-signature injection this file's dead code comment referenced.

Test plan

  • node --import tsx/esm --test tests/unit/translator-claude-to-gemini.test.ts — updated the two pre-existing tests that relied on the old (buggy) signature-less passthrough to prime a real stored signature, added a regression test reproducing the exact 400 (signature-less tool_use/tool_result pair is downgraded to inert text on a thinking-capable model), and a test locking in that older non-thinking models are unaffected.
  • node --import tsx/esm --test tests/unit/translator-resp-gemini-to-claude.test.ts — added a test asserting the signature from a standalone thoughtSignature part is persisted and retrievable via getGeminiThoughtSignature.
  • node --import tsx/esm --test tests/unit/vertex-functioncall-id-3440.test.ts — updated the one affected pre-existing test (primed a signature; unrelated to what it actually tests).
  • Full run across all touched/adjacent test files: 51/51 passing.
  • npm run lint clean on all touched files (test-file any usage stayed within the frozen per-file budget).
  • npx tsc --noEmit clean on all touched files.

Sam280903 and others added 3 commits August 4, 2026 15:33
… Claude<->Gemini path

The direct Claude<->Gemini translator (claude-to-gemini.ts / gemini-to-claude.ts)
never persisted the thoughtSignature Gemini returns on functionCall parts, and
never re-attached one on the next turn. Gemini 3+/2.5 strictly reject a native
functionCall part with no signature (400), which surfaces whenever a combo falls
back onto a Gemini model mid-conversation (the fallback tool_use never went
through Gemini, so no signature exists for it).

- gemini-to-claude.ts: store the signature (keyed by tool_use id + connection
  namespace) when Gemini's response carries one, mirroring the existing
  gemini-to-openai.ts hub-path behavior.
- claude-to-gemini.ts: resolve a stored signature for historical tool_use
  blocks; when none exists and the target model requires one, downgrade the
  tool_use/tool_result pair to inert text instead of sending a signature-less
  native part, matching the "context" fallback already used by the OpenAI hub
  path (diegosouzapw#3358) rather than the removed fake-signature injection.
@diegosouzapw

Copy link
Copy Markdown
Owner

Review: PR #9448 — fix(mcp): persist Gemini thoughtSignature on direct Claude<->Gemini path

Verdict: merge-ready (5/5 stars)

Correctly persists and re-attaches Gemini thoughtSignature. Matches existing hub-path behavior. Tests included.

Ready to merge after retarget/rebase to release/v3.8.50 tip.

The direct Claude<->Gemini thoughtSignature persist/re-attach fix this PR
introduced (open-sse/translator/request/claude-to-gemini.ts,
open-sse/translator/response/gemini-to-claude.ts) was independently
superseded on release/v3.8.50 by a more complete implementation covering
the same root cause (diegosouzapw#2504, diegosouzapw#3440) plus related fixes (diegosouzapw#9568 tool-name
casing, standalone thoughtSignature parts, first-call-only signature
embedding). Conflicts resolved by taking the release implementation and
its test coverage, which already delivers this PR's stated goal.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw
diegosouzapw marked this pull request as ready for review August 13, 2026 10:54
@diegosouzapw
diegosouzapw self-requested a review as a code owner August 13, 2026 10:54
@diegosouzapw
diegosouzapw merged commit 6143da7 into diegosouzapw:release/v3.8.50 Aug 13, 2026
13 checks passed
@diegosouzapw

Copy link
Copy Markdown
Owner

Merged into release/v3.8.50 — thank you @Sam280903 for the contribution! It was validated on a combined merge-train (static gates + affected tests + vitest) together with 30 sibling PRs before landing. The release had independently landed a superset of this fix meanwhile, so what shipped from your branch is the extra regression test covering the standalone thoughtSignature part — valuable coverage the suite did not have.

muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
… Claude<->Gemini path (diegosouzapw#9448)

The direct Claude<->Gemini translator (claude-to-gemini.ts / gemini-to-claude.ts)
never persisted the thoughtSignature Gemini returns on functionCall parts, and
never re-attached one on the next turn. Gemini 3+/2.5 strictly reject a native
functionCall part with no signature (400), which surfaces whenever a combo falls
back onto a Gemini model mid-conversation (the fallback tool_use never went
through Gemini, so no signature exists for it).

- gemini-to-claude.ts: store the signature (keyed by tool_use id + connection
  namespace) when Gemini's response carries one, mirroring the existing
  gemini-to-openai.ts hub-path behavior.
- claude-to-gemini.ts: resolve a stored signature for historical tool_use
  blocks; when none exists and the target model requires one, downgrade the
  tool_use/tool_result pair to inert text instead of sending a signature-less
  native part, matching the "context" fallback already used by the OpenAI hub
  path (diegosouzapw#3358) rather than the removed fake-signature injection.

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
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.

2 participants