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});