fix(acp): 显示命令工具调用详情
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
SessionNotification,
|
||||
SessionUpdate,
|
||||
ToolCall,
|
||||
ToolCallContent,
|
||||
ToolCallLocation,
|
||||
ToolKind,
|
||||
@@ -144,18 +145,13 @@ export function mapAgentSessionEventToAcpSessionUpdates(
|
||||
case "message_end":
|
||||
return mapAssistantMessageEnd(event, sessionId, options);
|
||||
case "tool_execution_start": {
|
||||
const update: SessionUpdate = {
|
||||
sessionUpdate: "tool_call",
|
||||
const update = buildToolCallStartUpdate({
|
||||
toolCallId: event.toolCallId,
|
||||
title: buildToolTitle(event.toolName, event.args, event.intent),
|
||||
kind: mapToolKind(event.toolName),
|
||||
status: "pending",
|
||||
rawInput: event.args,
|
||||
};
|
||||
const locations = extractToolLocations(event.args, options.cwd);
|
||||
if (locations.length > 0) {
|
||||
update.locations = locations;
|
||||
}
|
||||
toolName: event.toolName,
|
||||
args: event.args,
|
||||
intent: event.intent,
|
||||
cwd: options.cwd,
|
||||
});
|
||||
return [toSessionNotification(sessionId, update)];
|
||||
}
|
||||
case "tool_execution_update": {
|
||||
@@ -312,6 +308,45 @@ function mapTodoStatus(status: TodoStatus): "pending" | "in_progress" | "complet
|
||||
return todoStatusMap[status];
|
||||
}
|
||||
|
||||
export function buildToolCallStartUpdate(input: {
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
args: unknown;
|
||||
intent?: string;
|
||||
cwd?: string;
|
||||
status?: "pending" | "completed";
|
||||
}): SessionUpdate {
|
||||
const update: ToolCall & { sessionUpdate: "tool_call" } = {
|
||||
sessionUpdate: "tool_call",
|
||||
toolCallId: input.toolCallId,
|
||||
title: buildToolTitle(input.toolName, input.args, input.intent),
|
||||
kind: mapToolKind(input.toolName),
|
||||
status: input.status ?? "pending",
|
||||
rawInput: input.args,
|
||||
};
|
||||
const content = buildToolStartContent(input.toolName, input.args);
|
||||
if (content.length > 0) {
|
||||
update.content = content;
|
||||
}
|
||||
const locations = extractToolLocations(input.args, input.cwd);
|
||||
if (locations.length > 0) {
|
||||
update.locations = locations;
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
function buildToolStartContent(toolName: string, args: unknown): ToolCallContent[] {
|
||||
if (!isCommandToolName(toolName)) {
|
||||
return [];
|
||||
}
|
||||
const command = extractStringProperty<CommandContainer>(args, "command");
|
||||
return command ? [textToolCallContent(`$ ${command}`)] : [];
|
||||
}
|
||||
|
||||
function isCommandToolName(toolName: string): boolean {
|
||||
return toolName === "bash" || toolName === "shell" || toolName === "exec";
|
||||
}
|
||||
|
||||
function buildToolTitle(toolName: string, args: unknown, intent: string | undefined): string {
|
||||
const trimmedIntent = intent?.trim();
|
||||
if (trimmedIntent) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import path from "node:path";
|
||||
import type { SessionNotification } from "@agentclientprotocol/sdk";
|
||||
import { zSessionNotification } from "@agentclientprotocol/sdk/dist/schema/zod.gen.js";
|
||||
import { mapAgentSessionEventToAcpSessionUpdates } from "../src/modes/acp/acp-event-mapper";
|
||||
@@ -241,6 +242,84 @@ describe("ACP event mapper", () => {
|
||||
expect(update.sessionUpdate).toBe("tool_call_update");
|
||||
expect(update.content).toEqual([{ type: "terminal", terminalId: "term-42" }]);
|
||||
});
|
||||
it("shows bash commands in visible tool call content", () => {
|
||||
const updates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
type: "tool_execution_start",
|
||||
toolCallId: "toolu_bash_1",
|
||||
toolName: "bash",
|
||||
args: { command: "npm run check", cwd: "/repo" },
|
||||
} as AgentSessionEvent,
|
||||
"session-1",
|
||||
);
|
||||
|
||||
expect(updates).toHaveLength(1);
|
||||
expectAcpNotifications(updates);
|
||||
const update = updates[0]!.update as {
|
||||
sessionUpdate: string;
|
||||
toolCallId?: string;
|
||||
title?: string;
|
||||
kind?: string;
|
||||
status?: string;
|
||||
rawInput?: unknown;
|
||||
content?: unknown;
|
||||
};
|
||||
expect(update.sessionUpdate).toBe("tool_call");
|
||||
expect(update.toolCallId).toBe("toolu_bash_1");
|
||||
expect(update.title).toBe("bash: npm run check");
|
||||
expect(update.kind).toBe("execute");
|
||||
expect(update.status).toBe("pending");
|
||||
expect(update.rawInput).toEqual({ command: "npm run check", cwd: "/repo" });
|
||||
expect(update.content).toEqual([{ type: "content", content: { type: "text", text: "$ npm run check" } }]);
|
||||
});
|
||||
|
||||
it("does not add command text content to non-command tool starts", () => {
|
||||
const updates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
type: "tool_execution_start",
|
||||
toolCallId: "toolu_read_1",
|
||||
toolName: "read",
|
||||
args: { path: "README.md" },
|
||||
} as AgentSessionEvent,
|
||||
"session-1",
|
||||
);
|
||||
|
||||
expect(updates).toHaveLength(1);
|
||||
expectAcpNotifications(updates);
|
||||
const update = updates[0]!.update as {
|
||||
sessionUpdate: string;
|
||||
title?: string;
|
||||
kind?: string;
|
||||
rawInput?: unknown;
|
||||
locations?: { path: string }[];
|
||||
content?: unknown;
|
||||
};
|
||||
expect(update.sessionUpdate).toBe("tool_call");
|
||||
expect(update.title).toBe("read: README.md");
|
||||
expect(update.kind).toBe("read");
|
||||
expect(update.rawInput).toEqual({ path: "README.md" });
|
||||
expect(update.locations).toEqual([{ path: "README.md" }]);
|
||||
expect("content" in update).toBe(false);
|
||||
});
|
||||
it("resolves tool_execution_start locations against mapper cwd", () => {
|
||||
const updates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
type: "tool_execution_start",
|
||||
toolCallId: "toolu_read_cwd",
|
||||
toolName: "read",
|
||||
args: { path: "src/file.ts" },
|
||||
} as AgentSessionEvent,
|
||||
"session-1",
|
||||
{ cwd: "/repo" },
|
||||
);
|
||||
|
||||
expect(updates).toHaveLength(1);
|
||||
expectAcpNotifications(updates);
|
||||
const update = updates[0]!.update as { sessionUpdate: string; locations?: { path: string }[]; content?: unknown };
|
||||
expect(update.sessionUpdate).toBe("tool_call");
|
||||
expect(update.locations).toEqual([{ path: path.resolve("/repo", "src/file.ts") }]);
|
||||
expect("content" in update).toBe(false);
|
||||
});
|
||||
it("emits distinct locations for move-style path arguments", () => {
|
||||
const updates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user