97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
import {
|
|
createLocalToolExecutionContext,
|
|
formatShellResult,
|
|
type IToolExecutionContext,
|
|
} from './tool.context.js';
|
|
|
|
export interface IShellToolOptions {
|
|
/** Allowed commands whitelist. If empty, all commands are allowed. */
|
|
allowedCommands?: string[];
|
|
/** Working directory for shell execution */
|
|
cwd?: string;
|
|
/** Execution context. Defaults to a local Node.js context. */
|
|
context?: IToolExecutionContext;
|
|
/** Maximum output lines before truncating. */
|
|
maxLines?: number;
|
|
/** Maximum output bytes before truncating. */
|
|
maxBytes?: number;
|
|
}
|
|
|
|
export interface ICreateShellToolsOptions {
|
|
/** Allowed commands whitelist. If empty, all commands are allowed. */
|
|
allowedCommands?: string[];
|
|
/** Maximum output lines before truncating. */
|
|
maxLines?: number;
|
|
/** Maximum output bytes before truncating. */
|
|
maxBytes?: number;
|
|
}
|
|
|
|
export function createShellTools(context: IToolExecutionContext, options: ICreateShellToolsOptions = {}): plugins.ToolSet {
|
|
return {
|
|
run_command: plugins.tool({
|
|
description:
|
|
'Execute a shell command in the active workspace. Provide the full command string. stdout and stderr are returned.',
|
|
inputSchema: plugins.z.object({
|
|
command: plugins.z.string().describe('The shell command to execute'),
|
|
cwd: plugins.z
|
|
.string()
|
|
.optional()
|
|
.describe('Working directory for the command'),
|
|
timeout: plugins.z
|
|
.number()
|
|
.optional()
|
|
.describe('Timeout in milliseconds'),
|
|
timeoutMs: plugins.z
|
|
.number()
|
|
.optional()
|
|
.describe('Timeout in milliseconds'),
|
|
}),
|
|
execute: async ({
|
|
command,
|
|
cwd,
|
|
timeout,
|
|
timeoutMs,
|
|
}: {
|
|
command: string;
|
|
cwd?: string;
|
|
timeout?: number;
|
|
timeoutMs?: number;
|
|
}) => {
|
|
if (!context.shell) {
|
|
throw new Error('Shell tool is not available in this execution context.');
|
|
}
|
|
|
|
if (options.allowedCommands?.length) {
|
|
const baseCommand = command.split(/\s+/)[0];
|
|
if (!options.allowedCommands.includes(baseCommand)) {
|
|
return `Command "${baseCommand}" is not in the allowed commands list: ${options.allowedCommands.join(', ')}`;
|
|
}
|
|
}
|
|
|
|
await context.requestPermission?.({
|
|
type: 'shell',
|
|
title: 'Run shell command',
|
|
metadata: { command, cwd: cwd ?? context.cwd },
|
|
});
|
|
|
|
const execResult = await context.shell.run(command, {
|
|
cwd: cwd ?? context.cwd,
|
|
timeoutMs: timeoutMs ?? timeout,
|
|
abortSignal: context.abortSignal,
|
|
});
|
|
|
|
return plugins.truncateOutput(formatShellResult(execResult), {
|
|
maxLines: options.maxLines,
|
|
maxBytes: options.maxBytes,
|
|
}).content;
|
|
},
|
|
}),
|
|
};
|
|
}
|
|
|
|
export function shellTool(options?: IShellToolOptions): plugins.ToolSet {
|
|
const context = options?.context ?? createLocalToolExecutionContext({ cwd: options?.cwd });
|
|
return createShellTools(context, options);
|
|
}
|