Skip to content

fix(google): carry Gemini thought signatures through the Responses round trip - #1781

Merged
lidge-jun merged 1 commit into
devfrom
codex/gemini-thought-signature-1735
Aug 15, 2026
Merged

fix(google): carry Gemini thought signatures through the Responses round trip#1781
lidge-jun merged 1 commit into
devfrom
codex/gemini-thought-signature-1735

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Summary

Gemini validates a thought signature against the exact part that carried the function call. The signature was observed on the response and then dropped: the adapter event carried only id and name, the Responses function_call item had nowhere to put it, and the parser rebuilt the tool call without it. A same-process replay cache hid this for streaming turns, which is why the loss looked intermittent — history replay and previous_response_id had no cache to fall back on.

This supersedes #1772 by @chilung-cgu, which repaired one web-search reconstruction. Per the maintainer roadmap, a web-search-only patch is not sufficient because every other tool loop rebuilds calls the same way.

The metadata now travels with the individual tool call at every hop:

  • OcxProviderOpaqueToolCallMetadata on OcxToolCall and tool_call_start
  • src/responses/provider-opaque-metadata.ts — the one seam owning the wire shape extra_content.google.thought_signature
  • Google adapter attaches it from the originating part (streaming and buffered) and prefers it on the way back out, with the legacy thoughtSignature field kept as fallback
  • Bridge emits it on completed and incomplete items, so SSE and buffered JSON agree
  • Web-search and image loops copy it per call, so parallel calls can never share or swap a signature

Values are opaque: never parsed, merged, re-encoded, or synthesized, and refused above the same 64 KiB ceiling the replay cache already enforces.

Closes #1735

Follow-ups deliberately not in this PR

Two adjacent defects were found during review and are recorded in devlog/_plan/260815_roadmap_closeout/050_gemini_thought_signature.md rather than bundled here: Google-issued functionCall.id is replaced by a synthetic call_* (ids still match each other, so pairing works), and the web-search loop interleaves parallel calls as FC1, FR1, FC2, FR2 where Google wants FC1, FC2, FR1, FR2.

Verification

Run on the Linux validation host against this exact head:

bun test tests/google-provider-metadata-roundtrip.test.ts tests/google-signature-history-roundtrip.test.ts tests/google-vertex-thought-signature.test.ts tests/google-antigravity-wire.test.ts tests/bridge.test.ts tests/web-search.test.ts
176 pass, 0 fail, 534 expect() calls

bun x tsc --noEmit clean. New regressions cover the cold history replay path (no cache warmed), parallel calls keeping distinct signatures, unsigned history staying unsigned, and malformed/unknown/oversized payloads carrying nothing.

Checklist

  • Focused tests for the changed subsystem pass
  • bun x tsc --noEmit clean
  • Existing unsigned calls keep a byte-identical wire shape (conditional spread)
  • Native Responses passthrough untouched

Summary by CodeRabbit

  • New Features

    • Preserved Google Gemini thought-signature metadata across streaming, buffered responses, history replay, and rebuilt tool calls.
    • Maintained separate signatures for parallel tool calls.
    • Added support for safely carrying provider-specific metadata through Responses function-call data.
  • Bug Fixes

    • Prevented signatures from being lost during tool-call processing or incorrectly reused across calls.
    • Added validation for malformed metadata and oversized signatures.

…und trip

Gemini validates a thought signature against the exact part that carried the
function call. The signature was observed on the response and then dropped:
the adapter event carried only id and name, the Responses function_call item
had nowhere to put it, and the parser rebuilt the tool call without it. A
same-process replay cache hid this for streaming turns, which is why the loss
looked intermittent - history replay and previous_response_id had no cache to
fall back on.

Carry it with the individual tool call at every hop instead. One shared seam
owns the wire shape, the Google adapter attaches metadata from the originating
part and prefers it on the way back out, and both synthetic loops copy it per
call so parallel calls can never share or swap a signature.

