@dexto/tools-filesystem 1.7.1 → 1.8.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 +5 -4
- package/dist/directory-approval.d.ts +2 -1
- package/dist/directory-approval.d.ts.map +1 -1
- package/dist/directory-approval.integration.test.cjs +73 -33
- package/dist/directory-approval.integration.test.js +73 -33
- package/dist/directory-approval.js +2 -1
- package/dist/edit-file-tool.cjs +52 -37
- package/dist/edit-file-tool.d.ts +1 -1
- package/dist/edit-file-tool.d.ts.map +1 -1
- package/dist/edit-file-tool.js +43 -29
- package/dist/edit-file-tool.test.cjs +159 -2
- package/dist/edit-file-tool.test.js +159 -2
- package/dist/errors.cjs +53 -53
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/file-tool-types.d.ts +1 -1
- package/dist/file-tool-types.d.ts.map +1 -1
- package/dist/filesystem-service.cjs +60 -60
- package/dist/filesystem-service.d.ts +1 -1
- package/dist/filesystem-service.d.ts.map +1 -1
- package/dist/filesystem-service.js +5 -5
- package/dist/filesystem-service.test.cjs +1 -3
- package/dist/filesystem-service.test.js +1 -3
- package/dist/glob-files-tool.cjs +27 -24
- package/dist/glob-files-tool.d.ts +1 -1
- package/dist/glob-files-tool.d.ts.map +1 -1
- package/dist/glob-files-tool.js +24 -21
- package/dist/glob-files-tool.test.cjs +100 -88
- package/dist/glob-files-tool.test.js +101 -67
- package/dist/grep-content-tool.cjs +129 -44
- package/dist/grep-content-tool.d.ts +1 -1
- package/dist/grep-content-tool.d.ts.map +1 -1
- package/dist/grep-content-tool.js +120 -41
- package/dist/grep-content-tool.test.cjs +122 -87
- package/dist/grep-content-tool.test.js +123 -66
- package/dist/index.d.cts +3 -4
- package/dist/path-validator.d.ts +1 -1
- package/dist/path-validator.d.ts.map +1 -1
- package/dist/read-file-tool.cjs +43 -14
- package/dist/read-file-tool.d.ts +1 -1
- package/dist/read-file-tool.d.ts.map +1 -1
- package/dist/read-file-tool.js +40 -11
- package/dist/read-file-tool.test.cjs +119 -0
- package/dist/read-file-tool.test.d.ts +2 -0
- package/dist/read-file-tool.test.d.ts.map +1 -0
- package/dist/read-file-tool.test.js +96 -0
- package/dist/read-media-file-tool.cjs +4 -4
- package/dist/read-media-file-tool.d.ts +1 -1
- package/dist/read-media-file-tool.d.ts.map +1 -1
- package/dist/read-media-file-tool.js +1 -1
- package/dist/tool-factory.cjs +2 -2
- package/dist/tool-factory.js +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/workspace-paths.cjs +87 -0
- package/dist/workspace-paths.d.ts +4 -0
- package/dist/workspace-paths.d.ts.map +1 -0
- package/dist/workspace-paths.js +51 -0
- package/dist/write-file-tool.cjs +74 -34
- package/dist/write-file-tool.d.ts +1 -2
- package/dist/write-file-tool.d.ts.map +1 -1
- package/dist/write-file-tool.js +68 -29
- package/dist/write-file-tool.test.cjs +262 -11
- package/dist/write-file-tool.test.js +262 -11
- package/package.json +3 -3
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { DextoRuntimeError } from "@dexto/core/errors";
|
|
3
|
+
import { WorkspaceErrorCodes } from "@dexto/core/workspace";
|
|
4
|
+
import { ToolError } from "@dexto/core/tools";
|
|
5
|
+
function toWorkspaceRelativePath(toolName, workspaceRoot, filePath) {
|
|
6
|
+
if (!path.isAbsolute(filePath)) {
|
|
7
|
+
assertRelativePath(toolName, filePath);
|
|
8
|
+
return filePath.split(path.sep).join("/");
|
|
9
|
+
}
|
|
10
|
+
const relativePath = path.relative(workspaceRoot, filePath);
|
|
11
|
+
if (relativePath === "") {
|
|
12
|
+
return ".";
|
|
13
|
+
}
|
|
14
|
+
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
15
|
+
throw ToolError.validationFailed(
|
|
16
|
+
toolName,
|
|
17
|
+
`Path must be inside the active workspace: ${filePath}`,
|
|
18
|
+
{ file_path: filePath, workspaceRoot }
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return relativePath.split(path.sep).join("/");
|
|
22
|
+
}
|
|
23
|
+
function isWorkspaceFileNotFound(error) {
|
|
24
|
+
if (error instanceof DextoRuntimeError && error.code === WorkspaceErrorCodes.FILE_NOT_FOUND) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === WorkspaceErrorCodes.FILE_NOT_FOUND;
|
|
28
|
+
}
|
|
29
|
+
function assertWorkspaceRelativeGlob(toolName, pattern) {
|
|
30
|
+
if (path.isAbsolute(pattern) || pattern.split(/[\\/]/).includes("..")) {
|
|
31
|
+
throw ToolError.validationFailed(
|
|
32
|
+
toolName,
|
|
33
|
+
`Glob pattern must stay inside the active workspace: ${pattern}`,
|
|
34
|
+
{ pattern }
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function assertRelativePath(toolName, filePath) {
|
|
39
|
+
if (filePath.split(/[\\/]/).includes("..")) {
|
|
40
|
+
throw ToolError.validationFailed(
|
|
41
|
+
toolName,
|
|
42
|
+
`Path must stay inside the active workspace: ${filePath}`,
|
|
43
|
+
{ file_path: filePath }
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
assertWorkspaceRelativeGlob,
|
|
49
|
+
isWorkspaceFileNotFound,
|
|
50
|
+
toWorkspaceRelativePath
|
|
51
|
+
};
|
package/dist/write-file-tool.cjs
CHANGED
|
@@ -24,18 +24,21 @@ module.exports = __toCommonJS(write_file_tool_exports);
|
|
|
24
24
|
var import_node_crypto = require("node:crypto");
|
|
25
25
|
var import_zod = require("zod");
|
|
26
26
|
var import_diff = require("diff");
|
|
27
|
-
var
|
|
27
|
+
var import_tools = require("@dexto/core/tools");
|
|
28
|
+
var import_errors = require("@dexto/core/errors");
|
|
28
29
|
var import_error_codes = require("./error-codes.js");
|
|
29
30
|
var import_directory_approval = require("./directory-approval.js");
|
|
31
|
+
var import_workspace_paths = require("./workspace-paths.js");
|
|
30
32
|
const previewContentHashCache = /* @__PURE__ */ new Map();
|
|
31
33
|
const FILE_NOT_EXISTS_MARKER = null;
|
|
32
34
|
function computeContentHash(content) {
|
|
33
35
|
return (0, import_node_crypto.createHash)("sha256").update(content, "utf8").digest("hex");
|
|
34
36
|
}
|
|
35
37
|
const WriteFileInputSchema = import_zod.z.object({
|
|
36
|
-
file_path: import_zod.z.string().min(1).describe(
|
|
38
|
+
file_path: import_zod.z.string().min(1).describe(
|
|
39
|
+
"Path where the file should be written. Relative paths resolve inside the active workspace; absolute paths must stay inside the active workspace."
|
|
40
|
+
),
|
|
37
41
|
content: import_zod.z.string().describe("Content to write to the file"),
|
|
38
|
-
create_dirs: import_zod.z.boolean().optional().default(false).describe("Create parent directories if they don't exist (default: false)"),
|
|
39
42
|
encoding: import_zod.z.enum(["utf-8", "ascii", "latin1", "utf16le"]).optional().default("utf-8").describe("File encoding (default: utf-8)")
|
|
40
43
|
}).strict();
|
|
41
44
|
function generateDiffPreview(filePath, originalContent, newContent) {
|
|
@@ -54,10 +57,10 @@ function generateDiffPreview(filePath, originalContent, newContent) {
|
|
|
54
57
|
};
|
|
55
58
|
}
|
|
56
59
|
function createWriteFileTool(getFileSystemService) {
|
|
57
|
-
return (0,
|
|
60
|
+
return (0, import_tools.defineTool)({
|
|
58
61
|
id: "write_file",
|
|
59
62
|
aliases: ["write"],
|
|
60
|
-
description: "Write content to a file. Creates
|
|
63
|
+
description: "Write content to a file inside the active workspace. Creates missing parent directories, creates new files, and overwrites existing files. Relative paths resolve inside the workspace; absolute paths must stay inside it. Requires approval for all write operations. Returns success status, path, bytes written, and display data.",
|
|
61
64
|
inputSchema: WriteFileInputSchema,
|
|
62
65
|
...(0, import_directory_approval.createDirectoryAccessApprovalHandlers)({
|
|
63
66
|
toolName: "write_file",
|
|
@@ -67,9 +70,9 @@ function createWriteFileTool(getFileSystemService) {
|
|
|
67
70
|
resolvePaths: (input, fileSystemService) => (0, import_directory_approval.resolveFilePath)(fileSystemService.getWorkingDirectory(), input.file_path)
|
|
68
71
|
}),
|
|
69
72
|
presentation: {
|
|
70
|
-
describeHeader: (input) => (0,
|
|
73
|
+
describeHeader: (input) => (0, import_tools.createLocalToolCallHeader)({
|
|
71
74
|
title: "Write",
|
|
72
|
-
argsText: (0,
|
|
75
|
+
argsText: (0, import_tools.truncateForHeader)(input.file_path, 140)
|
|
73
76
|
}),
|
|
74
77
|
/**
|
|
75
78
|
* Generate preview for approval UI - shows diff or file creation info
|
|
@@ -77,6 +80,41 @@ function createWriteFileTool(getFileSystemService) {
|
|
|
77
80
|
*/
|
|
78
81
|
preview: async (input, context) => {
|
|
79
82
|
const { file_path, content } = input;
|
|
83
|
+
const workspaceManager = context.services?.workspaceManager;
|
|
84
|
+
if (workspaceManager !== void 0) {
|
|
85
|
+
const handle = await workspaceManager.open({ intent: "write" });
|
|
86
|
+
const workspacePath = (0, import_workspace_paths.toWorkspaceRelativePath)(
|
|
87
|
+
"write_file",
|
|
88
|
+
handle.context.path,
|
|
89
|
+
file_path
|
|
90
|
+
);
|
|
91
|
+
try {
|
|
92
|
+
const originalContent = await handle.files.readText(workspacePath);
|
|
93
|
+
if (context.toolCallId) {
|
|
94
|
+
previewContentHashCache.set(
|
|
95
|
+
context.toolCallId,
|
|
96
|
+
computeContentHash(originalContent)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return generateDiffPreview(file_path, originalContent, content);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (!(0, import_workspace_paths.isWorkspaceFileNotFound)(error)) {
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
if (context.toolCallId) {
|
|
105
|
+
previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
type: "file",
|
|
109
|
+
title: "Create file",
|
|
110
|
+
path: file_path,
|
|
111
|
+
operation: "create",
|
|
112
|
+
size: Buffer.byteLength(content, "utf8"),
|
|
113
|
+
lineCount: content.split("\n").length,
|
|
114
|
+
content
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
80
118
|
const resolvedFileSystemService = await getFileSystemService(context);
|
|
81
119
|
const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
|
|
82
120
|
resolvedFileSystemService.getWorkingDirectory(),
|
|
@@ -88,7 +126,7 @@ function createWriteFileTool(getFileSystemService) {
|
|
|
88
126
|
const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
|
|
89
127
|
originalContent = originalFile.content;
|
|
90
128
|
} catch (error) {
|
|
91
|
-
if (error instanceof
|
|
129
|
+
if (error instanceof import_errors.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.INVALID_PATH) {
|
|
92
130
|
const originalFile = await resolvedFileSystemService.readFileForToolPreview(
|
|
93
131
|
resolvedPath
|
|
94
132
|
);
|
|
@@ -105,7 +143,7 @@ function createWriteFileTool(getFileSystemService) {
|
|
|
105
143
|
}
|
|
106
144
|
return generateDiffPreview(resolvedPath, originalContent, content);
|
|
107
145
|
} catch (error) {
|
|
108
|
-
if (error instanceof
|
|
146
|
+
if (error instanceof import_errors.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
|
|
109
147
|
if (context.toolCallId) {
|
|
110
148
|
previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
|
|
111
149
|
}
|
|
@@ -127,78 +165,80 @@ function createWriteFileTool(getFileSystemService) {
|
|
|
127
165
|
}
|
|
128
166
|
},
|
|
129
167
|
async execute(input, context) {
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
168
|
+
const { file_path, content, encoding } = input;
|
|
169
|
+
const handle = await openWorkspace(context, "write_file");
|
|
170
|
+
const workspacePath = (0, import_workspace_paths.toWorkspaceRelativePath)(
|
|
171
|
+
"write_file",
|
|
172
|
+
handle.context.path,
|
|
134
173
|
file_path
|
|
135
174
|
);
|
|
136
175
|
let originalContent = null;
|
|
137
176
|
let fileExistsNow = false;
|
|
138
177
|
try {
|
|
139
|
-
|
|
140
|
-
originalContent = originalFile.content;
|
|
178
|
+
originalContent = await handle.files.readText(workspacePath);
|
|
141
179
|
fileExistsNow = true;
|
|
142
180
|
} catch (error) {
|
|
143
|
-
if (
|
|
144
|
-
originalContent = null;
|
|
145
|
-
fileExistsNow = false;
|
|
146
|
-
} else {
|
|
181
|
+
if (!(0, import_workspace_paths.isWorkspaceFileNotFound)(error)) {
|
|
147
182
|
throw error;
|
|
148
183
|
}
|
|
184
|
+
originalContent = null;
|
|
185
|
+
fileExistsNow = false;
|
|
149
186
|
}
|
|
150
187
|
if (context.toolCallId && previewContentHashCache.has(context.toolCallId)) {
|
|
151
188
|
const expectedHash = previewContentHashCache.get(context.toolCallId);
|
|
152
189
|
previewContentHashCache.delete(context.toolCallId);
|
|
153
190
|
if (expectedHash === FILE_NOT_EXISTS_MARKER) {
|
|
154
191
|
if (fileExistsNow) {
|
|
155
|
-
throw
|
|
192
|
+
throw import_tools.ToolError.fileModifiedSincePreview("write_file", file_path);
|
|
156
193
|
}
|
|
157
194
|
} else if (expectedHash !== null) {
|
|
158
195
|
if (!fileExistsNow) {
|
|
159
|
-
throw
|
|
196
|
+
throw import_tools.ToolError.fileModifiedSincePreview("write_file", file_path);
|
|
160
197
|
}
|
|
161
198
|
if (originalContent === null) {
|
|
162
|
-
throw
|
|
199
|
+
throw import_tools.ToolError.executionFailed(
|
|
163
200
|
"write_file",
|
|
164
201
|
"Expected original file content when fileExistsNow is true"
|
|
165
202
|
);
|
|
166
203
|
}
|
|
167
204
|
const currentHash = computeContentHash(originalContent);
|
|
168
205
|
if (expectedHash !== currentHash) {
|
|
169
|
-
throw
|
|
206
|
+
throw import_tools.ToolError.fileModifiedSincePreview("write_file", file_path);
|
|
170
207
|
}
|
|
171
208
|
}
|
|
172
209
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
encoding
|
|
176
|
-
});
|
|
210
|
+
await handle.files.writeFile(workspacePath, content);
|
|
211
|
+
const bytesWritten = Buffer.byteLength(content, encoding);
|
|
177
212
|
let _display;
|
|
178
213
|
if (originalContent === null) {
|
|
179
214
|
const lineCount = content.split("\n").length;
|
|
180
215
|
_display = {
|
|
181
216
|
type: "file",
|
|
182
217
|
title: "Create file",
|
|
183
|
-
path:
|
|
218
|
+
path: file_path,
|
|
184
219
|
operation: "create",
|
|
185
|
-
size:
|
|
220
|
+
size: bytesWritten,
|
|
186
221
|
lineCount,
|
|
187
222
|
content
|
|
188
223
|
};
|
|
189
224
|
} else {
|
|
190
|
-
_display = generateDiffPreview(
|
|
225
|
+
_display = generateDiffPreview(file_path, originalContent, content);
|
|
191
226
|
}
|
|
192
227
|
return {
|
|
193
|
-
success:
|
|
194
|
-
path:
|
|
195
|
-
bytes_written:
|
|
196
|
-
...result.backupPath && { backup_path: result.backupPath },
|
|
228
|
+
success: true,
|
|
229
|
+
path: file_path,
|
|
230
|
+
bytes_written: bytesWritten,
|
|
197
231
|
_display
|
|
198
232
|
};
|
|
199
233
|
}
|
|
200
234
|
});
|
|
201
235
|
}
|
|
236
|
+
async function openWorkspace(context, toolName) {
|
|
237
|
+
if (!context.services) {
|
|
238
|
+
throw new Error(`${toolName} requires ToolExecutionContext.services`);
|
|
239
|
+
}
|
|
240
|
+
return context.services.workspaceManager.open({ intent: "write" });
|
|
241
|
+
}
|
|
202
242
|
// Annotate the CommonJS export names for ESM import in node:
|
|
203
243
|
0 && (module.exports = {
|
|
204
244
|
createWriteFileTool
|
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
* Internal tool for writing content to files (requires approval)
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import type { Tool } from '@dexto/core';
|
|
7
|
+
import type { Tool } from '@dexto/core/tools';
|
|
8
8
|
import type { FileSystemServiceGetter } from './file-tool-types.js';
|
|
9
9
|
declare const WriteFileInputSchema: z.ZodObject<{
|
|
10
10
|
file_path: z.ZodString;
|
|
11
11
|
content: z.ZodString;
|
|
12
|
-
create_dirs: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
13
12
|
encoding: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
14
13
|
ascii: "ascii";
|
|
15
14
|
"utf-8": "utf-8";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write-file-tool.d.ts","sourceRoot":"","sources":["../src/write-file-tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"write-file-tool.d.ts","sourceRoot":"","sources":["../src/write-file-tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,OAAO,KAAK,EAGR,IAAI,EAEP,MAAM,mBAAmB,CAAC;AAG3B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAwBpE,QAAA,MAAM,oBAAoB;;;;;;;;;kBAeb,CAAC;AA0Bd;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,oBAAoB,EAAE,uBAAuB,GAC9C,IAAI,CAAC,OAAO,oBAAoB,CAAC,CA0NnC"}
|
package/dist/write-file-tool.js
CHANGED
|
@@ -3,22 +3,24 @@ import { z } from "zod";
|
|
|
3
3
|
import { createPatch } from "diff";
|
|
4
4
|
import {
|
|
5
5
|
createLocalToolCallHeader,
|
|
6
|
-
DextoRuntimeError,
|
|
7
6
|
ToolError,
|
|
8
7
|
defineTool,
|
|
9
8
|
truncateForHeader
|
|
10
|
-
} from "@dexto/core";
|
|
9
|
+
} from "@dexto/core/tools";
|
|
10
|
+
import { DextoRuntimeError } from "@dexto/core/errors";
|
|
11
11
|
import { FileSystemErrorCode } from "./error-codes.js";
|
|
12
12
|
import { createDirectoryAccessApprovalHandlers, resolveFilePath } from "./directory-approval.js";
|
|
13
|
+
import { isWorkspaceFileNotFound, toWorkspaceRelativePath } from "./workspace-paths.js";
|
|
13
14
|
const previewContentHashCache = /* @__PURE__ */ new Map();
|
|
14
15
|
const FILE_NOT_EXISTS_MARKER = null;
|
|
15
16
|
function computeContentHash(content) {
|
|
16
17
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
17
18
|
}
|
|
18
19
|
const WriteFileInputSchema = z.object({
|
|
19
|
-
file_path: z.string().min(1).describe(
|
|
20
|
+
file_path: z.string().min(1).describe(
|
|
21
|
+
"Path where the file should be written. Relative paths resolve inside the active workspace; absolute paths must stay inside the active workspace."
|
|
22
|
+
),
|
|
20
23
|
content: z.string().describe("Content to write to the file"),
|
|
21
|
-
create_dirs: z.boolean().optional().default(false).describe("Create parent directories if they don't exist (default: false)"),
|
|
22
24
|
encoding: z.enum(["utf-8", "ascii", "latin1", "utf16le"]).optional().default("utf-8").describe("File encoding (default: utf-8)")
|
|
23
25
|
}).strict();
|
|
24
26
|
function generateDiffPreview(filePath, originalContent, newContent) {
|
|
@@ -40,7 +42,7 @@ function createWriteFileTool(getFileSystemService) {
|
|
|
40
42
|
return defineTool({
|
|
41
43
|
id: "write_file",
|
|
42
44
|
aliases: ["write"],
|
|
43
|
-
description: "Write content to a file. Creates
|
|
45
|
+
description: "Write content to a file inside the active workspace. Creates missing parent directories, creates new files, and overwrites existing files. Relative paths resolve inside the workspace; absolute paths must stay inside it. Requires approval for all write operations. Returns success status, path, bytes written, and display data.",
|
|
44
46
|
inputSchema: WriteFileInputSchema,
|
|
45
47
|
...createDirectoryAccessApprovalHandlers({
|
|
46
48
|
toolName: "write_file",
|
|
@@ -60,6 +62,41 @@ function createWriteFileTool(getFileSystemService) {
|
|
|
60
62
|
*/
|
|
61
63
|
preview: async (input, context) => {
|
|
62
64
|
const { file_path, content } = input;
|
|
65
|
+
const workspaceManager = context.services?.workspaceManager;
|
|
66
|
+
if (workspaceManager !== void 0) {
|
|
67
|
+
const handle = await workspaceManager.open({ intent: "write" });
|
|
68
|
+
const workspacePath = toWorkspaceRelativePath(
|
|
69
|
+
"write_file",
|
|
70
|
+
handle.context.path,
|
|
71
|
+
file_path
|
|
72
|
+
);
|
|
73
|
+
try {
|
|
74
|
+
const originalContent = await handle.files.readText(workspacePath);
|
|
75
|
+
if (context.toolCallId) {
|
|
76
|
+
previewContentHashCache.set(
|
|
77
|
+
context.toolCallId,
|
|
78
|
+
computeContentHash(originalContent)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return generateDiffPreview(file_path, originalContent, content);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (!isWorkspaceFileNotFound(error)) {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
if (context.toolCallId) {
|
|
87
|
+
previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
type: "file",
|
|
91
|
+
title: "Create file",
|
|
92
|
+
path: file_path,
|
|
93
|
+
operation: "create",
|
|
94
|
+
size: Buffer.byteLength(content, "utf8"),
|
|
95
|
+
lineCount: content.split("\n").length,
|
|
96
|
+
content
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
63
100
|
const resolvedFileSystemService = await getFileSystemService(context);
|
|
64
101
|
const { path: resolvedPath } = resolveFilePath(
|
|
65
102
|
resolvedFileSystemService.getWorkingDirectory(),
|
|
@@ -110,36 +147,35 @@ function createWriteFileTool(getFileSystemService) {
|
|
|
110
147
|
}
|
|
111
148
|
},
|
|
112
149
|
async execute(input, context) {
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
|
|
150
|
+
const { file_path, content, encoding } = input;
|
|
151
|
+
const handle = await openWorkspace(context, "write_file");
|
|
152
|
+
const workspacePath = toWorkspaceRelativePath(
|
|
153
|
+
"write_file",
|
|
154
|
+
handle.context.path,
|
|
117
155
|
file_path
|
|
118
156
|
);
|
|
119
157
|
let originalContent = null;
|
|
120
158
|
let fileExistsNow = false;
|
|
121
159
|
try {
|
|
122
|
-
|
|
123
|
-
originalContent = originalFile.content;
|
|
160
|
+
originalContent = await handle.files.readText(workspacePath);
|
|
124
161
|
fileExistsNow = true;
|
|
125
162
|
} catch (error) {
|
|
126
|
-
if (error
|
|
127
|
-
originalContent = null;
|
|
128
|
-
fileExistsNow = false;
|
|
129
|
-
} else {
|
|
163
|
+
if (!isWorkspaceFileNotFound(error)) {
|
|
130
164
|
throw error;
|
|
131
165
|
}
|
|
166
|
+
originalContent = null;
|
|
167
|
+
fileExistsNow = false;
|
|
132
168
|
}
|
|
133
169
|
if (context.toolCallId && previewContentHashCache.has(context.toolCallId)) {
|
|
134
170
|
const expectedHash = previewContentHashCache.get(context.toolCallId);
|
|
135
171
|
previewContentHashCache.delete(context.toolCallId);
|
|
136
172
|
if (expectedHash === FILE_NOT_EXISTS_MARKER) {
|
|
137
173
|
if (fileExistsNow) {
|
|
138
|
-
throw ToolError.fileModifiedSincePreview("write_file",
|
|
174
|
+
throw ToolError.fileModifiedSincePreview("write_file", file_path);
|
|
139
175
|
}
|
|
140
176
|
} else if (expectedHash !== null) {
|
|
141
177
|
if (!fileExistsNow) {
|
|
142
|
-
throw ToolError.fileModifiedSincePreview("write_file",
|
|
178
|
+
throw ToolError.fileModifiedSincePreview("write_file", file_path);
|
|
143
179
|
}
|
|
144
180
|
if (originalContent === null) {
|
|
145
181
|
throw ToolError.executionFailed(
|
|
@@ -149,39 +185,42 @@ function createWriteFileTool(getFileSystemService) {
|
|
|
149
185
|
}
|
|
150
186
|
const currentHash = computeContentHash(originalContent);
|
|
151
187
|
if (expectedHash !== currentHash) {
|
|
152
|
-
throw ToolError.fileModifiedSincePreview("write_file",
|
|
188
|
+
throw ToolError.fileModifiedSincePreview("write_file", file_path);
|
|
153
189
|
}
|
|
154
190
|
}
|
|
155
191
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
encoding
|
|
159
|
-
});
|
|
192
|
+
await handle.files.writeFile(workspacePath, content);
|
|
193
|
+
const bytesWritten = Buffer.byteLength(content, encoding);
|
|
160
194
|
let _display;
|
|
161
195
|
if (originalContent === null) {
|
|
162
196
|
const lineCount = content.split("\n").length;
|
|
163
197
|
_display = {
|
|
164
198
|
type: "file",
|
|
165
199
|
title: "Create file",
|
|
166
|
-
path:
|
|
200
|
+
path: file_path,
|
|
167
201
|
operation: "create",
|
|
168
|
-
size:
|
|
202
|
+
size: bytesWritten,
|
|
169
203
|
lineCount,
|
|
170
204
|
content
|
|
171
205
|
};
|
|
172
206
|
} else {
|
|
173
|
-
_display = generateDiffPreview(
|
|
207
|
+
_display = generateDiffPreview(file_path, originalContent, content);
|
|
174
208
|
}
|
|
175
209
|
return {
|
|
176
|
-
success:
|
|
177
|
-
path:
|
|
178
|
-
bytes_written:
|
|
179
|
-
...result.backupPath && { backup_path: result.backupPath },
|
|
210
|
+
success: true,
|
|
211
|
+
path: file_path,
|
|
212
|
+
bytes_written: bytesWritten,
|
|
180
213
|
_display
|
|
181
214
|
};
|
|
182
215
|
}
|
|
183
216
|
});
|
|
184
217
|
}
|
|
218
|
+
async function openWorkspace(context, toolName) {
|
|
219
|
+
if (!context.services) {
|
|
220
|
+
throw new Error(`${toolName} requires ToolExecutionContext.services`);
|
|
221
|
+
}
|
|
222
|
+
return context.services.workspaceManager.open({ intent: "write" });
|
|
223
|
+
}
|
|
185
224
|
export {
|
|
186
225
|
createWriteFileTool
|
|
187
226
|
};
|