videobook 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +6 -2
  3. package/dist/archive-b2.js +1 -1
  4. package/dist/cli/args.js +42 -3
  5. package/dist/cli/config-command.js +17 -1
  6. package/dist/cli/doctor-command.js +15 -0
  7. package/dist/cli/har-command.js +86 -0
  8. package/dist/cli/main.js +13 -1
  9. package/dist/command-reference.js +5 -1
  10. package/dist/content-store.js +2 -1
  11. package/dist/diagnostics.js +1 -1
  12. package/dist/env-secrets.js +1 -0
  13. package/dist/generation-har.js +106 -0
  14. package/dist/har-capture.js +142 -0
  15. package/dist/har-export.js +238 -0
  16. package/dist/har-index.js +98 -0
  17. package/dist/har-retention.js +73 -0
  18. package/dist/har-settings.js +27 -0
  19. package/dist/jobs/_alibaba-upload.js +2 -4
  20. package/dist/jobs/_fal-upload.js +2 -4
  21. package/dist/jobs/_image-edit-adapter.js +1 -1
  22. package/dist/jobs/_kie-upload.js +2 -4
  23. package/dist/jobs/complete-provider-task.js +5 -2
  24. package/dist/jobs/generate-media.js +9 -1
  25. package/dist/jobs/image-subjects.js +1 -1
  26. package/dist/jobs/kie-paygate.js +10 -3
  27. package/dist/jobs/run-notebook.js +4 -1
  28. package/dist/jobs/xai-paygate.js +5 -2
  29. package/dist/media/adapters/kie.js +2 -1
  30. package/dist/media/catalog.js +20 -0
  31. package/dist/media/cost.js +1 -1
  32. package/dist/package-version.js +18 -0
  33. package/dist/posts/dropbox-auth.js +1 -1
  34. package/dist/posts/post-metrics.js +2 -1
  35. package/dist/provider-usage.js +1 -18
  36. package/dist/queue.js +12 -1
  37. package/dist/serve.js +2 -0
  38. package/dist/tools/api-status.js +1 -1
  39. package/dist/tools/generate-media.js +3 -0
  40. package/dist/tools/generation-payload.js +17 -2
  41. package/dist/tools/get-generation-har.js +43 -0
  42. package/dist/tools/index.js +4 -0
  43. package/dist/tools/list-cell-generations.js +19 -0
  44. package/dist/tools/reconcile-project-work.js +18 -12
  45. package/dist/tools/set-har-capture.js +30 -0
  46. package/dist/upload.js +2 -1
  47. package/dist/usage-providers.js +48 -10
  48. package/dist/workspace-action-groups.js +2 -2
  49. package/dist/x/account.js +1 -1
  50. package/dist/x/authenticated-handle.js +1 -1
  51. package/dist/x/oauth-flow.js +1 -1
  52. package/dist/x/post-to-x.js +2 -1
  53. package/dist/x/x-tokens.js +2 -1
  54. package/package.json +15 -14
  55. package/ui/assets/{alive-captions-Bp6dxosy.js → alive-captions-Cj8C1tCN.js} +1 -1
  56. package/ui/assets/{confirm-bodies-Ds1HtaGc.js → confirm-bodies-BxWXwDf0.js} +1 -1
  57. package/ui/assets/{fullscreen-BCeVHjvY.js → fullscreen-DyEncSdm.js} +1 -1
  58. package/ui/assets/{goal-ui-DbAGwgMN.js → goal-ui-BdFeoSWh.js} +2 -2
  59. package/ui/assets/{index-D1d-F1y9.js → index-QZ8lbemN.js} +9 -9
  60. package/ui/assets/inspector-BC6xWkJU.js +1 -0
  61. package/ui/assets/{jobs-ui-C1cbw-xG.js → jobs-ui-BudI4w0r.js} +1 -1
  62. package/ui/assets/{music-video-CWtxPlxq.js → music-video-CT4C55ok.js} +1 -1
  63. package/ui/assets/ui-mq0uIS5P.css +1 -0
  64. package/ui/assets/ui-z5r-p0K9.js +2 -0
  65. package/ui/assets/{vendor-BFupLz37.js → vendor-D8XcMav4.js} +2 -2
  66. package/ui/assets/{vendor-flow-CKiuzaeM.js → vendor-flow-nqTFQmTh.js} +1 -1
  67. package/ui/assets/vendor-mcp-D_C3EZPv.js +60 -0
  68. package/ui/assets/videobook-CsW453sb.js +21 -0
  69. package/ui/index.html +12 -12
  70. package/ui/assets/inspector-BJTC0luH.js +0 -1
  71. package/ui/assets/ui-CXDi5Omq.js +0 -2
  72. package/ui/assets/ui-Oirr9VOf.css +0 -1
  73. package/ui/assets/vendor-mcp-DQEeVZH0.js +0 -67
  74. package/ui/assets/videobook-J-j9bSjM.js +0 -20
@@ -203,6 +203,26 @@ export const MODEL_ENTRIES = [
203
203
  { id: "fal:alibaba/wan-3.0-prime/text-to-video", media: "video", operations: ["text_to_video"], hidden: true },
204
204
  { id: "fal:alibaba/wan-3.0-prime/image-to-video", media: "video", operations: ["image_to_video"], hidden: true },
205
205
  { id: "fal:alibaba/wan-3.0-prime/reference-to-video", media: "video", operations: ["reference_to_video"], hidden: true },
206
+ { id: "fal:kling/v4/text-to-video", media: "video", operations: ["text_to_video"], dialect: "seedance" },
207
+ { id: "fal:kling/v4/image-to-video", media: "video", operations: ["image_to_video"], dialect: "seedance", modes: FRAME_MODES },
208
+ { id: "fal:kling/v4/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "seedance" },
209
+ { id: "fal:kling/v4/video-to-video/edit", media: "video", operations: ["edit_video"], dialect: "seedance" },
210
+ { id: "fal:heygen/heygen-video/text-to-video", media: "video", operations: ["text_to_video"], dialect: "natural" },
211
+ { id: "fal:heygen/heygen-video/image-to-video", media: "video", operations: ["image_to_video"], dialect: "strip_frames", refs: { first_frame: "image_url" } },
212
+ { id: "fal:heygen/heygen-video/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "natural", modes: [{ id: "r2v", label: "References", roles: ["image", "video", "audio"], requireAny: ["image", "video"] }] },
213
+ ...[3, 4].map((version) => ({
214
+ id: `fal:fal-ai/heygen/avatar${version}/digital-twin`,
215
+ media: "video",
216
+ operations: ["text_to_video"],
217
+ dialect: "plain",
218
+ prompt: "voice.prompt",
219
+ defaults: { character: { avatar: "Abigail Office Front" }, voice: { voice: "Ivy" } },
220
+ aliases: { avatar: "character.avatar", avatar_style: "character.avatar_style", voice: "voice.voice", voice_id: "voice.voice_id", speed: "voice.speed" },
221
+ })),
222
+ { id: "fal:fal-ai/heygen/avatar5/digital-twin", media: "video", operations: ["text_to_video"], dialect: "plain" },
223
+ { id: "fal:fal-ai/heygen/avatar4/image-to-video", media: "video", operations: ["image_to_video"], dialect: "plain", defaults: { voice: "Ivy" } },
224
+ { id: "fal:fal-ai/heygen/v2/video-agent", media: "video", operations: ["text_to_video"], dialect: "plain" },
225
+ { id: "fal:fal-ai/heygen/v3/video-agent", media: "video", operations: ["text_to_video"], dialect: "plain" },
206
226
  { id: "fal:blackforestlabs/flux-3-max/text-to-video", media: "video", operations: ["text_to_video"], title: "FLUX 3 Max", defaults: FLUX_3_MAX_DEFAULTS },
207
227
  { id: "fal:blackforestlabs/flux-3-max/image-to-video", media: "video", operations: ["image_to_video"], title: "FLUX 3 Max Image", refs: { first_frame: "image_url" }, modes: FRAME_MODES, defaults: FLUX_3_MAX_DEFAULTS },
208
228
  { id: "fal:blackforestlabs/flux-3-max/keyframes-to-video", media: "video", operations: ["image_to_video"], title: "FLUX 3 Max Keyframes", refs: { image: "keyframes" }, keyframeFps: 24, defaults: FLUX_3_MAX_DEFAULTS },
@@ -1,4 +1,4 @@
1
- import { createFal } from "@apicity/fal";
1
+ import { createFal } from "../usage-providers.js";
2
2
  import { computeEstimate, createFalEstimateCache, resolveFalDynamicEstimate, } from "@apicity/cost";
3
3
  import { buildWireRequest } from "./request.js";
4
4
  const FAL_ESTIMATE_TTL_MS = 5 * 60 * 1000;
@@ -0,0 +1,18 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ export function installedPackageVersion(name) {
5
+ let dir = dirname(fileURLToPath(import.meta.resolve(name)));
6
+ while (dir !== dirname(dir)) {
7
+ try {
8
+ const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
9
+ if (manifest.name === name && manifest.version)
10
+ return manifest.version;
11
+ }
12
+ catch {
13
+ void 0;
14
+ }
15
+ dir = dirname(dir);
16
+ }
17
+ return "unknown";
18
+ }
@@ -1,6 +1,6 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
2
  import { createServer } from "node:http";
3
- import { createDropbox } from "@apicity/dropbox";
3
+ import { createDropbox } from "../usage-providers.js";
4
4
  import { appState } from "../app-state.js";
5
5
  const KEY = "dropbox.oauth.tokens";
6
6
  const REDIRECT = "http://127.0.0.1:8766/callback";
@@ -1,4 +1,5 @@
1
- import { createX, XError } from "@apicity/x";
1
+ import { createX } from "../usage-providers.js";
2
+ import { XError } from "@apicity/x";
2
3
  import { v7 as uuidv7 } from "uuid";
3
4
  import { getValidAccessToken } from "../x/x-tokens.js";
4
5
  import { listBookEngines, queue } from "../queue.js";
@@ -1,26 +1,9 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { readFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
2
  import { createCost, PRICING, PRICING_AS_OF } from "@apicity/cost";
6
3
  import { modelEntry } from "./media/catalog.js";
7
4
  import { estimateFalRequest } from "./media/cost.js";
8
5
  import { currentBookContext } from "./book-context.js";
9
- function installedPackageVersion(name) {
10
- let dir = dirname(fileURLToPath(import.meta.resolve(name)));
11
- while (dir !== dirname(dir)) {
12
- try {
13
- const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
14
- if (manifest.name === name && manifest.version)
15
- return manifest.version;
16
- }
17
- catch {
18
- void 0;
19
- }
20
- dir = dirname(dir);
21
- }
22
- return "unknown";
23
- }
6
+ import { installedPackageVersion } from "./package-version.js";
24
7
  const COST_PACKAGE_VERSION = installedPackageVersion("@apicity/cost");
25
8
  const incomplete = new WeakSet();
26
9
  const cost = createCost();
package/dist/queue.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { sweepHarCaptures } from "./har-retention.js";
1
2
  import { appendDiagnostic, registerDiagnosticName } from "./diagnostics.js";
2
3
  import { createEngine, } from "videobook-engine";
3
4
  import { activeBookEngine, withBookContext } from "./book-context.js";
@@ -54,11 +55,13 @@ export function isProviderPollDriverActive(bookId) {
54
55
  const info = runnerByBook.get(bookId);
55
56
  return info?.providerPollTimer != null;
56
57
  }
58
+ let harRetentionTimer = null;
57
59
  async function tickProviderPoll(info) {
58
60
  if (info.providerPollInFlight || info.pollAbort.signal.aborted)
59
61
  return;
60
62
  info.providerPollInFlight = true;
61
63
  try {
64
+ await sweepHarCaptures();
62
65
  if (providerPollWorkHook) {
63
66
  await providerPollWorkHook(info.pollAbort.signal);
64
67
  }
@@ -258,7 +261,10 @@ function makeWrappedResolver(bookId, engine) {
258
261
  const handler = handlers.get(type);
259
262
  if (!handler)
260
263
  return null;
261
- return async (job, queueSignal) => withBookContext({ bookId, engine, operation: type, ...(job.artifactId ? { artifactId: job.artifactId } : {}) }, () => runWithJobDeadline(type, async (signal) => {
264
+ return async (job, queueSignal) => withBookContext({ bookId, engine, operation: type, ...(job.artifactId ? { artifactId: job.artifactId } : {}),
265
+ ...(typeof job.payload.runId === "string" ? { runId: job.payload.runId } : {}),
266
+ ...(typeof job.payload.generationId === "string" ? { attemptId: job.payload.generationId } : {}),
267
+ ...(typeof job.payload.model === "string" ? { model: job.payload.model } : {}) }, () => runWithJobDeadline(type, async (signal) => {
262
268
  const jobSignal = queueSignal
263
269
  ? AbortSignal.any([signal, queueSignal])
264
270
  : signal;
@@ -579,6 +585,8 @@ export const queue = {
579
585
  };
580
586
  },
581
587
  async start() {
588
+ await sweepHarCaptures(true);
589
+ harRetentionTimer ??= setInterval(() => { void sweepHarCaptures(); }, 60_000).unref();
582
590
  await ensureJobHandlersLoaded();
583
591
  await reconcileQueueOnStartup();
584
592
  },
@@ -586,6 +594,9 @@ export const queue = {
586
594
  await ensureRunner(bookId);
587
595
  },
588
596
  async stop() {
597
+ if (harRetentionTimer)
598
+ clearInterval(harRetentionTimer);
599
+ harRetentionTimer = null;
589
600
  if (stoppingQueue)
590
601
  return stoppingQueue;
591
602
  const pending = stopQueueRunners();
package/dist/serve.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { flushHarCaptures } from "./har-capture.js";
1
2
  import { once } from "node:events";
2
3
  import { setTimeout as delay } from "node:timers/promises";
3
4
  import { stopAceServer } from "./acestep/process.js";
@@ -22,6 +23,7 @@ async function shutdownServices(services) {
22
23
  await services.voiceSessions.close();
23
24
  await services.mcpHandler.close();
24
25
  await queue.stop();
26
+ await flushHarCaptures();
25
27
  await stopAceServer();
26
28
  await bookManager.close();
27
29
  }
@@ -1,4 +1,4 @@
1
- import { createKimiCoding } from "@apicity/kimicoding";
1
+ import { createKimiCoding } from "../usage-providers.js";
2
2
  import { isPlaceholderApiKey } from "../api-key.js";
3
3
  export function apiStatusDef() {
4
4
  return {
@@ -1,3 +1,4 @@
1
+ import { currentBookContext } from "../book-context.js";
1
2
  import { z } from "zod";
2
3
  import { queue } from "../queue.js";
3
4
  import { generationLabel } from "../artifact-label.js";
@@ -92,6 +93,8 @@ async function enqueueGeneration(engine, model, args, refs) {
92
93
  const jobId = `generate-${model.media}-${artifactId}-${Date.now()}`;
93
94
  const job = {
94
95
  type: "generate_media",
96
+ runId: currentBookContext()?.runId,
97
+ generationId: currentBookContext()?.attemptId,
95
98
  model: model.id,
96
99
  bookId: String(args.book_id),
97
100
  assetId: artifactId,
@@ -1,20 +1,34 @@
1
1
  import { z } from "zod";
2
+ import { captureKeys, generationHarStatus } from "../generation-har.js";
3
+ import { readHarSettings } from "../har-settings.js";
4
+ import { logger } from "../logger.js";
2
5
  import { readProviderTaskStatus } from "../provider-task-status.js";
6
+ async function inspectionHar(engine, assetId) {
7
+ try {
8
+ const { mode } = await readHarSettings();
9
+ return await generationHarStatus(await captureKeys(engine, [assetId]), mode);
10
+ }
11
+ catch (error) {
12
+ logger.warn("har-capture-failed", { error });
13
+ return null;
14
+ }
15
+ }
3
16
  export function generationPayloadDef(engine) {
4
17
  return {
5
18
  name: "inspect_generation_request",
6
- description: "Read the persisted provider request payload, task details, status-page information, and failure info for a single asset. Used by the generating-cell inspector to show what was sent to the provider.",
19
+ description: "Read the persisted provider request payload, task details, status-page information, failure info and HAR capture status for a single asset. Used by the generating-cell inspector to show what was sent to the provider.",
7
20
  schema: {
8
21
  book_id: z.string().describe("Book id"),
9
22
  asset_id: z.string().describe("Artifact id"),
10
23
  },
11
24
  handler: async (args) => {
12
25
  const { book_id: _book_id, asset_id } = args;
13
- const [payloadRes, taskRes, errorRes, providerTask] = await Promise.all([
26
+ const [payloadRes, taskRes, errorRes, providerTask, har] = await Promise.all([
14
27
  engine.metadata.artifacts.read(asset_id, "generation-payload"),
15
28
  engine.jobs.pending.read(asset_id),
16
29
  engine.jobs.failures.read(asset_id),
17
30
  readProviderTaskStatus(engine, asset_id),
31
+ inspectionHar(engine, asset_id),
18
32
  ]);
19
33
  const task = taskRes.ok ? taskRes.value : null;
20
34
  const errorRecord = errorRes.ok ? errorRes.value : null;
@@ -32,6 +46,7 @@ export function generationPayloadDef(engine) {
32
46
  error,
33
47
  taskType: task ? task.taskType : null,
34
48
  providerTask,
49
+ har,
35
50
  task: task
36
51
  ? {
37
52
  taskId: task.taskId,
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+ import { generationCaptureKeys, generationHar } from "../generation-har.js";
3
+ import { readHarSettings } from "../har-settings.js";
4
+ function errorResult(message) {
5
+ return { isError: true, content: [{ type: "text", text: message }] };
6
+ }
7
+ export function getGenerationHarDef(engine) {
8
+ return {
9
+ name: "get_generation_har",
10
+ description: "Return the scrubbed provider HAR capture of one generation attempt, by attempt id or asset id. Several captured calls are combined into one HAR with a page per call. Needs provider capture (set_har_capture).",
11
+ schema: {
12
+ book_id: z.string().describe("Book id"),
13
+ generation_id: z.string().min(1).describe("Generation attempt id or its asset id"),
14
+ },
15
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
16
+ handler: async (args) => {
17
+ const { generation_id } = args;
18
+ try {
19
+ const result = await generationHar(await generationCaptureKeys(engine, generation_id), generation_id);
20
+ if (!result) {
21
+ const { mode } = await readHarSettings();
22
+ return errorResult(mode === "off"
23
+ ? "No HAR capture for this generation. Provider capture is off; turn it on in Settings > Diagnostics or with set_har_capture."
24
+ : "No HAR capture for this generation.");
25
+ }
26
+ return {
27
+ content: [{
28
+ type: "text",
29
+ text: JSON.stringify({
30
+ generationId: generation_id,
31
+ filename: `videobook-generation-${generation_id.replace(/[^A-Za-z0-9._-]+/gu, "-")}.har`,
32
+ captures: result.captures,
33
+ har: result.har,
34
+ }),
35
+ }],
36
+ };
37
+ }
38
+ catch (error) {
39
+ return errorResult(error instanceof Error ? error.message : String(error));
40
+ }
41
+ },
42
+ };
43
+ }
@@ -1,3 +1,4 @@
1
+ import { setHarCaptureDef } from "./set-har-capture.js";
1
2
  import { coldOpenDefs } from "./cold-open.js";
2
3
  import { withBookContext } from "../book-context.js";
3
4
  import { bookManager } from "../engine.js";
@@ -78,6 +79,7 @@ import { listAssetsDef } from "./list-assets.js";
78
79
  import { listCellGenerationsDef } from "./list-cell-generations.js";
79
80
  import { waitForAssetReadyDef } from "./wait-for-asset-ready.js";
80
81
  import { generationPayloadDef } from "./generation-payload.js";
82
+ import { getGenerationHarDef } from "./get-generation-har.js";
81
83
  import { concatImagesDef } from "./concat-images.js";
82
84
  import { chatDef } from "./chat.js";
83
85
  import { chainDef } from "./chain.js";
@@ -250,6 +252,7 @@ export function getAllToolDefs(engine, books = bookManager) {
250
252
  listChatModelsDef(),
251
253
  providerKeyStatusDef(),
252
254
  setEnvSecretDef(),
255
+ setHarCaptureDef(),
253
256
  listElevenLabsVoicesDef(),
254
257
  estimateCostDef(engine, books),
255
258
  bookUsageDef(engine),
@@ -285,6 +288,7 @@ export function getAllToolDefs(engine, books = bookManager) {
285
288
  listCellGenerationsDef(engine),
286
289
  waitForAssetReadyDef(engine),
287
290
  generationPayloadDef(engine),
291
+ getGenerationHarDef(engine),
288
292
  concatImagesDef(engine),
289
293
  chatDef(engine),
290
294
  chainDef(engine),
@@ -2,6 +2,20 @@ import { z } from "zod";
2
2
  import { findGenerateAudioOutput, findGenerateImageOutput, findGenerateVideoOutput, listGenerateImageOutputs, listGenerateVideoOutputs, } from "../notebook-cell-outputs.js";
3
3
  import { buildAssetList } from "./reconcile-project-work.js";
4
4
  import { readProviderTaskStatus } from "../provider-task-status.js";
5
+ import { captureKeys, generationHarStatus } from "../generation-har.js";
6
+ import { readHarIndex } from "../har-index.js";
7
+ import { readHarSettings } from "../har-settings.js";
8
+ import { logger } from "../logger.js";
9
+ async function attemptHarLookup(engine) {
10
+ try {
11
+ const [{ mode }, rows] = await Promise.all([readHarSettings(), readHarIndex()]);
12
+ return { mode, status: async (ids) => generationHarStatus(await captureKeys(engine, ids), mode, rows) };
13
+ }
14
+ catch (error) {
15
+ logger.warn("har-capture-failed", { error });
16
+ return null;
17
+ }
18
+ }
5
19
  function generationArtifactIds(generations) {
6
20
  return [...new Set(generations.flatMap((row) => [
7
21
  ...(row.outputArtifactId ? [row.outputArtifactId] : []),
@@ -132,6 +146,7 @@ export function listCellGenerationsDef(engine) {
132
146
  ? generationsResult.value.filter((row) => !row.outputCellId || row.outputCellId === requestedResult.id)
133
147
  : generationsResult.value;
134
148
  const transitions = generationTransitions(engine, rows);
149
+ const harLookup = await attemptHarLookup(engine);
135
150
  const assets = await buildAssetList(engine, book_id);
136
151
  const assetById = new Map(assets.map((asset) => [asset.artifact_id, asset]));
137
152
  const attempts = [];
@@ -180,6 +195,9 @@ export function listCellGenerationsDef(engine) {
180
195
  ...(include_payloads ? { payload } : {}),
181
196
  commandPlan,
182
197
  transitions: transitions.get(row.generationId) ?? [],
198
+ har: harLookup
199
+ ? await harLookup.status([row.generationId, row.providerArtifactId, row.outputArtifactId])
200
+ : null,
183
201
  createdAt: row.createdAt,
184
202
  updatedAt: row.updatedAt,
185
203
  });
@@ -205,6 +223,7 @@ export function listCellGenerationsDef(engine) {
205
223
  latestStatus: rows[0]?.status ?? null,
206
224
  artifactIds: generationArtifactIds(rows),
207
225
  },
226
+ harMode: harLookup?.mode ?? null,
208
227
  };
209
228
  return {
210
229
  content: [{
@@ -791,12 +791,18 @@ async function processMachGenTaskStatus(engine, task, status, result, bookId) {
791
791
  });
792
792
  result.completed.push({ assetId: task.artifactId, taskType: task.taskType, jobId });
793
793
  }
794
+ function withProviderTaskContext(engine, bookId, task, operation) {
795
+ return withBookContext({ bookId, engine, operation: "poll_pending_provider_tasks", artifactId: task.artifactId,
796
+ providerTaskId: task.taskId, attemptId: typeof task.meta.attemptId === "string" ? task.meta.attemptId : task.artifactId,
797
+ runId: typeof task.meta.runId === "string" ? task.meta.runId : undefined,
798
+ model: typeof task.meta.model === "string" ? task.meta.model : undefined }, operation);
799
+ }
794
800
  async function pollMachGenTasks(engine, tasks, result, bookId, baseURL) {
795
801
  const machgenTasks = tasks.filter((task) => task.taskType === MEDIA_TASK_TYPES.machgen);
796
802
  if (!getApiKey("MACHGEN_API_KEY") || machgenTasks.length === 0)
797
803
  return;
798
804
  const provider = machgenProvider(30_000, baseURL);
799
- await Promise.all(machgenTasks.map(async (task) => {
805
+ await Promise.all(machgenTasks.map(async (task) => withProviderTaskContext(engine, bookId, task, async () => {
800
806
  try {
801
807
  await processMachGenTaskStatus(engine, task, await provider.api.v0.tasks(task.taskId), result, bookId);
802
808
  }
@@ -807,7 +813,7 @@ async function pollMachGenTasks(engine, tasks, result, bookId, baseURL) {
807
813
  if (machgenPollFailureIsFinal(error))
808
814
  await failPendingTaskAndLog(engine, task.artifactId, task.taskId, { message: failMsg, prompt });
809
815
  }
810
- }));
816
+ })));
811
817
  }
812
818
  /**
813
819
  * Poll provider-pending tasks: submit queued KIE rows, age out stale tasks,
@@ -887,7 +893,7 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
887
893
  timeout: 30_000,
888
894
  ...(kieBaseURL ? { baseURL: kieBaseURL } : {}),
889
895
  });
890
- await Promise.all(kieTasks.map(async (task) => {
896
+ await Promise.all(kieTasks.map(async (task) => withProviderTaskContext(engine, book_id, task, async () => {
891
897
  try {
892
898
  const taskInfo = await provider.get.api.v1.jobs.recordInfo(task.taskId);
893
899
  await processKieTaskResult(engine, task, taskInfo, result, book_id);
@@ -904,7 +910,7 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
904
910
  }
905
911
  }
906
912
  }
907
- }));
913
+ })));
908
914
  }
909
915
  // Poll KIE Veo tasks (separate namespace: kie.veo.get.api.v1.veo.recordInfo)
910
916
  const veoTasks = pollableTasks.filter(isVeoPendingTask);
@@ -914,7 +920,7 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
914
920
  timeout: 30_000,
915
921
  ...(kieBaseURL ? { baseURL: kieBaseURL } : {}),
916
922
  });
917
- await Promise.all(veoTasks.map(async (task) => {
923
+ await Promise.all(veoTasks.map(async (task) => withProviderTaskContext(engine, book_id, task, async () => {
918
924
  const prompt = metaPrompt(task.meta);
919
925
  try {
920
926
  const recordInfo = await provider.veo.get.api.v1.veo.recordInfo(task.taskId);
@@ -973,13 +979,13 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
973
979
  }
974
980
  }
975
981
  }
976
- }));
982
+ })));
977
983
  }
978
984
  // Poll xAI tasks (only if API key is set)
979
985
  const xaiTasks = pollableTasks.filter((t) => t.taskType.startsWith("xai_"));
980
986
  if (process.env.XAI_API_KEY && xaiTasks.length > 0) {
981
987
  const provider = createXai({ apiKey: process.env.XAI_API_KEY, timeout: 30_000 });
982
- await Promise.all(xaiTasks.map(async (task) => {
988
+ await Promise.all(xaiTasks.map(async (task) => withProviderTaskContext(engine, book_id, task, async () => {
983
989
  const prompt = metaPrompt(task.meta);
984
990
  try {
985
991
  const videoResult = await provider.get.v1.videos(task.taskId);
@@ -1029,13 +1035,13 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
1029
1035
  }
1030
1036
  }
1031
1037
  }
1032
- }));
1038
+ })));
1033
1039
  }
1034
1040
  // Poll Alibaba tasks (only if API key is set)
1035
1041
  const alibabaTasks = pollableTasks.filter((t) => t.taskType.startsWith("alibaba_"));
1036
1042
  if (process.env.DASHSCOPE_API_KEY && alibabaTasks.length > 0) {
1037
1043
  const provider = createAlibaba({ apiKey: process.env.DASHSCOPE_API_KEY, timeout: 30_000 });
1038
- await Promise.all(alibabaTasks.map(async (task) => {
1044
+ await Promise.all(alibabaTasks.map(async (task) => withProviderTaskContext(engine, book_id, task, async () => {
1039
1045
  const prompt = metaPrompt(task.meta);
1040
1046
  try {
1041
1047
  const taskStatus = await provider.get.api.v1.tasks(task.taskId);
@@ -1105,7 +1111,7 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
1105
1111
  }
1106
1112
  }
1107
1113
  }
1108
- }));
1114
+ })));
1109
1115
  }
1110
1116
  // Poll FAL tasks (only if API key is set)
1111
1117
  const falTasks = pollableTasks.filter((t) => t.taskType.startsWith("fal_"));
@@ -1116,7 +1122,7 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
1116
1122
  // — e.g. `fal-ai/elevenlabs/speech-to-text/scribe-v2` — 405s. Strip back
1117
1123
  // to `fal-ai/elevenlabs` for polling.
1118
1124
  const falNamespace = (endpointId) => endpointId.split("/").slice(0, 2).join("/");
1119
- await Promise.all(falTasks.map(async (task) => {
1125
+ await Promise.all(falTasks.map(async (task) => withProviderTaskContext(engine, book_id, task, async () => {
1120
1126
  const prompt = metaPrompt(task.meta);
1121
1127
  const endpointId = typeof task.meta?.falEndpointId === "string" ? task.meta.falEndpointId : null;
1122
1128
  if (!endpointId) {
@@ -1179,7 +1185,7 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
1179
1185
  }
1180
1186
  }
1181
1187
  }
1182
- }));
1188
+ })));
1183
1189
  }
1184
1190
  await pollMachGenTasks(engine, pollableTasks, result, book_id, options.machgenBaseURL ?? providerEndpoints().machgenBaseURL);
1185
1191
  for (const task of expiredTasks) {
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ import { harSettingsSchema, readHarSettings, writeHarSettings, harDirectory } from "../har-settings.js";
3
+ import { appState } from "../app-state.js";
4
+ export function setHarCaptureDef(store = appState) {
5
+ return {
6
+ name: "set_har_capture",
7
+ description: "Read or change opt-in local provider HAR diagnostics. Captures retain prompts, media URLs, parameters, responses and errors; secrets, cookies and oversized media bytes are scrubbed. Omit mode to read settings. Environment override wins.",
8
+ schema: {
9
+ mode: harSettingsSchema.shape.mode.removeDefault().optional(),
10
+ maxAgeDays: harSettingsSchema.shape.maxAgeDays.removeDefault().optional(),
11
+ maxTotalMB: harSettingsSchema.shape.maxTotalMB.removeDefault().optional(),
12
+ },
13
+ handler: async (args) => {
14
+ try {
15
+ const changes = z.object({
16
+ mode: harSettingsSchema.shape.mode.removeDefault().optional(),
17
+ maxAgeDays: harSettingsSchema.shape.maxAgeDays.removeDefault().optional(),
18
+ maxTotalMB: harSettingsSchema.shape.maxTotalMB.removeDefault().optional(),
19
+ }).parse(args);
20
+ const settings = Object.values(changes).some((value) => value !== undefined)
21
+ ? await writeHarSettings({ ...await readHarSettings(store, {}), ...changes }, store)
22
+ : await readHarSettings(store);
23
+ return { content: [{ type: "text", text: JSON.stringify({ ...settings, dir: harDirectory(), overridden: Boolean(process.env.VIDEOBOOK_HAR_CAPTURE?.trim()) }) }] };
24
+ }
25
+ catch (error) {
26
+ return { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }] };
27
+ }
28
+ },
29
+ };
30
+ }
package/dist/upload.js CHANGED
@@ -1,4 +1,5 @@
1
- import { createFreeMediaUpload, uploadToAnyHost } from "@apicity/free-media-upload";
1
+ import { createFreeMediaUpload } from "./usage-providers.js";
2
+ import { uploadToAnyHost } from "@apicity/free-media-upload";
2
3
  import { createHash } from "node:crypto";
3
4
  import { maybeReencodeImage, maybeUpscaleSmallImage } from "./image-compress.js";
4
5
  export function contentHash(data) {
@@ -1,29 +1,67 @@
1
+ import { createFreeMediaUpload as freemediaupload } from "@apicity/free-media-upload";
2
+ import { createKimiCoding as kimicoding } from "@apicity/kimicoding";
3
+ import { createXOAuth as xoauth } from "@apicity/x";
4
+ import { createX as x } from "@apicity/x";
5
+ import { createDropbox as dropbox } from "@apicity/dropbox";
6
+ import { createB2 as b2 } from "@apicity/b2";
1
7
  import { createOpenAi as openai } from "@apicity/openai";
2
8
  import { createXai as xai } from "@apicity/xai";
3
- import { createKie as kie } from "@apicity/kie";
4
- import { createFal as fal } from "@apicity/fal";
5
- import { createAlibaba as alibaba } from "@apicity/alibaba";
9
+ import { createKie as kie, uploadFile as kieUpload, submitMediaJob as submitKieMediaJob } from "@apicity/kie";
10
+ import { createFal as fal, uploadFile as falUpload } from "@apicity/fal";
11
+ import { createAlibaba as alibaba, uploadFile as alibabaUpload } from "@apicity/alibaba";
6
12
  import { createElevenLabs as elevenlabs } from "@apicity/elevenlabs";
7
13
  import { createMachGen as machgen } from "@apicity/machgen";
14
+ import { capturedProvider, withHarCapture, withProviderCall } from "./har-capture.js";
8
15
  import { providerUsageFetch } from "./provider-usage.js";
9
16
  export function createOpenAi(options) {
10
- return openai({ ...options, fetch: providerUsageFetch("openai", options.fetch) });
17
+ return capturedProvider("openai", (fetch) => openai({ ...options, fetch }), providerUsageFetch("openai", options.fetch));
11
18
  }
12
19
  export function createXai(options) {
13
- return xai({ ...options, fetch: providerUsageFetch("xai", options.fetch) });
20
+ return capturedProvider("xai", (fetch) => xai({ ...options, fetch }), providerUsageFetch("xai", options.fetch));
14
21
  }
15
22
  export function createKie(options) {
16
- return kie({ ...options, fetch: providerUsageFetch("kie", options.fetch) });
23
+ return capturedProvider("kie", (fetch) => kie({ ...options, fetch }), providerUsageFetch("kie", options.fetch));
17
24
  }
18
25
  export function createFal(options) {
19
- return fal({ ...options, fetch: providerUsageFetch("fal", options.fetch) });
26
+ return capturedProvider("fal", (fetch) => fal({ ...options, fetch }), providerUsageFetch("fal", options.fetch));
20
27
  }
21
28
  export function createAlibaba(options) {
22
- return alibaba({ ...options, fetch: providerUsageFetch("alibaba", options.fetch) });
29
+ return capturedProvider("alibaba", (fetch) => alibaba({ ...options, fetch }), providerUsageFetch("alibaba", options.fetch));
23
30
  }
24
31
  export function createElevenLabs(options) {
25
- return elevenlabs({ ...options, fetch: providerUsageFetch("elevenlabs", options.fetch) });
32
+ return capturedProvider("elevenlabs", (fetch) => elevenlabs({ ...options, fetch }), providerUsageFetch("elevenlabs", options.fetch));
26
33
  }
27
34
  export function createMachGen(options) {
28
- return machgen({ ...options, fetch: providerUsageFetch("machgen", options.fetch) });
35
+ return capturedProvider("machgen", (fetch) => machgen({ ...options, fetch }), providerUsageFetch("machgen", options.fetch));
36
+ }
37
+ export function createB2(options) {
38
+ return capturedProvider("b2", (fetch) => b2({ ...options, fetch }), options?.fetch);
39
+ }
40
+ export function createDropbox(options) {
41
+ return capturedProvider("dropbox", (fetch) => dropbox({ ...options, fetch }), options?.fetch);
42
+ }
43
+ export function createX(options) {
44
+ return capturedProvider("x", (fetch) => x({ ...options, fetch }), options?.fetch);
45
+ }
46
+ export function createXOAuth(options) {
47
+ return capturedProvider("x", (fetch) => xoauth({ ...options, fetch }), options?.fetch);
48
+ }
49
+ export function createKimiCoding(options) {
50
+ return capturedProvider("kimicoding", (fetch) => kimicoding({ ...options, fetch }), options?.fetch);
51
+ }
52
+ export function createFreeMediaUpload(options = {}) {
53
+ return capturedProvider("free-media-upload", (fetch) => freemediaupload({ ...options, fetch }), options?.fetch);
54
+ }
55
+ export function uploadFalFile(options, input) {
56
+ return withHarCapture({ provider: "fal", endpoint: "uploadFile", input }, (fetch) => falUpload(fal({ ...options, fetch }), input), providerUsageFetch("fal", options.fetch));
57
+ }
58
+ export function uploadAlibabaFile(options, input) {
59
+ return withHarCapture({ provider: "alibaba", endpoint: "uploadFile", input }, (fetch) => alibabaUpload(alibaba({ ...options, fetch }), input), providerUsageFetch("alibaba", options.fetch));
60
+ }
61
+ export function uploadKieFile(options, file, filename) {
62
+ return withHarCapture({ provider: "kie", endpoint: "uploadFile", input: { file, filename } }, (fetch) => kieUpload(kie({ ...options, fetch }), file, filename), providerUsageFetch("kie", options.fetch));
63
+ }
64
+ export function submitMediaJob(...args) {
65
+ const [provider, request, approval] = args;
66
+ return withProviderCall(provider, { provider: "kie", endpoint: "api.v1.jobs.createTask", input: request }, (original) => submitKieMediaJob(original, request, approval));
29
67
  }
@@ -79,7 +79,7 @@ export const WORKSPACE_TOOL_GROUPS = {
79
79
  ],
80
80
  History: [
81
81
  "asset_history", "restore_asset_version", "read_file_at_commit",
82
- "list_cell_generations", "inspect_generation_request",
82
+ "list_cell_generations", "inspect_generation_request", "get_generation_har",
83
83
  "record_prompt", "list_prompt_history", "list_book_actions",
84
84
  "get_history_action",
85
85
  ],
@@ -92,7 +92,7 @@ export const WORKSPACE_TOOL_GROUPS = {
92
92
  ],
93
93
  Publishing: ["create_post", "update_post", "get_post", "list_posts", "link_post", "upload_post_to_dropbox", "save_post_to_icloud", "refresh_post_metrics", "record_post_metrics", "connect_service", "service_status", "post_icloud_sync_status", "draft_post_copy", "post", "verify_x_mentions"],
94
94
  Providers: [
95
- "check_kimi_coding_api", "list_chat_models", "provider_key_status", "set_env_secret",
95
+ "check_kimi_coding_api", "list_chat_models", "provider_key_status", "set_env_secret", "set_har_capture",
96
96
  "list_elevenlabs_voices",
97
97
  ],
98
98
  };
package/dist/x/account.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * store next to the OAuth tokens, so posting never spends an extra API call.
8
8
  * Callers refresh it after a new authorization or on explicit request.
9
9
  */
10
- import { createX } from "@apicity/x";
10
+ import { createX } from "../usage-providers.js";
11
11
  import { appState } from "../app-state.js";
12
12
  import { getValidAccessToken } from "./x-tokens.js";
13
13
  export const X_POST_MAX_CHARACTERS = 280;
@@ -5,7 +5,7 @@
5
5
  * API rejects, or the response is malformed. Callers should treat null as
6
6
  * "unknown" and fall back to a cached value or prompt the user.
7
7
  */
8
- import { createX } from "@apicity/x";
8
+ import { createX } from "../usage-providers.js";
9
9
  import { getValidAccessToken } from "./x-tokens.js";
10
10
  import { normalizeHandle } from "../watermark-config.js";
11
11
  export async function fetchAuthenticatedXHandle() {
@@ -1,6 +1,6 @@
1
1
  import http from "node:http";
2
2
  import crypto from "node:crypto";
3
- import { createXOAuth } from "@apicity/x";
3
+ import { createXOAuth } from "../usage-providers.js";
4
4
  import { appState } from "../app-state.js";
5
5
  import { readTokens, writeTokens } from "./x-tokens.js";
6
6
  import { clearXAccount, fetchXAccount, resolveXAccount, xPostMaxCharacters, } from "./account.js";
@@ -1,5 +1,6 @@
1
1
  import { findPrimaryMediaFile } from "videobook-engine";
2
- import { createX, XError } from "@apicity/x";
2
+ import { createX } from "../usage-providers.js";
3
+ import { XError } from "@apicity/x";
3
4
  import { extname, join } from "node:path";
4
5
  import { tmpdir } from "node:os";
5
6
  import { readFile, unlink } from "node:fs/promises";