Skip to content

fix(plugin): 修复 OpenClaw 工具调用消息格式不兼容导致的 toolResult 孤儿错误#1632

Merged
Mijamind719 merged 2 commits intovolcengine:mainfrom
824156793:fix/openclaw-toolcall-canonicalization
Apr 22, 2026
Merged

fix(plugin): 修复 OpenClaw 工具调用消息格式不兼容导致的 toolResult 孤儿错误#1632
Mijamind719 merged 2 commits intovolcengine:mainfrom
824156793:fix/openclaw-toolcall-canonicalization

Conversation

@824156793
Copy link
Copy Markdown
Contributor

@824156793 824156793 commented Apr 22, 2026

问题

OpenViking 作为 OpenClaw 上下文引擎插件时,组装的上下文中工具调用块使用的是旧版 toolUse 类型(带 input 字段),而 OpenClaw Responses API 重放路径只识别规范格式 toolCall(带 arguments 字段),导致多轮对话中工具调用后出现 toolResult 孤儿错误。

根因

convertToAgentMessages() 输出:

  • type: "toolUse" + input 字段 → OpenClaw Responses 重放不识别
  • 导致 toolResult 消息配对失败,触发验证错误

修改内容

1. convertToAgentMessages — 输出改为规范格式

  • toolUse + inputtoolCall + arguments
  • 变量重命名:toolUseBlockstoolCallBlocks
  • 注释同步更新

2. 新增 canonicalizeAssistantBlock() + canonicalizeAgentMessages()

  • sanitizeToolUseResultPairing 之前对 assembled context 做规范化
  • 映射:toolUse / functionCall / tool_calltoolCall
  • 映射:input / toolInputarguments
  • 映射:toolUseId / toolCallIdid(保留已有值)
  • 确保 toolResult 消息携带 toolCallIdtoolName

3. messageDigest — 类型守卫更新

  • b.type === "toolUse"b.type === "toolCall"

向后兼容

三种旧格式输入(toolUsefunctionCalltool_call)仍然被接受,组装时统一规范化为 toolCall 输出。

测试验证

在运行中的 OpenClaw 实例上验证:应用修复后重新启用 OpenViking 插件,多轮对话中的工具调用不再产生孤儿错误。

The OpenClaw Responses API replay path only recognizes `toolCall` blocks
(with `arguments` field), but the OpenViking context engine plugin was
emitting `toolUse` blocks (with `input` field) from its assembled context.
This mismatch caused `toolResult` orphan errors on subsequent turns after
tool use.

Changes:
- convertToAgentMessages: emit toolCall+arguments instead of
  toolUse+input, matching the OpenClaw canonical format
- Add canonicalizeAgentMessages() to normalize legacy/variant block
  formats (toolUse, functionCall, tool_call) into toolCall before
  sanitizeToolUseResultPairing
- Add canonicalizeAssistantBlock() for per-block normalization with
  id/name/arguments field mapping
- messageDigest: update type guard from toolUse to toolCall

This preserves backward compatibility on input (old toolUse blocks are
still accepted and normalized) while ensuring output always matches the
current OpenClaw Responses API contract.
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Moss seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@824156793 824156793 changed the title fix(plugin): canonicalize tool message format for OpenClaw Responses API fix(plugin): 修复 OpenClaw 工具调用消息格式不兼容导致的 toolResult 孤儿错误 Apr 22, 2026
@github-actions
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🏅 Score: 90
🧪 No relevant tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ No major issues detected

@github-actions
Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix unnecessary changed flag for toolResult

The reference check nextMsg !== msg will always be true because nextMsg is a new
object created via spread, even when no fields actually changed. This causes
unnecessary array copies. Instead, explicitly check if the new fields differ from
the original before marking as changed.

