@dexto/tools-filesystem 1.5.8 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/directory-approval.cjs +98 -0
  2. package/dist/directory-approval.d.ts +24 -0
  3. package/dist/directory-approval.d.ts.map +1 -0
  4. package/dist/directory-approval.integration.test.cjs +175 -390
  5. package/dist/directory-approval.integration.test.d.ts +14 -2
  6. package/dist/directory-approval.integration.test.d.ts.map +1 -0
  7. package/dist/directory-approval.integration.test.js +178 -390
  8. package/dist/directory-approval.js +63 -0
  9. package/dist/edit-file-tool.cjs +109 -120
  10. package/dist/edit-file-tool.d.ts +22 -9
  11. package/dist/edit-file-tool.d.ts.map +1 -0
  12. package/dist/edit-file-tool.js +116 -110
  13. package/dist/edit-file-tool.test.cjs +109 -29
  14. package/dist/edit-file-tool.test.d.ts +7 -2
  15. package/dist/edit-file-tool.test.d.ts.map +1 -0
  16. package/dist/edit-file-tool.test.js +109 -29
  17. package/dist/error-codes.cjs +4 -0
  18. package/dist/error-codes.d.ts +6 -3
  19. package/dist/error-codes.d.ts.map +1 -0
  20. package/dist/error-codes.js +4 -0
  21. package/dist/errors.cjs +48 -0
  22. package/dist/errors.d.ts +20 -7
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +48 -0
  25. package/dist/file-tool-types.d.ts +8 -40
  26. package/dist/file-tool-types.d.ts.map +1 -0
  27. package/dist/filesystem-service.cjs +325 -10
  28. package/dist/filesystem-service.d.ts +41 -12
  29. package/dist/filesystem-service.d.ts.map +1 -0
  30. package/dist/filesystem-service.js +326 -11
  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 +32 -46
  36. package/dist/glob-files-tool.d.ts +19 -9
  37. package/dist/glob-files-tool.d.ts.map +1 -0
  38. package/dist/glob-files-tool.js +33 -47
  39. package/dist/grep-content-tool.cjs +40 -45
  40. package/dist/grep-content-tool.d.ts +28 -9
  41. package/dist/grep-content-tool.d.ts.map +1 -0
  42. package/dist/grep-content-tool.js +41 -46
  43. package/dist/index.cjs +6 -3
  44. package/dist/index.d.cts +852 -14
  45. package/dist/index.d.ts +11 -5
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +4 -2
  48. package/dist/path-validator.cjs +28 -2
  49. package/dist/path-validator.d.ts +20 -9
  50. package/dist/path-validator.d.ts.map +1 -0
  51. package/dist/path-validator.js +28 -2
  52. package/dist/path-validator.test.d.ts +7 -2
  53. package/dist/path-validator.test.d.ts.map +1 -0
  54. package/dist/read-file-tool.cjs +26 -59
  55. package/dist/read-file-tool.d.ts +19 -9
  56. package/dist/read-file-tool.d.ts.map +1 -0
  57. package/dist/read-file-tool.js +27 -50
  58. package/dist/tool-factory-config.cjs +61 -0
  59. package/dist/{tool-provider.d.ts → tool-factory-config.d.ts} +13 -30
  60. package/dist/tool-factory-config.d.ts.map +1 -0
  61. package/dist/tool-factory-config.js +36 -0
  62. package/dist/tool-factory.cjs +123 -0
  63. package/dist/tool-factory.d.ts +4 -0
  64. package/dist/tool-factory.d.ts.map +1 -0
  65. package/dist/tool-factory.js +102 -0
  66. package/dist/types.d.ts +82 -18
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/write-file-tool.cjs +93 -99
  69. package/dist/write-file-tool.d.ts +22 -9
  70. package/dist/write-file-tool.d.ts.map +1 -0
  71. package/dist/write-file-tool.js +97 -91
  72. package/dist/write-file-tool.test.cjs +139 -33
  73. package/dist/write-file-tool.test.d.ts +7 -2
  74. package/dist/write-file-tool.test.d.ts.map +1 -0
  75. package/dist/write-file-tool.test.js +139 -33
  76. package/package.json +5 -4
  77. package/dist/directory-approval.integration.test.d.cts +0 -2
  78. package/dist/edit-file-tool.d.cts +0 -17
  79. package/dist/edit-file-tool.test.d.cts +0 -2
  80. package/dist/error-codes.d.cts +0 -32
  81. package/dist/errors.d.cts +0 -112
  82. package/dist/file-tool-types.d.cts +0 -46
  83. package/dist/filesystem-service.d.cts +0 -112
  84. package/dist/filesystem-service.test.d.cts +0 -2
  85. package/dist/glob-files-tool.d.cts +0 -17
  86. package/dist/grep-content-tool.d.cts +0 -17
  87. package/dist/path-validator.d.cts +0 -97
  88. package/dist/path-validator.test.d.cts +0 -2
  89. package/dist/read-file-tool.d.cts +0 -17
  90. package/dist/tool-provider.cjs +0 -123
  91. package/dist/tool-provider.d.cts +0 -77
  92. package/dist/tool-provider.js +0 -99
  93. package/dist/types.d.cts +0 -178
  94. package/dist/write-file-tool.d.cts +0 -17
  95. package/dist/write-file-tool.test.d.cts +0 -2
