@probelabs/probe 0.6.0-rc257 → 0.6.0-rc259

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 (30) hide show
  1. package/bin/binaries/probe-v0.6.0-rc259-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc259-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc259-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc259-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc259-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.js +13 -0
  7. package/build/agent/index.js +518 -135
  8. package/build/agent/probeTool.js +9 -0
  9. package/build/agent/shared/prompts.js +40 -8
  10. package/build/agent/tools.js +8 -0
  11. package/build/index.js +7 -2
  12. package/build/tools/common.js +56 -23
  13. package/build/tools/edit.js +139 -6
  14. package/build/tools/index.js +5 -2
  15. package/cjs/agent/ProbeAgent.cjs +3811 -5637
  16. package/cjs/index.cjs +3821 -5637
  17. package/package.json +1 -1
  18. package/src/agent/ProbeAgent.js +13 -0
  19. package/src/agent/probeTool.js +9 -0
  20. package/src/agent/shared/prompts.js +40 -8
  21. package/src/agent/tools.js +8 -0
  22. package/src/index.js +7 -2
  23. package/src/tools/common.js +56 -23
  24. package/src/tools/edit.js +139 -6
  25. package/src/tools/index.js +5 -2
  26. package/bin/binaries/probe-v0.6.0-rc257-aarch64-apple-darwin.tar.gz +0 -0
  27. package/bin/binaries/probe-v0.6.0-rc257-aarch64-unknown-linux-musl.tar.gz +0 -0
  28. package/bin/binaries/probe-v0.6.0-rc257-x86_64-apple-darwin.tar.gz +0 -0
  29. package/bin/binaries/probe-v0.6.0-rc257-x86_64-pc-windows-msvc.zip +0 -0
  30. package/bin/binaries/probe-v0.6.0-rc257-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -256,6 +256,15 @@ export function createWrappedTools(baseTools) {
256
256
  );
257
257
  }
258
258
 
259
+ // Wrap multi_edit tool
260
+ if (baseTools.multiEditTool) {
261
+ wrappedTools.multiEditToolInstance = wrapToolWithEmitter(
262
+ baseTools.multiEditTool,
263
+ 'multi_edit',
264
+ baseTools.multiEditTool.execute
265
+ );
266
+ }
267
+
259
268
  return wrappedTools;
260
269
  }
261
270
 
@@ -56,20 +56,52 @@ When reviewing code:
56
56
 
57
57
  'code-review-template': `You are going to perform code review according to provided user rules. Ensure to review only code provided in diff and latest commit, if provided. However you still need to fully understand how modified code works, and read dependencies if something is not clear.`,
58
58
 
59
- 'engineer': `You are senior engineer focused on software architecture and design.
60
- Before jumping on the task you first, in details analyse user request, and try to provide elegant and concise solution.
61
- If solution is clear, you can jump to implementation right away, if not, you can ask user a clarification question, by calling attempt_completion tool, with required details.
59
+ 'engineer': `You are a senior engineer focused on software architecture and design.
60
+ Before jumping on the task you first analyse the user request in detail, and try to provide an elegant and concise solution.
61
+ If the solution is clear, you can jump to implementation right away. If not, ask the user a clarification question by calling the attempt_completion tool with the required details.
62
62
 
63
- Before jumping to implementation:
63
+ # Tone and Style
64
+ - Be concise and direct. Explain your approach briefly before implementing, then let the code speak for itself.
65
+ - Do not add unnecessary preamble or postamble. Skip "Here is what I will do" or "Here is a summary of changes" unless the user asks.
66
+ - Do not add code comments unless the logic is genuinely complex and non-obvious.
67
+
68
+ # Before Implementation
64
69
  - Focus on high-level design patterns and system organization
65
70
  - Identify architectural patterns and component relationships
66
71
  - Evaluate system structure and suggest architectural improvements
67
- - Focus on backward compatibility.
72
+ - Focus on backward compatibility
68
73
  - Consider scalability, maintainability, and extensibility in your analysis
69
74
 
70
- During the implementation:
71
- - Avoid implementing special cases
72
- - Do not forget to add the tests`,
75
+ # Following Conventions
76
+ - NEVER assume a library or dependency is available. Before using any library, check the project's dependency file (package.json, Cargo.toml, go.mod, requirements.txt, etc.) to confirm it exists in the project.
77
+ - Before writing new code, look at neighboring files and existing implementations to understand the project's code style, naming conventions, and patterns. Mimic them.
78
+ - Check imports and existing utilities before creating new helpers — the project may already have what you need.
79
+
80
+ # Task Planning
81
+ - If the task tool is available, use it to break complex work into milestones before starting implementation.
82
+ - Stay flexible — if your understanding changes mid-task, add, remove, or reorganize tasks as needed. The plan should serve you, not constrain you.
83
+
84
+ # During Implementation
85
+ - Always create a new branch before making changes to the codebase.
86
+ - Fix problems at the root cause, not with surface-level patches. Prefer general solutions over special cases.
87
+ - Avoid implementing special cases when a general approach works
88
+ - Never expose secrets, API keys, or credentials in generated code. Never log sensitive information.
89
+ - Do not surprise the user with unrequested changes. Do what was asked, including reasonable follow-up actions, but do not refactor surrounding code or add features that were not requested.
90
+ - After every significant change, verify the project still builds and passes linting. Do not wait until the end to discover breakage.
91
+
92
+ # After Implementation
93
+ - Always run the project's tests before considering the task complete. If tests fail, fix them.
94
+ - Run lint and typecheck commands if known for the project.
95
+ - If a build, lint, or test fails, fix the issue before finishing.
96
+ - When the task is done, respond to the user with a concise summary of what was implemented, what files were changed, and any relevant details. Include links (e.g. pull request URL) so the user has everything they need.
97
+
98
+ # GitHub Integration
99
+ - Use the \`gh\` CLI for all GitHub operations: issues, pull requests, checks, releases.
100
+ - To create a pull request: commit your changes, push the branch, then use \`gh pr create --title "..." --body "..."\`.
101
+ - To view issues or PRs: \`gh issue view <number>\`, \`gh pr view <number>\`.
102
+ - If given a GitHub URL, use \`gh\` to fetch the relevant information rather than guessing.
103
+ - Always return the pull request URL to the user after creating one.
104
+ - When checking GitHub Actions, only read logs of failed jobs — do not waste time on successful ones. Use \`gh run view <run-id> --log-failed\` to fetch only the relevant output.`,
73
105
 
74
106
  'support': `You are ProbeChat Support, a specialized AI assistant focused on helping developers troubleshoot issues and solve problems. Your primary function is to help users diagnose errors, understand unexpected behaviors, and find solutions using the provided code analysis tools.
75
107
 
@@ -10,6 +10,7 @@ import {
10
10
  bashTool,
11
11
  editTool,
12
12
  createTool,
13
+ multiEditTool,
13
14
  DEFAULT_SYSTEM_MESSAGE,
14
15
  attemptCompletionSchema,
15
16
  attemptCompletionToolDefinition,
@@ -23,6 +24,7 @@ import {
23
24
  bashSchema,
24
25
  editSchema,
25
26
  createSchema,
27
+ multiEditSchema,
26
28
  searchToolDefinition,
27
29
  queryToolDefinition,
28
30
  extractToolDefinition,
@@ -33,6 +35,7 @@ import {
33
35
  bashToolDefinition,
34
36
  editToolDefinition,
35
37
  createToolDefinition,
38
+ multiEditToolDefinition,
36
39
  googleSearchToolDefinition,
37
40
  urlContextToolDefinition,
38
41
  parseXmlToolCall
@@ -87,6 +90,9 @@ export function createTools(configOptions) {
87
90
  if (configOptions.allowEdit && isToolAllowed('create')) {
88
91
  tools.createTool = createTool(configOptions);
89
92
  }
93
+ if (configOptions.allowEdit && isToolAllowed('multi_edit')) {
94
+ tools.multiEditTool = multiEditTool(configOptions);
95
+ }
90
96
  return tools;
91
97
  }
92
98
 
@@ -114,6 +120,7 @@ export {
114
120
  bashSchema,
115
121
  editSchema,
116
122
  createSchema,
123
+ multiEditSchema,
117
124
  attemptCompletionSchema,
118
125
  searchToolDefinition,
119
126
  queryToolDefinition,
@@ -125,6 +132,7 @@ export {
125
132
  bashToolDefinition,
126
133
  editToolDefinition,
127
134
  createToolDefinition,
135
+ multiEditToolDefinition,
128
136
  attemptCompletionToolDefinition,
129
137
  googleSearchToolDefinition,
130
138
  urlContextToolDefinition,
package/build/index.js CHANGED
@@ -44,13 +44,15 @@ import {
44
44
  import {
45
45
  editSchema,
46
46
  createSchema,
47
+ multiEditSchema,
47
48
  editToolDefinition,
48
- createToolDefinition
49
+ createToolDefinition,
50
+ multiEditToolDefinition
49
51
  } from './tools/edit.js';
50
52
  import { searchTool, queryTool, extractTool, delegateTool, analyzeAllTool } from './tools/vercel.js';
51
53
  import { createExecutePlanTool, getExecutePlanToolDefinition, createCleanupExecutePlanTool, getCleanupExecutePlanToolDefinition } from './tools/executePlan.js';
52
54
  import { bashTool } from './tools/bash.js';
53
- import { editTool, createTool } from './tools/edit.js';
55
+ import { editTool, createTool, multiEditTool } from './tools/edit.js';
54
56
  import { FileTracker } from './tools/fileTracker.js';
55
57
  import { ProbeAgent } from './agent/ProbeAgent.js';
56
58
  import { SimpleTelemetry, SimpleAppTracer, initializeSimpleTelemetryFromOptions } from './agent/simpleTelemetry.js';
@@ -99,6 +101,7 @@ export {
99
101
  bashTool,
100
102
  editTool,
101
103
  createTool,
104
+ multiEditTool,
102
105
  FileTracker,
103
106
  // Export tool instances
104
107
  listFilesToolInstance,
@@ -115,6 +118,7 @@ export {
115
118
  bashSchema,
116
119
  editSchema,
117
120
  createSchema,
121
+ multiEditSchema,
118
122
  // Export tool definitions
119
123
  searchToolDefinition,
120
124
  queryToolDefinition,
@@ -127,6 +131,7 @@ export {
127
131
  bashToolDefinition,
128
132
  editToolDefinition,
129
133
  createToolDefinition,
134
+ multiEditToolDefinition,
130
135
  googleSearchToolDefinition,
131
136
  urlContextToolDefinition,
132
137
  // Export parser function
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { z } from 'zod';
7
7
  import { resolve, isAbsolute } from 'path';
8
- import { editSchema, createSchema } from './edit.js';
8
+ import { editSchema, createSchema, multiEditSchema } from './edit.js';
9
9
  import { taskSchema } from '../agent/tasks/taskTool.js';
10
10
 
11
11
  // Common schemas for tool parameters (used for internal execution after XML parsing)
@@ -492,7 +492,8 @@ function getValidParamsForTool(toolName) {
492
492
  task: taskSchema,
493
493
  attempt_completion: attemptCompletionSchema,
494
494
  edit: editSchema,
495
- create: createSchema
495
+ create: createSchema,
496
+ multi_edit: multiEditSchema
496
497
  };
497
498
 
498
499
  const schema = schemaMap[toolName];
@@ -538,6 +539,15 @@ export function unescapeXmlEntities(str) {
538
539
  .replace(/&amp;/g, '&');
539
540
  }
540
541
 
542
+ // Parameters that contain arbitrary code/file content — use lastIndexOf for closing tag
543
+ // to handle cases where the content itself contains the closing tag string.
544
+ const RAW_CONTENT_PARAMS = new Set(['content', 'new_string', 'old_string']);
545
+
546
+ // Tools whose content can include their own closing tag string (e.g., file content
547
+ // containing </create> or </edit>). Use lastIndexOf for outer tag boundary, same
548
+ // strategy already used for attempt_completion.
549
+ const LAST_INDEX_TOOLS = new Set(['attempt_completion', 'create', 'edit']);
550
+
541
551
  // Simple XML parser helper - safer string-based approach
542
552
  export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
543
553
  // Find the tool that appears EARLIEST in the string
@@ -564,13 +574,13 @@ export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
564
574
  const closeTag = `</${toolName}>`;
565
575
  const openIndex = earliestOpenIndex;
566
576
 
567
- // For attempt_completion, use lastIndexOf to find the LAST occurrence of closing tag
568
- // This prevents issues where the content contains the closing tag string (e.g., in regex patterns)
569
- // For other tools, use indexOf from the opening tag position
577
+ // For tools that contain arbitrary content (file content, code), use lastIndexOf
578
+ // to find the LAST occurrence of the closing tag. This prevents issues where the
579
+ // content itself contains the closing tag string (e.g., file content with </create>).
580
+ // For other tools, use indexOf from the opening tag position.
570
581
  let closeIndex;
571
- if (toolName === 'attempt_completion') {
582
+ if (LAST_INDEX_TOOLS.has(toolName)) {
572
583
  // Find the last occurrence of the closing tag in the entire string
573
- // This assumes attempt_completion doesn't have nested tags of the same name
574
584
  closeIndex = xmlString.lastIndexOf(closeTag);
575
585
  // Make sure the closing tag is after the opening tag
576
586
  if (closeIndex !== -1 && closeIndex <= openIndex + openTag.length) {
@@ -610,7 +620,19 @@ export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
610
620
  continue; // Parameter not found
611
621
  }
612
622
 
613
- let paramCloseIndex = innerContent.indexOf(paramCloseTag, paramOpenIndex + paramOpenTag.length);
623
+ // For raw content params (file content, code), use lastIndexOf to find the
624
+ // LAST closing tag — the content itself may contain the closing tag string.
625
+ // For other params (file_path, overwrite, etc.), use indexOf (first match).
626
+ let paramCloseIndex;
627
+ if (RAW_CONTENT_PARAMS.has(paramName)) {
628
+ paramCloseIndex = innerContent.lastIndexOf(paramCloseTag);
629
+ // Ensure it's after the opening tag
630
+ if (paramCloseIndex !== -1 && paramCloseIndex <= paramOpenIndex + paramOpenTag.length) {
631
+ paramCloseIndex = -1;
632
+ }
633
+ } else {
634
+ paramCloseIndex = innerContent.indexOf(paramCloseTag, paramOpenIndex + paramOpenTag.length);
635
+ }
614
636
 
615
637
  // Handle unclosed parameter tags - use content until next tag or end of content
616
638
  if (paramCloseIndex === -1) {
@@ -626,23 +648,34 @@ export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
626
648
  paramCloseIndex = nextTagIndex;
627
649
  }
628
650
 
629
- let paramValue = unescapeXmlEntities(innerContent.substring(
651
+ const rawValue = innerContent.substring(
630
652
  paramOpenIndex + paramOpenTag.length,
631
653
  paramCloseIndex
632
- ).trim());
633
-
634
- // Basic type inference (can be improved)
635
- if (paramValue.toLowerCase() === 'true') {
636
- paramValue = true;
637
- } else if (paramValue.toLowerCase() === 'false') {
638
- paramValue = false;
639
- } else if (!isNaN(paramValue) && paramValue.trim() !== '') {
640
- // Check if it's potentially a number (handle integers and floats)
641
- const num = Number(paramValue);
642
- if (Number.isFinite(num)) { // Use Number.isFinite to avoid Infinity/NaN
643
- paramValue = num;
654
+ );
655
+
656
+ // For raw content params, preserve whitespace (only strip XML formatting newlines).
657
+ // For other params, trim all whitespace.
658
+ let paramValue;
659
+ if (RAW_CONTENT_PARAMS.has(paramName)) {
660
+ paramValue = unescapeXmlEntities(rawValue.replace(/^\n/, '').replace(/\n$/, ''));
661
+ } else {
662
+ paramValue = unescapeXmlEntities(rawValue.trim());
663
+ }
664
+
665
+ // Type coercion for non-content params only (content/new_string/old_string must stay strings)
666
+ if (!RAW_CONTENT_PARAMS.has(paramName)) {
667
+ if (paramValue.toLowerCase() === 'true') {
668
+ paramValue = true;
669
+ } else if (paramValue.toLowerCase() === 'false') {
670
+ paramValue = false;
671
+ } else if (!isNaN(paramValue) && paramValue.trim() !== '') {
672
+ // Check if it's potentially a number (handle integers and floats)
673
+ const num = Number(paramValue);
674
+ if (Number.isFinite(num)) { // Use Number.isFinite to avoid Infinity/NaN
675
+ paramValue = num;
676
+ }
677
+ // Keep as string if not a valid finite number
644
678
  }
645
- // Keep as string if not a valid finite number
646
679
  }
647
680
 
648
681
  params[paramName] = paramValue;
@@ -707,7 +740,7 @@ export function detectUnrecognizedToolCall(xmlString, validTools) {
707
740
  const knownToolNames = [
708
741
  'search', 'query', 'extract', 'listFiles', 'searchFiles',
709
742
  'listSkills', 'useSkill', 'readImage', 'edit',
710
- 'create', 'delegate', 'bash', 'task', 'attempt_completion',
743
+ 'create', 'multi_edit', 'delegate', 'bash', 'task', 'attempt_completion',
711
744
  'attempt_complete', 'read_file', 'write_file', 'run_command',
712
745
  'grep', 'find', 'cat', 'list_directory'
713
746
  ];
@@ -363,7 +363,7 @@ Parameters:
363
363
  required: ['file_path', 'new_string']
364
364
  },
365
365
 
366
- execute: async ({ file_path, old_string, new_string, replace_all = false, symbol, position, start_line, end_line }) => {
366
+ execute: async ({ file_path, old_string, new_string, replace_all = false, symbol, position, start_line, end_line, workingDirectory }) => {
367
367
  try {
368
368
  // Validate input parameters
369
369
  if (!file_path || typeof file_path !== 'string' || file_path.trim() === '') {
@@ -373,8 +373,9 @@ Parameters:
373
373
  return `Error editing file: Invalid new_string - must be a string. Provide the replacement content as a string value (empty string "" is valid for deletions).`;
374
374
  }
375
375
 
376
- // Resolve the file path
377
- const resolvedPath = isAbsolute(file_path) ? file_path : resolve(cwd || process.cwd(), file_path);
376
+ // Resolve the file path (workingDirectory from runtime takes priority over cwd from tool creation)
377
+ const effectiveCwd = workingDirectory || cwd || process.cwd();
378
+ const resolvedPath = isAbsolute(file_path) ? file_path : resolve(effectiveCwd, file_path);
378
379
 
379
380
  if (debug) {
380
381
  console.error(`[Edit] Attempting to edit file: ${resolvedPath}`);
@@ -530,7 +531,7 @@ Important:
530
531
  required: ['file_path', 'content']
531
532
  },
532
533
 
533
- execute: async ({ file_path, content, overwrite = false }) => {
534
+ execute: async ({ file_path, content, overwrite = false, workingDirectory }) => {
534
535
  try {
535
536
  // Validate input parameters
536
537
  if (!file_path || typeof file_path !== 'string' || file_path.trim() === '') {
@@ -540,8 +541,9 @@ Important:
540
541
  return `Error creating file: Invalid content - must be a string. Provide the file content as a string value (empty string "" is valid for an empty file).`;
541
542
  }
542
543
 
543
- // Resolve the file path
544
- const resolvedPath = isAbsolute(file_path) ? file_path : resolve(cwd || process.cwd(), file_path);
544
+ // Resolve the file path (workingDirectory from runtime takes priority over cwd from tool creation)
545
+ const effectiveCwd = workingDirectory || cwd || process.cwd();
546
+ const resolvedPath = isAbsolute(file_path) ? file_path : resolve(effectiveCwd, file_path);
545
547
 
546
548
  if (debug) {
547
549
  console.error(`[Create] Attempting to create file: ${resolvedPath}`);
@@ -587,6 +589,86 @@ Important:
587
589
  });
588
590
  };
589
591
 
592
+ /**
593
+ * Multi-edit tool — apply multiple edit operations in a single call.
594
+ * Reuses the edit tool internally; edits are applied sequentially.
595
+ *
596
+ * @param {Object} [options] - Same configuration as editTool
597
+ * @returns {Object} Configured multi_edit tool
598
+ */
599
+ export const multiEditTool = (options = {}) => {
600
+ const editInstance = editTool(options);
601
+
602
+ return tool({
603
+ name: 'multi_edit',
604
+ description: 'Apply multiple file edits in a single tool call. Accepts a JSON array of edit operations.',
605
+
606
+ inputSchema: {
607
+ type: 'object',
608
+ properties: {
609
+ edits: {
610
+ type: 'string',
611
+ description: 'JSON array of edit operations. Each object supports: file_path, old_string, new_string, replace_all, symbol, position, start_line, end_line.'
612
+ }
613
+ },
614
+ required: ['edits']
615
+ },
616
+
617
+ execute: async ({ edits: rawEdits }) => {
618
+ let edits;
619
+ if (typeof rawEdits === 'string') {
620
+ try {
621
+ edits = JSON.parse(rawEdits);
622
+ } catch (e) {
623
+ return `Error: Invalid JSON in edits parameter - ${e.message}. Provide a raw JSON array between <edits> tags.`;
624
+ }
625
+ } else if (Array.isArray(rawEdits)) {
626
+ edits = rawEdits;
627
+ } else {
628
+ return 'Error: edits must be a JSON array of edit operations.';
629
+ }
630
+
631
+ if (!Array.isArray(edits) || edits.length === 0) {
632
+ return 'Error: edits must be a non-empty JSON array.';
633
+ }
634
+ if (edits.length > 50) {
635
+ return `Error: Too many edits (${edits.length}). Maximum 50 per batch.`;
636
+ }
637
+
638
+ const results = [];
639
+ let successCount = 0;
640
+ let failCount = 0;
641
+
642
+ for (let i = 0; i < edits.length; i++) {
643
+ const editOp = edits[i];
644
+ if (!editOp || typeof editOp !== 'object' || Array.isArray(editOp)) {
645
+ results.push(`[${i + 1}] FAIL: Invalid edit operation - must be an object`);
646
+ failCount++;
647
+ continue;
648
+ }
649
+ try {
650
+ const result = await editInstance.execute(editOp);
651
+ const isError = typeof result === 'string' && result.startsWith('Error');
652
+ if (isError) {
653
+ results.push(`[${i + 1}] FAIL: ${result}`);
654
+ failCount++;
655
+ } else {
656
+ results.push(`[${i + 1}] OK: ${result}`);
657
+ successCount++;
658
+ }
659
+ } catch (error) {
660
+ results.push(`[${i + 1}] FAIL: ${error.message}`);
661
+ failCount++;
662
+ }
663
+ }
664
+
665
+ const summary = `Multi-edit: ${successCount}/${edits.length} succeeded` +
666
+ (failCount > 0 ? `, ${failCount} failed` : '');
667
+ return summary + '\n\n' + results.join('\n');
668
+ }
669
+ });
670
+ };
671
+
590
672
  // Export schemas for tool definitions
591
673
  export const editSchema = {
592
674
  type: 'object',
@@ -647,9 +729,21 @@ export const createSchema = {
647
729
  required: ['file_path', 'content']
648
730
  };
649
731
 
732
+ export const multiEditSchema = {
733
+ type: 'object',
734
+ properties: {
735
+ edits: {
736
+ type: 'string',
737
+ description: 'JSON array of edit operations'
738
+ }
739
+ },
740
+ required: ['edits']
741
+ };
742
+
650
743
  // Tool descriptions for XML definitions
651
744
  export const editDescription = 'Edit files using text replacement, AST-aware symbol operations, or line-targeted editing. Supports fuzzy matching for text edits and optional hash-based integrity verification for line edits.';
652
745
  export const createDescription = 'Create new files with specified content. Will create parent directories if needed.';
746
+ export const multiEditDescription = 'Apply multiple file edits in a single tool call. Accepts a JSON array of edit operations, each supporting the same modes as the edit tool.';
653
747
 
654
748
  // XML tool definitions
655
749
  export const editToolDefinition = `
@@ -808,3 +902,42 @@ Examples:
808
902
  This is a new project.</content>
809
903
  <overwrite>true</overwrite>
810
904
  </create>`;
905
+
906
+ export const multiEditToolDefinition = `
907
+ ## multi_edit
908
+ Description: ${multiEditDescription}
909
+
910
+ Apply multiple edits in one call. Each operation in the array uses the same parameters as the edit tool:
911
+ - file_path, old_string, new_string (text mode)
912
+ - file_path, symbol, new_string (symbol replace)
913
+ - file_path, symbol, new_string, position (symbol insert)
914
+ - file_path, start_line, new_string (line-targeted)
915
+
916
+ Edits are applied sequentially. Failures do not stop remaining edits. Maximum 50 edits per call.
917
+
918
+ Parameters:
919
+ - edits: (required) JSON array of edit objects. Place raw JSON between tags.
920
+
921
+ When to use multi_edit vs edit:
922
+ - Use edit for a single change to one file
923
+ - Use multi_edit when making 2+ related changes across files (e.g., rename a function and update all call sites)
924
+ - Use multi_edit for coordinated multi-file refactoring where order matters
925
+
926
+ Examples:
927
+
928
+ Multiple text replacements across files:
929
+ <multi_edit>
930
+ <edits>[
931
+ {"file_path": "src/main.js", "old_string": "return false;", "new_string": "return true;"},
932
+ {"file_path": "src/config.js", "old_string": "debug: false", "new_string": "debug: true"}
933
+ ]</edits>
934
+ </multi_edit>
935
+
936
+ Mixed edit modes in one batch:
937
+ <multi_edit>
938
+ <edits>[
939
+ {"file_path": "src/utils.js", "symbol": "oldHelper", "new_string": "function newHelper() { return 42; }"},
940
+ {"file_path": "src/main.js", "old_string": "oldHelper()", "new_string": "newHelper()", "replace_all": true},
941
+ {"file_path": "src/index.js", "start_line": "10", "end_line": "12", "new_string": "export { newHelper };"}
942
+ ]</edits>
943
+ </multi_edit>`;
@@ -6,7 +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
+ export { editTool, createTool, multiEditTool } from './edit.js';
10
10
 
11
11
  // Export LangChain tools
12
12
  export { createSearchTool, createQueryTool, createExtractTool } from './langchain.js';
@@ -39,10 +39,13 @@ export {
39
39
  export {
40
40
  editSchema,
41
41
  createSchema,
42
+ multiEditSchema,
42
43
  editDescription,
43
44
  createDescription,
45
+ multiEditDescription,
44
46
  editToolDefinition,
45
- createToolDefinition
47
+ createToolDefinition,
48
+ multiEditToolDefinition
46
49
  } from './edit.js';
47
50
 
48
51
  // Export system message