diff.ts

 1import { Type } from "@sinclair/typebox";
 2import type { AgentTool } from "@mariozechner/pi-ai";
 3import simpleGit from "simple-git";
 4import { ToolInputError } from "../../../util/errors.js";
 5
 6const DiffSchema = Type.Object({
 7  ref: Type.Optional(Type.String({ description: "Base ref (optional)" })),
 8  ref2: Type.Optional(Type.String({ description: "Compare ref (optional)" })),
 9  path: Type.Optional(Type.String({ description: "Limit diff to path" })),
10});
11
12export const createGitDiffTool = (workspacePath: string): AgentTool => ({
13  name: "git_diff",
14  label: "Git Diff",
15  description: "Show diff between refs or working tree.",
16  parameters: DiffSchema as any,
17  execute: async (_toolCallId: string, params: any) => {
18    const git = simpleGit(workspacePath);
19    const args: string[] = [];
20
21    if (params.ref && !String(params.ref).trim()) {
22      throw new ToolInputError("ref must be a non-empty string");
23    }
24    if (params.ref2 && !String(params.ref2).trim()) {
25      throw new ToolInputError("ref2 must be a non-empty string");
26    }
27    if (params.path && !String(params.path).trim()) {
28      throw new ToolInputError("path must be a non-empty string");
29    }
30    if (params.ref) args.push(params.ref);
31    if (params.ref2) args.push(params.ref2);
32    if (params.path) args.push("--", params.path);
33
34    const text = await git.diff(args);
35    return {
36      content: [{ type: "text", text }],
37      details: { path: params.path ?? null },
38    };
39  },
40});