@probelabs/probe 0.6.0-rc154 → 0.6.0-rc161

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 (49) hide show
  1. package/README.md +80 -1
  2. package/build/agent/FallbackManager.d.ts +176 -0
  3. package/build/agent/FallbackManager.js +545 -0
  4. package/build/agent/ProbeAgent.d.ts +9 -1
  5. package/build/agent/ProbeAgent.js +218 -10
  6. package/build/agent/RetryManager.d.ts +157 -0
  7. package/build/agent/RetryManager.js +334 -0
  8. package/build/agent/acp/server.js +1 -0
  9. package/build/agent/acp/tools.js +6 -2
  10. package/build/agent/index.js +1814 -355
  11. package/build/agent/probeTool.js +20 -2
  12. package/build/agent/tools.js +16 -0
  13. package/build/delegate.js +326 -201
  14. package/build/downloader.js +46 -17
  15. package/build/extractor.js +12 -12
  16. package/build/index.js +13 -0
  17. package/build/tools/common.js +5 -3
  18. package/build/tools/edit.js +409 -0
  19. package/build/tools/index.js +11 -0
  20. package/build/tools/vercel.js +55 -14
  21. package/build/utils.js +18 -9
  22. package/cjs/agent/ProbeAgent.cjs +2268 -699
  23. package/cjs/index.cjs +75902 -74348
  24. package/package.json +2 -2
  25. package/src/agent/FallbackManager.d.ts +176 -0
  26. package/src/agent/FallbackManager.js +545 -0
  27. package/src/agent/ProbeAgent.d.ts +9 -1
  28. package/src/agent/ProbeAgent.js +218 -10
  29. package/src/agent/RetryManager.d.ts +157 -0
  30. package/src/agent/RetryManager.js +334 -0
  31. package/src/agent/acp/server.js +1 -0
  32. package/src/agent/acp/tools.js +6 -2
  33. package/src/agent/index.js +8 -0
  34. package/src/agent/probeTool.js +20 -2
  35. package/src/agent/tools.js +16 -0
  36. package/src/delegate.js +326 -201
  37. package/src/downloader.js +46 -17
  38. package/src/extractor.js +12 -12
  39. package/src/index.js +13 -0
  40. package/src/tools/common.js +5 -3
  41. package/src/tools/edit.js +409 -0
  42. package/src/tools/index.js +11 -0
  43. package/src/tools/vercel.js +55 -14
  44. package/src/utils.js +18 -9
  45. package/bin/binaries/probe-v0.6.0-rc154-aarch64-apple-darwin.tar.gz +0 -0
  46. package/bin/binaries/probe-v0.6.0-rc154-aarch64-unknown-linux-gnu.tar.gz +0 -0
  47. package/bin/binaries/probe-v0.6.0-rc154-x86_64-apple-darwin.tar.gz +0 -0
  48. package/bin/binaries/probe-v0.6.0-rc154-x86_64-pc-windows-msvc.zip +0 -0
  49. package/bin/binaries/probe-v0.6.0-rc154-x86_64-unknown-linux-gnu.tar.gz +0 -0
@@ -17,6 +17,35 @@ import { getPackageBinDir } from './directory-resolver.js';
17
17
 
18
18
  const exec = promisify(execCallback);
19
19
 
20
+ /**
21
+ * Create a plain, serializable Error from possibly complex/circular errors (e.g., axios)
22
+ * Avoids passing circular req/res objects to IPC serializers and test runners.
23
+ */
24
+ function sanitizeError(err) {
25
+ try {
26
+ const status = err?.response?.status;
27
+ const statusText = err?.response?.statusText;
28
+ const url = err?.config?.url || err?.response?.config?.url;
29
+ const code = err?.code;
30
+ const serverMsg = typeof err?.response?.data === 'string'
31
+ ? err.response.data.slice(0, 500)
32
+ : (typeof err?.response?.data?.message === 'string' ? err.response.data.message : undefined);
33
+ const base = err?.message || String(err);
34
+ const parts = [base];
35
+ if (status || statusText) parts.push(`[${(status ?? '')} ${(statusText ?? '')}]`.trim());
36
+ if (code) parts.push(`code=${code}`);
37
+ if (url) parts.push(`url=${url}`);
38
+ if (serverMsg) parts.push(`server="${String(serverMsg).replace(/\s+/g, ' ').trim()}"`);
39
+ const e = new Error(parts.filter(Boolean).join(' '));
40
+ if (status) e.status = status;
41
+ if (code) e.code = code;
42
+ if (url) e.url = url;
43
+ return e;
44
+ } catch (_) {
45
+ return new Error(err?.message || String(err));
46
+ }
47
+ }
48
+
20
49
  // GitHub repository information
