Skip to content

fix(acp): parent tool calls to an assistant message for projection - #81

Merged
Yevanchen merged 1 commit into
mainfrom
fix/acp-tool-parent-message
Jul 25, 2026
Merged

fix(acp): parent tool calls to an assistant message for projection#81
Yevanchen merged 1 commit into
mainfrom
fix/acp-tool-parent-message

Conversation

@Yevanchen

Copy link
Copy Markdown
Collaborator

Summary

Fixes the OpenCode (ACP) runtime rendering every tool call as a generic tool card in the platform chat UI (reported in YEF-891 with OpenCode + DeepSeek V4 Pro + an MCP server).

Root cause. ACP tool.call.updated payloads carried no parentMessageId/messageId, unlike the Claude runtime (agent-sdk-event-writer.ts sets parentMessageId) and the OpenAI runtime (app-server-item-events.ts sets messageId). The platform projection (mosoo/pkgs/runtime-events/src/session-event-projection.ts toolCallStartEvent) drops TOOL_CALL_START — the only AG-UI event carrying toolCallName — when both ids are missing. The tool result still lands, so ag-ui-session's appendToolResult creates an orphaned segment with the hardcoded fallback name "tool". Every OpenCode tool call (MCP and built-in, any model) therefore rendered as tool, losing its real name. The item.started event does carry the title, but it is not projected to AG-UI at all.

Changes

  • AcpAssistantTranscriptState: when a tool call opens without an active assistant message, start an anonymous assistant message first (mirroring the Claude runtime's ensureMessageStarted), so tools are parented to an assistant message rather than the user's prompt message. Applied to both the tool_call/tool_call_update path and the permission-request path.
  • AcpToolEventState.patch: stamp a sticky parentMessageId (first observed parent wins for the call's lifetime) on every tool.call.updated payload. The field is already part of the wire contract (runtime-events.ts validates parentMessageId as an optional string on tool.call.updated).
  • Updated the three ACP fixture cases and translator test expectations for the new message.started/message.completed envelope.

Verification

Fed identical tool events through the real platform pipeline (appRuntimeEventToAgUiSessionEventsapplyAgUiEventsToSessionLiveState from the mosoo repo):

=== OLD (broken) ACP payloads
AG-UI events: TOOL_CALL_ARGS, TOOL_CALL_ARGS, TOOL_CALL_RESULT, TOOL_CALL_END
tool segments: ["assistant:tool_result:tool"]

=== NEW (fixed) ACP payloads
AG-UI events: TEXT_MESSAGE_START, TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_RESULT, TOOL_CALL_END, TEXT_MESSAGE_END
tool segments: ["assistant:tool_use:deepwiki_read_wiki_structure","assistant:tool_result:deepwiki_read_wiki_structure"]

Also reproduced the full platform path locally (opencode-ai 1.18.4 opencode acp --pure, OPENCODE_CONFIG_CONTENT with deepseek/deepseek-v4-pro, MCP servers passed via ACP session/new, including through a faithful replica of the platform MCP proxy): OpenCode emits title/kind on every tool update, tool calls execute, turns end with end_turn — confirming the name loss happened in the event pipeline, not in OpenCode/DeepSeek/MCP transport.

  • bun test: 1074 pass / 1 pre-existing failure (acp-file-system.test.ts /tmp vs /private/tmp realpath on macOS, fails on a clean checkout too)
  • tsc --noEmit: clean
  • vp lint: clean

ACP tool.call.updated payloads carried no parentMessageId/messageId,
unlike the Claude and OpenAI runtimes. The platform session event
projection drops TOOL_CALL_START when both are missing, so every
OpenCode tool call lost its name and rendered as an orphaned generic
"tool" card in the chat UI.

Start an anonymous assistant message when a tool call opens without an
active one (mirroring the Claude runtime's ensureMessageStarted) and
stamp the sticky parentMessageId on every tool.call.updated payload.
@Yevanchen
Yevanchen merged commit f6567a3 into main Jul 25, 2026
2 checks passed
@Yevanchen
Yevanchen deleted the fix/acp-tool-parent-message branch July 25, 2026 05:53
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