fix: preserve gemini thought signatures#25933
Conversation
4879cd2 to
e555857
Compare
e555857 to
6cfa027
Compare
|
/coder-agents-review |
|
Chat: Review posted | View chat Review historydeep-review v0.6.1 | Round 4 | Last posted: Round 4, 17 findings (1 P2, 8 P3, 1 P4, 7 Nit), APPROVE. Review Finding inventoryFinding inventoryFindings
Round logRound 1Panel. Netero: 0 findings. Panel (16 reviewers): 1 P2, 7 P3, 1 P4, 4 Nit, 1 Note (dropped). Reviewed against fc01aee..6cfa027. Round 2All 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 3BLOCKED. CRF-15/17/18/19 addressed in 6eae190. CRF-16 (PR title terminology) silent. No review. Round 4CRF-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-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
There was a problem hiding this comment.
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.
6cfa027 to
c33e4a0
Compare
|
/coder-agents-review |
There was a problem hiding this comment.
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
EnsureThoughtSignaturewithoutAddThoughtSignaturesToLatestTurnis a loaded gun without the safety." (Knov)
🤖 This review was automatically generated with Coder Agents.
|
/coder-agents-review |
There was a problem hiding this comment.
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.
|
/coder-agents-review |
There was a problem hiding this comment.
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.
6eae190 to
440e251
Compare
9cc161b to
1969d97
Compare
There was a problem hiding this comment.
💡 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".
|
@codex review |
|
Codex Review: Didn't find any major issues. Swish! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
| return patched, nil | ||
| } | ||
|
|
||
| func (i *interceptionBase) chatCompletionRequestOptions(opts []option.RequestOption) ([]option.RequestOption, bool, error) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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" .
| // 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 { |
There was a problem hiding this comment.
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
Port parseProviderBaseURL logic from main into internal/googleopenai. Handles bare hostnames without schemes and empty strings.
d4809b3 to
b06967c
Compare
ssncferreira
left a comment
There was a problem hiding this comment.
LGTM with some nits, but nothing blocking
| if isDirectGeminiOpenAIEndpoint(baseURL) { | ||
| return true | ||
| } | ||
| return isCoderAIBridgeEndpoint(baseURL) && isGeminiModelID(modelID) |
There was a problem hiding this comment.
I think this else branch is unnecessary, because whenever requests go through AI Gateway, it already performs the patch. IIUC, the flow would be:
- Coder agent makes a request to Gemini via AIBridge
isCoderAIBridgeEndpoint(baseURL) && isGeminiModelID(modelID)returnstrue- AIBridge removes the though signature due to the SDK
- AIBridge does
chatCompletionRequestOptionswhich patches the request body to include the thought signature again.
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 withFunction call is missing a thought_signature.This PR:
extra_contentis dropped before the upstream body is patched