Repository navigation
[PR] fix(google): promote stripped proxy call thoughtSignature onto surviving sibling functionCall (#6079, #5252) - #6103
Conversation
(ai: qwen/qwen3.8-27b) Reviewed PR #6103 against current master — checked out the head, ran typecheck + build + targeted suites locally, and read all three source changes end-to-end while tracing the proxy-vs-real classification path. Root cause — confirmed correct. Gemini attaches Coverage is comprehensive, not a band-aid. All three surfaces where the unsigned part can reappear are handled: streaming SSE settle ( Local verification: Two minor observations (non-blocking, follow-up):
Verdict: quality meets the bar — correct root cause, complete surface coverage, tests with teeth (exact-value assertions, both directions), clean focused diff, no dead code, comments explain why. Recommend merge once the remaining test shards go green; the head is ~10 commits behind master with no conflicts, so a rebase is worth doing first. 八问(结构化审核结论)
判定:可合(等剩余 test shard 转绿后由你合并;建议先 rebase,head 落后 master 约 10 提交但无冲突)。 |
c76985d to
4550055
Compare
…ing sibling functionCall (ranxianglei#6079, ranxianglei#5252)
4550055 to
39145bd
Compare
(ai: vllm-qwen/qwen3.8-27b) Merge conflict resolved — the PR is now mergeable. What I did this run:
Live state this run: 八问
判定:冲突已解决,PR 现为 mergeable( |
(ai: vllm-qwen/qwen3.8-27b) Re-trigger handled (auto-fix lane, attempt 1/3). Per status-freshness I re-verified the live state this run instead of trusting the prior run's result. Conflict — still resolved, nothing to rebase. PR head CI — in progress, no failures. Last mirror snapshot: 4 success, 13 skipped, 2 in_progress, 11 queued, 0 failures. The test shards (windows/ubuntu × node 22/24) and static-gates are running. Honest caveat (status-freshness): the mirror check cache has not refreshed since 07:52Z across this run's polls, so I could not confirm the final CI verdict live — I'm reporting the last known state, not asserting green. There is no Action this run: none required. My primary task (conflict resolution) was completed in the prior run and remains resolved; CI is not red, so there is no red-CI fix to make. The PR is now waiting on CI completion + human approval (merge is human-only). On the next trigger, if CI has come back red I'll take the red-CI fix. 八问:
判定: 冲突已解决且本轮复检确认保持解决,master 未前进,CI 无失败(进行中;最终结论因镜像缓存未刷新暂无法 live 确认,如实上报而非断言绿)。本轮无需修复,等待 CI 完成 + 人工批准。 |
(ai: vllm-qwen/qwen3.8-27b) Re-verified live this run (status-freshness). The state has moved forward since the last check: CI is now GREEN (all completed). Mirror cache (fresh, 08:38Z): 16 success, 14 skipped, 0 failures, 0 in_progress, 0 queued. The test shards (windows/ubuntu × node 22/24) and static-gates all passed on head Conflict: still resolved. PR head Action this run: none required. Both of my responsibilities are clear:
The PR is now ready for human approval (merge is human-only). Nothing left for the CI lane to do. 八问:
判定: 冲突已解决且保持解决,CI 全绿(16 success / 14 skipped / 0 failures),本轮无需修复;PR 已具备合并条件,等待人工批准(合并权在 owner)。 |
Fixes #6079
Fixes #5252
Root Cause
In Gemini's function-calling protocol (Gemini 2.5 / 3 / 4), when a model turn emits multiple parallel
functionCallparts, only the firstfunctionCallpart in the turn carriesthoughtSignature; subsequent siblingfunctionCallparts in the same turn omitthoughtSignature. On the next turn, Gemini validates that the leadingfunctionCallpart of the replayed model turn carriesthoughtSignature.When a model turn emits a
biliproxy tool call (e.g.compressat call index0, carryingthoughtSignature) in parallel with a real client tool call (e.g.write_file/run_command/herdr_pane_readat call index1, withoutthoughtSignature):biliexecutescompressserver-side and strips itsfunctionCallpart from the response stream forwarded to the client.functionCallpart becomes the leadingfunctionCallpart in the client's recorded model turn, butbilidiscarded thethoughtSignaturethat lived on the strippedcompresspart instead of promoting it onto the first surviving realfunctionCallpart.functionCallpart (which is why#5277's inbound/outboundgoogleToCore/coreToGoogleaudit saw the part arrive unsigned from the client), and Gemini rejects the request with:400 INVALID_ARGUMENT: Function call is missing a thought_signature in functionCall parts.What Changed
src/loop/adapter-google.ts(settleToolCalls+filterRealCallChunk):functionCallpart lacks athoughtSignaturewhile a stripped proxyfunctionCallpart carried one, promotes thatthoughtSignatureonto the first surviving realfunctionCallpart — both when calls arrive in the same SSE chunk and when they arrive across separate SSE chunks in the same turn, as well as on the structuredtool_callevent."acp_cache","acp_retrieve","acp_rule", and"acp_read_original_image"toPROXY_TOOL_SETso all server-side proxy tools matchisProxyToolForduring Google stream settle.src/stream-google.ts(rewriteGoogleJsonSteps):functionCallpart'sthoughtSignatureonto the first surviving realfunctionCallpart in non-streaming JSON responses (or onto the synthesized marker part on a proxy-only turn when no other part carries athoughtSignature).src/loop/core.ts(runCompressLoop):role: "model"turns on the re-request wire), propagatesroundProxySignatureto sibling proxy tool-call records that lacked their ownsignature.tests/adapter-google.test.ts:[compress (signed), write_file (unsigned), read_file (unsigned)], separate-chunk parallel[compress (signed), write_file (unsigned)], negative control preserving a real call's ownthoughtSignature, non-streamingrewriteGoogleJsonResponseAsync, fullrunCompressLoop+ next-turngoogleToCore→coreToGooglereplay after folding, and parallel proxy-only re-requests.Verification
origin/master(v0.1.213)npm run typecheck: cleannpm run build: clean (dist-import-annotations: OK,dist-external: OK)node --import tsx --test tests/adapter-google.test.ts: 18/18 passing