@dexto/tools-filesystem 1.5.8 → 1.6.0
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 +94 -0
- package/dist/directory-approval.d.cts +22 -0
- package/dist/directory-approval.d.ts +20 -0
- package/dist/directory-approval.d.ts.map +1 -0
- package/dist/directory-approval.integration.test.cjs +303 -269
- 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 +309 -270
- package/dist/directory-approval.js +59 -0
- package/dist/edit-file-tool.cjs +57 -90
- package/dist/edit-file-tool.d.cts +20 -3
- 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 +53 -76
- package/dist/edit-file-tool.test.cjs +66 -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 +66 -29
- package/dist/error-codes.d.ts +2 -3
- package/dist/error-codes.d.ts.map +1 -0
- package/dist/errors.d.ts +4 -7
- package/dist/errors.d.ts.map +1 -0
- package/dist/file-tool-types.d.cts +7 -35
- package/dist/file-tool-types.d.ts +8 -40
- package/dist/file-tool-types.d.ts.map +1 -0
- package/dist/filesystem-service.cjs +18 -1
- package/dist/filesystem-service.d.cts +11 -6
- package/dist/filesystem-service.d.ts +14 -12
- package/dist/filesystem-service.d.ts.map +1 -0
- package/dist/filesystem-service.js +18 -1
- 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 +22 -47
- package/dist/glob-files-tool.d.cts +17 -3
- 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 +23 -48
- package/dist/grep-content-tool.cjs +29 -46
- package/dist/grep-content-tool.d.cts +26 -3
- 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 +30 -47
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +10 -5
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/path-validator.d.cts +2 -2
- package/dist/path-validator.d.ts +6 -9
- package/dist/path-validator.d.ts.map +1 -0
- 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 +21 -60
- package/dist/read-file-tool.d.cts +17 -3
- 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 +22 -51
- package/dist/tool-factory-config.cjs +61 -0
- package/dist/{tool-provider.d.ts → tool-factory-config.d.cts} +9 -23
- package/dist/{tool-provider.d.cts → 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 +102 -0
- package/dist/tool-factory.d.cts +7 -0
- package/dist/tool-factory.d.ts +4 -0
- package/dist/tool-factory.d.ts.map +1 -0
- package/dist/tool-factory.js +81 -0
- package/dist/types.d.ts +17 -18
- package/dist/types.d.ts.map +1 -0
- package/dist/write-file-tool.cjs +45 -73
- package/dist/write-file-tool.d.cts +20 -3
- 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 +46 -68
- package/dist/write-file-tool.test.cjs +76 -32
- 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 +76 -32
- package/package.json +4 -3
- package/dist/tool-provider.cjs +0 -123
- package/dist/tool-provider.js +0 -99
|
@@ -29,13 +29,25 @@ var import_write_file_tool = require("./write-file-tool.js");
|
|
|
29
29
|
var import_filesystem_service = require("./filesystem-service.js");
|
|
30
30
|
var import_core = require("@dexto/core");
|
|
31
31
|
var import_core2 = require("@dexto/core");
|
|
32
|
-
const createMockLogger = () =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
const createMockLogger = () => {
|
|
33
|
+
const logger = {
|
|
34
|
+
debug: import_vitest.vi.fn(),
|
|
35
|
+
silly: import_vitest.vi.fn(),
|
|
36
|
+
info: import_vitest.vi.fn(),
|
|
37
|
+
warn: import_vitest.vi.fn(),
|
|
38
|
+
error: import_vitest.vi.fn(),
|
|
39
|
+
trackException: import_vitest.vi.fn(),
|
|
40
|
+
createChild: import_vitest.vi.fn(() => logger),
|
|
41
|
+
setLevel: import_vitest.vi.fn(),
|
|
42
|
+
getLevel: import_vitest.vi.fn(() => "debug"),
|
|
43
|
+
getLogFilePath: import_vitest.vi.fn(() => null),
|
|
44
|
+
destroy: import_vitest.vi.fn(async () => void 0)
|
|
45
|
+
};
|
|
46
|
+
return logger;
|
|
47
|
+
};
|
|
48
|
+
function createToolContext(logger, overrides = {}) {
|
|
49
|
+
return { logger, ...overrides };
|
|
50
|
+
}
|
|
39
51
|
(0, import_vitest.describe)("write_file tool", () => {
|
|
40
52
|
let mockLogger;
|
|
41
53
|
let tempDir;
|
|
@@ -67,7 +79,7 @@ const createMockLogger = () => ({
|
|
|
67
79
|
});
|
|
68
80
|
(0, import_vitest.describe)("File Modification Detection - Existing Files", () => {
|
|
69
81
|
(0, import_vitest.it)("should succeed when existing file is not modified between preview and execute", async () => {
|
|
70
|
-
const tool = (0, import_write_file_tool.createWriteFileTool)(
|
|
82
|
+
const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
|
|
71
83
|
const testFile = path.join(tempDir, "test.txt");
|
|
72
84
|
await fs.writeFile(testFile, "original content");
|
|
73
85
|
const toolCallId = "test-call-123";
|
|
@@ -75,17 +87,24 @@ const createMockLogger = () => ({
|
|
|
75
87
|
file_path: testFile,
|
|
76
88
|
content: "new content"
|
|
77
89
|
};
|
|
78
|
-
const
|
|
90
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
91
|
+
const preview = await tool.generatePreview(
|
|
92
|
+
parsedInput,
|
|
93
|
+
createToolContext(mockLogger, { toolCallId })
|
|
94
|
+
);
|
|
79
95
|
(0, import_vitest.expect)(preview).toBeDefined();
|
|
80
96
|
(0, import_vitest.expect)(preview?.type).toBe("diff");
|
|
81
|
-
const result = await tool.execute(
|
|
97
|
+
const result = await tool.execute(
|
|
98
|
+
parsedInput,
|
|
99
|
+
createToolContext(mockLogger, { toolCallId })
|
|
100
|
+
);
|
|
82
101
|
(0, import_vitest.expect)(result.success).toBe(true);
|
|
83
102
|
(0, import_vitest.expect)(result.path).toBe(testFile);
|
|
84
103
|
const content = await fs.readFile(testFile, "utf-8");
|
|
85
104
|
(0, import_vitest.expect)(content).toBe("new content");
|
|
86
105
|
});
|
|
87
106
|
(0, import_vitest.it)("should fail when existing file is modified between preview and execute", async () => {
|
|
88
|
-
const tool = (0, import_write_file_tool.createWriteFileTool)(
|
|
107
|
+
const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
|
|
89
108
|
const testFile = path.join(tempDir, "test.txt");
|
|
90
109
|
await fs.writeFile(testFile, "original content");
|
|
91
110
|
const toolCallId = "test-call-456";
|
|
@@ -93,10 +112,11 @@ const createMockLogger = () => ({
|
|
|
93
112
|
file_path: testFile,
|
|
94
113
|
content: "new content"
|
|
95
114
|
};
|
|
96
|
-
|
|
115
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
116
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
97
117
|
await fs.writeFile(testFile, "user modified this");
|
|
98
118
|
try {
|
|
99
|
-
await tool.execute(
|
|
119
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
100
120
|
import_vitest.expect.fail("Should have thrown an error");
|
|
101
121
|
} catch (error) {
|
|
102
122
|
(0, import_vitest.expect)(error).toBeInstanceOf(import_core2.DextoRuntimeError);
|
|
@@ -108,7 +128,7 @@ const createMockLogger = () => ({
|
|
|
108
128
|
(0, import_vitest.expect)(content).toBe("user modified this");
|
|
109
129
|
});
|
|
110
130
|
(0, import_vitest.it)("should fail when existing file is deleted between preview and execute", async () => {
|
|
111
|
-
const tool = (0, import_write_file_tool.createWriteFileTool)(
|
|
131
|
+
const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
|
|
112
132
|
const testFile = path.join(tempDir, "test.txt");
|
|
113
133
|
await fs.writeFile(testFile, "original content");
|
|
114
134
|
const toolCallId = "test-call-deleted";
|
|
@@ -116,10 +136,11 @@ const createMockLogger = () => ({
|
|
|
116
136
|
file_path: testFile,
|
|
117
137
|
content: "new content"
|
|
118
138
|
};
|
|
119
|
-
|
|
139
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
140
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
120
141
|
await fs.unlink(testFile);
|
|
121
142
|
try {
|
|
122
|
-
await tool.execute(
|
|
143
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
123
144
|
import_vitest.expect.fail("Should have thrown an error");
|
|
124
145
|
} catch (error) {
|
|
125
146
|
(0, import_vitest.expect)(error).toBeInstanceOf(import_core2.DextoRuntimeError);
|
|
@@ -131,35 +152,46 @@ const createMockLogger = () => ({
|
|
|
131
152
|
});
|
|
132
153
|
(0, import_vitest.describe)("File Modification Detection - New Files", () => {
|
|
133
154
|
(0, import_vitest.it)("should succeed when creating new file that still does not exist", async () => {
|
|
134
|
-
const tool = (0, import_write_file_tool.createWriteFileTool)(
|
|
155
|
+
const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
|
|
135
156
|
const testFile = path.join(tempDir, "new-file.txt");
|
|
136
157
|
const toolCallId = "test-call-new";
|
|
137
158
|
const input = {
|
|
138
159
|
file_path: testFile,
|
|
139
160
|
content: "brand new content"
|
|
140
161
|
};
|
|
141
|
-
const
|
|
162
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
163
|
+
const preview = await tool.generatePreview(
|
|
164
|
+
parsedInput,
|
|
165
|
+
createToolContext(mockLogger, { toolCallId })
|
|
166
|
+
);
|
|
142
167
|
(0, import_vitest.expect)(preview).toBeDefined();
|
|
143
168
|
(0, import_vitest.expect)(preview?.type).toBe("file");
|
|
144
169
|
(0, import_vitest.expect)(preview.operation).toBe("create");
|
|
145
|
-
const result = await tool.execute(
|
|
170
|
+
const result = await tool.execute(
|
|
171
|
+
parsedInput,
|
|
172
|
+
createToolContext(mockLogger, { toolCallId })
|
|
173
|
+
);
|
|
146
174
|
(0, import_vitest.expect)(result.success).toBe(true);
|
|
147
175
|
const content = await fs.readFile(testFile, "utf-8");
|
|
148
176
|
(0, import_vitest.expect)(content).toBe("brand new content");
|
|
149
177
|
});
|
|
150
178
|
(0, import_vitest.it)("should fail when file is created by someone else between preview and execute", async () => {
|
|
151
|
-
const tool = (0, import_write_file_tool.createWriteFileTool)(
|
|
179
|
+
const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
|
|
152
180
|
const testFile = path.join(tempDir, "race-condition.txt");
|
|
153
181
|
const toolCallId = "test-call-race";
|
|
154
182
|
const input = {
|
|
155
183
|
file_path: testFile,
|
|
156
184
|
content: "agent content"
|
|
157
185
|
};
|
|
158
|
-
const
|
|
186
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
187
|
+
const preview = await tool.generatePreview(
|
|
188
|
+
parsedInput,
|
|
189
|
+
createToolContext(mockLogger, { toolCallId })
|
|
190
|
+
);
|
|
159
191
|
(0, import_vitest.expect)(preview?.type).toBe("file");
|
|
160
192
|
await fs.writeFile(testFile, "someone else created this");
|
|
161
193
|
try {
|
|
162
|
-
await tool.execute(
|
|
194
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
163
195
|
import_vitest.expect.fail("Should have thrown an error");
|
|
164
196
|
} catch (error) {
|
|
165
197
|
(0, import_vitest.expect)(error).toBeInstanceOf(import_core2.DextoRuntimeError);
|
|
@@ -173,7 +205,7 @@ const createMockLogger = () => ({
|
|
|
173
205
|
});
|
|
174
206
|
(0, import_vitest.describe)("Cache Cleanup", () => {
|
|
175
207
|
(0, import_vitest.it)("should clean up hash cache after successful execution", async () => {
|
|
176
|
-
const tool = (0, import_write_file_tool.createWriteFileTool)(
|
|
208
|
+
const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
|
|
177
209
|
const testFile = path.join(tempDir, "test.txt");
|
|
178
210
|
await fs.writeFile(testFile, "original");
|
|
179
211
|
const toolCallId = "test-call-cleanup";
|
|
@@ -181,20 +213,28 @@ const createMockLogger = () => ({
|
|
|
181
213
|
file_path: testFile,
|
|
182
214
|
content: "first write"
|
|
183
215
|
};
|
|
184
|
-
|
|
185
|
-
await tool.
|
|
216
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
217
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
218
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
186
219
|
const input2 = {
|
|
187
220
|
file_path: testFile,
|
|
188
221
|
content: "second write"
|
|
189
222
|
};
|
|
190
|
-
|
|
191
|
-
|
|
223
|
+
const parsedInput2 = tool.inputSchema.parse(input2);
|
|
224
|
+
await tool.generatePreview(
|
|
225
|
+
parsedInput2,
|
|
226
|
+
createToolContext(mockLogger, { toolCallId })
|
|
227
|
+
);
|
|
228
|
+
const result = await tool.execute(
|
|
229
|
+
parsedInput2,
|
|
230
|
+
createToolContext(mockLogger, { toolCallId })
|
|
231
|
+
);
|
|
192
232
|
(0, import_vitest.expect)(result.success).toBe(true);
|
|
193
233
|
const content = await fs.readFile(testFile, "utf-8");
|
|
194
234
|
(0, import_vitest.expect)(content).toBe("second write");
|
|
195
235
|
});
|
|
196
236
|
(0, import_vitest.it)("should clean up hash cache after failed execution", async () => {
|
|
197
|
-
const tool = (0, import_write_file_tool.createWriteFileTool)(
|
|
237
|
+
const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
|
|
198
238
|
const testFile = path.join(tempDir, "test.txt");
|
|
199
239
|
await fs.writeFile(testFile, "original");
|
|
200
240
|
const toolCallId = "test-call-fail";
|
|
@@ -202,15 +242,19 @@ const createMockLogger = () => ({
|
|
|
202
242
|
file_path: testFile,
|
|
203
243
|
content: "new content"
|
|
204
244
|
};
|
|
205
|
-
|
|
245
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
246
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
206
247
|
await fs.writeFile(testFile, "modified");
|
|
207
248
|
try {
|
|
208
|
-
await tool.execute(
|
|
249
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
209
250
|
} catch {
|
|
210
251
|
}
|
|
211
252
|
await fs.writeFile(testFile, "reset content");
|
|
212
|
-
await tool.generatePreview(
|
|
213
|
-
const result = await tool.execute(
|
|
253
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
254
|
+
const result = await tool.execute(
|
|
255
|
+
parsedInput,
|
|
256
|
+
createToolContext(mockLogger, { toolCallId })
|
|
257
|
+
);
|
|
214
258
|
(0, import_vitest.expect)(result.success).toBe(true);
|
|
215
259
|
});
|
|
216
260
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-file-tool.test.d.ts","sourceRoot":"","sources":["../src/write-file-tool.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -6,13 +6,25 @@ import { createWriteFileTool } from "./write-file-tool.js";
|
|
|
6
6
|
import { FileSystemService } from "./filesystem-service.js";
|
|
7
7
|
import { ToolErrorCode } from "@dexto/core";
|
|
8
8
|
import { DextoRuntimeError } from "@dexto/core";
|
|
9
|
-
const createMockLogger = () =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
const createMockLogger = () => {
|
|
10
|
+
const logger = {
|
|
11
|
+
debug: vi.fn(),
|
|
12
|
+
silly: vi.fn(),
|
|
13
|
+
info: vi.fn(),
|
|
14
|
+
warn: vi.fn(),
|
|
15
|
+
error: vi.fn(),
|
|
16
|
+
trackException: vi.fn(),
|
|
17
|
+
createChild: vi.fn(() => logger),
|
|
18
|
+
setLevel: vi.fn(),
|
|
19
|
+
getLevel: vi.fn(() => "debug"),
|
|
20
|
+
getLogFilePath: vi.fn(() => null),
|
|
21
|
+
destroy: vi.fn(async () => void 0)
|
|
22
|
+
};
|
|
23
|
+
return logger;
|
|
24
|
+
};
|
|
25
|
+
function createToolContext(logger, overrides = {}) {
|
|
26
|
+
return { logger, ...overrides };
|
|
27
|
+
}
|
|
16
28
|
describe("write_file tool", () => {
|
|
17
29
|
let mockLogger;
|
|
18
30
|
let tempDir;
|
|
@@ -44,7 +56,7 @@ describe("write_file tool", () => {
|
|
|
44
56
|
});
|
|
45
57
|
describe("File Modification Detection - Existing Files", () => {
|
|
46
58
|
it("should succeed when existing file is not modified between preview and execute", async () => {
|
|
47
|
-
const tool = createWriteFileTool(
|
|
59
|
+
const tool = createWriteFileTool(async () => fileSystemService);
|
|
48
60
|
const testFile = path.join(tempDir, "test.txt");
|
|
49
61
|
await fs.writeFile(testFile, "original content");
|
|
50
62
|
const toolCallId = "test-call-123";
|
|
@@ -52,17 +64,24 @@ describe("write_file tool", () => {
|
|
|
52
64
|
file_path: testFile,
|
|
53
65
|
content: "new content"
|
|
54
66
|
};
|
|
55
|
-
const
|
|
67
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
68
|
+
const preview = await tool.generatePreview(
|
|
69
|
+
parsedInput,
|
|
70
|
+
createToolContext(mockLogger, { toolCallId })
|
|
71
|
+
);
|
|
56
72
|
expect(preview).toBeDefined();
|
|
57
73
|
expect(preview?.type).toBe("diff");
|
|
58
|
-
const result = await tool.execute(
|
|
74
|
+
const result = await tool.execute(
|
|
75
|
+
parsedInput,
|
|
76
|
+
createToolContext(mockLogger, { toolCallId })
|
|
77
|
+
);
|
|
59
78
|
expect(result.success).toBe(true);
|
|
60
79
|
expect(result.path).toBe(testFile);
|
|
61
80
|
const content = await fs.readFile(testFile, "utf-8");
|
|
62
81
|
expect(content).toBe("new content");
|
|
63
82
|
});
|
|
64
83
|
it("should fail when existing file is modified between preview and execute", async () => {
|
|
65
|
-
const tool = createWriteFileTool(
|
|
84
|
+
const tool = createWriteFileTool(async () => fileSystemService);
|
|
66
85
|
const testFile = path.join(tempDir, "test.txt");
|
|
67
86
|
await fs.writeFile(testFile, "original content");
|
|
68
87
|
const toolCallId = "test-call-456";
|
|
@@ -70,10 +89,11 @@ describe("write_file tool", () => {
|
|
|
70
89
|
file_path: testFile,
|
|
71
90
|
content: "new content"
|
|
72
91
|
};
|
|
73
|
-
|
|
92
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
93
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
74
94
|
await fs.writeFile(testFile, "user modified this");
|
|
75
95
|
try {
|
|
76
|
-
await tool.execute(
|
|
96
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
77
97
|
expect.fail("Should have thrown an error");
|
|
78
98
|
} catch (error) {
|
|
79
99
|
expect(error).toBeInstanceOf(DextoRuntimeError);
|
|
@@ -85,7 +105,7 @@ describe("write_file tool", () => {
|
|
|
85
105
|
expect(content).toBe("user modified this");
|
|
86
106
|
});
|
|
87
107
|
it("should fail when existing file is deleted between preview and execute", async () => {
|
|
88
|
-
const tool = createWriteFileTool(
|
|
108
|
+
const tool = createWriteFileTool(async () => fileSystemService);
|
|
89
109
|
const testFile = path.join(tempDir, "test.txt");
|
|
90
110
|
await fs.writeFile(testFile, "original content");
|
|
91
111
|
const toolCallId = "test-call-deleted";
|
|
@@ -93,10 +113,11 @@ describe("write_file tool", () => {
|
|
|
93
113
|
file_path: testFile,
|
|
94
114
|
content: "new content"
|
|
95
115
|
};
|
|
96
|
-
|
|
116
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
117
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
97
118
|
await fs.unlink(testFile);
|
|
98
119
|
try {
|
|
99
|
-
await tool.execute(
|
|
120
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
100
121
|
expect.fail("Should have thrown an error");
|
|
101
122
|
} catch (error) {
|
|
102
123
|
expect(error).toBeInstanceOf(DextoRuntimeError);
|
|
@@ -108,35 +129,46 @@ describe("write_file tool", () => {
|
|
|
108
129
|
});
|
|
109
130
|
describe("File Modification Detection - New Files", () => {
|
|
110
131
|
it("should succeed when creating new file that still does not exist", async () => {
|
|
111
|
-
const tool = createWriteFileTool(
|
|
132
|
+
const tool = createWriteFileTool(async () => fileSystemService);
|
|
112
133
|
const testFile = path.join(tempDir, "new-file.txt");
|
|
113
134
|
const toolCallId = "test-call-new";
|
|
114
135
|
const input = {
|
|
115
136
|
file_path: testFile,
|
|
116
137
|
content: "brand new content"
|
|
117
138
|
};
|
|
118
|
-
const
|
|
139
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
140
|
+
const preview = await tool.generatePreview(
|
|
141
|
+
parsedInput,
|
|
142
|
+
createToolContext(mockLogger, { toolCallId })
|
|
143
|
+
);
|
|
119
144
|
expect(preview).toBeDefined();
|
|
120
145
|
expect(preview?.type).toBe("file");
|
|
121
146
|
expect(preview.operation).toBe("create");
|
|
122
|
-
const result = await tool.execute(
|
|
147
|
+
const result = await tool.execute(
|
|
148
|
+
parsedInput,
|
|
149
|
+
createToolContext(mockLogger, { toolCallId })
|
|
150
|
+
);
|
|
123
151
|
expect(result.success).toBe(true);
|
|
124
152
|
const content = await fs.readFile(testFile, "utf-8");
|
|
125
153
|
expect(content).toBe("brand new content");
|
|
126
154
|
});
|
|
127
155
|
it("should fail when file is created by someone else between preview and execute", async () => {
|
|
128
|
-
const tool = createWriteFileTool(
|
|
156
|
+
const tool = createWriteFileTool(async () => fileSystemService);
|
|
129
157
|
const testFile = path.join(tempDir, "race-condition.txt");
|
|
130
158
|
const toolCallId = "test-call-race";
|
|
131
159
|
const input = {
|
|
132
160
|
file_path: testFile,
|
|
133
161
|
content: "agent content"
|
|
134
162
|
};
|
|
135
|
-
const
|
|
163
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
164
|
+
const preview = await tool.generatePreview(
|
|
165
|
+
parsedInput,
|
|
166
|
+
createToolContext(mockLogger, { toolCallId })
|
|
167
|
+
);
|
|
136
168
|
expect(preview?.type).toBe("file");
|
|
137
169
|
await fs.writeFile(testFile, "someone else created this");
|
|
138
170
|
try {
|
|
139
|
-
await tool.execute(
|
|
171
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
140
172
|
expect.fail("Should have thrown an error");
|
|
141
173
|
} catch (error) {
|
|
142
174
|
expect(error).toBeInstanceOf(DextoRuntimeError);
|
|
@@ -150,7 +182,7 @@ describe("write_file tool", () => {
|
|
|
150
182
|
});
|
|
151
183
|
describe("Cache Cleanup", () => {
|
|
152
184
|
it("should clean up hash cache after successful execution", async () => {
|
|
153
|
-
const tool = createWriteFileTool(
|
|
185
|
+
const tool = createWriteFileTool(async () => fileSystemService);
|
|
154
186
|
const testFile = path.join(tempDir, "test.txt");
|
|
155
187
|
await fs.writeFile(testFile, "original");
|
|
156
188
|
const toolCallId = "test-call-cleanup";
|
|
@@ -158,20 +190,28 @@ describe("write_file tool", () => {
|
|
|
158
190
|
file_path: testFile,
|
|
159
191
|
content: "first write"
|
|
160
192
|
};
|
|
161
|
-
|
|
162
|
-
await tool.
|
|
193
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
194
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
195
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
163
196
|
const input2 = {
|
|
164
197
|
file_path: testFile,
|
|
165
198
|
content: "second write"
|
|
166
199
|
};
|
|
167
|
-
|
|
168
|
-
|
|
200
|
+
const parsedInput2 = tool.inputSchema.parse(input2);
|
|
201
|
+
await tool.generatePreview(
|
|
202
|
+
parsedInput2,
|
|
203
|
+
createToolContext(mockLogger, { toolCallId })
|
|
204
|
+
);
|
|
205
|
+
const result = await tool.execute(
|
|
206
|
+
parsedInput2,
|
|
207
|
+
createToolContext(mockLogger, { toolCallId })
|
|
208
|
+
);
|
|
169
209
|
expect(result.success).toBe(true);
|
|
170
210
|
const content = await fs.readFile(testFile, "utf-8");
|
|
171
211
|
expect(content).toBe("second write");
|
|
172
212
|
});
|
|
173
213
|
it("should clean up hash cache after failed execution", async () => {
|
|
174
|
-
const tool = createWriteFileTool(
|
|
214
|
+
const tool = createWriteFileTool(async () => fileSystemService);
|
|
175
215
|
const testFile = path.join(tempDir, "test.txt");
|
|
176
216
|
await fs.writeFile(testFile, "original");
|
|
177
217
|
const toolCallId = "test-call-fail";
|
|
@@ -179,15 +219,19 @@ describe("write_file tool", () => {
|
|
|
179
219
|
file_path: testFile,
|
|
180
220
|
content: "new content"
|
|
181
221
|
};
|
|
182
|
-
|
|
222
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
223
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
183
224
|
await fs.writeFile(testFile, "modified");
|
|
184
225
|
try {
|
|
185
|
-
await tool.execute(
|
|
226
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
186
227
|
} catch {
|
|
187
228
|
}
|
|
188
229
|
await fs.writeFile(testFile, "reset content");
|
|
189
|
-
await tool.generatePreview(
|
|
190
|
-
const result = await tool.execute(
|
|
230
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
231
|
+
const result = await tool.execute(
|
|
232
|
+
parsedInput,
|
|
233
|
+
createToolContext(mockLogger, { toolCallId })
|
|
234
|
+
);
|
|
191
235
|
expect(result.success).toBe(true);
|
|
192
236
|
});
|
|
193
237
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dexto/tools-filesystem",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "FileSystem tools
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "FileSystem tools factory for Dexto agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"glob": "^11.1.0",
|
|
23
23
|
"safe-regex": "^2.1.1",
|
|
24
24
|
"zod": "^3.25.0",
|
|
25
|
-
"@dexto/
|
|
25
|
+
"@dexto/agent-config": "1.6.0",
|
|
26
|
+
"@dexto/core": "1.6.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@types/diff": "^5.2.3",
|
package/dist/tool-provider.cjs
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var tool_provider_exports = {};
|
|
20
|
-
__export(tool_provider_exports, {
|
|
21
|
-
fileSystemToolsProvider: () => fileSystemToolsProvider
|
|
22
|
-
});
|
|
23
|
-
module.exports = __toCommonJS(tool_provider_exports);
|
|
24
|
-
var import_zod = require("zod");
|
|
25
|
-
var import_filesystem_service = require("./filesystem-service.js");
|
|
26
|
-
var import_read_file_tool = require("./read-file-tool.js");
|
|
27
|
-
var import_write_file_tool = require("./write-file-tool.js");
|
|
28
|
-
var import_edit_file_tool = require("./edit-file-tool.js");
|
|
29
|
-
var import_glob_files_tool = require("./glob-files-tool.js");
|
|
30
|
-
var import_grep_content_tool = require("./grep-content-tool.js");
|
|
31
|
-
const DEFAULT_ALLOWED_PATHS = ["."];
|
|
32
|
-
const DEFAULT_BLOCKED_PATHS = [".git", "node_modules/.bin", ".env"];
|
|
33
|
-
const DEFAULT_BLOCKED_EXTENSIONS = [".exe", ".dll", ".so"];
|
|
34
|
-
const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
35
|
-
const DEFAULT_ENABLE_BACKUPS = false;
|
|
36
|
-
const DEFAULT_BACKUP_RETENTION_DAYS = 7;
|
|
37
|
-
const FILESYSTEM_TOOL_NAMES = [
|
|
38
|
-
"read_file",
|
|
39
|
-
"write_file",
|
|
40
|
-
"edit_file",
|
|
41
|
-
"glob_files",
|
|
42
|
-
"grep_content"
|
|
43
|
-
];
|
|
44
|
-
const FileSystemToolsConfigSchema = import_zod.z.object({
|
|
45
|
-
type: import_zod.z.literal("filesystem-tools"),
|
|
46
|
-
allowedPaths: import_zod.z.array(import_zod.z.string()).default(DEFAULT_ALLOWED_PATHS).describe("List of allowed base paths for file operations"),
|
|
47
|
-
blockedPaths: import_zod.z.array(import_zod.z.string()).default(DEFAULT_BLOCKED_PATHS).describe("List of blocked paths to exclude from operations"),
|
|
48
|
-
blockedExtensions: import_zod.z.array(import_zod.z.string()).default(DEFAULT_BLOCKED_EXTENSIONS).describe("List of blocked file extensions"),
|
|
49
|
-
maxFileSize: import_zod.z.number().int().positive().default(DEFAULT_MAX_FILE_SIZE).describe(
|
|
50
|
-
`Maximum file size in bytes (default: ${DEFAULT_MAX_FILE_SIZE / 1024 / 1024}MB)`
|
|
51
|
-
),
|
|
52
|
-
workingDirectory: import_zod.z.string().optional().describe("Working directory for file operations (defaults to process.cwd())"),
|
|
53
|
-
enableBackups: import_zod.z.boolean().default(DEFAULT_ENABLE_BACKUPS).describe("Enable automatic backups of modified files"),
|
|
54
|
-
backupPath: import_zod.z.string().optional().describe("Absolute path for storing file backups (if enableBackups is true)"),
|
|
55
|
-
backupRetentionDays: import_zod.z.number().int().positive().default(DEFAULT_BACKUP_RETENTION_DAYS).describe(
|
|
56
|
-
`Number of days to retain backup files (default: ${DEFAULT_BACKUP_RETENTION_DAYS})`
|
|
57
|
-
),
|
|
58
|
-
enabledTools: import_zod.z.array(import_zod.z.enum(FILESYSTEM_TOOL_NAMES)).optional().describe(
|
|
59
|
-
`Subset of tools to enable. If not specified, all tools are enabled. Available: ${FILESYSTEM_TOOL_NAMES.join(", ")}`
|
|
60
|
-
)
|
|
61
|
-
}).strict();
|
|
62
|
-
const fileSystemToolsProvider = {
|
|
63
|
-
type: "filesystem-tools",
|
|
64
|
-
configSchema: FileSystemToolsConfigSchema,
|
|
65
|
-
create: (config, context) => {
|
|
66
|
-
const { logger, services } = context;
|
|
67
|
-
logger.debug("Creating FileSystemService for filesystem tools");
|
|
68
|
-
const fileSystemService = new import_filesystem_service.FileSystemService(
|
|
69
|
-
{
|
|
70
|
-
allowedPaths: config.allowedPaths,
|
|
71
|
-
blockedPaths: config.blockedPaths,
|
|
72
|
-
blockedExtensions: config.blockedExtensions,
|
|
73
|
-
maxFileSize: config.maxFileSize,
|
|
74
|
-
workingDirectory: config.workingDirectory || process.cwd(),
|
|
75
|
-
enableBackups: config.enableBackups,
|
|
76
|
-
backupPath: config.backupPath,
|
|
77
|
-
backupRetentionDays: config.backupRetentionDays
|
|
78
|
-
},
|
|
79
|
-
logger
|
|
80
|
-
);
|
|
81
|
-
fileSystemService.initialize().catch((error) => {
|
|
82
|
-
logger.error(`Failed to initialize FileSystemService: ${error.message}`);
|
|
83
|
-
});
|
|
84
|
-
logger.debug("FileSystemService created - initialization will complete on first tool use");
|
|
85
|
-
const approvalManager = services?.approvalManager;
|
|
86
|
-
if (approvalManager) {
|
|
87
|
-
const approvalChecker = (filePath) => {
|
|
88
|
-
return approvalManager.isDirectoryApproved(filePath);
|
|
89
|
-
};
|
|
90
|
-
fileSystemService.setDirectoryApprovalChecker(approvalChecker);
|
|
91
|
-
logger.debug("Directory approval checker configured for FileSystemService");
|
|
92
|
-
}
|
|
93
|
-
const directoryApproval = approvalManager ? {
|
|
94
|
-
isSessionApproved: (filePath) => approvalManager.isDirectorySessionApproved(filePath),
|
|
95
|
-
addApproved: (directory, type) => approvalManager.addApprovedDirectory(directory, type)
|
|
96
|
-
} : void 0;
|
|
97
|
-
const fileToolOptions = {
|
|
98
|
-
fileSystemService,
|
|
99
|
-
directoryApproval
|
|
100
|
-
};
|
|
101
|
-
const toolCreators = {
|
|
102
|
-
read_file: () => (0, import_read_file_tool.createReadFileTool)(fileToolOptions),
|
|
103
|
-
write_file: () => (0, import_write_file_tool.createWriteFileTool)(fileToolOptions),
|
|
104
|
-
edit_file: () => (0, import_edit_file_tool.createEditFileTool)(fileToolOptions),
|
|
105
|
-
glob_files: () => (0, import_glob_files_tool.createGlobFilesTool)(fileToolOptions),
|
|
106
|
-
grep_content: () => (0, import_grep_content_tool.createGrepContentTool)(fileToolOptions)
|
|
107
|
-
};
|
|
108
|
-
const toolsToCreate = config.enabledTools ?? FILESYSTEM_TOOL_NAMES;
|
|
109
|
-
if (config.enabledTools) {
|
|
110
|
-
logger.debug(`Creating subset of filesystem tools: ${toolsToCreate.join(", ")}`);
|
|
111
|
-
}
|
|
112
|
-
return toolsToCreate.map((toolName) => toolCreators[toolName]());
|
|
113
|
-
},
|
|
114
|
-
metadata: {
|
|
115
|
-
displayName: "FileSystem Tools",
|
|
116
|
-
description: "File system operations (read, write, edit, glob, grep)",
|
|
117
|
-
category: "filesystem"
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
121
|
-
0 && (module.exports = {
|
|
122
|
-
fileSystemToolsProvider
|
|
123
|
-
});
|