@@ -0,0 +1,63 @@
1
+ import * as path from "node:path";
2
+ import { ApprovalStatus, ApprovalType, ToolError } from "@dexto/core";
3
+ function resolveFilePath(workingDirectory, filePath) {
4
+ const resolvedPath = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(workingDirectory, filePath);
5
+ return { path: resolvedPath, parentDir: path.dirname(resolvedPath) };
6
+ }
7
+ function createDirectoryAccessApprovalHandlers(options) {
8
+ return {
9
+ approval: {
10
+ async override(input, context) {
11
+ const resolvedFileSystemService = await options.getFileSystemService(context);
12
+ const paths = options.resolvePaths(input, resolvedFileSystemService);
13
+ const isAllowed = await resolvedFileSystemService.isPathWithinConfigAllowed(
14
+ paths.path
15
+ );
16
+ if (isAllowed) {
17
+ return null;
18
+ }
19
+ const approvalManager = context.services?.approval;
20
+ if (!approvalManager) {
21
+ throw ToolError.configInvalid(
22
+ `${options.toolName} requires ToolExecutionContext.services.approval`
23
+ );
24
+ }
25
+ if (approvalManager.isDirectorySessionApproved(paths.path)) {
26
+ return null;
27
+ }
28
+ return {
29
+ type: ApprovalType.DIRECTORY_ACCESS,
30
+ metadata: {
31
+ path: paths.path,
32
+ parentDir: paths.parentDir,
33
+ operation: options.operation,
34
+ toolName: options.toolName
35
+ }
36
+ };
37
+ },
38
+ async onGranted(response, context, approvalRequest) {
39
+ const approvalManager = context.services?.approval;
40
+ if (!approvalManager) {
41
+ return;
42
+ }
43
+ if (response.status !== ApprovalStatus.APPROVED) {
44
+ return;
45
+ }
46
+ const data = response.data;
47
+ const rememberDirectory = data?.rememberDirectory ?? false;
48
+ const metadata = approvalRequest.metadata;
49
+ if (!metadata?.parentDir) {
50
+ return;
51
+ }
52
+ approvalManager.addApprovedDirectory(
53
+ metadata.parentDir,
54
+ rememberDirectory ? "session" : "once"
55
+ );
56
+ }
57
+ }
58
+ };
59
+ }
60
+ export {
61
+ createDirectoryAccessApprovalHandlers,
62
+ resolveFilePath
63
+ };
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,35 +15,24 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
  var edit_file_tool_exports = {};
30
20
  __export(edit_file_tool_exports, {
31
21
  createEditFileTool: () => createEditFileTool
32
22
  });
33
23
  module.exports = __toCommonJS(edit_file_tool_exports);
34
- var path = __toESM(require("node:path"), 1);
35
24
  var import_node_crypto = require("node:crypto");
36
25
  var import_zod = require("zod");
37
26
  var import_diff = require("diff");
38
27
  var import_core = require("@dexto/core");
39
- var import_core2 = require("@dexto/core");
40
- var import_core3 = require("@dexto/core");
41
- var import_core4 = require("@dexto/core");
42
28
  var import_error_codes = require("./error-codes.js");
29
+ var import_directory_approval = require("./directory-approval.js");
43
30
  const previewContentHashCache = /* @__PURE__ */ new Map();
44
31
  function computeContentHash(content) {
45
32
  return (0, import_node_crypto.createHash)("sha256").update(content, "utf8").digest("hex");
46
33
  }
47
34
  const EditFileInputSchema = import_zod.z.object({
48
- file_path: import_zod.z.string().describe("Absolute path to the file to edit"),
35
+ file_path: import_zod.z.string().min(1).describe("Absolute path to the file to edit"),
49
36
  old_string: import_zod.z.string().describe("Text to replace (must be unique unless replace_all is true)"),
50
37
  new_string: import_zod.z.string().describe("Replacement text"),
51
38
  replace_all: import_zod.z.boolean().optional().default(false).describe("Replace all occurrences (default: false, requires unique match)")
@@ -58,134 +45,136 @@ function generateDiffPreview(filePath, originalContent, newContent) {
58
45
  const deletions = (unified.match(/^-[^-]/gm) || []).length;
59
46
  return {
60
47
  type: "diff",
48
+ title: "Update file",
61
49
  unified,
62
50
  filename: filePath,
63
51
  additions,
64
52
  deletions
65
53
  };
66
54
  }
67
- function createEditFileTool(options) {
68
- const { fileSystemService, directoryApproval } = options;
69
- let pendingApprovalParentDir;
70
- return {
55
+ function createEditFileTool(getFileSystemService) {
56
+ return (0, import_core.defineTool)({
71
57
  id: "edit_file",
58
+ aliases: ["edit"],
72
59
  description: "Edit a file by replacing text. By default, old_string must be unique in the file (will error if found multiple times). Set replace_all=true to replace all occurrences. Automatically creates backup before editing. Requires approval. Returns success status, path, number of changes made, and backup path.",
73
60
  inputSchema: EditFileInputSchema,
74
- /**
75
- * Check if this edit operation needs directory access approval.
76
- * Returns custom approval request if the file is outside allowed paths.
77
- */
78
- getApprovalOverride: async (args) => {
79
- const { file_path } = args;
80
- if (!file_path) return null;
81
- const isAllowed = await fileSystemService.isPathWithinConfigAllowed(file_path);
82
- if (isAllowed) {
83
- return null;
84
- }
85
- if (directoryApproval?.isSessionApproved(file_path)) {
86
- return null;
87
- }
88
- const absolutePath = path.resolve(file_path);
89
- const parentDir = path.dirname(absolutePath);
90
- pendingApprovalParentDir = parentDir;
91
- return {
92
- type: import_core.ApprovalType.DIRECTORY_ACCESS,
93
- metadata: {
94
- path: absolutePath,
95
- parentDir,
96
- operation: "edit",
97
- toolName: "edit_file"
98
- }
99
- };
100
- },
101
- /**
102
- * Handle approved directory access - remember the directory for session
103
- */
104
- onApprovalGranted: (response) => {
105
- if (!directoryApproval || !pendingApprovalParentDir) return;
106
- const data = response.data;
107
- const rememberDirectory = data?.rememberDirectory ?? false;
108
- directoryApproval.addApproved(
109
- pendingApprovalParentDir,
110
- rememberDirectory ? "session" : "once"
111
- );
112
- pendingApprovalParentDir = void 0;
113
- },
114
- /**
115
- * Generate preview for approval UI - shows diff without modifying file
116
- * Throws ToolError.validationFailed() for validation errors (file not found, string not found)
117
- * Stores content hash for change detection in execute phase.
118
- */
119
- generatePreview: async (input, context) => {
120
- const { file_path, old_string, new_string, replace_all } = input;
121
- try {
122
- const originalFile = await fileSystemService.readFile(file_path);
123
- const originalContent = originalFile.content;
124
- if (context?.toolCallId) {
125
- previewContentHashCache.set(
126
- context.toolCallId,
127
- computeContentHash(originalContent)
128
- );
129
- }
130
- if (!replace_all) {
131
- const occurrences = originalContent.split(old_string).length - 1;
132
- if (occurrences > 1) {
133
- throw import_core2.ToolError.validationFailed(
61
+ ...(0, import_directory_approval.createDirectoryAccessApprovalHandlers)({
62
+ toolName: "edit_file",
63
+ operation: "edit",
64
+ inputSchema: EditFileInputSchema,
65
+ getFileSystemService,
66
+ resolvePaths: (input, fileSystemService) => (0, import_directory_approval.resolveFilePath)(fileSystemService.getWorkingDirectory(), input.file_path)
67
+ }),
68
+ presentation: {
69
+ describeHeader: (input) => (0, import_core.createLocalToolCallHeader)({
70
+ title: "Update",
71
+ argsText: (0, import_core.truncateForHeader)(input.file_path, 140)
72
+ }),
73
+ /**
74
+ * Generate preview for approval UI - shows diff without modifying file
75
+ * Throws ToolError.validationFailed() for validation errors (file not found, string not found)
76
+ * Stores content hash for change detection in execute phase.
77
+ */
78
+ preview: async (input, context) => {
79
+ const { file_path, old_string, new_string, replace_all } = input;
80
+ const resolvedFileSystemService = await getFileSystemService(context);
81
+ const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
82
+ resolvedFileSystemService.getWorkingDirectory(),
83
+ file_path
84
+ );
85
+ try {
86
+ let originalContent;
87
+ try {
88
+ const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
89
+ originalContent = originalFile.content;
90
+ } catch (error) {
91
+ if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.INVALID_PATH) {
92
+ const originalFile = await resolvedFileSystemService.readFileForToolPreview(
93
+ resolvedPath
94
+ );
95
+ originalContent = originalFile.content;
96
+ } else {
97
+ throw error;
98
+ }
99
+ }
100
+ if (context.toolCallId) {
101
+ previewContentHashCache.set(
102
+ context.toolCallId,
103
+ computeContentHash(originalContent)
104
+ );
105
+ }
106
+ if (!replace_all) {
107
+ const occurrences = originalContent.split(old_string).length - 1;
108
+ if (occurrences > 1) {
109
+ throw import_core.ToolError.validationFailed(
110
+ "edit_file",
111
+ `String found ${occurrences} times in file. Set replace_all=true to replace all, or provide more context to make old_string unique.`,
112
+ { file_path: resolvedPath, occurrences }
113
+ );
114
+ }
115
+ }
116
+ const newContent = replace_all ? originalContent.split(old_string).join(new_string) : originalContent.replace(old_string, new_string);
117
+ if (originalContent === newContent) {
118
+ throw import_core.ToolError.validationFailed(
134
119
  "edit_file",
135
- `String found ${occurrences} times in file. Set replace_all=true to replace all, or provide more context to make old_string unique.`,
136
- { file_path, occurrences }
120
+ `String not found in file: "${old_string.slice(0, 50)}${old_string.length > 50 ? "..." : ""}"`,
121
+ {
122
+ file_path: resolvedPath,
123
+ old_string_preview: old_string.slice(0, 100)
124
+ }
137
125
  );
138
126
  }
127
+ return generateDiffPreview(resolvedPath, originalContent, newContent);
128
+ } catch (error) {
129
+ if (error instanceof import_core.DextoRuntimeError && error.code === import_core.ToolErrorCode.VALIDATION_FAILED) {
130
+ throw error;
131
+ }
132
+ if (error instanceof import_core.DextoRuntimeError) {
133
+ throw import_core.ToolError.validationFailed("edit_file", error.message, {
134
+ file_path: resolvedPath,
135
+ originalErrorCode: error.code
136
+ });
137
+ }
138
+ return null;
139
139
  }
140
- const newContent = replace_all ? originalContent.split(old_string).join(new_string) : originalContent.replace(old_string, new_string);
141
- if (originalContent === newContent) {
142
- throw import_core2.ToolError.validationFailed(
143
- "edit_file",
144
- `String not found in file: "${old_string.slice(0, 50)}${old_string.length > 50 ? "..." : ""}"`,
145
- { file_path, old_string_preview: old_string.slice(0, 100) }
146
- );
147
- }
148
- return generateDiffPreview(file_path, originalContent, newContent);
149
- } catch (error) {
150
- if (error instanceof import_core4.DextoRuntimeError && error.code === import_core3.ToolErrorCode.VALIDATION_FAILED) {
151
- throw error;
152
- }
153
- if (error instanceof import_core4.DextoRuntimeError) {
154
- throw import_core2.ToolError.validationFailed("edit_file", error.message, {
155
- file_path,
156
- originalErrorCode: error.code
157
- });
158
- }
159
- return null;
160
140
  }
161
141
  },
162
- execute: async (input, context) => {
142
+ async execute(input, context) {
143
+ const resolvedFileSystemService = await getFileSystemService(context);
163
144
  const { file_path, old_string, new_string, replace_all } = input;
164
- if (context?.toolCallId && previewContentHashCache.has(context.toolCallId)) {
165
- const expectedHash = previewContentHashCache.get(context.toolCallId);
166
- previewContentHashCache.delete(context.toolCallId);
167
- let currentContent;
168
- try {
169
- const currentFile = await fileSystemService.readFile(file_path);
170
- currentContent = currentFile.content;
171
- } catch (error) {
172
- if (error instanceof import_core4.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
173
- throw import_core2.ToolError.fileModifiedSincePreview("edit_file", file_path);
145
+ const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
146
+ resolvedFileSystemService.getWorkingDirectory(),
147
+ file_path
148
+ );
149
+ const toolCallId = context.toolCallId;
150
+ if (toolCallId) {
151
+ const expectedHash = previewContentHashCache.get(toolCallId);
152
+ if (expectedHash === void 0) {
153
+ } else {
154
+ previewContentHashCache.delete(toolCallId);
155
+ let currentContent;
156
+ try {
157
+ const currentFile = await resolvedFileSystemService.readFile(resolvedPath);
158
+ currentContent = currentFile.content;
159
+ } catch (error) {
160
+ if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
161
+ throw import_core.ToolError.fileModifiedSincePreview("edit_file", resolvedPath);
162
+ }
163
+ throw error;
164
+ }
165
+ const currentHash = computeContentHash(currentContent);
166
+ if (expectedHash !== currentHash) {
167
+ throw import_core.ToolError.fileModifiedSincePreview("edit_file", resolvedPath);
174
168
  }
175
- throw error;
176
- }
177
- const currentHash = computeContentHash(currentContent);
178
- if (expectedHash !== currentHash) {
179
- throw import_core2.ToolError.fileModifiedSincePreview("edit_file", file_path);
180
169
  }
181
170
  }
182
- const result = await fileSystemService.editFile(file_path, {
171
+ const result = await resolvedFileSystemService.editFile(resolvedPath, {
183
172
  oldString: old_string,
184
173
  newString: new_string,
185
174
  replaceAll: replace_all
186
175
  });
187
176
  const _display = generateDiffPreview(
188
- file_path,
177
+ resolvedPath,
189
178
  result.originalContent,
190
179
  result.newContent
191
180
  );
@@ -197,7 +186,7 @@ function createEditFileTool(options) {
197
186
  _display
198
187
  };
199
188
  }
200
- };
189
+ });
201
190
  }
202
191
  // Annotate the CommonJS export names for ESM import in node:
203
192
  0 && (module.exports = {
@@ -1,17 +1,30 @@
1
- import { InternalTool } from '@dexto/core';
2
- import { FileToolOptions } from './file-tool-types.js';
3
- import './filesystem-service.js';
4
- import './types.js';
5
-
6
1
  /**
7
2
  * Edit File Tool
8
3
  *
9
4
  * Internal tool for editing files by replacing text (requires approval)
10
5
  */
11
-
6
+ import { z } from 'zod';
7
+ import type { Tool } from '@dexto/core';
8
+ import type { FileSystemServiceGetter } from './file-tool-types.js';
9
+ declare const EditFileInputSchema: z.ZodObject<{
10
+ file_path: z.ZodString;
11
+ old_string: z.ZodString;
12
+ new_string: z.ZodString;
13
+ replace_all: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
14
+ }, "strict", z.ZodTypeAny, {
15
+ file_path: string;
16
+ old_string: string;
17
+ new_string: string;
18
+ replace_all: boolean;
19
+ }, {
20
+ file_path: string;
21
+ old_string: string;
22
+ new_string: string;
23
+ replace_all?: boolean | undefined;
24
+ }>;
12
25
  /**
13
26
  * Create the edit_file internal tool with directory approval support
14
27
  */
15
- declare function createEditFileTool(options: FileToolOptions): InternalTool;
16
-
17
- export { createEditFileTool };
28
+ export declare function createEditFileTool(getFileSystemService: FileSystemServiceGetter): Tool<typeof EditFileInputSchema>;
29
+ export {};
30
+ //# sourceMappingURL=edit-file-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-file-tool.d.ts","sourceRoot":"","sources":["../src/edit-file-tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,aAAa,CAAC;AAE9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAkBpE,QAAA,MAAM,mBAAmB;;;;;;;;;;;;;;;EAaZ,CAAC;AA0Bd;;GAEG;AACH,wBAAgB,kBAAkB,CAC9B,oBAAoB,EAAE,uBAAuB,GAC9C,IAAI,CAAC,OAAO,mBAAmB,CAAC,CA2LlC"}