@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_edit_file_tool = require("./edit-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)("edit_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", () => {
|
|
69
81
|
(0, import_vitest.it)("should succeed when file is not modified between preview and execute", async () => {
|
|
70
|
-
const tool = (0, import_edit_file_tool.createEditFileTool)(
|
|
82
|
+
const tool = (0, import_edit_file_tool.createEditFileTool)(async () => fileSystemService);
|
|
71
83
|
const testFile = path.join(tempDir, "test.txt");
|
|
72
84
|
await fs.writeFile(testFile, "hello world");
|
|
73
85
|
const toolCallId = "test-call-123";
|
|
@@ -76,16 +88,23 @@ const createMockLogger = () => ({
|
|
|
76
88
|
old_string: "world",
|
|
77
89
|
new_string: "universe"
|
|
78
90
|
};
|
|
79
|
-
const
|
|
91
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
92
|
+
const preview = await tool.generatePreview(
|
|
93
|
+
parsedInput,
|
|
94
|
+
createToolContext(mockLogger, { toolCallId })
|
|
95
|
+
);
|
|
80
96
|
(0, import_vitest.expect)(preview).toBeDefined();
|
|
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("hello universe");
|
|
86
105
|
});
|
|
87
106
|
(0, import_vitest.it)("should fail when file is modified between preview and execute", async () => {
|
|
88
|
-
const tool = (0, import_edit_file_tool.createEditFileTool)(
|
|
107
|
+
const tool = (0, import_edit_file_tool.createEditFileTool)(async () => fileSystemService);
|
|
89
108
|
const testFile = path.join(tempDir, "test.txt");
|
|
90
109
|
await fs.writeFile(testFile, "hello world");
|
|
91
110
|
const toolCallId = "test-call-456";
|
|
@@ -94,11 +113,15 @@ const createMockLogger = () => ({
|
|
|
94
113
|
old_string: "world",
|
|
95
114
|
new_string: "universe"
|
|
96
115
|
};
|
|
97
|
-
const
|
|
116
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
117
|
+
const preview = await tool.generatePreview(
|
|
118
|
+
parsedInput,
|
|
119
|
+
createToolContext(mockLogger, { toolCallId })
|
|
120
|
+
);
|
|
98
121
|
(0, import_vitest.expect)(preview).toBeDefined();
|
|
99
122
|
await fs.writeFile(testFile, "hello world - user added this");
|
|
100
123
|
try {
|
|
101
|
-
await tool.execute(
|
|
124
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
102
125
|
import_vitest.expect.fail("Should have thrown an error");
|
|
103
126
|
} catch (error) {
|
|
104
127
|
(0, import_vitest.expect)(error).toBeInstanceOf(import_core2.DextoRuntimeError);
|
|
@@ -110,7 +133,7 @@ const createMockLogger = () => ({
|
|
|
110
133
|
(0, import_vitest.expect)(content).toBe("hello world - user added this");
|
|
111
134
|
});
|
|
112
135
|
(0, import_vitest.it)("should detect file modification with correct error code", async () => {
|
|
113
|
-
const tool = (0, import_edit_file_tool.createEditFileTool)(
|
|
136
|
+
const tool = (0, import_edit_file_tool.createEditFileTool)(async () => fileSystemService);
|
|
114
137
|
const testFile = path.join(tempDir, "test.txt");
|
|
115
138
|
await fs.writeFile(testFile, "hello world");
|
|
116
139
|
const toolCallId = "test-call-789";
|
|
@@ -119,10 +142,11 @@ const createMockLogger = () => ({
|
|
|
119
142
|
old_string: "world",
|
|
120
143
|
new_string: "universe"
|
|
121
144
|
};
|
|
122
|
-
|
|
145
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
146
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
123
147
|
await fs.writeFile(testFile, "hello world modified");
|
|
124
148
|
try {
|
|
125
|
-
await tool.execute(
|
|
149
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
126
150
|
import_vitest.expect.fail("Should have thrown an error");
|
|
127
151
|
} catch (error) {
|
|
128
152
|
(0, import_vitest.expect)(error).toBeInstanceOf(import_core2.DextoRuntimeError);
|
|
@@ -136,7 +160,7 @@ const createMockLogger = () => ({
|
|
|
136
160
|
}
|
|
137
161
|
});
|
|
138
162
|
(0, import_vitest.it)("should work without toolCallId (no modification check)", async () => {
|
|
139
|
-
const tool = (0, import_edit_file_tool.createEditFileTool)(
|
|
163
|
+
const tool = (0, import_edit_file_tool.createEditFileTool)(async () => fileSystemService);
|
|
140
164
|
const testFile = path.join(tempDir, "test.txt");
|
|
141
165
|
await fs.writeFile(testFile, "hello world");
|
|
142
166
|
const input = {
|
|
@@ -144,10 +168,11 @@ const createMockLogger = () => ({
|
|
|
144
168
|
old_string: "world",
|
|
145
169
|
new_string: "universe"
|
|
146
170
|
};
|
|
147
|
-
|
|
171
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
172
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger));
|
|
148
173
|
await fs.writeFile(testFile, "hello world changed");
|
|
149
174
|
try {
|
|
150
|
-
await tool.execute(
|
|
175
|
+
await tool.execute(parsedInput, createToolContext(mockLogger));
|
|
151
176
|
} catch (error) {
|
|
152
177
|
(0, import_vitest.expect)(error).toBeInstanceOf(import_core2.DextoRuntimeError);
|
|
153
178
|
(0, import_vitest.expect)(error.code).not.toBe(
|
|
@@ -156,7 +181,7 @@ const createMockLogger = () => ({
|
|
|
156
181
|
}
|
|
157
182
|
});
|
|
158
183
|
(0, import_vitest.it)("should clean up hash cache after successful execution", async () => {
|
|
159
|
-
const tool = (0, import_edit_file_tool.createEditFileTool)(
|
|
184
|
+
const tool = (0, import_edit_file_tool.createEditFileTool)(async () => fileSystemService);
|
|
160
185
|
const testFile = path.join(tempDir, "test.txt");
|
|
161
186
|
await fs.writeFile(testFile, "hello world");
|
|
162
187
|
const toolCallId = "test-call-cleanup";
|
|
@@ -165,21 +190,29 @@ const createMockLogger = () => ({
|
|
|
165
190
|
old_string: "world",
|
|
166
191
|
new_string: "universe"
|
|
167
192
|
};
|
|
168
|
-
|
|
169
|
-
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 }));
|
|
170
196
|
const input2 = {
|
|
171
197
|
file_path: testFile,
|
|
172
198
|
old_string: "universe",
|
|
173
199
|
new_string: "galaxy"
|
|
174
200
|
};
|
|
175
|
-
|
|
176
|
-
|
|
201
|
+
const parsedInput2 = tool.inputSchema.parse(input2);
|
|
202
|
+
await tool.generatePreview(
|
|
203
|
+
parsedInput2,
|
|
204
|
+
createToolContext(mockLogger, { toolCallId })
|
|
205
|
+
);
|
|
206
|
+
const result = await tool.execute(
|
|
207
|
+
parsedInput2,
|
|
208
|
+
createToolContext(mockLogger, { toolCallId })
|
|
209
|
+
);
|
|
177
210
|
(0, import_vitest.expect)(result.success).toBe(true);
|
|
178
211
|
const content = await fs.readFile(testFile, "utf-8");
|
|
179
212
|
(0, import_vitest.expect)(content).toBe("hello galaxy");
|
|
180
213
|
});
|
|
181
214
|
(0, import_vitest.it)("should clean up hash cache after failed execution", async () => {
|
|
182
|
-
const tool = (0, import_edit_file_tool.createEditFileTool)(
|
|
215
|
+
const tool = (0, import_edit_file_tool.createEditFileTool)(async () => fileSystemService);
|
|
183
216
|
const testFile = path.join(tempDir, "test.txt");
|
|
184
217
|
await fs.writeFile(testFile, "hello world");
|
|
185
218
|
const toolCallId = "test-call-fail-cleanup";
|
|
@@ -188,15 +221,19 @@ const createMockLogger = () => ({
|
|
|
188
221
|
old_string: "world",
|
|
189
222
|
new_string: "universe"
|
|
190
223
|
};
|
|
191
|
-
|
|
224
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
225
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
192
226
|
await fs.writeFile(testFile, "hello world modified");
|
|
193
227
|
try {
|
|
194
|
-
await tool.execute(
|
|
228
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
195
229
|
} catch {
|
|
196
230
|
}
|
|
197
231
|
await fs.writeFile(testFile, "hello world");
|
|
198
|
-
await tool.generatePreview(
|
|
199
|
-
const result = await tool.execute(
|
|
232
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
233
|
+
const result = await tool.execute(
|
|
234
|
+
parsedInput,
|
|
235
|
+
createToolContext(mockLogger, { toolCallId })
|
|
236
|
+
);
|
|
200
237
|
(0, import_vitest.expect)(result.success).toBe(true);
|
|
201
238
|
});
|
|
202
239
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit-file-tool.test.d.ts","sourceRoot":"","sources":["../src/edit-file-tool.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -6,13 +6,25 @@ import { createEditFileTool } from "./edit-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("edit_file tool", () => {
|
|
17
29
|
let mockLogger;
|
|
18
30
|
let tempDir;
|
|
@@ -44,7 +56,7 @@ describe("edit_file tool", () => {
|
|
|
44
56
|
});
|
|
45
57
|
describe("File Modification Detection", () => {
|
|
46
58
|
it("should succeed when file is not modified between preview and execute", async () => {
|
|
47
|
-
const tool = createEditFileTool(
|
|
59
|
+
const tool = createEditFileTool(async () => fileSystemService);
|
|
48
60
|
const testFile = path.join(tempDir, "test.txt");
|
|
49
61
|
await fs.writeFile(testFile, "hello world");
|
|
50
62
|
const toolCallId = "test-call-123";
|
|
@@ -53,16 +65,23 @@ describe("edit_file tool", () => {
|
|
|
53
65
|
old_string: "world",
|
|
54
66
|
new_string: "universe"
|
|
55
67
|
};
|
|
56
|
-
const
|
|
68
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
69
|
+
const preview = await tool.generatePreview(
|
|
70
|
+
parsedInput,
|
|
71
|
+
createToolContext(mockLogger, { toolCallId })
|
|
72
|
+
);
|
|
57
73
|
expect(preview).toBeDefined();
|
|
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("hello universe");
|
|
63
82
|
});
|
|
64
83
|
it("should fail when file is modified between preview and execute", async () => {
|
|
65
|
-
const tool = createEditFileTool(
|
|
84
|
+
const tool = createEditFileTool(async () => fileSystemService);
|
|
66
85
|
const testFile = path.join(tempDir, "test.txt");
|
|
67
86
|
await fs.writeFile(testFile, "hello world");
|
|
68
87
|
const toolCallId = "test-call-456";
|
|
@@ -71,11 +90,15 @@ describe("edit_file tool", () => {
|
|
|
71
90
|
old_string: "world",
|
|
72
91
|
new_string: "universe"
|
|
73
92
|
};
|
|
74
|
-
const
|
|
93
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
94
|
+
const preview = await tool.generatePreview(
|
|
95
|
+
parsedInput,
|
|
96
|
+
createToolContext(mockLogger, { toolCallId })
|
|
97
|
+
);
|
|
75
98
|
expect(preview).toBeDefined();
|
|
76
99
|
await fs.writeFile(testFile, "hello world - user added this");
|
|
77
100
|
try {
|
|
78
|
-
await tool.execute(
|
|
101
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
79
102
|
expect.fail("Should have thrown an error");
|
|
80
103
|
} catch (error) {
|
|
81
104
|
expect(error).toBeInstanceOf(DextoRuntimeError);
|
|
@@ -87,7 +110,7 @@ describe("edit_file tool", () => {
|
|
|
87
110
|
expect(content).toBe("hello world - user added this");
|
|
88
111
|
});
|
|
89
112
|
it("should detect file modification with correct error code", async () => {
|
|
90
|
-
const tool = createEditFileTool(
|
|
113
|
+
const tool = createEditFileTool(async () => fileSystemService);
|
|
91
114
|
const testFile = path.join(tempDir, "test.txt");
|
|
92
115
|
await fs.writeFile(testFile, "hello world");
|
|
93
116
|
const toolCallId = "test-call-789";
|
|
@@ -96,10 +119,11 @@ describe("edit_file tool", () => {
|
|
|
96
119
|
old_string: "world",
|
|
97
120
|
new_string: "universe"
|
|
98
121
|
};
|
|
99
|
-
|
|
122
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
123
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
100
124
|
await fs.writeFile(testFile, "hello world modified");
|
|
101
125
|
try {
|
|
102
|
-
await tool.execute(
|
|
126
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
103
127
|
expect.fail("Should have thrown an error");
|
|
104
128
|
} catch (error) {
|
|
105
129
|
expect(error).toBeInstanceOf(DextoRuntimeError);
|
|
@@ -113,7 +137,7 @@ describe("edit_file tool", () => {
|
|
|
113
137
|
}
|
|
114
138
|
});
|
|
115
139
|
it("should work without toolCallId (no modification check)", async () => {
|
|
116
|
-
const tool = createEditFileTool(
|
|
140
|
+
const tool = createEditFileTool(async () => fileSystemService);
|
|
117
141
|
const testFile = path.join(tempDir, "test.txt");
|
|
118
142
|
await fs.writeFile(testFile, "hello world");
|
|
119
143
|
const input = {
|
|
@@ -121,10 +145,11 @@ describe("edit_file tool", () => {
|
|
|
121
145
|
old_string: "world",
|
|
122
146
|
new_string: "universe"
|
|
123
147
|
};
|
|
124
|
-
|
|
148
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
149
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger));
|
|
125
150
|
await fs.writeFile(testFile, "hello world changed");
|
|
126
151
|
try {
|
|
127
|
-
await tool.execute(
|
|
152
|
+
await tool.execute(parsedInput, createToolContext(mockLogger));
|
|
128
153
|
} catch (error) {
|
|
129
154
|
expect(error).toBeInstanceOf(DextoRuntimeError);
|
|
130
155
|
expect(error.code).not.toBe(
|
|
@@ -133,7 +158,7 @@ describe("edit_file tool", () => {
|
|
|
133
158
|
}
|
|
134
159
|
});
|
|
135
160
|
it("should clean up hash cache after successful execution", async () => {
|
|
136
|
-
const tool = createEditFileTool(
|
|
161
|
+
const tool = createEditFileTool(async () => fileSystemService);
|
|
137
162
|
const testFile = path.join(tempDir, "test.txt");
|
|
138
163
|
await fs.writeFile(testFile, "hello world");
|
|
139
164
|
const toolCallId = "test-call-cleanup";
|
|
@@ -142,21 +167,29 @@ describe("edit_file tool", () => {
|
|
|
142
167
|
old_string: "world",
|
|
143
168
|
new_string: "universe"
|
|
144
169
|
};
|
|
145
|
-
|
|
146
|
-
await tool.
|
|
170
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
171
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
172
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
147
173
|
const input2 = {
|
|
148
174
|
file_path: testFile,
|
|
149
175
|
old_string: "universe",
|
|
150
176
|
new_string: "galaxy"
|
|
151
177
|
};
|
|
152
|
-
|
|
153
|
-
|
|
178
|
+
const parsedInput2 = tool.inputSchema.parse(input2);
|
|
179
|
+
await tool.generatePreview(
|
|
180
|
+
parsedInput2,
|
|
181
|
+
createToolContext(mockLogger, { toolCallId })
|
|
182
|
+
);
|
|
183
|
+
const result = await tool.execute(
|
|
184
|
+
parsedInput2,
|
|
185
|
+
createToolContext(mockLogger, { toolCallId })
|
|
186
|
+
);
|
|
154
187
|
expect(result.success).toBe(true);
|
|
155
188
|
const content = await fs.readFile(testFile, "utf-8");
|
|
156
189
|
expect(content).toBe("hello galaxy");
|
|
157
190
|
});
|
|
158
191
|
it("should clean up hash cache after failed execution", async () => {
|
|
159
|
-
const tool = createEditFileTool(
|
|
192
|
+
const tool = createEditFileTool(async () => fileSystemService);
|
|
160
193
|
const testFile = path.join(tempDir, "test.txt");
|
|
161
194
|
await fs.writeFile(testFile, "hello world");
|
|
162
195
|
const toolCallId = "test-call-fail-cleanup";
|
|
@@ -165,15 +198,19 @@ describe("edit_file tool", () => {
|
|
|
165
198
|
old_string: "world",
|
|
166
199
|
new_string: "universe"
|
|
167
200
|
};
|
|
168
|
-
|
|
201
|
+
const parsedInput = tool.inputSchema.parse(input);
|
|
202
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
169
203
|
await fs.writeFile(testFile, "hello world modified");
|
|
170
204
|
try {
|
|
171
|
-
await tool.execute(
|
|
205
|
+
await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
172
206
|
} catch {
|
|
173
207
|
}
|
|
174
208
|
await fs.writeFile(testFile, "hello world");
|
|
175
|
-
await tool.generatePreview(
|
|
176
|
-
const result = await tool.execute(
|
|
209
|
+
await tool.generatePreview(parsedInput, createToolContext(mockLogger, { toolCallId }));
|
|
210
|
+
const result = await tool.execute(
|
|
211
|
+
parsedInput,
|
|
212
|
+
createToolContext(mockLogger, { toolCallId })
|
|
213
|
+
);
|
|
177
214
|
expect(result.success).toBe(true);
|
|
178
215
|
});
|
|
179
216
|
});
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Standardized error codes for file system operations
|
|
5
5
|
*/
|
|
6
|
-
declare enum FileSystemErrorCode {
|
|
6
|
+
export declare enum FileSystemErrorCode {
|
|
7
7
|
FILE_NOT_FOUND = "FILESYSTEM_FILE_NOT_FOUND",
|
|
8
8
|
DIRECTORY_NOT_FOUND = "FILESYSTEM_DIRECTORY_NOT_FOUND",
|
|
9
9
|
PERMISSION_DENIED = "FILESYSTEM_PERMISSION_DENIED",
|
|
@@ -28,5 +28,4 @@ declare enum FileSystemErrorCode {
|
|
|
28
28
|
INVALID_CONFIG = "FILESYSTEM_INVALID_CONFIG",
|
|
29
29
|
SERVICE_NOT_INITIALIZED = "FILESYSTEM_SERVICE_NOT_INITIALIZED"
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
export { FileSystemErrorCode };
|
|
31
|
+
//# sourceMappingURL=error-codes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oBAAY,mBAAmB;IAE3B,cAAc,8BAA8B;IAC5C,mBAAmB,mCAAmC;IAGtD,iBAAiB,iCAAiC;IAClD,gBAAgB,gCAAgC;IAChD,YAAY,4BAA4B;IAGxC,YAAY,4BAA4B;IACxC,uBAAuB,uCAAuC;IAC9D,sBAAsB,sCAAsC;IAC5D,gBAAgB,gCAAgC;IAGhD,cAAc,8BAA8B;IAC5C,gBAAgB,gCAAgC;IAGhD,WAAW,2BAA2B;IACtC,YAAY,4BAA4B;IACxC,aAAa,6BAA6B;IAC1C,WAAW,2BAA2B;IACtC,iBAAiB,iCAAiC;IAClD,gBAAgB,gCAAgC;IAGhD,WAAW,2BAA2B;IACtC,aAAa,6BAA6B;IAC1C,eAAe,+BAA+B;IAC9C,aAAa,6BAA6B;IAG1C,cAAc,8BAA8B;IAC5C,uBAAuB,uCAAuC;CACjE"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { DextoRuntimeError } from '@dexto/core';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* FileSystem Service Errors
|
|
5
3
|
*
|
|
6
4
|
* Error classes for file system operations
|
|
7
5
|
*/
|
|
8
|
-
|
|
9
|
-
interface FileSystemErrorContext {
|
|
6
|
+
import { DextoRuntimeError } from '@dexto/core';
|
|
7
|
+
export interface FileSystemErrorContext {
|
|
10
8
|
path?: string;
|
|
11
9
|
pattern?: string;
|
|
12
10
|
size?: number;
|
|
@@ -17,7 +15,7 @@ interface FileSystemErrorContext {
|
|
|
17
15
|
/**
|
|
18
16
|
* Factory class for creating FileSystem-related errors
|
|
19
17
|
*/
|
|
20
|
-
declare class FileSystemError {
|
|
18
|
+
export declare class FileSystemError {
|
|
21
19
|
private constructor();
|
|
22
20
|
/**
|
|
23
21
|
* File not found error
|
|
@@ -108,5 +106,4 @@ declare class FileSystemError {
|
|
|
108
106
|
*/
|
|
109
107
|
static notInitialized(): DextoRuntimeError;
|
|
110
108
|
}
|
|
111
|
-
|
|
112
|
-
export { FileSystemError, type FileSystemErrorContext };
|
|
109
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAa,MAAM,aAAa,CAAC;AAM3D,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,qBAAa,eAAe;IACxB,OAAO;IAIP;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB;IAUpD;;OAEG;IACH,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB;IAUzD;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,iBAAiB;IAU3E;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,iBAAiB;IAW9E;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB;IAUnE;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB;IAUnE;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB;IAUrD;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,GAAG,iBAAiB;IAUrF;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB;IAUnF;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,iBAAiB;IAW9F;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB;IAUjE;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB;IAUlE;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB;IAUnE;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB;IAUjE;;OAEG;IACH,MAAM,CAAC,eAAe,CAClB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GACpB,iBAAiB;IAWpB;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,iBAAiB;IAU5E;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB;IAUpE;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB;IAUtE;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB;IAUxE;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB;IAWvD;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB;IAUvD;;OAEG;IACH,MAAM,CAAC,cAAc,IAAI,iBAAiB;CAU7C"}
|
|
@@ -1,46 +1,18 @@
|
|
|
1
|
+
import { ToolExecutionContext } from '@dexto/core';
|
|
1
2
|
import { FileSystemService } from './filesystem-service.cjs';
|
|
2
|
-
import '@dexto/core';
|
|
3
3
|
import './types.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* File Tool Types
|
|
7
7
|
*
|
|
8
|
-
* Types shared between file tools
|
|
8
|
+
* Types shared between file tools and factories.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Getter for a lazily-initialized {@link FileSystemService}.
|
|
13
|
+
* Tool factories construct tools before runtime services are available, so tools
|
|
14
|
+
* resolve the service on-demand using {@link ToolExecutionContext}.
|
|
15
15
|
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Check if a path is within any session-approved directory.
|
|
19
|
-
* Used to determine if directory approval prompt is needed.
|
|
20
|
-
* @param filePath The file path to check (absolute or relative)
|
|
21
|
-
* @returns true if path is in a session-approved directory
|
|
22
|
-
*/
|
|
23
|
-
isSessionApproved: (filePath: string) => boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Add a directory to the approved list for this session.
|
|
26
|
-
* Called after user approves directory access.
|
|
27
|
-
* @param directory Absolute path to the directory to approve
|
|
28
|
-
* @param type 'session' (remembered) or 'once' (single use)
|
|
29
|
-
*/
|
|
30
|
-
addApproved: (directory: string, type: 'session' | 'once') => void;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Options for creating file tools with directory approval support
|
|
34
|
-
*/
|
|
35
|
-
interface FileToolOptions {
|
|
36
|
-
/** FileSystemService instance for file operations */
|
|
37
|
-
fileSystemService: FileSystemService;
|
|
38
|
-
/**
|
|
39
|
-
* Optional callbacks for directory approval.
|
|
40
|
-
* If provided, file tools can request approval for accessing paths
|
|
41
|
-
* outside the configured working directory.
|
|
42
|
-
*/
|
|
43
|
-
directoryApproval?: DirectoryApprovalCallbacks | undefined;
|
|
44
|
-
}
|
|
16
|
+
type FileSystemServiceGetter = (context: ToolExecutionContext) => Promise<FileSystemService>;
|
|
45
17
|
|
|
46
|
-
export type {
|
|
18
|
+
export type { FileSystemServiceGetter };
|
|
@@ -1,46 +1,14 @@
|
|
|
1
|
-
import { FileSystemService } from './filesystem-service.js';
|
|
2
|
-
import '@dexto/core';
|
|
3
|
-
import './types.js';
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
2
|
* File Tool Types
|
|
7
3
|
*
|
|
8
|
-
* Types shared between file tools
|
|
4
|
+
* Types shared between file tools and factories.
|
|
9
5
|
*/
|
|
10
|
-
|
|
6
|
+
import type { ToolExecutionContext } from '@dexto/core';
|
|
7
|
+
import type { FileSystemService } from './filesystem-service.js';
|
|
11
8
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Getter for a lazily-initialized {@link FileSystemService}.
|
|
10
|
+
* Tool factories construct tools before runtime services are available, so tools
|
|
11
|
+
* resolve the service on-demand using {@link ToolExecutionContext}.
|
|
15
12
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* Check if a path is within any session-approved directory.
|
|
19
|
-
* Used to determine if directory approval prompt is needed.
|
|
20
|
-
* @param filePath The file path to check (absolute or relative)
|
|
21
|
-
* @returns true if path is in a session-approved directory
|
|
22
|
-
*/
|
|
23
|
-
isSessionApproved: (filePath: string) => boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Add a directory to the approved list for this session.
|
|
26
|
-
* Called after user approves directory access.
|
|
27
|
-
* @param directory Absolute path to the directory to approve
|
|
28
|
-
* @param type 'session' (remembered) or 'once' (single use)
|
|
29
|
-
*/
|
|
30
|
-
addApproved: (directory: string, type: 'session' | 'once') => void;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Options for creating file tools with directory approval support
|
|
34
|
-
*/
|
|
35
|
-
interface FileToolOptions {
|
|
36
|
-
/** FileSystemService instance for file operations */
|
|
37
|
-
fileSystemService: FileSystemService;
|
|
38
|
-
/**
|
|
39
|
-
* Optional callbacks for directory approval.
|
|
40
|
-
* If provided, file tools can request approval for accessing paths
|
|
41
|
-
* outside the configured working directory.
|
|
42
|
-
*/
|
|
43
|
-
directoryApproval?: DirectoryApprovalCallbacks | undefined;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export type { DirectoryApprovalCallbacks, FileToolOptions };
|
|
13
|
+
export type FileSystemServiceGetter = (context: ToolExecutionContext) => Promise<FileSystemService>;
|
|
14
|
+
//# sourceMappingURL=file-tool-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-tool-types.d.ts","sourceRoot":"","sources":["../src/file-tool-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC"}
|
|
@@ -47,10 +47,11 @@ class FileSystemService {
|
|
|
47
47
|
initialized = false;
|
|
48
48
|
initPromise = null;
|
|
49
49
|
logger;
|
|
50
|
+
directoryApprovalChecker;
|
|
50
51
|
/**
|
|
51
52
|
* Create a new FileSystemService with validated configuration.
|
|
52
53
|
*
|
|
53
|
-
* @param config - Fully-validated configuration from
|
|
54
|
+
* @param config - Fully-validated configuration from the factory schema.
|
|
54
55
|
* All required fields have values, defaults already applied.
|
|
55
56
|
* @param logger - Logger instance for this service
|
|
56
57
|
*/
|
|
@@ -124,8 +125,24 @@ class FileSystemService {
|
|
|
124
125
|
* @param checker Function that returns true if path is in an approved directory
|
|
125
126
|
*/
|
|
126
127
|
setDirectoryApprovalChecker(checker) {
|
|
128
|
+
this.directoryApprovalChecker = checker;
|
|
127
129
|
this.pathValidator.setDirectoryApprovalChecker(checker);
|
|
128
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Update the working directory at runtime (e.g., when workspace changes).
|
|
133
|
+
* Rebuilds the PathValidator so allowed/blocked path roots are recalculated.
|
|
134
|
+
*/
|
|
135
|
+
setWorkingDirectory(workingDirectory) {
|
|
136
|
+
const normalized = workingDirectory?.trim();
|
|
137
|
+
if (!normalized) return;
|
|
138
|
+
if (this.config.workingDirectory === normalized) return;
|
|
139
|
+
this.config = { ...this.config, workingDirectory: normalized };
|
|
140
|
+
this.pathValidator = new import_path_validator.PathValidator(this.config, this.logger);
|
|
141
|
+
if (this.directoryApprovalChecker) {
|
|
142
|
+
this.pathValidator.setDirectoryApprovalChecker(this.directoryApprovalChecker);
|
|
143
|
+
}
|
|
144
|
+
this.logger.info(`FileSystemService working directory set to ${normalized}`);
|
|
145
|
+
}
|
|
129
146
|
/**
|
|
130
147
|
* Check if a file path is within the configured allowed paths (config only).
|
|
131
148
|
* This is used by file tools to determine if directory approval is needed.
|