fix(acp): show live execute tool details
This commit is contained in:
@@ -69,6 +69,16 @@ interface CommandContainer {
|
||||
command?: unknown;
|
||||
}
|
||||
|
||||
interface EvalCellContainer {
|
||||
cells?: unknown;
|
||||
}
|
||||
|
||||
interface EvalCellLike {
|
||||
language?: unknown;
|
||||
title?: unknown;
|
||||
code?: unknown;
|
||||
}
|
||||
|
||||
interface PatternContainer {
|
||||
pattern?: unknown;
|
||||
}
|
||||
@@ -435,11 +445,43 @@ function getToolExecutionEndArgs(
|
||||
}
|
||||
|
||||
function buildToolStartContent(toolName: string, args: unknown): ToolCallContent[] {
|
||||
if (!isCommandToolName(toolName)) {
|
||||
return [];
|
||||
const text = buildToolStartText(toolName, args);
|
||||
return text ? [textToolCallContent(text)] : [];
|
||||
}
|
||||
|
||||
function buildToolStartText(toolName: string, args: unknown): string | undefined {
|
||||
if (isCommandToolName(toolName)) {
|
||||
const command = extractStringProperty<CommandContainer>(args, "command");
|
||||
return command ? limitText(`$ ${command}`) : undefined;
|
||||
}
|
||||
const command = extractStringProperty<CommandContainer>(args, "command");
|
||||
return command ? [textToolCallContent(`$ ${command}`)] : [];
|
||||
if (toolName === "eval") {
|
||||
return buildEvalStartText(args);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function buildEvalStartText(args: unknown): string | undefined {
|
||||
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
||||
return undefined;
|
||||
}
|
||||
const cells = (args as EvalCellContainer).cells;
|
||||
if (!Array.isArray(cells) || cells.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const lines: string[] = [];
|
||||
for (const cell of cells) {
|
||||
if (typeof cell !== "object" || cell === null || Array.isArray(cell)) {
|
||||
continue;
|
||||
}
|
||||
const language = extractStringProperty<EvalCellLike>(cell, "language") ?? "?";
|
||||
const title = extractStringProperty<EvalCellLike>(cell, "title");
|
||||
const code = extractStringProperty<EvalCellLike>(cell, "code");
|
||||
if (!code) {
|
||||
continue;
|
||||
}
|
||||
lines.push(title ? `[${language}] ${title}` : `[${language}]`, code);
|
||||
}
|
||||
return lines.length > 0 ? limitText(lines.join("\n")) : undefined;
|
||||
}
|
||||
|
||||
function mergeToolUpdateContent(startContent: ToolCallContent[], resultContent: ToolCallContent[]): ToolCallContent[] {
|
||||
@@ -465,6 +507,14 @@ function isCommandToolName(toolName: string): boolean {
|
||||
}
|
||||
|
||||
function buildToolTitle(toolName: string, args: unknown, intent: string | undefined): string {
|
||||
if (isCommandToolName(toolName)) {
|
||||
const commandText = buildToolStartText(toolName, args);
|
||||
if (commandText) return commandText;
|
||||
}
|
||||
if (toolName === "eval") {
|
||||
const evalText = buildEvalStartText(args);
|
||||
if (evalText) return evalText;
|
||||
}
|
||||
const trimmedIntent = intent?.trim();
|
||||
if (trimmedIntent) {
|
||||
return trimmedIntent;
|
||||
|
||||
@@ -188,6 +188,128 @@ describe("ACP event mapper", () => {
|
||||
expect(doneUpdates).toEqual([]);
|
||||
});
|
||||
|
||||
it("preserves command text when a new command tool is started", () => {
|
||||
const updates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
type: "tool_execution_start",
|
||||
toolCallId: "tc-command-start",
|
||||
toolName: "bash",
|
||||
args: { command: "npm run check" },
|
||||
} as AgentSessionEvent,
|
||||
"session-1",
|
||||
);
|
||||
|
||||
expect(updates).toHaveLength(1);
|
||||
expectAcpNotifications(updates);
|
||||
const update = updates[0]!.update as {
|
||||
sessionUpdate: string;
|
||||
content?: Array<{ type: string; content?: { type: string; text?: string } }>;
|
||||
};
|
||||
expect(update.sessionUpdate).toBe("tool_call");
|
||||
expect(update.content).toContainEqual({ type: "content", content: { type: "text", text: "$ npm run check" } });
|
||||
});
|
||||
|
||||
it("uses command text for a new command tool even when intent is generic", () => {
|
||||
const updates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
type: "tool_execution_start",
|
||||
toolCallId: "tc-command-start-generic-intent",
|
||||
toolName: "bash",
|
||||
args: { command: "echo hi" },
|
||||
intent: "Running command",
|
||||
} as AgentSessionEvent,
|
||||
"session-1",
|
||||
);
|
||||
|
||||
expect(updates).toHaveLength(1);
|
||||
expectAcpNotifications(updates);
|
||||
const update = updates[0]!.update as {
|
||||
title: string;
|
||||
content?: Array<{ type: string; content?: { type: string; text?: string } }>;
|
||||
};
|
||||
expect(update.title).toBe("$ echo hi");
|
||||
expect(update.content).toContainEqual({ type: "content", content: { type: "text", text: "$ echo hi" } });
|
||||
});
|
||||
|
||||
it("preserves eval source when a new eval tool is started", () => {
|
||||
const updates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
type: "tool_execution_start",
|
||||
toolCallId: "tc-eval-start",
|
||||
toolName: "eval",
|
||||
args: { cells: [{ language: "js", title: "sum", code: "return 1 + 1;" }] },
|
||||
intent: "sum",
|
||||
} as AgentSessionEvent,
|
||||
"session-1",
|
||||
);
|
||||
|
||||
expect(updates).toHaveLength(1);
|
||||
expectAcpNotifications(updates);
|
||||
const update = updates[0]!.update as {
|
||||
sessionUpdate: string;
|
||||
title: string;
|
||||
kind?: string;
|
||||
status?: string;
|
||||
rawInput?: unknown;
|
||||
content?: Array<{ type: string; content?: { type: string; text?: string } }>;
|
||||
};
|
||||
expect(update.sessionUpdate).toBe("tool_call");
|
||||
expect(update.title).toBe("[js] sum\nreturn 1 + 1;");
|
||||
expect(update.kind).toBe("execute");
|
||||
expect(update.status).toBe("pending");
|
||||
expect(update.rawInput).toEqual({ cells: [{ language: "js", title: "sum", code: "return 1 + 1;" }] });
|
||||
expect(update.content).toContainEqual({
|
||||
type: "content",
|
||||
content: { type: "text", text: "[js] sum\nreturn 1 + 1;" },
|
||||
});
|
||||
});
|
||||
|
||||
it("builds eval source content from valid cells only", () => {
|
||||
const updates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
type: "tool_execution_start",
|
||||
toolCallId: "tc-eval-mixed-cells",
|
||||
toolName: "eval",
|
||||
args: {
|
||||
cells: [null, {}, { code: "" }, { code: "x" }, { language: "py", code: "y" }],
|
||||
},
|
||||
intent: "evaluating",
|
||||
} as AgentSessionEvent,
|
||||
"session-1",
|
||||
);
|
||||
|
||||
expect(updates).toHaveLength(1);
|
||||
expectAcpNotifications(updates);
|
||||
const update = updates[0]!.update as {
|
||||
title: string;
|
||||
content?: Array<{ type: string; content?: { type: string; text?: string } }>;
|
||||
};
|
||||
expect(update.title).toBe("[?]\nx\n[py]\ny");
|
||||
expect(update.content).toEqual([{ type: "content", content: { type: "text", text: "[?]\nx\n[py]\ny" } }]);
|
||||
});
|
||||
|
||||
it("limits eval source before emitting visible tool-call text", () => {
|
||||
const source = "x".repeat(4_100);
|
||||
const updates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
type: "tool_execution_start",
|
||||
toolCallId: "tc-eval-long-source",
|
||||
toolName: "eval",
|
||||
args: { cells: [{ language: "js", code: source }] },
|
||||
} as AgentSessionEvent,
|
||||
"session-1",
|
||||
);
|
||||
|
||||
expect(updates).toHaveLength(1);
|
||||
expectAcpNotifications(updates);
|
||||
const update = updates[0]!.update as {
|
||||
title: string;
|
||||
content?: Array<{ type: string; content?: { type: string; text?: string } }>;
|
||||
};
|
||||
expect(update.title).toHaveLength(4_000);
|
||||
expect(update.title.endsWith("…")).toBe(true);
|
||||
expect(update.content).toEqual([{ type: "content", content: { type: "text", text: update.title } }]);
|
||||
});
|
||||
it("emits a diff ToolCallContent for each per-file edit result", () => {
|
||||
const updates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
@@ -547,7 +669,7 @@ describe("ACP event mapper", () => {
|
||||
};
|
||||
expect(update.sessionUpdate).toBe("tool_call");
|
||||
expect(update.toolCallId).toBe("toolu_bash_1");
|
||||
expect(update.title).toBe("bash: npm run check");
|
||||
expect(update.title).toBe("$ npm run check");
|
||||
expect(update.kind).toBe("execute");
|
||||
expect(update.status).toBe("pending");
|
||||
expect(update.rawInput).toEqual({ command: "npm run check", cwd: "/repo" });
|
||||
@@ -676,7 +798,7 @@ describe("ACP event mapper", () => {
|
||||
expect(update).toMatchObject({
|
||||
sessionUpdate: "tool_call",
|
||||
toolCallId: "toolu_replay_1",
|
||||
title: "bash: npm test",
|
||||
title: "$ npm test",
|
||||
kind: "execute",
|
||||
status: "completed",
|
||||
rawInput: { command: "npm test", cwd: "/repo" },
|
||||
@@ -741,7 +863,7 @@ describe("ACP event mapper", () => {
|
||||
expect(replayArgs.args).toBe(rawArgs);
|
||||
expectAcpStructure(zSessionNotification, { sessionId: "session-1", update });
|
||||
expect(update).toMatchObject({
|
||||
title: "bash: bun test",
|
||||
title: "$ bun test",
|
||||
status: "completed",
|
||||
rawInput: rawArgs,
|
||||
content: [{ type: "content", content: { type: "text", text: "$ bun test" } }],
|
||||
|
||||
Reference in New Issue
Block a user