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

Function createEditTool

packages/computer/src/tools/fs/edit.ts:93–159  ·  view source on GitHub ↗
(options: EditToolOptions)

Source from the content-addressed store, hash-verified

91}
92
93export function createEditTool(options: EditToolOptions): Tool<z.infer<typeof inputSchema>> {
94 const { store } = options;
95 const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
96
97 return tool({
98 description:
99 "Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file. If two changes touch the same block, merge them into one edit.",
100 inputSchema,
101 execute: async (rawInput: z.infer<typeof inputSchema>) => {
102 const { path, edits } = prepareArguments(rawInput);
103
104 if (!Array.isArray(edits) || edits.length === 0) {
105 return { error: "edits must contain at least one replacement." };
106 }
107
108 return withFileLock(path, async () => {
109 try {
110 const stat = await store.stat(path);
111 if (!stat) return { error: `File not found: ${path}` };
112 if (stat.size > maxBytes) {
113 return {
114 error: `File too large to edit: ${stat.size} bytes exceeds the ${maxBytes}-byte cap. Use the write tool to rewrite the file from scratch.`,
115 };
116 }
117
118 const bytes = await store.readAll(path);
119 if (!bytes) return { error: `File not found: ${path}` };
120
121 const rawContent = new TextDecoder("utf-8", { fatal: false, ignoreBOM: true }).decode(
122 bytes,
123 );
124 const { bom, text } = stripBom(rawContent);
125 const ending = detectLineEnding(text);
126 const normalized = normalizeToLF(text);
127
128 let baseContent: string;
129 let newContent: string;
130 try {
131 ({ baseContent, newContent } = applyEditsToNormalizedContent(normalized, edits, path));
132 } catch (err) {
133 return { error: err instanceof Error ? err.message : String(err) };
134 }
135
136 const finalContent = bom + restoreLineEndings(newContent, ending);
137 // Round-trip the file's mode so editing an executable script (or any
138 // file with a non-default mode) doesn't silently drop bits. `stat.mode`
139 // is undefined for stores that don't track modes; pass `undefined` in
140 // that case so the store applies its own default.
141 await store.write(path, new TextEncoder().encode(finalContent), { mode: stat.mode });
142
143 const diffResult = generateDiffString(baseContent, newContent);
144 const patch = generateUnifiedPatch(path, baseContent, newContent);
145
146 return {
147 path,
148 editsApplied: edits.length,
149 diff: diffResult.diff,
150 patch,

Callers 2

createAIToolsFunction · 0.85
ai.test.tsFile · 0.85

Calls 12

prepareArgumentsFunction · 0.85
withFileLockFunction · 0.85
stripBomFunction · 0.85
detectLineEndingFunction · 0.85
normalizeToLFFunction · 0.85
restoreLineEndingsFunction · 0.85
generateDiffStringFunction · 0.85
generateUnifiedPatchFunction · 0.85
statMethod · 0.65
readAllMethod · 0.65
writeMethod · 0.65

Tested by

no test coverage detected