1import { Type } from "@sinclair/typebox";
2import type { AgentTool } from "@mariozechner/pi-agent-core";
3import simpleGit from "simple-git";
4import { formatSize, truncateHead } from "../../../util/truncate.js";
5
6// Trust boundary: refs and paths are passed directly to simple-git, which is
7// scoped to the workspace. The user chose to clone this repo, so its contents
8// are trusted. See AGENTS.md § Workspace Sandboxing.
9
10const RefsSchema = Type.Object({
11 type: Type.Union([
12 Type.Literal("branches"),
13 Type.Literal("tags"),
14 Type.Literal("remotes"),
15 ]),
16});
17
18export const createGitRefsTool = (workspacePath: string): AgentTool => ({
19 name: "git_refs",
20 label: "Git Refs",
21 description: "List branches or tags.",
22 parameters: RefsSchema as any,
23 execute: async (_toolCallId: string, params: any) => {
24 const git = simpleGit(workspacePath);
25
26 let raw: string;
27 let baseDetails: Record<string, any> = {};
28
29 if (params.type === "tags") {
30 const tags = await git.tags();
31 raw = tags.all.join("\n");
32 baseDetails = { count: tags.all.length };
33 } else if (params.type === "remotes") {
34 raw = await git.raw(["branch", "-r"]);
35 } else {
36 raw = await git.raw(["branch", "-a"]);
37 }
38
39 const truncation = truncateHead(raw);
40 let text = truncation.content;
41 if (truncation.truncated) {
42 text += `\n\n[truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)})]`;
43 }
44
45 return {
46 content: [{ type: "text", text }],
47 details: { ...baseDetails, ...(truncation.truncated ? { truncation } : {}) },
48 };
49 },
50});