@empiricalrun/test-gen 0.64.0 → 0.64.2

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 (58) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/agent/browsing/run.d.ts +2 -1
  3. package/dist/agent/browsing/run.d.ts.map +1 -1
  4. package/dist/agent/browsing/run.js +15 -7
  5. package/dist/agent/chat/index.d.ts.map +1 -1
  6. package/dist/agent/chat/index.js +3 -0
  7. package/dist/agent/chat/models.js +1 -1
  8. package/dist/agent/cua/computer.d.ts.map +1 -1
  9. package/dist/agent/cua/computer.js +5 -1
  10. package/dist/agent/cua/pw-codegen/element-from-point.d.ts.map +1 -1
  11. package/dist/agent/cua/pw-codegen/element-from-point.js +58 -46
  12. package/dist/artifacts/utils.d.ts +21 -0
  13. package/dist/artifacts/utils.d.ts.map +1 -0
  14. package/dist/artifacts/utils.js +102 -0
  15. package/dist/bin/index.js +2 -1
  16. package/dist/bin/utils/platform/web/index.d.ts.map +1 -1
  17. package/dist/bin/utils/platform/web/index.js +2 -0
  18. package/dist/test-build/index.js +1 -1
  19. package/dist/tool-call-service/index.d.ts +7 -2
  20. package/dist/tool-call-service/index.d.ts.map +1 -1
  21. package/dist/tool-call-service/index.js +11 -2
  22. package/dist/tool-call-service/utils.d.ts +2 -1
  23. package/dist/tool-call-service/utils.d.ts.map +1 -1
  24. package/dist/tool-call-service/utils.js +9 -5
  25. package/dist/tools/commit-and-create-pr.d.ts +1 -1
  26. package/dist/tools/commit-and-create-pr.d.ts.map +1 -1
  27. package/dist/tools/diagnosis-fetcher.d.ts +1 -1
  28. package/dist/tools/diagnosis-fetcher.d.ts.map +1 -1
  29. package/dist/tools/diagnosis-fetcher.js +3 -1
  30. package/dist/tools/download-build.d.ts +1 -1
  31. package/dist/tools/download-build.d.ts.map +1 -1
  32. package/dist/tools/download-build.js +3 -1
  33. package/dist/tools/environment-crud.d.ts +1 -1
  34. package/dist/tools/environment-crud.d.ts.map +1 -1
  35. package/dist/tools/grep/index.d.ts +1 -1
  36. package/dist/tools/grep/index.d.ts.map +1 -1
  37. package/dist/tools/grep/index.js +3 -2
  38. package/dist/tools/str_replace_editor.d.ts +1 -6
  39. package/dist/tools/str_replace_editor.d.ts.map +1 -1
  40. package/dist/tools/str_replace_editor.js +237 -229
  41. package/dist/tools/test-gen-browser.d.ts +1 -1
  42. package/dist/tools/test-gen-browser.d.ts.map +1 -1
  43. package/dist/tools/test-gen-browser.js +35 -21
  44. package/dist/tools/test-run-fetcher/index.d.ts +1 -1
  45. package/dist/tools/test-run-fetcher/index.d.ts.map +1 -1
  46. package/dist/tools/test-run.d.ts +1 -1
  47. package/dist/tools/test-run.d.ts.map +1 -1
  48. package/dist/tools/test-run.js +36 -12
  49. package/dist/tools/utils/index.d.ts +0 -13
  50. package/dist/tools/utils/index.d.ts.map +1 -1
  51. package/dist/tools/utils/index.js +0 -47
  52. package/dist/utils/exec.d.ts +4 -4
  53. package/dist/utils/exec.d.ts.map +1 -1
  54. package/dist/utils/exec.js +2 -4
  55. package/dist/utils/file-tree.d.ts.map +1 -1
  56. package/dist/utils/file-tree.js +2 -0
  57. package/package.json +4 -4
  58. package/tsconfig.tsbuildinfo +1 -1
@@ -1,3 +1,3 @@
1
- import type { Tool } from "@empiricalrun/llm/chat";
1
+ import type { Tool } from "@empiricalrun/shared-types";
2
2
  export declare const fetchDiagnosisReportTool: Tool;
3
3
  //# sourceMappingURL=diagnosis-fetcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"diagnosis-fetcher.d.ts","sourceRoot":"","sources":["../../src/tools/diagnosis-fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAenD,eAAO,MAAM,wBAAwB,EAAE,IAyFtC,CAAC"}
1
+ {"version":3,"file":"diagnosis-fetcher.d.ts","sourceRoot":"","sources":["../../src/tools/diagnosis-fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,4BAA4B,CAAC;AAepE,eAAO,MAAM,wBAAwB,EAAE,IAqFtC,CAAC"}
@@ -19,7 +19,9 @@ exports.fetchDiagnosisReportTool = {
19
19
  description: "Fetch details about a test case diagnosis using its URL or slug",
20
20
  parameters: DiagnosisSchema,
21
21
  },
22
- execute: async ({ input, repoPath, apiKey, }) => {
22
+ execute: async (params) => {
23
+ const { input } = params;
24
+ const { repoPath, apiKey } = params;
23
25
  const { diagnosisUrl } = input;
24
26
  // Extract the slug from the URL - it's the part after the last '--'
25
27
  const slug = diagnosisUrl.split("--").pop();
@@ -1,4 +1,4 @@
1
- import type { Tool } from "@empiricalrun/llm/chat";
1
+ import type { Tool } from "@empiricalrun/shared-types";
2
2
  import { z } from "zod";
3
3
  export declare const downloadBuildToolSchema: z.ZodObject<{
4
4
  buildUrl: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"download-build.d.ts","sourceRoot":"","sources":["../../src/tools/download-build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,uBAAuB;;;;;;EAElC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE7E,eAAO,MAAM,iBAAiB,EAAE,IAsC/B,CAAC"}
1
+ {"version":3,"file":"download-build.d.ts","sourceRoot":"","sources":["../../src/tools/download-build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,uBAAuB;;;;;;EAElC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE7E,eAAO,MAAM,iBAAiB,EAAE,IAkC/B,CAAC"}
@@ -14,7 +14,9 @@ have a build URL, you can try getting the environment details with the getEnviro
14
14
  Environment details will include the build URL.`,
15
15
  parameters: exports.downloadBuildToolSchema,
16
16
  },
17
- execute: async ({ input, repoPath, apiKey, }) => {
17
+ execute: async (params) => {
18
+ const { input } = params;
19
+ const { repoPath, apiKey } = params;
18
20
  if (!(await (0, test_build_1.hasDownloadScript)(repoPath))) {
19
21
  return {
20
22
  isError: true,
@@ -1,4 +1,4 @@
1
- import type { Tool } from "@empiricalrun/llm/chat";
1
+ import type { Tool } from "@empiricalrun/shared-types";
2
2
  export declare const getEnvironmentTool: Tool;
3
3
  export declare const environmentTools: Tool[];
4
4
  //# sourceMappingURL=environment-crud.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"environment-crud.d.ts","sourceRoot":"","sources":["../../src/tools/environment-crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AA6DnD,eAAO,MAAM,kBAAkB,EAAE,IAiFhC,CAAC;AAGF,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAAyB,CAAC"}
1
+ {"version":3,"file":"environment-crud.d.ts","sourceRoot":"","sources":["../../src/tools/environment-crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,IAAI,EAEL,MAAM,4BAA4B,CAAC;AA6DpC,eAAO,MAAM,kBAAkB,EAAE,IAoFhC,CAAC;AAGF,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAAyB,CAAC"}
@@ -1,3 +1,3 @@
1
- import type { Tool } from "@empiricalrun/llm/chat";
1
+ import type { Tool } from "@empiricalrun/shared-types";
2
2
  export declare const grepTool: Tool;
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/grep/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,wBAAwB,CAAC;AA6H/D,eAAO,MAAM,QAAQ,EAAE,IAuBtB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/grep/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,IAAI,EAGL,MAAM,4BAA4B,CAAC;AA8HpC,eAAO,MAAM,QAAQ,EAAE,IA2BtB,CAAC"}
@@ -75,8 +75,9 @@ async function usingRipgrep(input, repoPath) {
75
75
  try {
76
76
  const dir = path_1.default.join(repoPath, input.directory || "");
77
77
  const escapedPattern = input.pattern
78
- .replace(/\s+/g, "\\ ")
79
- .replace(/([[\]()])/g, "\\$1");
78
+ .replace(/[.*+?^${}()|[\]\\]/g, "\\$&") // Escape special characters that have meaning in regex/grep
79
+ .replace(/[&;|<>]/g, "\\$&") // Escape shell special characters
80
+ .replace(/\s+/g, "\\ "); // Handle spaces
80
81
  const results = (0, ripgrep_1.ripgrep)(dir, {
81
82
  string: escapedPattern,
82
83
  globs: input.filePattern ? [input.filePattern] : undefined,
@@ -1,4 +1,4 @@
1
- import { Tool, ToolResult } from "@empiricalrun/llm/chat";
1
+ import type { Tool, ToolResult } from "@empiricalrun/shared-types";
2
2
  interface StrReplaceInput {
3
3
  command: string;
4
4
  path: string;
@@ -8,11 +8,6 @@ interface StrReplaceInput {
8
8
  file_text?: string;
9
9
  insert_line?: number;
10
10
  }
11
- /**
12
- * Cleans up any backup files that were created during the editing process
13
- * @returns The number of backup files that were cleaned up
14
- */
15
- export declare function cleanupBackupFiles(repoDir: string): number;
16
11
  /**
17
12
  * Our implementation of Claude's built-in text editor tool
18
13
  * https://docs.anthropic.com/en/docs/build-with-claude/tool-use/text-editor-tool
@@ -1 +1 @@
1
- {"version":3,"file":"str_replace_editor.d.ts","sourceRoot":"","sources":["../../src/tools/str_replace_editor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AA2B1D,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAqED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwC1D;AAMD;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,EAC7C,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,UAAU,CAAC,CAkNtB;AAmID,eAAO,MAAM,eAAe,EAAE,IAAI,EAKjC,CAAC"}
1
+ {"version":3,"file":"str_replace_editor.d.ts","sourceRoot":"","sources":["../../src/tools/str_replace_editor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAe,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAOhF,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA6UD;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,EAC7C,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,UAAU,CAAC,CA2CtB;AA8HD,eAAO,MAAM,eAAe,EAAE,IAAI,EAKjC,CAAC"}
@@ -4,29 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.textEditorTools = void 0;
7
- exports.cleanupBackupFiles = cleanupBackupFiles;
8
7
  exports.strReplaceEditorExecutor = strReplaceEditorExecutor;
9
8
  const fs_1 = __importDefault(require("fs"));
10
9
  const path_1 = __importDefault(require("path"));
11
10
  const zod_1 = require("zod");
12
11
  const web_1 = require("../bin/utils/platform/web");
13
- function createBackup(filePath) {
14
- const backupPath = `${filePath}.bak`;
15
- if (fs_1.default.existsSync(filePath)) {
16
- fs_1.default.copyFileSync(filePath, backupPath);
17
- }
18
- }
19
- function hasBackup(filePath) {
20
- const backupPath = `${filePath}.bak`;
21
- return fs_1.default.existsSync(backupPath);
22
- }
23
- function restoreBackup(filePath) {
24
- const backupPath = `${filePath}.bak`;
25
- if (fs_1.default.existsSync(backupPath)) {
26
- fs_1.default.copyFileSync(backupPath, filePath);
27
- fs_1.default.unlinkSync(backupPath);
28
- }
29
- }
30
12
  /**
31
13
  * While running str_replace command, we've seen LLM can struggle to send unique old_str.
32
14
  * This function tries to find unique contexts for each occurrence of old_str, so that the error
@@ -85,55 +67,212 @@ function getUniqueOccurences(contents, old_str) {
85
67
  }
86
68
  return uniqueContexts;
87
69
  }
88
- /**
89
- * Cleans up any backup files that were created during the editing process
90
- * @returns The number of backup files that were cleaned up
91
- */
92
- function cleanupBackupFiles(repoDir) {
93
- let cleanedCount = 0;
94
- const walkDir = (dir) => {
95
- let files = [];
96
- try {
97
- files = fs_1.default.readdirSync(dir);
98
- }
99
- catch (readDirError) {
100
- return;
101
- }
102
- for (const file of files) {
103
- const fullPath = path_1.default.join(dir, file);
104
- let stat;
105
- try {
106
- stat = fs_1.default.statSync(fullPath);
107
- }
108
- catch (statError) {
109
- continue;
110
- }
111
- if (stat.isDirectory()) {
112
- if (file !== "node_modules") {
113
- walkDir(fullPath);
114
- }
115
- }
116
- else if (file.endsWith(".bak")) {
117
- try {
118
- fs_1.default.unlinkSync(fullPath);
119
- cleanedCount++;
120
- }
121
- catch (unlinkError) {
122
- // Intentionally ignore errors during cleanup
123
- }
124
- }
70
+ function escapeRegExp(text) {
71
+ return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
72
+ }
73
+ async function fileViewExecutor({ input, filePath, absoluteFilePath, }) {
74
+ if (!fs_1.default.existsSync(absoluteFilePath)) {
75
+ return {
76
+ result: `Error: File ${filePath} not found. Please provide relative file path to the Repository.`,
77
+ isError: true,
78
+ };
79
+ }
80
+ // Handle directory view
81
+ if (fs_1.default.statSync(absoluteFilePath).isDirectory()) {
82
+ const files = fs_1.default.readdirSync(absoluteFilePath);
83
+ return {
84
+ result: files.join("\n"),
85
+ isError: false,
86
+ };
87
+ }
88
+ else {
89
+ // Check if file is binary, which is not supported
90
+ const { isBinary } = await import("istextorbinary");
91
+ const binary = isBinary(absoluteFilePath);
92
+ if (binary) {
93
+ return {
94
+ result: "Error: File is binary, which is not supported",
95
+ isError: true,
96
+ };
125
97
  }
98
+ }
99
+ // Handle file view
100
+ const content = fs_1.default.readFileSync(absoluteFilePath, "utf8");
101
+ const lines = content.split("\n");
102
+ if (input.view_range) {
103
+ const [start, end] = input.view_range;
104
+ const endLine = end === -1 ? lines.length : end;
105
+ return {
106
+ result: lines
107
+ .slice(start - 1, endLine)
108
+ .map((line, idx) => `${start + idx}: ${line}`)
109
+ .join("\n"),
110
+ isError: false,
111
+ };
112
+ }
113
+ return {
114
+ result: lines.map((line, idx) => `${idx + 1}: ${line}`).join("\n"),
115
+ isError: false,
126
116
  };
127
- try {
128
- walkDir(repoDir);
117
+ }
118
+ async function fileCreateExecutor({ input, filePath, absoluteFilePath, repoDir, }) {
119
+ if (input.file_text === undefined || input.file_text === null) {
120
+ return {
121
+ result: "Error: file_text is required for create command",
122
+ isError: true,
123
+ };
129
124
  }
130
- catch (error) {
131
- // Intentionally ignore errors during cleanup
125
+ if (filePath.endsWith("test.ts")) {
126
+ return {
127
+ result: "Error: Creating test.ts files is not allowed. Did you mean spec.ts?",
128
+ isError: true,
129
+ };
130
+ }
131
+ if (filePath.endsWith("spec.ts") && !filePath.startsWith("tests/")) {
132
+ return {
133
+ result: "Error: Creating spec.ts files is not allowed outside tests/ directory",
134
+ isError: true,
135
+ };
136
+ }
137
+ const fileName = path_1.default.basename(filePath);
138
+ if (!fileName.includes(".")) {
139
+ return {
140
+ result: `Error: File name must include a file extension. This tool cannot create empty directories.
141
+ If you need to create a file which is inside a directory that does not exist, you can expect this tool to create
142
+ the required directories recursively for the new file.`,
143
+ isError: true,
144
+ };
145
+ }
146
+ const parentDir = path_1.default.dirname(absoluteFilePath);
147
+ if (parentDir !== "." && !fs_1.default.existsSync(parentDir)) {
148
+ // Ensure parent directory exists
149
+ fs_1.default.mkdirSync(parentDir, { recursive: true });
150
+ }
151
+ fs_1.default.writeFileSync(absoluteFilePath, input.file_text);
152
+ let createTypescriptResult = await (0, web_1.runTypescriptCompiler)(repoDir);
153
+ if (!createTypescriptResult.success) {
154
+ return {
155
+ result: `File ${filePath} has been created. However, type checks are failing with errors:\n\n${createTypescriptResult.errors.join("\n")}`,
156
+ isError: true,
157
+ };
158
+ }
159
+ else {
160
+ return {
161
+ result: `Successfully created file ${filePath}`,
162
+ isError: false,
163
+ };
132
164
  }
133
- return cleanedCount;
134
165
  }
135
- function escapeRegExp(text) {
136
- return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
166
+ async function fileStrReplaceExecutor({ input, filePath, absoluteFilePath, repoDir, }) {
167
+ if (!fs_1.default.existsSync(absoluteFilePath)) {
168
+ return {
169
+ result: `Error: File ${filePath} not found. Please provide relative file path to the Repository.`,
170
+ isError: true,
171
+ };
172
+ }
173
+ if (!input.old_str) {
174
+ return {
175
+ result: "Error: old_str is required for str_replace command",
176
+ isError: true,
177
+ };
178
+ }
179
+ if (input.new_str === undefined || input.new_str === null) {
180
+ // "" is valid as new_str, so we check for nullish -- not falsy
181
+ return {
182
+ result: "Error: new_str is required for str_replace command",
183
+ isError: true,
184
+ };
185
+ }
186
+ const content = fs_1.default.readFileSync(absoluteFilePath, "utf8");
187
+ // Normalize newlines in both the content and search string
188
+ const normalizedContent = content.replace(/\r\n/g, "\n");
189
+ const normalizedOldStr = input.old_str
190
+ .replace(/\\n/g, "\n")
191
+ .replace(/\r\n/g, "\n");
192
+ if (!normalizedContent.includes(normalizedOldStr)) {
193
+ return {
194
+ result: `old_str not found in file: ${filePath}`,
195
+ isError: true,
196
+ };
197
+ }
198
+ else {
199
+ const escapedOldStr = escapeRegExp(normalizedOldStr);
200
+ const occurences = normalizedContent.match(new RegExp(escapedOldStr, "g"));
201
+ if (occurences && occurences.length > 1) {
202
+ const uniqueContexts = getUniqueOccurences(content, input.old_str);
203
+ if (uniqueContexts.length === 0) {
204
+ return {
205
+ result: `Error: old_str found ${occurences.length} times in file: ${filePath}, but no unique contexts could be identified. Try using a more specific string.`,
206
+ isError: true,
207
+ };
208
+ }
209
+ const uniqueContextsString = uniqueContexts
210
+ .map(({ uniqueContext, lineNumber }, idx) => `${idx + 1}. For occurence at line number ${lineNumber}, unique context is:\n\`\`\`\n${uniqueContext}\n\`\`\`\n`)
211
+ .join("\n");
212
+ return {
213
+ result: `Error: old_str found ${occurences.length} times in file: ${filePath}. Please use one of these unique contexts instead:\n\n${uniqueContextsString}`,
214
+ isError: true,
215
+ };
216
+ }
217
+ const newContent = normalizedContent.replace(normalizedOldStr, input.new_str);
218
+ fs_1.default.writeFileSync(absoluteFilePath, newContent);
219
+ let strReplaceTypescriptResult = await (0, web_1.runTypescriptCompiler)(repoDir);
220
+ if (!strReplaceTypescriptResult.success) {
221
+ return {
222
+ result: `Edits to file ${filePath} have been applied. However, type checks are failing with errors:\n\n${strReplaceTypescriptResult.errors.join("\n")}`,
223
+ isError: true,
224
+ };
225
+ }
226
+ else {
227
+ return {
228
+ result: `Edits to file ${filePath} have been applied. Type checks have also passed.`,
229
+ isError: false,
230
+ };
231
+ }
232
+ }
233
+ }
234
+ async function fileInsertExecutor({ input, filePath, absoluteFilePath, repoDir, }) {
235
+ if (!fs_1.default.existsSync(absoluteFilePath)) {
236
+ return {
237
+ result: `Error: File ${filePath} not found. Please provide relative file path to the Repository.`,
238
+ isError: true,
239
+ };
240
+ }
241
+ if (input.insert_line === undefined || !input.new_str) {
242
+ return {
243
+ result: "Error: insert_line and new_str are required for insert command",
244
+ isError: true,
245
+ };
246
+ }
247
+ const content = fs_1.default.readFileSync(absoluteFilePath, "utf8");
248
+ const lines = content.split("\n");
249
+ if (input.insert_line < 1) {
250
+ return {
251
+ result: "Error: insert_line must be greater than or equal to 1 (line numbers are 1-indexed).",
252
+ isError: true,
253
+ };
254
+ }
255
+ if (input.insert_line > lines.length + 1) {
256
+ return {
257
+ result: `Error: The file at ${filePath} has only ${lines.length} lines, so insert_line must be less than or equal to ${lines.length + 1}. At the maximum value of ${lines.length + 1}, you can insert at the end of the file.`,
258
+ isError: true,
259
+ };
260
+ }
261
+ lines.splice(input.insert_line - 1, 0, input.new_str);
262
+ fs_1.default.writeFileSync(absoluteFilePath, lines.join("\n"));
263
+ let insertTypescriptResult = await (0, web_1.runTypescriptCompiler)(repoDir);
264
+ if (!insertTypescriptResult.success) {
265
+ return {
266
+ result: `Insertion in file ${filePath} was applied. However, type checks are failing with errors:\n${insertTypescriptResult.errors.join("\n")}`,
267
+ isError: true,
268
+ };
269
+ }
270
+ else {
271
+ return {
272
+ result: `Insertion in file ${filePath} was applied. Type checks have also passed.`,
273
+ isError: false,
274
+ };
275
+ }
137
276
  }
138
277
  /**
139
278
  * Our implementation of Claude's built-in text editor tool
@@ -144,175 +283,33 @@ async function strReplaceEditorExecutor({ input, repoPath, }) {
144
283
  const { path: filePath } = input;
145
284
  const absoluteFilePath = path_1.default.join(repoDir, filePath);
146
285
  try {
147
- let content;
148
- let lines;
149
- let newContent;
150
286
  switch (input.command) {
151
287
  case "view":
152
- if (!fs_1.default.existsSync(absoluteFilePath)) {
153
- return {
154
- result: "Error: File not found",
155
- isError: true,
156
- };
157
- }
158
- // Handle directory view
159
- if (fs_1.default.statSync(absoluteFilePath).isDirectory()) {
160
- const files = fs_1.default.readdirSync(absoluteFilePath);
161
- return {
162
- result: files.join("\n"),
163
- isError: false,
164
- };
165
- }
166
- else {
167
- // Check if file is binary, which is not supported
168
- const { isBinary } = await import("istextorbinary");
169
- const binary = isBinary(absoluteFilePath);
170
- if (binary) {
171
- return {
172
- result: "Error: File is binary, which is not supported",
173
- isError: true,
174
- };
175
- }
176
- }
177
- // Handle file view
178
- content = fs_1.default.readFileSync(absoluteFilePath, "utf8");
179
- lines = content.split("\n");
180
- if (input.view_range) {
181
- const [start, end] = input.view_range;
182
- const endLine = end === -1 ? lines.length : end;
183
- return {
184
- result: lines
185
- .slice(start - 1, endLine)
186
- .map((line, idx) => `${start + idx}: ${line}`)
187
- .join("\n"),
188
- isError: false,
189
- };
190
- }
191
- return {
192
- result: lines.map((line, idx) => `${idx + 1}: ${line}`).join("\n"),
193
- isError: false,
194
- };
288
+ return fileViewExecutor({ input, filePath, absoluteFilePath });
195
289
  case "create":
196
- if (input.file_text === undefined || input.file_text === null) {
197
- throw new Error("file_text is required for create command");
198
- }
199
- if (filePath.endsWith("test.ts")) {
200
- throw new Error("Creating test.ts files is not allowed. Did you mean spec.ts?");
201
- }
202
- if (filePath.endsWith("spec.ts") && !filePath.startsWith("tests/")) {
203
- throw new Error("Creating spec.ts files is not allowed outside tests/ directory");
204
- }
205
- const parentDir = path_1.default.dirname(absoluteFilePath);
206
- if (parentDir !== "." && !fs_1.default.existsSync(parentDir)) {
207
- // Ensure parent directory exists
208
- fs_1.default.mkdirSync(parentDir, { recursive: true });
209
- }
210
- fs_1.default.writeFileSync(absoluteFilePath, input.file_text);
211
- let createTypescriptResult = await (0, web_1.runTypescriptCompiler)(repoDir);
212
- if (!createTypescriptResult.success) {
213
- return {
214
- result: `File ${filePath} has been created. However, type checks are failing with errors:\n${createTypescriptResult.errors.join("\n")}`,
215
- isError: true,
216
- };
217
- }
218
- return {
219
- result: `Successfully created file ${filePath}`,
220
- isError: false,
221
- };
290
+ return fileCreateExecutor({
291
+ input,
292
+ filePath,
293
+ absoluteFilePath,
294
+ repoDir,
295
+ });
222
296
  case "str_replace":
223
- if (!input.old_str) {
224
- throw new Error("old_str is required for str_replace command");
225
- }
226
- if (input.new_str === undefined || input.new_str === null) {
227
- // "" is valid as new_str, so we check for nullish -- not falsy
228
- throw new Error("new_str is required for str_replace command");
229
- }
230
- createBackup(absoluteFilePath);
231
- content = fs_1.default.readFileSync(absoluteFilePath, "utf8");
232
- // Normalize newlines in both the content and search string
233
- const normalizedContent = content.replace(/\r\n/g, "\n");
234
- const normalizedOldStr = input.old_str
235
- .replace(/\\n/g, "\n")
236
- .replace(/\r\n/g, "\n");
237
- if (!normalizedContent.includes(normalizedOldStr)) {
238
- return {
239
- result: `old_str not found in file: ${filePath}`,
240
- isError: true,
241
- };
242
- }
243
- else {
244
- const escapedOldStr = escapeRegExp(normalizedOldStr);
245
- const occurences = normalizedContent.match(new RegExp(escapedOldStr, "g"));
246
- if (occurences && occurences.length > 1) {
247
- const uniqueContexts = getUniqueOccurences(content, input.old_str);
248
- if (uniqueContexts.length === 0) {
249
- return {
250
- result: `Error: old_str found ${occurences.length} times in file: ${filePath}, but no unique contexts could be identified. Try using a more specific string.`,
251
- isError: true,
252
- };
253
- }
254
- const uniqueContextsString = uniqueContexts
255
- .map(({ uniqueContext, lineNumber }, idx) => `${idx + 1}. For occurence at line number ${lineNumber}, unique context is:\n\`\`\`\n${uniqueContext}\n\`\`\`\n`)
256
- .join("\n");
257
- return {
258
- result: `Error: old_str found ${occurences.length} times in file: ${filePath}. Please use one of these unique contexts instead:\n\n${uniqueContextsString}`,
259
- isError: true,
260
- };
261
- }
262
- newContent = normalizedContent.replace(normalizedOldStr, input.new_str);
263
- fs_1.default.writeFileSync(absoluteFilePath, newContent);
264
- let strReplaceTypescriptResult = await (0, web_1.runTypescriptCompiler)(repoDir);
265
- if (!strReplaceTypescriptResult.success) {
266
- return {
267
- result: `Edits to file ${filePath} have been applied. However, type checks are failing with errors:\n${strReplaceTypescriptResult.errors.join("\n")}`,
268
- isError: true,
269
- };
270
- }
271
- else {
272
- return {
273
- result: `Edits to file ${filePath} have been applied. Type checks have also passed.`,
274
- isError: false,
275
- };
276
- }
277
- }
297
+ return fileStrReplaceExecutor({
298
+ input,
299
+ filePath,
300
+ absoluteFilePath,
301
+ repoDir,
302
+ });
278
303
  case "insert":
279
- if (input.insert_line === undefined || !input.new_str) {
280
- throw new Error("insert_line and new_str are required for insert command");
281
- }
282
- createBackup(absoluteFilePath);
283
- content = fs_1.default.readFileSync(absoluteFilePath, "utf8");
284
- lines = content.split("\n");
285
- if (input.insert_line < 1) {
286
- throw new Error("insert_line must be greater than or equal to 1 (line numbers are 1-indexed).");
287
- }
288
- if (input.insert_line > lines.length + 1) {
289
- throw new Error(`The file at ${filePath} has only ${lines.length} lines, so insert_line must be less than or equal to ${lines.length + 1}. At the maximum value of ${lines.length + 1}, you can insert at the end of the file.`);
290
- }
291
- lines.splice(input.insert_line - 1, 0, input.new_str);
292
- fs_1.default.writeFileSync(absoluteFilePath, lines.join("\n"));
293
- let insertTypescriptResult = await (0, web_1.runTypescriptCompiler)(repoDir);
294
- if (!insertTypescriptResult.success) {
295
- return {
296
- result: `Insertion in file ${filePath} was applied. However, type checks are failing with errors:\n${insertTypescriptResult.errors.join("\n")}`,
297
- isError: true,
298
- };
299
- }
300
- else {
301
- return {
302
- result: `Insertion in file ${filePath} was applied. Type checks have also passed.`,
303
- isError: false,
304
- };
305
- }
304
+ return fileInsertExecutor({
305
+ input,
306
+ filePath,
307
+ absoluteFilePath,
308
+ repoDir,
309
+ });
306
310
  case "undo_edit":
307
- if (hasBackup(absoluteFilePath)) {
308
- restoreBackup(absoluteFilePath);
309
- return {
310
- result: `Successfully restored ${filePath} from backup`,
311
- isError: false,
312
- };
313
- }
314
311
  return {
315
- result: `No backup file found for ${filePath}`,
312
+ result: `undo_edit tool is not supported.`,
316
313
  isError: true,
317
314
  };
318
315
  default:
@@ -343,7 +340,9 @@ File contents are returned with line numbers, starting from 1.
343
340
  path: zod_1.z.string().describe("The path to the file or directory to view."),
344
341
  }),
345
342
  },
346
- execute: async ({ input, repoPath, }) => {
343
+ execute: async (params) => {
344
+ const { input } = params;
345
+ const { repoPath } = params;
347
346
  return strReplaceEditorExecutor({
348
347
  input: {
349
348
  command: "view",
@@ -356,13 +355,18 @@ File contents are returned with line numbers, starting from 1.
356
355
  const fileCreateTool = {
357
356
  schema: {
358
357
  name: "fileCreateTool",
359
- description: "A tool to create a new file with given contents.",
358
+ description: `A tool to create a new file with given contents.
359
+ This tool will also create parent directories that do not exist.
360
+ For example, for path "tests/example/foo.spec.ts", the tool will create
361
+ directories "tests", "tests/example", and "tests/example/foo.spec.ts".`,
360
362
  parameters: zod_1.z.object({
361
363
  path: zod_1.z.string().describe("The path to the new file."),
362
364
  file_text: zod_1.z.string().describe("The contents of the new file."),
363
365
  }),
364
366
  },
365
- execute: async ({ input, repoPath, }) => {
367
+ execute: async (params) => {
368
+ const { input } = params;
369
+ const { repoPath } = params;
366
370
  return strReplaceEditorExecutor({
367
371
  input: {
368
372
  command: "create",
@@ -384,7 +388,9 @@ in the file. If old_str is not unique, the tool will return an error.`,
384
388
  new_str: zod_1.z.string().describe("The string to replace old_str with."),
385
389
  }),
386
390
  },
387
- execute: async ({ input, repoPath, }) => {
391
+ execute: async (params) => {
392
+ const { input } = params;
393
+ const { repoPath } = params;
388
394
  return strReplaceEditorExecutor({
389
395
  input: {
390
396
  command: "str_replace",
@@ -412,7 +418,9 @@ To insert a string at the end of the file, you should use insert_line = (total l
412
418
  new_str: zod_1.z.string().describe("The string to insert."),
413
419
  }),
414
420
  },
415
- execute: async ({ input, repoPath, }) => {
421
+ execute: async (params) => {
422
+ const { input } = params;
423
+ const { repoPath } = params;
416
424
  return strReplaceEditorExecutor({
417
425
  input: {
418
426
  command: "insert",
@@ -1,3 +1,3 @@
1
- import type { Tool } from "@empiricalrun/llm/chat";
1
+ import { Tool } from "@empiricalrun/shared-types";
2
2
  export declare const generateTestWithBrowserAgent: Tool;
3
3
  //# sourceMappingURL=test-gen-browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-gen-browser.d.ts","sourceRoot":"","sources":["../../src/tools/test-gen-browser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AA4FnD,eAAO,MAAM,4BAA4B,EAAE,IA+H1C,CAAC"}
1
+ {"version":3,"file":"test-gen-browser.d.ts","sourceRoot":"","sources":["../../src/tools/test-gen-browser.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAmGpE,eAAO,MAAM,4BAA4B,EAAE,IA0I1C,CAAC"}