Skip to content

fix: preserve gemini thought signatures#25933

Merged
ssncferreira merged 5 commits into
mainfrom
mike/no-workspace-terminal-tool-errors
Jun 9, 2026
Merged

fix: preserve gemini thought signatures#25933
ssncferreira merged 5 commits into
mainfrom
mike/no-workspace-terminal-tool-errors

Conversation

@ibetitsmike

@ibetitsmike ibetitsmike commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

AI Bridge reserializes OpenAI chat-completions requests before sending them upstream. For Gemini OpenAI-compatible routes, that OpenAI typed-parameter round trip drops tool_calls[].extra_content.google.thought_signature, so Google rejects tool-result continuations with Function call is missing a thought_signature.

This PR:

  • patches the AI Bridge upstream serialization boundary for Gemini OpenAI-compatible chat completions
  • shares the Gemini thought-signature patching helpers with chatd's OpenAI-compatible transport patch to keep behavior consistent
  • treats direct Google OpenAI-compatible upstream endpoints as Gemini-scoped even when the request model is an alias
  • adds the Google fallback thought signature to every assistant tool call in the active turn, including parallel tool calls
  • covers the regression that extra_content is dropped before the upstream body is patched

Mux updated this PR description on behalf of Mike.

@ibetitsmike ibetitsmike force-pushed the mike/no-workspace-terminal-tool-errors branch from 4879cd2 to e555857 Compare June 1, 2026 19:03
@ibetitsmike ibetitsmike changed the title fix(coderd/x/chatd): stop no-workspace tool continuations fix(coderd/x/chatd/chatprovider): patch gemini bridge tool calls Jun 1, 2026
@ibetitsmike ibetitsmike force-pushed the mike/no-workspace-terminal-tool-errors branch from e555857 to 6cfa027 Compare June 1, 2026 19:39
@ibetitsmike ibetitsmike changed the title fix(coderd/x/chatd/chatprovider): patch gemini bridge tool calls fix(aibridge/intercept/chatcompletions): preserve gemini tool signatures Jun 1, 2026
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-06-01 21:42 UTC by @ibetitsmike
Spend: $75.39 / $100.00

Review history
  • R1 (2026-06-01): 16 reviewers, 4 Nit, 1 P2, 7 P3, 1 P4, COMMENT. Review
  • R2 (2026-06-01): 15 reviewers, 7 Nit, 1 P2, 8 P3, 1 P4, APPROVE. Review
  • R3 (2026-06-01), 7 Nit, 1 P2, 8 P3, 1 P4, COMMENT. Review
  • R4 (2026-06-01): 6 reviewers, 7 Nit, 1 P2, 8 P3, 1 P4, APPROVE. Review

deep-review v0.6.1 | Round 4 | fc01aee..6eae190

Last posted: Round 4, 17 findings (1 P2, 8 P3, 1 P4, 7 Nit), APPROVE. Review