21
50
  const REPO_OWNER = "probelabs";
22
51
  const REPO_NAME = "probe";
@@ -69,7 +98,7 @@ async function acquireFileLock(lockPath, version) {
69
98
  console.log(`Acquired file lock: ${lockPath}`);
70
99
  }
71
100
  return true;
72
- } catch (error) {
101
+ } catch (error) {
73
102
  if (error.code === 'EEXIST') {
74
103
  // Lock file exists - check if it's stale
75
104
  try {
@@ -265,13 +294,13 @@ function detectOsArch() {
265
294
  let archInfo;
266
295
 
267
296
  // Detect OS
268
- switch (osType) {
269
- case 'linux':
270
- osInfo = {
271
- type: 'linux',
272
- keywords: ['linux', 'Linux', 'gnu']
273
- };
274
- break;
297
+ switch (osType) {
298
+ case 'linux':
299
+ osInfo = {
300
+ type: 'linux',
301
+ keywords: ['linux', 'Linux', 'musl', 'gnu']
302
+ };
303
+ break;
275
304
  case 'darwin':
276
305
  osInfo = {
277
306
  type: 'darwin',
@@ -324,11 +353,11 @@ function constructAssetInfo(version, osInfo, archInfo) {
324
353
  let extension;
325
354
 
326
355
  // Map OS and arch to the expected format in release names
327
- switch (osInfo.type) {
328
- case 'linux':
329
- platform = `${archInfo.type}-unknown-linux-gnu`;
330
- extension = 'tar.gz';
331
- break;
356
+ switch (osInfo.type) {
357
+ case 'linux':
358
+ platform = `${archInfo.type}-unknown-linux-musl`;
359
+ extension = 'tar.gz';
360
+ break;
332
361
  case 'darwin':
333
362
  platform = `${archInfo.type}-apple-darwin`;
334
363
  extension = 'tar.gz';
@@ -482,7 +511,7 @@ async function getLatestRelease(version) {
482
511
  return { tag, assets };
483
512
  }
484
513
 
485
- throw error;
514
+ throw sanitizeError(error);
486
515
  }
487
516
  }
488
517
 
@@ -787,7 +816,7 @@ async function extractBinary(assetPath, outputDir) {
787
816
  return binaryPath;
788
817
  } catch (error) {
789
818
  console.error(`Error extracting binary: ${error instanceof Error ? error.message : String(error)}`);
790
- throw error;
819
+ throw sanitizeError(error);
791
820
  }
792
821
  }
793
822
 
@@ -1032,7 +1061,7 @@ export async function downloadProbeBinary(version) {
1032
1061
  }
1033
1062
  return await withDownloadLock(version, () => doDownload(version));
1034
1063
  } catch (error) {
1035
- console.error('Error downloading probe binary:', error);
1036
- throw error;
1064
+ console.error('Error downloading probe binary:', error?.message || String(error));
1065
+ throw sanitizeError(error);
1037
1066
  }
1038
1067
  }
@@ -27,16 +27,16 @@ function detectPlatform() {
27
27
  let extension;
28
28
 
29
29
  // Map to the same format used in release artifacts
30
- if (osType === 'linux') {
31
- if (archType === 'x64') {
32
- platform = 'x86_64-unknown-linux-gnu';
33
- extension = 'tar.gz';
34
- } else if (archType === 'arm64') {
35
- platform = 'aarch64-unknown-linux-gnu';
36
- extension = 'tar.gz';
37
- } else {
38
- throw new Error(`Unsupported Linux architecture: ${archType}`);
39
- }
30
+ if (osType === 'linux') {
31
+ if (archType === 'x64') {
32
+ platform = 'x86_64-unknown-linux-musl';
33
+ extension = 'tar.gz';
34
+ } else if (archType === 'arm64') {
35
+ platform = 'aarch64-unknown-linux-musl';
36
+ extension = 'tar.gz';
37
+ } else {
38
+ throw new Error(`Unsupported Linux architecture: ${archType}`);
39
+ }
40
40
  } else if (osType === 'darwin') {
41
41
  if (archType === 'x64') {
42
42
  platform = 'x86_64-apple-darwin';
@@ -191,8 +191,8 @@ export async function extractBundledBinary(version) {
191
191
  `Searched in: ${binariesDir}\n` +
192
192
  `\n` +
193
193
  `Supported platforms:\n` +
194
- ` - x86_64-unknown-linux-gnu (Linux x64)\n` +
195
- ` - aarch64-unknown-linux-gnu (Linux ARM64)\n` +
194
+ ` - x86_64-unknown-linux-musl (Linux x64, static)\n` +
195
+ ` - aarch64-unknown-linux-musl (Linux ARM64, static)\n` +
196
196
  ` - x86_64-apple-darwin (macOS Intel)\n` +
197
197
  ` - aarch64-apple-darwin (macOS Apple Silicon)\n` +
198
198
  ` - x86_64-pc-windows-msvc (Windows x64)\n` +
package/build/index.js CHANGED
@@ -35,8 +35,15 @@ import {
35
35
  bashToolDefinition,
36
36
  parseXmlToolCall
37
37
  } from './tools/common.js';
38
+ import {
39
+ editSchema,
40
+ createSchema,
41
+ editToolDefinition,
42
+ createToolDefinition
43
+ } from './tools/edit.js';
38
44
  import { searchTool, queryTool, extractTool, delegateTool } from './tools/vercel.js';
39
45
  import { bashTool } from './tools/bash.js';
46
+ import { editTool, createTool } from './tools/edit.js';
40
47
  import { ProbeAgent } from './agent/ProbeAgent.js';
41
48
  import { SimpleTelemetry, SimpleAppTracer, initializeSimpleTelemetryFromOptions } from './agent/simpleTelemetry.js';
42
49
  import { listFilesToolInstance, searchFilesToolInstance } from './agent/probeTool.js';
@@ -72,6 +79,8 @@ export {
72
79
  extractTool,
73
80
  delegateTool,
74
81
  bashTool,
82
+ editTool,
83
+ createTool,
75
84
  // Export tool instances
76
85
  listFilesToolInstance,
77
86
  searchFilesToolInstance,
@@ -82,6 +91,8 @@ export {
82
91
  delegateSchema,
83
92
  attemptCompletionSchema,
84
93
  bashSchema,
94
+ editSchema,
95
+ createSchema,
85
96
  // Export tool definitions
86
97
  searchToolDefinition,
87
98
  queryToolDefinition,
@@ -89,6 +100,8 @@ export {
89
100
  delegateToolDefinition,
90
101
  attemptCompletionToolDefinition,
91
102
  bashToolDefinition,
103
+ editToolDefinition,
104
+ createToolDefinition,
92
105
  // Export parser function
93
106
  parseXmlToolCall
94
107
  };
@@ -15,12 +15,13 @@ export const querySchema = z.object({
15
15
  pattern: z.string().describe('AST pattern to search for. Use $NAME for variable names, $$$PARAMS for parameter lists, etc.'),
16
16
  path: z.string().optional().default('.').describe('Path to search in'),
17
17
  language: z.string().optional().default('rust').describe('Programming language to use for parsing'),
18
- allow_tests: z.boolean().optional().default(false).describe('Allow test files in search results')
18
+ allow_tests: z.boolean().optional().default(true).describe('Allow test files in search results')
19
19
  });
20
20
 
21
21
  export const extractSchema = z.object({
22
22
  targets: z.string().optional().describe('File paths or symbols to extract from. Formats: "file.js" (whole file), "file.js:42" (line 42), "file.js:10-20" (lines 10-20), "file.js#funcName" (symbol). Multiple targets separated by spaces.'),
23
- input_content: z.string().optional().describe('Text content to extract file paths from (alternative to targets)')
23
+ input_content: z.string().optional().describe('Text content to extract file paths from (alternative to targets)'),
24
+ allow_tests: z.boolean().optional().default(true).describe('Include test files in extraction results')
24
25
  });
25
26
 
26
27
  export const delegateSchema = z.object({
@@ -163,7 +164,7 @@ Parameters:
163
164
  - pattern: (required) AST pattern to search for. Use $NAME for variable names, $$$PARAMS for parameter lists, etc.
164
165
  - path: (optional, default: '.') Path to search in.
165
166
  - language: (optional, default: 'rust') Programming language to use for parsing.
166
- - allow_tests: (optional, default: false) Allow test files in search results (true/false).
167
+ - allow_tests: (optional, default: true) Allow test files in search results (true/false).
167
168
  Usage Example:
168
169
 
169
170
  <examples>
@@ -189,6 +190,7 @@ Full file extraction should be the LAST RESORT! Always prefer search.
189
190
  Parameters:
190
191
  - targets: (required) File paths or symbols to extract from. Formats: "file.js" (whole file), "file.js:42" (code block at line 42), "file.js:10-20" (lines 10-20), "file.js#funcName" (specific symbol). Multiple targets separated by spaces.
191
192
  - input_content: (optional) Text content to extract file paths from (alternative to targets for processing diffs/logs).
193
+ - allow_tests: (optional, default: true) Include test files in extraction results.
192
194
 
193
195
  Usage Example:
194
196
 
@@ -0,0 +1,409 @@
1
+ /**
2
+ * Edit and Create tools for file modification
3
+ * @module tools/edit
4
+ */
5
+
6
+ import { tool } from 'ai';
7
+ import { promises as fs } from 'fs';
8
+ import { dirname, resolve, isAbsolute, sep } from 'path';
9
+ import { existsSync } from 'fs';
10
+
11
+ /**
12
+ * Validates that a path is within allowed directories
13
+ * @param {string} filePath - Path to validate
14
+ * @param {string[]} allowedFolders - List of allowed folders
15
+ * @returns {boolean} True if path is allowed
16
+ */
17
+ function isPathAllowed(filePath, allowedFolders) {
18
+ if (!allowedFolders || allowedFolders.length === 0) {
19
+ // If no restrictions, allow current directory and below
20
+ const resolvedPath = resolve(filePath);
21
+ const cwd = resolve(process.cwd());
22
+ // Ensure proper path separator to prevent path traversal
23
+ return resolvedPath === cwd || resolvedPath.startsWith(cwd + sep);
24
+ }
25
+
26
+ const resolvedPath = resolve(filePath);
27
+ return allowedFolders.some(folder => {
28
+ const allowedPath = resolve(folder);
29
+ // Ensure proper path separator to prevent path traversal
30
+ return resolvedPath === allowedPath || resolvedPath.startsWith(allowedPath + sep);
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Common configuration for file tools
36
+ * @param {Object} options - Configuration options
37
+ * @returns {Object} Parsed configuration
38
+ */
39
+ function parseFileToolOptions(options = {}) {
40
+ return {
41
+ debug: options.debug || false,
42
+ allowedFolders: options.allowedFolders || [],
43
+ defaultPath: options.defaultPath
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Edit tool generator - Claude Code style string replacement
49
+ *
50
+ * @param {Object} [options] - Configuration options
51
+ * @param {boolean} [options.debug=false] - Enable debug logging
52
+ * @param {string[]} [options.allowedFolders] - Allowed directories for file operations
53
+ * @param {string} [options.defaultPath] - Default working directory
54
+ * @returns {Object} Configured edit tool
55
+ */
56
+ export const editTool = (options = {}) => {
57
+ const { debug, allowedFolders, defaultPath } = parseFileToolOptions(options);
58
+
59
+ return tool({
60
+ name: 'edit',
61
+ description: `Edit files using exact string replacement (Claude Code style).
62
+
63
+ This tool performs exact string replacements in files. It requires the old_string to match exactly what's in the file, including all whitespace and indentation.
64
+
65
+ Parameters:
66
+ - file_path: Path to the file to edit (absolute or relative)
67
+ - old_string: Exact text to find and replace (must be unique in the file unless replace_all is true)
68
+ - new_string: Text to replace with
69
+ - replace_all: (optional) Replace all occurrences instead of requiring uniqueness
70
+
71
+ Important:
72
+ - The old_string must match EXACTLY including whitespace
73
+ - If old_string appears multiple times and replace_all is false, the edit will fail
74
+ - Use larger context around the string to ensure uniqueness when needed`,
75
+
76
+ inputSchema: {
77
+ type: 'object',
78
+ properties: {
79
+ file_path: {
80
+ type: 'string',
81
+ description: 'Path to the file to edit'
82
+ },
83
+ old_string: {
84
+ type: 'string',
85
+ description: 'Exact text to find and replace'
86
+ },
87
+ new_string: {
88
+ type: 'string',
89
+ description: 'Text to replace with'
90
+ },
91
+ replace_all: {
92
+ type: 'boolean',
93
+ description: 'Replace all occurrences (default: false)',
94
+ default: false
95
+ }
96
+ },
97
+ required: ['file_path', 'old_string', 'new_string']
98
+ },
99
+
100
+ execute: async ({ file_path, old_string, new_string, replace_all = false }) => {
101
+ try {
102
+ // Validate input parameters
103
+ if (!file_path || typeof file_path !== 'string' || file_path.trim() === '') {
104
+ return `Error editing file: Invalid file_path - must be a non-empty string`;
105
+ }
106
+ if (old_string === undefined || old_string === null || typeof old_string !== 'string') {
107
+ return `Error editing file: Invalid old_string - must be a string`;
108
+ }
109
+ if (new_string === undefined || new_string === null || typeof new_string !== 'string') {
110
+ return `Error editing file: Invalid new_string - must be a string`;
111
+ }
112
+
113
+ // Resolve the file path
114
+ const resolvedPath = isAbsolute(file_path) ? file_path : resolve(defaultPath || process.cwd(), file_path);
115
+
116
+ if (debug) {
117
+ console.error(`[Edit] Attempting to edit file: ${resolvedPath}`);
118
+ }
119
+
120
+ // Check if path is allowed
121
+ if (!isPathAllowed(resolvedPath, allowedFolders)) {
122
+ return `Error editing file: Permission denied - ${file_path} is outside allowed directories`;
123
+ }
124
+
125
+ // Check if file exists
126
+ if (!existsSync(resolvedPath)) {
127
+ return `Error editing file: File not found - ${file_path}`;
128
+ }
129
+
130
+ // Read the file
131
+ const content = await fs.readFile(resolvedPath, 'utf-8');
132
+
133
+ // Check if old_string exists in the file
134
+ if (!content.includes(old_string)) {
135
+ return `Error editing file: String not found - the specified old_string was not found in ${file_path}`;
136
+ }
137
+
138
+ // Count occurrences
139
+ const occurrences = content.split(old_string).length - 1;
140
+
141
+ // Check uniqueness if not replacing all
142
+ if (!replace_all && occurrences > 1) {
143
+ return `Error editing file: Multiple occurrences found - the old_string appears ${occurrences} times. Use replace_all: true to replace all occurrences, or provide more context to make the string unique.`;
144
+ }
145
+
146
+ // Perform the replacement
147
+ let newContent;
148
+ if (replace_all) {
149
+ newContent = content.replaceAll(old_string, new_string);
150
+ } else {
151
+ newContent = content.replace(old_string, new_string);
152
+ }
153
+
154
+ // Check if replacement was made
155
+ if (newContent === content) {
156
+ return `Error editing file: No changes made - old_string and new_string might be the same`;
157
+ }
158
+
159
+ // Write the file back
160
+ await fs.writeFile(resolvedPath, newContent, 'utf-8');
161
+
162
+ const replacedCount = replace_all ? occurrences : 1;
163
+
164
+ if (debug) {
165
+ console.error(`[Edit] Successfully edited ${resolvedPath}, replaced ${replacedCount} occurrence(s)`);
166
+ }
167
+
168
+ // Return success message as a string (matching other tools pattern)
169
+ return `Successfully edited ${file_path} (${replacedCount} replacement${replacedCount !== 1 ? 's' : ''})`;
170
+
171
+ } catch (error) {
172
+ console.error('[Edit] Error:', error);
173
+ return `Error editing file: ${error.message}`;
174
+ }
175
+ }
176
+ });
177
+ };
178
+
179
+ /**
180
+ * Create tool generator - Create new files
181
+ *
182
+ * @param {Object} [options] - Configuration options
183
+ * @param {boolean} [options.debug=false] - Enable debug logging
184
+ * @param {string[]} [options.allowedFolders] - Allowed directories for file operations
185
+ * @param {string} [options.defaultPath] - Default working directory
186
+ * @returns {Object} Configured create tool
187
+ */
188
+ export const createTool = (options = {}) => {
189
+ const { debug, allowedFolders, defaultPath } = parseFileToolOptions(options);
190
+
191
+ return tool({
192
+ name: 'create',
193
+ description: `Create new files with specified content.
194
+
195
+ This tool creates new files in the filesystem. It will create parent directories if they don't exist.
196
+
197
+ Parameters:
198
+ - file_path: Path where the file should be created (absolute or relative)
199
+ - content: Content to write to the file
200
+ - overwrite: (optional) Whether to overwrite if file exists (default: false)
201
+
202
+ Important:
203
+ - By default, will fail if the file already exists
204
+ - Set overwrite: true to replace existing files
205
+ - Parent directories will be created automatically if needed`,
206
+
207
+ inputSchema: {
208
+ type: 'object',
209
+ properties: {
210
+ file_path: {
211
+ type: 'string',
212
+ description: 'Path where the file should be created'
213
+ },
214
+ content: {
215
+ type: 'string',
216
+ description: 'Content to write to the file'
217
+ },
218
+ overwrite: {
219
+ type: 'boolean',
220
+ description: 'Overwrite if file exists (default: false)',
221
+ default: false
222
+ }
223
+ },
224
+ required: ['file_path', 'content']
225
+ },
226
+
227
+ execute: async ({ file_path, content, overwrite = false }) => {
228
+ try {
229
+ // Validate input parameters
230
+ if (!file_path || typeof file_path !== 'string' || file_path.trim() === '') {
231
+ return `Error creating file: Invalid file_path - must be a non-empty string`;
232
+ }
233
+ if (content === undefined || content === null || typeof content !== 'string') {
234
+ return `Error creating file: Invalid content - must be a string`;
235
+ }
236
+
237
+ // Resolve the file path
238
+ const resolvedPath = isAbsolute(file_path) ? file_path : resolve(defaultPath || process.cwd(), file_path);
239
+
240
+ if (debug) {
241
+ console.error(`[Create] Attempting to create file: ${resolvedPath}`);
242
+ }
243
+
244
+ // Check if path is allowed
245
+ if (!isPathAllowed(resolvedPath, allowedFolders)) {
246
+ return `Error creating file: Permission denied - ${file_path} is outside allowed directories`;
247
+ }
248
+
249
+ // Check if file exists
250
+ if (existsSync(resolvedPath) && !overwrite) {
251
+ return `Error creating file: File already exists - ${file_path}. Use overwrite: true to replace it.`;
252
+ }
253
+
254
+ // Ensure parent directory exists
255
+ const dir = dirname(resolvedPath);
256
+ await fs.mkdir(dir, { recursive: true });
257
+
258
+ // Write the file
259
+ await fs.writeFile(resolvedPath, content, 'utf-8');
260
+
261
+ const action = existsSync(resolvedPath) && overwrite ? 'overwrote' : 'created';
262
+ const bytes = Buffer.byteLength(content, 'utf-8');
263
+
264
+ if (debug) {
265
+ console.error(`[Create] Successfully ${action} ${resolvedPath}`);
266
+ }
267
+
268
+ // Return success message as a string (matching other tools pattern)
269
+ return `Successfully ${action} ${file_path} (${bytes} bytes)`;
270
+
271
+ } catch (error) {
272
+ console.error('[Create] Error:', error);
273
+ return `Error creating file: ${error.message}`;
274
+ }
275
+ }
276
+ });
277
+ };
278
+
279
+ // Export schemas for tool definitions
280
+ export const editSchema = {
281
+ type: 'object',
282
+ properties: {
283
+ file_path: {
284
+ type: 'string',
285
+ description: 'Path to the file to edit'
286
+ },
287
+ old_string: {
288
+ type: 'string',
289
+ description: 'Exact text to find and replace'
290
+ },
291
+ new_string: {
292
+ type: 'string',
293
+ description: 'Text to replace with'
294
+ },
295
+ replace_all: {
296
+ type: 'boolean',
297
+ description: 'Replace all occurrences (default: false)'
298
+ }
299
+ },
300
+ required: ['file_path', 'old_string', 'new_string']
301
+ };
302
+
303
+ export const createSchema = {
304
+ type: 'object',
305
+ properties: {
306
+ file_path: {
307
+ type: 'string',
308
+ description: 'Path where the file should be created'
309
+ },
310
+ content: {
311
+ type: 'string',
312
+ description: 'Content to write to the file'
313
+ },
314
+ overwrite: {
315
+ type: 'boolean',
316
+ description: 'Overwrite if file exists (default: false)'
317
+ }
318
+ },
319
+ required: ['file_path', 'content']
320
+ };
321
+
322
+ // Tool descriptions for XML definitions
323
+ export const editDescription = 'Edit files using exact string replacement. Requires exact match including whitespace.';
324
+ export const createDescription = 'Create new files with specified content. Will create parent directories if needed.';
325
+
326
+ // XML tool definitions
327
+ export const editToolDefinition = `
328
+ ## edit
329
+ Description: ${editDescription}
330
+
331
+ When to use:
332
+ - For precise, surgical edits to existing files
333
+ - When you need to change specific lines or blocks of code
334
+ - For renaming functions, variables, or updating configuration values
335
+ - When the exact text to replace is known and unique (or use replace_all for multiple occurrences)
336
+
337
+ When NOT to use:
338
+ - For creating new files (use 'create' tool instead)
339
+ - When you cannot determine the exact text to replace
340
+ - When changes span multiple locations that would be better handled together
341
+
342
+ Parameters:
343
+ - file_path: (required) Path to the file to edit
344
+ - old_string: (required) Exact text to find and replace (must match including whitespace, newlines, and indentation)
345
+ - new_string: (required) Text to replace with
346
+ - replace_all: (optional, default: false) Replace all occurrences if the string appears multiple times
347
+
348
+ Important notes:
349
+ - The old_string MUST match EXACTLY, including all whitespace, indentation, and line breaks
350
+ - If old_string appears multiple times and replace_all is false, the tool will fail
351
+ - Always verify the exact formatting of the text you want to replace
352
+
353
+ Examples:
354
+ <edit>
355
+ <file_path>src/main.js</file_path>
356
+ <old_string>function oldName() {
357
+ return 42;
358
+ }</old_string>
359
+ <new_string>function newName() {
360
+ return 42;
361
+ }</new_string>
362
+ </edit>
363
+
364
+ <edit>
365
+ <file_path>config.json</file_path>
366
+ <old_string>"debug": false</old_string>
367
+ <new_string>"debug": true</new_string>
368
+ <replace_all>true</replace_all>
369
+ </edit>`;
370
+
371
+ export const createToolDefinition = `
372
+ ## create
373
+ Description: ${createDescription}
374
+
375
+ When to use:
376
+ - For creating brand new files from scratch
377
+ - When you need to add configuration files, documentation, or new modules
378
+ - For generating boilerplate code or templates
379
+ - When you have the complete content ready to write
380
+
381
+ When NOT to use:
382
+ - For editing existing files (use 'edit' tool instead)
383
+ - When a file already exists unless you explicitly want to overwrite it
384
+
385
+ Parameters:
386
+ - file_path: (required) Path where the file should be created
387
+ - content: (required) Complete content to write to the file
388
+ - overwrite: (optional, default: false) Whether to overwrite if file already exists
389
+
390
+ Important notes:
391
+ - Parent directories will be created automatically if they don't exist
392
+ - The tool will fail if the file already exists and overwrite is false
393
+ - Be careful with the overwrite option as it completely replaces existing files
394
+
395
+ Examples:
396
+ <create>
397
+ <file_path>src/newFile.js</file_path>
398
+ <content>export function hello() {
399
+ return "Hello, world!";
400
+ }</content>
401
+ </create>
402
+
403
+ <create>
404
+ <file_path>README.md</file_path>
405
+ <content># My Project
406
+
407
+ This is a new project.</content>
408
+ <overwrite>true</overwrite>
409
+ </create>`;
@@ -6,6 +6,7 @@
6
6
  // Export Vercel AI SDK tool generators
7
7
  export { searchTool, queryTool, extractTool, delegateTool } from './vercel.js';
8
8
  export { bashTool } from './bash.js';
9
+ export { editTool, createTool } from './edit.js';
9
10
 
10
11
  // Export LangChain tools
11
12
  export { createSearchTool, createQueryTool, createExtractTool } from './langchain.js';
@@ -25,6 +26,16 @@ export {
25
26
  attemptCompletionToolDefinition
26
27
  } from './common.js';
27
28
 
29
+ // Export edit and create schemas
30
+ export {
31
+ editSchema,
32
+ createSchema,
33
+ editDescription,
34
+ createDescription,
35
+ editToolDefinition,
36
+ createToolDefinition
37
+ } from './edit.js';
38
+
28
39
  // Export system message
29
40
  export { DEFAULT_SYSTEM_MESSAGE } from './system-message.js';
30
41