@dexto/tools-filesystem 1.5.8 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/directory-approval.cjs +98 -0
- package/dist/directory-approval.d.ts +24 -0
- package/dist/directory-approval.d.ts.map +1 -0
- package/dist/directory-approval.integration.test.cjs +175 -390
- package/dist/directory-approval.integration.test.d.ts +14 -2
- package/dist/directory-approval.integration.test.d.ts.map +1 -0
- package/dist/directory-approval.integration.test.js +178 -390
- package/dist/directory-approval.js +63 -0
- package/dist/edit-file-tool.cjs +109 -120
- package/dist/edit-file-tool.d.ts +22 -9
- package/dist/edit-file-tool.d.ts.map +1 -0
- package/dist/edit-file-tool.js +116 -110
- package/dist/edit-file-tool.test.cjs +109 -29
- package/dist/edit-file-tool.test.d.ts +7 -2
- package/dist/edit-file-tool.test.d.ts.map +1 -0
- package/dist/edit-file-tool.test.js +109 -29
- package/dist/error-codes.cjs +4 -0
- package/dist/error-codes.d.ts +6 -3
- package/dist/error-codes.d.ts.map +1 -0
- package/dist/error-codes.js +4 -0
- package/dist/errors.cjs +48 -0
- package/dist/errors.d.ts +20 -7
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +48 -0
- package/dist/file-tool-types.d.ts +8 -40
- package/dist/file-tool-types.d.ts.map +1 -0
- package/dist/filesystem-service.cjs +325 -10
- package/dist/filesystem-service.d.ts +41 -12
- package/dist/filesystem-service.d.ts.map +1 -0
- package/dist/filesystem-service.js +326 -11
- package/dist/filesystem-service.test.cjs +10 -2
- package/dist/filesystem-service.test.d.ts +7 -2
- package/dist/filesystem-service.test.d.ts.map +1 -0
- package/dist/filesystem-service.test.js +10 -2
- package/dist/glob-files-tool.cjs +32 -46
- package/dist/glob-files-tool.d.ts +19 -9
- package/dist/glob-files-tool.d.ts.map +1 -0
- package/dist/glob-files-tool.js +33 -47
- package/dist/grep-content-tool.cjs +40 -45
- package/dist/grep-content-tool.d.ts +28 -9
- package/dist/grep-content-tool.d.ts.map +1 -0
- package/dist/grep-content-tool.js +41 -46
- package/dist/index.cjs +6 -3
- package/dist/index.d.cts +852 -14
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -2
- package/dist/path-validator.cjs +28 -2
- package/dist/path-validator.d.ts +20 -9
- package/dist/path-validator.d.ts.map +1 -0
- package/dist/path-validator.js +28 -2
- package/dist/path-validator.test.d.ts +7 -2
- package/dist/path-validator.test.d.ts.map +1 -0
- package/dist/read-file-tool.cjs +26 -59
- package/dist/read-file-tool.d.ts +19 -9
- package/dist/read-file-tool.d.ts.map +1 -0
- package/dist/read-file-tool.js +27 -50
- package/dist/tool-factory-config.cjs +61 -0
- package/dist/{tool-provider.d.ts → tool-factory-config.d.ts} +13 -30
- package/dist/tool-factory-config.d.ts.map +1 -0
- package/dist/tool-factory-config.js +36 -0
- package/dist/tool-factory.cjs +123 -0
- package/dist/tool-factory.d.ts +4 -0
- package/dist/tool-factory.d.ts.map +1 -0
- package/dist/tool-factory.js +102 -0
- package/dist/types.d.ts +82 -18
- package/dist/types.d.ts.map +1 -0
- package/dist/write-file-tool.cjs +93 -99
- package/dist/write-file-tool.d.ts +22 -9
- package/dist/write-file-tool.d.ts.map +1 -0
- package/dist/write-file-tool.js +97 -91
- package/dist/write-file-tool.test.cjs +139 -33
- package/dist/write-file-tool.test.d.ts +7 -2
- package/dist/write-file-tool.test.d.ts.map +1 -0
- package/dist/write-file-tool.test.js +139 -33
- package/package.json +5 -4
- package/dist/directory-approval.integration.test.d.cts +0 -2
- package/dist/edit-file-tool.d.cts +0 -17
- package/dist/edit-file-tool.test.d.cts +0 -2
- package/dist/error-codes.d.cts +0 -32
- package/dist/errors.d.cts +0 -112
- package/dist/file-tool-types.d.cts +0 -46
- package/dist/filesystem-service.d.cts +0 -112
- package/dist/filesystem-service.test.d.cts +0 -2
- package/dist/glob-files-tool.d.cts +0 -17
- package/dist/grep-content-tool.d.cts +0 -17
- package/dist/path-validator.d.cts +0 -97
- package/dist/path-validator.test.d.cts +0 -2
- package/dist/read-file-tool.d.cts +0 -17
- package/dist/tool-provider.cjs +0 -123
- package/dist/tool-provider.d.cts +0 -77
- package/dist/tool-provider.js +0 -99
- package/dist/types.d.cts +0 -178
- package/dist/write-file-tool.d.cts +0 -17
- package/dist/write-file-tool.test.d.cts +0 -2
package/dist/write-file-tool.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,33 +15,25 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
var write_file_tool_exports = {};
|
|
30
20
|
__export(write_file_tool_exports, {
|
|
31
21
|
createWriteFileTool: () => createWriteFileTool
|
|
32
22
|
});
|
|
33
23
|
module.exports = __toCommonJS(write_file_tool_exports);
|
|
34
|
-
var path = __toESM(require("node:path"), 1);
|
|
35
24
|
var import_node_crypto = require("node:crypto");
|
|
36
25
|
var import_zod = require("zod");
|
|
37
26
|
var import_diff = require("diff");
|
|
38
27
|
var import_core = require("@dexto/core");
|
|
39
28
|
var import_error_codes = require("./error-codes.js");
|
|
29
|
+
var import_directory_approval = require("./directory-approval.js");
|
|
40
30
|
const previewContentHashCache = /* @__PURE__ */ new Map();
|
|
41
31
|
const FILE_NOT_EXISTS_MARKER = null;
|
|
42
32
|
function computeContentHash(content) {
|
|
43
33
|
return (0, import_node_crypto.createHash)("sha256").update(content, "utf8").digest("hex");
|
|
44
34
|
}
|
|
45
35
|
const WriteFileInputSchema = import_zod.z.object({
|
|
46
|
-
file_path: import_zod.z.string().describe("Absolute path where the file should be written"),
|
|
36
|
+
file_path: import_zod.z.string().min(1).describe("Absolute path where the file should be written"),
|
|
47
37
|
content: import_zod.z.string().describe("Content to write to the file"),
|
|
48
38
|
create_dirs: import_zod.z.boolean().optional().default(false).describe("Create parent directories if they don't exist (default: false)"),
|
|
49
39
|
encoding: import_zod.z.enum(["utf-8", "ascii", "latin1", "utf16le"]).optional().default("utf-8").describe("File encoding (default: utf-8)")
|
|
@@ -56,101 +46,97 @@ function generateDiffPreview(filePath, originalContent, newContent) {
|
|
|
56
46
|
const deletions = (unified.match(/^-[^-]/gm) || []).length;
|
|
57
47
|
return {
|
|
58
48
|
type: "diff",
|
|
49
|
+
title: "Update file",
|
|
59
50
|
unified,
|
|
60
51
|
filename: filePath,
|
|
61
52
|
additions,
|
|
62
53
|
deletions
|
|
63
54
|
};
|
|
64
55
|
}
|
|
65
|
-
function createWriteFileTool(
|
|
66
|
-
|
|
67
|
-
let pendingApprovalParentDir;
|
|
68
|
-
return {
|
|
56
|
+
function createWriteFileTool(getFileSystemService) {
|
|
57
|
+
return (0, import_core.defineTool)({
|
|
69
58
|
id: "write_file",
|
|
59
|
+
aliases: ["write"],
|
|
70
60
|
description: "Write content to a file. Creates a new file or overwrites existing file. Automatically creates backup of existing files before overwriting. Use create_dirs to create parent directories. Requires approval for all write operations. Returns success status, path, bytes written, and backup path if applicable.",
|
|
71
61
|
inputSchema: WriteFileInputSchema,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
62
|
+
...(0, import_directory_approval.createDirectoryAccessApprovalHandlers)({
|
|
63
|
+
toolName: "write_file",
|
|
64
|
+
operation: "write",
|
|
65
|
+
inputSchema: WriteFileInputSchema,
|
|
66
|
+
getFileSystemService,
|
|
67
|
+
resolvePaths: (input, fileSystemService) => (0, import_directory_approval.resolveFilePath)(fileSystemService.getWorkingDirectory(), input.file_path)
|
|
68
|
+
}),
|
|
69
|
+
presentation: {
|
|
70
|
+
describeHeader: (input) => (0, import_core.createLocalToolCallHeader)({
|
|
71
|
+
title: "Write",
|
|
72
|
+
argsText: (0, import_core.truncateForHeader)(input.file_path, 140)
|
|
73
|
+
}),
|
|
74
|
+
/**
|
|
75
|
+
* Generate preview for approval UI - shows diff or file creation info
|
|
76
|
+
* Stores content hash for change detection in execute phase.
|
|
77
|
+
*/
|
|
78
|
+
preview: async (input, context) => {
|
|
79
|
+
const { file_path, content } = input;
|
|
80
|
+
const resolvedFileSystemService = await getFileSystemService(context);
|
|
81
|
+
const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
|
|
82
|
+
resolvedFileSystemService.getWorkingDirectory(),
|
|
83
|
+
file_path
|
|
84
|
+
);
|
|
85
|
+
try {
|
|
86
|
+
let originalContent;
|
|
87
|
+
try {
|
|
88
|
+
const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
|
|
89
|
+
originalContent = originalFile.content;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.INVALID_PATH) {
|
|
92
|
+
const originalFile = await resolvedFileSystemService.readFileForToolPreview(
|
|
93
|
+
resolvedPath
|
|
94
|
+
);
|
|
95
|
+
originalContent = originalFile.content;
|
|
96
|
+
} else {
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (context.toolCallId) {
|
|
101
|
+
previewContentHashCache.set(
|
|
102
|
+
context.toolCallId,
|
|
103
|
+
computeContentHash(originalContent)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return generateDiffPreview(resolvedPath, originalContent, content);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
|
|
109
|
+
if (context.toolCallId) {
|
|
110
|
+
previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
|
|
111
|
+
}
|
|
112
|
+
const lineCount = content.split("\n").length;
|
|
113
|
+
const preview = {
|
|
114
|
+
type: "file",
|
|
115
|
+
title: "Create file",
|
|
116
|
+
path: resolvedPath,
|
|
117
|
+
operation: "create",
|
|
118
|
+
size: Buffer.byteLength(content, "utf8"),
|
|
119
|
+
lineCount,
|
|
120
|
+
content
|
|
121
|
+
// Include content for approval preview
|
|
122
|
+
};
|
|
123
|
+
return preview;
|
|
132
124
|
}
|
|
133
|
-
|
|
134
|
-
const preview = {
|
|
135
|
-
type: "file",
|
|
136
|
-
path: file_path,
|
|
137
|
-
operation: "create",
|
|
138
|
-
size: content.length,
|
|
139
|
-
lineCount,
|
|
140
|
-
content
|
|
141
|
-
// Include content for approval preview
|
|
142
|
-
};
|
|
143
|
-
return preview;
|
|
125
|
+
throw error;
|
|
144
126
|
}
|
|
145
|
-
throw error;
|
|
146
127
|
}
|
|
147
128
|
},
|
|
148
|
-
|
|
129
|
+
async execute(input, context) {
|
|
130
|
+
const resolvedFileSystemService = await getFileSystemService(context);
|
|
149
131
|
const { file_path, content, create_dirs, encoding } = input;
|
|
132
|
+
const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
|
|
133
|
+
resolvedFileSystemService.getWorkingDirectory(),
|
|
134
|
+
file_path
|
|
135
|
+
);
|
|
150
136
|
let originalContent = null;
|
|
151
137
|
let fileExistsNow = false;
|
|
152
138
|
try {
|
|
153
|
-
const originalFile = await
|
|
139
|
+
const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
|
|
154
140
|
originalContent = originalFile.content;
|
|
155
141
|
fileExistsNow = true;
|
|
156
142
|
} catch (error) {
|
|
@@ -161,24 +147,30 @@ function createWriteFileTool(options) {
|
|
|
161
147
|
throw error;
|
|
162
148
|
}
|
|
163
149
|
}
|
|
164
|
-
if (context
|
|
150
|
+
if (context.toolCallId && previewContentHashCache.has(context.toolCallId)) {
|
|
165
151
|
const expectedHash = previewContentHashCache.get(context.toolCallId);
|
|
166
152
|
previewContentHashCache.delete(context.toolCallId);
|
|
167
153
|
if (expectedHash === FILE_NOT_EXISTS_MARKER) {
|
|
168
154
|
if (fileExistsNow) {
|
|
169
|
-
throw import_core.ToolError.fileModifiedSincePreview("write_file",
|
|
155
|
+
throw import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
|
|
170
156
|
}
|
|
171
157
|
} else if (expectedHash !== null) {
|
|
172
158
|
if (!fileExistsNow) {
|
|
173
|
-
throw import_core.ToolError.fileModifiedSincePreview("write_file",
|
|
159
|
+
throw import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
|
|
160
|
+
}
|
|
161
|
+
if (originalContent === null) {
|
|
162
|
+
throw import_core.ToolError.executionFailed(
|
|
163
|
+
"write_file",
|
|
164
|
+
"Expected original file content when fileExistsNow is true"
|
|
165
|
+
);
|
|
174
166
|
}
|
|
175
167
|
const currentHash = computeContentHash(originalContent);
|
|
176
168
|
if (expectedHash !== currentHash) {
|
|
177
|
-
throw import_core.ToolError.fileModifiedSincePreview("write_file",
|
|
169
|
+
throw import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
|
|
178
170
|
}
|
|
179
171
|
}
|
|
180
172
|
}
|
|
181
|
-
const result = await
|
|
173
|
+
const result = await resolvedFileSystemService.writeFile(resolvedPath, content, {
|
|
182
174
|
createDirs: create_dirs,
|
|
183
175
|
encoding
|
|
184
176
|
});
|
|
@@ -187,13 +179,15 @@ function createWriteFileTool(options) {
|
|
|
187
179
|
const lineCount = content.split("\n").length;
|
|
188
180
|
_display = {
|
|
189
181
|
type: "file",
|
|
190
|
-
|
|
182
|
+
title: "Create file",
|
|
183
|
+
path: resolvedPath,
|
|
191
184
|
operation: "create",
|
|
192
185
|
size: result.bytesWritten,
|
|
193
|
-
lineCount
|
|
186
|
+
lineCount,
|
|
187
|
+
content
|
|
194
188
|
};
|
|
195
189
|
} else {
|
|
196
|
-
_display = generateDiffPreview(
|
|
190
|
+
_display = generateDiffPreview(resolvedPath, originalContent, content);
|
|
197
191
|
}
|
|
198
192
|
return {
|
|
199
193
|
success: result.success,
|
|
@@ -203,7 +197,7 @@ function createWriteFileTool(options) {
|
|
|
203
197
|
_display
|
|
204
198
|
};
|
|
205
199
|
}
|
|
206
|
-
};
|
|
200
|
+
});
|
|
207
201
|
}
|
|
208
202
|
// Annotate the CommonJS export names for ESM import in node:
|
|
209
203
|
0 && (module.exports = {
|
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
import { InternalTool } from '@dexto/core';
|
|
2
|
-
import { FileToolOptions } from './file-tool-types.js';
|
|
3
|
-
import './filesystem-service.js';
|
|
4
|
-
import './types.js';
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* Write File Tool
|
|
8
3
|
*
|
|
9
4
|
* Internal tool for writing content to files (requires approval)
|
|
10
5
|
*/
|
|
11
|
-
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import type { Tool } from '@dexto/core';
|
|
8
|
+
import type { FileSystemServiceGetter } from './file-tool-types.js';
|
|
9
|
+
declare const WriteFileInputSchema: z.ZodObject<{
|
|
10
|
+
file_path: z.ZodString;
|
|
11
|
+
content: z.ZodString;
|
|
12
|
+
create_dirs: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
13
|
+
encoding: z.ZodDefault<z.ZodOptional<z.ZodEnum<["utf-8", "ascii", "latin1", "utf16le"]>>>;
|
|
14
|
+
}, "strict", z.ZodTypeAny, {
|
|
15
|
+
content: string;
|
|
16
|
+
encoding: "ascii" | "utf-8" | "utf16le" | "latin1";
|
|
17
|
+
file_path: string;
|
|
18
|
+
create_dirs: boolean;
|
|
19
|
+
}, {
|
|
20
|
+
content: string;
|
|
21
|
+
file_path: string;
|
|
22
|
+
encoding?: "ascii" | "utf-8" | "utf16le" | "latin1" | undefined;
|
|
23
|
+
create_dirs?: boolean | undefined;
|
|
24
|
+
}>;
|
|
12
25
|
/**
|
|
13
26
|
* Create the write_file internal tool with directory approval support
|
|
14
27
|
*/
|
|
15
|
-
declare function createWriteFileTool(
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
export declare function createWriteFileTool(getFileSystemService: FileSystemServiceGetter): Tool<typeof WriteFileInputSchema>;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=write-file-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-file-tool.d.ts","sourceRoot":"","sources":["../src/write-file-tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,aAAa,CAAC;AAG9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAuBpE,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;EAeb,CAAC;AA0Bd;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,oBAAoB,EAAE,uBAAuB,GAC9C,IAAI,CAAC,OAAO,oBAAoB,CAAC,CAqMnC"}
|
package/dist/write-file-tool.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import { createHash } from "node:crypto";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
import { createPatch } from "diff";
|
|
5
4
|
import {
|
|
5
|
+
createLocalToolCallHeader,
|
|
6
6
|
DextoRuntimeError,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
ToolError,
|
|
8
|
+
defineTool,
|
|
9
|
+
truncateForHeader
|
|
9
10
|
} from "@dexto/core";
|
|
10
11
|
import { FileSystemErrorCode } from "./error-codes.js";
|
|
12
|
+
import { createDirectoryAccessApprovalHandlers, resolveFilePath } from "./directory-approval.js";
|
|
11
13
|
const previewContentHashCache = /* @__PURE__ */ new Map();
|
|
12
14
|
const FILE_NOT_EXISTS_MARKER = null;
|
|
13
15
|
function computeContentHash(content) {
|
|
14
16
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
15
17
|
}
|
|
16
18
|
const WriteFileInputSchema = z.object({
|
|
17
|
-
file_path: z.string().describe("Absolute path where the file should be written"),
|
|
19
|
+
file_path: z.string().min(1).describe("Absolute path where the file should be written"),
|
|
18
20
|
content: z.string().describe("Content to write to the file"),
|
|
19
21
|
create_dirs: z.boolean().optional().default(false).describe("Create parent directories if they don't exist (default: false)"),
|
|
20
22
|
encoding: z.enum(["utf-8", "ascii", "latin1", "utf16le"]).optional().default("utf-8").describe("File encoding (default: utf-8)")
|
|
@@ -27,101 +29,97 @@ function generateDiffPreview(filePath, originalContent, newContent) {
|
|
|
27
29
|
const deletions = (unified.match(/^-[^-]/gm) || []).length;
|
|
28
30
|
return {
|
|
29
31
|
type: "diff",
|
|
32
|
+
title: "Update file",
|
|
30
33
|
unified,
|
|
31
34
|
filename: filePath,
|
|
32
35
|
additions,
|
|
33
36
|
deletions
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
|
-
function createWriteFileTool(
|
|
37
|
-
|
|
38
|
-
let pendingApprovalParentDir;
|
|
39
|
-
return {
|
|
39
|
+
function createWriteFileTool(getFileSystemService) {
|
|
40
|
+
return defineTool({
|
|
40
41
|
id: "write_file",
|
|
42
|
+
aliases: ["write"],
|
|
41
43
|
description: "Write content to a file. Creates a new file or overwrites existing file. Automatically creates backup of existing files before overwriting. Use create_dirs to create parent directories. Requires approval for all write operations. Returns success status, path, bytes written, and backup path if applicable.",
|
|
42
44
|
inputSchema: WriteFileInputSchema,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
45
|
+
...createDirectoryAccessApprovalHandlers({
|
|
46
|
+
toolName: "write_file",
|
|
47
|
+
operation: "write",
|
|
48
|
+
inputSchema: WriteFileInputSchema,
|
|
49
|
+
getFileSystemService,
|
|
50
|
+
resolvePaths: (input, fileSystemService) => resolveFilePath(fileSystemService.getWorkingDirectory(), input.file_path)
|
|
51
|
+
}),
|
|
52
|
+
presentation: {
|
|
53
|
+
describeHeader: (input) => createLocalToolCallHeader({
|
|
54
|
+
title: "Write",
|
|
55
|
+
argsText: truncateForHeader(input.file_path, 140)
|
|
56
|
+
}),
|
|
57
|
+
/**
|
|
58
|
+
* Generate preview for approval UI - shows diff or file creation info
|
|
59
|
+
* Stores content hash for change detection in execute phase.
|
|
60
|
+
*/
|
|
61
|
+
preview: async (input, context) => {
|
|
62
|
+
const { file_path, content } = input;
|
|
63
|
+
const resolvedFileSystemService = await getFileSystemService(context);
|
|
64
|
+
const { path: resolvedPath } = resolveFilePath(
|
|
65
|
+
resolvedFileSystemService.getWorkingDirectory(),
|
|
66
|
+
file_path
|
|
67
|
+
);
|
|
68
|
+
try {
|
|
69
|
+
let originalContent;
|
|
70
|
+
try {
|
|
71
|
+
const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
|
|
72
|
+
originalContent = originalFile.content;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error instanceof DextoRuntimeError && error.code === FileSystemErrorCode.INVALID_PATH) {
|
|
75
|
+
const originalFile = await resolvedFileSystemService.readFileForToolPreview(
|
|
76
|
+
resolvedPath
|
|
77
|
+
);
|
|
78
|
+
originalContent = originalFile.content;
|
|
79
|
+
} else {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (context.toolCallId) {
|
|
84
|
+
previewContentHashCache.set(
|
|
85
|
+
context.toolCallId,
|
|
86
|
+
computeContentHash(originalContent)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return generateDiffPreview(resolvedPath, originalContent, content);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof DextoRuntimeError && error.code === FileSystemErrorCode.FILE_NOT_FOUND) {
|
|
92
|
+
if (context.toolCallId) {
|
|
93
|
+
previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
|
|
94
|
+
}
|
|
95
|
+
const lineCount = content.split("\n").length;
|
|
96
|
+
const preview = {
|
|
97
|
+
type: "file",
|
|
98
|
+
title: "Create file",
|
|
99
|
+
path: resolvedPath,
|
|
100
|
+
operation: "create",
|
|
101
|
+
size: Buffer.byteLength(content, "utf8"),
|
|
102
|
+
lineCount,
|
|
103
|
+
content
|
|
104
|
+
// Include content for approval preview
|
|
105
|
+
};
|
|
106
|
+
return preview;
|
|
103
107
|
}
|
|
104
|
-
|
|
105
|
-
const preview = {
|
|
106
|
-
type: "file",
|
|
107
|
-
path: file_path,
|
|
108
|
-
operation: "create",
|
|
109
|
-
size: content.length,
|
|
110
|
-
lineCount,
|
|
111
|
-
content
|
|
112
|
-
// Include content for approval preview
|
|
113
|
-
};
|
|
114
|
-
return preview;
|
|
108
|
+
throw error;
|
|
115
109
|
}
|
|
116
|
-
throw error;
|
|
117
110
|
}
|
|
118
111
|
},
|
|
119
|
-
|
|
112
|
+
async execute(input, context) {
|
|
113
|
+
const resolvedFileSystemService = await getFileSystemService(context);
|
|
120
114
|
const { file_path, content, create_dirs, encoding } = input;
|
|
115
|
+
const { path: resolvedPath } = resolveFilePath(
|
|
116
|
+
resolvedFileSystemService.getWorkingDirectory(),
|
|
117
|
+
file_path
|
|
118
|
+
);
|
|
121
119
|
let originalContent = null;
|
|
122
120
|
let fileExistsNow = false;
|
|
123
121
|
try {
|
|
124
|
-
const originalFile = await
|
|
122
|
+
const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
|
|
125
123
|
originalContent = originalFile.content;
|
|
126
124
|
fileExistsNow = true;
|
|
127
125
|
} catch (error) {
|
|
@@ -132,24 +130,30 @@ function createWriteFileTool(options) {
|
|
|
132
130
|
throw error;
|
|
133
131
|
}
|
|
134
132
|
}
|
|
135
|
-
if (context
|
|
133
|
+
if (context.toolCallId && previewContentHashCache.has(context.toolCallId)) {
|
|
136
134
|
const expectedHash = previewContentHashCache.get(context.toolCallId);
|
|
137
135
|
previewContentHashCache.delete(context.toolCallId);
|
|
138
136
|
if (expectedHash === FILE_NOT_EXISTS_MARKER) {
|
|
139
137
|
if (fileExistsNow) {
|
|
140
|
-
throw ToolError.fileModifiedSincePreview("write_file",
|
|
138
|
+
throw ToolError.fileModifiedSincePreview("write_file", resolvedPath);
|
|
141
139
|
}
|
|
142
140
|
} else if (expectedHash !== null) {
|
|
143
141
|
if (!fileExistsNow) {
|
|
144
|
-
throw ToolError.fileModifiedSincePreview("write_file",
|
|
142
|
+
throw ToolError.fileModifiedSincePreview("write_file", resolvedPath);
|
|
143
|
+
}
|
|
144
|
+
if (originalContent === null) {
|
|
145
|
+
throw ToolError.executionFailed(
|
|
146
|
+
"write_file",
|
|
147
|
+
"Expected original file content when fileExistsNow is true"
|
|
148
|
+
);
|
|
145
149
|
}
|
|
146
150
|
const currentHash = computeContentHash(originalContent);
|
|
147
151
|
if (expectedHash !== currentHash) {
|
|
148
|
-
throw ToolError.fileModifiedSincePreview("write_file",
|
|
152
|
+
throw ToolError.fileModifiedSincePreview("write_file", resolvedPath);
|
|
149
153
|
}
|
|
150
154
|
}
|
|
151
155
|
}
|
|
152
|
-
const result = await
|
|
156
|
+
const result = await resolvedFileSystemService.writeFile(resolvedPath, content, {
|
|
153
157
|
createDirs: create_dirs,
|
|
154
158
|
encoding
|
|
155
159
|
});
|
|
@@ -158,13 +162,15 @@ function createWriteFileTool(options) {
|
|
|
158
162
|
const lineCount = content.split("\n").length;
|
|
159
163
|
_display = {
|
|
160
164
|
type: "file",
|
|
161
|
-
|
|
165
|
+
title: "Create file",
|
|
166
|
+
path: resolvedPath,
|
|
162
167
|
operation: "create",
|
|
163
168
|
size: result.bytesWritten,
|
|
164
|
-
lineCount
|
|
169
|
+
lineCount,
|
|
170
|
+
content
|
|
165
171
|
};
|
|
166
172
|
} else {
|
|
167
|
-
_display = generateDiffPreview(
|
|
173
|
+
_display = generateDiffPreview(resolvedPath, originalContent, content);
|
|
168
174
|
}
|
|
169
175
|
return {
|
|
170
176
|
success: result.success,
|
|
@@ -174,7 +180,7 @@ function createWriteFileTool(options) {
|
|
|
174
180
|
_display
|
|
175
181
|
};
|
|
176
182
|
}
|
|
177
|
-
};
|
|
183
|
+
});
|
|
178
184
|
}
|
|
179
185
|
export {
|
|
180
186
|
createWriteFileTool
|