Summary / 概述
With a google-genai provider (gemini-3.7-flash, a thinking model), a session that calls tools fails on the follow-up request (the step right after a tool call) with one of two upstream 400 errors:
使用 google-genai 协议(gemini-3.7-flash 思考模型)时,调用过工具的会话在后续请求(工具调用后的下一步)会命中以下两个上游 400 错误之一:
Function call is missing a thought_signature in functionCall parts. ... function call default_api:<tool>, position N
Requests ending with a model turn are not supported.
Reproducible on v0.36.0 with the agent-core-v2 engine (default). Error #1 masks #2: after #1 is fixed, long tool-calling sessions then hit #2.
在 v0.36.0(默认的 agent-core-v2 引擎)上可稳定复现。错误 #1 会掩盖 #2:修复 #1 后,长工具调用会话会继续命中 #2。
Root causes (both client-side) / 根因(均在客户端)
1. tool.call loop event drops ToolCall.extras / tool.call 循环事件丢失 ToolCall.extras
packages/agent-core-v2/src/agent/loop/loopService.ts: executeStepTools records the event with only {toolCallId, name, args}, dropping extras (which carries thought_signature_b64). Context history is rebuilt from loop events via the fold (contextMemory/loopEventFold.ts), so the reconstructed assistant message loses the signature; the next request can't echo thoughtSignature on the functionCall part. The v1 engine (packages/agent-core/src/loop/tool-call.ts) passes extras correctly at both emit sites; the v2 port missed it. LoopRecordedEvent already declares the field.
packages/agent-core-v2/src/agent/loop/loopService.ts 中 executeStepTools 记录 tool.call 事件时只传了 {toolCallId, name, args},丢掉了 extras(其中携带 thought_signature_b64)。上下文历史通过 fold(contextMemory/loopEventFold.ts)从事件重建,恢复出的 assistant 消息因此丢失签名,后续请求无法在 functionCall part 上回传 thoughtSignature,被上游拒绝。v1 引擎(packages/agent-core/src/loop/tool-call.ts)的两处事件都在正确传递 extras,v2 移植时遗漏。LoopRecordedEvent 类型本就声明了该字段。
2. User-message merge puts text after functionResponse / 用户消息合并把文本放在 functionResponse 之后
packages/kosong/src/providers/google-genai.ts (and the agent-core-v2 copy): mergeConsecutiveUserMessages folds a trailing plain user message (e.g. an injected system reminder) into the preceding tool-result Content as [functionResponse, text]. Gemini 3.x rejects a request whose last Content starts with functionResponse followed by text. Verified empirically against the upstream: [functionResponse], [text, functionResponse], and separate messages all pass (200); only trailing [functionResponse, text] fails with error #2 verbatim.
packages/kosong/src/providers/google-genai.ts(以及 agent-core-v2 中的对应实现):mergeConsecutiveUserMessages 会把紧随 tool-result 消息之后的普通用户消息(例如注入的 system reminder)合并成 [functionResponse, text]。Gemini 3.x 拒绝"最后一条 Content 以 functionResponse 开头、其后跟文本"的请求。已对上游实测:[functionResponse]、[text, functionResponse]、以及分开的两条消息均通过(200);只有结尾为 [functionResponse, text] 时逐字复现错误 #2。
Fix / 修复
-
loopService.ts: pass extras (looked up from response.message.toolCalls) into the tool.call event.
loopService.ts:从 response.message.toolCalls 反查 extras 并写入 tool.call 事件。
-
google-genai merge callback (v1 + v2): when merging a tool-result Content with a following text-only user Content, keep the text first ([text, functionResponse]).
google-genai 合并回调(v1 + v2):当 tool-result Content 与后续纯文本用户消息合并时,保持文本在前([text, functionResponse])。
Validation / 验证
-
New regression test in packages/agent-core-v2/test/agent/loop/loop.test.ts (extras survive tool.call → fold → context); reverting the fix makes it fail with expected undefined to deeply equal {thought_signature_b64}.
在 packages/agent-core-v2/test/agent/loop/loop.test.ts 新增回归测试(extras 经 tool.call → fold → context 保留);回退修复后该测试精确失败(expected undefined to deeply equal {thought_signature_b64})。
-
Order assertion added to packages/kosong/test/google-genai.test.ts.
在 packages/kosong/test/google-genai.test.ts 补充了顺序断言。
-
Replayed a real failing session's wire log through fold → projector → content assembly: the trailing Content is now [text, functionResponse, text], and the same structure is accepted by the upstream (200), while the pre-fix [functionResponse, text] reproduced the exact 400.
用真实失败会话的 wire 日志重放 fold → projector → 内容组装:结尾 Content 现为 [text, functionResponse, text],该结构被上游接受(200);修复前的 [functionResponse, text] 则逐字复现 400。
-
kosong 85 tests + agent-core-v2 loop 49 tests pass; both packages typecheck.
kosong 85 个测试 + agent-core-v2 loop 49 个测试全部通过;两个包类型检查通过。
Environment / 环境
- kimi-code CLI 0.36.0, google-genai protocol, gemini-3.7-flash (thinking model) via a Gemini-compatible gateway.
kimi-code CLI 0.36.0,google-genai 协议,gemini-3.7-flash(思考模型,经 Gemini 兼容网关)。
Branch ready / 分支已就绪
SeleneXX/kimi-code → fix/gemini-tool-calls (two commits; happy to open a PR once the issue is up).
SeleneXX/kimi-code → fix/gemini-tool-calls(两个 commit;issue 创建后可开 PR)。
Summary / 概述
With a
google-genaiprovider (gemini-3.7-flash, a thinking model), a session that calls tools fails on the follow-up request (the step right after a tool call) with one of two upstream 400 errors:使用
google-genai协议(gemini-3.7-flash思考模型)时,调用过工具的会话在后续请求(工具调用后的下一步)会命中以下两个上游 400 错误之一:Function call is missing a thought_signature in functionCall parts. ... function call default_api:<tool>, position NRequests ending with a model turn are not supported.Reproducible on v0.36.0 with the agent-core-v2 engine (default). Error #1 masks #2: after #1 is fixed, long tool-calling sessions then hit #2.
在 v0.36.0(默认的 agent-core-v2 引擎)上可稳定复现。错误 #1 会掩盖 #2:修复 #1 后,长工具调用会话会继续命中 #2。
Root causes (both client-side) / 根因(均在客户端)
1.
tool.callloop event dropsToolCall.extras/tool.call循环事件丢失ToolCall.extraspackages/agent-core-v2/src/agent/loop/loopService.ts:executeStepToolsrecords the event with only{toolCallId, name, args}, droppingextras(which carriesthought_signature_b64). Context history is rebuilt from loop events via the fold (contextMemory/loopEventFold.ts), so the reconstructed assistant message loses the signature; the next request can't echothoughtSignatureon the functionCall part. The v1 engine (packages/agent-core/src/loop/tool-call.ts) passesextrascorrectly at both emit sites; the v2 port missed it.LoopRecordedEventalready declares the field.packages/agent-core-v2/src/agent/loop/loopService.ts中executeStepTools记录tool.call事件时只传了{toolCallId, name, args},丢掉了extras(其中携带thought_signature_b64)。上下文历史通过 fold(contextMemory/loopEventFold.ts)从事件重建,恢复出的 assistant 消息因此丢失签名,后续请求无法在 functionCall part 上回传thoughtSignature,被上游拒绝。v1 引擎(packages/agent-core/src/loop/tool-call.ts)的两处事件都在正确传递extras,v2 移植时遗漏。LoopRecordedEvent类型本就声明了该字段。2. User-message merge puts text after
functionResponse/ 用户消息合并把文本放在functionResponse之后packages/kosong/src/providers/google-genai.ts(and the agent-core-v2 copy):mergeConsecutiveUserMessagesfolds a trailing plain user message (e.g. an injected system reminder) into the preceding tool-result Content as[functionResponse, text]. Gemini 3.x rejects a request whose last Content starts withfunctionResponsefollowed by text. Verified empirically against the upstream:[functionResponse],[text, functionResponse], and separate messages all pass (200); only trailing[functionResponse, text]fails with error #2 verbatim.packages/kosong/src/providers/google-genai.ts(以及 agent-core-v2 中的对应实现):mergeConsecutiveUserMessages会把紧随 tool-result 消息之后的普通用户消息(例如注入的 system reminder)合并成[functionResponse, text]。Gemini 3.x 拒绝"最后一条 Content 以functionResponse开头、其后跟文本"的请求。已对上游实测:[functionResponse]、[text, functionResponse]、以及分开的两条消息均通过(200);只有结尾为[functionResponse, text]时逐字复现错误 #2。Fix / 修复
loopService.ts: passextras(looked up fromresponse.message.toolCalls) into thetool.callevent.loopService.ts:从response.message.toolCalls反查extras并写入tool.call事件。google-genai merge callback (v1 + v2): when merging a tool-result Content with a following text-only user Content, keep the text first (
[text, functionResponse]).google-genai 合并回调(v1 + v2):当 tool-result Content 与后续纯文本用户消息合并时,保持文本在前(
[text, functionResponse])。Validation / 验证
New regression test in
packages/agent-core-v2/test/agent/loop/loop.test.ts(extras survive tool.call → fold → context); reverting the fix makes it fail withexpected undefined to deeply equal {thought_signature_b64}.在
packages/agent-core-v2/test/agent/loop/loop.test.ts新增回归测试(extras 经 tool.call → fold → context 保留);回退修复后该测试精确失败(expected undefined to deeply equal {thought_signature_b64})。Order assertion added to
packages/kosong/test/google-genai.test.ts.在
packages/kosong/test/google-genai.test.ts补充了顺序断言。Replayed a real failing session's wire log through fold → projector → content assembly: the trailing Content is now
[text, functionResponse, text], and the same structure is accepted by the upstream (200), while the pre-fix[functionResponse, text]reproduced the exact 400.用真实失败会话的 wire 日志重放 fold → projector → 内容组装:结尾 Content 现为
[text, functionResponse, text],该结构被上游接受(200);修复前的[functionResponse, text]则逐字复现 400。kosong85 tests + agent-core-v2 loop 49 tests pass; both packages typecheck.kosong85 个测试 + agent-core-v2 loop 49 个测试全部通过;两个包类型检查通过。Environment / 环境
kimi-code CLI 0.36.0,google-genai 协议,gemini-3.7-flash(思考模型,经 Gemini 兼容网关)。
Branch ready / 分支已就绪
SeleneXX/kimi-code→fix/gemini-tool-calls(two commits; happy to open a PR once the issue is up).SeleneXX/kimi-code→fix/gemini-tool-calls(两个 commit;issue 创建后可开 PR)。