@gentbajko/slopify 2.4.0 → 3.0.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 (469) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +11 -1
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +55 -5
  20. package/dist/adapters/host-cli/transport.js +13 -6
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +60 -18
  23. package/dist/adapters/image/codex.js +166 -25
  24. package/dist/adapters/image/fal.js +31 -3
  25. package/dist/adapters/image/google.js +17 -2
  26. package/dist/adapters/image/openai.js +45 -16
  27. package/dist/adapters/image/reference.js +41 -0
  28. package/dist/adapters/image/replicate.js +14 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex-models.js +2 -1
  35. package/dist/adapters/llm/codex-server-models.js +2 -1
  36. package/dist/adapters/llm/codex.js +99 -12
  37. package/dist/adapters/llm/document-workspace.js +2 -2
  38. package/dist/adapters/llm/gemini-workspace.js +1 -1
  39. package/dist/adapters/llm/gemini.js +16 -3
  40. package/dist/adapters/llm/image-workspace.js +58 -0
  41. package/dist/adapters/llm/openrouter.js +11 -3
  42. package/dist/adapters/llm/run-cli.js +26 -9
  43. package/dist/adapters/tts/cartesia.js +10 -0
  44. package/dist/adapters/tts/elevenlabs.js +31 -9
  45. package/dist/adapters/tts/inworld-async.js +9 -5
  46. package/dist/adapters/tts/inworld.js +17 -3
  47. package/dist/adapters/tts/openai.js +3 -0
  48. package/dist/adapters/tts/voice-languages.js +80 -0
  49. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/OFL.txt +93 -0
  55. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  56. package/dist/assets/models.yaml +7 -0
  57. package/dist/assets/sample/sample-audiobook.tar +0 -0
  58. package/dist/assets/sample/sample-podcast.tar +0 -0
  59. package/dist/assets/sample/sample-project.tar +0 -0
  60. package/dist/catalog/merge.js +132 -0
  61. package/dist/catalog/registry.js +33 -12
  62. package/dist/catalog/runtime-models.js +1 -1
  63. package/dist/catalog/schema.js +12 -1
  64. package/dist/catalog/store.js +147 -8
  65. package/dist/catalog/validate.js +17 -2
  66. package/dist/compose.yaml +71 -0
  67. package/dist/edge/autostart/docker-record.js +32 -0
  68. package/dist/edge/autostart/docker.js +50 -0
  69. package/dist/edge/autostart/index.js +78 -0
  70. package/dist/edge/autostart/launcher.js +176 -0
  71. package/dist/edge/autostart/model.js +3 -0
  72. package/dist/edge/autostart/native.js +210 -0
  73. package/dist/edge/autostart/prompt.js +66 -0
  74. package/dist/edge/autostart/service.js +81 -0
  75. package/dist/edge/cli.js +57 -51
  76. package/dist/edge/docker-install/activation.js +56 -0
  77. package/dist/edge/docker-install/apply.js +458 -0
  78. package/dist/edge/docker-install/engine.js +362 -0
  79. package/dist/edge/docker-install/run.js +154 -0
  80. package/dist/edge/docker-install/state.js +146 -0
  81. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  82. package/dist/edge/docker.js +14 -22
  83. package/dist/edge/events/hub.js +11 -0
  84. package/dist/edge/host-cli.js +1 -1
  85. package/dist/edge/http/actions.js +32 -10
  86. package/dist/edge/http/app.js +60 -2
  87. package/dist/edge/http/auditions.js +70 -0
  88. package/dist/edge/http/autostart.js +37 -0
  89. package/dist/edge/http/backups.js +40 -0
  90. package/dist/edge/http/channel-memory.js +92 -0
  91. package/dist/edge/http/channels.js +128 -0
  92. package/dist/edge/http/entries.js +22 -3
  93. package/dist/edge/http/files.js +1 -1
  94. package/dist/edge/http/folder-location.js +1 -1
  95. package/dist/edge/http/home.js +39 -0
  96. package/dist/edge/http/host-cli.js +45 -6
  97. package/dist/edge/http/narration-peaks.js +76 -0
  98. package/dist/edge/http/onboarding.js +152 -0
  99. package/dist/edge/http/open-folder.js +1 -1
  100. package/dist/edge/http/patch-notes.js +45 -0
  101. package/dist/edge/http/planning.js +4 -2
  102. package/dist/edge/http/problem.js +1 -0
  103. package/dist/edge/http/project-create.js +87 -0
  104. package/dist/edge/http/projects.js +31 -99
  105. package/dist/edge/http/prompts.js +40 -3
  106. package/dist/edge/http/pronunciations.js +23 -4
  107. package/dist/edge/http/providers.js +86 -0
  108. package/dist/edge/http/reviews.js +121 -0
  109. package/dist/edge/http/revision-files.js +1 -1
  110. package/dist/edge/http/revisions.js +1 -1
  111. package/dist/edge/http/run-cost.js +25 -0
  112. package/dist/edge/http/schedules.js +111 -6
  113. package/dist/edge/http/settings.js +128 -6
  114. package/dist/edge/http/storage-files.js +50 -0
  115. package/dist/edge/http/storage.js +29 -2
  116. package/dist/edge/http/studio.js +207 -0
  117. package/dist/edge/http/style-preview.js +100 -0
  118. package/dist/edge/http/subtitles.js +1 -1
  119. package/dist/edge/http/trash.js +69 -0
  120. package/dist/edge/http/update.js +24 -3
  121. package/dist/edge/http/whats-new.js +15 -0
  122. package/dist/edge/http/youtube-edits.js +83 -0
  123. package/dist/edge/native-update.js +62 -0
  124. package/dist/host-cli/open-folder.js +10 -10
  125. package/dist/host-cli/runtime.js +12 -3
  126. package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
  127. package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
  128. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  129. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  130. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  131. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  132. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  133. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  134. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  135. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  136. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  137. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  138. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  139. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  140. package/dist/kernel/paths.js +27 -6
  141. package/dist/kernel/pipeline.js +4 -1
  142. package/dist/kernel/ports/host-cli.js +139 -16
  143. package/dist/kernel/ports/image.js +3 -1
  144. package/dist/kernel/ports/key-probe.js +1 -0
  145. package/dist/kernel/ports/languages.js +157 -0
  146. package/dist/kernel/ports/llm.js +3 -1
  147. package/dist/kernel/ports/model.js +5 -2
  148. package/dist/kernel/ports/narration-aliases.js +60 -0
  149. package/dist/kernel/ports/plan-limits.js +36 -0
  150. package/dist/kernel/runner/attempt.js +18 -9
  151. package/dist/kernel/runner/audition.js +36 -0
  152. package/dist/kernel/runner/graph.js +24 -5
  153. package/dist/kernel/runner/index.js +35 -5
  154. package/dist/kernel/runner/meter.js +1 -0
  155. package/dist/kernel/runner/providers.js +164 -9
  156. package/dist/kernel/runner/retry-policy.js +43 -0
  157. package/dist/kernel/runner/standalone.js +134 -0
  158. package/dist/kernel/runner/work-authority.js +28 -2
  159. package/dist/main.js +321 -20
  160. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  161. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  162. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  163. package/dist/patch-notes/3.0.0.md +70 -0
  164. package/dist/patch-notes/index.json +26 -0
  165. package/dist/slices/admission/model.js +35 -0
  166. package/dist/slices/admission/repo.js +24 -4
  167. package/dist/slices/admission/rules.js +126 -1
  168. package/dist/slices/admission/schema.js +68 -1
  169. package/dist/slices/admission/short-mode.js +47 -0
  170. package/dist/slices/admission/start.js +90 -16
  171. package/dist/slices/article/continuation.js +2 -2
  172. package/dist/slices/article/provided-entries.js +2 -2
  173. package/dist/slices/article/run.js +6 -2
  174. package/dist/slices/article/segments.js +3 -3
  175. package/dist/slices/backups/files.js +135 -0
  176. package/dist/slices/backups/folder.js +43 -0
  177. package/dist/slices/backups/model.js +65 -0
  178. package/dist/slices/backups/repo.js +28 -0
  179. package/dist/slices/backups/schedule.js +60 -0
  180. package/dist/slices/backups/service.js +311 -0
  181. package/dist/slices/batch/index.js +9 -2
  182. package/dist/slices/cancel/index.js +1 -1
  183. package/dist/slices/channels/cast-images.js +110 -0
  184. package/dist/slices/channels/cast-match.js +32 -0
  185. package/dist/slices/channels/images.js +19 -0
  186. package/dist/slices/channels/model.js +5 -0
  187. package/dist/slices/channels/rebrand.js +100 -0
  188. package/dist/slices/channels/repo.js +218 -0
  189. package/dist/slices/channels/runs.js +129 -0
  190. package/dist/slices/channels/schema.js +129 -0
  191. package/dist/slices/channels/service.js +180 -0
  192. package/dist/slices/channels/studio-csv.js +110 -0
  193. package/dist/slices/channels/videos.js +120 -0
  194. package/dist/slices/checkpoints/recovery.js +10 -0
  195. package/dist/slices/control/index.js +2 -1
  196. package/dist/slices/document/fonts.js +1 -1
  197. package/dist/slices/document/render.js +1 -1
  198. package/dist/slices/episodes/related.js +90 -0
  199. package/dist/slices/episodes/repo.js +60 -0
  200. package/dist/slices/episodes/service.js +58 -0
  201. package/dist/slices/episodes/summarize.js +170 -0
  202. package/dist/slices/estimate/index.js +113 -24
  203. package/dist/slices/estimate/requests.js +60 -2
  204. package/dist/slices/fixes/rules.js +73 -0
  205. package/dist/slices/fonts/coverage.js +82 -0
  206. package/dist/slices/fonts/sfnt.js +74 -0
  207. package/dist/slices/images/run.js +8 -5
  208. package/dist/slices/images/scale.js +84 -0
  209. package/dist/slices/library/diff.js +122 -0
  210. package/dist/slices/library/history.js +100 -0
  211. package/dist/slices/library/model.js +3 -0
  212. package/dist/slices/library/photorealistic.js +49 -0
  213. package/dist/slices/library/repo.js +19 -14
  214. package/dist/slices/library/save.js +62 -12
  215. package/dist/slices/library/slots.js +15 -3
  216. package/dist/slices/library/snapshot.js +8 -2
  217. package/dist/slices/library/used-by.js +153 -0
  218. package/dist/slices/loudness/level-pieces.js +41 -0
  219. package/dist/slices/loudness/loudnorm.js +164 -0
  220. package/dist/slices/loudness/model.js +153 -0
  221. package/dist/slices/model-upkeep/model.js +10 -0
  222. package/dist/slices/model-upkeep/switch.js +217 -0
  223. package/dist/slices/model-upkeep/usage.js +183 -0
  224. package/dist/slices/narration/aliases-library.js +31 -0
  225. package/dist/slices/narration/aliases-schema.js +53 -0
  226. package/dist/slices/narration/aliases.js +49 -0
  227. package/dist/slices/narration/blocks.js +352 -0
  228. package/dist/slices/narration/chunk.js +19 -7
  229. package/dist/slices/narration/concat.js +2 -2
  230. package/dist/slices/narration/describe.js +45 -0
  231. package/dist/slices/narration/pauses-model.js +63 -0
  232. package/dist/slices/narration/pauses.js +269 -0
  233. package/dist/slices/narration/peaks-model.js +1 -0
  234. package/dist/slices/narration/peaks.js +94 -0
  235. package/dist/slices/narration/preparation.js +21 -3
  236. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  237. package/dist/slices/narration/pronunciation.js +72 -25
  238. package/dist/slices/narration/run.js +6 -6
  239. package/dist/slices/narration/shared-glossary.js +1 -1
  240. package/dist/slices/narration/steering.js +15 -4
  241. package/dist/slices/notifications/notifier.js +94 -0
  242. package/dist/slices/notifications/rules.js +102 -0
  243. package/dist/slices/notifications/send.js +40 -0
  244. package/dist/slices/notifications/settings.js +34 -0
  245. package/dist/slices/onboarding/copy.js +143 -0
  246. package/dist/slices/onboarding/first-run.js +46 -0
  247. package/dist/slices/onboarding/install.js +119 -0
  248. package/dist/slices/onboarding/model.js +64 -0
  249. package/dist/slices/onboarding/packs.js +491 -0
  250. package/dist/slices/onboarding/quick-short.js +129 -0
  251. package/dist/slices/onboarding/sample.js +132 -0
  252. package/dist/slices/onboarding/state.js +68 -0
  253. package/dist/slices/onboarding/template.js +73 -0
  254. package/dist/slices/patch-notes/library.js +73 -0
  255. package/dist/slices/patch-notes/seen.js +77 -0
  256. package/dist/slices/play-drafts/convert.js +160 -9
  257. package/dist/slices/play-drafts/readiness.js +3 -0
  258. package/dist/slices/play-drafts/repo.js +9 -1
  259. package/dist/slices/play-drafts/review-inputs.js +22 -3
  260. package/dist/slices/play-drafts/schema.js +109 -2
  261. package/dist/slices/play-drafts/service.js +10 -1
  262. package/dist/slices/play-drafts/start.js +43 -18
  263. package/dist/slices/play-drafts/uploads.js +2 -1
  264. package/dist/slices/project-templates/from-project.js +70 -4
  265. package/dist/slices/project-templates/one-off.js +26 -0
  266. package/dist/slices/project-templates/repo.js +2 -1
  267. package/dist/slices/project-templates/service.js +15 -4
  268. package/dist/slices/project-templates/setup.js +29 -2
  269. package/dist/slices/rebuild/admission-repo.js +1 -1
  270. package/dist/slices/rebuild/model.js +6 -0
  271. package/dist/slices/rebuild/preview-details.js +67 -2
  272. package/dist/slices/rebuild/preview-plan.js +13 -6
  273. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  274. package/dist/slices/rebuild/recipe-audio.js +66 -12
  275. package/dist/slices/rebuild/recipe-build.js +15 -5
  276. package/dist/slices/rebuild/recipe-cast.js +18 -0
  277. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  278. package/dist/slices/rebuild/recipe-describe.js +93 -0
  279. package/dist/slices/rebuild/recipe-edit.js +13 -1
  280. package/dist/slices/rebuild/recipe-exports.js +62 -8
  281. package/dist/slices/rebuild/recipe-input-schema.js +43 -1
  282. package/dist/slices/rebuild/recipe-legacy.js +12 -0
  283. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  284. package/dist/slices/rebuild/recipe-model.js +25 -6
  285. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  286. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  287. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  288. package/dist/slices/rebuild/recipe-reference.js +58 -0
  289. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  290. package/dist/slices/rebuild/recipe-save.js +4 -1
  291. package/dist/slices/rebuild/recipe-shorts.js +32 -5
  292. package/dist/slices/rebuild/recipe-text.js +114 -19
  293. package/dist/slices/rebuild/recipe-validation.js +29 -3
  294. package/dist/slices/rebuild/recipe-visual.js +104 -29
  295. package/dist/slices/rebuild/recipe-voices.js +286 -0
  296. package/dist/slices/rebuild/recipe-work.js +77 -6
  297. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  298. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  299. package/dist/slices/rebuild/recovery-model.js +3 -0
  300. package/dist/slices/rebuild/recovery-selection.js +28 -2
  301. package/dist/slices/rebuild/recovery.js +35 -17
  302. package/dist/slices/rebuild/repo.js +3 -3
  303. package/dist/slices/rebuild/retry.js +18 -0
  304. package/dist/slices/rebuild/review-redo.js +110 -0
  305. package/dist/slices/rebuild/runtime-actions.js +14 -2
  306. package/dist/slices/rebuild/runtime-admission.js +4 -1
  307. package/dist/slices/rebuild/runtime-animate.js +4 -4
  308. package/dist/slices/rebuild/runtime-article.js +5 -5
  309. package/dist/slices/rebuild/runtime-document.js +4 -4
  310. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  311. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  312. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  313. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  314. package/dist/slices/rebuild/runtime-export.js +84 -19
  315. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  316. package/dist/slices/rebuild/runtime-image.js +69 -0
  317. package/dist/slices/rebuild/runtime-local.js +154 -27
  318. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  319. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  320. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  321. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  322. package/dist/slices/rebuild/runtime-piece-label.js +29 -0
  323. package/dist/slices/rebuild/runtime-provider.js +134 -19
  324. package/dist/slices/rebuild/runtime-publication.js +21 -5
  325. package/dist/slices/rebuild/runtime-review.js +237 -0
  326. package/dist/slices/rebuild/runtime-run.js +17 -11
  327. package/dist/slices/rebuild/runtime-shorts.js +48 -23
  328. package/dist/slices/rebuild/runtime-store.js +39 -10
  329. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  330. package/dist/slices/rebuild/runtime-voices.js +103 -0
  331. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  332. package/dist/slices/rebuild/service-readiness.js +15 -1
  333. package/dist/slices/rebuild/soften.js +49 -0
  334. package/dist/slices/rebuild/transition-repo.js +10 -3
  335. package/dist/slices/research/run.js +4 -4
  336. package/dist/slices/research/synthesis.js +2 -2
  337. package/dist/slices/reviews/model.js +105 -0
  338. package/dist/slices/reviews/outcome.js +22 -0
  339. package/dist/slices/reviews/repo.js +106 -0
  340. package/dist/slices/reviews/rules.js +63 -0
  341. package/dist/slices/reviews/verdict.js +71 -0
  342. package/dist/slices/reviews/view.js +30 -0
  343. package/dist/slices/revisions/adopt-content.js +12 -2
  344. package/dist/slices/revisions/adopt.js +9 -2
  345. package/dist/slices/revisions/downloads.js +7 -1
  346. package/dist/slices/revisions/image-plan.js +183 -0
  347. package/dist/slices/revisions/mutation-assets.js +43 -9
  348. package/dist/slices/revisions/mutation-cues.js +71 -0
  349. package/dist/slices/revisions/mutation-prepare.js +8 -10
  350. package/dist/slices/revisions/mutation-request.js +2 -1
  351. package/dist/slices/revisions/mutations.js +97 -3
  352. package/dist/slices/revisions/schema.js +16 -2
  353. package/dist/slices/run-cost/limits.js +128 -0
  354. package/dist/slices/run-cost/meter.js +47 -0
  355. package/dist/slices/run-cost/panel.js +234 -0
  356. package/dist/slices/run-cost/pricing.js +92 -0
  357. package/dist/slices/run-cost/week.js +108 -0
  358. package/dist/slices/schedules/agenda.js +158 -0
  359. package/dist/slices/schedules/repo.js +58 -18
  360. package/dist/slices/schedules/scheduler.js +67 -16
  361. package/dist/slices/schedules/schema.js +138 -1
  362. package/dist/slices/schedules/service.js +48 -4
  363. package/dist/slices/schedules/similar.js +90 -0
  364. package/dist/slices/schedules/topic-list.js +163 -0
  365. package/dist/slices/schedules/topics.js +422 -0
  366. package/dist/slices/settings/first-run.js +102 -0
  367. package/dist/slices/settings/health.js +190 -0
  368. package/dist/slices/settings/key-guides.js +128 -0
  369. package/dist/slices/settings/key-test.js +85 -0
  370. package/dist/slices/settings/model.js +11 -1
  371. package/dist/slices/settings/playback.js +15 -0
  372. package/dist/slices/settings/repo.js +17 -3
  373. package/dist/slices/settings/voices.js +64 -4
  374. package/dist/slices/settings/whats-new.js +64 -0
  375. package/dist/slices/shorts/captions.js +9 -0
  376. package/dist/slices/shorts/pick.js +3 -3
  377. package/dist/slices/shorts/prompts.js +1 -1
  378. package/dist/slices/shorts/render.js +54 -5
  379. package/dist/slices/storage/asset-name.js +3 -0
  380. package/dist/slices/storage/backup-export.js +48 -9
  381. package/dist/slices/storage/backup-format.js +38 -1
  382. package/dist/slices/storage/backup-import.js +191 -19
  383. package/dist/slices/storage/delete-project.js +7 -4
  384. package/dist/slices/storage/documents.js +79 -0
  385. package/dist/slices/storage/files-location.js +531 -0
  386. package/dist/slices/storage/layout.js +20 -0
  387. package/dist/slices/storage/model.js +14 -0
  388. package/dist/slices/storage/portable.js +95 -12
  389. package/dist/slices/storage/reconcile.js +19 -2
  390. package/dist/slices/storage/repo.js +4 -1
  391. package/dist/slices/storage/schema.js +11 -0
  392. package/dist/slices/storage/trim.js +146 -0
  393. package/dist/slices/studio/disclosure.js +74 -0
  394. package/dist/slices/studio/model.js +28 -0
  395. package/dist/slices/studio/pack.js +209 -0
  396. package/dist/slices/studio/settings.js +133 -0
  397. package/dist/slices/style-preview/images.js +61 -0
  398. package/dist/slices/style-preview/render.js +161 -0
  399. package/dist/slices/style-preview/schema.js +45 -0
  400. package/dist/slices/style-preview/service.js +93 -0
  401. package/dist/slices/style-preview/settings.js +89 -0
  402. package/dist/slices/subtitles/captions.js +42 -9
  403. package/dist/slices/subtitles/model.js +22 -0
  404. package/dist/slices/subtitles/prepare.js +27 -16
  405. package/dist/slices/subtitles/transcript.js +1 -1
  406. package/dist/slices/thumbnail/run.js +7 -7
  407. package/dist/slices/trash/model.js +25 -0
  408. package/dist/slices/trash/service.js +273 -0
  409. package/dist/slices/uploads/repo.js +20 -0
  410. package/dist/slices/video/ambient-bed-schema.js +15 -0
  411. package/dist/slices/video/ambient-bed.js +95 -0
  412. package/dist/slices/video/ambient-mix.js +72 -0
  413. package/dist/slices/video/audio-inputs.js +2 -2
  414. package/dist/slices/video/cards.js +5 -2
  415. package/dist/slices/video/edit-list.js +23 -2
  416. package/dist/slices/video/edit-settings.js +13 -3
  417. package/dist/slices/video/ffmpeg.js +63 -9
  418. package/dist/slices/video/figure-card.js +492 -0
  419. package/dist/slices/video/figure-spans.js +51 -0
  420. package/dist/slices/video/plan.js +125 -13
  421. package/dist/slices/video/run.js +14 -12
  422. package/dist/slices/video/slideshow.js +39 -4
  423. package/dist/slices/voices/attribution.js +69 -0
  424. package/dist/slices/voices/audio-files.js +82 -0
  425. package/dist/slices/voices/audition.js +25 -0
  426. package/dist/slices/voices/cast.js +56 -0
  427. package/dist/slices/voices/grouping.js +49 -0
  428. package/dist/slices/voices/join.js +35 -0
  429. package/dist/slices/voices/languages.js +25 -0
  430. package/dist/slices/voices/model.js +179 -0
  431. package/dist/slices/voices/palette.js +26 -0
  432. package/dist/slices/voices/panel.js +91 -0
  433. package/dist/slices/voices/portraits.js +55 -0
  434. package/dist/slices/voices/script.js +136 -0
  435. package/dist/slices/voices/timing.js +58 -0
  436. package/dist/slices/youtube/answer.js +1 -1
  437. package/dist/slices/youtube/chapters.js +126 -0
  438. package/dist/slices/youtube/edits-repo.js +69 -0
  439. package/dist/slices/youtube/edits.js +89 -0
  440. package/dist/slices/youtube/placeholders.js +98 -0
  441. package/dist/slices/youtube/transcript.js +11 -4
  442. package/dist/updater/service.js +66 -21
  443. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  444. package/dist/web/assets/index-CX590kE-.js +303 -0
  445. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  446. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  447. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  448. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  449. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  450. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  451. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  452. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  453. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  454. package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
  455. package/dist/web/index.html +2 -2
  456. package/package.json +4 -4
  457. package/dist/edge/docker-launch.js +0 -81
  458. package/dist/edge/docker-projects/activation.js +0 -35
  459. package/dist/edge/docker-projects/claims.js +0 -85
  460. package/dist/edge/docker-projects/committed.js +0 -40
  461. package/dist/edge/docker-projects/engine.js +0 -461
  462. package/dist/edge/docker-projects/install.js +0 -254
  463. package/dist/edge/docker-projects/prune.js +0 -66
  464. package/dist/edge/docker-projects/recover.js +0 -100
  465. package/dist/edge/docker-projects/state.js +0 -225
  466. package/dist/web/assets/index-BGgYMm2u.css +0 -1
  467. package/dist/web/assets/index-BVVgtntL.js +0 -171
  468. package/scripts/docker-run.sh +0 -3
  469. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -8,13 +8,67 @@ const maxOutputBytes = 32 * 1024 * 1024;
8
8
  function unavailable(detail) {
9
9
  return providerError({
10
10
  kind: "unavailable",
11
- message: `The Codex CLI finished, but Slopify could not collect the image: ${detail}. Use Retry stage to make it again (this uses your Codex quota again).`,
11
+ message: `The Codex CLI finished, but Slopify could not collect the image: ${detail}. Use Try again to make it again (this uses your Codex quota again).`,
12
12
  });
13
13
  }
14
+ // A thread that drew more than this is not an image job any more.
15
+ const maxImagesPerThread = 50;
16
+ const imageName = /^[a-zA-Z0-9_-]+\.png$/;
17
+ function imagesRoot(env) {
18
+ return join(resolve(env.CODEX_HOME || join(env.HOME || homedir(), ".codex")), "generated_images");
19
+ }
20
+ function latestImage(directory) {
21
+ const dir = opendirSync(directory);
22
+ let latest;
23
+ let count = 0;
24
+ try {
25
+ for (let entry = dir.readSync(); entry !== null; entry = dir.readSync()) {
26
+ if (++count > maxImagesPerThread)
27
+ throw unavailable("far too many files were saved");
28
+ if (!entry.isFile() || !imageName.test(entry.name))
29
+ throw unavailable("the saved file is not a plain image file");
30
+ const at = lstatSync(join(directory, entry.name)).mtimeMs;
31
+ if (latest === undefined || at > latest.at || (at === latest.at && entry.name > latest.name))
32
+ latest = { name: entry.name, at };
33
+ }
34
+ }
35
+ finally {
36
+ dir.closeSync();
37
+ }
38
+ if (latest === undefined)
39
+ throw unavailable("no image was saved");
40
+ return latest.name;
41
+ }
42
+ // How many images this job's thread has drawn so far, for its progress line. Never throws:
43
+ // a folder not made yet, or one being written, counts what it can.
44
+ export function codexImageCount(env, threadId) {
45
+ if (!z.uuid().safeParse(threadId).success)
46
+ return 0;
47
+ try {
48
+ const directory = join(imagesRoot(env), threadId);
49
+ const entry = lstatSync(directory);
50
+ if (!entry.isDirectory() || entry.isSymbolicLink())
51
+ return 0;
52
+ const dir = opendirSync(directory);
53
+ let count = 0;
54
+ try {
55
+ for (let one = dir.readSync(); one !== null; one = dir.readSync())
56
+ if (imageName.test(one.name))
57
+ count++;
58
+ }
59
+ finally {
60
+ dir.closeSync();
61
+ }
62
+ return count;
63
+ }
64
+ catch {
65
+ return 0;
66
+ }
67
+ }
14
68
  export function codexGeneratedImage(env, threadId, startedAt) {
15
69
  if (!z.uuid().safeParse(threadId).success || threadId === undefined)
16
70
  throw unavailable("the image job could not be identified");
17
- const root = join(resolve(env.CODEX_HOME || join(env.HOME || homedir(), ".codex")), "generated_images");
71
+ const root = imagesRoot(env);
18
72
  const directory = join(root, threadId);
19
73
  try {
20
74
  for (const path of [root, directory]) {
@@ -26,22 +80,10 @@ export function codexGeneratedImage(env, threadId, startedAt) {
26
80
  if (canonical !== join(realpathSync(root), threadId))
27
81
  throw unavailable("Codex's image folder moved while it was being read");
28
82
  // Codex 0.155.1 omits image items from exec JSONL. Its artifact contract is
29
- // generated_images/<thread.started ID>/<sanitized tool call ID>.png.
30
- const dir = opendirSync(directory);
31
- let name;
32
- try {
33
- const entry = dir.readSync();
34
- if (entry === null)
35
- throw unavailable("no image was saved");
36
- if (dir.readSync() !== null)
37
- throw unavailable("more than one file was saved");
38
- if (!entry.isFile() || !/^[a-zA-Z0-9_-]+\.png$/.test(entry.name))
39
- throw unavailable("the saved file is not a plain image file");
40
- name = entry.name;
41
- }
42
- finally {
43
- dir.closeSync();
44
- }
83
+ // generated_images/<thread.started ID>/<sanitized tool call ID>.png. An agent that reviews
84
+ // its work may draw several in its one thread; the last one it drew is its answer, so the
85
+ // newest file wins (the name breaks a tie, the call IDs being in no useful order).
86
+ const name = latestImage(directory);
45
87
  const path = join(directory, name);
46
88
  const before = lstatSync(path);
47
89
  if (!before.isFile() || before.isSymbolicLink() || realpathSync(path) !== join(canonical, name))
@@ -1,20 +1,36 @@
1
- import { mkdtempSync, rmSync } from "node:fs";
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { z } from "zod";
5
5
  import { redact } from "../../kernel/log.js";
6
+ import { agentImageTimeoutMs, } from "../../kernel/ports/image.js";
6
7
  import { providerError } from "../../kernel/ports/model.js";
7
8
  import { cliReported, quoted, refusedImage } from "../explain.js";
8
9
  import { cliLoginError } from "../llm/cli-login-error.js";
9
- import { cliEvent, cliShaped, endedWithout, stopCliRun } from "../llm/run-cli.js";
10
+ import { codexLimitError, readingOf } from "../llm/codex.js";
11
+ import { codexPlanLimit } from "../llm/codex-limits.js";
12
+ import { cliEvent, cliShaped, endedKind, endedWithout, stopCliRun, } from "../llm/run-cli.js";
10
13
  import { lines } from "../llm/sse-lines.js";
11
- import { codexGeneratedImage } from "./codex-output.js";
14
+ import { codexGeneratedImage, codexImageCount } from "./codex-output.js";
15
+ import { referencePictures } from "./reference.js";
16
+ // "Codex default" puts no model and no effort on the command line, so the CLI's own defaults
17
+ // draw the image; it is what every project saved before the choice existed runs. Every other
18
+ // model id is one of the Codex CLI's own models - the list the Codex text provider shows -
19
+ // run with the chosen reasoning effort.
12
20
  export const codexImageModel = {
13
21
  id: "codex-imagegen",
14
- name: "Codex built-in image generation",
22
+ name: "Codex default",
15
23
  };
16
- const maxEventBytes = 1024 * 1024;
17
- const disabledFeatures = [
24
+ // An agent that reviews and redraws its image works for several minutes at a high effort, so
25
+ // the usual 300 s image limit would cut the best runs off.
26
+ export const codexImageTimeoutMs = agentImageTimeoutMs;
27
+ const maxEventBytes = 4 * 1024 * 1024;
28
+ // Every tool beyond the image tool stays off. `view_image`, which only reads an image file
29
+ // into the conversation, is let back in when the agent is asked to review its work (a chosen
30
+ // model or effort) or has a reference to look at. The bundled imagegen skill stays off with
31
+ // the shell: its workflow runs Python scripts against the Images API with an API key, which
32
+ // this job neither needs nor has, and the built-in tool it wraps is already here.
33
+ const alwaysDisabled = [
18
34
  "shell_tool",
19
35
  "unified_exec",
20
36
  "hooks",
@@ -25,18 +41,64 @@ const disabledFeatures = [
25
41
  "multi_agent",
26
42
  "computer_use",
27
43
  "browser_use",
28
- "view_image",
29
44
  "workspace_dependencies",
30
45
  ];
31
- export function codexImageArgs(req, directory) {
32
- const prompt = [
33
- "You are making exactly one image for Slopify. Use the image generation tool.",
34
- "Let the image generation tool save its output in its default location. Slopify will collect it.",
35
- "Generate only one image, using PNG or JPEG. Do not copy, rename, edit or create any other file.",
46
+ function reviews(req) {
47
+ return (req.model !== codexImageModel.id ||
48
+ req.thinking !== undefined ||
49
+ referencePictures(req).length > 0);
50
+ }
51
+ // The binary's image tool (`ImagegenArgs`: prompt, referenced_image_paths,
52
+ // num_last_images_to_include) takes each referenced image as an absolute path it reads
53
+ // itself, so the copy sits in the job's own folder - the sandbox's writable, readable root.
54
+ export function codexReferencePath(directory, image) {
55
+ return join(directory, image.mime === "image/jpeg" ? "reference.jpg" : "reference.png");
56
+ }
57
+ // Every picture the request carries, each at its own path: the establishing image keeps the
58
+ // name it always had, the cast pictures follow it as reference-2, reference-3 and so on.
59
+ export function codexReferencePaths(directory, req) {
60
+ return referencePictures(req).map((picture, index) => ({
61
+ picture,
62
+ path: index === 0
63
+ ? codexReferencePath(directory, picture.image)
64
+ : join(directory, `reference-${String(index + 1)}${picture.image.mime === "image/jpeg" ? ".jpg" : ".png"}`),
65
+ }));
66
+ }
67
+ function castInstructions(req, directory) {
68
+ const paths = codexReferencePaths(directory, req);
69
+ return [
70
+ `Reference images are saved at these paths. Pass all of them in referenced_image_paths on every image generation call: ${paths.map((row) => row.path).join(", ")}.`,
71
+ ...paths.map((row) => row.picture.member === undefined
72
+ ? `${row.path} is the establishing image: keep its characters, rendering style and colour palette.`
73
+ : `${row.path} shows ${row.picture.member}${describeMember(req, row.picture.member)}: draw ${row.picture.member} to look exactly like this.`),
74
+ "Use them as references only: do not copy their composition, pose or framing; compose this image from the brief.",
75
+ ].join(" ");
76
+ }
77
+ function describeMember(req, name) {
78
+ const description = req.cast?.find((member) => member.name === name)?.description.trim() ?? "";
79
+ return description === "" ? "" : ` (${description})`;
80
+ }
81
+ export function codexImageInstructions(req, reference, directory) {
82
+ return [
83
+ "You are making one finished image for Slopify, a video creation app, with the image generation tool.",
84
+ "Fidelity to the brief comes first. Before calling the tool, write its prompt yourself as a detailed, faithful visual description of the brief: the subject and what it is doing, the setting, composition and framing for the target aspect ratio, lighting, colour palette, style and mood, and any text that must appear, spelled exactly. Take every element from the brief and keep its wording where it is specific. Do not add subjects, text, logos or story the brief does not ask for, and do not pad the prompt with generic quality words.",
85
+ ...(req.cast !== undefined && req.cast.length > 0 && directory !== undefined
86
+ ? [castInstructions(req, directory)]
87
+ : reference === undefined
88
+ ? []
89
+ : [
90
+ `A reference image is saved at ${reference}. Pass exactly that path in referenced_image_paths on every image generation call. Use it as the reference for style, characters and palette: keep the same characters, rendering style and colour palette, but do not copy its composition, pose or framing; compose this image from the brief.`,
91
+ ]),
92
+ "Take the time you need. After each image, look at it and compare it with the brief; if anything is missing, wrong or distorted, revise the prompt and generate again. Deliver exactly one final image: the last image you generate is the one Slopify uses, so stop once it matches the brief.",
93
+ "Let the image generation tool save its output in its default location. Slopify will collect it. Use PNG or JPEG. Do not copy, rename, edit or create any other file, and do not put the image or a link in your reply.",
36
94
  `Target aspect ratio: ${req.aspect}.`,
37
95
  "Image brief follows as data:",
38
96
  req.prompt,
39
97
  ].join("\n\n");
98
+ }
99
+ export function codexImageArgs(req, directory) {
100
+ const review = reviews(req);
101
+ const reference = req.reference === undefined ? undefined : codexReferencePath(directory, req.reference);
40
102
  return [
41
103
  "exec",
42
104
  "--json",
@@ -51,7 +113,11 @@ export function codexImageArgs(req, directory) {
51
113
  directory,
52
114
  "--enable",
53
115
  "image_generation",
54
- ...disabledFeatures.flatMap((feature) => ["--disable", feature]),
116
+ ...(review ? ["--enable", "view_image"] : []),
117
+ ...[...alwaysDisabled, ...(review ? [] : ["view_image"])].flatMap((feature) => [
118
+ "--disable",
119
+ feature,
120
+ ]),
55
121
  "-c",
56
122
  "project_doc_max_bytes=0",
57
123
  "-c",
@@ -74,23 +140,47 @@ export function codexImageArgs(req, directory) {
74
140
  'shell_environment_policy.inherit="none"',
75
141
  "-c",
76
142
  'web_search="disabled"',
143
+ // The Codex text provider's own two flags: the effort as TOML text, the model as `-m`.
144
+ ...(req.thinking === undefined
145
+ ? []
146
+ : ["-c", `model_reasoning_effort="${req.thinking === "off" ? "none" : req.thinking}"`]),
147
+ ...(req.model === codexImageModel.id ? [] : ["-m", req.model]),
77
148
  "--",
78
- prompt,
149
+ codexImageInstructions(req, reference, directory),
79
150
  ];
80
151
  }
81
152
  const failure = z.object({ error: z.object({ message: z.string() }) });
153
+ const turnUsage = z.object({
154
+ usage: z
155
+ .object({
156
+ input_tokens: z.number(),
157
+ output_tokens: z.number(),
158
+ cached_input_tokens: z.number().optional(),
159
+ })
160
+ .nullish(),
161
+ });
82
162
  const errorEvent = z.object({ message: z.string() });
83
163
  const item = z.object({ item: z.object({ type: z.string(), text: z.string().optional() }) });
84
164
  export function codexImage(deps) {
85
165
  const binary = deps.binary ?? "codex";
86
166
  return {
87
167
  id: "codex-image",
88
- models: async () => [codexImageModel],
168
+ timeoutMs: codexImageTimeoutMs,
169
+ models: async () => {
170
+ let listed = [];
171
+ try {
172
+ listed = (await deps.readModels?.()) ?? [];
173
+ }
174
+ catch {
175
+ // The default still works without the list.
176
+ }
177
+ return [codexImageModel, ...listed.filter((model) => model.id !== codexImageModel.id)];
178
+ },
89
179
  generate: async (req) => {
90
- if (req.model !== codexImageModel.id)
180
+ if (req.model.trim() === "" || req.model.startsWith("-"))
91
181
  throw providerError({
92
182
  kind: "unsupported",
93
- message: "The Codex CLI makes images only with its own image model. Choose that model for images in the Providers section of Edit project, then use Retry stage.",
183
+ message: "No Codex model is chosen for images. Choose one (or Codex default) for images in the Providers section of Edit project, then use Try again.",
94
184
  });
95
185
  req.signal.throwIfAborted();
96
186
  const directory = mkdtempSync(join(tmpdir(), "slopify-codex-image-"));
@@ -98,7 +188,30 @@ export function codexImage(deps) {
98
188
  const startedAt = Date.now();
99
189
  let threadId;
100
190
  let run;
191
+ let reported = -1;
192
+ let usage;
193
+ const before = deps.readLimits?.().catch(() => null);
194
+ // A line for the stage's live panel each time this thread's image count moves.
195
+ const report = () => {
196
+ if (req.onProgress === undefined)
197
+ return;
198
+ const count = threadId === undefined ? 0 : codexImageCount(env, threadId);
199
+ if (count === reported)
200
+ return;
201
+ reported = count;
202
+ try {
203
+ req.onProgress(count === 0
204
+ ? "Codex is working on the image…"
205
+ : `Codex is refining the image… ${String(count)} ${count === 1 ? "image" : "images"} so far`);
206
+ }
207
+ catch {
208
+ // A progress line is optional; it never fails the image.
209
+ }
210
+ };
101
211
  try {
212
+ for (const { path, picture } of codexReferencePaths(directory, req))
213
+ writeFileSync(path, picture.image.bytes, { mode: 0o600 });
214
+ report();
102
215
  try {
103
216
  run = deps.run(binary, codexImageArgs(req, directory), req.signal, {
104
217
  cwd: directory,
@@ -108,7 +221,7 @@ export function codexImage(deps) {
108
221
  catch {
109
222
  throw providerError({
110
223
  kind: "unsupported",
111
- message: "The Codex CLI could not be started. Check it is installed and set up in Settings → Providers, then use Retry stage.",
224
+ message: "The Codex CLI could not be started. Check it is installed and set up in Settings → Providers, then use Try again.",
112
225
  });
113
226
  }
114
227
  let completed = false;
@@ -118,21 +231,37 @@ export function codexImage(deps) {
118
231
  continue;
119
232
  const event = cliEvent(binary, line);
120
233
  req.signal.throwIfAborted();
234
+ // Only the whole turn's end counts: an agent that reviews its work draws, looks and
235
+ // draws again, so the first image is rarely its answer.
236
+ report();
121
237
  if (event.type === "thread.started") {
122
238
  if (threadId !== undefined)
123
239
  throw providerError({
124
240
  kind: "unavailable",
125
- message: "The Codex CLI started more than one image job for one image, so Slopify cannot tell which result is right (both may have used your quota). Use Retry stage to make the image again.",
241
+ message: "The Codex CLI started more than one image job for one image, so Slopify cannot tell which result is right (both may have used your quota). Use Try again to make the image again.",
126
242
  });
127
243
  threadId = cliShaped(binary, z.object({ thread_id: z.string() }), event.value).thread_id;
128
244
  }
129
- else if (event.type === "turn.completed")
245
+ else if (event.type === "turn.completed") {
130
246
  completed = true;
247
+ const counted = turnUsage.safeParse(event.value);
248
+ if (counted.success && counted.data.usage)
249
+ usage = {
250
+ inputTokens: counted.data.usage.input_tokens,
251
+ outputTokens: counted.data.usage.output_tokens,
252
+ ...(counted.data.usage.cached_input_tokens
253
+ ? { cachedInputTokens: counted.data.usage.cached_input_tokens }
254
+ : {}),
255
+ };
256
+ }
131
257
  else if (event.type === "turn.failed") {
132
258
  const message = cliShaped(binary, failure, event.value).error.message;
133
259
  const login = cliLoginError("codex", message);
134
260
  if (login)
135
261
  throw login;
262
+ const planLimit = codexPlanLimit(message, new Date());
263
+ if (planLimit !== undefined)
264
+ throw codexLimitError(message, planLimit);
136
265
  throw providerError({
137
266
  kind: /refus|content.policy|safety/i.test(message) ? "refusal" : "other",
138
267
  message: /refus|content.policy|safety/i.test(message)
@@ -145,6 +274,9 @@ export function codexImage(deps) {
145
274
  const login = cliLoginError("codex", message);
146
275
  if (login)
147
276
  throw login;
277
+ const planLimit = codexPlanLimit(message, new Date());
278
+ if (planLimit !== undefined)
279
+ throw codexLimitError(message, planLimit);
148
280
  throw providerError({
149
281
  kind: /image.generation|image tool|feature.*unavailable/i.test(message)
150
282
  ? "unsupported"
@@ -167,12 +299,15 @@ export function codexImage(deps) {
167
299
  if (ended.error !== null)
168
300
  throw providerError({
169
301
  kind: "unsupported",
170
- message: "The Codex CLI could not be started. Check it is installed and set up in Settings → Providers, then use Retry stage.",
302
+ message: "The Codex CLI could not be started. Check it is installed and set up in Settings → Providers, then use Try again.",
171
303
  });
304
+ const stderrLimit = codexPlanLimit(run.stderr(), new Date());
305
+ if ((ended.code !== 0 || !completed) && stderrLimit !== undefined)
306
+ throw codexLimitError(run.stderr().trim(), stderrLimit);
172
307
  if (ended.code !== 0 || !completed)
173
308
  throw (cliLoginError("codex", run.stderr()) ??
174
309
  providerError({
175
- kind: unavailable ? "unsupported" : "other",
310
+ kind: unavailable ? "unsupported" : endedKind(ended),
176
311
  message: endedWithout(binary, ended, run.stderr()),
177
312
  }));
178
313
  if (unavailable)
@@ -180,7 +315,13 @@ export function codexImage(deps) {
180
315
  kind: "unsupported",
181
316
  message: `The Codex CLI cannot make images. ${cannotDraw}`,
182
317
  });
183
- return codexGeneratedImage(env, threadId, startedAt);
318
+ const image = codexGeneratedImage(env, threadId, startedAt);
319
+ const limits = await readingOf(before, deps.readLimits);
320
+ return {
321
+ ...image,
322
+ ...(usage === undefined ? {} : { usage }),
323
+ ...(limits === undefined ? {} : { limits }),
324
+ };
184
325
  }
185
326
  catch (error) {
186
327
  req.signal.throwIfAborted();
@@ -205,12 +346,12 @@ async function* bounded(source, maximum) {
205
346
  if (size > maximum)
206
347
  throw providerError({
207
348
  kind: "other",
208
- message: "The Codex CLI sent far more output than an image job should, so Slopify stopped it. Use Retry stage; if it keeps happening, update the Codex CLI to the latest version.",
349
+ message: "The Codex CLI sent far more output than an image job should, so Slopify stopped it. Use Try again; if it keeps happening, update the Codex CLI to the latest version.",
209
350
  });
210
351
  yield chunk;
211
352
  }
212
353
  }
213
- const imageNext = "Use Retry stage; if it keeps failing, run codex in a terminal to check it works and is signed in, or choose another image provider in the Providers section of Edit project.";
354
+ const imageNext = "Use Try again; if it keeps failing, run codex in a terminal to check it works and is signed in, or choose another image provider in the Providers section of Edit project.";
214
355
  const cannotDraw = "Update the Codex CLI and check your ChatGPT plan includes image generation, or choose another image provider in the Providers section of Edit project.";
215
356
  function imageEnvironment(directory, source) {
216
357
  const env = { PWD: directory };
@@ -4,6 +4,7 @@ import { providerError } from "../../kernel/ports/model.js";
4
4
  import { httpFailure, internalError, missingKey, noImage, providerSaid, unreadable, } from "../explain.js";
5
5
  import { retryAfter } from "../retry-after.js";
6
6
  import { downloadImage } from "./bytes.js";
7
+ import { referencePictures, withReferences } from "./reference.js";
7
8
  import { dataUri, downloadVideo } from "./video.js";
8
9
  // The HTTP gateway adapter for fal.ai: `fetch` and the downloader beside this file, no SDK.
9
10
  // `@fal-ai/client` would buy queue polling, which only the video clips need and which is three
@@ -49,6 +50,16 @@ function aspectOf(model, aspect) {
49
50
  const shape = field ?? "image_size";
50
51
  return { [shape]: sizes[shape][aspect] };
51
52
  }
53
+ // The text-to-image models whose fal endpoint has an `/edit` twin taking the same fields plus
54
+ // `image_urls`, which is how an establishing image is sent.
55
+ const editModels = [
56
+ "fal-ai/flux-2",
57
+ "fal-ai/nano-banana",
58
+ "fal-ai/nano-banana-2",
59
+ ];
60
+ function editEndpoint(model) {
61
+ return editModels.includes(model) ? `${model}/edit` : undefined;
62
+ }
52
63
  // Image-to-video runs on fal's queue, not the synchronous host: a clip takes one to five
53
64
  // minutes, and fal documents the queue as the way to call anything that slow. A request is
54
65
  // submitted, its status polled until it completes, and its answer fetched from the links the
@@ -89,12 +100,26 @@ export function falImage(deps) {
89
100
  id: "fal",
90
101
  models: () => Promise.resolve(falModels),
91
102
  generate: async (req) => {
92
- const response = await deps.fetch(`${falBase}/${req.model}`, {
103
+ // An establishing image goes to the model's edit endpoint, which takes input images as
104
+ // `image_urls`; only the models that have one are marked as taking a reference.
105
+ // Cast pictures ride the same list; a model without an edit endpoint gets the cast in
106
+ // words instead, since only the establishing image is something the user turned on.
107
+ const pictures = referencePictures(req);
108
+ const edit = pictures.length === 0 ? undefined : editEndpoint(req.model);
109
+ if (req.reference !== undefined && edit === undefined)
110
+ throw providerError({
111
+ kind: "unsupported",
112
+ message: "This fal.ai model can't use an establishing image as a reference. Choose FLUX.2 or Nano Banana 2 under Images → Model on Play or in Edit project → Providers, or set Establishing image to Off in the Images section.",
113
+ });
114
+ const response = await deps.fetch(`${falBase}/${edit ?? req.model}`, {
93
115
  method: "POST",
94
116
  signal: req.signal,
95
117
  headers: { Authorization: `Key ${keyOf(deps)}`, "Content-Type": "application/json" },
96
118
  body: JSON.stringify({
97
- prompt: req.prompt,
119
+ prompt: withReferences(req.prompt, req, edit === undefined ? 0 : pictures.length),
120
+ ...(edit === undefined
121
+ ? {}
122
+ : { image_urls: pictures.map((picture) => dataUri(picture.image)) }),
98
123
  ...aspectOf(req.model, req.aspect),
99
124
  // The stage sends Number as that many independent calls, one piece each.
100
125
  num_images: 1,
@@ -147,7 +172,7 @@ export function falImage(deps) {
147
172
  if (current.error)
148
173
  throw providerError({
149
174
  kind: "other",
150
- message: providerSaid("fal.ai", "could not make the video clip", redact(current.error), "Use Retry stage; if it keeps happening, choose another image-to-video model under Animate images in Edit project → Inputs → Look."),
175
+ message: providerSaid("fal.ai", "could not make the video clip", redact(current.error), "Use Try again; if it keeps happening, choose another image-to-video model under Animate images in Edit project → Inputs → Look."),
151
176
  });
152
177
  if (current.status === "COMPLETED")
153
178
  break;
@@ -203,6 +228,9 @@ function kindOf(status) {
203
228
  // ceiling: everything else is `other` and is retried, so a 422 naming an input this model
204
229
  // does not take fails the same way four times over. A terminal "this will never work" kind
205
230
  // has to reach the port's error contract first, which is not this adapter's to widen.
231
+ // The provider's own server failed; the same request may well succeed later.
232
+ if (status >= 500)
233
+ return "dropped";
206
234
  return "other";
207
235
  }
208
236
  async function failure(response, subject = "image request") {
@@ -5,6 +5,7 @@ import { httpFailure, missingKey, noImage, quoted, refusedImage, unreadable } fr
5
5
  import { retryAfter } from "../retry-after.js";
6
6
  import { describeBytes, sniffImage } from "./bytes.js";
7
7
  import { discoverGoogleImages } from "./models.js";
8
+ import { referencePictures, withReferences } from "./reference.js";
8
9
  // The HTTP gateway adapter for Google's own image generation, billed to a Gemini API key
9
10
  // rather than to a host reselling the same models. Like the OpenAI one it hands back the
10
11
  // bytes with the call, so there is no link to follow.
@@ -62,7 +63,18 @@ export function googleImage(deps) {
62
63
  headers: { "x-goog-api-key": keyOf(deps), "Content-Type": "application/json" },
63
64
  body: JSON.stringify({
64
65
  model: req.model,
65
- input: req.prompt,
66
+ // With an establishing image the input is two parts, the image first; the Gemini
67
+ // image models draw with an input image as their reference.
68
+ input: referencePictures(req).length === 0
69
+ ? req.prompt
70
+ : [
71
+ ...referencePictures(req).map(({ image }) => ({
72
+ type: "image",
73
+ mime_type: image.mime,
74
+ data: Buffer.from(image.bytes).toString("base64"),
75
+ })),
76
+ { type: "text", text: withReferences(req.prompt, req) },
77
+ ],
66
78
  // The stage sends Number as that many independent calls, one piece each, so one
67
79
  // image per request is what it asks for. Nothing about style is set: the stage
68
80
  // asks for the provider's own.
@@ -105,7 +117,7 @@ function decode(part) {
105
117
  const claimed = part.mime_type ?? "nothing";
106
118
  throw providerError({
107
119
  kind: "other",
108
- message: `Google sent back something that is not a PNG or JPEG image (labelled ${claimed}, but ${describeBytes(bytes)}). Use Retry stage; if it keeps happening, choose another image model in the Providers section of Edit project.`,
120
+ message: `Google sent back something that is not a PNG or JPEG image (labelled ${claimed}, but ${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image model in the Providers section of Edit project.`,
109
121
  });
110
122
  }
111
123
  return { bytes, mime };
@@ -143,6 +155,9 @@ function kindOf(status, named, message) {
143
155
  // ceiling: everything else is `other` and is retried, so a prompt past the model's limit
144
156
  // fails the same way four times over. A terminal "this will never work" kind has to reach
145
157
  // the port's error contract first, which is not this adapter's to widen.
158
+ // The provider's own server failed; the same request may well succeed later.
159
+ if (status >= 500)
160
+ return "dropped";
146
161
  return "other";
147
162
  }
148
163
  async function failure(response) {
@@ -5,6 +5,7 @@ import { httpFailure, missingKey, noImage, refusedImage, unreadable } from "../e
5
5
  import { retryAfter } from "../retry-after.js";
6
6
  import { describeBytes, sniffImage } from "./bytes.js";
7
7
  import { discoverOpenAiImages } from "./models.js";
8
+ import { referencePictures, withReferences } from "./reference.js";
8
9
  // The HTTP gateway adapter for OpenAI's images endpoint: the platform's own `fetch` and
9
10
  // nothing else, because the whole call is one request. Unlike fal and Replicate this one
10
11
  // hands back the image itself - a GPT image model always answers with base64, never a URL -
@@ -53,21 +54,34 @@ export function openAiImage(deps) {
53
54
  id: "openai-image",
54
55
  models: () => discoverOpenAiImages(deps),
55
56
  generate: async (req) => {
56
- const response = await deps.fetch(`${openAiImagesBase}/images/generations`, {
57
- method: "POST",
58
- signal: req.signal,
59
- headers: { Authorization: `Bearer ${keyOf(deps)}`, "Content-Type": "application/json" },
60
- body: JSON.stringify({
61
- model: req.model,
62
- prompt: req.prompt,
63
- // The stage sends Number as that many independent calls, one piece each, so one
64
- // image per request is what it asks for.
65
- n: 1,
66
- size: sizeFor(req.model, req.aspect),
67
- // No `quality` and no `background`: the stage asks for the provider's default
68
- // quality and style, which is what leaving them off means.
69
- }),
70
- });
57
+ const pictures = referencePictures(req);
58
+ const response = pictures.length === 0
59
+ ? await deps.fetch(`${openAiImagesBase}/images/generations`, {
60
+ method: "POST",
61
+ signal: req.signal,
62
+ headers: {
63
+ Authorization: `Bearer ${keyOf(deps)}`,
64
+ "Content-Type": "application/json",
65
+ },
66
+ body: JSON.stringify({
67
+ model: req.model,
68
+ prompt: req.prompt,
69
+ // The stage sends Number as that many independent calls, one piece each, so
70
+ // one image per request is what it asks for.
71
+ n: 1,
72
+ size: sizeFor(req.model, req.aspect),
73
+ // No `quality` and no `background`: the stage asks for the provider's default
74
+ // quality and style, which is what leaving them off means.
75
+ }),
76
+ })
77
+ : // With an establishing image the GPT image models take it as an input image on the
78
+ // edits endpoint, a multipart form; the note says it is a reference, not the canvas.
79
+ await deps.fetch(`${openAiImagesBase}/images/edits`, {
80
+ method: "POST",
81
+ signal: req.signal,
82
+ headers: { Authorization: `Bearer ${keyOf(deps)}` },
83
+ body: editForm(req, pictures),
84
+ });
71
85
  if (!response.ok) {
72
86
  throw await failure(response);
73
87
  }
@@ -79,6 +93,18 @@ export function openAiImage(deps) {
79
93
  },
80
94
  };
81
95
  }
96
+ // The edits endpoint takes several input images as repeated `image[]` parts: the establishing
97
+ // image first, then the cast pictures.
98
+ function editForm(req, pictures) {
99
+ const form = new FormData();
100
+ form.set("model", req.model);
101
+ form.set("prompt", withReferences(req.prompt, req));
102
+ form.set("n", "1");
103
+ form.set("size", sizeFor(req.model, req.aspect));
104
+ for (const [index, { image }] of pictures.entries())
105
+ form.append("image[]", new Blob([Uint8Array.from(image.bytes)], { type: image.mime }), `${index === 0 ? "reference" : `reference-${String(index + 1)}`}${image.mime === "image/jpeg" ? ".jpg" : ".png"}`);
106
+ return form;
107
+ }
82
108
  // A GPT image model always answers with base64, so the bytes arrive with the call and
83
109
  // there is no link to follow. They are still sniffed: what the port stores is a PNG or a
84
110
  // JPEG, and a truncated payload that decodes to something else must not reach the disk.
@@ -88,7 +114,7 @@ function decode(b64) {
88
114
  if (mime === undefined) {
89
115
  throw providerError({
90
116
  kind: "other",
91
- message: `OpenAI sent back something that is not a PNG or JPEG image (${describeBytes(bytes)}). Use Retry stage; if it keeps happening, choose another image model in the Providers section of Edit project.`,
117
+ message: `OpenAI sent back something that is not a PNG or JPEG image (${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image model in the Providers section of Edit project.`,
92
118
  });
93
119
  }
94
120
  return { bytes, mime };
@@ -115,6 +141,9 @@ function kindOf(status, code) {
115
141
  // ceiling: everything else is `other` and is retried, so a prompt past the model's length
116
142
  // limit fails the same way four times over. A terminal "this will never work" kind has to
117
143
  // reach the port's error contract first, which is not this adapter's to widen.
144
+ // The provider's own server failed; the same request may well succeed later.
145
+ if (status >= 500)
146
+ return "dropped";
118
147
  return "other";
119
148
  }
120
149
  async function failure(response) {