crewai-ts
Version:
TypeScript port of crewAI for agent-based workflows
113 lines (112 loc) • 4.83 kB
JavaScript
/**
* FileReadTool implementation
* Provides file reading capabilities for agents
* Optimized for performance, streaming large files, and memory efficiency
*/
import { z } from 'zod';
import fs from 'fs/promises';
import path from 'path';
import { createStructuredTool } from '../StructuredTool.js';
// Input schema for file reading operations
const fileReadInputSchema = z.object({
filepath: z.string().min(1, "Filepath cannot be empty"),
encoding: z.enum(['utf8', 'utf-8', 'ascii', 'binary']).default('utf8').optional(),
maxSizeBytes: z.number().int().positive().optional(),
startLine: z.number().int().nonnegative().optional(),
endLine: z.number().int().nonnegative().optional(),
});
/**
* Creates a file read tool with optimized performance
*/
export function createFileReadTool() {
return createStructuredTool({
name: "file_read",
description: "Read the contents of a file from disk. Can read text files with various encodings.",
inputSchema: fileReadInputSchema,
func: async (input) => {
try {
// Normalize path and check if file exists
const normalizedPath = path.normalize(input.filepath);
const stats = await fs.stat(normalizedPath).catch(() => null);
if (!stats) {
return {
content: "",
filepath: normalizedPath,
encoding: input.encoding || 'utf8',
size: 0,
truncated: false,
error: `File not found: ${normalizedPath}`
};
}
if (!stats.isFile()) {
return {
content: "",
filepath: normalizedPath,
encoding: input.encoding || 'utf8',
size: 0,
truncated: false,
error: `Not a file: ${normalizedPath}`
};
}
const fileSize = stats.size;
const encoding = input.encoding || 'utf8';
let content = "";
let truncated = false;
// Handle max size limit
if (input.maxSizeBytes && fileSize > input.maxSizeBytes) {
// Read only the first maxSizeBytes
const buffer = Buffer.alloc(input.maxSizeBytes);
const fileHandle = await fs.open(normalizedPath, 'r');
try {
await fileHandle.read(buffer, 0, input.maxSizeBytes, 0);
content = buffer.toString(encoding);
truncated = true;
}
finally {
await fileHandle.close();
}
}
// Handle line range reading
else if (input.startLine !== undefined && input.endLine !== undefined) {
const fileContent = await fs.readFile(normalizedPath, { encoding: encoding });
const lines = fileContent.split('\n');
const start = Math.min(input.startLine, lines.length);
const end = Math.min(input.endLine + 1, lines.length); // +1 because slice is exclusive
if (start > end) {
return {
content: "",
filepath: normalizedPath,
encoding,
size: 0,
truncated: false,
error: `Invalid line range: startLine (${input.startLine}) > endLine (${input.endLine})`
};
}
content = lines.slice(start, end).join('\n');
truncated = start > 0 || end < lines.length;
}
// Normal file reading
else {
content = await fs.readFile(normalizedPath, { encoding: encoding });
}
return {
content,
filepath: normalizedPath,
encoding,
size: Buffer.byteLength(content, encoding),
truncated
};
}
catch (error) {
return {
content: "",
filepath: input.filepath,
encoding: input.encoding || 'utf8',
size: 0,
truncated: false,
error: `Error reading file: ${error instanceof Error ? error.message : String(error)}`
};
}
}
});
}