@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.
Files changed (66) hide show
  1. package/dist/directory-approval.cjs +5 -4
  2. package/dist/directory-approval.d.ts +2 -1
  3. package/dist/directory-approval.d.ts.map +1 -1
  4. package/dist/directory-approval.integration.test.cjs +73 -33
  5. package/dist/directory-approval.integration.test.js +73 -33
  6. package/dist/directory-approval.js +2 -1
  7. package/dist/edit-file-tool.cjs +52 -37
  8. package/dist/edit-file-tool.d.ts +1 -1
  9. package/dist/edit-file-tool.d.ts.map +1 -1
  10. package/dist/edit-file-tool.js +43 -29
  11. package/dist/edit-file-tool.test.cjs +159 -2
  12. package/dist/edit-file-tool.test.js +159 -2
  13. package/dist/errors.cjs +53 -53
  14. package/dist/errors.d.ts +1 -1
  15. package/dist/errors.d.ts.map +1 -1
  16. package/dist/errors.js +1 -1
  17. package/dist/file-tool-types.d.ts +1 -1
  18. package/dist/file-tool-types.d.ts.map +1 -1
  19. package/dist/filesystem-service.cjs +60 -60
  20. package/dist/filesystem-service.d.ts +1 -1
  21. package/dist/filesystem-service.d.ts.map +1 -1
  22. package/dist/filesystem-service.js +5 -5
  23. package/dist/filesystem-service.test.cjs +1 -3
  24. package/dist/filesystem-service.test.js +1 -3
  25. package/dist/glob-files-tool.cjs +27 -24
  26. package/dist/glob-files-tool.d.ts +1 -1
  27. package/dist/glob-files-tool.d.ts.map +1 -1
  28. package/dist/glob-files-tool.js +24 -21
  29. package/dist/glob-files-tool.test.cjs +100 -88
  30. package/dist/glob-files-tool.test.js +101 -67
  31. package/dist/grep-content-tool.cjs +129 -44
  32. package/dist/grep-content-tool.d.ts +1 -1
  33. package/dist/grep-content-tool.d.ts.map +1 -1
  34. package/dist/grep-content-tool.js +120 -41
  35. package/dist/grep-content-tool.test.cjs +122 -87
  36. package/dist/grep-content-tool.test.js +123 -66
  37. package/dist/index.d.cts +3 -4
  38. package/dist/path-validator.d.ts +1 -1
  39. package/dist/path-validator.d.ts.map +1 -1
  40. package/dist/read-file-tool.cjs +43 -14
  41. package/dist/read-file-tool.d.ts +1 -1
  42. package/dist/read-file-tool.d.ts.map +1 -1
  43. package/dist/read-file-tool.js +40 -11
  44. package/dist/read-file-tool.test.cjs +119 -0
  45. package/dist/read-file-tool.test.d.ts +2 -0
  46. package/dist/read-file-tool.test.d.ts.map +1 -0
  47. package/dist/read-file-tool.test.js +96 -0
  48. package/dist/read-media-file-tool.cjs +4 -4
  49. package/dist/read-media-file-tool.d.ts +1 -1
  50. package/dist/read-media-file-tool.d.ts.map +1 -1
  51. package/dist/read-media-file-tool.js +1 -1
  52. package/dist/tool-factory.cjs +2 -2
  53. package/dist/tool-factory.js +1 -1
  54. package/dist/types.d.ts +0 -2
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/workspace-paths.cjs +87 -0
  57. package/dist/workspace-paths.d.ts +4 -0
  58. package/dist/workspace-paths.d.ts.map +1 -0
  59. package/dist/workspace-paths.js +51 -0
  60. package/dist/write-file-tool.cjs +74 -34
  61. package/dist/write-file-tool.d.ts +1 -2
  62. package/dist/write-file-tool.d.ts.map +1 -1
  63. package/dist/write-file-tool.js +68 -29
  64. package/dist/write-file-tool.test.cjs +262 -11
  65. package/dist/write-file-tool.test.js +262 -11
  66. 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
