Skip to content

fix(proxy): rescue leaked tool-call XML + honor Anthropic tool_choice#7

Merged
walaqi merged 2 commits into
mainfrom
feat/tool-call-xml-leak-and-tool-choice
Jun 15, 2026
Merged

fix(proxy): rescue leaked tool-call XML + honor Anthropic tool_choice#7
walaqi merged 2 commits into
mainfrom
feat/tool-call-xml-leak-and-tool-choice

Conversation

@walaqi

@walaqi walaqi commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Summary

本 PR 移植自上游 Kiro-Go PR Quorinex#114,包含两个独立改动(各一个 commit):

1. fix: 救回泄漏成文本的工具调用 XML

Kiro 后端偶发故障,会把本该走结构化 toolUseEvent 通道的工具调用,错误地当作普通文本塞进 assistantResponseEvent。泄漏出来的是 Anthropic 风格的 <function_calls>/<invoke>/<parameter> XML(<function_calls> 有时损坏成纯文本 count),且可能跨多个流式帧分片。结果是:原始 XML 泄漏成可见脏文本,客户端拿不到结构化 tool_use → 工具不执行、Agent 任务中断。

修复:新增有状态的跨帧过滤器 proxy/tool_leak_filter.go,接入 parseEventStream

  • 跨帧缓冲文本,分离正常文本与泄漏的工具 XML
  • 解析泄漏的 <invoke> 为结构化 tool_use,还原 bool/int/float/null 类型(字符串保留原始空白)
  • 帧边界扣留可能被截断的半截标签
  • 流末与已下发的结构化工具去重(同名同参丢弃,避免重复执行),其余作为合成 tool_use 注入救回
  • 环境变量开关:KIRO_TOOL_LEAK_FIX=off 回退直通,KIRO_TOOL_LEAK_DEBUG=1 打印调试日志

2. feat: 支持 Anthropic tool_choice(Claude 路径)

Kiro 后端无原生 tool_choice 概念,本改动在 ClaudeToKiro 中模拟其语义:

  • none → 不向 Kiro 发送任何工具
  • any → 照常发工具,并在用户消息追加指令强制模型调用至少一个工具
  • tool → 同上,并把指定工具名解析为 sanitized Kiro 名后点名要求调用

Test plan

  • go build ./... 通过
  • go vet ./... 通过
  • 新增 9 个 leak-fix 测试 + 3 个 tool_choice 测试全部通过
  • 完整 proxy 套件仅剩两个已知的、与本改动无关的历史失败用例

Notes

  • 上游 PR 基线的 convertClaudeTools 为单参;本仓库已演进为双参(带 ToolDescReplaceRules),合并时已适配,工具描述替换行为不变。
  • 上游 TS 实现中的 async/SSE 背压改动在 Go 同步回调模型下不需要,未移植。

🤖 Generated with Claude Code

walaqi and others added 2 commits June 15, 2026 18:58
Kiro's backend occasionally emits a model's tool call as plain text in
assistantResponseEvent (the raw <function_calls>/<invoke>/<parameter>
XML, sometimes with the <function_calls> open tag corrupted to "count"),
split across streaming frames. The old path passed text through
untouched, so the XML leaked to users as visible text and the client
never received a structured tool_use -- the tool was never executed and
the agent task stalled.

Add a stateful cross-frame filter (proxy/tool_leak_filter.go) that:
- buffers assistant text and splits normal text from leaked tool XML
- restores bool/int/float/null types while preserving raw strings
- holds back partial trailing tags at frame boundaries
- at stream end, dedups rescued tools against structured toolUseEvents
  already seen (same name+input dropped) and injects the rest as
  synthetic tool_use blocks

Wired into parseEventStream and gated by KIRO_TOOL_LEAK_FIX (default on,
"off" to bypass) and KIRO_TOOL_LEAK_DEBUG=1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Kiro's backend has no native tool_choice concept, so translate the
Anthropic semantics during ClaudeToKiro:
- "none": omit all tools so the model cannot call any (text-only turn)
- "any": keep tools and append a backend directive requiring at least
  one tool call this turn
- {"type":"tool","name":X}: append a directive naming the specific tool,
  resolving the client name to the sanitized Kiro tool name

Adds claudeToolChoiceTypeAndName (handles string / json.RawMessage / map
/ arbitrary shapes), buildClaudeToolChoiceDirective,
resolveKiroToolChoiceName, and appendBackendDirective, plus tests for
the any / tool / none cases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@walaqi walaqi merged commit 922aaa3 into main Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant