show.ts

 1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2//
 3// SPDX-License-Identifier: GPL-3.0-or-later
 4
 5import type { AgentTool } from "@mariozechner/pi-agent-core";
 6import { Type } from "@sinclair/typebox";
 7import simpleGit from "simple-git";
 8import { ToolInputError } from "../../../util/errors.js";
 9import { formatSize, truncateHead } from "../../../util/truncate.js";
10
11// Trust boundary: refs and paths are passed directly to simple-git, which is
12// scoped to the workspace. The user chose to clone this repo, so its contents
13// are trusted. See AGENTS.md § Workspace Sandboxing.
14
15const ShowSchema = Type.Object({
16  ref: Type.String({ description: "Commit hash or ref" }),
17});
18
19export const createGitShowTool = (workspacePath: string): AgentTool => ({
20  name: "git_show",
21  label: "Git Show",
22  description: "Show details for a commit or object.",
23  parameters: ShowSchema as any,
24  execute: async (_toolCallId: string, params: any) => {
25    if (!String(params.ref ?? "").trim()) {
26      throw new ToolInputError("ref must be a non-empty string");
27    }
28    if (
29      String(params.ref ?? "")
30        .trim()
31        .startsWith("-")
32    ) {
33      throw new ToolInputError("ref must not start with '-'");
34    }
35    const git = simpleGit(workspacePath);
36    const raw = await git.show([params.ref]);
37    const truncation = truncateHead(raw);
38
39    let text = truncation.content;
40    if (truncation.truncated) {
41      text += `\n\n[truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)})]`;
42    }
43
44    return {
45      content: [{ type: "text", text }],
46      details: {
47        ref: params.ref,
48        ...(truncation.truncated ? { truncation } : {}),
49      },
50    };
51  },
52});