videobook 0.2.5 → 0.3.1
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 +96 -0
- package/README.md +6 -2
- package/dist/archive-b2.js +1 -1
- package/dist/asset-media-projection.js +161 -0
- package/dist/cli/args.js +42 -3
- package/dist/cli/config-command.js +17 -1
- package/dist/cli/doctor-command.js +15 -0
- package/dist/cli/har-command.js +86 -0
- package/dist/cli/main.js +13 -1
- package/dist/command-reference.js +3 -1
- package/dist/content-store.js +2 -1
- package/dist/diagnostics.js +2 -2
- package/dist/env-secrets.js +1 -0
- package/dist/generation-har.js +106 -0
- package/dist/generation-reference-mentions.js +19 -0
- package/dist/goal-plan-v3.js +11 -1
- package/dist/goal-plan.js +1 -1
- package/dist/goal-store.js +2 -2
- package/dist/har-capture.js +175 -0
- package/dist/har-export.js +238 -0
- package/dist/har-index.js +98 -0
- package/dist/har-retention.js +73 -0
- package/dist/har-settings.js +27 -0
- package/dist/jobs/_alibaba-upload.js +2 -4
- package/dist/jobs/_fal-upload.js +2 -4
- package/dist/jobs/_image-edit-adapter.js +3 -1
- package/dist/jobs/_kie-upload.js +2 -4
- package/dist/jobs/assess-media-probes.js +94 -0
- package/dist/jobs/generate-media.js +10 -1
- package/dist/jobs/image-subjects.js +3 -1
- package/dist/jobs/kie-paygate.js +10 -3
- package/dist/jobs/register-all.js +2 -0
- package/dist/jobs/run-notebook.js +20 -2
- package/dist/jobs/xai-paygate.js +5 -2
- package/dist/library-asset-projection.js +141 -0
- package/dist/lightning-defaults.js +4 -4
- package/dist/media/adapters/kie.js +2 -1
- package/dist/media/catalog.js +8 -2
- package/dist/media/cost.js +1 -1
- package/dist/media/descriptor.js +7 -4
- package/dist/media/frontier-gaps.js +0 -1
- package/dist/media/har-endpoint.js +25 -0
- package/dist/media/json-schema.js +3 -0
- package/dist/media/preferred-defaults.js +161 -0
- package/dist/media/prompt-dialects.js +9 -1
- package/dist/media-library.js +158 -153
- package/dist/notebook-sticky-settings.js +60 -0
- package/dist/package-version.js +18 -0
- package/dist/posts/dropbox-auth.js +1 -1
- package/dist/posts/post-metrics.js +2 -1
- package/dist/provider-usage.js +1 -18
- package/dist/queue.js +12 -1
- package/dist/serve.js +2 -0
- package/dist/tools/api-status.js +1 -1
- package/dist/tools/generate-media.js +3 -0
- package/dist/tools/generation-payload.js +17 -2
- package/dist/tools/get-generation-har.js +43 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/list-cell-generations.js +19 -0
- package/dist/tools/reconcile-project-work.js +52 -193
- package/dist/tools/set-har-capture.js +30 -0
- package/dist/tools/videobook-domain.js +16 -1
- package/dist/upload.js +2 -1
- package/dist/usage-providers.js +48 -10
- package/dist/workspace-action-groups.js +2 -2
- package/dist/x/account.js +1 -1
- package/dist/x/authenticated-handle.js +1 -1
- package/dist/x/oauth-flow.js +1 -1
- package/dist/x/post-to-x.js +2 -1
- package/dist/x/x-tokens.js +2 -1
- package/package.json +15 -14
- package/ui/assets/{alive-captions-duTKsyll.js → alive-captions-CnLK22ED.js} +1 -1
- package/ui/assets/confirm-bodies-BaalBxp1.js +1 -0
- package/ui/assets/{fullscreen-COrmiHPn.js → fullscreen-DTJQJ6dX.js} +1 -1
- package/ui/assets/{goal-ui-D5Xsm2EY.js → goal-ui-CN-kZTEO.js} +2 -2
- package/ui/assets/index-CTPstLYq.css +1 -0
- package/ui/assets/index-Dp81yChE.js +37 -0
- package/ui/assets/inspector-jeyma7HL.js +1 -0
- package/ui/assets/{jobs-ui-DdP2Re41.js → jobs-ui-D6S3LJ4j.js} +1 -1
- package/ui/assets/{music-video-B709JZqa.js → music-video-CDqr0X6G.js} +1 -1
- package/ui/assets/ui-BBM3YCY6.css +1 -0
- package/ui/assets/ui-Cqs1-YNg.js +2 -0
- package/ui/assets/vendor-BWf-WCsY.js +19 -0
- package/ui/assets/{vendor-flow-CKiuzaeM.js → vendor-flow-DPvi8ynA.js} +1 -1
- package/ui/assets/vendor-icons-D3KrGMJY.js +1 -0
- package/ui/assets/vendor-mcp-BSR9acpG.js +60 -0
- package/ui/assets/videobook-b9VODNOA.js +21 -0
- package/ui/index.html +14 -14
- package/ui/assets/confirm-bodies--2sc9DbH.js +0 -1
- package/ui/assets/index-DEKl00St.js +0 -37
- package/ui/assets/index-DWfR79lA.css +0 -1
- package/ui/assets/inspector-EBp3IhvH.js +0 -1
- package/ui/assets/ui-Oirr9VOf.css +0 -1
- package/ui/assets/ui-jRPWsNUw.js +0 -2
- package/ui/assets/vendor-BFupLz37.js +0 -19
- package/ui/assets/vendor-icons-CGqhCIXV.js +0 -1
- package/ui/assets/vendor-mcp-DQEeVZH0.js +0 -67
- package/ui/assets/videobook-0DOLlhG2.js +0 -20
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
3
|
+
import { harDirectory } from "./har-settings.js";
|
|
4
|
+
const OUTCOMES = new Set(["ok", "provider_error", "local_error", "network_error"]);
|
|
5
|
+
let indexCache = null;
|
|
6
|
+
function record(value) {
|
|
7
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8
|
+
}
|
|
9
|
+
function indexRow(line) {
|
|
10
|
+
let value;
|
|
11
|
+
try {
|
|
12
|
+
value = JSON.parse(line);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const row = record(value);
|
|
18
|
+
if (!row || typeof row.ts !== "string" || typeof row.callId !== "string" || typeof row.provider !== "string"
|
|
19
|
+
|| typeof row.endpoint !== "string" || typeof row.outcome !== "string" || !OUTCOMES.has(row.outcome)
|
|
20
|
+
|| typeof row.file !== "string")
|
|
21
|
+
return null;
|
|
22
|
+
const context = record(row.context);
|
|
23
|
+
return {
|
|
24
|
+
ts: row.ts,
|
|
25
|
+
callId: row.callId,
|
|
26
|
+
provider: row.provider,
|
|
27
|
+
endpoint: row.endpoint,
|
|
28
|
+
outcome: row.outcome,
|
|
29
|
+
...(typeof row.status === "number" ? { status: row.status } : {}),
|
|
30
|
+
file: row.file,
|
|
31
|
+
...(context ? { context } : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function readHarIndex(dir = harDirectory()) {
|
|
35
|
+
const path = join(dir, "index.jsonl");
|
|
36
|
+
let key;
|
|
37
|
+
try {
|
|
38
|
+
const info = await stat(path);
|
|
39
|
+
key = `${info.dev}:${info.ino}:${info.size}:${info.mtimeMs}`;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error.code === "ENOENT")
|
|
43
|
+
return [];
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
if (indexCache?.path === path && indexCache.key === key)
|
|
47
|
+
return indexCache.rows;
|
|
48
|
+
const text = await readFile(path, "utf8");
|
|
49
|
+
const rows = text.split("\n").flatMap((line) => {
|
|
50
|
+
const row = line.trim() ? indexRow(line) : null;
|
|
51
|
+
return row ? [row] : [];
|
|
52
|
+
});
|
|
53
|
+
indexCache = { path, key, rows };
|
|
54
|
+
return rows;
|
|
55
|
+
}
|
|
56
|
+
export function captureFilePath(dir, file) {
|
|
57
|
+
if (!file.endsWith(".har") || isAbsolute(file) || file.includes("\0"))
|
|
58
|
+
return null;
|
|
59
|
+
const parts = file.split(/[\\/]/u);
|
|
60
|
+
if (parts.some((part) => part === "" || part === "." || part === ".."))
|
|
61
|
+
return null;
|
|
62
|
+
return join(dir, ...parts);
|
|
63
|
+
}
|
|
64
|
+
function isHar(value) {
|
|
65
|
+
const log = record(record(value)?.log);
|
|
66
|
+
return Boolean(log && Array.isArray(log.entries));
|
|
67
|
+
}
|
|
68
|
+
export async function readCaptureFile(dir, file) {
|
|
69
|
+
const path = captureFilePath(dir, file);
|
|
70
|
+
if (!path)
|
|
71
|
+
return null;
|
|
72
|
+
try {
|
|
73
|
+
if (!(await lstat(path)).isFile())
|
|
74
|
+
return null;
|
|
75
|
+
const location = relative(await realpath(dir), await realpath(path));
|
|
76
|
+
if (location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location))
|
|
77
|
+
return null;
|
|
78
|
+
const bytes = await readFile(path);
|
|
79
|
+
const har = JSON.parse(bytes.toString("utf8"));
|
|
80
|
+
return isHar(har) ? { path, bytes, har } : null;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error instanceof SyntaxError || error.code === "ENOENT")
|
|
84
|
+
return null;
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export async function captureFileExists(dir, file) {
|
|
89
|
+
const path = captureFilePath(dir, file);
|
|
90
|
+
if (!path)
|
|
91
|
+
return false;
|
|
92
|
+
try {
|
|
93
|
+
return (await lstat(path)).isFile();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readFile, readdir, stat, unlink, writeFile, rename } from "node:fs/promises";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { harDirectory, readHarSettings } from "./har-settings.js";
|
|
5
|
+
import { maintainHarCaptures } from "./har-capture.js";
|
|
6
|
+
import { logger } from "./logger.js";
|
|
7
|
+
let nextSweep = 0;
|
|
8
|
+
async function captureFiles(dir, root = dir) {
|
|
9
|
+
const result = [];
|
|
10
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
11
|
+
const path = join(dir, entry.name);
|
|
12
|
+
if (entry.isDirectory())
|
|
13
|
+
result.push(...await captureFiles(path, root));
|
|
14
|
+
else if (entry.isFile() && entry.name.endsWith(".har")) {
|
|
15
|
+
const info = await stat(path);
|
|
16
|
+
result.push({ file: relative(root, path), bytes: info.size, time: info.mtimeMs });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
export async function pruneHarCaptures(dir, settings, now = Date.now()) {
|
|
22
|
+
if (settings.mode === "off")
|
|
23
|
+
return;
|
|
24
|
+
let files;
|
|
25
|
+
try {
|
|
26
|
+
files = await captureFiles(dir);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code === "ENOENT")
|
|
30
|
+
return;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
files.sort((a, b) => a.time - b.time || a.file.localeCompare(b.file));
|
|
34
|
+
let index = "";
|
|
35
|
+
try {
|
|
36
|
+
index = await readFile(join(dir, "index.jsonl"), "utf8");
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code !== "ENOENT")
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
const rows = index.split("\n").filter(Boolean).map((line) => ({ line, value: JSON.parse(line) }));
|
|
43
|
+
let bytes = files.reduce((sum, file) => sum + file.bytes, 0) + Buffer.byteLength(index);
|
|
44
|
+
const keep = new Set(files.map((file) => file.file));
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
if (file.time >= now - settings.maxAgeDays * 86_400_000 && bytes <= settings.maxTotalMB * 1024 * 1024)
|
|
47
|
+
continue;
|
|
48
|
+
await unlink(join(dir, file.file));
|
|
49
|
+
keep.delete(file.file);
|
|
50
|
+
bytes -= file.bytes + rows.filter((row) => row.value.file === file.file).reduce((sum, row) => sum + Buffer.byteLength(row.line) + 1, 0);
|
|
51
|
+
}
|
|
52
|
+
const retained = rows.filter((row) => keep.has(row.value.file)).map((row) => row.line + "\n").join("");
|
|
53
|
+
const temporary = join(dir, `.index-${randomUUID()}.tmp`);
|
|
54
|
+
await writeFile(temporary, retained, { mode: 0o600 });
|
|
55
|
+
await rename(temporary, join(dir, "index.jsonl"));
|
|
56
|
+
}
|
|
57
|
+
export async function sweepHarCaptures(force = false) {
|
|
58
|
+
if (!force && Date.now() < nextSweep)
|
|
59
|
+
return;
|
|
60
|
+
nextSweep = Date.now() + 86_400_000;
|
|
61
|
+
try {
|
|
62
|
+
const settings = await readHarSettings();
|
|
63
|
+
if (settings.mode === "off") {
|
|
64
|
+
nextSweep = 0;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!await maintainHarCaptures(() => pruneHarCaptures(harDirectory(), settings)))
|
|
68
|
+
nextSweep = Date.now() + 60_000;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logger.warn("har-capture-failed", { error });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { appState } from "./app-state.js";
|
|
4
|
+
import { dataLayout } from "./data-home.js";
|
|
5
|
+
export const harSettingsSchema = z.object({
|
|
6
|
+
mode: z.enum(["off", "failures", "all"]).default("off"),
|
|
7
|
+
maxAgeDays: z.number().positive().finite().default(30),
|
|
8
|
+
maxTotalMB: z.number().positive().finite().default(512),
|
|
9
|
+
});
|
|
10
|
+
export function harDirectory() {
|
|
11
|
+
const layout = dataLayout();
|
|
12
|
+
return join(layout.root, ...(layout.kind === "checkout" ? ["runtime"] : []), "har");
|
|
13
|
+
}
|
|
14
|
+
export async function readHarSettings(store = appState, env = process.env) {
|
|
15
|
+
const stored = harSettingsSchema.safeParse(await store.get("harCapture") ?? {});
|
|
16
|
+
const settings = stored.success ? stored.data : harSettingsSchema.parse({});
|
|
17
|
+
const override = env.VIDEOBOOK_HAR_CAPTURE?.trim();
|
|
18
|
+
if (!override)
|
|
19
|
+
return settings;
|
|
20
|
+
const mode = harSettingsSchema.shape.mode.safeParse(override);
|
|
21
|
+
return { ...settings, mode: mode.success ? mode.data : "off" };
|
|
22
|
+
}
|
|
23
|
+
export async function writeHarSettings(value, store = appState) {
|
|
24
|
+
const settings = harSettingsSchema.parse(value);
|
|
25
|
+
await store.set("harCapture", settings);
|
|
26
|
+
return readHarSettings(store);
|
|
27
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { uploadFile } from "@apicity/alibaba";
|
|
1
|
+
import { uploadAlibabaFile } from "../usage-providers.js";
|
|
3
2
|
import { prepareAssetForUpload, uploadBytesToFreeHost } from "../upload.js";
|
|
4
3
|
import { getApiKey } from "../api-key.js";
|
|
5
4
|
import { logger } from "../logger.js";
|
|
@@ -11,8 +10,7 @@ export async function uploadAssetToAlibaba(engine, assetId, filename, model) {
|
|
|
11
10
|
const apiKey = getApiKey("DASHSCOPE_API_KEY");
|
|
12
11
|
if (apiKey) {
|
|
13
12
|
try {
|
|
14
|
-
|
|
15
|
-
return await uploadFile(provider, {
|
|
13
|
+
return await uploadAlibabaFile({ apiKey, timeout: 60_000 }, {
|
|
16
14
|
model,
|
|
17
15
|
data,
|
|
18
16
|
filename: hashedFilename,
|
package/dist/jobs/_fal-upload.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { uploadFile } from "@apicity/fal";
|
|
1
|
+
import { uploadFalFile } from "../usage-providers.js";
|
|
3
2
|
import { prepareAssetForUpload, uploadBytesToFreeHost, } from "../upload.js";
|
|
4
3
|
import { getApiKey } from "../api-key.js";
|
|
5
4
|
import { logger } from "../logger.js";
|
|
@@ -10,8 +9,7 @@ async function uploadPreparedToFal(data, hashedFilename, contentType) {
|
|
|
10
9
|
const apiKey = getApiKey("FAL_API_KEY");
|
|
11
10
|
if (apiKey) {
|
|
12
11
|
try {
|
|
13
|
-
|
|
14
|
-
return await uploadFile(provider, {
|
|
12
|
+
return await uploadFalFile({ apiKey, timeout: 60_000 }, {
|
|
15
13
|
data,
|
|
16
14
|
filename: hashedFilename,
|
|
17
15
|
contentType,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createFal } from "../usage-providers.js";
|
|
2
|
-
import { submitMediaJob } from "
|
|
2
|
+
import { submitMediaJob } from "../usage-providers.js";
|
|
3
3
|
import { FalError } from "@apicity/fal";
|
|
4
4
|
import { SeedreamProImageToImageRequestSchema } from "@apicity/kie/zod";
|
|
5
5
|
import { setTimeout as delay } from "node:timers/promises";
|
|
@@ -99,6 +99,7 @@ function kieImageEditAdapter(model, build) {
|
|
|
99
99
|
});
|
|
100
100
|
return {
|
|
101
101
|
id: model.apiId,
|
|
102
|
+
envKey: "KIE_API_KEY",
|
|
102
103
|
uploadAsset: (engine, artifactId, filename) => uploadAssetToKie(engine, artifactId, filename),
|
|
103
104
|
buildSemanticRequest: (input) => buildRequest({ ...input, imageUrls: [input.imageUrls[0]] }),
|
|
104
105
|
buildRegionRequest: buildRequest,
|
|
@@ -166,6 +167,7 @@ function falImageEditAdapter(model) {
|
|
|
166
167
|
const buildRequest = (input) => toProviderRequest(model.apiId, catalogEditRequest(model, input));
|
|
167
168
|
return {
|
|
168
169
|
id: model.apiId,
|
|
170
|
+
envKey: "FAL_API_KEY",
|
|
169
171
|
uploadAsset: (engine, artifactId, filename) => uploadAssetToFal(engine, artifactId, filename),
|
|
170
172
|
buildSemanticRequest: (input) => buildRequest({ ...input, imageUrls: [input.imageUrls[0]] }),
|
|
171
173
|
buildRegionRequest: buildRequest,
|
package/dist/jobs/_kie-upload.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { uploadFile } from "@apicity/kie";
|
|
1
|
+
import { uploadKieFile } from "../usage-providers.js";
|
|
3
2
|
import { prepareAssetForUpload, uploadBytesToFreeHost, } from "../upload.js";
|
|
4
3
|
import { getApiKey } from "../api-key.js";
|
|
5
4
|
import { logger } from "../logger.js";
|
|
@@ -12,9 +11,8 @@ async function uploadPreparedToKie(data, hashedFilename, contentType) {
|
|
|
12
11
|
const apiKey = getApiKey("KIE_API_KEY");
|
|
13
12
|
if (apiKey) {
|
|
14
13
|
try {
|
|
15
|
-
const provider = createKie({ apiKey, timeout: 120_000 });
|
|
16
14
|
const blob = new Blob([new Uint8Array(data)], { type: contentType });
|
|
17
|
-
return await
|
|
15
|
+
return await uploadKieFile({ apiKey, timeout: 120_000 }, blob, hashedFilename);
|
|
18
16
|
}
|
|
19
17
|
catch (err) {
|
|
20
18
|
logger.warn("kie-upload-fallback", { error: err });
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { probeVideo } from "../ffmpeg.js";
|
|
3
|
+
import { logger } from "../logger.js";
|
|
4
|
+
import { queue, registerJobHandler } from "../queue.js";
|
|
5
|
+
import { invalidVideoProxyMetadata, probeStoredVideoFile, videoProxyAssessmentMatches, videoProxyMetadata, videoProxyValidationError, } from "../video-proxy.js";
|
|
6
|
+
const PROXY_FILENAME = "proxy.mp4";
|
|
7
|
+
export function scheduleVideoProxyAssessment(input) {
|
|
8
|
+
const jobId = `assess-video-proxy-${input.bookId}-${input.artifactId}-${input.proxyFile.objectHash}`;
|
|
9
|
+
void queue.enqueue(jobId, {
|
|
10
|
+
type: "assess_video_proxy",
|
|
11
|
+
bookId: input.bookId,
|
|
12
|
+
proxyAssetId: input.artifactId,
|
|
13
|
+
sourceFilename: input.sourceFile.name,
|
|
14
|
+
sourceHash: input.sourceFile.objectHash,
|
|
15
|
+
proxyHash: input.proxyFile.objectHash,
|
|
16
|
+
sourceProfile: input.sourceProfile,
|
|
17
|
+
}).catch((error) => {
|
|
18
|
+
logger.error("proxy-assessment-enqueue-failed", { assetId: input.artifactId, error });
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function scheduleMediaDurationBackfill(input) {
|
|
22
|
+
const jobId = `backfill-media-duration-${input.bookId}-${input.artifactId}-${input.mediaFile.objectHash}`;
|
|
23
|
+
void queue.enqueue(jobId, {
|
|
24
|
+
type: "backfill_media_duration",
|
|
25
|
+
bookId: input.bookId,
|
|
26
|
+
durationAssetId: input.artifactId,
|
|
27
|
+
mediaFilename: input.mediaFile.name,
|
|
28
|
+
mediaHash: input.mediaFile.objectHash,
|
|
29
|
+
}).catch((error) => {
|
|
30
|
+
logger.error("duration-backfill-enqueue-failed", { assetId: input.artifactId, error });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function currentFile(engine, artifactId, name, objectHash) {
|
|
34
|
+
const manifest = await engine.files.manifest(artifactId);
|
|
35
|
+
if (!manifest.ok)
|
|
36
|
+
return null;
|
|
37
|
+
return manifest.value.files.find((file) => file.name === name && file.objectHash === objectHash) ?? null;
|
|
38
|
+
}
|
|
39
|
+
export async function assessStoredVideoProxy(engine, assetId, sourceFile, proxyFile, sourceProfile) {
|
|
40
|
+
let assessment;
|
|
41
|
+
try {
|
|
42
|
+
const proxy = await probeStoredVideoFile(engine, assetId, proxyFile.name);
|
|
43
|
+
const error = videoProxyValidationError(sourceProfile, proxy);
|
|
44
|
+
assessment = error
|
|
45
|
+
? invalidVideoProxyMetadata(sourceFile, proxyFile, error)
|
|
46
|
+
: videoProxyMetadata(sourceFile, proxyFile, proxy);
|
|
47
|
+
}
|
|
48
|
+
catch (cause) {
|
|
49
|
+
const error = cause instanceof Error ? cause.message : String(cause);
|
|
50
|
+
assessment = invalidVideoProxyMetadata(sourceFile, proxyFile, error);
|
|
51
|
+
}
|
|
52
|
+
await engine.metadata.artifacts.write(assetId, "proxy", assessment);
|
|
53
|
+
return assessment.valid;
|
|
54
|
+
}
|
|
55
|
+
async function runVideoProxyAssessment(engine, payload) {
|
|
56
|
+
const { proxyAssetId } = payload;
|
|
57
|
+
const sourceFile = await currentFile(engine, proxyAssetId, payload.sourceFilename, payload.sourceHash);
|
|
58
|
+
const proxyFile = await currentFile(engine, proxyAssetId, PROXY_FILENAME, payload.proxyHash);
|
|
59
|
+
if (!sourceFile || !proxyFile)
|
|
60
|
+
return { skipped: true };
|
|
61
|
+
const existing = await engine.metadata.artifacts.read(proxyAssetId, "proxy");
|
|
62
|
+
if (existing.ok && videoProxyAssessmentMatches(existing.value, sourceFile, proxyFile)) {
|
|
63
|
+
return { skipped: true };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
valid: await assessStoredVideoProxy(engine, proxyAssetId, sourceFile, proxyFile, payload.sourceProfile),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function runMediaDurationBackfill(engine, payload, signal) {
|
|
70
|
+
const { durationAssetId } = payload;
|
|
71
|
+
const metadata = await engine.metadata.artifacts.read(durationAssetId, "original");
|
|
72
|
+
if (!metadata.ok || typeof metadata.value.duration === "number")
|
|
73
|
+
return { skipped: true };
|
|
74
|
+
if (!await currentFile(engine, durationAssetId, payload.mediaFilename, payload.mediaHash)) {
|
|
75
|
+
return { skipped: true };
|
|
76
|
+
}
|
|
77
|
+
const dir = await engine.workspaces.resolveArtifact(durationAssetId);
|
|
78
|
+
if (!dir.ok)
|
|
79
|
+
throw new Error(`backfill_media_duration: ${dir.error.message}`);
|
|
80
|
+
const probe = await probeVideo(join(dir.value, payload.mediaFilename), { signal });
|
|
81
|
+
if (!Number.isFinite(probe.duration) || probe.duration <= 0)
|
|
82
|
+
return { skipped: true };
|
|
83
|
+
const written = await engine.metadata.artifacts.write(durationAssetId, "original", {
|
|
84
|
+
...metadata.value,
|
|
85
|
+
duration: probe.duration,
|
|
86
|
+
});
|
|
87
|
+
if (!written.ok)
|
|
88
|
+
throw new Error(written.error.message);
|
|
89
|
+
return { duration: probe.duration };
|
|
90
|
+
}
|
|
91
|
+
export function registerAssessMediaProbeJobs(engine) {
|
|
92
|
+
registerJobHandler("assess_video_proxy", async (payload) => runVideoProxyAssessment(engine, payload));
|
|
93
|
+
registerJobHandler("backfill_media_duration", async (payload, { signal }) => runMediaDurationBackfill(engine, payload, signal));
|
|
94
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { withHarLocalFailure } from "../har-capture.js";
|
|
2
|
+
import { generationHarEndpoint } from "../media/har-endpoint.js";
|
|
3
|
+
import { withBookContext } from "../book-context.js";
|
|
1
4
|
import { isSeedanceEdit } from "../media/seedance-edit.js";
|
|
2
5
|
import { registerJobHandler } from "../queue.js";
|
|
3
6
|
import { assertProviderPayload } from "../provider-prompt-transform.js";
|
|
@@ -41,6 +44,8 @@ export async function generationSourceCostHints(engine, refs, job) {
|
|
|
41
44
|
function generationMeta(job, model, refs) {
|
|
42
45
|
return {
|
|
43
46
|
model: model.id,
|
|
47
|
+
runId: job.runId,
|
|
48
|
+
attemptId: job.generationId ?? job.assetId,
|
|
44
49
|
media: model.media,
|
|
45
50
|
prompt: job.prompt ?? "",
|
|
46
51
|
sourceImageIds: refs.sourceImageIds,
|
|
@@ -48,7 +53,7 @@ function generationMeta(job, model, refs) {
|
|
|
48
53
|
sourceAudioIds: refs.sourceAudioIds,
|
|
49
54
|
};
|
|
50
55
|
}
|
|
51
|
-
|
|
56
|
+
async function generateMedia(engine, job, ctx) {
|
|
52
57
|
const model = resolveModel(job.model);
|
|
53
58
|
const adapter = adapterFor(model);
|
|
54
59
|
requireApiKey(adapter.envKey);
|
|
@@ -81,6 +86,10 @@ export async function runGenerateMedia(engine, job, ctx) {
|
|
|
81
86
|
}
|
|
82
87
|
return { success: true, model: model.id, taskId: result.taskId, status: result.kind };
|
|
83
88
|
}
|
|
89
|
+
export async function runGenerateMedia(engine, job, ctx) {
|
|
90
|
+
return withBookContext({ bookId: job.bookId, engine, operation: "generate_media", artifactId: job.assetId,
|
|
91
|
+
attemptId: job.generationId ?? job.assetId, runId: job.runId, model: job.model }, () => withHarLocalFailure({ provider: resolveModel(job.model).provider, endpoint: generationHarEndpoint(resolveModel(job.model)), input: job }, () => generateMedia(engine, job, ctx)));
|
|
92
|
+
}
|
|
84
93
|
export function registerGenerateMediaJob(engine) {
|
|
85
94
|
registerJobHandler("generate_media", (payload, ctx) => runGenerateMedia(engine, payload, ctx));
|
|
86
95
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { submitMediaJob
|
|
1
|
+
import { submitMediaJob } from "../usage-providers.js";
|
|
2
2
|
import { GrokImagineImage2ImageEditRequestSchema, GrokImagineImage2SegmentMapRequestSchema, SeedreamProLayerDecompositionRequestSchema, } from "@apicity/kie/zod";
|
|
3
3
|
import sharp from "sharp";
|
|
4
4
|
import { KIE_RESULT_TIMEOUT_MS, kieProvider, parseResultUrls, waitForKieResult, } from "./_kie-poll.js";
|
|
5
5
|
import { GROK_EXACT_MASK_ADAPTER_ID, imageEditAdapter, IMAGE_EDIT_ADAPTERS, } from "./_image-edit-adapter.js";
|
|
6
6
|
import { DEFAULT_IMAGE_EDIT_MODEL_ID } from "../image-edit-models.js";
|
|
7
|
+
import { requireApiKey } from "../api-key.js";
|
|
7
8
|
import { compositeImageRegionEdit, imageRegionEditPrompt } from "../image-region-edit.js";
|
|
8
9
|
import { IMAGE_SUBJECT_MAP_VERSION, applyGrokSegmentsToSubjects, composeImageSubjectRemoval, exactEditMaskIndexes, grokMaskIndex, parseGrokSegmentMapResult, persistSeedreamSubjectLayers, readImageSubjectMapRecord, resolveImageSubjectSource, writeCurrentImageSubjectAnalysisRecord, } from "../image-subject-map.js";
|
|
9
10
|
import { enqueueSearchIndex } from "./enqueue-search-index.js";
|
|
@@ -534,6 +535,7 @@ export async function previewImageSubjectEditRequest(engine, payload, imageUrls)
|
|
|
534
535
|
: adapter.buildSemanticRequest(input);
|
|
535
536
|
}
|
|
536
537
|
async function runAdapterImageSubjectEdit(engine, payload, adapter, route, prepared, sourceFilename, signal) {
|
|
538
|
+
requireApiKey(adapter.envKey);
|
|
537
539
|
const stored = await readStoredImageSubjectEditTask(engine, payload.imageAssetId);
|
|
538
540
|
const storedMatch = stored?.adapterId === adapter.id ? stored : null;
|
|
539
541
|
let request = storedMatch?.request ?? null;
|
package/dist/jobs/kie-paygate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { withHarLocalFailure } from "../har-capture.js";
|
|
1
2
|
import { createKie } from "../usage-providers.js";
|
|
2
|
-
import { submitMediaJob } from "
|
|
3
|
+
import { submitMediaJob } from "../usage-providers.js";
|
|
3
4
|
import { mintOtp } from "@apicity/cost";
|
|
4
5
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
6
|
import { randomBytes } from "node:crypto";
|
|
@@ -81,7 +82,7 @@ function createKieProvider(apiKey) {
|
|
|
81
82
|
paygate: { secret: ensurePaygateSecret() },
|
|
82
83
|
});
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
+
async function submitKieTaskInner(engine, ctx, args) {
|
|
85
86
|
const request = args.request;
|
|
86
87
|
assertProviderPayload(request, {
|
|
87
88
|
toolName: args.taskType,
|
|
@@ -129,7 +130,7 @@ export async function submitKieTask(engine, ctx, args) {
|
|
|
129
130
|
ctx.setLease({ kind: "task", taskId });
|
|
130
131
|
return taskId;
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
+
async function submitQueuedKieTaskInner(engine, task) {
|
|
133
134
|
if (task.taskId !== QUEUED_TASK_ID)
|
|
134
135
|
throw new Error(`KIE task ${task.artifactId} is not locally queued`);
|
|
135
136
|
const ownerId = await engine.jobs.pending.getOwner(task.artifactId, QUEUED_TASK_ID);
|
|
@@ -175,3 +176,9 @@ export async function submitQueuedKieTask(engine, task) {
|
|
|
175
176
|
await engine.metadata.artifacts.write(task.artifactId, "kie-task-id", { taskId });
|
|
176
177
|
return writeResult.value.task;
|
|
177
178
|
}
|
|
179
|
+
export async function submitKieTask(engine, ctx, args) {
|
|
180
|
+
return withHarLocalFailure({ provider: "kie", endpoint: "api.v1.jobs.createTask", input: args.request }, () => submitKieTaskInner(engine, ctx, args));
|
|
181
|
+
}
|
|
182
|
+
export async function submitQueuedKieTask(engine, task) {
|
|
183
|
+
return withHarLocalFailure({ provider: "kie", endpoint: "api.v1.jobs.createTask", input: task }, () => submitQueuedKieTaskInner(engine, task));
|
|
184
|
+
}
|
|
@@ -38,6 +38,7 @@ import { registerLabelMusicSectionsJob } from "./label-music-sections.js";
|
|
|
38
38
|
import { registerEditingMomentsJob } from "./analyze-editing-moments.js";
|
|
39
39
|
import { registerExtractWaveformJob } from "./extract-waveform.js";
|
|
40
40
|
import { registerExtractVideoPosterJob } from "./extract-video-poster.js";
|
|
41
|
+
import { registerAssessMediaProbeJobs } from "./assess-media-probes.js";
|
|
41
42
|
import { registerReplaceVideoAudioJob } from "./replace-video-audio.js";
|
|
42
43
|
import { registerSpliceVideoJob } from "./splice-video.js";
|
|
43
44
|
import { registerSmoothSceneConcatJob } from "./smooth-scene-concat.js";
|
|
@@ -109,6 +110,7 @@ export function registerAllJobHandlers(engine, books = bookManager) {
|
|
|
109
110
|
registerExtractAudioJob(engine);
|
|
110
111
|
registerExtractWaveformJob(engine);
|
|
111
112
|
registerExtractVideoPosterJob(engine);
|
|
113
|
+
registerAssessMediaProbeJobs(engine);
|
|
112
114
|
registerAnalyzeMusicJob(engine);
|
|
113
115
|
registerPlanGoalJob(engine);
|
|
114
116
|
registerAssembleGoalJob(engine);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withBookContext } from "../book-context.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
4
|
import { LIGHTNING_VIDEO_MODEL, payloadPrompt, submittedGenerationPrompt, } from "../lightning-defaults.js";
|
|
@@ -1006,6 +1007,7 @@ async function generationTool(engine, document, cell, bookId, options = {}) {
|
|
|
1006
1007
|
await queue.enqueue(jobId, {
|
|
1007
1008
|
...prepared.imageEdit.jobInput,
|
|
1008
1009
|
type: "generate_image_subject_edit", imageAssetId, bookId,
|
|
1010
|
+
runId: options.runId, model: prepared.model,
|
|
1009
1011
|
notebookId: document.id, producerCellId: cell.id, outputCellId, jobId,
|
|
1010
1012
|
managedByNotebook: true,
|
|
1011
1013
|
...(generationId ? { generationId } : {}),
|
|
@@ -1013,7 +1015,8 @@ async function generationTool(engine, document, cell, bookId, options = {}) {
|
|
|
1013
1015
|
response = { content: [{ type: "text", text: JSON.stringify({ image_asset_id: imageAssetId }) }] };
|
|
1014
1016
|
}
|
|
1015
1017
|
else {
|
|
1016
|
-
response = await
|
|
1018
|
+
response = await withBookContext({ bookId, engine, operation: prepared.tool, runId: options.runId,
|
|
1019
|
+
attemptId: generationId ?? undefined, model: prepared.model }, () => definition.handler(prepared.input));
|
|
1017
1020
|
}
|
|
1018
1021
|
}
|
|
1019
1022
|
catch (cause) {
|
|
@@ -2606,6 +2609,21 @@ async function finishAwaitingNotebookCell(engine, input) {
|
|
|
2606
2609
|
throw new Error(refreshed.error.message);
|
|
2607
2610
|
return { outputArtifactId, document: refreshed.value };
|
|
2608
2611
|
}
|
|
2612
|
+
function withFailedGenerationPlan(document, cellId, runId, error, updatedAt) {
|
|
2613
|
+
return {
|
|
2614
|
+
...document,
|
|
2615
|
+
generationPlans: (document.generationPlans ?? []).map((record) => record.cellId === cellId
|
|
2616
|
+
&& (record.status === "queued" || record.status === "awaiting-output")
|
|
2617
|
+
&& generationPlanClaimRunId(record.plan) === runId
|
|
2618
|
+
? {
|
|
2619
|
+
...record,
|
|
2620
|
+
status: "failed",
|
|
2621
|
+
updatedAt,
|
|
2622
|
+
error: truncateGenerationError(error),
|
|
2623
|
+
}
|
|
2624
|
+
: record),
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2609
2627
|
async function failAwaitingNotebookCell(engine, input, message) {
|
|
2610
2628
|
const updatedAt = new Date().toISOString();
|
|
2611
2629
|
const result = await mutateNotebook(engine, input.notebookId, "fail_notebook_provider_output", (document) => {
|
|
@@ -2625,7 +2643,7 @@ async function failAwaitingNotebookCell(engine, input, message) {
|
|
|
2625
2643
|
: undefined) ?? cell.id;
|
|
2626
2644
|
const current = executionState(document)[failTargetId]
|
|
2627
2645
|
?? executionState(document)[cell.id];
|
|
2628
|
-
const withFailure = withExecution(document, failTargetId, {
|
|
2646
|
+
const withFailure = withExecution(withFailedGenerationPlan(document, cell.id, input.runId, message, updatedAt), failTargetId, {
|
|
2629
2647
|
fingerprint: current?.fingerprint
|
|
2630
2648
|
?? notebookCellFingerprint(document, cell),
|
|
2631
2649
|
status: "error",
|
package/dist/jobs/xai-paygate.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withHarCapture } from "../har-capture.js";
|
|
1
2
|
import { createXai } from "../usage-providers.js";
|
|
2
3
|
import { mintOtp } from "@apicity/cost";
|
|
3
4
|
import { ensurePaygateSecret } from "./kie-paygate.js";
|
|
@@ -21,6 +22,8 @@ export function mintXaiOtp(dotPath, request, ttlSeconds = 10 * 60) {
|
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
export async function callXaiPaid(method, dotPath, request) {
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
return withHarCapture({ provider: "xai", endpoint: dotPath, input: request }, () => {
|
|
26
|
+
assertProviderPayload(request, { toolName: dotPath, wire: true });
|
|
27
|
+
return method(request, { otp: mintXaiOtp(dotPath, request) });
|
|
28
|
+
});
|
|
26
29
|
}
|