examples/openclaw-plugin/context-engine.ts [453-473]

 if (msg.role === "toolResult") {
   const raw = msg as Record<string, unknown>;
   const toolCallId =
     (typeof raw.toolCallId === "string" && raw.toolCallId) ||
     (typeof raw.toolUseId === "string" && raw.toolUseId) ||
     undefined;
   const toolName =
     typeof raw.toolName === "string" && raw.toolName.trim()
       ? raw.toolName.trim()
       : "unknown";
 
-  const nextMsg = {
-    ...msg,
-    ...(toolCallId ? { toolCallId } : {}),
-    toolName,
-  } as AgentMessage;
+  const hasToolCallIdChanged = toolCallId !== raw.toolCallId;
+  const hasToolNameChanged = toolName !== raw.toolName;
 
-  if (nextMsg !== msg) {
+  if (hasToolCallIdChanged || hasToolNameChanged) {
     changed = true;
+    return {
+      ...msg,
+      ...(toolCallId ? { toolCallId } : {}),
+      toolName,
+    } as AgentMessage;
   }
-  return nextMsg;
+  return msg;
 }
Suggestion importance[1-10]: 6

__

Why: The original nextMsg !== msg check always returns true since nextMsg is a new object via spread, causing unnecessary array copies even when no fields changed. The improved code explicitly checks for actual changes in toolCallId and toolName, avoiding spurious updates and improving efficiency.

Low

@enjiew
Copy link
Copy Markdown

enjiew commented Apr 22, 2026

持续关注

@wlff123
Copy link
Copy Markdown
Contributor

wlff123 commented Apr 22, 2026

Important

  1. toolResult.toolName 被过早固化为 "unknown",破坏旧消息兼容回填
    文件: examples/openclaw-plugin/context-engine.ts:453
    问题: canonicalizeAgentMessages() 在旧 toolResult 缺少 toolName 时,直接写入 "unknown"。这样会挡住后续 examples/openclaw-plugin/session-transcript-repair.ts:445 基于配对到的 assistant tool call 回填真实 toolName 的逻辑。
    影响: toolCall/toolResult 配对本身仍能成功,但老 transcript 的工具名会退化成 "unknown",不符合提交说明里“保留向后兼容”的目标。
    修改建议: 这里不要主动兜底成 "unknown";仅在原消息已有合法 toolName 时保留/trim,否则保持缺失,让下游 pairing repair 用关联的 tool call 名称补全。

Minor

  1. 测试和说明文档仍在断言旧格式,需同步到 canonical 格式
    文件: examples/openclaw-plugin/tests/ut/tool-round-trip.test.ts:96, examples/openclaw-plugin/tests/ut/context-engine-assemble.test.ts:141, examples/openclaw-plugin/README.md:113, examples/openclaw-plugin/README_CN.md:113
    问题: 代码已切换为 toolCall + arguments,但测试/文档还写 toolUse + input。
    影响: 容易误判为逻辑回归,也会让后续维护者理解错误。
    修改建议: 将预期统一更新为 canonical 输出;兼容性描述改为“输入兼容旧格式,输出统一规范格式”。

- Remove 'unknown' fallback for toolResult.toolName in canonicalizeAgentMessages();
  preserve undefined to let downstream normalizeToolResultName() backfill from paired toolCall
- Remove 'unknown' fallback for tool_name in convertToAgentMessages() structured path;
  only set toolName on toolResult when the value exists
- Only include toolName on toolResult when present (not always)
- Update test expectations: toolUse → toolCall, input → arguments
- Update README/README_CN: describe canonical output format with backward-compat note
@824156793
Copy link
Copy Markdown
Contributor Author

感谢 review,两条反馈都已在 40ac50e 中修复合并:

  1. toolName 兜底 "unknown" 的问题:已改为缺失时不设字段,保留 undefined,让下游 normalizeToolResultName() 用配对的 toolCall.name 回填。convertToAgentMessages()tool_name 缺失时同样不再写 "unknown",toolResult 只在 toolName 有值时才写入。

  2. 测试和文档格式同步:全部从 toolUse/input 更新为 toolCall/arguments,README/README_CN 补充说明输入兼容旧格式、输出统一规范格式。

25 个 ut 测试全部通过。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants