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.
Files changed (98) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +6 -2
  3. package/dist/archive-b2.js +1 -1
  4. package/dist/asset-media-projection.js +161 -0
  5. package/dist/cli/args.js +42 -3
  6. package/dist/cli/config-command.js +17 -1
  7. package/dist/cli/doctor-command.js +15 -0
  8. package/dist/cli/har-command.js +86 -0
  9. package/dist/cli/main.js +13 -1
  10. package/dist/command-reference.js +3 -1
  11. package/dist/content-store.js +2 -1
  12. package/dist/diagnostics.js +2 -2
  13. package/dist/env-secrets.js +1 -0
  14. package/dist/generation-har.js +106 -0
  15. package/dist/generation-reference-mentions.js +19 -0
  16. package/dist/goal-plan-v3.js +11 -1
  17. package/dist/goal-plan.js +1 -1
  18. package/dist/goal-store.js +2 -2
  19. package/dist/har-capture.js +175 -0
  20. package/dist/har-export.js +238 -0
  21. package/dist/har-index.js +98 -0
  22. package/dist/har-retention.js +73 -0
  23. package/dist/har-settings.js +27 -0
  24. package/dist/jobs/_alibaba-upload.js +2 -4
  25. package/dist/jobs/_fal-upload.js +2 -4
  26. package/dist/jobs/_image-edit-adapter.js +3 -1
  27. package/dist/jobs/_kie-upload.js +2 -4
  28. package/dist/jobs/assess-media-probes.js +94 -0
  29. package/dist/jobs/generate-media.js +10 -1
  30. package/dist/jobs/image-subjects.js +3 -1
  31. package/dist/jobs/kie-paygate.js +10 -3
  32. package/dist/jobs/register-all.js +2 -0
  33. package/dist/jobs/run-notebook.js +20 -2
  34. package/dist/jobs/xai-paygate.js +5 -2
  35. package/dist/library-asset-projection.js +141 -0
  36. package/dist/lightning-defaults.js +4 -4
  37. package/dist/media/adapters/kie.js +2 -1
  38. package/dist/media/catalog.js +8 -2
  39. package/dist/media/cost.js +1 -1
  40. package/dist/media/descriptor.js +7 -4
  41. package/dist/media/frontier-gaps.js +0 -1
  42. package/dist/media/har-endpoint.js +25 -0
  43. package/dist/media/json-schema.js +3 -0
  44. package/dist/media/preferred-defaults.js +161 -0
  45. package/dist/media/prompt-dialects.js +9 -1
  46. package/dist/media-library.js +158 -153
  47. package/dist/notebook-sticky-settings.js +60 -0
  48. package/dist/package-version.js +18 -0
  49. package/dist/posts/dropbox-auth.js +1 -1
  50. package/dist/posts/post-metrics.js +2 -1
  51. package/dist/provider-usage.js +1 -18
  52. package/dist/queue.js +12 -1
  53. package/dist/serve.js +2 -0
  54. package/dist/tools/api-status.js +1 -1
  55. package/dist/tools/generate-media.js +3 -0
  56. package/dist/tools/generation-payload.js +17 -2
  57. package/dist/tools/get-generation-har.js +43 -0
  58. package/dist/tools/index.js +4 -0
  59. package/dist/tools/list-cell-generations.js +19 -0
  60. package/dist/tools/reconcile-project-work.js +52 -193
  61. package/dist/tools/set-har-capture.js +30 -0
  62. package/dist/tools/videobook-domain.js +16 -1
  63. package/dist/upload.js +2 -1
  64. package/dist/usage-providers.js +48 -10
  65. package/dist/workspace-action-groups.js +2 -2
  66. package/dist/x/account.js +1 -1
  67. package/dist/x/authenticated-handle.js +1 -1
  68. package/dist/x/oauth-flow.js +1 -1
  69. package/dist/x/post-to-x.js +2 -1
  70. package/dist/x/x-tokens.js +2 -1
  71. package/package.json +15 -14
  72. package/ui/assets/{alive-captions-duTKsyll.js → alive-captions-CnLK22ED.js} +1 -1
  73. package/ui/assets/confirm-bodies-BaalBxp1.js +1 -0
  74. package/ui/assets/{fullscreen-COrmiHPn.js → fullscreen-DTJQJ6dX.js} +1 -1
  75. package/ui/assets/{goal-ui-D5Xsm2EY.js → goal-ui-CN-kZTEO.js} +2 -2
  76. package/ui/assets/index-CTPstLYq.css +1 -0
  77. package/ui/assets/index-Dp81yChE.js +37 -0
  78. package/ui/assets/inspector-jeyma7HL.js +1 -0
  79. package/ui/assets/{jobs-ui-DdP2Re41.js → jobs-ui-D6S3LJ4j.js} +1 -1
  80. package/ui/assets/{music-video-B709JZqa.js → music-video-CDqr0X6G.js} +1 -1
  81. package/ui/assets/ui-BBM3YCY6.css +1 -0
  82. package/ui/assets/ui-Cqs1-YNg.js +2 -0
  83. package/ui/assets/vendor-BWf-WCsY.js +19 -0
  84. package/ui/assets/{vendor-flow-CKiuzaeM.js → vendor-flow-DPvi8ynA.js} +1 -1
  85. package/ui/assets/vendor-icons-D3KrGMJY.js +1 -0
  86. package/ui/assets/vendor-mcp-BSR9acpG.js +60 -0
  87. package/ui/assets/videobook-b9VODNOA.js +21 -0
  88. package/ui/index.html +14 -14
  89. package/ui/assets/confirm-bodies--2sc9DbH.js +0 -1
  90. package/ui/assets/index-DEKl00St.js +0 -37
  91. package/ui/assets/index-DWfR79lA.css +0 -1
  92. package/ui/assets/inspector-EBp3IhvH.js +0 -1
  93. package/ui/assets/ui-Oirr9VOf.css +0 -1
  94. package/ui/assets/ui-jRPWsNUw.js +0 -2
  95. package/ui/assets/vendor-BFupLz37.js +0 -19
  96. package/ui/assets/vendor-icons-CGqhCIXV.js +0 -1
  97. package/ui/assets/vendor-mcp-DQEeVZH0.js +0 -67
  98. 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 { createAlibaba } from "../usage-providers.js";
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
- const provider = createAlibaba({ apiKey, timeout: 60_000 });
15
- return await uploadFile(provider, {
13
+ return await uploadAlibabaFile({ apiKey, timeout: 60_000 }, {
16
14
  model,
17
15
  data,
18
16
  filename: hashedFilename,
@@ -1,5 +1,4 @@
1
- import { createFal } from "../usage-providers.js";
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
- const provider = createFal({ apiKey, timeout: 60_000 });
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 "@apicity/kie";
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,
@@ -1,5 +1,4 @@
1
- import { createKie } from "../usage-providers.js";
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 uploadFile(provider, blob, hashedFilename);
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
- export async function runGenerateMedia(engine, job, ctx) {
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, } from "@apicity/kie";
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;
@@ -1,5 +1,6 @@
1
+ import { withHarLocalFailure } from "../har-capture.js";
1
2
  import { createKie } from "../usage-providers.js";
2
- import { submitMediaJob } from "@apicity/kie";
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
- export async function submitKieTask(engine, ctx, args) {
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
- export async function submitQueuedKieTask(engine, task) {
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 definition.handler(prepared.input);
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",
@@ -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
- assertProviderPayload(request, { toolName: dotPath, wire: true });
25
- return method(request, { otp: mintXaiOtp(dotPath, request) });
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
  }