Finding inventory

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Author fixed (c33e4a0) google_openai_compat.go:100 Five functions + constant duplicated from chatd/chatprovider with divergent parallel tool call behavior R1 Robin P2, Mafuuu P2, Mafu-san P2, Zoro P2 Yes
CRF-2 P3 Author fixed (c33e4a0) google_openai_compat.go:76 Current-turn-only patching is correct per Google docs but the assumption is undocumented R1 Pariston P2, Kite P2, Meruem P2, Razor Note (confirms correctness) Yes
CRF-3 P3 Author fixed (c33e4a0) google_openai_compat.go:11 Constant has no provenance comment; chatd copy has doc link to Google thought-signatures page R1 Leorio P3, Mafu-san P3, Zoro P3, Razor P3 Yes
CRF-4 P3 Author fixed (c33e4a0) google_openai_compat.go:1 134 lines of vendor workaround with zero file-level or function-level comments R1 Leorio P2 Yes
CRF-5 P3 Author fixed (c33e4a0) blocking.go:289 Blocking path passes full typed params then overrides with WithRequestBody; streaming path passes empty params R1 Hisoka P3, Ryosuke P3 Yes
CRF-6 P3 Author fixed (c33e4a0) google_openai_compat.go:62 Host check pinned to generativelanguage.googleapis.com; Vertex AI endpoints not covered R1 Meruem P3, Razor P2 Yes
CRF-7 P3 Author fixed (c33e4a0) google_openai_compat_internal_test.go:53 No test exercises isAssistantRole("model"), the Gemini-specific role value R1 Bisky P3 Yes
CRF-8 P3 Author fixed (c33e4a0) google_openai_compat_internal_test.go:12 No multi-turn conversation test to document current-turn-only scoping R1 Bisky P3 Yes
CRF-9 P4 Author fixed (c33e4a0) google_openai_compat.go:21 chatCompletionRequestOptions (blocking path entry point) has no direct test R1 Bisky P4, Chopper P3 Yes
CRF-10 Nit Author fixed (c33e4a0) google_openai_compat_internal_test.go:70 Guard test uses positional assertions without named subtests R1 Chopper Nit, Kite P3 Yes
CRF-11 Nit Author fixed (c33e4a0) google_openai_compat.go:39 Silent error fallback returns unpatched body without logging R1 Chopper P3, Meruem Nit Yes
CRF-12 Nit Author fixed (c33e4a0) streaming.go:186 Comment now incomplete; does not mention thought signature patching added by chatCompletionRequestBody R1 Leorio Nit, Gon P2 Yes
CRF-13 Nit Author fixed (c33e4a0) google_openai_compat.go:113 isAssistantRole checks for "model" without explaining why R1 Leorio Nit Yes
CRF-14 Note Dropped by orchestrator (informational, no code change needed) google_openai_compat.go:11 Google documents that skip_thought_signature_validator degrades reasoning quality R1 Razor Note, Hisoka Note No
CRF-15 P3 Author fixed (6eae190) thought_signature_test.go:12 ShouldPatchOpenAICompatRequest has no unit test at home package level; behavioral difference from ShouldPatchGoogleUpstreamRequest untested R2 Netero P3 Yes
CRF-16 P3 Author fixed (PR title update) PR title PR title says "tool signatures" but code and docs consistently say "thought signatures" R2 Leorio P3 Yes (body)
CRF-17 Nit Author fixed (6eae190) thought_signature.go:34 Five helpers exported without external callers; test file needs exports but could use internal test package R2 Meruem Nit, Ryosuke Nit, Zoro P3, Knov Nit Yes
CRF-18 Nit Author fixed (6eae190) thought_signature.go:76 Doc comments on AddThoughtSignaturesToLatestTurn, IsAssistantRole, EnsureThoughtSignature have restating first sentences R2 Gon P2 Yes
CRF-19 Nit Author fixed (6eae190) google_openai_compat.go:34 append([]option.RequestOption{}, opts...) should be slices.Clone(opts) R2 Ging-Go Nit Yes
CRF-20 Nit Dropped by orchestrator (common Go test pattern, cross-package helpers have trade-offs) thought_signature_test.go:111 Test helper thoughtSignature reimplemented in 3-4 places across packages R2 Robin Nit, Zoro Nit No
CRF-21 Nit Dropped by orchestrator (intentional, tested, confirmed correct by Hisoka and Pariston) thought_signature.go:30 ShouldPatchGoogleUpstreamRequest requires model prefix for direct endpoints while sibling does not R4 Mafuuu P3 No

Round log

Round 1

Panel. Netero: 0 findings. Panel (16 reviewers): 1 P2, 7 P3, 1 P4, 4 Nit, 1 Note (dropped). Reviewed against fc01aee..6cfa027.

Round 2

All 13 R1 findings addressed in c33e4a0. Netero: 1 P3 (CRF-15). Panel (15 reviewers): 2 P3, 3 Nit, 1 Nit (dropped). 7 reviewers reported no findings. Reviewed against fc01aee..c33e4a0.

Round 3

BLOCKED. CRF-15/17/18/19 addressed in 6eae190. CRF-16 (PR title terminology) silent. No review.

Round 4

