@empiricalrun/test-gen 0.75.0 → 0.77.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.
- package/CHANGELOG.md +48 -0
- package/dist/agent/base/index.d.ts +32 -21
- package/dist/agent/base/index.d.ts.map +1 -1
- package/dist/agent/base/index.js +100 -57
- package/dist/agent/browsing/run.d.ts +1 -2
- package/dist/agent/browsing/run.d.ts.map +1 -1
- package/dist/agent/browsing/run.js +3 -9
- package/dist/agent/browsing/utils.d.ts +2 -9
- package/dist/agent/browsing/utils.d.ts.map +1 -1
- package/dist/agent/browsing/utils.js +5 -109
- package/dist/agent/chat/agent-loop.d.ts +8 -7
- package/dist/agent/chat/agent-loop.d.ts.map +1 -1
- package/dist/agent/chat/agent-loop.js +7 -18
- package/dist/agent/chat/exports.d.ts +9 -6
- package/dist/agent/chat/exports.d.ts.map +1 -1
- package/dist/agent/chat/exports.js +11 -13
- package/dist/agent/chat/index.d.ts +6 -10
- package/dist/agent/chat/index.d.ts.map +1 -1
- package/dist/agent/chat/index.js +117 -196
- package/dist/agent/chat/models.d.ts +0 -2
- package/dist/agent/chat/models.d.ts.map +1 -1
- package/dist/agent/chat/models.js +12 -26
- package/dist/agent/chat/prompt/pw-utils-docs.d.ts +1 -1
- package/dist/agent/chat/prompt/pw-utils-docs.d.ts.map +1 -1
- package/dist/agent/chat/prompt/pw-utils-docs.js +52 -0
- package/dist/agent/chat/prompt/repo.d.ts.map +1 -1
- package/dist/agent/chat/prompt/repo.js +11 -22
- package/dist/agent/chat/prompt/test-case-def.d.ts +2 -0
- package/dist/agent/chat/prompt/test-case-def.d.ts.map +1 -0
- package/dist/agent/chat/prompt/test-case-def.js +44 -0
- package/dist/agent/chat/state.d.ts +8 -8
- package/dist/agent/chat/state.d.ts.map +1 -1
- package/dist/agent/chat/state.js +17 -47
- package/dist/agent/chat/utils.d.ts +4 -5
- package/dist/agent/chat/utils.d.ts.map +1 -1
- package/dist/agent/chat/utils.js +15 -9
- package/dist/agent/cli.d.ts +11 -0
- package/dist/agent/cli.d.ts.map +1 -0
- package/dist/agent/cli.js +213 -0
- package/dist/agent/code-review/executor/index.d.ts +5 -0
- package/dist/agent/code-review/executor/index.d.ts.map +1 -0
- package/dist/agent/code-review/executor/index.js +13 -0
- package/dist/agent/code-review/index.d.ts +12 -0
- package/dist/agent/code-review/index.d.ts.map +1 -0
- package/dist/agent/code-review/index.js +159 -0
- package/dist/agent/code-review/parser.d.ts +5 -0
- package/dist/agent/code-review/parser.d.ts.map +1 -0
- package/dist/agent/code-review/parser.js +70 -0
- package/dist/agent/code-review/types.d.ts +36 -0
- package/dist/agent/code-review/types.d.ts.map +1 -0
- package/dist/agent/code-review/types.js +13 -0
- package/dist/agent/cua/index.d.ts.map +1 -1
- package/dist/agent/cua/index.js +18 -2
- package/dist/agent/cua/model.d.ts.map +1 -1
- package/dist/agent/cua/model.js +4 -1
- package/dist/agent/cua/pw-codegen/pw-pause/index.d.ts.map +1 -1
- package/dist/agent/index.d.ts +10 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +19 -0
- package/dist/agent/triage/index.d.ts +7 -0
- package/dist/agent/triage/index.d.ts.map +1 -0
- package/dist/agent/triage/index.js +103 -0
- package/dist/agent/video-analysis/executor/index.d.ts +5 -0
- package/dist/agent/video-analysis/executor/index.d.ts.map +1 -0
- package/dist/agent/video-analysis/executor/index.js +10 -0
- package/dist/agent/video-analysis/index.d.ts +7 -0
- package/dist/agent/video-analysis/index.d.ts.map +1 -0
- package/dist/agent/video-analysis/index.js +60 -0
- package/dist/artifacts/index.d.ts +1 -1
- package/dist/artifacts/index.d.ts.map +1 -1
- package/dist/artifacts/index.js +3 -1
- package/dist/artifacts/utils.d.ts.map +1 -1
- package/dist/bin/index.js +68 -23
- package/dist/constants/index.d.ts +14 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +33 -1
- package/dist/file/server.d.ts +1 -3
- package/dist/file/server.d.ts.map +1 -1
- package/dist/file/server.js +0 -13
- package/dist/file-info/adapters/file-system/index.d.ts.map +1 -1
- package/dist/file-info/adapters/file-system/reader.d.ts.map +1 -1
- package/dist/file-info/adapters/file-system/reader.js +8 -1
- package/dist/file-info/adapters/github/index.d.ts.map +1 -1
- package/dist/file-info/adapters/github/index.js +1 -2
- package/dist/file-info/adapters/github/reader.d.ts +4 -9
- package/dist/file-info/adapters/github/reader.d.ts.map +1 -1
- package/dist/file-info/adapters/github/reader.js +166 -134
- package/dist/index.d.ts.map +1 -1
- package/dist/tools/analyse-video/index.d.ts +5 -0
- package/dist/tools/analyse-video/index.d.ts.map +1 -0
- package/dist/tools/analyse-video/index.js +50 -0
- package/dist/tools/create-pull-request/index.d.ts.map +1 -0
- package/dist/tools/{definitions/commit-and-create-pr.js → create-pull-request/index.js} +28 -1
- package/dist/tools/create-pull-request/utils.d.ts +21 -0
- package/dist/tools/create-pull-request/utils.d.ts.map +1 -0
- package/dist/tools/create-pull-request/utils.js +83 -0
- package/dist/tools/definitions/{fetch-video-analysis.d.ts → analyse-video.d.ts} +17 -12
- package/dist/tools/definitions/analyse-video.d.ts.map +1 -0
- package/dist/tools/definitions/analyse-video.js +60 -0
- package/dist/tools/definitions/review-pull-request.d.ts +3 -0
- package/dist/tools/definitions/review-pull-request.d.ts.map +1 -0
- package/dist/tools/definitions/review-pull-request.js +16 -0
- package/dist/tools/definitions/str_replace_editor.d.ts +1 -0
- package/dist/tools/definitions/str_replace_editor.d.ts.map +1 -1
- package/dist/tools/definitions/str_replace_editor.js +4 -1
- package/dist/tools/definitions/test-gen-browser.d.ts +0 -3
- package/dist/tools/definitions/test-gen-browser.d.ts.map +1 -1
- package/dist/tools/definitions/test-gen-browser.js +33 -8
- package/dist/tools/delete-file/index.d.ts.map +1 -1
- package/dist/tools/delete-file/index.js +1 -19
- package/dist/tools/executor/base.d.ts +32 -0
- package/dist/tools/executor/base.d.ts.map +1 -0
- package/dist/tools/executor/base.js +114 -0
- package/dist/tools/executor/index.d.ts +3 -22
- package/dist/tools/executor/index.d.ts.map +1 -1
- package/dist/tools/executor/index.js +13 -92
- package/dist/tools/executor/utils/checkpoint.d.ts +1 -1
- package/dist/tools/executor/utils/checkpoint.d.ts.map +1 -1
- package/dist/tools/executor/utils/checkpoint.js +6 -2
- package/dist/tools/executor/utils/git.d.ts +2 -2
- package/dist/tools/executor/utils/git.d.ts.map +1 -1
- package/dist/tools/executor/utils/git.js +7 -3
- package/dist/tools/executor/utils/index.d.ts.map +1 -1
- package/dist/tools/executor/utils/index.js +1 -1
- package/dist/tools/fetch-session-diff/index.d.ts +3 -0
- package/dist/tools/fetch-session-diff/index.d.ts.map +1 -0
- package/dist/tools/fetch-session-diff/index.js +46 -0
- package/dist/tools/file-operations/create.d.ts.map +1 -1
- package/dist/tools/file-operations/create.js +1 -4
- package/dist/tools/file-operations/index.d.ts +2 -1
- package/dist/tools/file-operations/index.d.ts.map +1 -1
- package/dist/tools/file-operations/index.js +4 -1
- package/dist/tools/file-operations/insert.d.ts +1 -2
- package/dist/tools/file-operations/insert.d.ts.map +1 -1
- package/dist/tools/file-operations/insert.js +1 -4
- package/dist/tools/file-operations/replace.d.ts.map +1 -1
- package/dist/tools/file-operations/replace.js +1 -4
- package/dist/tools/grep/index.d.ts.map +1 -1
- package/dist/tools/grep/index.js +18 -11
- package/dist/tools/index.d.ts +28 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +52 -33
- package/dist/tools/merge-conflicts/index.d.ts.map +1 -1
- package/dist/tools/merge-conflicts/index.js +1 -1
- package/dist/tools/rename-file/index.js +1 -1
- package/dist/tools/review-pull-request/index.d.ts +3 -0
- package/dist/tools/review-pull-request/index.d.ts.map +1 -0
- package/dist/tools/review-pull-request/index.js +89 -0
- package/dist/tools/run-test.d.ts.map +1 -1
- package/dist/tools/run-test.js +25 -3
- package/dist/tools/test-gen-browser.d.ts.map +1 -1
- package/dist/tools/test-gen-browser.js +51 -47
- package/dist/tools/test-run-fetcher/index.d.ts.map +1 -1
- package/dist/tools/test-run-fetcher/index.js +4 -14
- package/dist/tools/utils/urls.d.ts +5 -0
- package/dist/tools/utils/urls.d.ts.map +1 -0
- package/dist/tools/utils/urls.js +19 -0
- package/dist/tools/view-failed-test-run-report/index.d.ts.map +1 -1
- package/dist/tools/view-failed-test-run-report/index.js +3 -15
- package/dist/utils/artifact-paths.d.ts +20 -0
- package/dist/utils/artifact-paths.d.ts.map +1 -0
- package/dist/utils/artifact-paths.js +16 -0
- package/dist/utils/dedup-image-fs.d.ts +2 -16
- package/dist/utils/dedup-image-fs.d.ts.map +1 -1
- package/dist/utils/dedup-image-fs.js +12 -16
- package/dist/utils/dedup-image.d.ts +1 -14
- package/dist/utils/dedup-image.d.ts.map +1 -1
- package/dist/utils/dedup-image.js +7 -62
- package/dist/{tools/fetch-video-analysis/local-ffmpeg-client.d.ts → utils/ffmpeg/index.d.ts} +9 -6
- package/dist/utils/ffmpeg/index.d.ts.map +1 -0
- package/dist/utils/ffmpeg/index.js +415 -0
- package/dist/utils/file.d.ts +1 -0
- package/dist/utils/file.d.ts.map +1 -1
- package/dist/utils/file.js +45 -1
- package/dist/utils/find-threshold.d.ts +8 -0
- package/dist/utils/find-threshold.d.ts.map +1 -0
- package/dist/utils/find-threshold.js +55 -0
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +24 -0
- package/dist/utils/model.d.ts +1 -1
- package/dist/utils/model.d.ts.map +1 -1
- package/dist/utils/model.js +7 -5
- package/dist/utils/repo-tree.d.ts +0 -1
- package/dist/utils/repo-tree.d.ts.map +1 -1
- package/dist/utils/repo-tree.js +2 -14
- package/dist/utils/slug.js +1 -1
- package/dist/video-core/agent-orchestrator.d.ts +14 -0
- package/dist/video-core/agent-orchestrator.d.ts.map +1 -0
- package/dist/video-core/agent-orchestrator.js +78 -0
- package/dist/video-core/analysis-server.d.ts +24 -0
- package/dist/video-core/analysis-server.d.ts.map +1 -0
- package/dist/video-core/analysis-server.js +398 -0
- package/dist/video-core/analysis-viewer.html +1374 -0
- package/dist/video-core/index.d.ts +44 -0
- package/dist/video-core/index.d.ts.map +1 -0
- package/dist/video-core/index.js +204 -0
- package/dist/video-core/model-limits.d.ts +4 -0
- package/dist/video-core/model-limits.d.ts.map +1 -0
- package/dist/video-core/model-limits.js +67 -0
- package/dist/video-core/storage-manager.d.ts +5 -0
- package/dist/video-core/storage-manager.d.ts.map +1 -0
- package/dist/video-core/storage-manager.js +55 -0
- package/dist/video-core/types.d.ts +13 -0
- package/dist/video-core/types.d.ts.map +1 -0
- package/dist/video-core/types.js +2 -0
- package/dist/video-core/utils.d.ts +25 -0
- package/dist/video-core/utils.d.ts.map +1 -0
- package/dist/video-core/utils.js +211 -0
- package/dist/video-core/xml-parser.d.ts +3 -0
- package/dist/video-core/xml-parser.d.ts.map +1 -0
- package/dist/video-core/xml-parser.js +27 -0
- package/package.json +5 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/agent/chat/prompt/index.d.ts +0 -5
- package/dist/agent/chat/prompt/index.d.ts.map +0 -1
- package/dist/agent/chat/prompt/index.js +0 -189
- package/dist/agent/chat/utils/tool-calls.d.ts +0 -21
- package/dist/agent/chat/utils/tool-calls.d.ts.map +0 -1
- package/dist/agent/chat/utils/tool-calls.js +0 -64
- package/dist/agent/code-review/prompt.d.ts +0 -2
- package/dist/agent/code-review/prompt.d.ts.map +0 -1
- package/dist/agent/code-review/prompt.js +0 -19
- package/dist/agent/diagnosis-agent/index.d.ts +0 -11
- package/dist/agent/diagnosis-agent/index.d.ts.map +0 -1
- package/dist/agent/diagnosis-agent/index.js +0 -88
- package/dist/agent/diagnosis-agent/strict-mode-violation.d.ts +0 -10
- package/dist/agent/diagnosis-agent/strict-mode-violation.d.ts.map +0 -1
- package/dist/agent/diagnosis-agent/strict-mode-violation.js +0 -30
- package/dist/tools/commit-and-create-pr/index.d.ts.map +0 -1
- package/dist/tools/commit-and-create-pr/index.js +0 -83
- package/dist/tools/definitions/commit-and-create-pr.d.ts +0 -3
- package/dist/tools/definitions/commit-and-create-pr.d.ts.map +0 -1
- package/dist/tools/definitions/fetch-video-analysis.d.ts.map +0 -1
- package/dist/tools/definitions/fetch-video-analysis.js +0 -61
- package/dist/tools/fetch-video-analysis/index.d.ts +0 -5
- package/dist/tools/fetch-video-analysis/index.d.ts.map +0 -1
- package/dist/tools/fetch-video-analysis/index.js +0 -138
- package/dist/tools/fetch-video-analysis/local-ffmpeg-client.d.ts.map +0 -1
- package/dist/tools/fetch-video-analysis/local-ffmpeg-client.js +0 -247
- package/dist/tools/fetch-video-analysis/open-ai.d.ts +0 -6
- package/dist/tools/fetch-video-analysis/open-ai.d.ts.map +0 -1
- package/dist/tools/fetch-video-analysis/open-ai.js +0 -37
- package/dist/tools/fetch-video-analysis/utils.d.ts +0 -13
- package/dist/tools/fetch-video-analysis/utils.d.ts.map +0 -1
- package/dist/tools/fetch-video-analysis/utils.js +0 -98
- package/dist/tools/fetch-video-analysis/video-analysis.d.ts +0 -7
- package/dist/tools/fetch-video-analysis/video-analysis.d.ts.map +0 -1
- package/dist/tools/fetch-video-analysis/video-analysis.js +0 -54
- package/dist/tools/file-operations/shared/git-helper.d.ts +0 -4
- package/dist/tools/file-operations/shared/git-helper.d.ts.map +0 -1
- package/dist/tools/file-operations/shared/git-helper.js +0 -29
- package/eslint.config.mjs +0 -43
- /package/dist/tools/{commit-and-create-pr → create-pull-request}/index.d.ts +0 -0
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createPullRequestTool = void 0;
|
|
4
|
-
const child_process_1 = require("child_process");
|
|
5
|
-
const commit_and_create_pr_1 = require("../definitions/commit-and-create-pr");
|
|
6
|
-
const utils_1 = require("../executor/utils");
|
|
7
|
-
const git_1 = require("../executor/utils/git");
|
|
8
|
-
const pr_description_1 = require("../executor/utils/pr-description");
|
|
9
|
-
exports.createPullRequestTool = {
|
|
10
|
-
...commit_and_create_pr_1.createPullRequestTool,
|
|
11
|
-
execute: async ({ input, repoPath, apiClient, chatSession }) => {
|
|
12
|
-
try {
|
|
13
|
-
const { pullRequestTitle, pullRequestDescription } = input;
|
|
14
|
-
const remoteUrl = (0, child_process_1.execSync)("git config --get remote.origin.url", {
|
|
15
|
-
cwd: repoPath,
|
|
16
|
-
})
|
|
17
|
-
.toString()
|
|
18
|
-
.trim();
|
|
19
|
-
const { owner, repo } = (0, utils_1.parseGitHubUrl)(remoteUrl);
|
|
20
|
-
const branchName = await (0, git_1.getCurrentBranchName)(repoPath);
|
|
21
|
-
(0, child_process_1.execSync)(`git push origin ${branchName} --set-upstream`, {
|
|
22
|
-
cwd: repoPath,
|
|
23
|
-
});
|
|
24
|
-
if (!apiClient) {
|
|
25
|
-
throw new Error("Dashboard API client is not available.");
|
|
26
|
-
}
|
|
27
|
-
let pullRequest = await (0, utils_1.getExistingPR)({
|
|
28
|
-
owner,
|
|
29
|
-
repo,
|
|
30
|
-
branchName,
|
|
31
|
-
apiClient,
|
|
32
|
-
});
|
|
33
|
-
if (pullRequest) {
|
|
34
|
-
pullRequest = await (0, utils_1.updatePullRequest)({
|
|
35
|
-
owner,
|
|
36
|
-
repo,
|
|
37
|
-
prNumber: pullRequest.number,
|
|
38
|
-
body: (0, pr_description_1.addMetadataToPRDescription)(pullRequestDescription, chatSession),
|
|
39
|
-
apiClient,
|
|
40
|
-
});
|
|
41
|
-
const mergeableState = await (0, utils_1.getMergeableState)({
|
|
42
|
-
owner,
|
|
43
|
-
repo,
|
|
44
|
-
pullRequest,
|
|
45
|
-
apiClient,
|
|
46
|
-
});
|
|
47
|
-
const stateDescription = (0, utils_1.getMergeableStateDescription)(mergeableState);
|
|
48
|
-
return {
|
|
49
|
-
isError: false,
|
|
50
|
-
result: `Updated existing PR: ${pullRequest.html_url}\n\nMergeable state: ${mergeableState} - ${stateDescription}`,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
pullRequest = await (0, utils_1.createPullRequest)({
|
|
55
|
-
owner,
|
|
56
|
-
repo,
|
|
57
|
-
title: pullRequestTitle,
|
|
58
|
-
head: branchName,
|
|
59
|
-
base: chatSession?.baseBranchName || "main",
|
|
60
|
-
body: (0, pr_description_1.addMetadataToPRDescription)(pullRequestDescription, chatSession),
|
|
61
|
-
apiClient,
|
|
62
|
-
});
|
|
63
|
-
const mergeableState = await (0, utils_1.getMergeableState)({
|
|
64
|
-
owner,
|
|
65
|
-
repo,
|
|
66
|
-
pullRequest,
|
|
67
|
-
apiClient,
|
|
68
|
-
});
|
|
69
|
-
const stateDescription = (0, utils_1.getMergeableStateDescription)(mergeableState);
|
|
70
|
-
return {
|
|
71
|
-
isError: false,
|
|
72
|
-
result: `Created a new PR: ${pullRequest.html_url}\n\nMergeable state: ${mergeableState} - ${stateDescription}`,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
return {
|
|
78
|
-
isError: true,
|
|
79
|
-
result: `Failed to commit and push changes: ${error instanceof Error ? error.message : String(error)}`,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"commit-and-create-pr.d.ts","sourceRoot":"","sources":["../../../src/tools/definitions/commit-and-create-pr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAmBjE,eAAO,MAAM,qBAAqB,EAAE,cAenC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-video-analysis.d.ts","sourceRoot":"","sources":["../../../src/tools/definitions/fetch-video-analysis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,CAAC,MAAM,KAAK,CAAC;AAEpB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2C9B,CAAC;AAEH,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAC7C,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAcpC,CAAC"}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.fetchVideoAnalysis = exports.videoAnalysisSchema = void 0;
|
|
7
|
-
const zod_1 = __importDefault(require("zod"));
|
|
8
|
-
exports.videoAnalysisSchema = zod_1.default.object({
|
|
9
|
-
videoUrl: zod_1.default
|
|
10
|
-
.string()
|
|
11
|
-
.url("Must be a valid URL")
|
|
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(),
|
|
47
|
-
});
|
|
48
|
-
exports.fetchVideoAnalysis = {
|
|
49
|
-
schema: {
|
|
50
|
-
name: "fetchVideoAnalysis",
|
|
51
|
-
description: `
|
|
52
|
-
Analyzes Playwright test execution videos to identify test failures and UI issues.
|
|
53
|
-
|
|
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`,
|
|
57
|
-
parameters: exports.videoAnalysisSchema,
|
|
58
|
-
},
|
|
59
|
-
needsBrowser: false,
|
|
60
|
-
isInlineTool: false,
|
|
61
|
-
};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { Tool } from "@empiricalrun/shared-types";
|
|
2
|
-
import type { z } from "zod";
|
|
3
|
-
import { type videoAnalysisSchema } from "../definitions/fetch-video-analysis";
|
|
4
|
-
export declare const fetchVideoAnalysis: Tool<z.infer<typeof videoAnalysisSchema>>;
|
|
5
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.fetchVideoAnalysis = void 0;
|
|
7
|
-
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const fetch_video_analysis_1 = require("../definitions/fetch-video-analysis");
|
|
10
|
-
const local_ffmpeg_client_1 = require("./local-ffmpeg-client");
|
|
11
|
-
const utils_1 = require("./utils");
|
|
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
|
-
}
|
|
35
|
-
exports.fetchVideoAnalysis = {
|
|
36
|
-
...fetch_video_analysis_1.fetchVideoAnalysis,
|
|
37
|
-
execute: async ({ input, trace, }) => {
|
|
38
|
-
const { videoUrl } = input;
|
|
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}`;
|
|
46
|
-
const R2_BASE_URL = `https://video-analysis.empirical.run/${videoUrlHash}/`;
|
|
47
|
-
const videoAnalysisSpan = trace?.span({
|
|
48
|
-
name: "video-analysis",
|
|
49
|
-
input: { videoUrl },
|
|
50
|
-
});
|
|
51
|
-
try {
|
|
52
|
-
const ffmpegClient = new local_ffmpeg_client_1.LocalFFmpegClient();
|
|
53
|
-
const response = await fetch(videoUrl, { method: "HEAD" });
|
|
54
|
-
if (!response.ok) {
|
|
55
|
-
return {
|
|
56
|
-
result: `Failed to access video at ${videoUrl}: ${response.statusText}`,
|
|
57
|
-
isError: true,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
const processingSpan = videoAnalysisSpan?.span({
|
|
61
|
-
name: "ffmpeg-processing",
|
|
62
|
-
input: {
|
|
63
|
-
videoUrl,
|
|
64
|
-
fps: effectiveFps,
|
|
65
|
-
threshold: effectiveThreshold,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
try {
|
|
69
|
-
const extractionResult = await ffmpegClient.extractVideoFrames(videoUrl, WORKING_DIR, {
|
|
70
|
-
fps: effectiveFps,
|
|
71
|
-
threshold: effectiveThreshold,
|
|
72
|
-
});
|
|
73
|
-
const { totalFramesCount, uniqueFrames } = extractionResult;
|
|
74
|
-
processingSpan?.end({
|
|
75
|
-
output: {
|
|
76
|
-
totalFramesCount,
|
|
77
|
-
uniqueFramesCount: uniqueFrames.length,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
console.log(`[video-analysis] Analyzing ${uniqueFrames.length} frames with LLM`);
|
|
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 = {
|
|
88
|
-
total_frames_count: totalFramesCount,
|
|
89
|
-
unique_frames_count: uniqueFrames.length,
|
|
90
|
-
video_url: videoUrl,
|
|
91
|
-
analysis_id: videoUrlHash,
|
|
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,
|
|
103
|
-
};
|
|
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,
|
|
113
|
-
};
|
|
114
|
-
return {
|
|
115
|
-
result: JSON.stringify(toolResult, null, 2),
|
|
116
|
-
isError: false,
|
|
117
|
-
usage,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
catch (ffmpegError) {
|
|
121
|
-
processingSpan?.end();
|
|
122
|
-
throw ffmpegError;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
console.error("Error in video analysis", error);
|
|
127
|
-
videoAnalysisSpan?.end();
|
|
128
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
129
|
-
return {
|
|
130
|
-
result: `Error analyzing video: ${message}`,
|
|
131
|
-
isError: true,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
videoAnalysisSpan?.end();
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"local-ffmpeg-client.d.ts","sourceRoot":"","sources":["../../../src/tools/fetch-video-analysis/local-ffmpeg-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAUrE,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAW;IAC7D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAAU;;IAMxD,OAAO,CAAC,uBAAuB;YAWjB,gBAAgB;YAiBhB,kBAAkB;YAgClB,aAAa;YAeb,cAAc;YASd,gBAAgB;YAoBhB,iBAAiB;YAoEjB,oBAAoB;YAyCpB,kBAAkB;IA2D1B,kBAAkB,CACtB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7C,OAAO,CAAC;QACT,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,uBAAuB,EAAE,CAAC;KACzC,CAAC;CAiEH"}
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.LocalFFmpegClient = void 0;
|
|
7
|
-
const child_process_1 = require("child_process");
|
|
8
|
-
const fs_1 = require("fs");
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const util_1 = require("util");
|
|
11
|
-
const dedup_image_fs_1 = require("../../utils/dedup-image-fs");
|
|
12
|
-
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
13
|
-
class LocalFFmpegClient {
|
|
14
|
-
static MAX_VIDEO_DURATION_SECONDS = 15 * 60; // 15 minutes
|
|
15
|
-
static CHUNK_DURATION_SECONDS = 2 * 60; // 2 minutes
|
|
16
|
-
constructor() {
|
|
17
|
-
this.checkFFmpegAvailability();
|
|
18
|
-
}
|
|
19
|
-
checkFFmpegAvailability() {
|
|
20
|
-
try {
|
|
21
|
-
(0, child_process_1.execSync)("ffmpeg -version", { stdio: "ignore" });
|
|
22
|
-
(0, child_process_1.execSync)("ffprobe -version", { stdio: "ignore" });
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
throw new Error(`ffmpeg and ffprobe are required for video processing: ${error}`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
async getVideoDuration(videoPath) {
|
|
29
|
-
const command = `ffprobe -v quiet -show_entries format=duration -of csv=p=0 "${videoPath}"`;
|
|
30
|
-
try {
|
|
31
|
-
const { stdout } = await execAsync(command);
|
|
32
|
-
const duration = parseFloat(stdout.trim());
|
|
33
|
-
if (isNaN(duration)) {
|
|
34
|
-
throw new Error("Could not determine video duration");
|
|
35
|
-
}
|
|
36
|
-
return duration;
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
throw new Error(`Failed to get video duration: ${error}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async validateVideoChunk(chunkPath) {
|
|
43
|
-
const command = `ffprobe -v error -show_entries format=duration -of csv=p=0 "${chunkPath}"`;
|
|
44
|
-
try {
|
|
45
|
-
const { stdout, stderr } = await execAsync(command);
|
|
46
|
-
if (stderr && stderr.toLowerCase().includes("error")) {
|
|
47
|
-
console.warn(`Chunk validation found errors: ${stderr}`);
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
const duration = parseFloat(stdout.trim());
|
|
51
|
-
if (isNaN(duration) || duration <= 0) {
|
|
52
|
-
console.warn(`Chunk has invalid duration: ${duration}`);
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
// Check file size as additional validation
|
|
56
|
-
const stats = await fs_1.promises.stat(chunkPath);
|
|
57
|
-
if (stats.size < 1024) {
|
|
58
|
-
// Less than 1KB is suspicious for a video chunk
|
|
59
|
-
console.warn(`Chunk file size too small: ${stats.size} bytes`);
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
console.warn(`Chunk validation failed: ${error}`);
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
async downloadVideo(videoUrl, outputPath) {
|
|
70
|
-
console.log(`Downloading video from: ${videoUrl}`);
|
|
71
|
-
const response = await fetch(videoUrl);
|
|
72
|
-
if (!response.ok) {
|
|
73
|
-
throw new Error(`Failed to download video: ${response.statusText}`);
|
|
74
|
-
}
|
|
75
|
-
const buffer = await response.arrayBuffer();
|
|
76
|
-
await fs_1.promises.writeFile(outputPath, Buffer.from(buffer));
|
|
77
|
-
}
|
|
78
|
-
async ensureEmptyDir(dir) {
|
|
79
|
-
try {
|
|
80
|
-
await fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
// ignore
|
|
84
|
-
}
|
|
85
|
-
await fs_1.promises.mkdir(dir, { recursive: true });
|
|
86
|
-
}
|
|
87
|
-
async runFFmpegCommand({ inputPath, args, outputPath, }) {
|
|
88
|
-
const quotedInput = `"${inputPath}"`;
|
|
89
|
-
const output = outputPath ? ` "${outputPath}"` : "";
|
|
90
|
-
const cmd = `ffmpeg -y -nostdin -i ${quotedInput} ${args.join(" ")}${output}`;
|
|
91
|
-
try {
|
|
92
|
-
await execAsync(cmd);
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
throw new Error(`ffmpeg command failed: ${cmd} => ${String(error)}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
async createVideoChunks(videoPath, outputDir, duration) {
|
|
99
|
-
const chunkPaths = [];
|
|
100
|
-
const chunkCount = Math.ceil(duration / LocalFFmpegClient.CHUNK_DURATION_SECONDS);
|
|
101
|
-
console.log(`Creating ${chunkCount} chunks of ${LocalFFmpegClient.CHUNK_DURATION_SECONDS} seconds each`);
|
|
102
|
-
for (let i = 0; i < chunkCount; i++) {
|
|
103
|
-
const startTime = i * LocalFFmpegClient.CHUNK_DURATION_SECONDS;
|
|
104
|
-
const chunkPath = path_1.default.join(outputDir, `chunk_${i.toString().padStart(3, "0")}.mp4`);
|
|
105
|
-
const remainingDuration = duration - startTime;
|
|
106
|
-
const chunkDuration = Math.min(LocalFFmpegClient.CHUNK_DURATION_SECONDS, remainingDuration);
|
|
107
|
-
try {
|
|
108
|
-
await fs_1.promises.rm(chunkPath, { force: true });
|
|
109
|
-
await this.runFFmpegCommand({
|
|
110
|
-
inputPath: videoPath,
|
|
111
|
-
args: [
|
|
112
|
-
"-ss",
|
|
113
|
-
String(startTime),
|
|
114
|
-
"-t",
|
|
115
|
-
String(chunkDuration),
|
|
116
|
-
"-c:v",
|
|
117
|
-
"libx264",
|
|
118
|
-
"-c:a",
|
|
119
|
-
"aac",
|
|
120
|
-
"-preset",
|
|
121
|
-
"ultrafast",
|
|
122
|
-
"-crf",
|
|
123
|
-
"28",
|
|
124
|
-
],
|
|
125
|
-
outputPath: chunkPath,
|
|
126
|
-
});
|
|
127
|
-
// Validate the created chunk
|
|
128
|
-
const isValid = await this.validateVideoChunk(chunkPath);
|
|
129
|
-
if (isValid) {
|
|
130
|
-
chunkPaths.push(chunkPath);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
console.warn(`Chunk ${i} appears corrupted, skipping...`);
|
|
134
|
-
try {
|
|
135
|
-
await fs_1.promises.unlink(chunkPath);
|
|
136
|
-
}
|
|
137
|
-
catch (unlinkError) {
|
|
138
|
-
console.warn(`Failed to remove corrupted chunk: ${unlinkError}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
throw new Error(`Failed to create chunk ${i}: ${error}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return chunkPaths;
|
|
147
|
-
}
|
|
148
|
-
async extractFramesToFiles(videoPath, outputDir, chunkIndex, fps) {
|
|
149
|
-
await this.ensureEmptyDir(outputDir);
|
|
150
|
-
const framePrefix = chunkIndex !== undefined ? `chunk${chunkIndex}_frame` : "frame";
|
|
151
|
-
const framePattern = path_1.default.join(outputDir, `${framePrefix}_%04d.png`);
|
|
152
|
-
console.log(`Extracting frames with command: ffmpeg -i "${videoPath}" -vf "fps=${fps}" "${framePattern}"`);
|
|
153
|
-
try {
|
|
154
|
-
await this.runFFmpegCommand({
|
|
155
|
-
inputPath: videoPath,
|
|
156
|
-
args: ["-vf", `fps=${fps}`],
|
|
157
|
-
outputPath: framePattern,
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
catch (error) {
|
|
161
|
-
throw new Error(`Frame extraction failed: ${error}`);
|
|
162
|
-
}
|
|
163
|
-
const files = await fs_1.promises.readdir(outputDir);
|
|
164
|
-
const frameFiles = files
|
|
165
|
-
.filter((file) => file.startsWith(framePrefix) && file.endsWith(".png"))
|
|
166
|
-
.sort()
|
|
167
|
-
.map((file) => path_1.default.join(outputDir, file));
|
|
168
|
-
console.log(`Extracted ${frameFiles.length} frames from ${chunkIndex !== undefined ? `chunk ${chunkIndex}` : "video"}`);
|
|
169
|
-
return frameFiles;
|
|
170
|
-
}
|
|
171
|
-
async processVideoChunks(chunkPaths, workingDir, fps) {
|
|
172
|
-
const allFramePaths = [];
|
|
173
|
-
const consolidatedFramesDir = path_1.default.join(workingDir, "consolidated_frames");
|
|
174
|
-
await this.ensureEmptyDir(consolidatedFramesDir);
|
|
175
|
-
let globalFrameIndex = 0;
|
|
176
|
-
for (let i = 0; i < chunkPaths.length; i++) {
|
|
177
|
-
const chunkPath = chunkPaths[i];
|
|
178
|
-
const chunkFramesDir = path_1.default.join(workingDir, `chunk_${i}_frames`);
|
|
179
|
-
console.log(`Processing chunk ${i + 1}/${chunkPaths.length}`);
|
|
180
|
-
if (chunkPath === undefined) {
|
|
181
|
-
throw new Error(`Chunk path is undefined for chunk ${i + 1}`);
|
|
182
|
-
}
|
|
183
|
-
try {
|
|
184
|
-
const chunkFramePaths = await this.extractFramesToFiles(chunkPath, chunkFramesDir, i, fps);
|
|
185
|
-
for (const framePath of chunkFramePaths) {
|
|
186
|
-
const newFramePath = path_1.default.join(consolidatedFramesDir, `frame_${globalFrameIndex.toString().padStart(6, "0")}.png`);
|
|
187
|
-
await fs_1.promises.rename(framePath, newFramePath);
|
|
188
|
-
allFramePaths.push(newFramePath);
|
|
189
|
-
globalFrameIndex++;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
console.warn(`Failed to process chunk ${i + 1}: ${error}. Skipping this chunk.`);
|
|
194
|
-
// Continue with other chunks instead of failing completely
|
|
195
|
-
}
|
|
196
|
-
try {
|
|
197
|
-
await fs_1.promises.rm(chunkFramesDir, { recursive: true });
|
|
198
|
-
}
|
|
199
|
-
catch (error) {
|
|
200
|
-
console.warn(`Failed to clean up chunk frames directory: ${error}`);
|
|
201
|
-
}
|
|
202
|
-
if (global.gc) {
|
|
203
|
-
global.gc();
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return allFramePaths;
|
|
207
|
-
}
|
|
208
|
-
async extractVideoFrames(videoUrl, outputDir, options) {
|
|
209
|
-
const workingDir = path_1.default.join(process.cwd(), outputDir);
|
|
210
|
-
const videoPath = path_1.default.join(workingDir, `video-${Date.now()}.webm`);
|
|
211
|
-
const fps = options?.fps ?? 30;
|
|
212
|
-
const threshold = options?.threshold ?? 0.001;
|
|
213
|
-
try {
|
|
214
|
-
await fs_1.promises.mkdir(workingDir, { recursive: true });
|
|
215
|
-
await this.downloadVideo(videoUrl, videoPath);
|
|
216
|
-
const duration = await this.getVideoDuration(videoPath);
|
|
217
|
-
console.log(`Video duration: ${Math.round(duration)} seconds`);
|
|
218
|
-
if (duration > LocalFFmpegClient.MAX_VIDEO_DURATION_SECONDS) {
|
|
219
|
-
throw new Error(`Video duration (${Math.round(duration)}s) exceeds maximum allowed duration (${LocalFFmpegClient.MAX_VIDEO_DURATION_SECONDS}s)`);
|
|
220
|
-
}
|
|
221
|
-
const chunkPaths = await this.createVideoChunks(videoPath, workingDir, duration);
|
|
222
|
-
const allFramePaths = await this.processVideoChunks(chunkPaths, workingDir, fps);
|
|
223
|
-
const allFramesCount = allFramePaths.length;
|
|
224
|
-
const uniqueImages = await (0, dedup_image_fs_1.deduplicateImageFiles)({
|
|
225
|
-
imagePaths: allFramePaths,
|
|
226
|
-
batchSize: 50,
|
|
227
|
-
threshold,
|
|
228
|
-
logPrefix: "ffmpeg-chunk-frame-dedup",
|
|
229
|
-
});
|
|
230
|
-
console.log(`Filtered to ${uniqueImages.length} unique frames from ${allFramesCount} total frames across ${chunkPaths.length} chunks`);
|
|
231
|
-
if (uniqueImages.length === 0) {
|
|
232
|
-
if (chunkPaths.length === 0) {
|
|
233
|
-
throw new Error("No valid video chunks were created. The video may be corrupted or in an unsupported format.");
|
|
234
|
-
}
|
|
235
|
-
throw new Error("No frames were extracted from the video");
|
|
236
|
-
}
|
|
237
|
-
return {
|
|
238
|
-
totalFramesCount: allFramesCount,
|
|
239
|
-
uniqueFrames: uniqueImages,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
throw new Error(`Frame extraction failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
exports.LocalFFmpegClient = LocalFFmpegClient;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"open-ai.d.ts","sourceRoot":"","sources":["../../../src/tools/fetch-video-analysis/open-ai.ts"],"names":[],"mappings":"AAOA,wBAAsB,aAAa,CAAC,EAClC,YAAY,EACZ,gBAAgB,EAChB,UAAU,GACX,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;CACpB,mBA+BA"}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.analyzeImages = analyzeImages;
|
|
7
|
-
const openai_1 = __importDefault(require("openai"));
|
|
8
|
-
const openai = new openai_1.default({
|
|
9
|
-
apiKey: ``,
|
|
10
|
-
});
|
|
11
|
-
async function analyzeImages({ systemPrompt, imageBase64Array, userPrompt, }) {
|
|
12
|
-
const response = await openai.responses.create({
|
|
13
|
-
model: "gpt-4.1-2025-04-14",
|
|
14
|
-
input: [
|
|
15
|
-
{
|
|
16
|
-
role: "system",
|
|
17
|
-
content: systemPrompt,
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
role: "user",
|
|
21
|
-
content: [
|
|
22
|
-
{
|
|
23
|
-
type: "input_text",
|
|
24
|
-
text: userPrompt,
|
|
25
|
-
},
|
|
26
|
-
...imageBase64Array.map((base64) => ({
|
|
27
|
-
detail: "auto",
|
|
28
|
-
type: "input_image",
|
|
29
|
-
image_url: `data:image/png;base64,${base64}`,
|
|
30
|
-
})),
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
});
|
|
35
|
-
console.log(response.output_text);
|
|
36
|
-
return response.output_text;
|
|
37
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type FileMap } from "@empiricalrun/r2-uploader";
|
|
2
|
-
import { UniqueFrameInfo, UniqueFrameWithMetadata, VideoAnalysisSummary } from "@empiricalrun/shared-types";
|
|
3
|
-
export declare const safeCleanupDirectory: (dirPath: string, label?: string) => Promise<void>;
|
|
4
|
-
export declare const uploadFramesToR2: (videoInfo: Omit<VideoAnalysisSummary, "analysis">, frames: {
|
|
5
|
-
metadata: {
|
|
6
|
-
index: number;
|
|
7
|
-
path: string;
|
|
8
|
-
};
|
|
9
|
-
image: string;
|
|
10
|
-
}[], r2BaseUrl: string) => Promise<UniqueFrameInfo[]>;
|
|
11
|
-
export declare const uploadSummaryToR2: (videoInfo: VideoAnalysisSummary, r2BaseUrl: string) => Promise<string>;
|
|
12
|
-
export declare const zipAndUploadFramesToR2: (uniqueFrames: UniqueFrameWithMetadata[], outputZipPath: string, videoUrlHash: string) => Promise<FileMap>;
|
|
13
|
-
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/tools/fetch-video-analysis/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAGb,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,4BAA4B,CAAC;AAKpC,eAAO,MAAM,oBAAoB,GAC/B,SAAS,MAAM,EACf,cAAiB,KAChB,OAAO,CAAC,IAAI,CAUd,CAAC;AAoCF,eAAO,MAAM,gBAAgB,GAC3B,WAAW,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,EACjD,QAAQ;IACN,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;CACf,EAAE,EACH,WAAW,MAAM,KAChB,OAAO,CAAC,eAAe,EAAE,CAqB3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,oBAAoB,EAC/B,WAAW,MAAM,KAChB,OAAO,CAAC,MAAM,CAsBhB,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,cAAc,uBAAuB,EAAE,EACvC,eAAe,MAAM,EACrB,cAAc,MAAM,KACnB,OAAO,CAAC,OAAO,CAsBjB,CAAC"}
|