refs.ts

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