videobook 0.0.0-stage → 0.2.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 (565) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +195 -2
  4. package/bin/videobook.js +7 -0
  5. package/dist/acestep/client.js +203 -0
  6. package/dist/acestep/config.js +62 -0
  7. package/dist/acestep/errors.js +8 -0
  8. package/dist/acestep/process.js +175 -0
  9. package/dist/alive-captions/analyze.js +117 -0
  10. package/dist/alive-captions/audio.js +179 -0
  11. package/dist/alive-captions/frames.js +59 -0
  12. package/dist/alive-captions/model.js +37 -0
  13. package/dist/alive-captions/schema.js +49 -0
  14. package/dist/alive-captions/track.js +9 -0
  15. package/dist/api-key.js +33 -0
  16. package/dist/app-state.js +56 -0
  17. package/dist/app.js +257 -0
  18. package/dist/archive-b2.js +332 -0
  19. package/dist/archive-store.js +169 -0
  20. package/dist/artifact-label.js +36 -0
  21. package/dist/artifact-refs.js +49 -0
  22. package/dist/asset-tag-extraction.js +169 -0
  23. package/dist/asset-tags.js +228 -0
  24. package/dist/asset-usage.js +23 -0
  25. package/dist/audio-cue.js +71 -0
  26. package/dist/audio-encoding.js +32 -0
  27. package/dist/audio-sync-request.js +1 -0
  28. package/dist/audio-sync.js +223 -0
  29. package/dist/audio-trim-range.js +31 -0
  30. package/dist/book-context.js +31 -0
  31. package/dist/book-manager.js +374 -0
  32. package/dist/catalog-gc.js +26 -0
  33. package/dist/chain-executor.js +815 -0
  34. package/dist/chain-generation.js +115 -0
  35. package/dist/chain-plan.js +746 -0
  36. package/dist/chain-step-review.js +60 -0
  37. package/dist/character-name.js +25 -0
  38. package/dist/chat-generation-routing.js +73 -0
  39. package/dist/chat-generation-settings.js +78 -0
  40. package/dist/chat-models.js +44 -0
  41. package/dist/chat-prompt-rewrite.js +67 -0
  42. package/dist/chat-router.js +628 -0
  43. package/dist/chat-view-actions.js +49 -0
  44. package/dist/cli/args.js +242 -0
  45. package/dist/cli/config-command.js +103 -0
  46. package/dist/cli/doctor-command.js +289 -0
  47. package/dist/cli/entry.js +2 -0
  48. package/dist/cli/key-report.js +16 -0
  49. package/dist/cli/main.js +104 -0
  50. package/dist/cli/mcp-command.js +88 -0
  51. package/dist/cli/mcp-relay.js +51 -0
  52. package/dist/cli/open-browser.js +14 -0
  53. package/dist/cli/run-command.js +231 -0
  54. package/dist/cli/serve-command.js +62 -0
  55. package/dist/cli/setup-command.js +60 -0
  56. package/dist/cli/stdio-guard.js +8 -0
  57. package/dist/cold-open-model.js +7 -0
  58. package/dist/cold-open.js +79 -0
  59. package/dist/command-reference.js +95 -0
  60. package/dist/content-store.js +74 -0
  61. package/dist/cost.js +8 -0
  62. package/dist/crop-geometry.js +23 -0
  63. package/dist/dancing-workflow.js +447 -0
  64. package/dist/data-home.js +44 -0
  65. package/dist/diagnostics.js +109 -0
  66. package/dist/dialog-quality.js +34 -0
  67. package/dist/downloads-dest.js +56 -0
  68. package/dist/editing-moments.js +194 -0
  69. package/dist/elevenlabs-audio-extension.js +66 -0
  70. package/dist/engine.js +54 -0
  71. package/dist/env-file.js +91 -0
  72. package/dist/env-secrets.js +148 -0
  73. package/dist/ffmpeg-binary.js +12 -0
  74. package/dist/ffmpeg.js +425 -0
  75. package/dist/ffprobe-binary.js +77 -0
  76. package/dist/forget-asset.js +216 -0
  77. package/dist/frame-utils.js +27 -0
  78. package/dist/generation-credit-error.js +11 -0
  79. package/dist/generation-input-mentions.js +117 -0
  80. package/dist/generation-input-policy.js +68 -0
  81. package/dist/generation-reference-mentions.js +97 -0
  82. package/dist/goal-characters.js +49 -0
  83. package/dist/goal-plan-v3.js +273 -0
  84. package/dist/goal-plan.js +220 -0
  85. package/dist/goal-planner-prompt.js +28 -0
  86. package/dist/goal-prompt-budget.js +4 -0
  87. package/dist/goal-reference-images.js +25 -0
  88. package/dist/goal-state.js +261 -0
  89. package/dist/goal-stills.js +153 -0
  90. package/dist/goal-store.js +144 -0
  91. package/dist/goal-styles.js +144 -0
  92. package/dist/goal-v3-runtime.js +157 -0
  93. package/dist/guides/character_schema_guide.md +70 -0
  94. package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
  95. package/dist/guides/grok_imagine_prompting_guide.md +72 -0
  96. package/dist/guides/image_reproduction_guide.md +84 -0
  97. package/dist/guides/kling_3_0_prompting_guide.md +136 -0
  98. package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
  99. package/dist/guides/seedance_2_prompting_guide.md +172 -0
  100. package/dist/http-access.js +49 -0
  101. package/dist/http-byte-range.js +31 -0
  102. package/dist/hydration.js +56 -0
  103. package/dist/image-compress.js +77 -0
  104. package/dist/image-edit-default.js +1 -0
  105. package/dist/image-edit-models.js +41 -0
  106. package/dist/image-edit-settings.js +11 -0
  107. package/dist/image-region-edit.js +32 -0
  108. package/dist/image-subject-map.js +523 -0
  109. package/dist/image.js +46 -0
  110. package/dist/index.js +12 -0
  111. package/dist/job-deadline.js +60 -0
  112. package/dist/jobs/_alibaba-upload.js +30 -0
  113. package/dist/jobs/_audio-chunks.js +31 -0
  114. package/dist/jobs/_download-into-asset.js +15 -0
  115. package/dist/jobs/_fal-shared.js +22 -0
  116. package/dist/jobs/_fal-upload.js +35 -0
  117. package/dist/jobs/_image-edit-adapter.js +213 -0
  118. package/dist/jobs/_kie-poll.js +119 -0
  119. package/dist/jobs/_kie-upload.js +32 -0
  120. package/dist/jobs/_lease.js +163 -0
  121. package/dist/jobs/_persist-output.js +19 -0
  122. package/dist/jobs/_storage-retry.js +21 -0
  123. package/dist/jobs/_transcribe.js +245 -0
  124. package/dist/jobs/_translate-transcript.js +270 -0
  125. package/dist/jobs/analyze-alive-captions.js +10 -0
  126. package/dist/jobs/analyze-editing-moments.js +104 -0
  127. package/dist/jobs/analyze-jokes.js +143 -0
  128. package/dist/jobs/analyze-keyword-placement.js +155 -0
  129. package/dist/jobs/analyze-music.js +14 -0
  130. package/dist/jobs/analyze-video.js +282 -0
  131. package/dist/jobs/apply-cuts.js +83 -0
  132. package/dist/jobs/apply-sfx.js +107 -0
  133. package/dist/jobs/archive-clip.js +458 -0
  134. package/dist/jobs/assemble-goal.js +98 -0
  135. package/dist/jobs/audio-boost.js +71 -0
  136. package/dist/jobs/audio-mix.js +161 -0
  137. package/dist/jobs/backfill-asset-tags.js +144 -0
  138. package/dist/jobs/burn-watermark.js +316 -0
  139. package/dist/jobs/change-video-speed.js +176 -0
  140. package/dist/jobs/clean-video-audio.js +207 -0
  141. package/dist/jobs/cold-open.js +166 -0
  142. package/dist/jobs/combine-goal.js +108 -0
  143. package/dist/jobs/complete-provider-task.js +185 -0
  144. package/dist/jobs/copy-library-asset.js +33 -0
  145. package/dist/jobs/cost-estimate-assessment.js +113 -0
  146. package/dist/jobs/crop-image.js +47 -0
  147. package/dist/jobs/crop-video.js +71 -0
  148. package/dist/jobs/describe-image.js +181 -0
  149. package/dist/jobs/detect-scenes.js +33 -0
  150. package/dist/jobs/doctor-fix.js +100 -0
  151. package/dist/jobs/download-video.js +427 -0
  152. package/dist/jobs/enqueue-search-index.js +24 -0
  153. package/dist/jobs/even-split.js +122 -0
  154. package/dist/jobs/export-sequence.js +365 -0
  155. package/dist/jobs/extend-audio.js +229 -0
  156. package/dist/jobs/extract-audio.js +72 -0
  157. package/dist/jobs/extract-frame.js +58 -0
  158. package/dist/jobs/extract-notebook-audio.js +82 -0
  159. package/dist/jobs/extract-notebook-frame.js +93 -0
  160. package/dist/jobs/extract-video-clip.js +8 -0
  161. package/dist/jobs/extract-video-poster.js +55 -0
  162. package/dist/jobs/extract-waveform.js +25 -0
  163. package/dist/jobs/finish-goal.js +255 -0
  164. package/dist/jobs/forget-asset.js +11 -0
  165. package/dist/jobs/generate-acestep-cover.js +219 -0
  166. package/dist/jobs/generate-media.js +70 -0
  167. package/dist/jobs/image-subjects.js +1059 -0
  168. package/dist/jobs/index.js +18 -0
  169. package/dist/jobs/inject-first-frame.js +175 -0
  170. package/dist/jobs/isolate-vocals.js +69 -0
  171. package/dist/jobs/kie-paygate.js +177 -0
  172. package/dist/jobs/label-music-sections.js +5 -0
  173. package/dist/jobs/osg-placement.js +135 -0
  174. package/dist/jobs/pad-video.js +72 -0
  175. package/dist/jobs/plan-goal.js +97 -0
  176. package/dist/jobs/post-record.js +21 -0
  177. package/dist/jobs/post-to-x.js +21 -0
  178. package/dist/jobs/prepare-search-reference.js +190 -0
  179. package/dist/jobs/prepare-temporal-search.js +16 -0
  180. package/dist/jobs/process-audio.js +67 -0
  181. package/dist/jobs/process-image.js +58 -0
  182. package/dist/jobs/process-upload.js +166 -0
  183. package/dist/jobs/process-video.js +438 -0
  184. package/dist/jobs/register-all.js +161 -0
  185. package/dist/jobs/relayout-notebook.js +78 -0
  186. package/dist/jobs/remove-sound.js +299 -0
  187. package/dist/jobs/render-project.js +380 -0
  188. package/dist/jobs/render-qr-outro.js +138 -0
  189. package/dist/jobs/render-timeline.js +283 -0
  190. package/dist/jobs/repair-search-indexes.js +42 -0
  191. package/dist/jobs/replace-video-audio.js +64 -0
  192. package/dist/jobs/restore-archive-clip.js +53 -0
  193. package/dist/jobs/reverse-video.js +53 -0
  194. package/dist/jobs/rewrite-script.js +173 -0
  195. package/dist/jobs/run-notebook.js +3335 -0
  196. package/dist/jobs/semantic-index.js +797 -0
  197. package/dist/jobs/smooth-scene-concat.js +257 -0
  198. package/dist/jobs/splice-video.js +280 -0
  199. package/dist/jobs/split-video.js +127 -0
  200. package/dist/jobs/suggest-cuts.js +149 -0
  201. package/dist/jobs/suggest-sfx.js +135 -0
  202. package/dist/jobs/sync-video-to-audio.js +329 -0
  203. package/dist/jobs/tag-asset.js +266 -0
  204. package/dist/jobs/task-failure.js +16 -0
  205. package/dist/jobs/timewarp-video.js +152 -0
  206. package/dist/jobs/transcribe-video-audio.js +117 -0
  207. package/dist/jobs/transcribe.js +35 -0
  208. package/dist/jobs/translate-transcript.js +55 -0
  209. package/dist/jobs/trim-audio.js +133 -0
  210. package/dist/jobs/trim-video.js +291 -0
  211. package/dist/jobs/xai-paygate.js +26 -0
  212. package/dist/keyword-placement-cache.js +74 -0
  213. package/dist/lazy-render.js +142 -0
  214. package/dist/library-browse.js +78 -0
  215. package/dist/lightning-defaults.js +206 -0
  216. package/dist/load-env.js +12 -0
  217. package/dist/local-search-models.js +206 -0
  218. package/dist/local-upload.js +318 -0
  219. package/dist/logger.js +69 -0
  220. package/dist/media/adapters/alibaba.js +20 -0
  221. package/dist/media/adapters/elevenlabs.js +58 -0
  222. package/dist/media/adapters/fal.js +45 -0
  223. package/dist/media/adapters/index.js +26 -0
  224. package/dist/media/adapters/kie.js +120 -0
  225. package/dist/media/adapters/pending.js +21 -0
  226. package/dist/media/adapters/types.js +5 -0
  227. package/dist/media/adapters/xai.js +18 -0
  228. package/dist/media/catalog.js +236 -0
  229. package/dist/media/complete.js +191 -0
  230. package/dist/media/cost.js +45 -0
  231. package/dist/media/descriptor.js +157 -0
  232. package/dist/media/endpoints.js +11 -0
  233. package/dist/media/frame-options.js +18 -0
  234. package/dist/media/insert-video-timing.js +17 -0
  235. package/dist/media/json-schema.js +75 -0
  236. package/dist/media/legacy-task-types.js +129 -0
  237. package/dist/media/listing.js +24 -0
  238. package/dist/media/model-id.js +14 -0
  239. package/dist/media/preview.js +86 -0
  240. package/dist/media/prompt-dialects.js +65 -0
  241. package/dist/media/ref-fields.js +106 -0
  242. package/dist/media/refs.js +131 -0
  243. package/dist/media/request.js +188 -0
  244. package/dist/media/types.js +6 -0
  245. package/dist/media/wire.js +161 -0
  246. package/dist/media-library.js +535 -0
  247. package/dist/media-output-path.js +33 -0
  248. package/dist/multyculty-workflow.js +757 -0
  249. package/dist/notebook-action-catalog.js +608 -0
  250. package/dist/notebook-cell-outputs.js +726 -0
  251. package/dist/notebook-credit-retry.js +45 -0
  252. package/dist/notebook-fixtures.js +8 -0
  253. package/dist/notebook-generation-contract.js +77 -0
  254. package/dist/notebook-generation-controls.js +145 -0
  255. package/dist/notebook-goal-layout.js +40 -0
  256. package/dist/notebook-graph.js +37 -0
  257. package/dist/notebook-image-edit.js +84 -0
  258. package/dist/notebook-initial-generation-prompt.js +222 -0
  259. package/dist/notebook-live-analysis.js +941 -0
  260. package/dist/notebook-media-ops.js +579 -0
  261. package/dist/notebook-mutations.js +20 -0
  262. package/dist/notebook-placement.js +41 -0
  263. package/dist/notebook-provider-failure.js +87 -0
  264. package/dist/notebook-published-post.js +26 -0
  265. package/dist/notebook-reference-resolver.js +227 -0
  266. package/dist/notebook-relayout.js +270 -0
  267. package/dist/notebook-run-plan.js +519 -0
  268. package/dist/notebook-shots.js +162 -0
  269. package/dist/notebook-workflow.js +856 -0
  270. package/dist/one-password.js +200 -0
  271. package/dist/openai-intelligence.js +99 -0
  272. package/dist/original-media-file.js +12 -0
  273. package/dist/osg-assets.js +36 -0
  274. package/dist/osg-placement-cache.js +64 -0
  275. package/dist/osg-settings.js +27 -0
  276. package/dist/paid-tools.js +22 -0
  277. package/dist/platform-guidance.js +26 -0
  278. package/dist/posts/dropbox-auth.js +91 -0
  279. package/dist/posts/dropbox-upload.js +132 -0
  280. package/dist/posts/icloud-drive.js +134 -0
  281. package/dist/posts/icloud-status.js +64 -0
  282. package/dist/posts/post-contract.js +2 -0
  283. package/dist/posts/post-copy-format.js +43 -0
  284. package/dist/posts/post-copy.js +193 -0
  285. package/dist/posts/post-folder.js +18 -0
  286. package/dist/posts/post-metrics.js +119 -0
  287. package/dist/posts/post-store.js +340 -0
  288. package/dist/posts/x-post-target.js +43 -0
  289. package/dist/preview-payload.js +90 -0
  290. package/dist/prompt-surfaces.js +22 -0
  291. package/dist/provider-prompt-transform.js +77 -0
  292. package/dist/provider-status-link.js +21 -0
  293. package/dist/provider-task-record.js +78 -0
  294. package/dist/provider-task-status.js +47 -0
  295. package/dist/provider-usage.js +294 -0
  296. package/dist/queue.js +810 -0
  297. package/dist/reference-video-duration.js +17 -0
  298. package/dist/remotion/BlurFillVideo.js +51 -0
  299. package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
  300. package/dist/remotion/OsgOverlay.js +30 -0
  301. package/dist/remotion/QrOutroComposition.js +47 -0
  302. package/dist/remotion/RenderComposition.js +11 -0
  303. package/dist/remotion/browser.js +59 -0
  304. package/dist/remotion/constants.js +35 -0
  305. package/dist/remotion/index.js +66 -0
  306. package/dist/remotion/keyword-graphics.js +151 -0
  307. package/dist/remotion/keyword-placement.js +182 -0
  308. package/dist/remotion/osg-spec.js +198 -0
  309. package/dist/remotion/public/qr-outro-impact.wav +0 -0
  310. package/dist/remotion/qr-outro-audio.js +1 -0
  311. package/dist/remotion/qr-outro-spec.js +211 -0
  312. package/dist/remotion/shared-bundle.js +37 -0
  313. package/dist/remotion/subtitles/AliveCaptions.js +83 -0
  314. package/dist/remotion/subtitles/Highlight.js +58 -0
  315. package/dist/remotion/subtitles/Impact.js +67 -0
  316. package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
  317. package/dist/remotion/subtitles/alive-layout.js +321 -0
  318. package/dist/remotion/subtitles/alive-types.js +17 -0
  319. package/dist/remotion/subtitles/chunker.js +68 -0
  320. package/dist/remotion/subtitles/segments.js +42 -0
  321. package/dist/remotion/subtitles/styles.js +4 -0
  322. package/dist/remotion/subtitles/types.js +1 -0
  323. package/dist/remotion/util.js +18 -0
  324. package/dist/remotion-cancellation.js +15 -0
  325. package/dist/rename-helpers.js +204 -0
  326. package/dist/rhythm/analyze.js +128 -0
  327. package/dist/rhythm/contract.js +14 -0
  328. package/dist/rhythm/draft-model.js +19 -0
  329. package/dist/rhythm/energy.js +133 -0
  330. package/dist/rhythm/fft.js +44 -0
  331. package/dist/rhythm/model.js +71 -0
  332. package/dist/rhythm/music-analysis.js +124 -0
  333. package/dist/rhythm/music-draft.js +261 -0
  334. package/dist/rhythm/onset.js +101 -0
  335. package/dist/rhythm/run-worker.js +51 -0
  336. package/dist/rhythm/score.js +47 -0
  337. package/dist/rhythm/section-labels.js +168 -0
  338. package/dist/rhythm/sequence-rhythm.js +67 -0
  339. package/dist/rhythm/synthesize.js +103 -0
  340. package/dist/rhythm/timing.js +98 -0
  341. package/dist/rhythm/track.js +320 -0
  342. package/dist/rhythm/visual-fit.js +4 -0
  343. package/dist/rhythm/worker.js +25 -0
  344. package/dist/scene-detect.js +200 -0
  345. package/dist/scene-transition.js +12 -0
  346. package/dist/scheduled-posts.js +200 -0
  347. package/dist/search-index-coverage.js +13 -0
  348. package/dist/search-index-media.js +71 -0
  349. package/dist/search-reference-store.js +60 -0
  350. package/dist/search-scoring.js +17 -0
  351. package/dist/serve.js +125 -0
  352. package/dist/server-lock.js +143 -0
  353. package/dist/server.js +42 -0
  354. package/dist/srt.js +16 -0
  355. package/dist/tag-backfill-state.js +121 -0
  356. package/dist/tag-settings.js +30 -0
  357. package/dist/tag-vocabulary.js +237 -0
  358. package/dist/test-queue-cleanup.js +1 -0
  359. package/dist/time-parse.js +50 -0
  360. package/dist/timeline-helpers.js +554 -0
  361. package/dist/timewarp.js +87 -0
  362. package/dist/tools/_image-inputs.js +9 -0
  363. package/dist/tools/_media-refs.js +84 -0
  364. package/dist/tools/_shared.js +59 -0
  365. package/dist/tools/_suggest-shared.js +279 -0
  366. package/dist/tools/add-timeline-audio.js +73 -0
  367. package/dist/tools/alive-captions.js +157 -0
  368. package/dist/tools/analyze-jokes.js +56 -0
  369. package/dist/tools/analyze-video.js +66 -0
  370. package/dist/tools/animate-keywords.js +217 -0
  371. package/dist/tools/api-status.js +42 -0
  372. package/dist/tools/application-commands.js +34 -0
  373. package/dist/tools/apply-cuts.js +112 -0
  374. package/dist/tools/apply-sfx.js +152 -0
  375. package/dist/tools/apply-watermark.js +670 -0
  376. package/dist/tools/archive-clip.js +74 -0
  377. package/dist/tools/asset-history.js +61 -0
  378. package/dist/tools/asset-manifest.js +24 -0
  379. package/dist/tools/asset-tags.js +151 -0
  380. package/dist/tools/assign-slot.js +97 -0
  381. package/dist/tools/audio-boost.js +107 -0
  382. package/dist/tools/audio-mix.js +121 -0
  383. package/dist/tools/book-usage.js +16 -0
  384. package/dist/tools/chain.js +136 -0
  385. package/dist/tools/change-video-speed.js +105 -0
  386. package/dist/tools/chat-history.js +5 -0
  387. package/dist/tools/chat.js +848 -0
  388. package/dist/tools/clean-video-audio.js +97 -0
  389. package/dist/tools/clear-failed-tasks.js +66 -0
  390. package/dist/tools/cold-open.js +114 -0
  391. package/dist/tools/command-plan.js +1680 -0
  392. package/dist/tools/commands.js +1666 -0
  393. package/dist/tools/concat-images.js +236 -0
  394. package/dist/tools/connect-service.js +55 -0
  395. package/dist/tools/copy-file-to-clipboard.js +58 -0
  396. package/dist/tools/create-character.js +114 -0
  397. package/dist/tools/create-qart.js +253 -0
  398. package/dist/tools/create-qr-outro.js +149 -0
  399. package/dist/tools/create-qrbtf.js +215 -0
  400. package/dist/tools/create-watermark.js +97 -0
  401. package/dist/tools/crop-asset.js +18 -0
  402. package/dist/tools/crop-image.js +120 -0
  403. package/dist/tools/crop-video.js +97 -0
  404. package/dist/tools/delete-asset.js +104 -0
  405. package/dist/tools/describe-image.js +69 -0
  406. package/dist/tools/detect-scenes.js +44 -0
  407. package/dist/tools/doctor.js +126 -0
  408. package/dist/tools/download-video.js +114 -0
  409. package/dist/tools/duplicate-asset.js +184 -0
  410. package/dist/tools/edit-timeline.js +59 -0
  411. package/dist/tools/editing-moments.js +105 -0
  412. package/dist/tools/estimate-cost.js +171 -0
  413. package/dist/tools/even-split.js +295 -0
  414. package/dist/tools/export-asset-file.js +22 -0
  415. package/dist/tools/export-asset.js +140 -0
  416. package/dist/tools/export-sequence.js +250 -0
  417. package/dist/tools/export-to-downloads.js +54 -0
  418. package/dist/tools/export-transcript-srt.js +79 -0
  419. package/dist/tools/extend-audio.js +100 -0
  420. package/dist/tools/extract-audio.js +174 -0
  421. package/dist/tools/extract-frame.js +108 -0
  422. package/dist/tools/extract-notebook-audio.js +173 -0
  423. package/dist/tools/extract-notebook-frame.js +160 -0
  424. package/dist/tools/extract-video-clip.js +101 -0
  425. package/dist/tools/generate-media.js +157 -0
  426. package/dist/tools/generation-payload.js +48 -0
  427. package/dist/tools/get-timeline.js +12 -0
  428. package/dist/tools/get-watermark-config.js +38 -0
  429. package/dist/tools/goal.js +644 -0
  430. package/dist/tools/image-subjects.js +711 -0
  431. package/dist/tools/index.js +323 -0
  432. package/dist/tools/inject-first-frame.js +154 -0
  433. package/dist/tools/invert-image.js +70 -0
  434. package/dist/tools/isolate-vocals.js +66 -0
  435. package/dist/tools/list-assets.js +98 -0
  436. package/dist/tools/list-cell-generations.js +225 -0
  437. package/dist/tools/list-elevenlabs-voices.js +163 -0
  438. package/dist/tools/log-wrapper.js +29 -0
  439. package/dist/tools/make-cover-song.js +95 -0
  440. package/dist/tools/mark-asset-seen.js +23 -0
  441. package/dist/tools/media-library.js +245 -0
  442. package/dist/tools/move-timeline-audio.js +35 -0
  443. package/dist/tools/music-analysis.js +60 -0
  444. package/dist/tools/music-draft.js +29 -0
  445. package/dist/tools/pad-video.js +87 -0
  446. package/dist/tools/post-record.js +83 -0
  447. package/dist/tools/post.js +168 -0
  448. package/dist/tools/prompt-history.js +164 -0
  449. package/dist/tools/provider-key-status.js +248 -0
  450. package/dist/tools/qrbtf-render.js +849 -0
  451. package/dist/tools/read-asset-metadata.js +26 -0
  452. package/dist/tools/read-file-at-commit.js +29 -0
  453. package/dist/tools/read-transcript.js +41 -0
  454. package/dist/tools/reconcile-project-work.js +1452 -0
  455. package/dist/tools/recover-asset.js +28 -0
  456. package/dist/tools/remove-sound.js +72 -0
  457. package/dist/tools/remove-timeline-audio.js +34 -0
  458. package/dist/tools/remove-timeline-slot.js +40 -0
  459. package/dist/tools/rename-all.js +434 -0
  460. package/dist/tools/rename-asset.js +34 -0
  461. package/dist/tools/render-settings.js +47 -0
  462. package/dist/tools/render-timeline.js +120 -0
  463. package/dist/tools/render.js +384 -0
  464. package/dist/tools/reorder-character-images.js +45 -0
  465. package/dist/tools/replace-video-audio.js +133 -0
  466. package/dist/tools/restore-archive-clips.js +209 -0
  467. package/dist/tools/restore-asset-version.js +37 -0
  468. package/dist/tools/retry-generation.js +54 -0
  469. package/dist/tools/reverse-video.js +74 -0
  470. package/dist/tools/rewrite-prompt.js +388 -0
  471. package/dist/tools/rewrite-script.js +67 -0
  472. package/dist/tools/schema-from-apicity.js +66 -0
  473. package/dist/tools/search-archive.js +150 -0
  474. package/dist/tools/semantic-search.js +1214 -0
  475. package/dist/tools/sequence-rhythm.js +43 -0
  476. package/dist/tools/set-character-metadata.js +84 -0
  477. package/dist/tools/set-env-secret.js +40 -0
  478. package/dist/tools/set-osg.js +143 -0
  479. package/dist/tools/show-folder.js +37 -0
  480. package/dist/tools/smooth-scene-concat.js +122 -0
  481. package/dist/tools/splice-video.js +149 -0
  482. package/dist/tools/split-video.js +202 -0
  483. package/dist/tools/suggest-cuts.js +45 -0
  484. package/dist/tools/suggest-sfx.js +43 -0
  485. package/dist/tools/sync-video-to-audio.js +115 -0
  486. package/dist/tools/tag-backfill.js +177 -0
  487. package/dist/tools/timewarp-video.js +85 -0
  488. package/dist/tools/tool-def.js +1 -0
  489. package/dist/tools/tool-json-schema.js +116 -0
  490. package/dist/tools/transcribe.js +90 -0
  491. package/dist/tools/translate-subtitles.js +59 -0
  492. package/dist/tools/trim-audio.js +139 -0
  493. package/dist/tools/trim-video.js +168 -0
  494. package/dist/tools/update-script.js +25 -0
  495. package/dist/tools/update-timeline-audio.js +40 -0
  496. package/dist/tools/upload-file.js +41 -0
  497. package/dist/tools/verify-x-mentions.js +141 -0
  498. package/dist/tools/video-editing-command-contract.js +291 -0
  499. package/dist/tools/video-source-reference.js +30 -0
  500. package/dist/tools/video-time-input.js +18 -0
  501. package/dist/tools/videobook-domain.js +2021 -0
  502. package/dist/tools/wait-for-asset-ready.js +143 -0
  503. package/dist/tools/workspace-actions.js +54 -0
  504. package/dist/transcript-projection.js +202 -0
  505. package/dist/transcript-srt.js +33 -0
  506. package/dist/upload.js +76 -0
  507. package/dist/usage-providers.js +25 -0
  508. package/dist/video-analysis-projection.js +46 -0
  509. package/dist/video-poster.js +33 -0
  510. package/dist/video-proxy.js +99 -0
  511. package/dist/vision-regions.js +111 -0
  512. package/dist/voice-actions.js +90 -0
  513. package/dist/voice-audio.js +55 -0
  514. package/dist/voice-config.js +122 -0
  515. package/dist/voice-continuation.js +54 -0
  516. package/dist/voice-contract.js +1 -0
  517. package/dist/voice-http.js +145 -0
  518. package/dist/voice-reply-progress.js +55 -0
  519. package/dist/voice-session.js +992 -0
  520. package/dist/watermark-badge.js +259 -0
  521. package/dist/watermark-config.js +111 -0
  522. package/dist/watermark-handle-position.js +55 -0
  523. package/dist/watermark-platform.js +15 -0
  524. package/dist/watermark-timing.js +16 -0
  525. package/dist/workspace-action-catalog.js +315 -0
  526. package/dist/workspace-action-contract.js +21 -0
  527. package/dist/workspace-action-groups.js +98 -0
  528. package/dist/workspace-action-surfaces.js +7 -0
  529. package/dist/x/account.js +88 -0
  530. package/dist/x/authenticated-handle.js +30 -0
  531. package/dist/x/oauth-flow.js +191 -0
  532. package/dist/x/post-contract.js +1 -0
  533. package/dist/x/post-metadata.js +44 -0
  534. package/dist/x/post-to-x.js +456 -0
  535. package/dist/x/published-post-cell.js +90 -0
  536. package/dist/x/x-tokens.js +69 -0
  537. package/dist/ytdlp-binary.js +65 -0
  538. package/dist/zod-slots.js +334 -0
  539. package/package.json +84 -4
  540. package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
  541. package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
  542. package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
  543. package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
  544. package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
  545. package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
  546. package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
  547. package/ui/assets/index-DzPAnCUj.css +1 -0
  548. package/ui/assets/index-kS1905s5.js +37 -0
  549. package/ui/assets/inspector-MlzQbTVo.js +1 -0
  550. package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
  551. package/ui/assets/music-video-Cbe80kmQ.js +1 -0
  552. package/ui/assets/music-video-DPaiKKEq.css +1 -0
  553. package/ui/assets/ui-Oirr9VOf.css +1 -0
  554. package/ui/assets/ui-xo_Y1yQg.js +2 -0
  555. package/ui/assets/vendor-D2rfiZ5j.css +1 -0
  556. package/ui/assets/vendor-DfTCWgKm.js +19 -0
  557. package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
  558. package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
  559. package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
  560. package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
  561. package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
  562. package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
  563. package/ui/assets/videobook-Qlp9HQFu.js +20 -0
  564. package/ui/favicon.svg +8 -0
  565. package/ui/index.html +126 -0
@@ -0,0 +1,31 @@
1
+ import { runFfmpeg } from "../ffmpeg.js";
2
+ // Start offsets (seconds) of sequential fixed-size chunks covering `duration`.
3
+ export function chunkStarts(duration, chunkSeconds) {
4
+ if (!Number.isFinite(duration) || duration <= 0)
5
+ return [0];
6
+ const count = Math.max(1, Math.ceil(duration / chunkSeconds));
7
+ return Array.from({ length: count }, (_, index) => index * chunkSeconds);
8
+ }
9
+ // Clamped length (seconds) of the chunk starting at `start`, with a 0.1s floor
10
+ // so a near-zero tail still produces a decodable extract.
11
+ export function chunkLength(duration, start, chunkSeconds) {
12
+ if (duration <= 0)
13
+ return 0;
14
+ return Math.max(0.1, Math.min(chunkSeconds, duration - start));
15
+ }
16
+ // Extract a mono 16kHz mp3 segment of `mediaPath` to `chunkPath`.
17
+ export async function extractAudioChunk(mediaPath, start, duration, chunkPath) {
18
+ await runFfmpeg([
19
+ "-y",
20
+ ...(start > 0 ? ["-ss", String(start)] : []),
21
+ ...(duration > 0 ? ["-t", String(duration)] : []),
22
+ "-i", mediaPath,
23
+ "-vn",
24
+ "-ac", "1",
25
+ "-ar", "16000",
26
+ "-acodec", "libmp3lame",
27
+ "-q:a", "5",
28
+ "-f", "mp3",
29
+ chunkPath,
30
+ ]);
31
+ }
@@ -0,0 +1,15 @@
1
+ // Fetch `url`, write the response bytes to the given asset file, and return
2
+ // the buffer for callers that need it (e.g. for content-hash provenance).
3
+ // `options.kind` only changes the error-message wording.
4
+ export async function downloadUrlIntoAsset(engine, url, assetId, filename, options = {}) {
5
+ const response = await fetch(url, options.fetchInit);
6
+ if (!response.ok) {
7
+ throw new Error(`Failed to download ${options.kind ?? "file"}: ${response.status} ${response.statusText}`);
8
+ }
9
+ const buffer = Buffer.from(await response.arrayBuffer());
10
+ const written = await engine.files.write(assetId, filename, buffer);
11
+ if (!written.ok) {
12
+ throw new Error(`Failed to persist ${options.kind ?? "file"}: ${written.error.message}`);
13
+ }
14
+ return buffer;
15
+ }
@@ -0,0 +1,22 @@
1
+ import { FalError } from "@apicity/fal";
2
+ export function describeFalError(cause) {
3
+ const message = cause instanceof Error ? cause.message : String(cause);
4
+ if (!(cause instanceof FalError))
5
+ return message;
6
+ if (!cause.body || typeof cause.body !== "object")
7
+ return message;
8
+ const raw = cause.body.detail;
9
+ const detail = typeof raw === "string" ? raw : Array.isArray(raw) ? raw.slice(0, 8).flatMap((item) => {
10
+ if (!item || typeof item !== "object")
11
+ return [];
12
+ const { loc, msg } = item;
13
+ if (typeof msg !== "string")
14
+ return [];
15
+ const path = Array.isArray(loc) ? loc.filter((part) => typeof part === "string" || typeof part === "number")
16
+ .filter((part) => part !== "body").join(".") : "";
17
+ return [path ? `${path}: ${msg}` : msg];
18
+ }).join("; ") : "";
19
+ if (detail.length === 0 || message.includes(detail))
20
+ return message;
21
+ return `${message}: ${detail.slice(0, 2000)}`;
22
+ }
@@ -0,0 +1,35 @@
1
+ import { createFal } from "../usage-providers.js";
2
+ import { uploadFile } from "@apicity/fal";
3
+ import { prepareAssetForUpload, uploadBytesToFreeHost, } from "../upload.js";
4
+ import { getApiKey } from "../api-key.js";
5
+ import { logger } from "../logger.js";
6
+ // Upload prepared bytes to fal's native CDN via @apicity/fal's uploadFile
7
+ // helper. Returns a stable https://v3.fal.media/... URL. Falls back to the
8
+ // free-host rotation on any native failure (missing key, network, non-2xx).
9
+ async function uploadPreparedToFal(data, hashedFilename, contentType) {
10
+ const apiKey = getApiKey("FAL_API_KEY");
11
+ if (apiKey) {
12
+ try {
13
+ const provider = createFal({ apiKey, timeout: 60_000 });
14
+ return await uploadFile(provider, {
15
+ data,
16
+ filename: hashedFilename,
17
+ contentType,
18
+ });
19
+ }
20
+ catch (err) {
21
+ logger.warn("fal-upload-fallback", { error: err });
22
+ }
23
+ }
24
+ else {
25
+ logger.warn("fal-upload-unconfigured");
26
+ }
27
+ return uploadBytesToFreeHost(data, hashedFilename);
28
+ }
29
+ // Upload an asset file to fal's official CDN via storage.upload.initiate.
30
+ // Returns a stable https://v3.fal.media/... URL that fal's own model endpoints
31
+ // can fetch directly — no third-party free-host dependency.
32
+ export async function uploadAssetToFal(engine, assetId, filename) {
33
+ const { data, hashedFilename, contentType } = await prepareAssetForUpload(engine, assetId, filename);
34
+ return uploadPreparedToFal(data, hashedFilename, contentType);
35
+ }
@@ -0,0 +1,213 @@
1
+ import { createFal } from "../usage-providers.js";
2
+ import { submitMediaJob } from "@apicity/kie";
3
+ import { FalError } from "@apicity/fal";
4
+ import { SeedreamProImageToImageRequestSchema } from "@apicity/kie/zod";
5
+ import { setTimeout as delay } from "node:timers/promises";
6
+ import sharp from "sharp";
7
+ import { requireApiKey } from "../api-key.js";
8
+ import { IMAGE_EDIT_MODELS } from "../image-edit-models.js";
9
+ import { resolveModel } from "../media/descriptor.js";
10
+ import { buildWireRequest } from "../media/request.js";
11
+ import { mintKieOtp } from "./kie-paygate.js";
12
+ import { uploadAssetToFal } from "./_fal-upload.js";
13
+ import { uploadAssetToKie } from "./_kie-upload.js";
14
+ import { KIE_RESULT_TIMEOUT_MS, kieProvider, parseResultUrls, waitForKieResult, } from "./_kie-poll.js";
15
+ const FAL_RESULT_TIMEOUT_MS = 25 * 60_000;
16
+ export const GROK_EXACT_MASK_ADAPTER_ID = "grok-imagine-image-2-0/image-edit";
17
+ function closestAspectRatio(width, height) {
18
+ const target = width / height;
19
+ const candidates = [
20
+ ["1:1", 1],
21
+ ["4:3", 4 / 3],
22
+ ["3:4", 3 / 4],
23
+ ["16:9", 16 / 9],
24
+ ["9:16", 9 / 16],
25
+ ["2:3", 2 / 3],
26
+ ["3:2", 3 / 2],
27
+ ];
28
+ return candidates.reduce((best, candidate) => Math.abs(candidate[1] - target) < Math.abs(best[1] - target)
29
+ ? candidate
30
+ : best)[0];
31
+ }
32
+ export function seedreamRegionReplacementRequest(input) {
33
+ const parsed = SeedreamProImageToImageRequestSchema.parse({
34
+ model: "seedream/5-pro-image-to-image",
35
+ input: {
36
+ image_urls: [...input.imageUrls],
37
+ prompt: input.prompt,
38
+ aspect_ratio: closestAspectRatio(input.width, input.height),
39
+ quality: "basic",
40
+ output_format: "png",
41
+ nsfw_checker: false,
42
+ },
43
+ });
44
+ return { model: parsed.model, input: { ...parsed.input } };
45
+ }
46
+ export async function downloadResultImages(urls, signal, label) {
47
+ const images = [];
48
+ for (const [index, url] of urls.entries()) {
49
+ const response = await fetch(url, { signal });
50
+ if (!response.ok) {
51
+ throw new Error(`Failed to download ${label} ${index + 1}: ${response.status} ${response.statusText}`);
52
+ }
53
+ images.push(await sharp(Buffer.from(await response.arrayBuffer())).png().toBuffer());
54
+ }
55
+ return images;
56
+ }
57
+ function toProviderRequest(model, input) {
58
+ return { model, input };
59
+ }
60
+ function falNamespace(endpointId) {
61
+ return endpointId.split("/").slice(0, 2).join("/");
62
+ }
63
+ function falResultImageUrls(resultObject) {
64
+ const images = resultObject.images;
65
+ if (!Array.isArray(images))
66
+ return [];
67
+ return images
68
+ .map((image) => (typeof image === "object" && image !== null && "url" in image
69
+ ? image.url
70
+ : undefined))
71
+ .filter((url) => typeof url === "string");
72
+ }
73
+ function optionsFor(model, input) {
74
+ const options = {};
75
+ const aspect = model.options.find((option) => option.name === "aspect_ratio");
76
+ const closest = closestAspectRatio(input.width, input.height);
77
+ if (aspect?.enumValues.includes(closest))
78
+ options.aspect_ratio = closest;
79
+ const format = model.options.find((option) => option.name === "output_format");
80
+ if (format?.enumValues.includes("png"))
81
+ options.output_format = "png";
82
+ return options;
83
+ }
84
+ function catalogEditRequest(model, input) {
85
+ const built = buildWireRequest(model, {
86
+ prompt: input.prompt,
87
+ refUrls: { image: [...input.imageUrls] },
88
+ options: optionsFor(model, input),
89
+ });
90
+ if (!built.ok)
91
+ throw new Error(built.error);
92
+ return built.request;
93
+ }
94
+ function kieImageEditAdapter(model, build) {
95
+ const parse = (value) => model.wire.request.safeParse(value);
96
+ const buildRequest = build ?? ((input) => {
97
+ const request = catalogEditRequest(model, input);
98
+ return toProviderRequest(request.model, { ...request.input });
99
+ });
100
+ return {
101
+ id: model.apiId,
102
+ uploadAsset: (engine, artifactId, filename) => uploadAssetToKie(engine, artifactId, filename),
103
+ buildSemanticRequest: (input) => buildRequest({ ...input, imageUrls: [input.imageUrls[0]] }),
104
+ buildRegionRequest: buildRequest,
105
+ submit: async (engine, imageAssetId, request) => {
106
+ const parsed = parse(request);
107
+ if (!parsed.success)
108
+ throw new Error(`Invalid ${model.id} image edit request`);
109
+ const metadata = await engine.metadata.artifacts.write(imageAssetId, "generation-payload", parsed.data);
110
+ if (!metadata.ok)
111
+ throw new Error(metadata.error.message);
112
+ const taskId = await submitMediaJob(kieProvider(), parsed.data, {
113
+ otp: mintKieOtp(parsed.data),
114
+ });
115
+ const taskMetadata = await engine.metadata.artifacts.write(imageAssetId, "kie-task-id", { taskId });
116
+ if (!taskMetadata.ok)
117
+ throw new Error(taskMetadata.error.message);
118
+ return taskId;
119
+ },
120
+ awaitResult: async (taskId, signal) => {
121
+ const resultJson = await waitForKieResult(kieProvider(), taskId, signal, Date.now() + KIE_RESULT_TIMEOUT_MS, "subject-edit");
122
+ const urls = parseResultUrls(resultJson);
123
+ return downloadResultImages(urls, signal, `${model.apiId} edit`);
124
+ },
125
+ parseStoredPayload: (payload) => {
126
+ const parsed = parse(payload);
127
+ if (!parsed.success)
128
+ return null;
129
+ return toProviderRequest(parsed.data.model, { ...parsed.data.input });
130
+ },
131
+ };
132
+ }
133
+ async function awaitFalImages(model, taskId, signal) {
134
+ const provider = createFal({ apiKey: requireApiKey("FAL_API_KEY"), timeout: 30_000 });
135
+ const namespace = falNamespace(model.apiId);
136
+ const deadlineAt = Date.now() + FAL_RESULT_TIMEOUT_MS;
137
+ for (;;) {
138
+ if (Date.now() >= deadlineAt) {
139
+ throw new Error(`Fal task ${taskId} did not finish within 25 minutes`);
140
+ }
141
+ let status;
142
+ try {
143
+ status = await provider.v1.queue.status({ endpoint_id: namespace, request_id: taskId });
144
+ }
145
+ catch (cause) {
146
+ if (cause instanceof FalError && cause.status >= 400 && cause.status < 500)
147
+ throw cause;
148
+ await delay(Math.min(3_000, Math.max(1, deadlineAt - Date.now())), undefined, { signal });
149
+ continue;
150
+ }
151
+ if (status.status === "COMPLETED") {
152
+ if (status.error) {
153
+ throw new Error(`Fal image edit failed: ${status.error}`);
154
+ }
155
+ const result = await provider.v1.queue.result({ endpoint_id: namespace, request_id: taskId });
156
+ const urls = falResultImageUrls(result);
157
+ if (urls.length === 0)
158
+ throw new Error(`${model.apiId} image edit returned no image`);
159
+ return downloadResultImages(urls, signal, `${model.apiId} edit`);
160
+ }
161
+ await delay(Math.min(3_000, Math.max(1, deadlineAt - Date.now())), undefined, { signal });
162
+ }
163
+ }
164
+ function falImageEditAdapter(model) {
165
+ const parse = (value) => model.wire.input.safeParse(value);
166
+ const buildRequest = (input) => toProviderRequest(model.apiId, catalogEditRequest(model, input));
167
+ return {
168
+ id: model.apiId,
169
+ uploadAsset: (engine, artifactId, filename) => uploadAssetToFal(engine, artifactId, filename),
170
+ buildSemanticRequest: (input) => buildRequest({ ...input, imageUrls: [input.imageUrls[0]] }),
171
+ buildRegionRequest: buildRequest,
172
+ submit: async (engine, imageAssetId, request) => {
173
+ const parsed = parse(request.input);
174
+ if (!parsed.success)
175
+ throw new Error(`Invalid ${model.id} image edit request`);
176
+ const input = parsed.data;
177
+ const provider = createFal({ apiKey: requireApiKey("FAL_API_KEY"), timeout: 60_000 });
178
+ const metadata = await engine.metadata.artifacts.write(imageAssetId, "generation-payload", { endpoint_id: model.apiId, input });
179
+ if (!metadata.ok)
180
+ throw new Error(metadata.error.message);
181
+ const submitted = await provider.post.v1.queue.submit({ endpoint_id: model.apiId, input });
182
+ const taskMetadata = await engine.metadata.artifacts.write(imageAssetId, "kie-task-id", { taskId: submitted.request_id });
183
+ if (!taskMetadata.ok)
184
+ throw new Error(taskMetadata.error.message);
185
+ return submitted.request_id;
186
+ },
187
+ awaitResult: (taskId, signal) => awaitFalImages(model, taskId, signal),
188
+ parseStoredPayload: (payload) => {
189
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload))
190
+ return null;
191
+ if (!("endpoint_id" in payload) || payload.endpoint_id !== model.apiId)
192
+ return null;
193
+ if (!("input" in payload))
194
+ return null;
195
+ const parsed = parse(payload.input);
196
+ if (!parsed.success)
197
+ return null;
198
+ return toProviderRequest(model.apiId, { ...parsed.data });
199
+ },
200
+ };
201
+ }
202
+ function adapterForEditModel(info) {
203
+ const model = resolveModel(info.catalogModel);
204
+ if (info.provider === "fal")
205
+ return falImageEditAdapter(model);
206
+ if (model.apiId === "seedream/5-pro-image-to-image")
207
+ return kieImageEditAdapter(model, seedreamRegionReplacementRequest);
208
+ return kieImageEditAdapter(model);
209
+ }
210
+ export const IMAGE_EDIT_ADAPTERS = IMAGE_EDIT_MODELS.map(adapterForEditModel);
211
+ export function imageEditAdapter(id) {
212
+ return IMAGE_EDIT_ADAPTERS.find((adapter) => adapter.id === id);
213
+ }
@@ -0,0 +1,119 @@
1
+ import { createKie } from "../usage-providers.js";
2
+ import { setTimeout as delay } from "node:timers/promises";
3
+ import { requireApiKey } from "../api-key.js";
4
+ import { subjectAnalysisProviderFailureMessage } from "../image-subject-map.js";
5
+ import { ensurePaygateSecret } from "./kie-paygate.js";
6
+ export const KIE_RESULT_TIMEOUT_MS = 25 * 60_000;
7
+ export function kieProvider() {
8
+ return createKie({
9
+ apiKey: requireApiKey("KIE_API_KEY"),
10
+ timeout: 60_000,
11
+ paygate: { secret: ensurePaygateSecret() },
12
+ });
13
+ }
14
+ function stringValue(value) {
15
+ return typeof value === "string" && value.trim() ? value.trim() : null;
16
+ }
17
+ function record(value) {
18
+ return value !== null && typeof value === "object" && !Array.isArray(value)
19
+ ? value
20
+ : null;
21
+ }
22
+ function abortReason(signal) {
23
+ if (signal.reason instanceof Error)
24
+ return signal.reason;
25
+ const error = new Error(typeof signal.reason === "string" ? signal.reason : "Operation aborted");
26
+ error.name = "AbortError";
27
+ return error;
28
+ }
29
+ function waitForOperation(operation, signal, deadlineAt, timeoutError) {
30
+ if (signal.aborted)
31
+ return Promise.reject(abortReason(signal));
32
+ const remaining = deadlineAt - Date.now();
33
+ if (remaining <= 0)
34
+ return Promise.reject(timeoutError);
35
+ return new Promise((resolve, reject) => {
36
+ let settled = false;
37
+ const cleanup = () => {
38
+ clearTimeout(timer);
39
+ signal.removeEventListener("abort", onAbort);
40
+ };
41
+ const resolveOnce = (value) => {
42
+ if (settled)
43
+ return;
44
+ settled = true;
45
+ cleanup();
46
+ resolve(value);
47
+ };
48
+ const rejectOnce = (cause) => {
49
+ if (settled)
50
+ return;
51
+ settled = true;
52
+ cleanup();
53
+ reject(cause);
54
+ };
55
+ const onAbort = () => rejectOnce(abortReason(signal));
56
+ const timer = setTimeout(() => rejectOnce(timeoutError), remaining);
57
+ signal.addEventListener("abort", onAbort, { once: true });
58
+ if (signal.aborted)
59
+ onAbort();
60
+ void operation.then(resolveOnce, rejectOnce);
61
+ });
62
+ }
63
+ function kieResultFailureMessage(context, message, code) {
64
+ if (context === "subject-analysis") {
65
+ return subjectAnalysisProviderFailureMessage(message, code);
66
+ }
67
+ const detail = message || code || "Unknown provider error";
68
+ const codeDetail = code && code !== detail ? ` (${code})` : "";
69
+ return `Image subject edit provider failed: ${detail}${codeDetail}`;
70
+ }
71
+ export async function waitForKieResult(provider, taskId, signal, deadlineAt, context) {
72
+ const timeoutError = context === "subject-analysis"
73
+ ? new Error("Subject analysis did not finish within 25 minutes. Retry the analysis.")
74
+ : new Error(`Kie task ${taskId} did not finish within 25 minutes`);
75
+ while (Date.now() < deadlineAt) {
76
+ const result = await waitForOperation(provider.get.api.v1.jobs.recordInfo(taskId), signal, deadlineAt, timeoutError);
77
+ const state = result.data?.state?.toLowerCase();
78
+ if (state === "success") {
79
+ if (!result.data?.resultJson) {
80
+ throw new Error("Kie task succeeded without result JSON");
81
+ }
82
+ return result.data.resultJson;
83
+ }
84
+ if (state === "fail" || state === "failed") {
85
+ throw new Error(kieResultFailureMessage(context, stringValue(result.data?.failMsg), stringValue(result.data?.failCode)));
86
+ }
87
+ await delay(Math.min(3_000, Math.max(1, deadlineAt - Date.now())), undefined, {
88
+ signal,
89
+ });
90
+ }
91
+ throw timeoutError;
92
+ }
93
+ export function parseResultUrls(resultJson) {
94
+ let parsed;
95
+ try {
96
+ parsed = JSON.parse(resultJson);
97
+ }
98
+ catch {
99
+ throw new Error("Seedream returned invalid result JSON");
100
+ }
101
+ const root = record(parsed);
102
+ if (!root)
103
+ throw new Error("Seedream returned an invalid image result");
104
+ for (const value of [
105
+ root.resultUrls,
106
+ root.result_urls,
107
+ record(root.resultObject)?.resultUrls,
108
+ record(root.resultObject)?.result_urls,
109
+ record(root.result)?.resultUrls,
110
+ record(root.result)?.result_urls,
111
+ ]) {
112
+ if (!Array.isArray(value))
113
+ continue;
114
+ const urls = value.flatMap((entry) => stringValue(entry) ?? []);
115
+ if (urls.length > 0)
116
+ return urls;
117
+ }
118
+ throw new Error("Seedream image edit returned no downloadable images");
119
+ }
@@ -0,0 +1,32 @@
1
+ import { createKie } from "../usage-providers.js";
2
+ import { uploadFile } from "@apicity/kie";
3
+ import { prepareAssetForUpload, uploadBytesToFreeHost, } from "../upload.js";
4
+ import { getApiKey } from "../api-key.js";
5
+ import { logger } from "../logger.js";
6
+ // Upload prepared bytes to Kie's native storage via POST /api/file-stream-upload.
7
+ // Kie workers run in China — using their first-party CDN avoids the
8
+ // InvalidParameter.DataInspection failures we'd hit when the free-host URL
9
+ // is unreachable from their side. Falls back to the free-host rotation only
10
+ // if the native endpoint fails (missing key, timeout, 5xx, etc.).
11
+ async function uploadPreparedToKie(data, hashedFilename, contentType) {
12
+ const apiKey = getApiKey("KIE_API_KEY");
13
+ if (apiKey) {
14
+ try {
15
+ const provider = createKie({ apiKey, timeout: 120_000 });
16
+ const blob = new Blob([new Uint8Array(data)], { type: contentType });
17
+ return await uploadFile(provider, blob, hashedFilename);
18
+ }
19
+ catch (err) {
20
+ logger.warn("kie-upload-fallback", { error: err });
21
+ }
22
+ }
23
+ else {
24
+ logger.warn("kie-upload-unconfigured");
25
+ }
26
+ return uploadBytesToFreeHost(data, hashedFilename);
27
+ }
28
+ // Upload an asset to Kie's native storage.
29
+ export async function uploadAssetToKie(engine, assetId, filename) {
30
+ const { data, hashedFilename, contentType } = await prepareAssetForUpload(engine, assetId, filename);
31
+ return uploadPreparedToKie(data, hashedFilename, contentType);
32
+ }
@@ -0,0 +1,163 @@
1
+ import { logger } from "../logger.js";
2
+ const HEARTBEAT_INTERVAL_MS = 20_000;
3
+ const HEARTBEAT_EXTEND_MS = 60_000;
4
+ const DEFAULT_LEASE_MS = 15 * 60_000;
5
+ export const DOWNSTREAM_LEASE_WAIT_MS = 60_000;
6
+ function defaultSleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ async function beginLocalLease(engine, assetId, kind, options, signal) {
10
+ const started = Date.now();
11
+ const waitForActiveMs = options.waitForActiveMs ?? 0;
12
+ while (true) {
13
+ signal?.throwIfAborted();
14
+ const lease = await engine.jobs.artifactWork.begin(assetId, {
15
+ kind,
16
+ ownerKind: "job",
17
+ durationMs: options.durationMs ?? DEFAULT_LEASE_MS,
18
+ ...(options.meta ? { meta: options.meta } : {}),
19
+ });
20
+ if (lease)
21
+ return lease;
22
+ if (Date.now() - started >= waitForActiveMs)
23
+ return null;
24
+ await leaseSleep(250);
25
+ }
26
+ }
27
+ /**
28
+ * Pattern A — Provider handoff. Claim a local job lease, run the submission
29
+ * work (upload/build/submit), then atomically hand off to the provider lease
30
+ * via `pendingTasks.write(expectedOwnerId)`. The handler returns the freshly
31
+ * minted task_id; this helper transitions the wrapper's lease token from
32
+ * `{kind:'owner'}` to `{kind:'task'}` so a late crash CAS-fails by task_id.
33
+ *
34
+ * `fn` receives the local owner_id; it must pass that as the
35
+ * `expectedOwnerId` to `pendingTasks.write` and return the task_id the
36
+ * provider returned.
37
+ */
38
+ export async function withProviderHandoff(engine, assetId, ctx, kind, fn, options = {}) {
39
+ let ownerId = null;
40
+ let ownsLease = false;
41
+ try {
42
+ ownerId = ctx.getOwnerId();
43
+ }
44
+ catch {
45
+ ownerId = null;
46
+ }
47
+ if (!ownerId) {
48
+ const lease = await beginLocalLease(engine, assetId, kind, options, ctx.signal);
49
+ if (!lease) {
50
+ throw new Error(`could not claim local lease for ${assetId}: active owner already holds it`);
51
+ }
52
+ ownerId = lease.ownerId;
53
+ ownsLease = true;
54
+ ctx.setLease({ kind: "owner", ownerId });
55
+ }
56
+ const activeOwnerId = ownerId;
57
+ const heartbeat = ownsLease
58
+ ? setInterval(() => {
59
+ try {
60
+ engine.jobs.artifactWork.renew(assetId, activeOwnerId, HEARTBEAT_EXTEND_MS);
61
+ }
62
+ catch (error) {
63
+ logger.warn("heartbeat-failed", { assetId, error });
64
+ }
65
+ }, HEARTBEAT_INTERVAL_MS)
66
+ : null;
67
+ try {
68
+ const { taskId, result } = await fn(activeOwnerId);
69
+ ctx.setLease({ kind: "task", taskId });
70
+ return result;
71
+ }
72
+ finally {
73
+ if (heartbeat)
74
+ clearInterval(heartbeat);
75
+ }
76
+ }
77
+ /**
78
+ * Pattern B — Local-work handler. Claim a job lease, run the work end-to-end,
79
+ * call `completeAssetWork` on success or `failAssetWork` on throw. The
80
+ * handler doesn't need to touch pending_tasks at all.
81
+ */
82
+ export async function withLocalLease(engine, assetId, ctx, kind, fn, options = {}) {
83
+ const lease = await beginLocalLease(engine, assetId, kind, options, ctx.signal);
84
+ if (!lease) {
85
+ throw new Error(`could not claim local lease for ${assetId}: active owner already holds it`);
86
+ }
87
+ ctx.setLease({ kind: "owner", ownerId: lease.ownerId });
88
+ const heartbeat = setInterval(() => {
89
+ try {
90
+ engine.jobs.artifactWork.renew(assetId, lease.ownerId, HEARTBEAT_EXTEND_MS);
91
+ }
92
+ catch (error) {
93
+ logger.warn("heartbeat-failed", { assetId, error });
94
+ }
95
+ }, HEARTBEAT_INTERVAL_MS);
96
+ try {
97
+ const result = await fn(lease.ownerId);
98
+ await engine.jobs.artifactWork.complete(assetId, lease.ownerId);
99
+ return result;
100
+ }
101
+ catch (err) {
102
+ await engine.jobs.artifactWork.fail(assetId, lease.ownerId, {
103
+ message: err instanceof Error ? err.message : String(err),
104
+ });
105
+ throw err;
106
+ }
107
+ finally {
108
+ clearInterval(heartbeat);
109
+ }
110
+ }
111
+ export const CLAIM_RETRY_INTERVAL_MS = 1_000;
112
+ let leaseSleep = defaultSleep;
113
+ export function configureLeaseClockForTests(options) {
114
+ leaseSleep = options?.sleep ?? defaultSleep;
115
+ }
116
+ /**
117
+ * Claim the asset dir lock AND a local job lease together, polling both until
118
+ * `options.waitForActiveMs` elapses. Waiting (instead of failing fast)
119
+ * serializes workers that target the same asset — e.g. the three
120
+ * per-orientation render jobs the render tool enqueues at once — so
121
+ * latecomers run after the current holder finishes rather than erroring.
122
+ *
123
+ * On success the caller owns both and must itself complete/fail the lease
124
+ * and release the lock.
125
+ */
126
+ export async function claimLockedLease(engine, assetId, assetDir, kind, lock, options = {}) {
127
+ const started = Date.now();
128
+ const waitForActiveMs = options.waitForActiveMs ?? 0;
129
+ let reason = "could not claim asset";
130
+ while (true) {
131
+ const lockResult = await engine.jobs.locks.acquire(assetDir, lock);
132
+ if (lockResult.ok) {
133
+ const lease = await engine.jobs.artifactWork.begin(assetId, {
134
+ kind,
135
+ ownerKind: "job",
136
+ durationMs: options.durationMs ?? DEFAULT_LEASE_MS,
137
+ ...(options.meta ? { meta: options.meta } : {}),
138
+ });
139
+ if (lease)
140
+ return { ok: true, ownerId: lease.ownerId };
141
+ engine.jobs.locks.release(assetDir);
142
+ reason = "active owner already holds asset lease";
143
+ }
144
+ else {
145
+ reason = `Asset is locked: ${lockResult.error.message}`;
146
+ }
147
+ if (Date.now() - started >= waitForActiveMs)
148
+ return { ok: false, reason };
149
+ await leaseSleep(CLAIM_RETRY_INTERVAL_MS);
150
+ }
151
+ }
152
+ export async function withLockedLocalLease(engine, assetId, assetDir, ctx, kind, lock, fn, options = {}) {
153
+ const lockResult = await engine.jobs.locks.acquire(assetDir, lock);
154
+ if (!lockResult.ok) {
155
+ throw new Error(`Asset is locked: ${lockResult.error.message}`);
156
+ }
157
+ try {
158
+ return await withLocalLease(engine, assetId, ctx, kind, fn, options);
159
+ }
160
+ finally {
161
+ engine.jobs.locks.release(assetDir);
162
+ }
163
+ }
@@ -0,0 +1,19 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ export async function ingestWorkspaceOutputs(engine, artifact, paths, operation, details = {}) {
4
+ const result = await engine.files.ingestWorkspace(artifact, paths, operation, details);
5
+ if (!result.ok) {
6
+ throw new Error(`Failed to ingest ${artifact} workspace output: ${result.error.message}`);
7
+ }
8
+ return result.value;
9
+ }
10
+ export async function replaceArtifactFileFromPath(engine, artifact, targetFilename, sourcePath) {
11
+ const result = await engine.files.writeFromPath(artifact, targetFilename, sourcePath);
12
+ if (!result.ok) {
13
+ throw new Error(`Failed to persist ${artifact}/${targetFilename}: ${result.error.message}`);
14
+ }
15
+ if (resolve(sourcePath) !== resolve(result.value)) {
16
+ await rm(sourcePath, { force: true });
17
+ }
18
+ return result.value;
19
+ }
@@ -0,0 +1,21 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ const RETRY_DELAYS_MS = [50, 200, 800, 2_000];
3
+ export function isTransientStorageConflict(error) {
4
+ const message = error instanceof Error ? error.message : String(error);
5
+ return /commit conflict: another connection committed/i.test(message);
6
+ }
7
+ export async function withTransientStorageRetry(operation, signal) {
8
+ for (let attempt = 0;; attempt += 1) {
9
+ signal?.throwIfAborted();
10
+ try {
11
+ return await operation();
12
+ }
13
+ catch (error) {
14
+ const retryDelay = RETRY_DELAYS_MS[attempt];
15
+ if (retryDelay === undefined || !isTransientStorageConflict(error)) {
16
+ throw error;
17
+ }
18
+ await delay(retryDelay, undefined, signal ? { signal } : undefined);
19
+ }
20
+ }
21
+ }