Signatures are opaque here: never parsed, merged, re-encoded, or synthesized,
and refused above the same 64 KiB ceiling the replay cache already enforces.

Closes #1735
Co-authored-by: chilung-cgu <chilung-cgu@users.noreply.github.com>
@lidge-jun
lidge-jun merged commit 4b95010 into dev Aug 15, 2026
6 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Aug 15, 2026
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7d9277ac-78d5-42d7-9f80-9bd537f84c69

📥 Commits

Reviewing files that changed from the base of the PR and between 02728d2 and d8b01b5.

📒 Files selected for processing (11)
  • devlog/_plan/260815_roadmap_closeout/050_gemini_thought_signature.md
  • src/adapters/google.ts
  • src/bridge.ts
  • src/images/loop.ts
  • src/responses/parser.ts
  • src/responses/provider-opaque-metadata.ts
  • src/responses/schema.ts
  • src/types.ts
  • src/web-search/loop.ts
  • tests/google-provider-metadata-roundtrip.test.ts
  • tests/google-signature-history-roundtrip.test.ts

📝 Walkthrough

Walkthrough

The change preserves Google Gemini thoughtSignature metadata through Google adapter events, Responses function_call items, parsing, bridge state, media and web-search replay, and rebuilt tool-call requests. It adds validation, cloning, schema support, and regression tests.

Changes

Gemini signature round trip

Layer / File(s) Summary
Opaque metadata contract
src/types.ts, src/responses/schema.ts, src/responses/provider-opaque-metadata.ts, src/responses/parser.ts
Tool calls and adapter events now carry provider metadata. Responses parsing and serialization support validated Google thought_signature values with a 64 KiB UTF-8 limit.
Google adapter extraction and serialization
src/adapters/google.ts
Streaming and buffered Google responses attach per-call signatures to tool-call events. Request serialization prefers nested provider metadata and retains the legacy fallback.
Tool-call accumulation and replay
src/bridge.ts, src/images/loop.ts, src/web-search/loop.ts
Streaming, buffered, media, and web-search paths retain metadata and clone it onto rebuilt tool calls.
Round-trip validation and regression coverage
tests/google-provider-metadata-roundtrip.test.ts, tests/google-signature-history-roundtrip.test.ts, devlog/_plan/260815_roadmap_closeout/050_gemini_thought_signature.md
Tests cover wire conversion, validation, size boundaries, clone isolation, parallel calls, and cache-independent history replay. The plan records the complete propagation scope.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GoogleAdapter
  participant Bridge
  participant ResponsesParser
  participant ResponsesHistory
  GoogleAdapter->>Bridge: emit tool_call_start with thoughtSignature
  Bridge->>ResponsesHistory: write function_call extra_content
  ResponsesHistory->>ResponsesParser: provide function_call history
  ResponsesParser->>GoogleAdapter: rebuild tool call with provider metadata
  GoogleAdapter->>GoogleAdapter: serialize thoughtSignature in next request
Loading

Possibly related PRs

Suggested reviewers: ingwannu, wibias

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/gemini-thought-signature-1735

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d8b01b53b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/bridge.ts
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
// Provider-opaque metadata (issue #1735) rides the item so a client that replays
// this history can hand the signature back on the part it belongs to.
...(responsesExtraContentFromProviderMetadata(currentToolCall.providerMetadata) ?? {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve signatures on custom and tool-search calls

When Gemini invokes a declared custom tool such as apply_patch, or the client-side tool_search tool, the bridge selects one of the preceding branches, so this metadata spread never runs; the buffered path has the same omission. These tools are exposed to Gemini as function declarations and their calls can therefore carry a required thoughtSignature, but the resulting custom_tool_call/tool_search_call item and parser discard it. A direct-Gemini follow-up or cold history replay consequently sends the call back unsigned and can be rejected upstream. Attach the bounded metadata to these item variants as well and restore it when parsing them into OcxToolCall.

AGENTS.md reference: src/AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant