106 lines
2.9 KiB
TypeScript
106 lines
2.9 KiB
TypeScript
import { stat } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
|
|
import {
|
|
createBashToolDefinition,
|
|
getAgentDir,
|
|
SettingsManager,
|
|
type BashToolOptions,
|
|
type ExtensionAPI,
|
|
type ExtensionContext,
|
|
} from "@earendil-works/pi-coding-agent";
|
|
import { type Static, Type } from "typebox";
|
|
|
|
const bashCwdParameters = Type.Object({
|
|
command: Type.String({ description: "Shell command to execute" }),
|
|
cwd: Type.Optional(
|
|
Type.String({
|
|
description:
|
|
"Working directory. Relative paths resolve from the session working directory.",
|
|
}),
|
|
),
|
|
timeout: Type.Optional(
|
|
Type.Number({ description: "Timeout in seconds (optional, no default timeout)" }),
|
|
),
|
|
});
|
|
|
|
type BashCwdInput = Static<typeof bashCwdParameters>;
|
|
type BuiltInBashDefinition = ReturnType<typeof createBashToolDefinition>;
|
|
type BuiltInBashExecute = BuiltInBashDefinition["execute"];
|
|
|
|
async function resolveWorkingDirectory(
|
|
sessionCwd: string,
|
|
requestedCwd: string | undefined,
|
|
): Promise<string> {
|
|
const cwd = resolve(sessionCwd, requestedCwd ?? ".");
|
|
|
|
let metadata;
|
|
try {
|
|
metadata = await stat(cwd);
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
throw new Error(
|
|
`Working directory does not exist: ${cwd}\nCannot execute bash commands.`,
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
if (!metadata.isDirectory()) {
|
|
throw new Error(
|
|
`Working directory is not a directory: ${cwd}\nCannot execute bash commands.`,
|
|
);
|
|
}
|
|
|
|
return cwd;
|
|
}
|
|
|
|
function loadBashOptions(ctx: ExtensionContext): BashToolOptions {
|
|
const settings = SettingsManager.create(ctx.cwd, getAgentDir(), {
|
|
projectTrusted: ctx.isProjectTrusted(),
|
|
});
|
|
return {
|
|
commandPrefix: settings.getShellCommandPrefix(),
|
|
shellPath: settings.getShellPath(),
|
|
};
|
|
}
|
|
|
|
export function createContextualBashTool(options?: BashToolOptions) {
|
|
const baseTool = createBashToolDefinition(process.cwd(), options);
|
|
|
|
return {
|
|
...baseTool,
|
|
description:
|
|
`${baseTool.description} The optional cwd may be absolute or relative to the session working directory.`,
|
|
parameters: bashCwdParameters,
|
|
async execute(
|
|
toolCallId: Parameters<BuiltInBashExecute>[0],
|
|
params: BashCwdInput,
|
|
signal: Parameters<BuiltInBashExecute>[2],
|
|
onUpdate: Parameters<BuiltInBashExecute>[3],
|
|
ctx: ExtensionContext,
|
|
) {
|
|
if (!ctx) {
|
|
throw new Error("Bash execution context is unavailable");
|
|
}
|
|
|
|
const cwd = await resolveWorkingDirectory(ctx.cwd, params.cwd);
|
|
const contextualTool = createBashToolDefinition(
|
|
cwd,
|
|
options ?? loadBashOptions(ctx),
|
|
);
|
|
return contextualTool.execute(
|
|
toolCallId,
|
|
{ command: params.command, timeout: params.timeout },
|
|
signal,
|
|
onUpdate,
|
|
ctx,
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
export default function bashCwd(pi: ExtensionAPI): void {
|
|
pi.registerTool(createContextualBashTool());
|
|
}
|