Project Files
src / tools / git-show-tool.ts
import { tool } from "@lmstudio/sdk"
import { simpleGit } from "simple-git"
import { z } from "zod"
import { formatGitError } from "../git/error-formatting"
import { gitSafeString } from "../git/parameter-schemas"
import { resolveAndValidateRepoPath } from "../path/validation"
import type { Tool, ToolsProviderController } from "@lmstudio/sdk"
/**
* Maximum file size in bytes to load into memory.
*
* @const {number}
* @default 10485760
*/
const maxFileSize = 10 * 1024 * 1024
/**
* Maximum number of lines a single paginated read can return.
*
* @const {number}
* @default 10000
*/
const maxLineLimit = 10_000
/**
* Maximum allowed value for the offset parameter.
*
* @const {number}
* @default 10000000
*/
const maxOffset = 10_000_000
/**
* Maximum allowed character length for ref strings.
*
* @const {number}
* @default 255
*/
const maxReferenceLength = 255
/**
* Maximum allowed character length for file paths inside a repo.
*
* @const {number}
* @default 4096
*/
const maxFilePathLength = 4096
/**
* Create the git show tool.
*
* @param ctl Tools provider controller supplied by the LM Studio SDK.
* @returns The configured git show tool.
*/
export function createGitShowTool(ctl: ToolsProviderController): Tool {
return tool({
name: "git_show",
description: "View the contents of a specific file or commit. Defaults to showing HEAD.",
parameters: {
repoName: gitSafeString("repoName", maxFilePathLength).describe("Directory name of the cloned git repository."),
ref: gitSafeString("ref", maxReferenceLength)
.refine(value => !/\s/.test(value), {
message: "ref must not contain whitespace.",
})
.optional()
.default("HEAD")
.describe("Commit hash, tag, or ref to show (defaults to HEAD)."),
file: gitSafeString("file", maxFilePathLength)
.optional()
.describe("Specific file path to show within the commit."),
offset: z
.number()
.int()
.min(0)
.max(maxOffset)
.optional()
.describe("0-based line number to start reading from. Omit to begin from the first line."),
limit: z
.number()
.int()
.min(1)
.max(maxLineLimit)
.optional()
.describe(`Maximum number of lines to read (max ${maxLineLimit}). Omit to return all available lines.`),
},
/**
* Executes the git show command with optional pagination.
*
* @param arguments_ Validated tool parameters.
* @param arguments_.repoName Repository directory name relative to the working directory.
* @param arguments_.ref Commit hash, tag, or ref to show.
* @param arguments_.file Optional file path to show within the commit.
* @param arguments_.offset 0-based line number to start reading from.
* @param arguments_.limit Maximum number of lines to read.
* @param context Runtime tool context supplied by the SDK.
* @returns The commit or file content, optionally paginated, or a clear error string.
*/
implementation: async (arguments_, context) => {
const { repoName, ref, file, offset, limit } = arguments_
const repoPath = resolveAndValidateRepoPath(repoName, ctl.getWorkingDirectory())
const git = simpleGit(repoPath)
const objectName = file === undefined ? ref : `${ref}:${file}`
context.status(`Showing "${objectName}" in "${repoName}"…`)
try {
if (file !== undefined) {
context.status(`Checking size of "${objectName}" in "${repoName}"…`)
const sizeRaw = await git.raw(["cat-file", "-s", objectName])
const trimmedSize = sizeRaw.trim()
const byteSize = Number(trimmedSize)
if (Number.isNaN(byteSize)) {
return `Error: git_show could not determine the size of "${objectName}". "git cat-file -s" returned "${trimmedSize}".`
}
if (byteSize > maxFileSize) {
return `Error: file "${objectName}" exceeds the maximum allowed size of ${maxFileSize} bytes (${byteSize} bytes). Use offset and limit to read it in chunks.`
}
}
context.status(`Loading content of "${objectName}" in "${repoName}"…`)
const fullContent = await git.show([objectName])
const outputSize = Buffer.byteLength(fullContent, "utf8")
if (outputSize > maxFileSize) {
return `Error: output for "${objectName}" exceeds the maximum allowed size of ${maxFileSize} bytes (${outputSize} bytes). Specify a file path to show a single blob, or pick a narrower ref.`
}
const allLines = fullContent.split("\n")
const totalLines = allLines.length
const start = offset === undefined ? 0 : Math.min(offset, totalLines)
if (limit === undefined) {
const sliced = allLines.slice(start)
const text = sliced.join("\n")
return JSON.stringify(
{
content: text,
total_lines: totalLines,
from_line: start + 1,
to_line: totalLines,
},
undefined,
2
)
}
const end = Math.min(start + limit, totalLines)
const sliced = allLines.slice(start, end)
const text = sliced.join("\n")
return JSON.stringify(
{
content: text,
total_lines: totalLines,
from_line: start + 1,
to_line: end,
},
undefined,
2
)
} catch (error) {
return formatGitError("git_show", repoName, error)
}
},
})
}