feat: Add tool names to tool calls (#1128)

Supports the new tool call name RFD
This commit is contained in:
Ben Brandt
2026-09-14 04:11:52 -07:00
committed by GitHub
parent 4deace4037
commit 67c7c9b117
5 changed files with 59 additions and 3 deletions

View File

@@ -71,7 +71,9 @@ with `Reason: `. The SDK `description` operation subtitle is not copied there.
The permission request carries the same standard ACP tool information used for normal tool updates:
- `name`, `kind`, `title`, `content`, and `locations` come from `toolInfoFromToolUse`;
- `name` is the SDK tool name (for example `Read` or `mcp__server__tool`), matching the standard
`name` field on the initial `tool_call` update;
- `kind`, `title`, `content`, and `locations` come from `toolInfoFromToolUse`;
- `status` is `pending`;
- `rawInput` is the original SDK input object;
- `blockedPath` is appended to `locations` when it is valid and not already present;

View File

@@ -1252,7 +1252,9 @@ type ProviderConfig = {
export type ToolUpdateMeta = {
contextCompaction?: ContextCompactionMetadata;
claudeCode?: {
/* The name of the tool that was used in Claude Code. */
/* The name of the tool that was used in Claude Code. Also carried as the
standard ACP `name` field on the initial `tool_call`; kept here so every
`tool_call_update` stays self-describing for clients that key off it. */
toolName: string;
/* A human-readable title supplied by Claude Code for the tool call. */
title?: string;
@@ -9358,7 +9360,10 @@ function resolveSkillPath(skillName: string, cwd?: string): string | undefined {
* refine) and the permission flow (`ensureToolCallEmitted`), so they can't
* drift. The initial `tool_call` carries `status: "pending"` and, for Bash, the
* `terminal_info` _meta that the later `terminal_output`/`terminal_exit`
* updates key off of; a refining `tool_call_update` carries neither. */
* updates key off of, and the programmatic tool `name` (ACP's tool-call-name
* RFD); a refining `tool_call_update` carries none of these. `name` is set
* once at first report — on a v1 update, omitting it means "unchanged", and
* the tool behind a `toolCallId` never changes. */
function toolCallNotification(
toolUse: { id: string; name: string; input: unknown },
rawInput: unknown,
@@ -9384,6 +9389,7 @@ function toolCallNotification(
} satisfies ToolUpdateMeta,
toolCallId: toolUse.id,
sessionUpdate: "tool_call",
name: toolUse.name,
rawInput,
status: "pending",
...toolInfoFromToolUse(toolUse, supportsTerminalOutput, cwd),

View File

@@ -422,6 +422,9 @@ function failedControlFallback(
...terminal.update,
sessionUpdate: "tool_call",
status: "failed",
// The synthesized tool_call is this call's first report, so give it
// the standard `name` the initial one would have carried.
...(typeof claudeMeta?.toolName === "string" ? { name: claudeMeta.toolName } : {}),
title:
typeof terminal.update.title === "string" && terminal.update.title.length > 0
? terminal.update.title

View File

@@ -140,6 +140,7 @@ describe("NativeSubagentRuntime lifecycle", () => {
update: {
sessionUpdate: "tool_call",
toolCallId: "agent-tool",
name: "Agent",
title: "Agent",
status: "failed",
_meta: { claudeCode: { toolName: "Agent" } },

View File

@@ -3420,6 +3420,50 @@ describe("Skill tool rendering", () => {
});
});
// ACP tool-call-name RFD: the initial tool_call carries the programmatic
// tool name as the standard `name` field, alongside `_meta.claudeCode.toolName`.
describe("standard `name` on tool_call notifications", () => {
it.each([
["Read", { file_path: "/tmp/a.ts" }],
["Bash", { command: "ls" }],
["mcp__github__list_issues", { repo: "acp" }],
])("reports %s as `name` on the initial tool_call", (name, input) => {
const notifications = toAcpNotifications(
[{ type: "tool_use", id: "toolu_name", name, input }] as any,
"assistant",
"test-session",
{},
{} as AcpClient,
mockLogger,
);
expect(notifications[0]?.update).toMatchObject({
sessionUpdate: "tool_call",
toolCallId: "toolu_name",
name,
_meta: { claudeCode: { toolName: name } },
});
});
it("does not re-send `name` on the refining tool_call_update", () => {
const notifications = toAcpNotifications(
[
{ type: "tool_use", id: "toolu_refine", name: "Read", input: { file_path: "/tmp/a" } },
] as any,
"assistant",
"test-session",
{},
{} as AcpClient,
mockLogger,
{ emittedToolCalls: new Set(["toolu_refine"]) },
);
expect(notifications[0]?.update).toMatchObject({
sessionUpdate: "tool_call_update",
toolCallId: "toolu_refine",
});
expect((notifications[0]?.update as any).name).toBeUndefined();
});
});
describe("_meta.claudeCode.skillPath", () => {
const skillMeta = (skill: string, cwd?: string) =>
(