Files
smartagent/ts_tools/tool.filesystem.ts
T

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);
}