MCPcopy Create free account
hub / github.com/cloudflare/computer / createReadTool

Function createReadTool

packages/computer/src/tools/fs/read.ts:56–179  ·  view source on GitHub ↗
(options: ReadToolOptions)

Source from the content-addressed store, hash-verified

54 return _enc.encode(s).length;
55}
56export function createReadTool(options: ReadToolOptions): Tool<z.infer<typeof inputSchema>> {
57 const { store } = options;
58 const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
59 const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
60
61 return tool({
62 description: `Read the contents of a file. Output is truncated to ${maxLines} lines or ${Math.round(maxBytes / 1024)}KB, whichever is reached first; use offset/limit to page through large files.`,
63 inputSchema,
64 execute: async ({ path, offset, limit }): Promise<ReadResult | { error: string }> => {
65 const stat = await store.stat(path);
66 if (!stat) return { error: `File not found: ${path}` };
67
68 const startLine = offset ?? 1;
69 const wantedLines = limit ?? maxLines;
70 const lineCap = Math.min(wantedLines, maxLines);
71
72 const decoder = new TextDecoder("utf-8");
73 let carry = ""; // bytes from previous chunk that didn't end on a newline
74 let currentLine = 1; // 1-indexed line we're about to emit
75 const collected: string[] = [];
76 let collectedBytes = 0;
77 let firstEmittedLine: number | null = null;
78 let truncatedByBudget = false;
79 let firstLineOverflow = false;
80
81 const processLine = (line: string): boolean => {
82 // Returns true to keep going, false to stop the outer pump.
83 if (currentLine < startLine) {
84 currentLine++;
85 return true;
86 }
87 // We're at or past `startLine` — try to emit.
88 const lineBytes = utf8ByteLength(line);
89 if (collected.length === 0 && lineBytes > maxBytes) {
90 firstLineOverflow = true;
91 return false;
92 }
93 // Stop before emitting if this line would push us over either cap.
94 if (collected.length >= lineCap) {
95 truncatedByBudget = true;
96 return false;
97 }
98 if (collectedBytes + lineBytes + (collected.length > 0 ? 1 : 0) > maxBytes) {
99 truncatedByBudget = true;
100 return false;
101 }
102 if (firstEmittedLine === null) firstEmittedLine = currentLine;
103 collected.push(line);
104 collectedBytes += lineBytes + (collected.length > 1 ? 1 : 0);
105 currentLine++;
106 return true;
107 };
108
109 let keepGoing = true;
110 for await (const chunk of store.readChunks(path)) {
111 if (!keepGoing) break;
112 carry += decoder.decode(chunk, { stream: true });
113 // Process every complete line in `carry`. Keep the final partial line

Callers 2

createAIToolsFunction · 0.85
ai.test.tsFile · 0.85

Calls 3

processLineFunction · 0.85
statMethod · 0.65
readChunksMethod · 0.65

Tested by

no test coverage detected