Skip to content

ACP: initial tool_call notification should include tool arguments in rawInput #31313

@SXongin

Description

@SXongin

Description

When OpenCode communicates via ACP protocol, the initial tool_call notification (sent when a tool starts) always sends an empty rawInput: {}, even though the actual tool arguments are available shortly after in the subsequent tool_call_update.

This breaks ACP clients that display tool call details from the initial notification — they can only show the tool name (e.g. "Read", "Skill") but not the tool arguments (e.g. which file is being read, which skill is being invoked).

Root Cause

The issue is in packages/opencode/src/acp/tool.ts:121-130 — the pendingToolCall() function only receives toolCallId and toolName, so it cannot include real tool input:

export function pendingToolCall(input: { readonly toolCallId: string; readonly toolName: string }): ToolCall {
  return {
    toolCallId: input.toolCallId,
    title: input.toolName,
    kind: toToolKind(input.toolName),
    status: "pending",
    locations: [],
    rawInput: {},  // always empty
  }
}

And in event.ts:323-335, toolStart() calls pendingToolCall() with just these two fields, without passing part.state.input:

private async toolStart(sessionId: string, part: ToolPart) {
    if (this.toolStarts.has(part.callID)) return
    this.toolStarts.add(part.callID)
    await this.input.connection.sessionUpdate({
      sessionId,
      update: {
        sessionUpdate: "tool_call",
        ...pendingToolCall({
          toolCallId: part.callID,
          toolName: part.tool,        // part.state.input is available but not passed
        }),
      },
    })
  }

Meanwhile, the runningToolUpdate() function (line 132) does include real input:

export function runningToolUpdate(input: {
  readonly toolCallId: string
  readonly toolName: string
  readonly state: RunningToolState  // has input: ToolInput
}): ToolCallUpdate {
  return {
    ...
    rawInput: input.state.input,  // real data!
  }
}

But this comes as a separate tool_call_update notification, so clients that only display data from the initial tool_call miss it.

Proposed Fix

Two changes:

  1. In pendingToolCall(): accept an optional state parameter with the tool input, so the initial notification can include it when available.

  2. In toolStart(): pass part.state.input (and any other available state) to pendingToolCall() so the initial notification carries actual tool arguments.

For tools that go from pending directly to completed (e.g., very fast tool calls), this ensures the client never misses the tool arguments.

ACP Protocol Context

The ACP spec defines rawInput as rawInput?: unknown (optional) in ToolCall, so sending it or not is valid either way. However, including real input when available is better for interoperability — ACP clients that rely on the initial notification for tool call display benefit immediately, and it costs nothing for clients that ignore it.

Impact

Without this fix, ACP clients (like cc-connect, Windsurf, etc.) can only display:

  • "Read" — but not which file is being read
  • "Skill" — but not which skill is being invoked
  • "Bash" — but not what command is being executed

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions