@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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { 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
|
+
async getApprovalOverride(input, context) {
|
|
10
|
+
const resolvedFileSystemService = await options.getFileSystemService(context);
|
|
11
|
+
const paths = options.resolvePaths(input, resolvedFileSystemService);
|
|
12
|
+
const isAllowed = await resolvedFileSystemService.isPathWithinConfigAllowed(paths.path);
|
|
13
|
+
if (isAllowed) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const approvalManager = context.services?.approval;
|
|
17
|
+
if (!approvalManager) {
|
|
18
|
+
throw ToolError.configInvalid(
|
|
19
|
+
`${options.toolName} requires ToolExecutionContext.services.approval`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (approvalManager.isDirectorySessionApproved(paths.path)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
type: ApprovalType.DIRECTORY_ACCESS,
|
|
27
|
+
metadata: {
|
|
28
|
+
path: paths.path,
|
|
29
|
+
parentDir: paths.parentDir,
|
|
30
|
+
operation: options.operation,
|
|
31
|
+
toolName: options.toolName
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
onApprovalGranted(response, context, approvalRequest) {
|
|
36
|
+
if (approvalRequest.type !== ApprovalType.DIRECTORY_ACCESS) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const metadata = approvalRequest.metadata;
|
|
40
|
+
const parentDir = typeof metadata?.parentDir === "string" ? metadata.parentDir : null;
|
|
41
|
+
if (!parentDir) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const data = response.data;
|
|
45
|
+
const rememberDirectory = data?.rememberDirectory ?? false;
|
|
46
|
+
const approvalManager = context.services?.approval;
|
|
47
|
+
if (!approvalManager) {
|
|
48
|
+
throw ToolError.configInvalid(
|
|
49
|
+
`${options.toolName} requires ToolExecutionContext.services.approval`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
approvalManager.addApprovedDirectory(parentDir, rememberDirectory ? "session" : "once");
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
createDirectoryAccessApprovalHandlers,
|
|
58
|
+
resolveFilePath
|
|
59
|
+
};
|
package/dist/edit-file-tool.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,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)")
|
|
@@ -64,64 +51,35 @@ function generateDiffPreview(filePath, originalContent, newContent) {
|
|
|
64
51
|
deletions
|
|
65
52
|
};
|
|
66
53
|
}
|
|
67
|
-
function createEditFileTool(
|
|
68
|
-
|
|
69
|
-
let pendingApprovalParentDir;
|
|
70
|
-
return {
|
|
54
|
+
function createEditFileTool(getFileSystemService) {
|
|
55
|
+
return (0, import_core.defineTool)({
|
|
71
56
|
id: "edit_file",
|
|
57
|
+
displayName: "Update",
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
},
|
|
61
|
+
...(0, import_directory_approval.createDirectoryAccessApprovalHandlers)({
|
|
62
|
+
toolName: "edit_file",
|
|
63
|
+
operation: "edit",
|
|
64
|
+
getFileSystemService,
|
|
65
|
+
resolvePaths: (input, fileSystemService) => (0, import_directory_approval.resolveFilePath)(fileSystemService.getWorkingDirectory(), input.file_path)
|
|
66
|
+
}),
|
|
114
67
|
/**
|
|
115
68
|
* Generate preview for approval UI - shows diff without modifying file
|
|
116
69
|
* Throws ToolError.validationFailed() for validation errors (file not found, string not found)
|
|
117
70
|
* Stores content hash for change detection in execute phase.
|
|
118
71
|
*/
|
|
119
|
-
|
|
72
|
+
async generatePreview(input, context) {
|
|
120
73
|
const { file_path, old_string, new_string, replace_all } = input;
|
|
74
|
+
const resolvedFileSystemService = await getFileSystemService(context);
|
|
75
|
+
const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
|
|
76
|
+
resolvedFileSystemService.getWorkingDirectory(),
|
|
77
|
+
file_path
|
|
78
|
+
);
|
|
121
79
|
try {
|
|
122
|
-
const originalFile = await
|
|
80
|
+
const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
|
|
123
81
|
const originalContent = originalFile.content;
|
|
124
|
-
if (context
|
|
82
|
+
if (context.toolCallId) {
|
|
125
83
|
previewContentHashCache.set(
|
|
126
84
|
context.toolCallId,
|
|
127
85
|
computeContentHash(originalContent)
|
|
@@ -130,62 +88,71 @@ function createEditFileTool(options) {
|
|
|
130
88
|
if (!replace_all) {
|
|
131
89
|
const occurrences = originalContent.split(old_string).length - 1;
|
|
132
90
|
if (occurrences > 1) {
|
|
133
|
-
throw
|
|
91
|
+
throw import_core.ToolError.validationFailed(
|
|
134
92
|
"edit_file",
|
|
135
93
|
`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 }
|
|
94
|
+
{ file_path: resolvedPath, occurrences }
|
|
137
95
|
);
|
|
138
96
|
}
|
|
139
97
|
}
|
|
140
98
|
const newContent = replace_all ? originalContent.split(old_string).join(new_string) : originalContent.replace(old_string, new_string);
|
|
141
99
|
if (originalContent === newContent) {
|
|
142
|
-
throw
|
|
100
|
+
throw import_core.ToolError.validationFailed(
|
|
143
101
|
"edit_file",
|
|
144
102
|
`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) }
|
|
103
|
+
{ file_path: resolvedPath, old_string_preview: old_string.slice(0, 100) }
|
|
146
104
|
);
|
|
147
105
|
}
|
|
148
|
-
return generateDiffPreview(
|
|
106
|
+
return generateDiffPreview(resolvedPath, originalContent, newContent);
|
|
149
107
|
} catch (error) {
|
|
150
|
-
if (error instanceof
|
|
108
|
+
if (error instanceof import_core.DextoRuntimeError && error.code === import_core.ToolErrorCode.VALIDATION_FAILED) {
|
|
151
109
|
throw error;
|
|
152
110
|
}
|
|
153
|
-
if (error instanceof
|
|
154
|
-
throw
|
|
155
|
-
file_path,
|
|
111
|
+
if (error instanceof import_core.DextoRuntimeError) {
|
|
112
|
+
throw import_core.ToolError.validationFailed("edit_file", error.message, {
|
|
113
|
+
file_path: resolvedPath,
|
|
156
114
|
originalErrorCode: error.code
|
|
157
115
|
});
|
|
158
116
|
}
|
|
159
117
|
return null;
|
|
160
118
|
}
|
|
161
119
|
},
|
|
162
|
-
|
|
120
|
+
async execute(input, context) {
|
|
121
|
+
const resolvedFileSystemService = await getFileSystemService(context);
|
|
163
122
|
const { file_path, old_string, new_string, replace_all } = input;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
123
|
+
const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
|
|
124
|
+
resolvedFileSystemService.getWorkingDirectory(),
|
|
125
|
+
file_path
|
|
126
|
+
);
|
|
127
|
+
const toolCallId = context.toolCallId;
|
|
128
|
+
if (toolCallId) {
|
|
129
|
+
const expectedHash = previewContentHashCache.get(toolCallId);
|
|
130
|
+
if (expectedHash === void 0) {
|
|
131
|
+
} else {
|
|
132
|
+
previewContentHashCache.delete(toolCallId);
|
|
133
|
+
let currentContent;
|
|
134
|
+
try {
|
|
135
|
+
const currentFile = await resolvedFileSystemService.readFile(resolvedPath);
|
|
136
|
+
currentContent = currentFile.content;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
|
|
139
|
+
throw import_core.ToolError.fileModifiedSincePreview("edit_file", resolvedPath);
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
const currentHash = computeContentHash(currentContent);
|
|
144
|
+
if (expectedHash !== currentHash) {
|
|
145
|
+
throw import_core.ToolError.fileModifiedSincePreview("edit_file", resolvedPath);
|
|
174
146
|
}
|
|
175
|
-
throw error;
|
|
176
|
-
}
|
|
177
|
-
const currentHash = computeContentHash(currentContent);
|
|
178
|
-
if (expectedHash !== currentHash) {
|
|
179
|
-
throw import_core2.ToolError.fileModifiedSincePreview("edit_file", file_path);
|
|
180
147
|
}
|
|
181
148
|
}
|
|
182
|
-
const result = await
|
|
149
|
+
const result = await resolvedFileSystemService.editFile(resolvedPath, {
|
|
183
150
|
oldString: old_string,
|
|
184
151
|
newString: new_string,
|
|
185
152
|
replaceAll: replace_all
|
|
186
153
|
});
|
|
187
154
|
const _display = generateDiffPreview(
|
|
188
|
-
|
|
155
|
+
resolvedPath,
|
|
189
156
|
result.originalContent,
|
|
190
157
|
result.newContent
|
|
191
158
|
);
|
|
@@ -197,7 +164,7 @@ function createEditFileTool(options) {
|
|
|
197
164
|
_display
|
|
198
165
|
};
|
|
199
166
|
}
|
|
200
|
-
};
|
|
167
|
+
});
|
|
201
168
|
}
|
|
202
169
|
// Annotate the CommonJS export names for ESM import in node:
|
|
203
170
|
0 && (module.exports = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Tool } from '@dexto/core';
|
|
3
|
+
import { FileSystemServiceGetter } from './file-tool-types.cjs';
|
|
3
4
|
import './filesystem-service.cjs';
|
|
4
5
|
import './types.cjs';
|
|
5
6
|
|
|
@@ -9,9 +10,25 @@ import './types.cjs';
|
|
|
9
10
|
* Internal tool for editing files by replacing text (requires approval)
|
|
10
11
|
*/
|
|
11
12
|
|
|
13
|
+
declare const EditFileInputSchema: z.ZodObject<{
|
|
14
|
+
file_path: z.ZodString;
|
|
15
|
+
old_string: z.ZodString;
|
|
16
|
+
new_string: z.ZodString;
|
|
17
|
+
replace_all: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
18
|
+
}, "strict", z.ZodTypeAny, {
|
|
19
|
+
file_path: string;
|
|
20
|
+
old_string: string;
|
|
21
|
+
new_string: string;
|
|
22
|
+
replace_all: boolean;
|
|
23
|
+
}, {
|
|
24
|
+
file_path: string;
|
|
25
|
+
old_string: string;
|
|
26
|
+
new_string: string;
|
|
27
|
+
replace_all?: boolean | undefined;
|
|
28
|
+
}>;
|
|
12
29
|
/**
|
|
13
30
|
* Create the edit_file internal tool with directory approval support
|
|
14
31
|
*/
|
|
15
|
-
declare function createEditFileTool(
|
|
32
|
+
declare function createEditFileTool(getFileSystemService: FileSystemServiceGetter): Tool<typeof EditFileInputSchema>;
|
|
16
33
|
|
|
17
34
|
export { createEditFileTool };
|
package/dist/edit-file-tool.d.ts
CHANGED
|
@@ -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(
|
|
16
|
-
|
|
17
|
-
|
|
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;AAGxB,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,aAAa,CAAC;AAE9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAkBpE,QAAA,MAAM,mBAAmB;;;;;;;;;;;;;;;EAaZ,CAAC;AAyBd;;GAEG;AACH,wBAAgB,kBAAkB,CAC9B,oBAAoB,EAAE,uBAAuB,GAC9C,IAAI,CAAC,OAAO,mBAAmB,CAAC,CAgKlC"}
|
package/dist/edit-file-tool.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import { createHash } from "node:crypto";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
import { createPatch } from "diff";
|
|
5
|
-
import {
|
|
6
|
-
import { ToolError } from "@dexto/core";
|
|
7
|
-
import { ToolErrorCode } from "@dexto/core";
|
|
8
|
-
import { DextoRuntimeError } from "@dexto/core";
|
|
4
|
+
import { DextoRuntimeError, ToolError, ToolErrorCode, defineTool } from "@dexto/core";
|
|
9
5
|
import { FileSystemErrorCode } from "./error-codes.js";
|
|
6
|
+
import { createDirectoryAccessApprovalHandlers, resolveFilePath } from "./directory-approval.js";
|
|
10
7
|
const previewContentHashCache = /* @__PURE__ */ new Map();
|
|
11
8
|
function computeContentHash(content) {
|
|
12
9
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
13
10
|
}
|
|
14
11
|
const EditFileInputSchema = z.object({
|
|
15
|
-
file_path: z.string().describe("Absolute path to the file to edit"),
|
|
12
|
+
file_path: z.string().min(1).describe("Absolute path to the file to edit"),
|
|
16
13
|
old_string: z.string().describe("Text to replace (must be unique unless replace_all is true)"),
|
|
17
14
|
new_string: z.string().describe("Replacement text"),
|
|
18
15
|
replace_all: z.boolean().optional().default(false).describe("Replace all occurrences (default: false, requires unique match)")
|
|
@@ -31,64 +28,35 @@ function generateDiffPreview(filePath, originalContent, newContent) {
|
|
|
31
28
|
deletions
|
|
32
29
|
};
|
|
33
30
|
}
|
|
34
|
-
function createEditFileTool(
|
|
35
|
-
|
|
36
|
-
let pendingApprovalParentDir;
|
|
37
|
-
return {
|
|
31
|
+
function createEditFileTool(getFileSystemService) {
|
|
32
|
+
return defineTool({
|
|
38
33
|
id: "edit_file",
|
|
34
|
+
displayName: "Update",
|
|
35
|
+
aliases: ["edit"],
|
|
39
36
|
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.",
|
|
40
37
|
inputSchema: EditFileInputSchema,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!file_path) return null;
|
|
48
|
-
const isAllowed = await fileSystemService.isPathWithinConfigAllowed(file_path);
|
|
49
|
-
if (isAllowed) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
if (directoryApproval?.isSessionApproved(file_path)) {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
const absolutePath = path.resolve(file_path);
|
|
56
|
-
const parentDir = path.dirname(absolutePath);
|
|
57
|
-
pendingApprovalParentDir = parentDir;
|
|
58
|
-
return {
|
|
59
|
-
type: ApprovalType.DIRECTORY_ACCESS,
|
|
60
|
-
metadata: {
|
|
61
|
-
path: absolutePath,
|
|
62
|
-
parentDir,
|
|
63
|
-
operation: "edit",
|
|
64
|
-
toolName: "edit_file"
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
},
|
|
68
|
-
/**
|
|
69
|
-
* Handle approved directory access - remember the directory for session
|
|
70
|
-
*/
|
|
71
|
-
onApprovalGranted: (response) => {
|
|
72
|
-
if (!directoryApproval || !pendingApprovalParentDir) return;
|
|
73
|
-
const data = response.data;
|
|
74
|
-
const rememberDirectory = data?.rememberDirectory ?? false;
|
|
75
|
-
directoryApproval.addApproved(
|
|
76
|
-
pendingApprovalParentDir,
|
|
77
|
-
rememberDirectory ? "session" : "once"
|
|
78
|
-
);
|
|
79
|
-
pendingApprovalParentDir = void 0;
|
|
80
|
-
},
|
|
38
|
+
...createDirectoryAccessApprovalHandlers({
|
|
39
|
+
toolName: "edit_file",
|
|
40
|
+
operation: "edit",
|
|
41
|
+
getFileSystemService,
|
|
42
|
+
resolvePaths: (input, fileSystemService) => resolveFilePath(fileSystemService.getWorkingDirectory(), input.file_path)
|
|
43
|
+
}),
|
|
81
44
|
/**
|
|
82
45
|
* Generate preview for approval UI - shows diff without modifying file
|
|
83
46
|
* Throws ToolError.validationFailed() for validation errors (file not found, string not found)
|
|
84
47
|
* Stores content hash for change detection in execute phase.
|
|
85
48
|
*/
|
|
86
|
-
|
|
49
|
+
async generatePreview(input, context) {
|
|
87
50
|
const { file_path, old_string, new_string, replace_all } = input;
|
|
51
|
+
const resolvedFileSystemService = await getFileSystemService(context);
|
|
52
|
+
const { path: resolvedPath } = resolveFilePath(
|
|
53
|
+
resolvedFileSystemService.getWorkingDirectory(),
|
|
54
|
+
file_path
|
|
55
|
+
);
|
|
88
56
|
try {
|
|
89
|
-
const originalFile = await
|
|
57
|
+
const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
|
|
90
58
|
const originalContent = originalFile.content;
|
|
91
|
-
if (context
|
|
59
|
+
if (context.toolCallId) {
|
|
92
60
|
previewContentHashCache.set(
|
|
93
61
|
context.toolCallId,
|
|
94
62
|
computeContentHash(originalContent)
|
|
@@ -100,7 +68,7 @@ function createEditFileTool(options) {
|
|
|
100
68
|
throw ToolError.validationFailed(
|
|
101
69
|
"edit_file",
|
|
102
70
|
`String found ${occurrences} times in file. Set replace_all=true to replace all, or provide more context to make old_string unique.`,
|
|
103
|
-
{ file_path, occurrences }
|
|
71
|
+
{ file_path: resolvedPath, occurrences }
|
|
104
72
|
);
|
|
105
73
|
}
|
|
106
74
|
}
|
|
@@ -109,50 +77,59 @@ function createEditFileTool(options) {
|
|
|
109
77
|
throw ToolError.validationFailed(
|
|
110
78
|
"edit_file",
|
|
111
79
|
`String not found in file: "${old_string.slice(0, 50)}${old_string.length > 50 ? "..." : ""}"`,
|
|
112
|
-
{ file_path, old_string_preview: old_string.slice(0, 100) }
|
|
80
|
+
{ file_path: resolvedPath, old_string_preview: old_string.slice(0, 100) }
|
|
113
81
|
);
|
|
114
82
|
}
|
|
115
|
-
return generateDiffPreview(
|
|
83
|
+
return generateDiffPreview(resolvedPath, originalContent, newContent);
|
|
116
84
|
} catch (error) {
|
|
117
85
|
if (error instanceof DextoRuntimeError && error.code === ToolErrorCode.VALIDATION_FAILED) {
|
|
118
86
|
throw error;
|
|
119
87
|
}
|
|
120
88
|
if (error instanceof DextoRuntimeError) {
|
|
121
89
|
throw ToolError.validationFailed("edit_file", error.message, {
|
|
122
|
-
file_path,
|
|
90
|
+
file_path: resolvedPath,
|
|
123
91
|
originalErrorCode: error.code
|
|
124
92
|
});
|
|
125
93
|
}
|
|
126
94
|
return null;
|
|
127
95
|
}
|
|
128
96
|
},
|
|
129
|
-
|
|
97
|
+
async execute(input, context) {
|
|
98
|
+
const resolvedFileSystemService = await getFileSystemService(context);
|
|
130
99
|
const { file_path, old_string, new_string, replace_all } = input;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
100
|
+
const { path: resolvedPath } = resolveFilePath(
|
|
101
|
+
resolvedFileSystemService.getWorkingDirectory(),
|
|
102
|
+
file_path
|
|
103
|
+
);
|
|
104
|
+
const toolCallId = context.toolCallId;
|
|
105
|
+
if (toolCallId) {
|
|
106
|
+
const expectedHash = previewContentHashCache.get(toolCallId);
|
|
107
|
+
if (expectedHash === void 0) {
|
|
108
|
+
} else {
|
|
109
|
+
previewContentHashCache.delete(toolCallId);
|
|
110
|
+
let currentContent;
|
|
111
|
+
try {
|
|
112
|
+
const currentFile = await resolvedFileSystemService.readFile(resolvedPath);
|
|
113
|
+
currentContent = currentFile.content;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error instanceof DextoRuntimeError && error.code === FileSystemErrorCode.FILE_NOT_FOUND) {
|
|
116
|
+
throw ToolError.fileModifiedSincePreview("edit_file", resolvedPath);
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
const currentHash = computeContentHash(currentContent);
|
|
121
|
+
if (expectedHash !== currentHash) {
|
|
122
|
+
throw ToolError.fileModifiedSincePreview("edit_file", resolvedPath);
|
|
141
123
|
}
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
144
|
-
const currentHash = computeContentHash(currentContent);
|
|
145
|
-
if (expectedHash !== currentHash) {
|
|
146
|
-
throw ToolError.fileModifiedSincePreview("edit_file", file_path);
|
|
147
124
|
}
|
|
148
125
|
}
|
|
149
|
-
const result = await
|
|
126
|
+
const result = await resolvedFileSystemService.editFile(resolvedPath, {
|
|
150
127
|
oldString: old_string,
|
|
151
128
|
newString: new_string,
|
|
152
129
|
replaceAll: replace_all
|
|
153
130
|
});
|
|
154
131
|
const _display = generateDiffPreview(
|
|
155
|
-
|
|
132
|
+
resolvedPath,
|
|
156
133
|
result.originalContent,
|
|
157
134
|
result.newContent
|
|
158
135
|
);
|
|
@@ -164,7 +141,7 @@ function createEditFileTool(options) {
|
|
|
164
141
|
_display
|
|
165
142
|
};
|
|
166
143
|
}
|
|
167
|
-
};
|
|
144
|
+
});
|
|
168
145
|
}
|
|
169
146
|
export {
|
|
170
147
|
createEditFileTool
|