Update files

This commit is contained in:
Mohamed Boudra
2026-02-08 22:37:52 +07:00
parent 8317b0d89b
commit 0176614467
36 changed files with 4098 additions and 5114 deletions
@@ -0,0 +1,68 @@
import { describe, expect, it } from "vitest";
import { buildToolCallDisplayModel, formatToolCallError } from "./tool-call-display";
describe("tool-call-display", () => {
it("builds display model from canonical shell detail", () => {
const display = buildToolCallDisplayModel({
name: "shell",
detail: {
type: "shell",
command: "npm test",
},
});
expect(display).toEqual({
kind: "execute",
displayName: "Shell",
summary: "npm test",
});
});
it("builds display model from canonical read detail", () => {
const display = buildToolCallDisplayModel({
name: "read_file",
detail: {
type: "read",
filePath: "/tmp/repo/src/index.ts",
},
cwd: "/tmp/repo",
});
expect(display).toEqual({
kind: "read",
displayName: "Read",
summary: "src/index.ts",
});
});
it("uses metadata summary for task tool calls", () => {
const display = buildToolCallDisplayModel({
name: "task",
metadata: {
subAgentActivity: "Running tests",
},
});
expect(display).toEqual({
kind: "agent",
displayName: "Task",
summary: "Running tests",
});
});
it("falls back to humanized tool name for unknown tools", () => {
const display = buildToolCallDisplayModel({
name: "custom_tool_name",
});
expect(display).toEqual({
kind: "tool",
displayName: "Custom Tool Name",
});
});
it("formats non-string errors", () => {
expect(formatToolCallError({ message: "boom" })).toBe('{\n "message": "boom"\n}');
});
});
+211
View File
@@ -0,0 +1,211 @@
import { z } from "zod";
import type { ToolCallDetail } from "@server/server/agent/agent-sdk-types";
export type ToolCallKind =
| "read"
| "edit"
| "write"
| "execute"
| "search"
| "agent"
| "tool"
| "thinking";
type SummaryParams = {
name: string;
detail?: ToolCallDetail;
metadata?: Record<string, unknown>;
cwd?: string;
};
export type ToolCallDisplayModel = {
kind: ToolCallKind;
displayName: string;
summary?: string;
};
const TOOL_CALL_DETAIL_SCHEMA: z.ZodType<ToolCallDetail> = z.discriminatedUnion("type", [
z.object({
type: z.literal("shell"),
command: z.string(),
cwd: z.string().optional(),
output: z.string().optional(),
exitCode: z.number().nullable().optional(),
}),
z.object({
type: z.literal("read"),
filePath: z.string(),
content: z.string().optional(),
offset: z.number().optional(),
limit: z.number().optional(),
}),
z.object({
type: z.literal("edit"),
filePath: z.string(),
oldString: z.string().optional(),
newString: z.string().optional(),
unifiedDiff: z.string().optional(),
}),
z.object({
type: z.literal("write"),
filePath: z.string(),
content: z.string().optional(),
}),
z.object({
type: z.literal("search"),
query: z.string(),
}),
]);
const TOOL_CALL_DISPLAY_INPUT_SCHEMA = z.object({
name: z.string().min(1),
detail: TOOL_CALL_DETAIL_SCHEMA.optional(),
metadata: z.record(z.unknown()).optional(),
cwd: z.string().optional(),
});
function toTitleCase(words: string): string {
return words
.split(" ")
.filter((segment) => segment.length > 0)
.map((segment) => `${segment[0]?.toUpperCase() ?? ""}${segment.slice(1)}`)
.join(" ");
}
function humanizeToolName(name: string): string {
const trimmed = name.trim();
if (!trimmed) {
return name;
}
return toTitleCase(trimmed.replace(/[._-]+/g, " "));
}
export function stripCwdPrefix(filePath: string, cwd?: string): string {
if (!cwd || !filePath) return filePath;
const normalizedCwd = cwd.replace(/\\/g, "/").replace(/\/+$/, "");
const normalizedPath = filePath.replace(/\\/g, "/");
const prefix = `${normalizedCwd}/`;
if (normalizedPath.startsWith(prefix)) {
return normalizedPath.slice(prefix.length);
}
if (normalizedPath === normalizedCwd) {
return ".";
}
return filePath;
}
function buildDisplayFromDetail(detail: ToolCallDetail, cwd?: string): ToolCallDisplayModel {
switch (detail.type) {
case "shell":
return {
kind: "execute",
displayName: "Shell",
summary: detail.command,
};
case "read":
return {
kind: "read",
displayName: "Read",
summary: stripCwdPrefix(detail.filePath, cwd),
};
case "edit":
return {
kind: "edit",
displayName: "Edit",
summary: stripCwdPrefix(detail.filePath, cwd),
};
case "write":
return {
kind: "write",
displayName: "Write",
summary: stripCwdPrefix(detail.filePath, cwd),
};
case "search":
return {
kind: "search",
displayName: "Search",
summary: detail.query,
};
default:
return {
kind: "tool",
displayName: "Tool",
};
}
}
function buildDisplayWithoutDetail(params: {
toolNameLower: string;
rawName: string;
metadata?: Record<string, unknown>;
}): ToolCallDisplayModel {
if (params.toolNameLower === "task") {
const summary = params.metadata?.subAgentActivity;
return {
kind: "agent",
displayName: "Task",
summary: typeof summary === "string" && summary.trim().length > 0 ? summary : undefined,
};
}
if (params.toolNameLower === "thinking") {
return {
kind: "thinking",
displayName: "Thinking",
};
}
return {
kind: "tool",
displayName: humanizeToolName(params.rawName),
};
}
export function buildToolCallDisplayModel(params: SummaryParams): ToolCallDisplayModel {
const parsed = TOOL_CALL_DISPLAY_INPUT_SCHEMA.parse(params);
if (parsed.detail) {
return buildDisplayFromDetail(parsed.detail, parsed.cwd);
}
return buildDisplayWithoutDetail({
toolNameLower: parsed.name.trim().toLowerCase(),
rawName: parsed.name,
metadata: parsed.metadata,
});
}
export function resolveToolCallDisplayName(name: string, detail?: ToolCallDetail): string {
return buildToolCallDisplayModel({ name, detail }).displayName;
}
export function resolveToolCallKind(name: string, detail?: ToolCallDetail): ToolCallKind {
return buildToolCallDisplayModel({ name, detail }).kind;
}
export function resolveToolCallSummary(params: SummaryParams): string | undefined {
return buildToolCallDisplayModel(params).summary;
}
export function formatToolCallError(error: unknown): string | undefined {
if (error === null || error === undefined) {
return undefined;
}
if (typeof error === "string") {
return error;
}
if (
typeof error === "object" &&
"content" in (error as Record<string, unknown>) &&
typeof (error as Record<string, unknown>).content === "string"
) {
return (error as Record<string, unknown>).content as string;
}
try {
return JSON.stringify(error, null, 2);
} catch {
return String(error);
}
}
+22 -415
View File
@@ -1,428 +1,35 @@
import { describe, test, expect } from "vitest";
import { describe, expect, it } from "vitest";
import {
extractKeyValuePairs,
parseToolCallDisplay,
type ToolCallDisplayInfo,
buildLineDiff,
parseUnifiedDiff,
extractTaskEntriesFromToolCall,
} from "./tool-call-parsers";
describe("tool-call-parsers - real runtime shapes", () => {
// Real data captured from Claude agent test: "shows the command inside pending tool calls"
// Run: npx vitest run claude-agent.test.ts -t "shows the command"
describe("tool-call-parsers", () => {
it("builds line diff for text changes", () => {
const diff = buildLineDiff("old\nline\n", "new\nline\n");
test("bash tool call - input shape", () => {
// REAL shape from Claude SDK timeline event (status: pending/completed)
const bashInput = {
command: "pwd",
description: "Print working directory",
};
const pairs = extractKeyValuePairs(bashInput);
expect(pairs).toContainEqual({ key: "command", value: "pwd" });
expect(pairs).toContainEqual({ key: "description", value: "Print working directory" });
expect(diff.some((entry) => entry.type === "remove")).toBe(true);
expect(diff.some((entry) => entry.type === "add")).toBe(true);
});
test("bash tool call - output shape (completed)", () => {
// REAL shape from Claude SDK timeline event (status: completed)
// NOTE: output already has type: "command" discriminator!
const bashOutput = {
type: "command",
command: "pwd",
output: "/private/var/folders/xl/kkk9drfd3ms_t8x7rmy4z6900000gn/T/claude-agent-e2e-9tnmUm",
};
it("parses unified diff", () => {
const parsed = parseUnifiedDiff("@@\n-old\n+new\n");
const pairs = extractKeyValuePairs(bashOutput);
expect(pairs).toContainEqual({ key: "type", value: "command" });
expect(pairs).toContainEqual({ key: "command", value: "pwd" });
expect(pairs).toContainEqual({ key: "output", value: expect.stringContaining("claude-agent") });
});
});
describe("parseToolCallDisplay", () => {
test("parses completed bash tool call into shell detail", () => {
const input = { command: "pwd", description: "Print working directory" };
const output = { type: "command", command: "pwd", output: "/some/path" };
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "Bash", input, output });
expect(info.detail.type).toBe("shell");
expect(info.displayName).toBe("Shell");
if (info.detail.type === "shell") {
expect(info.detail.command).toBe("pwd");
expect(info.detail.output).toBe("/some/path");
}
expect(parsed.find((entry) => entry.type === "remove")?.content).toBe("-old");
expect(parsed.find((entry) => entry.type === "add")?.content).toBe("+new");
});
test("parses pending bash tool call into shell detail with empty output", () => {
// When tool is pending, we have input but no result yet
const input = { command: "pwd", description: "Print working directory" };
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "Bash", input });
expect(info.detail.type).toBe("shell");
expect(info.displayName).toBe("Shell");
if (info.detail.type === "shell") {
expect(info.detail.command).toBe("pwd");
expect(info.detail.output).toBe("");
}
});
test("falls back to output command when shell input is missing", () => {
const output = { type: "command", command: "pwd", output: "/some/path" };
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "Bash", output });
expect(info.summary).toBe("pwd");
expect(info.detail.type).toBe("shell");
if (info.detail.type === "shell") {
expect(info.detail.command).toBe("pwd");
expect(info.detail.output).toBe("/some/path");
}
});
test("falls back to read output path when input is missing", () => {
const info: ToolCallDisplayInfo = parseToolCallDisplay({
name: "Read",
output: {
type: "file_read",
filePath: "/some/file.txt",
content: "hello",
},
it("extracts TodoWrite task entries", () => {
const tasks = extractTaskEntriesFromToolCall("TodoWrite", {
todos: [
{ content: "Task 1", status: "pending" },
{ content: "Task 2", status: "completed" },
],
});
expect(info.summary).toBe("/some/file.txt");
expect(info.detail.type).toBe("read");
if (info.detail.type === "read") {
expect(info.detail.filePath).toBe("/some/file.txt");
expect(info.detail.content).toBe("hello");
}
});
test("strips shell + cd wrapper from command (Codex exec_command style)", () => {
const input = {
command:
'/bin/zsh -lc "cd /Users/me/dev/paseo && nl -ba packages/app/src/utils/tool-call-parsers.test.ts | sed -n \'150,260p\'"',
};
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "shell", input });
expect(info.detail.type).toBe("shell");
if (info.detail.type === "shell") {
expect(info.detail.command).toBe(
"nl -ba packages/app/src/utils/tool-call-parsers.test.ts | sed -n '150,260p'"
);
}
});
test("handles command as array", () => {
const input = { command: ["git", "status"] };
const output = { type: "command", output: "On branch main" };
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "shell", input, output });
expect(info.detail.type).toBe("shell");
expect(info.displayName).toBe("Shell");
if (info.detail.type === "shell") {
expect(info.detail.command).toBe("git status");
expect(info.detail.output).toBe("On branch main");
}
});
test("normalizes tool names - shell to Shell", () => {
const input = { command: "pwd" };
const info = parseToolCallDisplay({ name: "shell", input });
expect(info.displayName).toBe("Shell");
});
test("normalizes tool names - Bash to Shell", () => {
const input = { command: "pwd" };
const info = parseToolCallDisplay({ name: "Bash", input });
expect(info.displayName).toBe("Shell");
});
test("normalizes tool names - read_file to Read", () => {
const input = { file_path: "/some/file.txt" };
const info = parseToolCallDisplay({ name: "read_file", input });
expect(info.displayName).toBe("Read");
});
test("uses stable label for Edit in frontend", () => {
const input = { file_path: "/some/file.txt", old_string: "a", new_string: "b" };
const first = parseToolCallDisplay({ name: "Edit", input });
const second = parseToolCallDisplay({ name: "Edit", input });
expect(first.displayName).toBe("Edit");
expect(second.displayName).toBe("Edit");
});
test("normalizes tool names - paseo_voice.speak to Speak", () => {
const input = { text: "hello from namespaced speak" };
const info = parseToolCallDisplay({ name: "paseo_voice.speak", input });
expect(info.displayName).toBe("Speak");
});
test("normalizes tool names - mcp__paseo_voice__speak to Speak", () => {
const input = { text: "hello from claude mcp speak" };
const info = parseToolCallDisplay({ name: "mcp__paseo_voice__speak", input });
expect(info.displayName).toBe("Speak");
});
test("preserves unknown tool names", () => {
const input = { some_arg: "value" };
const info = parseToolCallDisplay({ name: "MyCustomTool", input });
expect(info.displayName).toBe("MyCustomTool");
});
test("does not let Task metadata override non-Task summary", () => {
const info = parseToolCallDisplay({
name: "shell",
input: { command: "pwd" },
metadata: { subAgentActivity: "Read" },
});
expect(info.summary).toBe("pwd");
});
test("parses non-command tool call into generic detail", () => {
const input = { file_path: "/some/file.txt" };
const output = { content: "file contents here", lineCount: 42 };
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "SomeTool", input, output });
expect(info.detail.type).toBe("generic");
if (info.detail.type === "generic") {
expect(info.detail.input).toContainEqual({ key: "file_path", value: "/some/file.txt" });
expect(info.detail.output).toContainEqual({ key: "content", value: "file contents here" });
expect(info.detail.output).toContainEqual({ key: "lineCount", value: "42" });
}
});
test("handles file_write output as edit", () => {
const input = { file_path: "/some/file.txt", content: "new content" };
const output = { type: "file_write", filePath: "/some/file.txt" };
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "Write", input, output });
expect(info.detail.type).toBe("edit");
expect(info.summary).toBe("/some/file.txt");
if (info.detail.type === "edit") {
expect(info.detail.filePath).toBe("/some/file.txt");
}
});
test("handles undefined input and output gracefully", () => {
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "unknown" });
expect(info.detail.type).toBe("generic");
if (info.detail.type === "generic") {
expect(info.detail.input).toEqual([]);
expect(info.detail.output).toEqual([]);
}
});
test("parses edit tool call into edit detail with old_string/new_string", () => {
const input = {
file_path: "/some/file.txt",
old_string: "const foo = 1;",
new_string: "const foo = 2;",
};
const output = {
type: "file_edit",
filePath: "/some/file.txt",
};
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "Edit", input, output });
expect(info.detail.type).toBe("edit");
if (info.detail.type === "edit") {
expect(info.detail.filePath).toBe("/some/file.txt");
expect(info.detail.oldString).toBe("const foo = 1;");
expect(info.detail.newString).toBe("const foo = 2;");
}
});
test("parses edit tool call with old_str/new_str variants", () => {
const input = {
file_path: "/some/file.txt",
old_str: "line 1",
new_str: "line 2",
};
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "Edit", input });
expect(info.detail.type).toBe("edit");
if (info.detail.type === "edit") {
expect(info.detail.filePath).toBe("/some/file.txt");
expect(info.detail.oldString).toBe("line 1");
expect(info.detail.newString).toBe("line 2");
}
});
test("parses pending edit tool call (no result yet)", () => {
const input = {
file_path: "/some/file.txt",
old_string: "old content",
new_string: "new content",
};
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "Edit", input });
expect(info.detail.type).toBe("edit");
if (info.detail.type === "edit") {
expect(info.detail.filePath).toBe("/some/file.txt");
expect(info.detail.oldString).toBe("old content");
expect(info.detail.newString).toBe("new content");
}
});
});
describe("parseToolCallDisplay - apply_patch (Codex)", () => {
test("parses apply_patch into edit detail with unified diff", () => {
const input = {
files: [
{
path: "/Users/me/dev/blankpage/editor/.tasks/578561c8.md",
kind: "update",
},
],
};
const output = {
files: [
{
path: "/Users/me/dev/blankpage/editor/.tasks/578561c8.md",
patch: "@@ -15,3 +15,2 @@\n-This task defines the **design philosophy**\n+This task defines the **updated philosophy**",
kind: "update",
},
],
message: "Success. Updated the following files:\nM .tasks/578561c8.md",
success: true,
};
const info = parseToolCallDisplay({ name: "apply_patch", input, output });
expect(info.detail.type).toBe("edit");
expect(info.displayName).toBe("Edit");
if (info.detail.type === "edit") {
expect(info.detail.filePath).toBe("/Users/me/dev/blankpage/editor/.tasks/578561c8.md");
expect(info.detail.unifiedDiff).toBe("@@ -15,3 +15,2 @@\n-This task defines the **design philosophy**\n+This task defines the **updated philosophy**");
expect(info.detail.oldString).toBe("");
expect(info.detail.newString).toBe("");
}
});
test("parses apply_patch with kind object (type/move_path) into edit detail", () => {
const input = {
files: [
{
path: "/Users/me/.paseo/worktrees/paseo/naive-zebra/packages/server/src/server/daemon-keypair.ts",
kind: { type: "update", move_path: null },
},
],
};
const output = {
files: [
{
path: "/Users/me/.paseo/worktrees/paseo/naive-zebra/packages/server/src/server/daemon-keypair.ts",
patch: "@@ -1,1 +1,1 @@\n-foo\n+bar",
kind: { type: "update", move_path: null },
},
],
success: true,
};
const info = parseToolCallDisplay({ name: "apply_patch", input, output });
expect(info.detail.type).toBe("edit");
expect(info.displayName).toBe("Edit");
if (info.detail.type === "edit") {
expect(info.detail.filePath).toBe(
"/Users/me/.paseo/worktrees/paseo/naive-zebra/packages/server/src/server/daemon-keypair.ts"
);
expect(info.detail.unifiedDiff).toBe("@@ -1,1 +1,1 @@\n-foo\n+bar");
}
});
test("prefers move_path for display but still finds patch by original path", () => {
const input = {
files: [
{
path: "/some/old-path.txt",
kind: { type: "update", move_path: "/some/new-path.txt" },
},
],
};
const output = {
files: [
{
path: "/some/old-path.txt",
patch: "@@ -1,1 +1,1 @@\n-old\n+new",
kind: { type: "update", move_path: "/some/new-path.txt" },
},
],
success: true,
};
const info = parseToolCallDisplay({ name: "apply_patch", input, output });
expect(info.detail.type).toBe("edit");
if (info.detail.type === "edit") {
expect(info.detail.filePath).toBe("/some/new-path.txt");
expect(info.detail.unifiedDiff).toBe("@@ -1,1 +1,1 @@\n-old\n+new");
}
});
test("parses pending apply_patch (no result yet)", () => {
const input = {
files: [
{
path: "/some/file.txt",
kind: "create",
},
],
};
const info = parseToolCallDisplay({ name: "apply_patch", input });
expect(info.detail.type).toBe("edit");
expect(info.displayName).toBe("Edit");
if (info.detail.type === "edit") {
expect(info.detail.filePath).toBe("/some/file.txt");
expect(info.detail.unifiedDiff).toBeUndefined();
}
});
test("handles apply_patch with multiple files (uses first file)", () => {
const input = {
files: [
{ path: "/first/file.txt", kind: "update" },
{ path: "/second/file.txt", kind: "create" },
],
};
const output = {
files: [
{ path: "/first/file.txt", patch: "@@ -1 +1 @@\n-old\n+new", kind: "update" },
{ path: "/second/file.txt", patch: "@@ -0,0 +1 @@\n+content", kind: "create" },
],
success: true,
};
const info = parseToolCallDisplay({ name: "apply_patch", input, output });
expect(info.detail.type).toBe("edit");
if (info.detail.type === "edit") {
expect(info.detail.filePath).toBe("/first/file.txt");
expect(info.detail.unifiedDiff).toBe("@@ -1 +1 @@\n-old\n+new");
}
});
});
describe("parseToolCallDisplay - read_file (Codex)", () => {
test("parses Codex read_file into read detail", () => {
const input = {
path: "/Users/me/dev/blankpage/editor/.tasks/578561c8.md",
};
const output = {
type: "read_file",
path: "/Users/me/dev/blankpage/editor/.tasks/578561c8.md",
content: "260 - Source: `**bold**|`\n261 - Action: `Shift+ArrowLeft`",
};
const info = parseToolCallDisplay({ name: "read_file", input, output });
expect(info.detail.type).toBe("read");
expect(info.displayName).toBe("Read");
if (info.detail.type === "read") {
expect(info.detail.filePath).toBe("/Users/me/dev/blankpage/editor/.tasks/578561c8.md");
expect(info.detail.content).toBe("260 - Source: `**bold**|`\n261 - Action: `Shift+ArrowLeft`");
}
});
test("Codex read_file stays read when result is missing", () => {
const input = {
path: "/some/file.txt",
};
const info = parseToolCallDisplay({ name: "read_file", input });
expect(info.detail.type).toBe("read");
expect(info.displayName).toBe("Read");
expect(tasks?.map((task) => task.text)).toEqual(["Task 1", "Task 2"]);
expect(tasks?.map((task) => task.completed)).toEqual([false, true]);
});
});
-647
View File
@@ -18,31 +18,6 @@ export type DiffLine = {
segments?: DiffSegment[];
};
export type EditEntry = {
filePath?: string;
diffLines: DiffLine[];
};
export type ReadEntry = {
filePath?: string;
content: string;
};
export type CommandDetails = {
command?: string;
cwd?: string;
output?: string;
exitCode?: number | null;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function getString(value: unknown): string | undefined {
return typeof value === "string" ? value : undefined;
}
function splitIntoLines(text: string): string[] {
if (!text) {
return [];
@@ -323,612 +298,6 @@ export function parseUnifiedDiff(diffText?: string): DiffLine[] {
return diff;
}
function deriveDiffLines({
unifiedDiff,
original,
updated,
}: {
unifiedDiff?: string;
original?: string;
updated?: string;
}): DiffLine[] {
if (unifiedDiff) {
const parsed = parseUnifiedDiff(unifiedDiff);
if (parsed.length > 0) {
return parsed;
}
}
if (original !== undefined || updated !== undefined) {
return buildLineDiff(original ?? "", updated ?? "");
}
return [];
}
function looksLikePatch(text: string): boolean {
if (!text) {
return false;
}
return /(\*\*\* Begin Patch|@@|diff --git|\+\+\+|--- )/.test(text);
}
function parsePatchText(text: string): DiffLine[] {
if (!text) {
return [];
}
return parseUnifiedDiff(text);
}
function getFilePathFromRecord(record: Record<string, unknown>): string | undefined {
return (
getString(record["file_path"]) ??
getString(record["filePath"]) ??
getString(record["path"]) ??
getString(record["target_path"]) ??
getString(record["targetPath"]) ??
undefined
);
}
const ChangeBlockSchema = z
.object({
unified_diff: z.string().optional(),
unifiedDiff: z.string().optional(),
diff: z.string().optional(),
patch: z.string().optional(),
old_content: z.string().optional(),
oldContent: z.string().optional(),
previous_content: z.string().optional(),
previousContent: z.string().optional(),
base_content: z.string().optional(),
baseContent: z.string().optional(),
old_string: z.string().optional(),
new_string: z.string().optional(),
new_content: z.string().optional(),
newContent: z.string().optional(),
replace_with: z.string().optional(),
replaceWith: z.string().optional(),
content: z.string().optional(),
})
.passthrough();
function buildEditEntryFromBlock(
filePath: string | undefined,
blockValue: Record<string, unknown>
): EditEntry | null {
const parsed = ChangeBlockSchema.safeParse(blockValue);
if (!parsed.success) {
return null;
}
const data = parsed.data;
const diffLines = deriveDiffLines({
unifiedDiff:
getString(
data.unified_diff ??
data.unifiedDiff ??
data.patch ??
data.diff
) ?? undefined,
original:
getString(
data.old_string ??
data.old_content ??
data.oldContent ??
data.previous_content ??
data.previousContent ??
data.base_content ??
data.baseContent
) ?? undefined,
updated:
getString(
data.new_string ??
data.new_content ??
data.newContent ??
data.replace_with ??
data.replaceWith ??
data.content
) ?? undefined,
});
if (diffLines.length > 0) {
return {
filePath: filePath ?? getFilePathFromRecord(blockValue),
diffLines,
};
}
const patchCandidate =
getString(data.unified_diff ?? data.unifiedDiff ?? data.patch ?? data.diff) ??
undefined;
if (patchCandidate && looksLikePatch(patchCandidate)) {
const parsedLines = parsePatchText(patchCandidate);
if (parsedLines.length > 0) {
return {
filePath: filePath ?? getFilePathFromRecord(blockValue),
diffLines: parsedLines,
};
}
}
return null;
}
function mergeEditEntries(entries: EditEntry[]): EditEntry[] {
if (entries.length === 0) {
return [];
}
const seen = new Map<string, EditEntry>();
entries.forEach((entry) => {
if (!entry.diffLines.length) {
return;
}
const hash = `${entry.filePath ?? "unknown"}::${entry.diffLines
.map((line) => `${line.type}:${line.content}`)
.join("|")}`;
if (!seen.has(hash)) {
seen.set(hash, entry);
}
});
return Array.from(seen.values());
}
function parseEditArguments(value: unknown, depth = 0): EditEntry[] {
if (!value || depth > 5) {
return [];
}
if (typeof value === "string") {
if (looksLikePatch(value)) {
const diffLines = parsePatchText(value);
return diffLines.length ? [{ diffLines }] : [];
}
return [];
}
if (Array.isArray(value)) {
return value.flatMap((entry) => parseEditArguments(entry, depth + 1));
}
if (!isRecord(value)) {
return [];
}
const filePathHint = getFilePathFromRecord(value) ?? getString(value["name"]);
if (value["patch"] || value["diff"] || value["unified_diff"] || value["unifiedDiff"]) {
const entry = buildEditEntryFromBlock(filePathHint, value);
return entry ? [entry] : [];
}
const entries: EditEntry[] = [];
const changeKeys = [
"changes",
"files",
"fileChanges",
"file_changes",
"edits",
"diffs",
"patches",
"fileDiffs",
"file_diffs",
] as const;
for (const key of changeKeys) {
const block = value[key];
if (!block) {
continue;
}
if (Array.isArray(block)) {
for (const item of block) {
if (isRecord(item)) {
const entry = buildEditEntryFromBlock(filePathHint, item);
if (entry) {
entries.push(entry);
}
}
}
continue;
}
if (isRecord(block)) {
if (block["patch"] || block["diff"]) {
const entry = buildEditEntryFromBlock(filePathHint, block);
if (entry) {
entries.push(entry);
}
continue;
}
for (const [path, nested] of Object.entries(block)) {
if (isRecord(nested)) {
const entry = buildEditEntryFromBlock(path, nested);
if (entry) {
entries.push(entry);
}
} else if (typeof nested === "string" && looksLikePatch(nested)) {
const diffLines = parsePatchText(nested);
if (diffLines.length) {
entries.push({ filePath: path, diffLines });
}
}
}
}
}
const changeEntry = buildEditEntryFromBlock(filePathHint, value);
if (changeEntry) {
entries.push(changeEntry);
}
const nestedKeys = [
"create",
"delete",
"raw",
"data",
"payload",
"arguments",
"result",
] as const;
for (const key of nestedKeys) {
if (value[key] !== undefined) {
const nestedEntries = parseEditArguments(value[key], depth + 1);
entries.push(
...nestedEntries.map((entry) => ({
...entry,
filePath: entry.filePath ?? filePathHint,
}))
);
}
}
return entries;
}
const ReadContainerSchema = z
.object({
filePath: z.string().optional(),
file_path: z.string().optional(),
path: z.string().optional(),
content: z.string().optional(),
text: z.string().optional(),
blob: z.string().optional(),
data: z
.object({
content: z.string().optional(),
text: z.string().optional(),
})
.optional(),
structuredContent: z
.object({
content: z.string().optional(),
text: z.string().optional(),
data: z
.object({
content: z.string().optional(),
text: z.string().optional(),
})
.optional(),
})
.optional(),
structured_content: z
.object({
content: z.string().optional(),
text: z.string().optional(),
})
.optional(),
output: z
.object({
content: z.string().optional(),
text: z.string().optional(),
})
.optional(),
})
.passthrough();
function parseReadEntriesInternal(value: unknown, depth = 0): ReadEntry[] {
if (!value || depth > 4) {
return [];
}
if (typeof value === "string") {
const trimmed = value.trim();
return trimmed.length ? [{ content: value }] : [];
}
if (Array.isArray(value)) {
return value.flatMap((entry) => parseReadEntriesInternal(entry, depth + 1));
}
if (!isRecord(value)) {
return [];
}
const parsed = ReadContainerSchema.safeParse(value);
if (parsed.success) {
const data = parsed.data;
const content =
getString(data.content) ??
getString(data.text) ??
getString(data.blob) ??
getString(data.data?.content) ??
getString(data.data?.text) ??
getString(data.structuredContent?.content) ??
getString(data.structuredContent?.text) ??
getString(data.structuredContent?.data?.content) ??
getString(data.structuredContent?.data?.text) ??
getString(data.structured_content?.content) ??
getString(data.structured_content?.text) ??
getString(data.output?.content) ??
getString(data.output?.text);
if (content) {
return [
{
filePath: data.filePath ?? data.file_path ?? data.path,
content,
},
];
}
}
const nestedKeys = [
"output",
"result",
"structuredContent",
"structured_content",
"data",
"raw",
"value",
"content",
] as const;
const entries: ReadEntry[] = [];
for (const key of nestedKeys) {
if (value[key] !== undefined) {
entries.push(...parseReadEntriesInternal(value[key], depth + 1));
}
}
return entries;
}
function mergeReadEntries(entries: ReadEntry[]): ReadEntry[] {
if (!entries.length) {
return [];
}
const seen = new Map<string, ReadEntry>();
entries.forEach((entry) => {
const hash = `${entry.filePath ?? "content"}::${entry.content}`;
if (!seen.has(hash)) {
seen.set(hash, entry);
}
});
return Array.from(seen.values());
}
const CommandRawSchema = z
.object({
type: z.string().optional(),
command: z.union([z.string(), z.array(z.string())]).optional(),
aggregated_output: z.string().optional(),
exit_code: z.number().optional(),
cwd: z.string().optional(),
directory: z.string().optional(),
metadata: z
.object({
exit_code: z.number().optional(),
})
.optional(),
input: z.unknown().optional(),
output: z.unknown().optional(),
})
.passthrough();
const CommandResultSchema = z
.object({
output: z.string().optional(),
exitCode: z.number().nullable().optional(),
structuredContent: z
.object({
output: z.string().optional(),
text: z.string().optional(),
content: z.string().optional(),
})
.optional(),
structured_content: z
.object({
output: z.string().optional(),
text: z.string().optional(),
content: z.string().optional(),
})
.optional(),
metadata: z
.object({
exit_code: z.number().optional(),
})
.optional(),
result: z.unknown().optional(),
})
.passthrough();
function coerceCommandValue(value: unknown): string | undefined {
if (typeof value === "string" && value.length > 0) {
return value;
}
if (Array.isArray(value)) {
const tokens = value.filter((entry): entry is string => typeof entry === "string");
if (tokens.length) {
return tokens.join(" ");
}
}
return undefined;
}
function collectCommandDetails(
target: CommandDetails,
value: unknown,
depth = 0
): void {
if (!value || depth > 4) {
return;
}
if (typeof value === "string") {
if (!target.output) {
target.output = value;
}
return;
}
if (!isRecord(value)) {
return;
}
const rawParsed = CommandRawSchema.safeParse(value);
if (rawParsed.success) {
const data = rawParsed.data;
const commandCandidate =
coerceCommandValue(data.command) ??
(isRecord(data.input) ? coerceCommandValue(data.input["command"]) : undefined);
if (!target.command && commandCandidate) {
target.command = commandCandidate;
}
const cwdCandidate =
getString(data.cwd ?? data.directory) ??
(isRecord(data.input)
? getString(data.input["cwd"] ?? data.input["directory"])
: undefined);
if (!target.cwd && cwdCandidate) {
target.cwd = cwdCandidate;
}
const aggregatedOutput =
getString(data.aggregated_output) ??
(isRecord(data.output)
? getString(
(data.output as Record<string, unknown>)["aggregated_output"] ??
(data.output as Record<string, unknown>)["output"] ??
(data.output as Record<string, unknown>)["text"]
)
: undefined);
if (!target.output && aggregatedOutput) {
target.output = aggregatedOutput;
}
const exitCandidate =
data.exit_code ??
(data.metadata ? data.metadata.exit_code : undefined) ??
(isRecord(data.output)
? ((data.output as Record<string, unknown>)["exit_code"] as number | undefined) ??
((data.output as Record<string, unknown>)["exitCode"] as number | undefined)
: undefined);
if (target.exitCode === undefined && exitCandidate !== undefined) {
target.exitCode = exitCandidate;
}
}
const resultParsed = CommandResultSchema.safeParse(value);
if (resultParsed.success) {
const data = resultParsed.data;
if (!target.output) {
target.output =
getString(data.output) ??
getString(data.structuredContent?.output) ??
getString(data.structuredContent?.text) ??
getString(data.structured_content?.output) ??
getString(data.structured_content?.text) ??
(typeof data.result === "string" ? data.result : undefined);
}
if (target.exitCode === undefined) {
target.exitCode = data.exitCode ?? data.metadata?.exit_code;
}
if (!target.command && isRecord(data.result)) {
const nestedCommand =
coerceCommandValue(data.result["command"]) ??
coerceCommandValue((data.result as Record<string, unknown>)["args"]);
if (nestedCommand) {
target.command = nestedCommand;
}
}
}
const nestedKeys = [
"input",
"output",
"result",
"response",
"data",
"raw",
"payload",
] as const;
for (const key of nestedKeys) {
if (value[key] !== undefined) {
collectCommandDetails(target, value[key], depth + 1);
}
}
}
export function extractEditEntries(...sources: unknown[]): EditEntry[] {
const entries = sources.flatMap((value) => parseEditArguments(value));
return mergeEditEntries(entries);
}
export function extractReadEntries(...sources: unknown[]): ReadEntry[] {
return mergeReadEntries(sources.flatMap((value) => parseReadEntriesInternal(value)));
}
export function extractCommandDetails(...sources: unknown[]): CommandDetails | null {
const details: CommandDetails = {};
sources.forEach((value) => collectCommandDetails(details, value));
if (details.command || details.output || details.cwd) {
return details;
}
return null;
}
// ---- Key-Value Extraction for Generic Tool Results ----
export interface KeyValuePair {
key: string;
value: string;
}
function stringifyValue(value: unknown): string {
if (value === null) {
return "null";
}
if (value === undefined) {
return "undefined";
}
if (typeof value === "string") {
return value;
}
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
try {
return JSON.stringify(value, null, 2);
} catch {
return String(value);
}
}
const WrappedOutputSchema = z
.object({ output: z.record(z.unknown()) })
.transform((data) => data.output);
const DirectRecordSchema = z.record(z.unknown());
const ToolResultRecordSchema = z.union([WrappedOutputSchema, DirectRecordSchema]);
export function extractKeyValuePairs(result: unknown): KeyValuePair[] {
const parsed = ToolResultRecordSchema.safeParse(result);
if (!parsed.success) {
return [];
}
const record = parsed.data;
return Object.entries(record).map(([key, value]) => ({
key,
value: stringifyValue(value),
}));
}
// ---- Task Extraction (cross-provider) ----
export type TaskStatus = "pending" | "in_progress" | "completed";
@@ -1007,19 +376,3 @@ export function extractTaskEntriesFromToolCall(
return null;
}
// ---- Unified Tool Call Display ----
// Re-export from server — single source of truth
export {
parseToolCallDisplay,
stripCwdPrefix,
extractTodos,
normalizeToolDisplayName,
stripShellWrapperPrefix,
type ToolCallInput,
type ToolCallDisplayInfo,
type ToolCallDetail,
type ToolCallKind,
type TodoItem,
type KeyValuePair as ServerKeyValuePair,
} from "@getpaseo/server/utils/tool-call-parsers";