@empiricalrun/test-gen 0.74.2 → 0.76.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 +37 -0
- package/dist/agent/base/index.d.ts +43 -0
- package/dist/agent/base/index.d.ts.map +1 -0
- package/dist/agent/base/index.js +106 -0
- package/dist/agent/chat/agent-loop.d.ts +7 -7
- package/dist/agent/chat/agent-loop.d.ts.map +1 -1
- package/dist/agent/chat/agent-loop.js +5 -18
- package/dist/agent/chat/exports.d.ts +6 -4
- package/dist/agent/chat/exports.d.ts.map +1 -1
- package/dist/agent/chat/exports.js +9 -8
- 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 +130 -200
- package/dist/agent/chat/prompt/index.d.ts +5 -4
- package/dist/agent/chat/prompt/index.d.ts.map +1 -1
- package/dist/agent/chat/prompt/index.js +79 -68
- package/dist/agent/chat/state.d.ts +3 -5
- package/dist/agent/chat/state.d.ts.map +1 -1
- package/dist/agent/chat/state.js +2 -10
- package/dist/agent/chat/utils.d.ts +2 -4
- package/dist/agent/chat/utils.d.ts.map +1 -1
- package/dist/agent/chat/utils.js +2 -16
- package/dist/agent/cli.d.ts +11 -0
- package/dist/agent/cli.d.ts.map +1 -0
- package/dist/agent/cli.js +209 -0
- package/dist/agent/code-review/index.d.ts +7 -0
- package/dist/agent/code-review/index.d.ts.map +1 -0
- package/dist/agent/code-review/index.js +65 -0
- package/dist/agent/code-review/prompt.d.ts +1 -1
- package/dist/agent/code-review/prompt.d.ts.map +1 -1
- package/dist/agent/code-review/prompt.js +52 -16
- 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 +102 -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 +35 -0
- package/dist/bin/index.js +6 -6
- package/dist/file-info/adapters/github/index.d.ts +2 -2
- package/dist/file-info/adapters/github/index.d.ts.map +1 -1
- package/dist/file-info/adapters/github/index.js +4 -4
- package/dist/file-info/adapters/github/reader.d.ts +5 -10
- package/dist/file-info/adapters/github/reader.d.ts.map +1 -1
- package/dist/file-info/adapters/github/reader.js +168 -138
- 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} +30 -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/extract-frames-from-video.d.ts +39 -0
- package/dist/tools/definitions/extract-frames-from-video.d.ts.map +1 -0
- package/dist/tools/definitions/extract-frames-from-video.js +60 -0
- package/dist/tools/definitions/fetch-video-analysis.d.ts +28 -0
- package/dist/tools/definitions/fetch-video-analysis.d.ts.map +1 -1
- package/dist/tools/definitions/fetch-video-analysis.js +39 -4
- package/dist/tools/definitions/rename-file.d.ts +3 -0
- package/dist/tools/definitions/rename-file.d.ts.map +1 -0
- package/dist/tools/definitions/rename-file.js +23 -0
- package/dist/tools/delete-file/index.d.ts.map +1 -1
- package/dist/tools/delete-file/index.js +13 -1
- package/dist/tools/executor/index.d.ts +1 -1
- package/dist/tools/executor/index.d.ts.map +1 -1
- package/dist/tools/executor/index.js +22 -7
- package/dist/tools/executor/utils/checkpoint.d.ts +1 -3
- package/dist/tools/executor/utils/checkpoint.d.ts.map +1 -1
- package/dist/tools/executor/utils/checkpoint.js +17 -17
- package/dist/tools/executor/utils/git.d.ts +9 -1
- package/dist/tools/executor/utils/git.d.ts.map +1 -1
- package/dist/tools/executor/utils/git.js +72 -2
- package/dist/tools/extract-frames-from-video/index.d.ts +7 -0
- package/dist/tools/extract-frames-from-video/index.d.ts.map +1 -0
- package/dist/tools/extract-frames-from-video/index.js +145 -0
- package/dist/tools/{fetch-image → fetch-file}/index.d.ts +2 -2
- package/dist/tools/fetch-file/index.d.ts.map +1 -0
- package/dist/tools/fetch-file/index.js +97 -0
- 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/fetch-video-analysis/index.d.ts +3 -3
- package/dist/tools/fetch-video-analysis/index.d.ts.map +1 -1
- package/dist/tools/fetch-video-analysis/index.js +84 -24
- package/dist/tools/fetch-video-analysis/open-ai.d.ts +6 -0
- package/dist/tools/fetch-video-analysis/open-ai.d.ts.map +1 -0
- package/dist/tools/fetch-video-analysis/open-ai.js +37 -0
- package/dist/tools/fetch-video-analysis/utils.d.ts +9 -3
- package/dist/tools/fetch-video-analysis/utils.d.ts.map +1 -1
- package/dist/tools/fetch-video-analysis/utils.js +64 -15
- package/dist/tools/fetch-video-analysis/video-analysis.d.ts +2 -2
- package/dist/tools/fetch-video-analysis/video-analysis.d.ts.map +1 -1
- package/dist/tools/fetch-video-analysis/video-analysis.js +24 -8
- package/dist/tools/file-operations/create.d.ts.map +1 -1
- package/dist/tools/file-operations/create.js +6 -3
- package/dist/tools/file-operations/insert.d.ts.map +1 -1
- package/dist/tools/file-operations/insert.js +6 -3
- package/dist/tools/file-operations/replace.d.ts.map +1 -1
- package/dist/tools/file-operations/replace.js +6 -3
- package/dist/tools/file-operations/shared/git-helper.d.ts.map +1 -1
- package/dist/tools/file-operations/shared/git-helper.js +1 -1
- package/dist/tools/file-operations/view/index.d.ts +2 -5
- package/dist/tools/file-operations/view/index.d.ts.map +1 -1
- package/dist/tools/file-operations/view/index.js +2 -22
- package/dist/tools/index.d.ts +28 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +50 -22
- package/dist/tools/issues/update-issue.d.ts.map +1 -1
- package/dist/tools/issues/update-issue.js +16 -9
- package/dist/tools/merge-conflicts/index.js +1 -1
- package/dist/tools/rename-file/index.d.ts +3 -0
- package/dist/tools/rename-file/index.d.ts.map +1 -0
- package/dist/tools/rename-file/index.js +88 -0
- 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 +103 -0
- package/dist/tools/run-test.js +2 -2
- 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/trace-dot-zip/index.d.ts.map +1 -1
- package/dist/tools/trace-dot-zip/index.js +2 -1
- package/dist/tools/trace-dot-zip/types.d.ts +35 -3
- package/dist/tools/trace-dot-zip/types.d.ts.map +1 -1
- package/dist/tools/trace-dot-zip/utils/network-trace.d.ts +7 -2
- package/dist/tools/trace-dot-zip/utils/network-trace.d.ts.map +1 -1
- package/dist/tools/trace-dot-zip/utils/network-trace.js +130 -10
- package/dist/tools/upgrade-packages/index.js +1 -1
- 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/file.d.ts +1 -0
- package/dist/utils/file.d.ts.map +1 -1
- package/dist/utils/file.js +45 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -3
- package/dist/utils/local-ffmpeg-client.d.ts +27 -0
- package/dist/utils/local-ffmpeg-client.d.ts.map +1 -0
- package/dist/{tools/fetch-video-analysis → utils}/local-ffmpeg-client.js +117 -27
- package/package.json +5 -5
- package/tsconfig.tsbuildinfo +1 -1
- 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/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/fetch-image/index.d.ts.map +0 -1
- package/dist/tools/fetch-image/index.js +0 -63
- package/dist/tools/fetch-video-analysis/local-ffmpeg-client.d.ts +0 -24
- package/dist/tools/fetch-video-analysis/local-ffmpeg-client.d.ts.map +0 -1
- /package/dist/tools/{commit-and-create-pr → create-pull-request}/index.d.ts +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
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.extractFramesFromVideo = void 0;
|
|
7
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const file_1 = require("../../utils/file");
|
|
10
|
+
const local_ffmpeg_client_1 = require("../../utils/local-ffmpeg-client");
|
|
11
|
+
const extract_frames_from_video_1 = require("../definitions/extract-frames-from-video");
|
|
12
|
+
function parseTimeToSeconds(timeString) {
|
|
13
|
+
const timeRegex = /^(\d{1,2}):(\d{2})$/;
|
|
14
|
+
const match = timeString.match(timeRegex);
|
|
15
|
+
if (!match || match.length < 3) {
|
|
16
|
+
throw new Error(`Invalid time format: ${timeString}. Expected MM:SS format (e.g., "01:32")`);
|
|
17
|
+
}
|
|
18
|
+
const minutesStr = match[1];
|
|
19
|
+
const secondsStr = match[2];
|
|
20
|
+
if (!minutesStr || !secondsStr) {
|
|
21
|
+
throw new Error(`Invalid time format: ${timeString}. Expected MM:SS format (e.g., "01:32")`);
|
|
22
|
+
}
|
|
23
|
+
const minutes = parseInt(minutesStr, 10);
|
|
24
|
+
const seconds = parseInt(secondsStr, 10);
|
|
25
|
+
if (seconds >= 60) {
|
|
26
|
+
throw new Error(`Invalid seconds: ${seconds}. Seconds must be 0-59`);
|
|
27
|
+
}
|
|
28
|
+
return minutes * 60 + seconds;
|
|
29
|
+
}
|
|
30
|
+
function getExtractFramesParams(params) {
|
|
31
|
+
return {
|
|
32
|
+
fps: params?.fps ?? 30,
|
|
33
|
+
threshold: params?.threshold ?? 0.001,
|
|
34
|
+
startTime: params?.startTime
|
|
35
|
+
? parseTimeToSeconds(params.startTime)
|
|
36
|
+
: undefined,
|
|
37
|
+
duration: params?.duration
|
|
38
|
+
? parseTimeToSeconds(params.duration)
|
|
39
|
+
: undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function hashExtractParams(videoUrl, params) {
|
|
43
|
+
const sortedParams = Object.keys(params)
|
|
44
|
+
.sort()
|
|
45
|
+
.reduce((acc, key) => {
|
|
46
|
+
acc[key] = params[key];
|
|
47
|
+
return acc;
|
|
48
|
+
}, {});
|
|
49
|
+
const json = JSON.stringify({ videoUrl, ...sortedParams });
|
|
50
|
+
return node_crypto_1.default
|
|
51
|
+
.createHash("sha256")
|
|
52
|
+
.update(json)
|
|
53
|
+
.digest("hex")
|
|
54
|
+
.substring(0, 16);
|
|
55
|
+
}
|
|
56
|
+
exports.extractFramesFromVideo = {
|
|
57
|
+
...extract_frames_from_video_1.extractFramesFromVideo,
|
|
58
|
+
execute: async ({ input, trace, }) => {
|
|
59
|
+
const { videoUrl } = input;
|
|
60
|
+
const params = getExtractFramesParams(input.params);
|
|
61
|
+
const videoUrlHash = hashExtractParams(videoUrl, params);
|
|
62
|
+
const WORKING_DIR = `./extract-frames-artifacts/${videoUrlHash}`;
|
|
63
|
+
// const R2_BASE_URL = `https://video-analysis.empirical.run/${videoUrlHash}`;
|
|
64
|
+
const extractionSpan = trace?.span({
|
|
65
|
+
name: "extract-frames-from-video",
|
|
66
|
+
input: { videoUrl, params },
|
|
67
|
+
});
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetch(videoUrl, { method: "HEAD" });
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
return {
|
|
72
|
+
result: `Failed to access video: ${response.statusText}`,
|
|
73
|
+
isError: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const ffmpegClient = new local_ffmpeg_client_1.LocalFFmpegClient();
|
|
77
|
+
const extractionResult = await ffmpegClient.extractVideoFrames(videoUrl, WORKING_DIR, {
|
|
78
|
+
fps: params.fps,
|
|
79
|
+
threshold: params.threshold,
|
|
80
|
+
startTime: params.startTime,
|
|
81
|
+
duration: params.duration,
|
|
82
|
+
});
|
|
83
|
+
const { totalFramesCount, uniqueFrames, videoDurationSeconds } = extractionResult;
|
|
84
|
+
// Create response metadata
|
|
85
|
+
const extractFramesResponse = {
|
|
86
|
+
videoUrl,
|
|
87
|
+
videoDurationSeconds,
|
|
88
|
+
totalFramesCount,
|
|
89
|
+
extractedFramesCount: uniqueFrames.length,
|
|
90
|
+
timeRange: params.startTime !== undefined || params.duration !== undefined
|
|
91
|
+
? {
|
|
92
|
+
startTime: params.startTime || 0,
|
|
93
|
+
duration: params.duration || 0,
|
|
94
|
+
endTime: (params.startTime || 0) + (params.duration || 0),
|
|
95
|
+
}
|
|
96
|
+
: undefined,
|
|
97
|
+
frames: uniqueFrames.map((frame) => ({
|
|
98
|
+
index: frame.metadata.index,
|
|
99
|
+
path: frame.metadata.path,
|
|
100
|
+
frameId: `frame_${frame.metadata.index}`,
|
|
101
|
+
})),
|
|
102
|
+
};
|
|
103
|
+
// Convert frames to base64 images for multimodal response
|
|
104
|
+
const frameImageParts = await Promise.all(uniqueFrames.map(async (frame) => {
|
|
105
|
+
try {
|
|
106
|
+
const imageBuffer = await fs_1.promises.readFile(frame.metadata.path);
|
|
107
|
+
const base64Data = imageBuffer.toString("base64");
|
|
108
|
+
return {
|
|
109
|
+
type: "image/png",
|
|
110
|
+
base64Data: base64Data,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.warn(`Failed to read frame ${frame.metadata.path}:`, error);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}));
|
|
118
|
+
await (0, file_1.safeCleanupDirectory)(WORKING_DIR, "extract-frames-tool");
|
|
119
|
+
// Filter out any failed frame reads
|
|
120
|
+
const validFrameImageParts = frameImageParts.filter((part) => part !== null);
|
|
121
|
+
return {
|
|
122
|
+
result: [
|
|
123
|
+
{
|
|
124
|
+
type: "text",
|
|
125
|
+
text: JSON.stringify(extractFramesResponse, null, 2),
|
|
126
|
+
},
|
|
127
|
+
...validFrameImageParts,
|
|
128
|
+
],
|
|
129
|
+
isError: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error("Error in extractFramesFromVideo", error);
|
|
134
|
+
extractionSpan?.end();
|
|
135
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
136
|
+
return {
|
|
137
|
+
result: `Error extracting frames from video: ${message}`,
|
|
138
|
+
isError: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
extractionSpan?.end();
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Tool } from "@empiricalrun/shared-types";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
declare const
|
|
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
|
|
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
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/fetch-session-diff/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAc,MAAM,4BAA4B,CAAC;AAO9D,eAAO,MAAM,oBAAoB,EAAE,IA0ClC,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchSessionDiffTool = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const SessionDiffInputSchema = zod_1.z.object({
|
|
6
|
+
url: zod_1.z.string().describe(`The URL of the chat session.`),
|
|
7
|
+
});
|
|
8
|
+
exports.fetchSessionDiffTool = {
|
|
9
|
+
schema: {
|
|
10
|
+
name: "fetchSessionDiff",
|
|
11
|
+
description: `Extracts the session url and returns the code diff for the session.`,
|
|
12
|
+
parameters: SessionDiffInputSchema,
|
|
13
|
+
},
|
|
14
|
+
needsBrowser: false,
|
|
15
|
+
isInlineTool: true,
|
|
16
|
+
execute: async ({ input, apiClient }) => {
|
|
17
|
+
try {
|
|
18
|
+
const sessionUrl = input.url;
|
|
19
|
+
if (!sessionUrl) {
|
|
20
|
+
return {
|
|
21
|
+
isError: true,
|
|
22
|
+
result: "No session URL provided",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (!apiClient) {
|
|
26
|
+
throw new Error("Dashboard API client is not available.");
|
|
27
|
+
}
|
|
28
|
+
const sessionId = sessionUrl.split("/").pop();
|
|
29
|
+
const sessionDiffApiUrl = `/api/chat-sessions/${sessionId}/diff`;
|
|
30
|
+
const sessionDiff = await apiClient.request(sessionDiffApiUrl, {
|
|
31
|
+
method: "GET",
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
isError: false,
|
|
35
|
+
result: JSON.stringify(sessionDiff, null, 2),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error("Error fetching session diff", error);
|
|
40
|
+
return {
|
|
41
|
+
isError: true,
|
|
42
|
+
result: error instanceof Error ? error.message : String(error),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -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":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/fetch-video-analysis/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,IAAI,EAKL,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAI7B,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,qCAAqC,CAAC;AAuC7C,eAAO,MAAM,kBAAkB,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAsKxE,CAAC"}
|
|
@@ -4,20 +4,46 @@ 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
|
|
7
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const file_1 = require("../../utils/file");
|
|
10
|
+
const local_ffmpeg_client_1 = require("../../utils/local-ffmpeg-client");
|
|
8
11
|
const fetch_video_analysis_1 = require("../definitions/fetch-video-analysis");
|
|
9
|
-
const local_ffmpeg_client_1 = require("./local-ffmpeg-client");
|
|
10
12
|
const utils_1 = require("./utils");
|
|
11
13
|
const video_analysis_1 = require("./video-analysis");
|
|
14
|
+
function getVideoAnalysisParams(params) {
|
|
15
|
+
return {
|
|
16
|
+
model: params?.model || "gemini-2.5-pro",
|
|
17
|
+
fps: params?.fps ?? 30,
|
|
18
|
+
threshold: params?.threshold ?? 0.001,
|
|
19
|
+
featureFlag: params?.featureFlag ?? "send-all-frames",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function hashObject(obj) {
|
|
23
|
+
const sortedObj = Object.keys(obj)
|
|
24
|
+
.sort()
|
|
25
|
+
.reduce((acc, key) => {
|
|
26
|
+
acc[key] = obj[key];
|
|
27
|
+
return acc;
|
|
28
|
+
}, {});
|
|
29
|
+
const json = JSON.stringify(sortedObj);
|
|
30
|
+
return node_crypto_1.default
|
|
31
|
+
.createHash("sha256")
|
|
32
|
+
.update(json)
|
|
33
|
+
.digest("hex")
|
|
34
|
+
.substring(0, 16);
|
|
35
|
+
}
|
|
12
36
|
exports.fetchVideoAnalysis = {
|
|
13
37
|
...fetch_video_analysis_1.fetchVideoAnalysis,
|
|
14
|
-
execute: async ({ input, trace, }) => {
|
|
38
|
+
execute: async ({ input, trace, featureFlags, }) => {
|
|
15
39
|
const { videoUrl } = input;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
const params = getVideoAnalysisParams(input.params);
|
|
41
|
+
const videoUrlHash = hashObject({
|
|
42
|
+
videoUrl,
|
|
43
|
+
...params,
|
|
44
|
+
});
|
|
45
|
+
const { model: selectedModel, fps: effectiveFps, threshold: effectiveThreshold, featureFlag: effectiveFeatureFlag, } = params;
|
|
46
|
+
const WORKING_DIR = `./video-analysis-artifacts/${videoUrlHash}`;
|
|
21
47
|
const R2_BASE_URL = `https://video-analysis.empirical.run/${videoUrlHash}/`;
|
|
22
48
|
const videoAnalysisSpan = trace?.span({
|
|
23
49
|
name: "video-analysis",
|
|
@@ -34,36 +60,70 @@ exports.fetchVideoAnalysis = {
|
|
|
34
60
|
}
|
|
35
61
|
const processingSpan = videoAnalysisSpan?.span({
|
|
36
62
|
name: "ffmpeg-processing",
|
|
37
|
-
input: {
|
|
63
|
+
input: {
|
|
64
|
+
videoUrl,
|
|
65
|
+
fps: effectiveFps,
|
|
66
|
+
threshold: effectiveThreshold,
|
|
67
|
+
},
|
|
38
68
|
});
|
|
39
69
|
try {
|
|
40
|
-
const
|
|
70
|
+
const extractionResult = await ffmpegClient.extractVideoFrames(videoUrl, WORKING_DIR, {
|
|
71
|
+
fps: effectiveFps,
|
|
72
|
+
threshold: effectiveThreshold,
|
|
73
|
+
});
|
|
74
|
+
const { totalFramesCount, uniqueFrames } = extractionResult;
|
|
41
75
|
processingSpan?.end({
|
|
42
76
|
output: {
|
|
43
77
|
totalFramesCount,
|
|
44
|
-
uniqueFramesCount,
|
|
78
|
+
uniqueFramesCount: uniqueFrames.length,
|
|
45
79
|
},
|
|
46
80
|
});
|
|
47
81
|
console.log(`[video-analysis] Analyzing ${uniqueFrames.length} frames with LLM`);
|
|
48
|
-
const
|
|
82
|
+
const outputZipPath = node_path_1.default.join(WORKING_DIR, "frames.zip");
|
|
83
|
+
const zipUploadPromise = (0, utils_1.zipAndUploadFramesToR2)(uniqueFrames, outputZipPath, videoUrlHash).catch((error) => {
|
|
84
|
+
throw error; // Re-throw to maintain error in Promise.all
|
|
85
|
+
});
|
|
86
|
+
const { analysis: llmAnalysis, usage } = await (0, video_analysis_1.analyzeFramesWithLLM)(uniqueFrames, videoAnalysisSpan, selectedModel);
|
|
87
|
+
console.log(`[video-analysis] Finished Analyzing ${uniqueFrames.length} frames with LLM`);
|
|
88
|
+
let finalAnalysis = llmAnalysis;
|
|
89
|
+
let finalKeyFramesData = [];
|
|
90
|
+
if (featureFlags.includes("enableFetchVideoAnalysisToolMultiModal")) {
|
|
91
|
+
const { updatedAnalysis, keyFramesData } = await (0, utils_1.getUpdatedAnalysisAndToolResultImagePart)(llmAnalysis, uniqueFrames);
|
|
92
|
+
finalAnalysis = updatedAnalysis;
|
|
93
|
+
finalKeyFramesData = [...keyFramesData];
|
|
94
|
+
}
|
|
95
|
+
const videoInfo = {
|
|
49
96
|
total_frames_count: totalFramesCount,
|
|
50
|
-
unique_frames_count:
|
|
97
|
+
unique_frames_count: uniqueFrames.length,
|
|
51
98
|
video_url: videoUrl,
|
|
52
|
-
|
|
99
|
+
analysis_id: videoUrlHash,
|
|
53
100
|
created_at: new Date().toISOString(),
|
|
101
|
+
params: {
|
|
102
|
+
fps: effectiveFps,
|
|
103
|
+
threshold: effectiveThreshold,
|
|
104
|
+
model: selectedModel,
|
|
105
|
+
featureFlag: effectiveFeatureFlag,
|
|
106
|
+
},
|
|
107
|
+
usage,
|
|
108
|
+
langfuse_trace_id: trace?.id || undefined,
|
|
109
|
+
frames_zip_url: `${R2_BASE_URL}frames.zip`,
|
|
110
|
+
analysis: finalAnalysis,
|
|
54
111
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
112
|
+
await Promise.all([
|
|
113
|
+
zipUploadPromise,
|
|
114
|
+
(0, utils_1.uploadSummaryToR2)(videoInfo, R2_BASE_URL),
|
|
115
|
+
]);
|
|
116
|
+
await (0, file_1.safeCleanupDirectory)(WORKING_DIR, "video-analysis-cleanup");
|
|
117
|
+
const toolResult = {
|
|
118
|
+
video_url: videoUrl,
|
|
119
|
+
analysis_id: videoUrlHash,
|
|
120
|
+
analysis: finalAnalysis,
|
|
62
121
|
};
|
|
63
|
-
const uniqueFramesWithUrls = await framesUploadPromise;
|
|
64
|
-
await (0, utils_1.uploadAnalysisToR2)(videoInfo, uniqueFramesWithUrls, R2_BASE_URL);
|
|
65
122
|
return {
|
|
66
|
-
result:
|
|
123
|
+
result: [
|
|
124
|
+
{ type: "text", text: JSON.stringify(toolResult, null, 2) },
|
|
125
|
+
...finalKeyFramesData,
|
|
126
|
+
],
|
|
67
127
|
isError: false,
|
|
68
128
|
usage,
|
|
69
129
|
};
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
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,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { type FileMap } from "@empiricalrun/r2-uploader";
|
|
2
|
+
import { ToolResultImagePart, UniqueFrameInfo, UniqueFrameWithMetadata, VideoAnalysisSummary } from "@empiricalrun/shared-types";
|
|
3
|
+
export declare const uploadFramesToR2: (videoInfo: Omit<VideoAnalysisSummary, "analysis">, frames: {
|
|
3
4
|
metadata: {
|
|
4
5
|
index: number;
|
|
5
6
|
path: string;
|
|
6
7
|
};
|
|
7
8
|
image: string;
|
|
8
9
|
}[], r2BaseUrl: string) => Promise<UniqueFrameInfo[]>;
|
|
9
|
-
export declare const
|
|
10
|
+
export declare const uploadSummaryToR2: (videoInfo: VideoAnalysisSummary, r2BaseUrl: string) => Promise<string>;
|
|
11
|
+
export declare const zipAndUploadFramesToR2: (uniqueFrames: UniqueFrameWithMetadata[], outputZipPath: string, videoUrlHash: string) => Promise<FileMap>;
|
|
12
|
+
export declare const getUpdatedAnalysisAndToolResultImagePart: (llmAnalysis: string, uniqueFrames: UniqueFrameWithMetadata[]) => Promise<{
|
|
13
|
+
updatedAnalysis: string;
|
|
14
|
+
keyFramesData: ToolResultImagePart[];
|
|
15
|
+
}>;
|
|
10
16
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/tools/fetch-video-analysis/utils.ts"],"names":[],"mappings":"
|
|
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,mBAAmB,EACnB,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,4BAA4B,CAAC;AAuCpC,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;AAEF,eAAO,MAAM,wCAAwC,GACnD,aAAa,MAAM,EACnB,cAAc,uBAAuB,EAAE,KACtC,OAAO,CAAC;IACT,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,mBAAmB,EAAE,CAAC;CACtC,CA2CA,CAAC"}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
6
|
+
exports.getUpdatedAnalysisAndToolResultImagePart = exports.zipAndUploadFramesToR2 = exports.uploadSummaryToR2 = exports.uploadFramesToR2 = void 0;
|
|
4
7
|
const r2_uploader_1 = require("@empiricalrun/r2-uploader");
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
5
11
|
const uploadFilesToR2 = async (files, videoUrlHash) => {
|
|
6
12
|
const label = `video-analysis-upload:${videoUrlHash}`;
|
|
7
13
|
const start = Date.now();
|
|
@@ -25,7 +31,7 @@ const uploadFilesToR2 = async (files, videoUrlHash) => {
|
|
|
25
31
|
}
|
|
26
32
|
};
|
|
27
33
|
const uploadFramesToR2 = async (videoInfo, frames, r2BaseUrl) => {
|
|
28
|
-
const {
|
|
34
|
+
const { analysis_id: videoUrlHash } = videoInfo;
|
|
29
35
|
const frameFiles = frames.map((f) => {
|
|
30
36
|
const fileName = `frame_${f.metadata.index}_${videoUrlHash}.png`;
|
|
31
37
|
const buffer = Buffer.from(f.image, "base64");
|
|
@@ -41,21 +47,12 @@ const uploadFramesToR2 = async (videoInfo, frames, r2BaseUrl) => {
|
|
|
41
47
|
}));
|
|
42
48
|
};
|
|
43
49
|
exports.uploadFramesToR2 = uploadFramesToR2;
|
|
44
|
-
const
|
|
50
|
+
const uploadSummaryToR2 = async (videoInfo, r2BaseUrl) => {
|
|
45
51
|
try {
|
|
46
|
-
const {
|
|
47
|
-
const summary = {
|
|
48
|
-
total_frames_count,
|
|
49
|
-
unique_frames_count,
|
|
50
|
-
video_url,
|
|
51
|
-
llm_analysis,
|
|
52
|
-
video_url_hash: videoUrlHash,
|
|
53
|
-
uniqueFrames,
|
|
54
|
-
created_at,
|
|
55
|
-
};
|
|
52
|
+
const { analysis_id: videoUrlHash } = videoInfo;
|
|
56
53
|
const filesToUpload = [
|
|
57
54
|
{
|
|
58
|
-
buffer: Buffer.from(JSON.stringify(
|
|
55
|
+
buffer: Buffer.from(JSON.stringify(videoInfo, null, 2)),
|
|
59
56
|
fileName: "summary.json",
|
|
60
57
|
mimeType: "application/json",
|
|
61
58
|
},
|
|
@@ -69,4 +66,56 @@ const uploadAnalysisToR2 = async (videoInfo, uniqueFrames, r2BaseUrl) => {
|
|
|
69
66
|
throw error;
|
|
70
67
|
}
|
|
71
68
|
};
|
|
72
|
-
exports.
|
|
69
|
+
exports.uploadSummaryToR2 = uploadSummaryToR2;
|
|
70
|
+
const zipAndUploadFramesToR2 = async (uniqueFrames, outputZipPath, videoUrlHash) => {
|
|
71
|
+
const filePaths = uniqueFrames.map((u) => u.metadata.path);
|
|
72
|
+
await new Promise((resolve, reject) => {
|
|
73
|
+
(0, child_process_1.execFile)("zip", ["-0", "-j", outputZipPath, ...filePaths], (err) => {
|
|
74
|
+
if (err)
|
|
75
|
+
return reject(err);
|
|
76
|
+
resolve();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
const tempUploadDir = path_1.default.dirname(outputZipPath);
|
|
80
|
+
console.log(`[zip-upload] Uploading zip file: ${outputZipPath} to video-analysis/${videoUrlHash}/`);
|
|
81
|
+
return await (0, r2_uploader_1.uploadDirectory)({
|
|
82
|
+
sourceDir: tempUploadDir,
|
|
83
|
+
fileList: [outputZipPath],
|
|
84
|
+
destinationDir: videoUrlHash,
|
|
85
|
+
uploadBucket: "video-analysis",
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
exports.zipAndUploadFramesToR2 = zipAndUploadFramesToR2;
|
|
89
|
+
const getUpdatedAnalysisAndToolResultImagePart = async (llmAnalysis, uniqueFrames) => {
|
|
90
|
+
const keyFramesMatch = llmAnalysis.match(/KEY FRAMES:\s*(.+)$/);
|
|
91
|
+
const updatedAnalysis = keyFramesMatch
|
|
92
|
+
? llmAnalysis.replace(/\n\nKEY FRAMES:\s*(.+)$/, "")
|
|
93
|
+
: llmAnalysis;
|
|
94
|
+
const keyFrameIndices = keyFramesMatch?.[1]
|
|
95
|
+
?.match(/<frame_(\d+)>/g)
|
|
96
|
+
?.map((match) => parseInt(match.match(/<frame_(\d+)>/)?.[1] || "0", 10)) || [];
|
|
97
|
+
const frameDataMap = new Map();
|
|
98
|
+
await Promise.all(uniqueFrames.map(async (frame) => {
|
|
99
|
+
try {
|
|
100
|
+
const fileBuffer = await fs_1.promises.readFile(frame.metadata.path);
|
|
101
|
+
const base64Data = fileBuffer.toString("base64");
|
|
102
|
+
frameDataMap.set(frame.metadata.index, base64Data);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error(`Failed to read frame file ${frame.metadata.path}:`, error);
|
|
106
|
+
}
|
|
107
|
+
}));
|
|
108
|
+
const keyFramesData = keyFrameIndices
|
|
109
|
+
.map((frameIndex) => {
|
|
110
|
+
const base64Data = frameDataMap.get(frameIndex);
|
|
111
|
+
return base64Data
|
|
112
|
+
? {
|
|
113
|
+
type: "image/png",
|
|
114
|
+
base64Data: base64Data,
|
|
115
|
+
}
|
|
116
|
+
: null;
|
|
117
|
+
})
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
return { updatedAnalysis, keyFramesData };
|
|
120
|
+
};
|
|
121
|
+
exports.getUpdatedAnalysisAndToolResultImagePart = getUpdatedAnalysisAndToolResultImagePart;
|