@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.
Files changed (83) hide show
  1. package/dist/directory-approval.cjs +94 -0
  2. package/dist/directory-approval.d.cts +22 -0
  3. package/dist/directory-approval.d.ts +20 -0
  4. package/dist/directory-approval.d.ts.map +1 -0
  5. package/dist/directory-approval.integration.test.cjs +303 -269
  6. package/dist/directory-approval.integration.test.d.ts +14 -2
  7. package/dist/directory-approval.integration.test.d.ts.map +1 -0
  8. package/dist/directory-approval.integration.test.js +309 -270
  9. package/dist/directory-approval.js +59 -0
  10. package/dist/edit-file-tool.cjs +57 -90
  11. package/dist/edit-file-tool.d.cts +20 -3
  12. package/dist/edit-file-tool.d.ts +22 -9
  13. package/dist/edit-file-tool.d.ts.map +1 -0
  14. package/dist/edit-file-tool.js +53 -76
  15. package/dist/edit-file-tool.test.cjs +66 -29
  16. package/dist/edit-file-tool.test.d.ts +7 -2
  17. package/dist/edit-file-tool.test.d.ts.map +1 -0
  18. package/dist/edit-file-tool.test.js +66 -29
  19. package/dist/error-codes.d.ts +2 -3
  20. package/dist/error-codes.d.ts.map +1 -0
  21. package/dist/errors.d.ts +4 -7
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/file-tool-types.d.cts +7 -35
  24. package/dist/file-tool-types.d.ts +8 -40
  25. package/dist/file-tool-types.d.ts.map +1 -0
  26. package/dist/filesystem-service.cjs +18 -1
  27. package/dist/filesystem-service.d.cts +11 -6
  28. package/dist/filesystem-service.d.ts +14 -12
  29. package/dist/filesystem-service.d.ts.map +1 -0
  30. package/dist/filesystem-service.js +18 -1
  31. package/dist/filesystem-service.test.cjs +10 -2
  32. package/dist/filesystem-service.test.d.ts +7 -2
  33. package/dist/filesystem-service.test.d.ts.map +1 -0
  34. package/dist/filesystem-service.test.js +10 -2
  35. package/dist/glob-files-tool.cjs +22 -47
  36. package/dist/glob-files-tool.d.cts +17 -3
  37. package/dist/glob-files-tool.d.ts +19 -9
  38. package/dist/glob-files-tool.d.ts.map +1 -0
  39. package/dist/glob-files-tool.js +23 -48
  40. package/dist/grep-content-tool.cjs +29 -46
  41. package/dist/grep-content-tool.d.cts +26 -3
  42. package/dist/grep-content-tool.d.ts +28 -9
  43. package/dist/grep-content-tool.d.ts.map +1 -0
  44. package/dist/grep-content-tool.js +30 -47
  45. package/dist/index.cjs +3 -3
  46. package/dist/index.d.cts +4 -2
  47. package/dist/index.d.ts +10 -5
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +2 -2
  50. package/dist/path-validator.d.cts +2 -2
  51. package/dist/path-validator.d.ts +6 -9
  52. package/dist/path-validator.d.ts.map +1 -0
  53. package/dist/path-validator.test.d.ts +7 -2
  54. package/dist/path-validator.test.d.ts.map +1 -0
  55. package/dist/read-file-tool.cjs +21 -60
  56. package/dist/read-file-tool.d.cts +17 -3
  57. package/dist/read-file-tool.d.ts +19 -9
  58. package/dist/read-file-tool.d.ts.map +1 -0
  59. package/dist/read-file-tool.js +22 -51
  60. package/dist/tool-factory-config.cjs +61 -0
  61. package/dist/{tool-provider.d.ts → tool-factory-config.d.cts} +9 -23
  62. package/dist/{tool-provider.d.cts → tool-factory-config.d.ts} +13 -30
  63. package/dist/tool-factory-config.d.ts.map +1 -0
  64. package/dist/tool-factory-config.js +36 -0
  65. package/dist/tool-factory.cjs +102 -0
  66. package/dist/tool-factory.d.cts +7 -0
  67. package/dist/tool-factory.d.ts +4 -0
  68. package/dist/tool-factory.d.ts.map +1 -0
  69. package/dist/tool-factory.js +81 -0
  70. package/dist/types.d.ts +17 -18
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/write-file-tool.cjs +45 -73
  73. package/dist/write-file-tool.d.cts +20 -3
  74. package/dist/write-file-tool.d.ts +22 -9
  75. package/dist/write-file-tool.d.ts.map +1 -0
  76. package/dist/write-file-tool.js +46 -68
  77. package/dist/write-file-tool.test.cjs +76 -32
  78. package/dist/write-file-tool.test.d.ts +7 -2
  79. package/dist/write-file-tool.test.d.ts.map +1 -0
  80. package/dist/write-file-tool.test.js +76 -32
  81. package/package.json +4 -3
  82. package/dist/tool-provider.cjs +0 -123
  83. 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