CRF-16 addressed via PR title update. All findings resolved. Netero: 0 findings. Panel (6 reviewers): 1 Nit (dropped, intentional per Hisoka+Pariston). 5 reported no findings. Reviewed against fc01aee..6eae190.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Well-targeted fix for the Gemini thought-signature round-trip loss. The root cause diagnosis is solid: the test at line 42 proves the SDK drops extra_content, and the patch injects Google's documented bypass value at the serialization boundary. The approach is proportional, the guard logic is clean (host + model double-check), and ensureGoogleOpenAICompatThoughtSignature is properly idempotent.

Severity breakdown: 1 P2, 7 P3, 1 P4, 4 Nit.

The P2 is code duplication. Five functions and one constant are copied from coderd/x/chatd/chatprovider/openai_compat_patches.go (4 days old, same module), and the two implementations diverge on parallel tool calls: this PR patches all tool calls per assistant message, while the chatd version patches only the first. If patching all is correct (as the PR description states), the chatd version has a sibling bug for parallel tool calls.

Multiple reviewers (8+) flagged the current-turn-only scoping in addGoogleOpenAICompatThoughtSignatures as potentially incorrect for multi-turn conversations. Razor resolved this with evidence from Google's thought-signatures documentation: "Only current turn is required; we don't validate on previous turns." The scoping is correct, but the assumption is undocumented in the code.

"The agent used an existing verified value (trust signal: not fabricated), but dropped the trail that makes it verifiable by others." (Mafu-san)

🤖 This review was automatically generated with Coder Agents.

Comment thread aibridge/intercept/chatcompletions/google_openai_compat.go Outdated
Comment thread aibridge/intercept/chatcompletions/google_openai_compat.go Outdated
Comment thread aibridge/intercept/chatcompletions/google_openai_compat.go Outdated
Comment thread aibridge/intercept/chatcompletions/google_openai_compat.go
Comment thread aibridge/intercept/chatcompletions/blocking.go Outdated
Comment thread aibridge/intercept/chatcompletions/google_openai_compat.go Outdated
Comment thread aibridge/intercept/chatcompletions/google_openai_compat_internal_test.go Outdated
Comment thread aibridge/intercept/chatcompletions/google_openai_compat.go Outdated
Comment thread aibridge/intercept/chatcompletions/streaming.go
Comment thread aibridge/intercept/chatcompletions/google_openai_compat.go Outdated
@ibetitsmike ibetitsmike force-pushed the mike/no-workspace-terminal-tool-errors branch from 6cfa027 to c33e4a0 Compare June 1, 2026 20:56
@ibetitsmike ibetitsmike changed the title fix(aibridge/intercept/chatcompletions): preserve gemini tool signatures fix: preserve gemini tool signatures Jun 1, 2026
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All 13 Round 1 findings addressed thoroughly. The extraction to internal/googleopenai/ resolves the duplication (CRF-1), fixes the chatd parallel tool call bug, and centralizes the thought-signature logic with proper documentation. The blocking path now correctly passes empty params when the body is overridden (CRF-5). Every documentation gap from R1 is filled.

Round 2 severity: 2 P3, 3 Nit. 7 of 15 reviewers reported no new findings.

P3 [CRF-16] The PR title says "tool signatures" but all code, tests, and documentation consistently say "thought signatures." Future git log --grep=thought.signature searches will miss this PR. Consider updating to "fix: preserve gemini thought signatures." (Leorio P3)

"Five helpers exported without external callers. The test file forces the exports, but EnsureThoughtSignature without AddThoughtSignaturesToLatestTurn is a loaded gun without the safety." (Knov)

🤖 This review was automatically generated with Coder Agents.

Comment thread internal/googleopenai/thought_signature_test.go
Comment thread internal/googleopenai/thought_signature.go Outdated
Comment thread internal/googleopenai/thought_signature.go Outdated
Comment thread aibridge/intercept/chatcompletions/google_openai_compat.go Outdated
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CRF-15, CRF-17, CRF-18, and CRF-19 addressed in 6eae190. Thank you.

Further review is blocked on one unaddressed finding:

CRF-16 (P3): The PR title says "tool signatures" but all code, tests, and documentation consistently say "thought signatures." This was raised in the Round 2 review body. Please either update the PR title to "fix: preserve gemini thought signatures" or respond with why the current title is intentional.

🤖 This review was automatically generated with Coder Agents.

@ibetitsmike ibetitsmike changed the title fix: preserve gemini tool signatures fix: preserve gemini thought signatures Jun 1, 2026
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All 21 findings resolved or dropped across 4 rounds. Netero clean. Panel clean (5 of 6 no findings; 1 Nit dropped as intentional per independent confirmation from 2 prior reviewers).

This PR correctly diagnoses the Gemini thought-signature round-trip loss through the OpenAI SDK, patches it at the narrowest possible point in the serialization boundary, extracts shared logic into internal/googleopenai/ to keep both callers (aibridge and chatd) in sync, and fixes the pre-existing chatd bug for parallel tool calls. Test density is 58.8% with targeted behavioral tests that double as living documentation.

🤖 This review was automatically generated with Coder Agents.

@ibetitsmike ibetitsmike force-pushed the mike/no-workspace-terminal-tool-errors branch from 6eae190 to 440e251 Compare June 2, 2026 08:10
@dannykopping dannykopping requested a review from ssncferreira June 2, 2026 08:41
@ibetitsmike ibetitsmike marked this pull request as ready for review June 4, 2026 10:07
@ibetitsmike ibetitsmike force-pushed the mike/no-workspace-terminal-tool-errors branch from 9cc161b to 1969d97 Compare June 4, 2026 10:07

@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: 1969d97d8c

ℹ️ 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 internal/googleopenai/thought_signature.go Outdated
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

ℹ️ 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 aibridge/intercept/chatcompletions/blocking.go Outdated
return patched, nil
}

func (i *interceptionBase) chatCompletionRequestOptions(opts []option.RequestOption) ([]option.RequestOption, bool, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: would be nice to have a short summary explaining the returned values. Same on the above 🙂

if !googleopenai.ShouldPatchGoogleUpstreamRequest(i.cfg.BaseURL, i.req.Model) {
return body, nil
}
patched, _, err := googleopenai.PatchThoughtSignatures(body)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IIUC, for every request, we are checking if the request is going to Google Gemini. Instead of doing this every time at the interception layer, we could do it at the provider layer once. A Google provider (in this case, a OpenAI provider with base url "generativelanguage.googleapis.com") will always be sending request to "generativelanguage.googleapis.com" , and vice-versa a non-google provider wll never send request to "generativelanguage.googleapis.com" .

Comment thread internal/googleopenai/thought_signature.go Outdated
// history in body. It returns changed=false when no patch is needed.
func PatchThoughtSignatures(body []byte) ([]byte, bool, error) {
var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We are marshalling ChatCompletionNewParams to byte[], then here unmarshalling to map, and then again marshalling to map 😅 unfortunately, give the sdk struct, I don't think there is a better alternative

@ssncferreira ssncferreira force-pushed the mike/no-workspace-terminal-tool-errors branch from d4809b3 to b06967c Compare June 8, 2026 11:30
@ssncferreira ssncferreira self-assigned this Jun 8, 2026

@ssncferreira ssncferreira left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM with some nits, but nothing blocking

if isDirectGeminiOpenAIEndpoint(baseURL) {
return true
}
return isCoderAIBridgeEndpoint(baseURL) && isGeminiModelID(modelID)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this else branch is unnecessary, because whenever requests go through AI Gateway, it already performs the patch. IIUC, the flow would be:

  1. Coder agent makes a request to Gemini via AIBridge
  2. isCoderAIBridgeEndpoint(baseURL) && isGeminiModelID(modelID) returns true
  3. AIBridge removes the though signature due to the SDK
  4. AIBridge does chatCompletionRequestOptions which patches the request body to include the thought signature again.

@ssncferreira ssncferreira merged commit c349ea6 into main Jun 9, 2026
28 checks passed
@ssncferreira ssncferreira deleted the mike/no-workspace-terminal-tool-errors branch June 9, 2026 11:11
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants