@empiricalrun/test-gen 0.74.2 → 0.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/agent/base/index.d.ts +36 -0
  3. package/dist/agent/base/index.d.ts.map +1 -0
  4. package/dist/agent/base/index.js +74 -0
  5. package/dist/agent/chat/agent-loop.d.ts +4 -5
  6. package/dist/agent/chat/agent-loop.d.ts.map +1 -1
  7. package/dist/agent/chat/agent-loop.js +2 -9
  8. package/dist/agent/chat/exports.d.ts +3 -3
  9. package/dist/agent/chat/exports.d.ts.map +1 -1
  10. package/dist/agent/chat/exports.js +5 -5
  11. package/dist/agent/chat/index.d.ts.map +1 -1
  12. package/dist/agent/chat/index.js +8 -11
  13. package/dist/agent/chat/state.d.ts +2 -3
  14. package/dist/agent/chat/state.d.ts.map +1 -1
  15. package/dist/agent/chat/state.js +0 -8
  16. package/dist/agent/chat/utils.d.ts +0 -1
  17. package/dist/agent/chat/utils.d.ts.map +1 -1
  18. package/dist/agent/chat/utils.js +1 -14
  19. package/dist/file-info/adapters/github/index.d.ts +2 -2
  20. package/dist/file-info/adapters/github/index.d.ts.map +1 -1
  21. package/dist/file-info/adapters/github/index.js +3 -2
  22. package/dist/file-info/adapters/github/reader.d.ts +3 -3
  23. package/dist/file-info/adapters/github/reader.d.ts.map +1 -1
  24. package/dist/file-info/adapters/github/reader.js +17 -16
  25. package/dist/tools/commit-and-create-pr/index.js +1 -1
  26. package/dist/tools/definitions/fetch-video-analysis.d.ts +28 -0
  27. package/dist/tools/definitions/fetch-video-analysis.d.ts.map +1 -1
  28. package/dist/tools/definitions/fetch-video-analysis.js +39 -4
  29. package/dist/tools/definitions/rename-file.d.ts +3 -0
  30. package/dist/tools/definitions/rename-file.d.ts.map +1 -0
  31. package/dist/tools/definitions/rename-file.js +23 -0
  32. package/dist/tools/delete-file/index.d.ts.map +1 -1
  33. package/dist/tools/delete-file/index.js +13 -1
  34. package/dist/tools/executor/index.d.ts.map +1 -1
  35. package/dist/tools/executor/index.js +4 -3
  36. package/dist/tools/executor/utils/checkpoint.d.ts +1 -3
  37. package/dist/tools/executor/utils/checkpoint.d.ts.map +1 -1
  38. package/dist/tools/executor/utils/checkpoint.js +17 -17
  39. package/dist/tools/executor/utils/git.d.ts +9 -1
  40. package/dist/tools/executor/utils/git.d.ts.map +1 -1
  41. package/dist/tools/executor/utils/git.js +72 -2
  42. package/dist/tools/{fetch-image → fetch-file}/index.d.ts +2 -2
  43. package/dist/tools/fetch-file/index.d.ts.map +1 -0
  44. package/dist/tools/fetch-file/index.js +97 -0
  45. package/dist/tools/fetch-video-analysis/index.d.ts +3 -3
  46. package/dist/tools/fetch-video-analysis/index.d.ts.map +1 -1
  47. package/dist/tools/fetch-video-analysis/index.js +71 -22
  48. package/dist/tools/fetch-video-analysis/local-ffmpeg-client.d.ts +8 -9
  49. package/dist/tools/fetch-video-analysis/local-ffmpeg-client.d.ts.map +1 -1
  50. package/dist/tools/fetch-video-analysis/local-ffmpeg-client.js +55 -17
  51. package/dist/tools/fetch-video-analysis/open-ai.d.ts +6 -0
  52. package/dist/tools/fetch-video-analysis/open-ai.d.ts.map +1 -0
  53. package/dist/tools/fetch-video-analysis/open-ai.js +37 -0
  54. package/dist/tools/fetch-video-analysis/utils.d.ts +6 -3
  55. package/dist/tools/fetch-video-analysis/utils.d.ts.map +1 -1
  56. package/dist/tools/fetch-video-analysis/utils.js +41 -15
  57. package/dist/tools/fetch-video-analysis/video-analysis.js +1 -1
  58. package/dist/tools/file-operations/create.d.ts.map +1 -1
  59. package/dist/tools/file-operations/create.js +6 -3
  60. package/dist/tools/file-operations/insert.d.ts.map +1 -1
  61. package/dist/tools/file-operations/insert.js +6 -3
  62. package/dist/tools/file-operations/replace.d.ts.map +1 -1
  63. package/dist/tools/file-operations/replace.js +6 -3
  64. package/dist/tools/file-operations/shared/git-helper.d.ts.map +1 -1
  65. package/dist/tools/file-operations/shared/git-helper.js +1 -1
  66. package/dist/tools/file-operations/view/index.d.ts +2 -5
  67. package/dist/tools/file-operations/view/index.d.ts.map +1 -1
  68. package/dist/tools/file-operations/view/index.js +2 -22
  69. package/dist/tools/index.d.ts +1 -1
  70. package/dist/tools/index.d.ts.map +1 -1
  71. package/dist/tools/index.js +14 -4
  72. package/dist/tools/issues/update-issue.d.ts.map +1 -1
  73. package/dist/tools/issues/update-issue.js +16 -9
  74. package/dist/tools/merge-conflicts/index.js +1 -1
  75. package/dist/tools/rename-file/index.d.ts +3 -0
  76. package/dist/tools/rename-file/index.d.ts.map +1 -0
  77. package/dist/tools/rename-file/index.js +88 -0
  78. package/dist/tools/run-test.js +2 -2
  79. package/dist/tools/trace-dot-zip/index.d.ts.map +1 -1
  80. package/dist/tools/trace-dot-zip/index.js +2 -1
  81. package/dist/tools/trace-dot-zip/types.d.ts +35 -3
  82. package/dist/tools/trace-dot-zip/types.d.ts.map +1 -1
  83. package/dist/tools/trace-dot-zip/utils/network-trace.d.ts +7 -2
  84. package/dist/tools/trace-dot-zip/utils/network-trace.d.ts.map +1 -1
  85. package/dist/tools/trace-dot-zip/utils/network-trace.js +130 -10
  86. package/dist/tools/upgrade-packages/index.js +1 -1
  87. package/dist/utils/index.d.ts +0 -1
  88. package/dist/utils/index.d.ts.map +1 -1
  89. package/dist/utils/index.js +1 -3
  90. package/package.json +5 -5
  91. package/tsconfig.tsbuildinfo +1 -1
  92. package/dist/tools/fetch-image/index.d.ts.map +0 -1
  93. package/dist/tools/fetch-image/index.js +0 -63
@@ -10,15 +10,50 @@ exports.videoAnalysisSchema = zod_1.default.object({
10
10
  .string()
11
11
  .url("Must be a valid URL")
12
12
  .describe("The URL of the video to analyze."),
13
+ params: zod_1.default
14
+ .object({
15
+ fps: zod_1.default
16
+ .number()
17
+ .int()
18
+ .min(1)
19
+ .max(120)
20
+ .default(30)
21
+ .optional()
22
+ .describe("Frames per second to extract from the video (default: 30)"),
23
+ threshold: zod_1.default
24
+ .number()
25
+ .min(0)
26
+ .max(0.5)
27
+ .default(0.001)
28
+ .optional()
29
+ .describe("Deduplication threshold (fraction of pixels that may differ to consider frames identical). Lower = stricter. Default: 0.001"),
30
+ model: zod_1.default
31
+ .string()
32
+ .default("gemini-2.5-pro")
33
+ .optional()
34
+ .describe("Gemini LLM model identifier to use for analysis defaults to gemini-2.5-pro"),
35
+ featureFlag: zod_1.default
36
+ .enum([
37
+ "none",
38
+ "send-all-frames",
39
+ "separate-threshold",
40
+ "ai-based-frames",
41
+ ])
42
+ .default("none")
43
+ .optional()
44
+ .describe("Feature flag to use for analysis defaults to none"),
45
+ })
46
+ .optional(),
13
47
  });
14
48
  exports.fetchVideoAnalysis = {
15
49
  schema: {
16
50
  name: "fetchVideoAnalysis",
17
- description: `Analyzes Playwright test execution videos to identify test failures and UI issues.
51
+ description: `
52
+ Analyzes Playwright test execution videos to identify test failures and UI issues.
18
53
 
19
- **Input:** Video URL of a test execution recording, optional fps (frames per second, default: 30)
20
- **Output:** analysis summary of what happened during the test execution
21
- **Use when:** You have a failing test with a video recording and need to understand what went wrong`,
54
+ **Input:** The video URL is mandatory (supports webm, mp4). DO NOT specify params UNLESS the user has explicitly asked for a specific param value to be tested.
55
+ **Output:** analysis summary of what happened during the test execution.
56
+ **Use when:** You have a failing test with a video recording and need to understand what went wrong`,
22
57
  parameters: exports.videoAnalysisSchema,
23
58
  },
24
59
  needsBrowser: false,
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from "@empiricalrun/shared-types";
2
+ export declare const renameFileTool: ToolDefinition;
3
+ //# sourceMappingURL=rename-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rename-file.d.ts","sourceRoot":"","sources":["../../../src/tools/definitions/rename-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAgBjE,eAAO,MAAM,cAAc,EAAE,cAU5B,CAAC"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renameFileTool = void 0;
4
+ const zod_1 = require("zod");
5
+ const RenameFileInputSchema = zod_1.z.object({
6
+ oldPath: zod_1.z
7
+ .string()
8
+ .describe("The current path to the file to rename (relative to the repository root). For example, tests/foo.spec.ts"),
9
+ newPath: zod_1.z
10
+ .string()
11
+ .describe("The new path for the file (relative to the repository root). For example, tests/new-dir/bar.spec.ts"),
12
+ });
13
+ exports.renameFileTool = {
14
+ schema: {
15
+ name: "renameFile",
16
+ description: `Rename or move a file in the filesystem.
17
+ This tool will move the specified file from oldPath to newPath.
18
+ The operation can rename the file, move it to a different directory, or both.`,
19
+ parameters: RenameFileInputSchema,
20
+ },
21
+ needsBrowser: false,
22
+ isInlineTool: false,
23
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/delete-file/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,IAAI,EAEL,MAAM,4BAA4B,CAAC;AAOpC,eAAO,MAAM,cAAc,EAAE,IAiE5B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/delete-file/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,IAAI,EAEL,MAAM,4BAA4B,CAAC;AASpC,eAAO,MAAM,cAAc,EAAE,IA4E5B,CAAC"}
@@ -4,8 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.deleteFileTool = void 0;
7
+ const child_process_1 = require("child_process");
7
8
  const promises_1 = require("fs/promises");
8
9
  const path_1 = __importDefault(require("path"));
10
+ const web_1 = require("../../bin/utils/platform/web");
9
11
  const delete_file_1 = require("../definitions/delete-file");
10
12
  const git_1 = require("../executor/utils/git");
11
13
  exports.deleteFileTool = {
@@ -38,10 +40,12 @@ exports.deleteFileTool = {
38
40
  };
39
41
  }
40
42
  await (0, promises_1.unlink)(filePath);
43
+ // Stage the deletion
44
+ (0, child_process_1.execSync)(`git add "${input.path}"`, { cwd: repoPath });
41
45
  // Collect git patch artifact after deletion
42
46
  if (collectArtifacts) {
43
47
  try {
44
- const gitPatch = (0, git_1.getGitDiff)(input.path, repoPath);
48
+ const gitPatch = (0, git_1.getGitDiffStaged)(input.path, repoPath);
45
49
  if (gitPatch.trim()) {
46
50
  const patchArtifact = {
47
51
  name: `${path_1.default.basename(input.path, path_1.default.extname(input.path))}_delete.patch`,
@@ -55,6 +59,14 @@ exports.deleteFileTool = {
55
59
  console.warn("Failed to create git patch artifact:", error);
56
60
  }
57
61
  }
62
+ // Run TypeScript compilation check
63
+ const tscResult = await (0, web_1.runTypescriptCompiler)(repoPath);
64
+ if (!tscResult.success) {
65
+ return {
66
+ result: `File ${input.path} has been deleted. However, type checks are failing with errors:\n\n${tscResult.errors.join("\n")}`,
67
+ isError: true,
68
+ };
69
+ }
58
70
  return {
59
71
  isError: false,
60
72
  result: `Successfully deleted file: ${input.path}`,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/executor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAEL,eAAe,EACf,mBAAmB,EACnB,WAAW,EACZ,MAAM,4BAA4B,CAAC;AAyBpC,qBAAa,YAAY;IACvB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAM;IAChD,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAEjC,EACV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,SAAS,EACT,KAAK,EACL,YAAY,EACZ,oBAAyB,GAC1B,EAAE;QACD,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;QACpC,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,mBAAmB,CAAC;QAC/B,KAAK,CAAC,EAAE,WAAW,CAAC;QACpB,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC/C;IA6CK,OAAO,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;CAqEnE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/executor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAEL,eAAe,EACf,mBAAmB,EACnB,WAAW,EACZ,MAAM,4BAA4B,CAAC;AA0BpC,qBAAa,YAAY;IACvB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAM;IAChD,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAEjC,EACV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,SAAS,EACT,KAAK,EACL,YAAY,EACZ,oBAAyB,GAC1B,EAAE;QACD,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;QACpC,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,mBAAmB,CAAC;QAC/B,KAAK,CAAC,EAAE,WAAW,CAAC;QACpB,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC/C;IA8CK,OAAO,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;CAoEnE"}
@@ -6,7 +6,7 @@ const commit_and_create_pr_1 = require("../commit-and-create-pr");
6
6
  const delete_file_1 = require("../delete-file");
7
7
  const diagnosis_fetcher_1 = require("../diagnosis-fetcher");
8
8
  const download_build_1 = require("../download-build");
9
- const fetch_image_1 = require("../fetch-image");
9
+ const fetch_file_1 = require("../fetch-file");
10
10
  const fetch_last_successful_test_run_1 = require("../fetch-last-successful-test-run");
11
11
  const fetch_video_analysis_1 = require("../fetch-video-analysis");
12
12
  const file_operations_1 = require("../file-operations");
@@ -15,6 +15,7 @@ const issues_1 = require("../issues");
15
15
  const list_environments_1 = require("../list-environments");
16
16
  const list_tests_and_projects_1 = require("../list-tests-and-projects");
17
17
  const merge_conflicts_1 = require("../merge-conflicts");
18
+ const rename_file_1 = require("../rename-file");
18
19
  const run_test_1 = require("../run-test");
19
20
  const test_gen_browser_1 = require("../test-gen-browser");
20
21
  const test_run_fetcher_1 = require("../test-run-fetcher");
@@ -54,9 +55,10 @@ class ToolExecutor {
54
55
  issues_1.createIssueTool,
55
56
  download_build_1.downloadBuildTool,
56
57
  upgrade_packages_1.upgradePackagesTool,
57
- fetch_image_1.fetchImageTool,
58
+ fetch_file_1.fetchFileTool,
58
59
  fetch_video_analysis_1.fetchVideoAnalysis,
59
60
  delete_file_1.deleteFileTool,
61
+ rename_file_1.renameFileTool,
60
62
  trace_dot_zip_1.traceDotZipTool,
61
63
  view_failed_test_run_report_1.viewFailedTestRunReportTool,
62
64
  issues_1.updateIssueTool,
@@ -129,7 +131,6 @@ class ToolExecutor {
129
131
  }
130
132
  }
131
133
  (0, checkpoint_1.createCommitCheckpoint)({
132
- toolCalls,
133
134
  branchName: this.branchName,
134
135
  repoPath: this.repoPath,
135
136
  });
@@ -1,6 +1,4 @@
1
- import { PendingToolCall } from "@empiricalrun/llm/chat";
2
- export declare function createCommitCheckpoint({ toolCalls, branchName, repoPath, }: {
3
- toolCalls: PendingToolCall[];
1
+ export declare function createCommitCheckpoint({ branchName, repoPath, }: {
4
2
  branchName: string;
5
3
  repoPath: string;
6
4
  }): void;
@@ -1 +1 @@
1
- {"version":3,"file":"checkpoint.d.ts","sourceRoot":"","sources":["../../../../src/tools/executor/utils/checkpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAKzD,wBAAgB,sBAAsB,CAAC,EACrC,SAAS,EACT,UAAU,EACV,QAAQ,GACT,EAAE;IACD,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,QA0BA"}
1
+ {"version":3,"file":"checkpoint.d.ts","sourceRoot":"","sources":["../../../../src/tools/executor/utils/checkpoint.ts"],"names":[],"mappings":"AAIA,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,QAAQ,GACT,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,QAsBA"}
@@ -3,24 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createCommitCheckpoint = createCommitCheckpoint;
4
4
  const child_process_1 = require("child_process");
5
5
  const git_1 = require("./git");
6
- function createCommitCheckpoint({ toolCalls, branchName, repoPath, }) {
7
- const filesChanged = (0, git_1.getFilesChanged)(repoPath);
8
- const toolsWithUpdatedFiles = toolCalls
9
- .filter((tc) => "path" in tc.input &&
10
- tc.input.path &&
11
- filesChanged.includes(tc.input.path))
12
- .map((toolCall) => ({
13
- name: toolCall.name,
14
- path: toolCall.input.path,
15
- }));
16
- const filesToCommit = toolsWithUpdatedFiles.map((tool) => tool.path);
17
- if (toolsWithUpdatedFiles.length > 0) {
18
- let commitMessage = `${toolsWithUpdatedFiles.map((tool) => `${tool.name} on ${tool.path}`).join(", ")}`;
19
- (0, git_1.commitFilesAsBotUser)({
20
- commitMessage,
21
- files: filesToCommit,
22
- repoPath,
6
+ function createCommitCheckpoint({ branchName, repoPath, }) {
7
+ const filesChanged = (0, git_1.getStagedFiles)(repoPath);
8
+ if (filesChanged.length > 0) {
9
+ const commitMessages = filesChanged.map((change) => {
10
+ switch (change.type) {
11
+ case "added":
12
+ return `added ${change.path}`;
13
+ case "modified":
14
+ return `modified ${change.path}`;
15
+ case "removed":
16
+ return `removed ${change.path}`;
17
+ case "renamed":
18
+ return `renamed ${change.oldPath} ${change.path}`;
19
+ }
23
20
  });
21
+ const commitMessage = commitMessages.join(", ");
22
+ // Files are already staged by individual tools, just commit
23
+ (0, git_1.commitAsBotUser)(commitMessage, repoPath);
24
24
  (0, child_process_1.execSync)(`git push origin ${branchName}`, { cwd: repoPath });
25
25
  }
26
26
  }
@@ -1,15 +1,23 @@
1
1
  import { IDashboardAPIClient } from "@empiricalrun/shared-types";
2
2
  export declare function getGitDiff(filepath: string, cwd: string): string;
3
+ export declare function getGitDiffStaged(filepath: string, cwd: string): string;
4
+ export declare function getGitDiffForStagedOrUnstaged(filepath: string, cwd: string): string;
3
5
  export declare function getGitDiffForNewFile(filepath: string, cwd: string): string;
4
6
  export declare function checkoutBranch(branchName: string, cwd: string): Promise<void>;
5
7
  export declare function commitAsBotUser(commitMessage: string, cwd: string): boolean;
6
8
  export declare function getCurrentBranchName(repoPath: string): Promise<string>;
7
9
  export declare function pullBranch(branchName: string, cwd: string): Promise<void>;
8
- export declare function commitFilesAsBotUser({ commitMessage, files, repoPath, }: {
10
+ export declare function stageAndCommitFilesAsBotUser({ commitMessage, files, repoPath, }: {
9
11
  commitMessage: string;
10
12
  files: string[];
11
13
  repoPath: string;
12
14
  }): void;
15
+ export type FileChange = {
16
+ type: "added" | "modified" | "removed" | "renamed";
17
+ path: string;
18
+ oldPath?: string;
19
+ };
20
+ export declare function getStagedFiles(cwd: string): FileChange[];
13
21
  export declare function getFilesChanged(cwd: string): string[];
14
22
  export declare function mergePullRequest({ repoName, prNumber, apiClient, }: {
15
23
  repoName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../../src/tools/executor/utils/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAMjE,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAe1E;AAED,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,iBAMnE;AAED,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,WAcjE;AAED,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,mBAO1D;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,iBAE/D;AAED,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,KAAK,EACL,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,QAGA;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,YAU1C;AAED,wBAAsB,gBAAgB,CAAC,EACrC,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,mBAAmB,CAAC;CAChC,oBA8BA"}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../../src/tools/executor/utils/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAMjE,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMtE;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GACV,MAAM,CAaR;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAe1E;AAED,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,iBAMnE;AAED,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,WAcjE;AAED,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,mBAO1D;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,iBAE/D;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,aAAa,EACb,KAAK,EACL,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,QAGA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CA6CxD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAUrD;AAED,wBAAsB,gBAAgB,CAAC,EACrC,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,mBAAmB,CAAC;CAChC,oBA8BA"}
@@ -1,12 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getGitDiff = getGitDiff;
4
+ exports.getGitDiffStaged = getGitDiffStaged;
5
+ exports.getGitDiffForStagedOrUnstaged = getGitDiffForStagedOrUnstaged;
4
6
  exports.getGitDiffForNewFile = getGitDiffForNewFile;
5
7
  exports.checkoutBranch = checkoutBranch;
6
8
  exports.commitAsBotUser = commitAsBotUser;
7
9
  exports.getCurrentBranchName = getCurrentBranchName;
8
10
  exports.pullBranch = pullBranch;
9
- exports.commitFilesAsBotUser = commitFilesAsBotUser;
11
+ exports.stageAndCommitFilesAsBotUser = stageAndCommitFilesAsBotUser;
12
+ exports.getStagedFiles = getStagedFiles;
10
13
  exports.getFilesChanged = getFilesChanged;
11
14
  exports.mergePullRequest = mergePullRequest;
12
15
  const child_process_1 = require("child_process");
@@ -19,6 +22,27 @@ function getGitDiff(filepath, cwd) {
19
22
  });
20
23
  return diff;
21
24
  }
25
+ function getGitDiffStaged(filepath, cwd) {
26
+ const diff = (0, child_process_1.execSync)(`git diff --cached ${filepath}`, {
27
+ encoding: "utf-8",
28
+ cwd,
29
+ });
30
+ return diff;
31
+ }
32
+ function getGitDiffForStagedOrUnstaged(filepath, cwd) {
33
+ // First try to get staged changes (index vs HEAD)
34
+ try {
35
+ const stagedDiff = getGitDiffStaged(filepath, cwd);
36
+ if (stagedDiff.trim()) {
37
+ return stagedDiff;
38
+ }
39
+ }
40
+ catch {
41
+ // Fall through to try unstaged diff
42
+ }
43
+ // If no staged changes, try unstaged changes (working dir vs index)
44
+ return getGitDiff(filepath, cwd);
45
+ }
22
46
  function getGitDiffForNewFile(filepath, cwd) {
23
47
  try {
24
48
  const diff = (0, child_process_1.execSync)(`git diff --no-index /dev/null ${filepath}`, {
@@ -67,10 +91,56 @@ async function getCurrentBranchName(repoPath) {
67
91
  async function pullBranch(branchName, cwd) {
68
92
  (0, child_process_1.execSync)(`git pull origin ${branchName}`, { cwd });
69
93
  }
70
- function commitFilesAsBotUser({ commitMessage, files, repoPath, }) {
94
+ function stageAndCommitFilesAsBotUser({ commitMessage, files, repoPath, }) {
71
95
  (0, child_process_1.execSync)(`git add ${files.join(" ")}`, { cwd: repoPath });
72
96
  commitAsBotUser(commitMessage, repoPath);
73
97
  }
98
+ function getStagedFiles(cwd) {
99
+ const output = (0, child_process_1.execSync)("git diff --cached --name-status", {
100
+ cwd,
101
+ }).toString();
102
+ const filesChanged = [];
103
+ output.split("\n").forEach((line) => {
104
+ const trimmed = line.trim();
105
+ if (!trimmed)
106
+ return;
107
+ const parts = trimmed.split("\t");
108
+ if (parts.length < 2)
109
+ return;
110
+ const status = parts[0];
111
+ const filePath = parts[1];
112
+ if (status.startsWith("R")) {
113
+ // Handle rename: "R100 old_file new_file"
114
+ const newFilePath = parts[2];
115
+ if (newFilePath) {
116
+ filesChanged.push({
117
+ type: "renamed",
118
+ path: newFilePath,
119
+ oldPath: filePath,
120
+ });
121
+ }
122
+ }
123
+ else if (status === "A") {
124
+ filesChanged.push({
125
+ type: "added",
126
+ path: filePath,
127
+ });
128
+ }
129
+ else if (status === "M") {
130
+ filesChanged.push({
131
+ type: "modified",
132
+ path: filePath,
133
+ });
134
+ }
135
+ else if (status === "D") {
136
+ filesChanged.push({
137
+ type: "removed",
138
+ path: filePath,
139
+ });
140
+ }
141
+ });
142
+ return filesChanged;
143
+ }
74
144
  function getFilesChanged(cwd) {
75
145
  const output = (0, child_process_1.execSync)("git status --porcelain --untracked-files", {
76
146
  cwd,
@@ -1,12 +1,12 @@
1
1
  import { Tool } from "@empiricalrun/shared-types";
2
2
  import { z } from "zod";
3
- declare const fetchImageSchema: z.ZodObject<{
3
+ declare const fetchFileSchema: z.ZodObject<{
4
4
  url: z.ZodString;
5
5
  }, "strip", z.ZodTypeAny, {
6
6
  url: string;
7
7
  }, {
8
8
  url: string;
9
9
  }>;
10
- export declare const fetchImageTool: Tool<z.infer<typeof fetchImageSchema>>;
10
+ export declare const fetchFileTool: Tool<z.infer<typeof fetchFileSchema>>;
11
11
  export {};
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/fetch-file/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EAGL,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,QAAA,MAAM,eAAe;;;;;;EAEnB,CAAC;AAEH,eAAO,MAAM,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAyF/D,CAAC"}
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchFileTool = void 0;
4
+ const zod_1 = require("zod");
5
+ const SUPPORTED_IMAGE_TYPES = [
6
+ "image/png",
7
+ "image/jpeg",
8
+ "image/gif",
9
+ "image/webp",
10
+ ];
11
+ const SUPPORTED_TEXT_TYPES = ["text/markdown", "text/plain"];
12
+ const fetchFileSchema = zod_1.z.object({
13
+ url: zod_1.z.string(),
14
+ });
15
+ exports.fetchFileTool = {
16
+ schema: {
17
+ name: "fetchFile",
18
+ description: `Use this tool to fetch file data from any valid URL that responds with an image (PNG, JPEG, GIF, WebP) or markdown file.
19
+ For images, it returns the image in base64 format for you to view or analyze. For markdown files, it returns the text content for you to read and process.
20
+
21
+ ## Caveats
22
+ 1. This will not work to fetch markdown files from a repo due to access control issues. Use file view tools for that.
23
+
24
+ ## Scenarios to use fetchFile
25
+ 1. Understand a test report (for the runTest tool) by fetching screenshots and error context files (.md) which contain the accessibility tree of the page at the
26
+ time of test failures. Both of these are available in the attachments section of the test report from the runTest tool call.
27
+
28
+ 2. While adding new tests, if the user message contains image URLs, use this tool to fetch the images, and understand the steps that the user is looking to cover..`,
29
+ parameters: fetchFileSchema,
30
+ },
31
+ needsBrowser: false,
32
+ isInlineTool: true,
33
+ execute: async ({ input }) => {
34
+ const { url } = input;
35
+ try {
36
+ const response = await fetch(url);
37
+ if (!response.ok) {
38
+ console.error(`Failed to fetch file from ${url}: ${response.statusText}`);
39
+ return {
40
+ result: `Failed to fetch file from ${url}: ${response.statusText}`,
41
+ isError: true,
42
+ };
43
+ }
44
+ const contentType = response.headers.get("content-type");
45
+ if (!contentType) {
46
+ const errorMessage = `No content type header found. URL must return an image or markdown file.`;
47
+ return {
48
+ result: errorMessage,
49
+ isError: true,
50
+ };
51
+ }
52
+ const isImage = SUPPORTED_IMAGE_TYPES.some((type) => contentType.startsWith(type));
53
+ const isText = SUPPORTED_TEXT_TYPES.some((type) => contentType.startsWith(type));
54
+ if (!isImage && !isText) {
55
+ const errorMessage = `Invalid content type: ${contentType}. URL must return an image (PNG, JPEG, GIF, WebP) or markdown file.`;
56
+ console.error(errorMessage);
57
+ return {
58
+ result: errorMessage,
59
+ isError: true,
60
+ };
61
+ }
62
+ if (isImage) {
63
+ const buffer = await response.arrayBuffer();
64
+ const base64 = Buffer.from(buffer).toString("base64");
65
+ return {
66
+ result: [
67
+ {
68
+ type: contentType,
69
+ base64Data: base64,
70
+ },
71
+ ],
72
+ isError: false,
73
+ };
74
+ }
75
+ else {
76
+ // Handle text/markdown files
77
+ const text = await response.text();
78
+ return {
79
+ result: [
80
+ {
81
+ type: "text",
82
+ text: text,
83
+ },
84
+ ],
85
+ isError: false,
86
+ };
87
+ }
88
+ }
89
+ catch (error) {
90
+ console.error("Error fetching file", error);
91
+ return {
92
+ result: `Error fetching file: ${error}`,
93
+ isError: true,
94
+ };
95
+ }
96
+ },
97
+ };
@@ -1,5 +1,5 @@
1
- import { Tool } from "@empiricalrun/shared-types";
2
- import { z } from "zod";
3
- import { videoAnalysisSchema } from "../definitions/fetch-video-analysis";
1
+ import type { Tool } from "@empiricalrun/shared-types";
2
+ import type { z } from "zod";
3
+ import { type videoAnalysisSchema } from "../definitions/fetch-video-analysis";
4
4
  export declare const fetchVideoAnalysis: Tool<z.infer<typeof videoAnalysisSchema>>;
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/fetch-video-analysis/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAqB,MAAM,4BAA4B,CAAC;AAErE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAEL,mBAAmB,EACpB,MAAM,qCAAqC,CAAC;AAK7C,eAAO,MAAM,kBAAkB,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAiHxE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/fetch-video-analysis/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,IAAI,EAIL,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,qCAAqC,CAAC;AAwC7C,eAAO,MAAM,kBAAkB,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAoJxE,CAAC"}
@@ -4,20 +4,45 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.fetchVideoAnalysis = void 0;
7
- const crypto_1 = __importDefault(require("crypto"));
7
+ const node_crypto_1 = __importDefault(require("node:crypto"));
8
+ const node_path_1 = __importDefault(require("node:path"));
8
9
  const fetch_video_analysis_1 = require("../definitions/fetch-video-analysis");
9
10
  const local_ffmpeg_client_1 = require("./local-ffmpeg-client");
10
11
  const utils_1 = require("./utils");
11
12
  const video_analysis_1 = require("./video-analysis");
13
+ function getVideoAnalysisParams(params) {
14
+ return {
15
+ model: params?.model || "gemini-2.5-pro",
16
+ fps: params?.fps ?? 30,
17
+ threshold: params?.threshold ?? 0.001,
18
+ featureFlag: params?.featureFlag ?? "send-all-frames",
19
+ };
20
+ }
21
+ function hashObject(obj) {
22
+ const sortedObj = Object.keys(obj)
23
+ .sort()
24
+ .reduce((acc, key) => {
25
+ acc[key] = obj[key];
26
+ return acc;
27
+ }, {});
28
+ const json = JSON.stringify(sortedObj);
29
+ return node_crypto_1.default
30
+ .createHash("sha256")
31
+ .update(json)
32
+ .digest("hex")
33
+ .substring(0, 16);
34
+ }
12
35
  exports.fetchVideoAnalysis = {
13
36
  ...fetch_video_analysis_1.fetchVideoAnalysis,
14
37
  execute: async ({ input, trace, }) => {
15
38
  const { videoUrl } = input;
16
- const videoUrlHash = crypto_1.default
17
- .createHash("sha256")
18
- .update(videoUrl)
19
- .digest("hex")
20
- .substring(0, 16);
39
+ const params = getVideoAnalysisParams(input.params);
40
+ const videoUrlHash = hashObject({
41
+ videoUrl,
42
+ ...params,
43
+ });
44
+ const { model: selectedModel, fps: effectiveFps, threshold: effectiveThreshold, featureFlag: effectiveFeatureFlag, } = params;
45
+ const WORKING_DIR = `./video-analysis-artifacts/${videoUrlHash}`;
21
46
  const R2_BASE_URL = `https://video-analysis.empirical.run/${videoUrlHash}/`;
22
47
  const videoAnalysisSpan = trace?.span({
23
48
  name: "video-analysis",
@@ -34,36 +59,60 @@ exports.fetchVideoAnalysis = {
34
59
  }
35
60
  const processingSpan = videoAnalysisSpan?.span({
36
61
  name: "ffmpeg-processing",
37
- input: { videoUrl },
62
+ input: {
63
+ videoUrl,
64
+ fps: effectiveFps,
65
+ threshold: effectiveThreshold,
66
+ },
38
67
  });
39
68
  try {
40
- const { totalFramesCount, uniqueFramesCount, uniqueFrames } = await ffmpegClient.extractVideoFrames(videoUrl, "./temp");
69
+ const extractionResult = await ffmpegClient.extractVideoFrames(videoUrl, WORKING_DIR, {
70
+ fps: effectiveFps,
71
+ threshold: effectiveThreshold,
72
+ });
73
+ const { totalFramesCount, uniqueFrames } = extractionResult;
41
74
  processingSpan?.end({
42
75
  output: {
43
76
  totalFramesCount,
44
- uniqueFramesCount,
77
+ uniqueFramesCount: uniqueFrames.length,
45
78
  },
46
79
  });
47
80
  console.log(`[video-analysis] Analyzing ${uniqueFrames.length} frames with LLM`);
48
- const videoInfoWithoutAnalysis = {
81
+ const outputZipPath = node_path_1.default.join(WORKING_DIR, "frames.zip");
82
+ const zipUploadPromise = (0, utils_1.zipAndUploadFramesToR2)(uniqueFrames, outputZipPath, videoUrlHash).catch((error) => {
83
+ throw error; // Re-throw to maintain error in Promise.all
84
+ });
85
+ const { analysis: llmAnalysis, usage } = await (0, video_analysis_1.analyzeFramesWithLLM)(uniqueFrames.map((frame) => frame.image), videoAnalysisSpan, selectedModel);
86
+ console.log(`[video-analysis] Finished Analyzing ${uniqueFrames.length} frames with LLM`);
87
+ const videoInfo = {
49
88
  total_frames_count: totalFramesCount,
50
- unique_frames_count: uniqueFramesCount,
89
+ unique_frames_count: uniqueFrames.length,
51
90
  video_url: videoUrl,
52
- video_url_hash: videoUrlHash,
91
+ analysis_id: videoUrlHash,
53
92
  created_at: new Date().toISOString(),
93
+ params: {
94
+ fps: effectiveFps,
95
+ threshold: effectiveThreshold,
96
+ model: selectedModel,
97
+ featureFlag: effectiveFeatureFlag,
98
+ },
99
+ usage,
100
+ langfuse_trace_id: trace?.id || undefined,
101
+ frames_zip_url: `${R2_BASE_URL}frames.zip`,
102
+ analysis: llmAnalysis,
54
103
  };
55
- const framesUploadPromise = (0, utils_1.uploadFramesToR2)(videoInfoWithoutAnalysis, uniqueFrames, R2_BASE_URL);
56
- const { analysis: llmAnalysis, usage } = await (0, video_analysis_1.analyzeFramesWithLLM)(uniqueFrames.map((frame) => frame.image), videoAnalysisSpan, "gemini-2.5-pro");
57
- console.log(`[video-analysis] Finished Analyzing ${uniqueFrames.length} frames with LLM`);
58
- const videoInfo = {
59
- ...videoInfoWithoutAnalysis,
60
- llm_analysis: llmAnalysis,
61
- video_url_hash: videoUrlHash,
104
+ await Promise.all([
105
+ zipUploadPromise,
106
+ (0, utils_1.uploadSummaryToR2)(videoInfo, R2_BASE_URL),
107
+ ]);
108
+ await (0, utils_1.safeCleanupDirectory)(WORKING_DIR, "video-analysis-cleanup");
109
+ const toolResult = {
110
+ video_url: videoUrl,
111
+ analysis: llmAnalysis,
112
+ analysis_id: videoUrlHash,
62
113
  };
63
- const uniqueFramesWithUrls = await framesUploadPromise;
64
- await (0, utils_1.uploadAnalysisToR2)(videoInfo, uniqueFramesWithUrls, R2_BASE_URL);
65
114
  return {
66
- result: JSON.stringify(videoInfo, null, 2),
115
+ result: JSON.stringify(toolResult, null, 2),
67
116
  isError: false,
68
117
  usage,
69
118
  };