- debug: import_vitest.vi.fn(),
34
- info: import_vitest.vi.fn(),
35
- warn: import_vitest.vi.fn(),
36
- error: import_vitest.vi.fn(),
37
- createChild: import_vitest.vi.fn().mockReturnThis()
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)({ fileSystemService });
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 preview = await tool.generatePreview(input, { toolCallId });
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(input, { toolCallId });
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)({ fileSystemService });
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 preview = await tool.generatePreview(input, { toolCallId });
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(input, { toolCallId });
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)({ fileSystemService });
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
- await tool.generatePreview(input, { toolCallId });
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(input, { toolCallId });
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)({ fileSystemService });
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
- await tool.generatePreview(input, {});
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(input, {});
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)({ fileSystemService });
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
- await tool.generatePreview(input, { toolCallId });
169
- await tool.execute(input, { toolCallId });
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
- await tool.generatePreview(input2, { toolCallId });
176
- const result = await tool.execute(input2, { toolCallId });
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)({ fileSystemService });
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
- await tool.generatePreview(input, { toolCallId });
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(input, { toolCallId });
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(input, { toolCallId });
199
- const result = await tool.execute(input, { toolCallId });
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
  });
@@ -1,2 +1,7 @@
1
-
2
- export { }
1
+ /**
2
+ * Edit File Tool Tests
3
+ *
4
+ * Tests for the edit_file tool including file modification detection.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=edit-file-tool.test.d.ts.map
@@ -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
- debug: vi.fn(),
11
- info: vi.fn(),
12
- warn: vi.fn(),
13
- error: vi.fn(),
14
- createChild: vi.fn().mockReturnThis()
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({ fileSystemService });
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 preview = await tool.generatePreview(input, { toolCallId });
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(input, { toolCallId });
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({ fileSystemService });
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 preview = await tool.generatePreview(input, { toolCallId });
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(input, { toolCallId });
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({ fileSystemService });
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
- await tool.generatePreview(input, { toolCallId });
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(input, { toolCallId });
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({ fileSystemService });
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
- await tool.generatePreview(input, {});
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(input, {});
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({ fileSystemService });
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
- await tool.generatePreview(input, { toolCallId });
146
- await tool.execute(input, { toolCallId });
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
- await tool.generatePreview(input2, { toolCallId });
153
- const result = await tool.execute(input2, { toolCallId });
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({ fileSystemService });
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
- await tool.generatePreview(input, { toolCallId });
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(input, { toolCallId });
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(input, { toolCallId });
176
- const result = await tool.execute(input, { toolCallId });
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
  });
@@ -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 for directory approval support.
8
+ * Types shared between file tools and factories.
9
9
  */
10
10
 
11
11
  /**
12
- * Callbacks for directory access approval.
13
- * Allows file tools to check and request approval for accessing paths
14
- * outside the configured working directory.
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
- interface DirectoryApprovalCallbacks {
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 { DirectoryApprovalCallbacks, FileToolOptions };
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 for directory approval support.
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
- * Callbacks for directory access approval.
13
- * Allows file tools to check and request approval for accessing paths
14
- * outside the configured working directory.
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
- interface DirectoryApprovalCallbacks {
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 provider schema.
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.