Files
paseo/packages/server/src/server/agent/providers/opencode/tool-call-mapper.test.ts
T
Mohamed Boudra 3163e212fb Attach pull request feedback to chat (#1400)
* feat(app): add pull request panel context actions

Attach pull request comments, reviews, and failed check details to chat context. The PR panel now lives in its own module so the activity, query, and context policies have one home.

* fix(server): mark capped check details as truncated

* fix(app): preserve concurrent PR panel attachments

* fix(server): preserve annotation truncation state

* fix(app): keep matrix check attachments distinct

* fix(app): streamline pull request context attachments

* fix(app): simplify pull request feedback rendering

* feat(app): group pull request review threads

* fix(app): tidy pull request panel boundaries

* fix: use worktree cwd for dev services

* fix(app): keep git query keys UI-free

* feat(app): collapse resolved comments and add chat actions to headers

* fix: address PR feedback and Playwright flakes

* fix(app): hide check chat action without attachment scope

* fix(server): dedupe inline review timeline comments
2026-06-12 13:43:01 +08:00

543 lines
15 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { mapOpencodeToolCall } from "./tool-call-mapper.js";
function expectMapped<T>(item: T | null): T {
expect(item).toBeTruthy();
if (!item) {
throw new Error("Expected mapped tool call");
}
return item;
}
describe("opencode tool-call mapper", () => {
it("maps running shell calls", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "shell",
callId: "opencode-call-1",
status: "running",
input: { command: "pwd", cwd: "/tmp/repo" },
output: null,
}),
);
expect(item.status).toBe("running");
expect(item.error).toBeNull();
expect(item.callId).toBe("opencode-call-1");
expect(item.detail?.type).toBe("shell");
if (item.detail?.type === "shell") {
expect(item.detail.command).toBe("pwd");
}
});
it("maps running known tool variants with detail for early summaries", () => {
const readItem = expectMapped(
mapOpencodeToolCall({
toolName: "read_file",
callId: "opencode-running-read",
status: "running",
input: { file_path: "README.md" },
output: null,
}),
);
expect(readItem.detail).toEqual({
type: "read",
filePath: "README.md",
});
const writeItem = expectMapped(
mapOpencodeToolCall({
toolName: "write_file",
callId: "opencode-running-write",
status: "running",
input: { file_path: "src/new.ts" },
output: null,
}),
);
expect(writeItem.detail).toEqual({
type: "write",
filePath: "src/new.ts",
});
const editItem = expectMapped(
mapOpencodeToolCall({
toolName: "apply_patch",
callId: "opencode-running-edit",
status: "running",
input: { file_path: "src/index.ts" },
output: null,
}),
);
expect(editItem.detail).toEqual({
type: "edit",
filePath: "src/index.ts",
});
const searchItem = expectMapped(
mapOpencodeToolCall({
toolName: "web_search",
callId: "opencode-running-search",
status: "running",
input: { query: "opencode mapper" },
output: null,
}),
);
expect(searchItem.detail).toEqual({
type: "search",
query: "opencode mapper",
toolName: "web_search",
});
const globItem = expectMapped(
mapOpencodeToolCall({
toolName: "glob",
callId: "opencode-running-glob",
status: "running",
input: { pattern: "**/*.md" },
output: null,
}),
);
expect(globItem.detail).toEqual({
type: "search",
query: "**/*.md",
toolName: "glob",
});
const grepItem = expectMapped(
mapOpencodeToolCall({
toolName: "grep",
callId: "opencode-running-grep",
status: "running",
input: { pattern: "sendCorrelatedSessionRequest" },
output: null,
}),
);
expect(grepItem.detail).toEqual({
type: "search",
query: "sendCorrelatedSessionRequest",
toolName: "grep",
});
});
it("maps completed read calls", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "read_file",
callId: "opencode-call-2",
status: "complete",
input: { file_path: "README.md" },
output: { content: "hello" },
}),
);
expect(item.status).toBe("completed");
expect(item.error).toBeNull();
expect(item.callId).toBe("opencode-call-2");
expect(item.detail?.type).toBe("read");
if (item.detail?.type === "read") {
expect(item.detail.filePath).toBe("README.md");
expect(item.detail.content).toBe("hello");
}
});
it("preserves read content from array/object output variants", () => {
const arrayContent = expectMapped(
mapOpencodeToolCall({
toolName: "read_file",
callId: "opencode-read-array",
status: "completed",
input: { file_path: "README.md" },
output: {
content: [
{ type: "output_text", text: "alpha" },
{ type: "output_text", output: "beta" },
],
},
}),
);
expect(arrayContent.detail?.type).toBe("read");
if (arrayContent.detail?.type === "read") {
expect(arrayContent.detail.content).toBe("alpha\nbeta");
}
const objectContent = expectMapped(
mapOpencodeToolCall({
toolName: "read_file",
callId: "opencode-read-object",
status: "completed",
input: { file_path: "README.md" },
output: {
data: {
content: { type: "output_text", text: "gamma" },
},
},
}),
);
expect(objectContent.detail?.type).toBe("read");
if (objectContent.detail?.type === "read") {
expect(objectContent.detail.content).toBe("gamma");
}
});
it("unwraps OpenCode XML read output into file content", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "read",
callId: "opencode-read-xml",
status: "completed",
input: { filePath: "/Users/moboudra/dev/paseo/docs/release.md" },
output: [
"<path>/Users/moboudra/dev/paseo/docs/release.md</path>",
"<type>file</type>",
"<content>",
"1: # Release",
"2:",
"3: All workspaces share one version and release together.",
"</content>",
].join("\n"),
}),
);
expect(item.detail).toEqual({
type: "read",
filePath: "/Users/moboudra/dev/paseo/docs/release.md",
content: [
"1: # Release",
"2:",
"3: All workspaces share one version and release together.",
].join("\n"),
});
});
it("maps failed calls with required error", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "shell",
callId: "opencode-call-3",
status: "error",
input: { command: "false" },
output: null,
error: "command failed",
}),
);
expect(item.status).toBe("failed");
expect(item.error).toBe("command failed");
expect(item.callId).toBe("opencode-call-3");
});
it("maps write/edit/search known variants into canonical detail", () => {
const writeItem = expectMapped(
mapOpencodeToolCall({
toolName: "write_file",
callId: "opencode-write-1",
status: "completed",
input: { file_path: "src/new.ts", content: "const x = 1;" },
output: null,
}),
);
expect(writeItem.detail?.type).toBe("write");
if (writeItem.detail?.type === "write") {
expect(writeItem.detail.filePath).toBe("src/new.ts");
}
const editItem = expectMapped(
mapOpencodeToolCall({
toolName: "apply_patch",
callId: "opencode-edit-1",
status: "completed",
input: { file_path: "src/index.ts", diff: "@@\\n-old\\n+new\\n" },
output: null,
}),
);
expect(editItem.detail?.type).toBe("edit");
if (editItem.detail?.type === "edit") {
expect(editItem.detail.filePath).toBe("src/index.ts");
expect(editItem.detail.unifiedDiff).toContain("@@");
}
const searchItem = expectMapped(
mapOpencodeToolCall({
toolName: "web_search",
callId: "opencode-search-1",
status: "completed",
input: { query: "opencode mapper" },
output: null,
}),
);
expect(searchItem.detail).toEqual({
type: "search",
query: "opencode mapper",
toolName: "web_search",
});
});
it("maps completed write calls with OpenCode success text into canonical detail", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "write",
callId: "opencode-write-success-text",
status: "completed",
input: {
filePath:
"/Users/moboudra/dev/paseo/.dev/paseo-home/worktrees/1luy0po7/cold-ladybug/dummy.txt",
content: "hello world\n",
},
output: "Wrote file successfully.",
}),
);
expect(item.detail).toEqual({
type: "write",
filePath:
"/Users/moboudra/dev/paseo/.dev/paseo-home/worktrees/1luy0po7/cold-ladybug/dummy.txt",
content: "hello world\n",
});
});
it("maps completed edit calls with OpenCode camelCase input and success text", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "edit",
callId: "opencode-edit-camel",
status: "completed",
input: {
filePath: "/Users/moboudra/dev/paseo/packages/website/src/data/agent-pages.ts",
oldString: 'metaTitle: "Junie agent Mobile and Desktop App, Open Source"',
newString: 'metaTitle: "Junie Agent Mobile and Desktop App, Open Source"',
},
output: "Edit applied successfully.",
}),
);
expect(item.detail).toEqual({
type: "edit",
filePath: "/Users/moboudra/dev/paseo/packages/website/src/data/agent-pages.ts",
oldString: 'metaTitle: "Junie agent Mobile and Desktop App, Open Source"',
newString: 'metaTitle: "Junie Agent Mobile and Desktop App, Open Source"',
});
});
it("maps skill calls to plain text detail with the loaded skill name", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "skill",
callId: "opencode-skill-1",
status: "completed",
input: { name: "diagnose" },
output: '<skill_content name="diagnose"># Skill: diagnose</skill_content>',
}),
);
expect(item.detail).toEqual({
type: "plain_text",
label: "diagnose",
icon: "sparkles",
text: '<skill_content name="diagnose"># Skill: diagnose</skill_content>',
});
});
it("maps completed grep calls with string output into search detail", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "grep",
callId: "opencode-grep-string-output-1",
status: "completed",
input: { pattern: "todowrite" },
output: "Found 2 matches\nsrc/file.ts:\n Line 1: todowrite",
}),
);
expect(item.detail).toEqual({
type: "search",
query: "todowrite",
toolName: "grep",
content: "Found 2 matches\nsrc/file.ts:\n Line 1: todowrite",
numFiles: 0,
});
});
it("maps apply_patch patchText payloads into edit detail", () => {
const patchText = [
"*** Begin Patch",
"*** Delete File: /tmp/repo/src/App.tsx",
"*** End Patch",
].join("\n");
const item = expectMapped(
mapOpencodeToolCall({
toolName: "apply_patch",
callId: "opencode-apply-patch-text-1",
status: "completed",
input: { patchText },
output: "Success. Updated the following files:\nD /tmp/repo/src/App.tsx",
}),
);
expect(item.detail.type).toBe("edit");
expect(item.detail).toEqual({
type: "edit",
filePath: "/tmp/repo/src/App.tsx",
unifiedDiff: [
"diff --git a//tmp/repo/src/App.tsx b//tmp/repo/src/App.tsx",
"--- a//tmp/repo/src/App.tsx",
"+++ /dev/null",
].join("\n"),
});
});
it("maps unknown tools to unknown detail with raw payloads", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "my_custom_tool",
callId: "opencode-call-4",
status: "completed",
input: { foo: "bar" },
output: { ok: true },
}),
);
expect(item.status).toBe("completed");
expect(item.error).toBeNull();
expect(item.detail).toEqual({
type: "unknown",
input: { foo: "bar" },
output: { ok: true },
});
});
it("maps running task calls with subagent input to sub_agent detail", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "task",
callId: "opencode-task-running",
status: "running",
input: {
subagent_type: "explore",
description: "Explore agent-tools codebase",
},
output: null,
}),
);
expect(item.status).toBe("running");
expect(item.error).toBeNull();
expect(item.detail).toEqual({
type: "sub_agent",
subAgentType: "explore",
description: "Explore agent-tools codebase",
log: "",
actions: [],
});
});
it("maps completed task calls with final output log to sub_agent detail", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "task",
callId: "opencode-task-completed",
status: "completed",
input: {
subagent_type: "explore",
description: "Explore agent-tools codebase",
},
output: { result: "Found the CLI entrypoint and provider registry." },
}),
);
expect(item.status).toBe("completed");
expect(item.error).toBeNull();
expect(item.detail).toEqual({
type: "sub_agent",
subAgentType: "explore",
description: "Explore agent-tools codebase",
log: "Found the CLI entrypoint and provider registry.",
actions: [],
});
});
it("extracts child session ids from completed task output", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "task",
callId: "opencode-task-completed-with-id",
status: "completed",
input: {
subagent_type: "explore",
description: "Explore current directory",
},
output: "task_id: ses_2268db431ffe299vL1bbot8R7Z\n\n<task_result>done</task_result>",
}),
);
expect(item.detail).toEqual({
type: "sub_agent",
subAgentType: "explore",
description: "Explore current directory",
childSessionId: "ses_2268db431ffe299vL1bbot8R7Z",
log: "task_id: ses_2268db431ffe299vL1bbot8R7Z\n\n<task_result>done</task_result>",
actions: [],
});
});
it("maps aborted task calls with preserved error log to sub_agent detail", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "task",
callId: "opencode-task-aborted",
status: "aborted",
input: {
subagent_type: "explore",
description: "Explore agent-tools codebase",
},
output: null,
error: "Tool execution aborted",
}),
);
expect(item.status).toBe("failed");
expect(item.error).toBe("Tool execution aborted");
expect(item.detail).toEqual({
type: "sub_agent",
subAgentType: "explore",
description: "Explore agent-tools codebase",
log: "Tool execution aborted",
actions: [],
});
});
it("does not apply cross-provider speak normalization in opencode mapper", () => {
const item = expectMapped(
mapOpencodeToolCall({
toolName: "paseo_voice.speak",
callId: "opencode-call-voice-1",
status: "completed",
input: { text: "Voice response from OpenCode." },
output: { ok: true },
}),
);
expect(item.name).toBe("paseo_voice.speak");
expect(item.detail).toEqual({
type: "unknown",
input: { text: "Voice response from OpenCode." },
output: { ok: true },
});
});
it("drops tool calls when callId is missing", () => {
const item = mapOpencodeToolCall({
toolName: "read_file",
callId: null,
status: "completed",
input: { file_path: "README.md" },
output: { content: "hello" },
});
expect(item).toBeNull();
});
});