+ };
@@ -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 import_core = require("@dexto/core");
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("Absolute path where the file should be written"),
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, import_core.defineTool)({
60
+ return (0, import_tools.defineTool)({
58
61
  id: "write_file",
59
62
  aliases: ["write"],
60
- description: "Write content to a file. Creates a new file or overwrites existing file. Automatically creates backup of existing files before overwriting. Use create_dirs to create parent directories. Requires approval for all write operations. Returns success status, path, bytes written, and backup path if applicable.",
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, import_core.createLocalToolCallHeader)({
73
+ describeHeader: (input) => (0, import_tools.createLocalToolCallHeader)({
71
74
  title: "Write",
72
- argsText: (0, import_core.truncateForHeader)(input.file_path, 140)
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 import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.INVALID_PATH) {
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 import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
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 resolvedFileSystemService = await getFileSystemService(context);
131
- const { file_path, content, create_dirs, encoding } = input;
132
- const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
133
- resolvedFileSystemService.getWorkingDirectory(),
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
- const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
140
- originalContent = originalFile.content;
178
+ originalContent = await handle.files.readText(workspacePath);
141
179
  fileExistsNow = true;
142
180
  } catch (error) {
143
- if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
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 import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
192
+ throw import_tools.ToolError.fileModifiedSincePreview("write_file", file_path);
156
193
  }
157
194
  } else if (expectedHash !== null) {
158
195
  if (!fileExistsNow) {
159
- throw import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
196
+ throw import_tools.ToolError.fileModifiedSincePreview("write_file", file_path);
160
197
  }
161
198
  if (originalContent === null) {
162
- throw import_core.ToolError.executionFailed(
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 import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
206
+ throw import_tools.ToolError.fileModifiedSincePreview("write_file", file_path);
170
207
  }
171
208
  }
172
209
  }
173
- const result = await resolvedFileSystemService.writeFile(resolvedPath, content, {
174
- createDirs: create_dirs,
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: resolvedPath,
218
+ path: file_path,
184
219
  operation: "create",
185
- size: result.bytesWritten,
220
+ size: bytesWritten,
186
221
  lineCount,
187
222
  content
188
223
  };
189
224
  } else {
190
- _display = generateDiffPreview(resolvedPath, originalContent, content);
225
+ _display = generateDiffPreview(file_path, originalContent, content);
191
226
  }
192
227
  return {
193
- success: result.success,
194
- path: result.path,
195
- bytes_written: result.bytesWritten,
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;AAUxB,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,aAAa,CAAC;AAG9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAuBpE,QAAA,MAAM,oBAAoB;;;;;;;;;;kBAeb,CAAC;AA0Bd;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,oBAAoB,EAAE,uBAAuB,GAC9C,IAAI,CAAC,OAAO,oBAAoB,CAAC,CAqMnC"}
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"}
@@ -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("Absolute path where the file should be written"),
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 a new file or overwrites existing file. Automatically creates backup of existing files before overwriting. Use create_dirs to create parent directories. Requires approval for all write operations. Returns success status, path, bytes written, and backup path if applicable.",
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 resolvedFileSystemService = await getFileSystemService(context);
114
- const { file_path, content, create_dirs, encoding } = input;
115
- const { path: resolvedPath } = resolveFilePath(
116
- resolvedFileSystemService.getWorkingDirectory(),
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
- const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
123
- originalContent = originalFile.content;
160
+ originalContent = await handle.files.readText(workspacePath);
124
161
  fileExistsNow = true;
125
162
  } catch (error) {
126
- if (error instanceof DextoRuntimeError && error.code === FileSystemErrorCode.FILE_NOT_FOUND) {
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", resolvedPath);
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", resolvedPath);
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", resolvedPath);
188
+ throw ToolError.fileModifiedSincePreview("write_file", file_path);
153
189
  }
154
190
  }
155
191
  }
156
- const result = await resolvedFileSystemService.writeFile(resolvedPath, content, {
157
- createDirs: create_dirs,
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: resolvedPath,
200
+ path: file_path,
167
201
  operation: "create",
168
- size: result.bytesWritten,
202
+ size: bytesWritten,
169
203
  lineCount,
170
204
  content
171
205
  };
172
206
  } else {
173
- _display = generateDiffPreview(resolvedPath, originalContent, content);
207
+ _display = generateDiffPreview(file_path, originalContent, content);
174
208
  }
175
209
  return {
176
- success: result.success,
177
- path: result.path,
178
- bytes_written: result.bytesWritten,
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
  };