134 lines
5.0 KiB
TypeScript
134 lines
5.0 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
import {
|
|
createLocalToolExecutionContext,
|
|
formatToolOutput,
|
|
type IToolExecutionContext,
|
|
} from './tool.context.js';
|
|
|
|
export interface IFilesystemToolOptions {
|
|
/** Restrict file access to this directory. Default: process.cwd() */
|
|
rootDir?: string;
|
|
/** Execution context. Defaults to a local Node.js context. */
|
|
context?: IToolExecutionContext;
|
|
/** Include delete_file. Default: true for compatibility. */
|
|
includeDelete?: boolean;
|
|
/** Maximum output lines before truncating. */
|
|
maxLines?: number;
|
|
/** Maximum output bytes before truncating. */
|
|
maxBytes?: number;
|
|
}
|
|
|
|
export interface ICreateFilesystemToolsOptions {
|
|
/** Include delete_file. Default: true. */
|
|
includeDelete?: boolean;
|
|
/** Maximum output lines before truncating. */
|
|
maxLines?: number;
|
|
/** Maximum output bytes before truncating. */
|
|
maxBytes?: number;
|
|
}
|
|
|
|
export function createFilesystemTools(context: IToolExecutionContext, options: ICreateFilesystemToolsOptions = {}): plugins.ToolSet {
|
|
const truncate = (output: unknown) => plugins.truncateOutput(formatToolOutput(output), {
|
|
maxLines: options.maxLines,
|
|
maxBytes: options.maxBytes,
|
|
}).content;
|
|
const tools: plugins.ToolSet = {
|
|
read_file: plugins.tool({
|
|
description:
|
|
'Read a UTF-8 file in the active workspace. Paths may be absolute or relative to the workspace root.',
|
|
inputSchema: plugins.z.object({
|
|
path: plugins.z.string().describe('File path'),
|
|
startLine: plugins.z
|
|
.number()
|
|
.optional()
|
|
.describe('First line (1-indexed, inclusive)'),
|
|
endLine: plugins.z
|
|
.number()
|
|
.optional()
|
|
.describe('Last line (1-indexed, inclusive)'),
|
|
}),
|
|
execute: async ({
|
|
path: filePath,
|
|
startLine,
|
|
endLine,
|
|
}: {
|
|
path: string;
|
|
startLine?: number;
|
|
endLine?: number;
|
|
}) => {
|
|
if (!context.fs) {
|
|
throw new Error('Filesystem tools are not available in this execution context.');
|
|
}
|
|
return truncate(await context.fs.readFile(filePath, { startLine, endLine }));
|
|
},
|
|
}),
|
|
|
|
write_file: plugins.tool({
|
|
description:
|
|
'Write UTF-8 content to a file in the active workspace. Creates parent directories and overwrites existing content. Requires host permission when configured.',
|
|
inputSchema: plugins.z.object({
|
|
path: plugins.z.string().describe('File path'),
|
|
content: plugins.z.string().describe('Complete file content to write'),
|
|
}),
|
|
execute: async ({ path: filePath, content }: { path: string; content: string }) => {
|
|
if (!context.fs) {
|
|
throw new Error('Filesystem tools are not available in this execution context.');
|
|
}
|
|
await context.requestPermission?.({
|
|
type: 'write',
|
|
title: 'Write file',
|
|
metadata: { path: filePath, bytes: Buffer.byteLength(content, 'utf8') },
|
|
});
|
|
const result = await context.fs.writeFile(filePath, content);
|
|
return truncate(result ?? `Written ${Buffer.byteLength(content, 'utf8')} bytes to ${filePath}`);
|
|
},
|
|
}),
|
|
|
|
list_directory: plugins.tool({
|
|
description: 'List files and directories in the active workspace. Paths may be absolute or relative to the workspace root.',
|
|
inputSchema: plugins.z.object({
|
|
path: plugins.z.string().default('.').describe('Directory path to list'),
|
|
recursive: plugins.z
|
|
.boolean()
|
|
.optional()
|
|
.describe('List recursively. Default: false'),
|
|
}),
|
|
execute: async ({ path: directoryPath, recursive }: { path: string; recursive?: boolean }) => {
|
|
if (!context.fs) {
|
|
throw new Error('Filesystem tools are not available in this execution context.');
|
|
}
|
|
const result = await context.fs.listDirectory(directoryPath, { recursive });
|
|
return truncate(Array.isArray(result) ? result.join('\n') : result);
|
|
},
|
|
}),
|
|
};
|
|
|
|
if (options.includeDelete !== false) {
|
|
tools.delete_file = plugins.tool({
|
|
description: 'Delete a file or empty directory in the active workspace. Requires host permission when configured.',
|
|
inputSchema: plugins.z.object({
|
|
path: plugins.z.string().describe('Path to delete'),
|
|
}),
|
|
execute: async ({ path: targetPath }: { path: string }) => {
|
|
if (!context.fs?.deletePath) {
|
|
throw new Error('Deleting files is not available in this execution context.');
|
|
}
|
|
await context.requestPermission?.({
|
|
type: 'delete',
|
|
title: 'Delete file',
|
|
metadata: { path: targetPath },
|
|
});
|
|
const result = await context.fs.deletePath(targetPath);
|
|
return truncate(result ?? `Deleted ${targetPath}`);
|
|
},
|
|
});
|
|
}
|
|
|
|
return tools;
|
|
}
|
|
|
|
export function filesystemTool(options?: IFilesystemToolOptions): plugins.ToolSet {
|
|
const context = options?.context ?? createLocalToolExecutionContext({ rootDir: options?.rootDir });
|
|
return createFilesystemTools(context, options);
|
|
}
|