@link-assistant/agent 0.0.9 → 0.0.12

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 (104) hide show
  1. package/EXAMPLES.md +36 -0
  2. package/MODELS.md +72 -24
  3. package/README.md +59 -2
  4. package/TOOLS.md +20 -0
  5. package/package.json +35 -2
  6. package/src/agent/agent.ts +68 -54
  7. package/src/auth/claude-oauth.ts +426 -0
  8. package/src/auth/index.ts +28 -26
  9. package/src/auth/plugins.ts +876 -0
  10. package/src/bun/index.ts +53 -43
  11. package/src/bus/global.ts +5 -5
  12. package/src/bus/index.ts +59 -53
  13. package/src/cli/bootstrap.js +12 -12
  14. package/src/cli/bootstrap.ts +6 -6
  15. package/src/cli/cmd/agent.ts +97 -92
  16. package/src/cli/cmd/auth.ts +469 -0
  17. package/src/cli/cmd/cmd.ts +2 -2
  18. package/src/cli/cmd/export.ts +41 -41
  19. package/src/cli/cmd/mcp.ts +144 -119
  20. package/src/cli/cmd/models.ts +30 -29
  21. package/src/cli/cmd/run.ts +269 -213
  22. package/src/cli/cmd/stats.ts +185 -146
  23. package/src/cli/error.ts +17 -13
  24. package/src/cli/ui.ts +39 -24
  25. package/src/command/index.ts +26 -26
  26. package/src/config/config.ts +528 -288
  27. package/src/config/markdown.ts +15 -15
  28. package/src/file/ripgrep.ts +201 -169
  29. package/src/file/time.ts +21 -18
  30. package/src/file/watcher.ts +51 -42
  31. package/src/file.ts +1 -1
  32. package/src/flag/flag.ts +26 -11
  33. package/src/format/formatter.ts +206 -162
  34. package/src/format/index.ts +61 -61
  35. package/src/global/index.ts +21 -21
  36. package/src/id/id.ts +47 -33
  37. package/src/index.js +346 -199
  38. package/src/json-standard/index.ts +67 -51
  39. package/src/mcp/index.ts +135 -128
  40. package/src/patch/index.ts +336 -267
  41. package/src/project/bootstrap.ts +15 -15
  42. package/src/project/instance.ts +43 -36
  43. package/src/project/project.ts +47 -47
  44. package/src/project/state.ts +37 -33
  45. package/src/provider/models-macro.ts +5 -5
  46. package/src/provider/models.ts +32 -32
  47. package/src/provider/opencode.js +19 -19
  48. package/src/provider/provider.ts +518 -277
  49. package/src/provider/transform.ts +143 -102
  50. package/src/server/project.ts +21 -21
  51. package/src/server/server.ts +111 -105
  52. package/src/session/agent.js +66 -60
  53. package/src/session/compaction.ts +136 -111
  54. package/src/session/index.ts +189 -156
  55. package/src/session/message-v2.ts +312 -268
  56. package/src/session/message.ts +73 -57
  57. package/src/session/processor.ts +180 -166
  58. package/src/session/prompt.ts +678 -533
  59. package/src/session/retry.ts +26 -23
  60. package/src/session/revert.ts +76 -62
  61. package/src/session/status.ts +26 -26
  62. package/src/session/summary.ts +97 -76
  63. package/src/session/system.ts +77 -63
  64. package/src/session/todo.ts +22 -16
  65. package/src/snapshot/index.ts +92 -76
  66. package/src/storage/storage.ts +157 -120
  67. package/src/tool/bash.ts +116 -106
  68. package/src/tool/batch.ts +73 -59
  69. package/src/tool/codesearch.ts +60 -53
  70. package/src/tool/edit.ts +319 -263
  71. package/src/tool/glob.ts +32 -28
  72. package/src/tool/grep.ts +72 -53
  73. package/src/tool/invalid.ts +7 -7
  74. package/src/tool/ls.ts +77 -64
  75. package/src/tool/multiedit.ts +30 -21
  76. package/src/tool/patch.ts +121 -94
  77. package/src/tool/read.ts +140 -122
  78. package/src/tool/registry.ts +38 -38
  79. package/src/tool/task.ts +93 -60
  80. package/src/tool/todo.ts +16 -16
  81. package/src/tool/tool.ts +45 -36
  82. package/src/tool/webfetch.ts +97 -74
  83. package/src/tool/websearch.ts +78 -64
  84. package/src/tool/write.ts +21 -15
  85. package/src/util/binary.ts +27 -19
  86. package/src/util/context.ts +8 -8
  87. package/src/util/defer.ts +7 -5
  88. package/src/util/error.ts +24 -19
  89. package/src/util/eventloop.ts +16 -10
  90. package/src/util/filesystem.ts +37 -33
  91. package/src/util/fn.ts +11 -8
  92. package/src/util/iife.ts +1 -1
  93. package/src/util/keybind.ts +44 -44
  94. package/src/util/lazy.ts +7 -7
  95. package/src/util/locale.ts +20 -16
  96. package/src/util/lock.ts +43 -38
  97. package/src/util/log.ts +95 -85
  98. package/src/util/queue.ts +8 -8
  99. package/src/util/rpc.ts +35 -23
  100. package/src/util/scrap.ts +4 -4
  101. package/src/util/signal.ts +5 -5
  102. package/src/util/timeout.ts +6 -6
  103. package/src/util/token.ts +2 -2
  104. package/src/util/wildcard.ts +38 -27
package/src/tool/glob.ts CHANGED
@@ -1,55 +1,59 @@
1
- import z from "zod"
2
- import path from "path"
3
- import { Tool } from "./tool"
4
- import DESCRIPTION from "./glob.txt"
5
- import { Ripgrep } from "../file/ripgrep"
6
- import { Instance } from "../project/instance"
1
+ import z from 'zod';
2
+ import path from 'path';
3
+ import { Tool } from './tool';
4
+ import DESCRIPTION from './glob.txt';
5
+ import { Ripgrep } from '../file/ripgrep';
6
+ import { Instance } from '../project/instance';
7
7
 
8
- export const GlobTool = Tool.define("glob", {
8
+ export const GlobTool = Tool.define('glob', {
9
9
  description: DESCRIPTION,
10
10
  parameters: z.object({
11
- pattern: z.string().describe("The glob pattern to match files against"),
11
+ pattern: z.string().describe('The glob pattern to match files against'),
12
12
  path: z
13
13
  .string()
14
14
  .optional()
15
15
  .describe(
16
- `The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.`,
16
+ `The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.`
17
17
  ),
18
18
  }),
19
19
  async execute(params) {
20
- let search = params.path ?? Instance.directory
21
- search = path.isAbsolute(search) ? search : path.resolve(Instance.directory, search)
20
+ let search = params.path ?? Instance.directory;
21
+ search = path.isAbsolute(search)
22
+ ? search
23
+ : path.resolve(Instance.directory, search);
22
24
 
23
- const limit = 100
24
- const files = []
25
- let truncated = false
25
+ const limit = 100;
26
+ const files = [];
27
+ let truncated = false;
26
28
  for await (const file of Ripgrep.files({
27
29
  cwd: search,
28
30
  glob: [params.pattern],
29
31
  })) {
30
32
  if (files.length >= limit) {
31
- truncated = true
32
- break
33
+ truncated = true;
34
+ break;
33
35
  }
34
- const full = path.resolve(search, file)
36
+ const full = path.resolve(search, file);
35
37
  const stats = await Bun.file(full)
36
38
  .stat()
37
39
  .then((x) => x.mtime.getTime())
38
- .catch(() => 0)
40
+ .catch(() => 0);
39
41
  files.push({
40
42
  path: full,
41
43
  mtime: stats,
42
- })
44
+ });
43
45
  }
44
- files.sort((a, b) => b.mtime - a.mtime)
46
+ files.sort((a, b) => b.mtime - a.mtime);
45
47
 
46
- const output = []
47
- if (files.length === 0) output.push("No files found")
48
+ const output = [];
49
+ if (files.length === 0) output.push('No files found');
48
50
  if (files.length > 0) {
49
- output.push(...files.map((f) => f.path))
51
+ output.push(...files.map((f) => f.path));
50
52
  if (truncated) {
51
- output.push("")
52
- output.push("(Results are truncated. Consider using a more specific path or pattern.)")
53
+ output.push('');
54
+ output.push(
55
+ '(Results are truncated. Consider using a more specific path or pattern.)'
56
+ );
53
57
  }
54
58
  }
55
59
 
@@ -59,7 +63,7 @@ export const GlobTool = Tool.define("glob", {
59
63
  count: files.length,
60
64
  truncated,
61
65
  },
62
- output: output.join("\n"),
63
- }
66
+ output: output.join('\n'),
67
+ };
64
68
  },
65
- })
69
+ });
package/src/tool/grep.ts CHANGED
@@ -1,107 +1,126 @@
1
- import z from "zod"
2
- import { Tool } from "./tool"
3
- import { Ripgrep } from "../file/ripgrep"
1
+ import z from 'zod';
2
+ import { Tool } from './tool';
3
+ import { Ripgrep } from '../file/ripgrep';
4
4
 
5
- import DESCRIPTION from "./grep.txt"
6
- import { Instance } from "../project/instance"
5
+ import DESCRIPTION from './grep.txt';
6
+ import { Instance } from '../project/instance';
7
7
 
8
- export const GrepTool = Tool.define("grep", {
8
+ export const GrepTool = Tool.define('grep', {
9
9
  description: DESCRIPTION,
10
10
  parameters: z.object({
11
- pattern: z.string().describe("The regex pattern to search for in file contents"),
12
- path: z.string().optional().describe("The directory to search in. Defaults to the current working directory."),
13
- include: z.string().optional().describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'),
11
+ pattern: z
12
+ .string()
13
+ .describe('The regex pattern to search for in file contents'),
14
+ path: z
15
+ .string()
16
+ .optional()
17
+ .describe(
18
+ 'The directory to search in. Defaults to the current working directory.'
19
+ ),
20
+ include: z
21
+ .string()
22
+ .optional()
23
+ .describe(
24
+ 'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'
25
+ ),
14
26
  }),
15
27
  async execute(params) {
16
28
  if (!params.pattern) {
17
- throw new Error("pattern is required")
29
+ throw new Error('pattern is required');
18
30
  }
19
31
 
20
- const searchPath = params.path || Instance.directory
32
+ const searchPath = params.path || Instance.directory;
21
33
 
22
- const rgPath = await Ripgrep.filepath()
23
- const args = ["-nH", "--field-match-separator=|", "--regexp", params.pattern]
34
+ const rgPath = await Ripgrep.filepath();
35
+ const args = [
36
+ '-nH',
37
+ '--field-match-separator=|',
38
+ '--regexp',
39
+ params.pattern,
40
+ ];
24
41
  if (params.include) {
25
- args.push("--glob", params.include)
42
+ args.push('--glob', params.include);
26
43
  }
27
- args.push(searchPath)
44
+ args.push(searchPath);
28
45
 
29
46
  const proc = Bun.spawn([rgPath, ...args], {
30
- stdout: "pipe",
31
- stderr: "pipe",
32
- })
47
+ stdout: 'pipe',
48
+ stderr: 'pipe',
49
+ });
33
50
 
34
- const output = await new Response(proc.stdout).text()
35
- const errorOutput = await new Response(proc.stderr).text()
36
- const exitCode = await proc.exited
51
+ const output = await new Response(proc.stdout).text();
52
+ const errorOutput = await new Response(proc.stderr).text();
53
+ const exitCode = await proc.exited;
37
54
 
38
55
  if (exitCode === 1) {
39
56
  return {
40
57
  title: params.pattern,
41
58
  metadata: { matches: 0, truncated: false },
42
- output: "No files found",
43
- }
59
+ output: 'No files found',
60
+ };
44
61
  }
45
62
 
46
63
  if (exitCode !== 0) {
47
- throw new Error(`ripgrep failed: ${errorOutput}`)
64
+ throw new Error(`ripgrep failed: ${errorOutput}`);
48
65
  }
49
66
 
50
- const lines = output.trim().split("\n")
51
- const matches = []
67
+ const lines = output.trim().split('\n');
68
+ const matches = [];
52
69
 
53
70
  for (const line of lines) {
54
- if (!line) continue
71
+ if (!line) continue;
55
72
 
56
- const [filePath, lineNumStr, ...lineTextParts] = line.split("|")
57
- if (!filePath || !lineNumStr || lineTextParts.length === 0) continue
73
+ const [filePath, lineNumStr, ...lineTextParts] = line.split('|');
74
+ if (!filePath || !lineNumStr || lineTextParts.length === 0) continue;
58
75
 
59
- const lineNum = parseInt(lineNumStr, 10)
60
- const lineText = lineTextParts.join("|")
76
+ const lineNum = parseInt(lineNumStr, 10);
77
+ const lineText = lineTextParts.join('|');
61
78
 
62
- const file = Bun.file(filePath)
63
- const stats = await file.stat().catch(() => null)
64
- if (!stats) continue
79
+ const file = Bun.file(filePath);
80
+ const stats = await file.stat().catch(() => null);
81
+ if (!stats) continue;
65
82
 
66
83
  matches.push({
67
84
  path: filePath,
68
85
  modTime: stats.mtime.getTime(),
69
86
  lineNum,
70
87
  lineText,
71
- })
88
+ });
72
89
  }
73
90
 
74
- matches.sort((a, b) => b.modTime - a.modTime)
91
+ matches.sort((a, b) => b.modTime - a.modTime);
75
92
 
76
- const limit = 100
77
- const truncated = matches.length > limit
78
- const finalMatches = truncated ? matches.slice(0, limit) : matches
93
+ const limit = 100;
94
+ const truncated = matches.length > limit;
95
+ const finalMatches = truncated ? matches.slice(0, limit) : matches;
79
96
 
80
97
  if (finalMatches.length === 0) {
81
98
  return {
82
99
  title: params.pattern,
83
100
  metadata: { matches: 0, truncated: false },
84
- output: "No files found",
85
- }
101
+ output: 'No files found',
102
+ };
86
103
  }
87
104
 
88
- const outputLines = [`Found ${finalMatches.length} matches`]
105
+ const outputLines = [`Found ${finalMatches.length} matches`];
89
106
 
90
- let currentFile = ""
107
+ let currentFile = '';
91
108
  for (const match of finalMatches) {
92
109
  if (currentFile !== match.path) {
93
- if (currentFile !== "") {
94
- outputLines.push("")
110
+ if (currentFile !== '') {
111
+ outputLines.push('');
95
112
  }
96
- currentFile = match.path
97
- outputLines.push(`${match.path}:`)
113
+ currentFile = match.path;
114
+ outputLines.push(`${match.path}:`);
98
115
  }
99
- outputLines.push(` Line ${match.lineNum}: ${match.lineText}`)
116
+ outputLines.push(` Line ${match.lineNum}: ${match.lineText}`);
100
117
  }
101
118
 
102
119
  if (truncated) {
103
- outputLines.push("")
104
- outputLines.push("(Results are truncated. Consider using a more specific path or pattern.)")
120
+ outputLines.push('');
121
+ outputLines.push(
122
+ '(Results are truncated. Consider using a more specific path or pattern.)'
123
+ );
105
124
  }
106
125
 
107
126
  return {
@@ -110,7 +129,7 @@ export const GrepTool = Tool.define("grep", {
110
129
  matches: finalMatches.length,
111
130
  truncated,
112
131
  },
113
- output: outputLines.join("\n"),
114
- }
132
+ output: outputLines.join('\n'),
133
+ };
115
134
  },
116
- })
135
+ });
@@ -1,17 +1,17 @@
1
- import z from "zod"
2
- import { Tool } from "./tool"
1
+ import z from 'zod';
2
+ import { Tool } from './tool';
3
3
 
4
- export const InvalidTool = Tool.define("invalid", {
5
- description: "Do not use",
4
+ export const InvalidTool = Tool.define('invalid', {
5
+ description: 'Do not use',
6
6
  parameters: z.object({
7
7
  tool: z.string(),
8
8
  error: z.string(),
9
9
  }),
10
10
  async execute(params) {
11
11
  return {
12
- title: "Invalid Tool",
12
+ title: 'Invalid Tool',
13
13
  output: `The arguments provided to the tool are invalid: ${params.error}`,
14
14
  metadata: {},
15
- }
15
+ };
16
16
  },
17
- })
17
+ });
package/src/tool/ls.ts CHANGED
@@ -1,102 +1,115 @@
1
- import z from "zod"
2
- import { Tool } from "./tool"
3
- import * as path from "path"
4
- import DESCRIPTION from "./ls.txt"
5
- import { Instance } from "../project/instance"
6
- import { Ripgrep } from "../file/ripgrep"
1
+ import z from 'zod';
2
+ import { Tool } from './tool';
3
+ import * as path from 'path';
4
+ import DESCRIPTION from './ls.txt';
5
+ import { Instance } from '../project/instance';
6
+ import { Ripgrep } from '../file/ripgrep';
7
7
 
8
8
  export const IGNORE_PATTERNS = [
9
- "node_modules/",
10
- "__pycache__/",
11
- ".git/",
12
- "dist/",
13
- "build/",
14
- "target/",
15
- "vendor/",
16
- "bin/",
17
- "obj/",
18
- ".idea/",
19
- ".vscode/",
20
- ".zig-cache/",
21
- "zig-out",
22
- ".coverage",
23
- "coverage/",
24
- "vendor/",
25
- "tmp/",
26
- "temp/",
27
- ".cache/",
28
- "cache/",
29
- "logs/",
30
- ".venv/",
31
- "venv/",
32
- "env/",
33
- ]
34
-
35
- const LIMIT = 100
36
-
37
- export const ListTool = Tool.define("list", {
9
+ 'node_modules/',
10
+ '__pycache__/',
11
+ '.git/',
12
+ 'dist/',
13
+ 'build/',
14
+ 'target/',
15
+ 'vendor/',
16
+ 'bin/',
17
+ 'obj/',
18
+ '.idea/',
19
+ '.vscode/',
20
+ '.zig-cache/',
21
+ 'zig-out',
22
+ '.coverage',
23
+ 'coverage/',
24
+ 'vendor/',
25
+ 'tmp/',
26
+ 'temp/',
27
+ '.cache/',
28
+ 'cache/',
29
+ 'logs/',
30
+ '.venv/',
31
+ 'venv/',
32
+ 'env/',
33
+ ];
34
+
35
+ const LIMIT = 100;
36
+
37
+ export const ListTool = Tool.define('list', {
38
38
  description: DESCRIPTION,
39
39
  parameters: z.object({
40
- path: z.string().describe("The absolute path to the directory to list (must be absolute, not relative)").optional(),
41
- ignore: z.array(z.string()).describe("List of glob patterns to ignore").optional(),
40
+ path: z
41
+ .string()
42
+ .describe(
43
+ 'The absolute path to the directory to list (must be absolute, not relative)'
44
+ )
45
+ .optional(),
46
+ ignore: z
47
+ .array(z.string())
48
+ .describe('List of glob patterns to ignore')
49
+ .optional(),
42
50
  }),
43
51
  async execute(params) {
44
- const searchPath = path.resolve(Instance.directory, params.path || ".")
45
-
46
- const ignoreGlobs = IGNORE_PATTERNS.map((p) => `!${p}*`).concat(params.ignore?.map((p) => `!${p}`) || [])
47
- const files = []
48
- for await (const file of Ripgrep.files({ cwd: searchPath, glob: ignoreGlobs })) {
49
- files.push(file)
50
- if (files.length >= LIMIT) break
52
+ const searchPath = path.resolve(Instance.directory, params.path || '.');
53
+
54
+ const ignoreGlobs = IGNORE_PATTERNS.map((p) => `!${p}*`).concat(
55
+ params.ignore?.map((p) => `!${p}`) || []
56
+ );
57
+ const files = [];
58
+ for await (const file of Ripgrep.files({
59
+ cwd: searchPath,
60
+ glob: ignoreGlobs,
61
+ })) {
62
+ files.push(file);
63
+ if (files.length >= LIMIT) break;
51
64
  }
52
65
 
53
66
  // Build directory structure
54
- const dirs = new Set<string>()
55
- const filesByDir = new Map<string, string[]>()
67
+ const dirs = new Set<string>();
68
+ const filesByDir = new Map<string, string[]>();
56
69
 
57
70
  for (const file of files) {
58
- const dir = path.dirname(file)
59
- const parts = dir === "." ? [] : dir.split("/")
71
+ const dir = path.dirname(file);
72
+ const parts = dir === '.' ? [] : dir.split('/');
60
73
 
61
74
  // Add all parent directories
62
75
  for (let i = 0; i <= parts.length; i++) {
63
- const dirPath = i === 0 ? "." : parts.slice(0, i).join("/")
64
- dirs.add(dirPath)
76
+ const dirPath = i === 0 ? '.' : parts.slice(0, i).join('/');
77
+ dirs.add(dirPath);
65
78
  }
66
79
 
67
80
  // Add file to its directory
68
- if (!filesByDir.has(dir)) filesByDir.set(dir, [])
69
- filesByDir.get(dir)!.push(path.basename(file))
81
+ if (!filesByDir.has(dir)) filesByDir.set(dir, []);
82
+ filesByDir.get(dir)!.push(path.basename(file));
70
83
  }
71
84
 
72
85
  function renderDir(dirPath: string, depth: number): string {
73
- const indent = " ".repeat(depth)
74
- let output = ""
86
+ const indent = ' '.repeat(depth);
87
+ let output = '';
75
88
 
76
89
  if (depth > 0) {
77
- output += `${indent}${path.basename(dirPath)}/\n`
90
+ output += `${indent}${path.basename(dirPath)}/\n`;
78
91
  }
79
92
 
80
- const childIndent = " ".repeat(depth + 1)
93
+ const childIndent = ' '.repeat(depth + 1);
81
94
  const children = Array.from(dirs)
82
95
  .filter((d) => path.dirname(d) === dirPath && d !== dirPath)
83
- .sort()
96
+ .sort();
84
97
 
85
98
  // Render subdirectories first
86
99
  for (const child of children) {
87
- output += renderDir(child, depth + 1)
100
+ output += renderDir(child, depth + 1);
88
101
  }
89
102
 
90
103
  // Render files
91
- const files = filesByDir.get(dirPath) || []
104
+ const files = filesByDir.get(dirPath) || [];
92
105
  for (const file of files.sort()) {
93
- output += `${childIndent}${file}\n`
106
+ output += `${childIndent}${file}\n`;
94
107
  }
95
108
 
96
- return output
109
+ return output;
97
110
  }
98
111
 
99
- const output = `${searchPath}/\n` + renderDir(".", 0)
112
+ const output = `${searchPath}/\n` + renderDir('.', 0);
100
113
 
101
114
  return {
102
115
  title: path.relative(Instance.worktree, searchPath),
@@ -105,6 +118,6 @@ export const ListTool = Tool.define("list", {
105
118
  truncated: files.length >= LIMIT,
106
119
  },
107
120
  output,
108
- }
121
+ };
109
122
  },
110
- })
123
+ });
@@ -1,28 +1,37 @@
1
- import z from "zod"
2
- import { Tool } from "./tool"
3
- import { EditTool } from "./edit"
4
- import DESCRIPTION from "./multiedit.txt"
5
- import path from "path"
6
- import { Instance } from "../project/instance"
1
+ import z from 'zod';
2
+ import { Tool } from './tool';
3
+ import { EditTool } from './edit';
4
+ import DESCRIPTION from './multiedit.txt';
5
+ import path from 'path';
6
+ import { Instance } from '../project/instance';
7
7
 
8
- export const MultiEditTool = Tool.define("multiedit", {
8
+ export const MultiEditTool = Tool.define('multiedit', {
9
9
  description: DESCRIPTION,
10
10
  parameters: z.object({
11
- filePath: z.string().describe("The absolute path to the file to modify"),
11
+ filePath: z.string().describe('The absolute path to the file to modify'),
12
12
  edits: z
13
13
  .array(
14
14
  z.object({
15
- filePath: z.string().describe("The absolute path to the file to modify"),
16
- oldString: z.string().describe("The text to replace"),
17
- newString: z.string().describe("The text to replace it with (must be different from oldString)"),
18
- replaceAll: z.boolean().optional().describe("Replace all occurrences of oldString (default false)"),
19
- }),
15
+ filePath: z
16
+ .string()
17
+ .describe('The absolute path to the file to modify'),
18
+ oldString: z.string().describe('The text to replace'),
19
+ newString: z
20
+ .string()
21
+ .describe(
22
+ 'The text to replace it with (must be different from oldString)'
23
+ ),
24
+ replaceAll: z
25
+ .boolean()
26
+ .optional()
27
+ .describe('Replace all occurrences of oldString (default false)'),
28
+ })
20
29
  )
21
- .describe("Array of edit operations to perform sequentially on the file"),
30
+ .describe('Array of edit operations to perform sequentially on the file'),
22
31
  }),
23
32
  async execute(params, ctx) {
24
- const tool = await EditTool.init()
25
- const results = []
33
+ const tool = await EditTool.init();
34
+ const results = [];
26
35
  for (const [, edit] of params.edits.entries()) {
27
36
  const result = await tool.execute(
28
37
  {
@@ -31,9 +40,9 @@ export const MultiEditTool = Tool.define("multiedit", {
31
40
  newString: edit.newString,
32
41
  replaceAll: edit.replaceAll,
33
42
  },
34
- ctx,
35
- )
36
- results.push(result)
43
+ ctx
44
+ );
45
+ results.push(result);
37
46
  }
38
47
  return {
39
48
  title: path.relative(Instance.worktree, params.filePath),
@@ -41,6 +50,6 @@ export const MultiEditTool = Tool.define("multiedit", {
41
50
  results: results.map((r) => r.metadata),
42
51
  },
43
52
  output: results.at(-1)!.output,
44
- }
53
+ };
45
54
  },
46
- })
55
+ });