@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.
- package/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +21 -40
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +11 -1
- package/dist/adapters/alignment/cache.js +4 -1
- package/dist/adapters/alignment/ctc.js +25 -23
- package/dist/adapters/alignment/index.js +37 -6
- package/dist/adapters/alignment/multilingual.js +267 -0
- package/dist/adapters/alignment/protocol.js +11 -0
- package/dist/adapters/alignment/quality.js +4 -3
- package/dist/adapters/alignment/sentences.js +95 -0
- package/dist/adapters/alignment/spec.js +24 -0
- package/dist/adapters/alignment/spell.js +769 -0
- package/dist/adapters/alignment/text.js +56 -9
- package/dist/adapters/alignment/window.js +34 -30
- package/dist/adapters/alignment/worker.js +23 -21
- package/dist/adapters/explain.js +11 -11
- package/dist/adapters/host-cli/index.js +55 -5
- package/dist/adapters/host-cli/transport.js +13 -6
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +60 -18
- package/dist/adapters/image/codex.js +166 -25
- package/dist/adapters/image/fal.js +31 -3
- package/dist/adapters/image/google.js +17 -2
- package/dist/adapters/image/openai.js +45 -16
- package/dist/adapters/image/reference.js +41 -0
- package/dist/adapters/image/replicate.js +14 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +36 -0
- package/dist/adapters/llm/claude-code.js +168 -32
- package/dist/adapters/llm/cli-login-error.js +1 -1
- package/dist/adapters/llm/codex-limits.js +157 -0
- package/dist/adapters/llm/codex-models.js +2 -1
- package/dist/adapters/llm/codex-server-models.js +2 -1
- package/dist/adapters/llm/codex.js +99 -12
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +16 -3
- package/dist/adapters/llm/image-workspace.js +58 -0
- package/dist/adapters/llm/openrouter.js +11 -3
- package/dist/adapters/llm/run-cli.js +26 -9
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/inworld-async.js +9 -5
- package/dist/adapters/tts/inworld.js +17 -3
- package/dist/adapters/tts/openai.js +3 -0
- package/dist/adapters/tts/voice-languages.js +80 -0
- package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/OFL.txt +93 -0
- package/dist/assets/fonts/noto/SOURCE.txt +18 -0
- package/dist/assets/models.yaml +7 -0
- package/dist/assets/sample/sample-audiobook.tar +0 -0
- package/dist/assets/sample/sample-podcast.tar +0 -0
- package/dist/assets/sample/sample-project.tar +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +33 -12
- package/dist/catalog/runtime-models.js +1 -1
- package/dist/catalog/schema.js +12 -1
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +17 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +50 -0
- package/dist/edge/autostart/index.js +78 -0
- package/dist/edge/autostart/launcher.js +176 -0
- package/dist/edge/autostart/model.js +3 -0
- package/dist/edge/autostart/native.js +210 -0
- package/dist/edge/autostart/prompt.js +66 -0
- package/dist/edge/autostart/service.js +81 -0
- package/dist/edge/cli.js +57 -51
- package/dist/edge/docker-install/activation.js +56 -0
- package/dist/edge/docker-install/apply.js +458 -0
- package/dist/edge/docker-install/engine.js +362 -0
- package/dist/edge/docker-install/run.js +154 -0
- package/dist/edge/docker-install/state.js +146 -0
- package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
- package/dist/edge/docker.js +14 -22
- package/dist/edge/events/hub.js +11 -0
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +60 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/backups.js +40 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +1 -1
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +45 -6
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +152 -0
- package/dist/edge/http/open-folder.js +1 -1
- package/dist/edge/http/patch-notes.js +45 -0
- package/dist/edge/http/planning.js +4 -2
- package/dist/edge/http/problem.js +1 -0
- package/dist/edge/http/project-create.js +87 -0
- package/dist/edge/http/projects.js +31 -99
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +86 -0
- package/dist/edge/http/reviews.js +121 -0
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +111 -6
- package/dist/edge/http/settings.js +128 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +29 -2
- package/dist/edge/http/studio.js +207 -0
- package/dist/edge/http/style-preview.js +100 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/update.js +24 -3
- package/dist/edge/http/whats-new.js +15 -0
- package/dist/edge/http/youtube-edits.js +83 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +12 -3
- package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
- package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
- package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
- package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
- package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
- package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
- package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
- package/dist/kernel/db/migrations/0032-channels.sql +65 -0
- package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
- package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
- package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
- package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
- package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
- package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +139 -16
- package/dist/kernel/ports/image.js +3 -1
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/llm.js +3 -1
- package/dist/kernel/ports/model.js +5 -2
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/runner/attempt.js +18 -9
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +24 -5
- package/dist/kernel/runner/index.js +35 -5
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +164 -9
- package/dist/kernel/runner/retry-policy.js +43 -0
- package/dist/kernel/runner/standalone.js +134 -0
- package/dist/kernel/runner/work-authority.js +28 -2
- package/dist/main.js +321 -20
- package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
- package/dist/patch-notes/1.0-to-2.0.md +74 -0
- package/dist/patch-notes/2.0-to-3.0.md +257 -0
- package/dist/patch-notes/3.0.0.md +70 -0
- package/dist/patch-notes/index.json +26 -0
- package/dist/slices/admission/model.js +35 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +126 -1
- package/dist/slices/admission/schema.js +68 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +90 -16
- package/dist/slices/article/continuation.js +2 -2
- package/dist/slices/article/provided-entries.js +2 -2
- package/dist/slices/article/run.js +6 -2
- package/dist/slices/article/segments.js +3 -3
- package/dist/slices/backups/files.js +135 -0
- package/dist/slices/backups/folder.js +43 -0
- package/dist/slices/backups/model.js +65 -0
- package/dist/slices/backups/repo.js +28 -0
- package/dist/slices/backups/schedule.js +60 -0
- package/dist/slices/backups/service.js +311 -0
- package/dist/slices/batch/index.js +9 -2
- package/dist/slices/cancel/index.js +1 -1
- package/dist/slices/channels/cast-images.js +110 -0
- package/dist/slices/channels/cast-match.js +32 -0
- package/dist/slices/channels/images.js +19 -0
- package/dist/slices/channels/model.js +5 -0
- package/dist/slices/channels/rebrand.js +100 -0
- package/dist/slices/channels/repo.js +218 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +129 -0
- package/dist/slices/channels/service.js +180 -0
- package/dist/slices/channels/studio-csv.js +110 -0
- package/dist/slices/channels/videos.js +120 -0
- package/dist/slices/checkpoints/recovery.js +10 -0
- package/dist/slices/control/index.js +2 -1
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/render.js +1 -1
- package/dist/slices/episodes/related.js +90 -0
- package/dist/slices/episodes/repo.js +60 -0
- package/dist/slices/episodes/service.js +58 -0
- package/dist/slices/episodes/summarize.js +170 -0
- package/dist/slices/estimate/index.js +113 -24
- package/dist/slices/estimate/requests.js +60 -2
- package/dist/slices/fixes/rules.js +73 -0
- package/dist/slices/fonts/coverage.js +82 -0
- package/dist/slices/fonts/sfnt.js +74 -0
- package/dist/slices/images/run.js +8 -5
- package/dist/slices/images/scale.js +84 -0
- package/dist/slices/library/diff.js +122 -0
- package/dist/slices/library/history.js +100 -0
- package/dist/slices/library/model.js +3 -0
- package/dist/slices/library/photorealistic.js +49 -0
- package/dist/slices/library/repo.js +19 -14
- package/dist/slices/library/save.js +62 -12
- package/dist/slices/library/slots.js +15 -3
- package/dist/slices/library/snapshot.js +8 -2
- package/dist/slices/library/used-by.js +153 -0
- package/dist/slices/loudness/level-pieces.js +41 -0
- package/dist/slices/loudness/loudnorm.js +164 -0
- package/dist/slices/loudness/model.js +153 -0
- package/dist/slices/model-upkeep/model.js +10 -0
- package/dist/slices/model-upkeep/switch.js +217 -0
- package/dist/slices/model-upkeep/usage.js +183 -0
- package/dist/slices/narration/aliases-library.js +31 -0
- package/dist/slices/narration/aliases-schema.js +53 -0
- package/dist/slices/narration/aliases.js +49 -0
- package/dist/slices/narration/blocks.js +352 -0
- package/dist/slices/narration/chunk.js +19 -7
- package/dist/slices/narration/concat.js +2 -2
- package/dist/slices/narration/describe.js +45 -0
- package/dist/slices/narration/pauses-model.js +63 -0
- package/dist/slices/narration/pauses.js +269 -0
- package/dist/slices/narration/peaks-model.js +1 -0
- package/dist/slices/narration/peaks.js +94 -0
- package/dist/slices/narration/preparation.js +21 -3
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +72 -25
- package/dist/slices/narration/run.js +6 -6
- package/dist/slices/narration/shared-glossary.js +1 -1
- package/dist/slices/narration/steering.js +15 -4
- package/dist/slices/notifications/notifier.js +94 -0
- package/dist/slices/notifications/rules.js +102 -0
- package/dist/slices/notifications/send.js +40 -0
- package/dist/slices/notifications/settings.js +34 -0
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +46 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +64 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +129 -0
- package/dist/slices/onboarding/sample.js +132 -0
- package/dist/slices/onboarding/state.js +68 -0
- package/dist/slices/onboarding/template.js +73 -0
- package/dist/slices/patch-notes/library.js +73 -0
- package/dist/slices/patch-notes/seen.js +77 -0
- package/dist/slices/play-drafts/convert.js +160 -9
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +9 -1
- package/dist/slices/play-drafts/review-inputs.js +22 -3
- package/dist/slices/play-drafts/schema.js +109 -2
- package/dist/slices/play-drafts/service.js +10 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/play-drafts/uploads.js +2 -1
- package/dist/slices/project-templates/from-project.js +70 -4
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +29 -2
- package/dist/slices/rebuild/admission-repo.js +1 -1
- package/dist/slices/rebuild/model.js +6 -0
- package/dist/slices/rebuild/preview-details.js +67 -2
- package/dist/slices/rebuild/preview-plan.js +13 -6
- package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
- package/dist/slices/rebuild/recipe-audio.js +66 -12
- package/dist/slices/rebuild/recipe-build.js +15 -5
- package/dist/slices/rebuild/recipe-cast.js +18 -0
- package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
- package/dist/slices/rebuild/recipe-describe.js +93 -0
- package/dist/slices/rebuild/recipe-edit.js +13 -1
- package/dist/slices/rebuild/recipe-exports.js +62 -8
- package/dist/slices/rebuild/recipe-input-schema.js +43 -1
- package/dist/slices/rebuild/recipe-legacy.js +12 -0
- package/dist/slices/rebuild/recipe-loudness.js +45 -0
- package/dist/slices/rebuild/recipe-model.js +25 -6
- package/dist/slices/rebuild/recipe-pauses.js +7 -0
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
- package/dist/slices/rebuild/recipe-reference.js +58 -0
- package/dist/slices/rebuild/recipe-reviews.js +117 -0
- package/dist/slices/rebuild/recipe-save.js +4 -1
- package/dist/slices/rebuild/recipe-shorts.js +32 -5
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +29 -3
- package/dist/slices/rebuild/recipe-visual.js +104 -29
- package/dist/slices/rebuild/recipe-voices.js +286 -0
- package/dist/slices/rebuild/recipe-work.js +77 -6
- package/dist/slices/rebuild/recipe-youtube.js +2 -0
- package/dist/slices/rebuild/recovery-conflict.js +7 -3
- package/dist/slices/rebuild/recovery-model.js +3 -0
- package/dist/slices/rebuild/recovery-selection.js +28 -2
- package/dist/slices/rebuild/recovery.js +35 -17
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/retry.js +18 -0
- package/dist/slices/rebuild/review-redo.js +110 -0
- package/dist/slices/rebuild/runtime-actions.js +14 -2
- package/dist/slices/rebuild/runtime-admission.js +4 -1
- package/dist/slices/rebuild/runtime-animate.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-document.js +4 -4
- package/dist/slices/rebuild/runtime-export-bed.js +43 -0
- package/dist/slices/rebuild/runtime-export-edit.js +70 -11
- package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
- package/dist/slices/rebuild/runtime-export-short.js +85 -0
- package/dist/slices/rebuild/runtime-export.js +84 -19
- package/dist/slices/rebuild/runtime-figure-card.js +87 -0
- package/dist/slices/rebuild/runtime-image.js +69 -0
- package/dist/slices/rebuild/runtime-local.js +154 -27
- package/dist/slices/rebuild/runtime-materialize.js +8 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
- package/dist/slices/rebuild/runtime-narration-text.js +7 -7
- package/dist/slices/rebuild/runtime-pictures.js +28 -0
- package/dist/slices/rebuild/runtime-piece-label.js +29 -0
- package/dist/slices/rebuild/runtime-provider.js +134 -19
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +237 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +48 -23
- package/dist/slices/rebuild/runtime-store.js +39 -10
- package/dist/slices/rebuild/runtime-subtitles.js +72 -19
- package/dist/slices/rebuild/runtime-voices.js +103 -0
- package/dist/slices/rebuild/runtime-youtube.js +7 -6
- package/dist/slices/rebuild/service-readiness.js +15 -1
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +10 -3
- package/dist/slices/research/run.js +4 -4
- package/dist/slices/research/synthesis.js +2 -2
- package/dist/slices/reviews/model.js +105 -0
- package/dist/slices/reviews/outcome.js +22 -0
- package/dist/slices/reviews/repo.js +106 -0
- package/dist/slices/reviews/rules.js +63 -0
- package/dist/slices/reviews/verdict.js +71 -0
- package/dist/slices/reviews/view.js +30 -0
- package/dist/slices/revisions/adopt-content.js +12 -2
- package/dist/slices/revisions/adopt.js +9 -2
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +43 -9
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-prepare.js +8 -10
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +16 -2
- package/dist/slices/run-cost/limits.js +128 -0
- package/dist/slices/run-cost/meter.js +47 -0
- package/dist/slices/run-cost/panel.js +234 -0
- package/dist/slices/run-cost/pricing.js +92 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +158 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +67 -16
- package/dist/slices/schedules/schema.js +138 -1
- package/dist/slices/schedules/service.js +48 -4
- package/dist/slices/schedules/similar.js +90 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +422 -0
- package/dist/slices/settings/first-run.js +102 -0
- package/dist/slices/settings/health.js +190 -0
- package/dist/slices/settings/key-guides.js +128 -0
- package/dist/slices/settings/key-test.js +85 -0
- package/dist/slices/settings/model.js +11 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/voices.js +64 -4
- package/dist/slices/settings/whats-new.js +64 -0
- package/dist/slices/shorts/captions.js +9 -0
- package/dist/slices/shorts/pick.js +3 -3
- package/dist/slices/shorts/prompts.js +1 -1
- package/dist/slices/shorts/render.js +54 -5
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/backup-export.js +48 -9
- package/dist/slices/storage/backup-format.js +38 -1
- package/dist/slices/storage/backup-import.js +191 -19
- package/dist/slices/storage/delete-project.js +7 -4
- package/dist/slices/storage/documents.js +79 -0
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +20 -0
- package/dist/slices/storage/model.js +14 -0
- package/dist/slices/storage/portable.js +95 -12
- package/dist/slices/storage/reconcile.js +19 -2
- package/dist/slices/storage/repo.js +4 -1
- package/dist/slices/storage/schema.js +11 -0
- package/dist/slices/storage/trim.js +146 -0
- package/dist/slices/studio/disclosure.js +74 -0
- package/dist/slices/studio/model.js +28 -0
- package/dist/slices/studio/pack.js +209 -0
- package/dist/slices/studio/settings.js +133 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/render.js +161 -0
- package/dist/slices/style-preview/schema.js +45 -0
- package/dist/slices/style-preview/service.js +93 -0
- package/dist/slices/style-preview/settings.js +89 -0
- package/dist/slices/subtitles/captions.js +42 -9
- package/dist/slices/subtitles/model.js +22 -0
- package/dist/slices/subtitles/prepare.js +27 -16
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +7 -7
- package/dist/slices/trash/model.js +25 -0
- package/dist/slices/trash/service.js +273 -0
- package/dist/slices/uploads/repo.js +20 -0
- package/dist/slices/video/ambient-bed-schema.js +15 -0
- package/dist/slices/video/ambient-bed.js +95 -0
- package/dist/slices/video/ambient-mix.js +72 -0
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/cards.js +5 -2
- package/dist/slices/video/edit-list.js +23 -2
- package/dist/slices/video/edit-settings.js +13 -3
- package/dist/slices/video/ffmpeg.js +63 -9
- package/dist/slices/video/figure-card.js +492 -0
- package/dist/slices/video/figure-spans.js +51 -0
- package/dist/slices/video/plan.js +125 -13
- package/dist/slices/video/run.js +14 -12
- package/dist/slices/video/slideshow.js +39 -4
- package/dist/slices/voices/attribution.js +69 -0
- package/dist/slices/voices/audio-files.js +82 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +56 -0
- package/dist/slices/voices/grouping.js +49 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +179 -0
- package/dist/slices/voices/palette.js +26 -0
- package/dist/slices/voices/panel.js +91 -0
- package/dist/slices/voices/portraits.js +55 -0
- package/dist/slices/voices/script.js +136 -0
- package/dist/slices/voices/timing.js +58 -0
- package/dist/slices/youtube/answer.js +1 -1
- package/dist/slices/youtube/chapters.js +126 -0
- package/dist/slices/youtube/edits-repo.js +69 -0
- package/dist/slices/youtube/edits.js +89 -0
- package/dist/slices/youtube/placeholders.js +98 -0
- package/dist/slices/youtube/transcript.js +11 -4
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-CUA2sUxU.css +1 -0
- package/dist/web/assets/index-CX590kE-.js +303 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -4
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-BGgYMm2u.css +0 -1
- package/dist/web/assets/index-BVVgtntL.js +0 -171
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
// Images use saved prompts and can run immediately. A thumbnail's source and an
|
|
2
2
|
// audio-only export narrow these dependencies through dependenciesOf(). The document is
|
|
3
3
|
// laid out from the article alone, with the thumbnail as its cover when there is one, so it
|
|
4
|
-
// runs beside narration and images rather than after them.
|
|
4
|
+
// runs beside narration and images rather than after them. The video never reads the
|
|
5
|
+
// thumbnail, so a thumbnail that fails does not hold the video back.
|
|
5
6
|
export const deps = {
|
|
6
7
|
research: [],
|
|
7
8
|
article: ["research"],
|
|
8
9
|
audio: ["article"],
|
|
9
10
|
images: [],
|
|
10
11
|
thumbnail: ["article"],
|
|
11
|
-
video: ["article", "audio", "images"
|
|
12
|
+
video: ["article", "audio", "images"],
|
|
12
13
|
document: ["article", "thumbnail"],
|
|
13
14
|
};
|
|
14
15
|
// `provided` and `skipped` release a dependency exactly as `done` does.
|
|
15
16
|
export function satisfied(state) {
|
|
16
17
|
return state === "done" || state === "provided" || state === "skipped";
|
|
17
18
|
}
|
|
19
|
+
// What a run is for, in order: the first of these the run asks for is the one that has to be
|
|
20
|
+
// there for the run to count as finished. Anything else failing leaves it done with
|
|
21
|
+
// problems rather than failed.
|
|
22
|
+
const headline = ["video", "audio", "article"];
|
|
18
23
|
// Stage state is derived; an explicit persisted pause takes precedence while calls
|
|
19
|
-
// drain. Completed independent images do not imply that a run was canceled.
|
|
24
|
+
// drain. Completed independent images do not imply that a run was canceled. A stage
|
|
25
|
+
// waiting out a rate limit is still part of a run in progress.
|
|
20
26
|
export function derive(stages, paused = false) {
|
|
21
27
|
if (paused)
|
|
22
28
|
return "paused";
|
|
23
|
-
if (stages.some((stage) => stage.state === "running")) {
|
|
29
|
+
if (stages.some((stage) => stage.state === "running" || waiting(stage))) {
|
|
24
30
|
return "running";
|
|
25
31
|
}
|
|
26
32
|
if (stages.some((stage) => stage.state === "canceled")) {
|
|
27
33
|
return "canceled";
|
|
28
34
|
}
|
|
29
35
|
if (stages.some((stage) => stage.state === "failed")) {
|
|
30
|
-
return "failed";
|
|
36
|
+
return headlineMade(stages) ? "partial" : "failed";
|
|
31
37
|
}
|
|
32
38
|
if (stages.length > 0 && stages.every((stage) => satisfied(stage.state))) {
|
|
33
39
|
return "done";
|
|
@@ -35,6 +41,19 @@ export function derive(stages, paused = false) {
|
|
|
35
41
|
// Includes a run created but not yet claimed and unfinished work after restart.
|
|
36
42
|
return "pending";
|
|
37
43
|
}
|
|
44
|
+
function waiting(stage) {
|
|
45
|
+
return stage.state === "pending" && stage.retryAt !== undefined && stage.retryAt !== null;
|
|
46
|
+
}
|
|
47
|
+
function headlineMade(stages) {
|
|
48
|
+
for (const kind of headline) {
|
|
49
|
+
const stage = stages.find((one) => one.kind === kind);
|
|
50
|
+
if (stage === undefined || stage.state === "skipped")
|
|
51
|
+
continue;
|
|
52
|
+
// A file the user supplied is not something this run made.
|
|
53
|
+
return stage.state === "done";
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
38
57
|
export function dependenciesOf(kind, sources) {
|
|
39
58
|
if (kind === "thumbnail" && sources.thumbnail === "from_prompt")
|
|
40
59
|
return [];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { causedBy } from "../errors.js";
|
|
2
2
|
import { derive, deps as graph, satisfied } from "./graph.js";
|
|
3
3
|
import { progressGate } from "./progress.js";
|
|
4
|
+
import { faultOf } from "./retry-policy.js";
|
|
4
5
|
const pauseReason = new Error("paused by user");
|
|
5
6
|
export function createRunner(deps) {
|
|
6
7
|
const inflight = new Map();
|
|
@@ -15,7 +16,7 @@ export function createRunner(deps) {
|
|
|
15
16
|
const stopped = new Set();
|
|
16
17
|
let count = 0;
|
|
17
18
|
let shuttingDown = false;
|
|
18
|
-
const emitStage = (stage, state, reason) => {
|
|
19
|
+
const emitStage = (stage, state, reason, detail = {}) => {
|
|
19
20
|
deps.emit(stage.projectId, {
|
|
20
21
|
type: "stage.state",
|
|
21
22
|
revisionId: stage.work.revisionId,
|
|
@@ -24,6 +25,8 @@ export function createRunner(deps) {
|
|
|
24
25
|
stage: stage.kind,
|
|
25
26
|
state,
|
|
26
27
|
...(reason === null ? {} : { failureReason: reason }),
|
|
28
|
+
...(detail.retryAt === undefined ? {} : { retryAt: detail.retryAt }),
|
|
29
|
+
...(detail.failureKind === undefined ? {} : { failureKind: detail.failureKind }),
|
|
27
30
|
});
|
|
28
31
|
};
|
|
29
32
|
// The global tally counts projects with a stage in flight, read off the in-flight set
|
|
@@ -46,9 +49,9 @@ export function createRunner(deps) {
|
|
|
46
49
|
announced.set(projectId, state);
|
|
47
50
|
deps.emit(projectId, { type: "project.state", projectId, state });
|
|
48
51
|
};
|
|
49
|
-
const conclude = (stage, state, reason) => {
|
|
52
|
+
const conclude = (stage, state, reason, kind) => {
|
|
50
53
|
try {
|
|
51
|
-
deps.stages.finish(stage.work, state, reason);
|
|
54
|
+
deps.stages.finish(stage.work, state, reason, kind ?? null);
|
|
52
55
|
}
|
|
53
56
|
catch (error) {
|
|
54
57
|
// The row stays `running` when the write fails. The event still goes out, and the
|
|
@@ -59,7 +62,31 @@ export function createRunner(deps) {
|
|
|
59
62
|
detail: reasonOf(error),
|
|
60
63
|
});
|
|
61
64
|
}
|
|
62
|
-
emitStage(stage, state, reason);
|
|
65
|
+
emitStage(stage, state, reason, { failureKind: kind });
|
|
66
|
+
};
|
|
67
|
+
// False when the failure is to be shown now.
|
|
68
|
+
const deferred = (stage, fault, reason) => {
|
|
69
|
+
let at;
|
|
70
|
+
try {
|
|
71
|
+
at = deps.stages.waitToRetry?.(stage.work, fault, reason);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
deps.log.write("error", "stage.retry", {
|
|
75
|
+
projectId: stage.projectId,
|
|
76
|
+
stage: stage.kind,
|
|
77
|
+
detail: reasonOf(error),
|
|
78
|
+
});
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (at === undefined)
|
|
82
|
+
return false;
|
|
83
|
+
deps.log.write("warn", "stage.retry", {
|
|
84
|
+
projectId: stage.projectId,
|
|
85
|
+
stage: stage.kind,
|
|
86
|
+
detail: `waiting until ${at} to run again after ${fault?.kind ?? "a failure"}: ${reason}`,
|
|
87
|
+
});
|
|
88
|
+
emitStage(stage, "pending", reason, { retryAt: at, failureKind: fault?.kind });
|
|
89
|
+
return true;
|
|
63
90
|
};
|
|
64
91
|
async function execute(stage, controller) {
|
|
65
92
|
const tag = (event) => ({
|
|
@@ -103,12 +130,15 @@ export function createRunner(deps) {
|
|
|
103
130
|
conclude(stage, paused ? "pending" : "canceled", paused ? null : "canceled by user");
|
|
104
131
|
return;
|
|
105
132
|
}
|
|
133
|
+
const fault = faultOf(error);
|
|
134
|
+
if (deferred(stage, fault, reasonOf(error)))
|
|
135
|
+
return;
|
|
106
136
|
deps.log.write("error", "stage.failed", {
|
|
107
137
|
projectId: stage.projectId,
|
|
108
138
|
stage: stage.kind,
|
|
109
139
|
detail: causedBy(error),
|
|
110
140
|
});
|
|
111
|
-
conclude(stage, "failed", reasonOf(error));
|
|
141
|
+
conclude(stage, "failed", reasonOf(error), fault?.kind);
|
|
112
142
|
}
|
|
113
143
|
}
|
|
114
144
|
function start(stage) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { providerError } from "../ports/model.js";
|
|
2
|
+
import { isProviderError, providerError } from "../ports/model.js";
|
|
3
|
+
import { planAccountOf } from "../ports/plan-limits.js";
|
|
3
4
|
import { attempt } from "./attempt.js";
|
|
4
5
|
export function stageProviders(deps, context, pieceId) {
|
|
5
6
|
const ctx = {
|
|
@@ -15,7 +16,44 @@ export function stageProviders(deps, context, pieceId) {
|
|
|
15
16
|
...(pieceId === undefined ? {} : { pieceId }),
|
|
16
17
|
signal: context.signal,
|
|
17
18
|
};
|
|
18
|
-
const
|
|
19
|
+
const queued = (provider, work) => deps.queue ? deps.queue.run(provider, context.signal, work) : work();
|
|
20
|
+
const waiter = { projectId: context.stage.projectId, stage: context.stage.kind };
|
|
21
|
+
// A CLI whose plan allowance is used up is waited for here, outside the queue, so the wait
|
|
22
|
+
// holds no provider slot; the call is then made afresh with a full set of attempts.
|
|
23
|
+
const schedule = async (provider, work) => {
|
|
24
|
+
const account = planAccountOf(provider);
|
|
25
|
+
const gate = deps.limits;
|
|
26
|
+
if (account === undefined || gate === undefined)
|
|
27
|
+
return queued(provider, work);
|
|
28
|
+
for (;;) {
|
|
29
|
+
await gate.ready(account, waiter, context.signal);
|
|
30
|
+
try {
|
|
31
|
+
return await queued(provider, work);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const hit = isProviderError(error) ? error.fault.planLimit : undefined;
|
|
35
|
+
if (hit === undefined || context.signal.aborted)
|
|
36
|
+
throw error;
|
|
37
|
+
gate.exhausted(hit);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
// Metering never fails a call: the answer is already paid for.
|
|
42
|
+
const meter = (call) => {
|
|
43
|
+
if (deps.meter === undefined)
|
|
44
|
+
return;
|
|
45
|
+
try {
|
|
46
|
+
deps.meter.record({ ...call, projectId: context.stage.projectId, stage: context.stage.kind });
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
deps.log.write("warn", "usage.record", {
|
|
50
|
+
projectId: context.stage.projectId,
|
|
51
|
+
stage: context.stage.kind,
|
|
52
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const since = (started) => Math.max(0, deps.clock.now().getTime() - started);
|
|
19
57
|
return {
|
|
20
58
|
llm: (call, onEvent) => {
|
|
21
59
|
// Resolved once; the adapter reads the stored key per request, so a key replaced
|
|
@@ -32,11 +70,17 @@ export function stageProviders(deps, context, pieceId) {
|
|
|
32
70
|
text,
|
|
33
71
|
...(reset === undefined ? {} : { reset }),
|
|
34
72
|
});
|
|
35
|
-
|
|
73
|
+
const started = deps.clock.now().getTime();
|
|
74
|
+
let limits;
|
|
75
|
+
const answered = schedule(call.provider, () => attempt(ctx, async (signal, progress) => {
|
|
36
76
|
preview("", true);
|
|
37
77
|
let text = "";
|
|
78
|
+
// Typed-ahead text the panel shows past the committed answer; the next delta
|
|
79
|
+
// replaces it, so the panel never shows a sentence twice.
|
|
80
|
+
let typed = false;
|
|
38
81
|
let usage = null;
|
|
39
82
|
let finishReason = null;
|
|
83
|
+
limits = undefined;
|
|
40
84
|
for await (const event of port.complete({
|
|
41
85
|
model: call.model,
|
|
42
86
|
...(call.thinkingConfig === undefined ? {} : { thinkingConfig: call.thinkingConfig }),
|
|
@@ -44,6 +88,7 @@ export function stageProviders(deps, context, pieceId) {
|
|
|
44
88
|
messages: call.messages,
|
|
45
89
|
...(call.documents === undefined ? {} : { documents: call.documents }),
|
|
46
90
|
...(call.webSearch === undefined ? {} : { webSearch: call.webSearch }),
|
|
91
|
+
...(call.images === undefined ? {} : { images: call.images }),
|
|
47
92
|
signal,
|
|
48
93
|
})) {
|
|
49
94
|
signal.throwIfAborted();
|
|
@@ -51,13 +96,22 @@ export function stageProviders(deps, context, pieceId) {
|
|
|
51
96
|
progress();
|
|
52
97
|
if (event.type === "delta") {
|
|
53
98
|
text += event.text;
|
|
99
|
+
if (typed)
|
|
100
|
+
preview(text, true);
|
|
101
|
+
else
|
|
102
|
+
preview(event.text);
|
|
103
|
+
typed = false;
|
|
104
|
+
}
|
|
105
|
+
else if (event.type === "partial") {
|
|
106
|
+
typed = true;
|
|
54
107
|
preview(event.text);
|
|
55
108
|
}
|
|
56
109
|
else if (event.type === "done") {
|
|
57
110
|
usage = event.usage;
|
|
58
111
|
finishReason = event.finishReason;
|
|
112
|
+
limits = event.limits;
|
|
59
113
|
}
|
|
60
|
-
if (event.type
|
|
114
|
+
if (event.type === "delta" || event.type === "done")
|
|
61
115
|
onEvent?.(event);
|
|
62
116
|
}
|
|
63
117
|
const answer = { text, usage, finishReason };
|
|
@@ -68,6 +122,28 @@ export function stageProviders(deps, context, pieceId) {
|
|
|
68
122
|
}
|
|
69
123
|
return answer;
|
|
70
124
|
}, { kind: "llm", streaming: port.capabilities.streams }));
|
|
125
|
+
return answered.then((result) => {
|
|
126
|
+
if (result.ok) {
|
|
127
|
+
const usage = result.value.usage;
|
|
128
|
+
meter({
|
|
129
|
+
kind: "llm",
|
|
130
|
+
provider: call.provider,
|
|
131
|
+
model: usage?.model ?? call.model,
|
|
132
|
+
...(usage === null
|
|
133
|
+
? {}
|
|
134
|
+
: {
|
|
135
|
+
tokensIn: usage.inputTokens,
|
|
136
|
+
tokensOut: usage.outputTokens,
|
|
137
|
+
...(usage.cachedInputTokens === undefined
|
|
138
|
+
? {}
|
|
139
|
+
: { cachedTokens: usage.cachedInputTokens }),
|
|
140
|
+
}),
|
|
141
|
+
wallMs: since(started),
|
|
142
|
+
...(limits === undefined ? {} : { limits }),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
});
|
|
71
147
|
},
|
|
72
148
|
tts: (call, observe) => {
|
|
73
149
|
const port = deps.registry.tts(call.provider);
|
|
@@ -95,13 +171,20 @@ export function stageProviders(deps, context, pieceId) {
|
|
|
95
171
|
});
|
|
96
172
|
}
|
|
97
173
|
};
|
|
98
|
-
|
|
174
|
+
const started = deps.clock.now().getTime();
|
|
175
|
+
const spoken = schedule(call.provider, () => attempt({ ...ctx, continuation }, async (signal, progress) => {
|
|
99
176
|
notify({ type: "start" });
|
|
100
177
|
try {
|
|
178
|
+
if (call.dialogue !== undefined && port.capabilities.dialogue !== true)
|
|
179
|
+
throw providerError({
|
|
180
|
+
kind: "unsupported",
|
|
181
|
+
message: `${call.provider} can't speak several voices in one request. Turn off Native multi-speaker under Speakers (Play → Audio, or Edit project → Providers), then Try again.`,
|
|
182
|
+
});
|
|
101
183
|
const spoken = await port.synthesize({
|
|
102
184
|
model: call.model,
|
|
103
185
|
voiceId: call.voiceId,
|
|
104
186
|
text: call.text,
|
|
187
|
+
...(call.dialogue === undefined ? {} : { dialogue: call.dialogue }),
|
|
105
188
|
signal,
|
|
106
189
|
onActivity: progress,
|
|
107
190
|
continuation,
|
|
@@ -138,17 +221,76 @@ export function stageProviders(deps, context, pieceId) {
|
|
|
138
221
|
throw error;
|
|
139
222
|
}
|
|
140
223
|
}, { kind: "tts", streaming: port.capabilities.streams }));
|
|
224
|
+
return spoken.then((result) => {
|
|
225
|
+
// Narration is billed by the characters sent, which is the text of this call.
|
|
226
|
+
if (result.ok)
|
|
227
|
+
meter({
|
|
228
|
+
kind: "tts",
|
|
229
|
+
provider: call.provider,
|
|
230
|
+
model: call.model ?? "",
|
|
231
|
+
characters: call.text.length,
|
|
232
|
+
wallMs: since(started),
|
|
233
|
+
});
|
|
234
|
+
return result;
|
|
235
|
+
});
|
|
141
236
|
},
|
|
142
237
|
image: (call) => {
|
|
143
238
|
const port = deps.registry.image(call.provider);
|
|
144
|
-
|
|
239
|
+
const callId = randomUUID();
|
|
240
|
+
// A long job (the Codex CLI reviewing and redrawing its image) says how far it has got
|
|
241
|
+
// on the stage's live panel; the other providers report nothing and show no panel.
|
|
242
|
+
const onProgress = (text) => context.emit({
|
|
243
|
+
type: "llm.preview",
|
|
244
|
+
...(pieceId === undefined ? {} : { workPieceId: pieceId }),
|
|
245
|
+
projectId: context.stage.projectId,
|
|
246
|
+
stage: context.stage.kind,
|
|
247
|
+
callId,
|
|
248
|
+
label: call.previewLabel ?? "Image",
|
|
249
|
+
text,
|
|
250
|
+
reset: true,
|
|
251
|
+
});
|
|
252
|
+
const started = deps.clock.now().getTime();
|
|
253
|
+
const drawn = schedule(call.provider, () => attempt(ctx, (signal) => port.generate({
|
|
145
254
|
model: call.model,
|
|
146
255
|
prompt: call.prompt,
|
|
147
256
|
aspect: call.aspect,
|
|
257
|
+
...(call.thinking === undefined ? {} : { thinking: call.thinking }),
|
|
258
|
+
...(call.reference === undefined ? {} : { reference: call.reference }),
|
|
259
|
+
...(call.cast === undefined ? {} : { cast: call.cast }),
|
|
260
|
+
onProgress,
|
|
148
261
|
signal,
|
|
149
262
|
}),
|
|
150
|
-
// One request, one answer: the
|
|
151
|
-
|
|
263
|
+
// One request, one answer: the limit runs over the whole call - 300 s, or the
|
|
264
|
+
// provider's own when it needs longer.
|
|
265
|
+
{
|
|
266
|
+
kind: "image",
|
|
267
|
+
...(port.timeoutMs === undefined ? {} : { timeoutMs: port.timeoutMs }),
|
|
268
|
+
}));
|
|
269
|
+
return drawn.then((result) => {
|
|
270
|
+
if (result.ok) {
|
|
271
|
+
const { usage, limits } = result.value;
|
|
272
|
+
meter({
|
|
273
|
+
kind: "image",
|
|
274
|
+
provider: call.provider,
|
|
275
|
+
model: call.model,
|
|
276
|
+
images: 1,
|
|
277
|
+
size: call.aspect,
|
|
278
|
+
...(call.thinking === undefined ? {} : { quality: call.thinking }),
|
|
279
|
+
...(usage === undefined
|
|
280
|
+
? {}
|
|
281
|
+
: {
|
|
282
|
+
tokensIn: usage.inputTokens,
|
|
283
|
+
tokensOut: usage.outputTokens,
|
|
284
|
+
...(usage.cachedInputTokens === undefined
|
|
285
|
+
? {}
|
|
286
|
+
: { cachedTokens: usage.cachedInputTokens }),
|
|
287
|
+
}),
|
|
288
|
+
wallMs: since(started),
|
|
289
|
+
...(limits === undefined ? {} : { limits }),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return result;
|
|
293
|
+
});
|
|
152
294
|
},
|
|
153
295
|
animate: (call) => {
|
|
154
296
|
const port = deps.registry.image(call.provider);
|
|
@@ -158,7 +300,8 @@ export function stageProviders(deps, context, pieceId) {
|
|
|
158
300
|
kind: "unsupported",
|
|
159
301
|
message: `${call.provider} can't turn images into video clips. Choose fal.ai or Replicate as the image provider in Edit project → Providers, or turn Animate images off in Edit project → Inputs → Look.`,
|
|
160
302
|
}));
|
|
161
|
-
|
|
303
|
+
const started = deps.clock.now().getTime();
|
|
304
|
+
const animated = schedule(call.provider, () => attempt(ctx, (signal) => animate({
|
|
162
305
|
model: call.model,
|
|
163
306
|
prompt: call.prompt,
|
|
164
307
|
image: call.image,
|
|
@@ -168,6 +311,18 @@ export function stageProviders(deps, context, pieceId) {
|
|
|
168
311
|
}),
|
|
169
312
|
// Queued and slow: the 900 s runs over the whole call, polling included.
|
|
170
313
|
{ kind: "video" }));
|
|
314
|
+
return animated.then((result) => {
|
|
315
|
+
if (result.ok)
|
|
316
|
+
meter({
|
|
317
|
+
kind: "video",
|
|
318
|
+
provider: call.provider,
|
|
319
|
+
model: call.model,
|
|
320
|
+
seconds: call.seconds,
|
|
321
|
+
size: call.aspect,
|
|
322
|
+
wallMs: since(started),
|
|
323
|
+
});
|
|
324
|
+
return result;
|
|
325
|
+
});
|
|
171
326
|
},
|
|
172
327
|
forPiece: (piece) => stageProviders(deps, context, piece),
|
|
173
328
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isProviderError } from "../ports/model.js";
|
|
2
|
+
// The second tier of the retry policy. `attempt.ts` retries a failed call a few times within
|
|
3
|
+
// seconds; when those run out on a failure that time can fix, the step is put back to wait
|
|
4
|
+
// and runs again later, so a rate limit, a timeout or a dropped connection costs the user
|
|
5
|
+
// nothing but time. The wait is written down, not held in memory: a restart keeps it.
|
|
6
|
+
// The failures worth waiting out. A refusal, a rejected or missing key, an unsupported
|
|
7
|
+
// request and a lost submission are the provider's final answer (or need a person), so
|
|
8
|
+
// they are shown at once. `other` covers a request the provider rejected as well as an
|
|
9
|
+
// unreadable answer; the quick in-call retries are all it gets.
|
|
10
|
+
export const waitableKinds = ["rate_limit", "timeout", "dropped"];
|
|
11
|
+
// Four waits after the first failed run: about 2, 4, 8 and 16 minutes, 30 in all.
|
|
12
|
+
export const autoRetryLimit = 4;
|
|
13
|
+
export const autoRetryBaseMs = 2 * 60_000;
|
|
14
|
+
// ±25 %, so a burst of steps that hit the same limit together do not all return together.
|
|
15
|
+
export const autoRetryJitter = 0.25;
|
|
16
|
+
// A provider asking for longer than this (a daily quota) is reported rather than waited on.
|
|
17
|
+
export const retryAfterCeilingMs = 60 * 60_000;
|
|
18
|
+
// A slice may wrap the wrapper's error in its own sentence ("Image 3: ..."); the kind is on
|
|
19
|
+
// the cause.
|
|
20
|
+
export function faultOf(error) {
|
|
21
|
+
let current = error;
|
|
22
|
+
for (let depth = 0; depth < 6 && current !== undefined && current !== null; depth += 1) {
|
|
23
|
+
if (isProviderError(current))
|
|
24
|
+
return current.fault;
|
|
25
|
+
current = current instanceof Error ? current.cause : undefined;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
// `retries` is how many waits the step already had. Undefined: report the failure now.
|
|
30
|
+
export function autoRetryDelay(fault, retries, random) {
|
|
31
|
+
if (fault === undefined || !waitableKinds.includes(fault.kind))
|
|
32
|
+
return undefined;
|
|
33
|
+
if (retries >= autoRetryLimit)
|
|
34
|
+
return undefined;
|
|
35
|
+
const nominal = autoRetryBaseMs * 2 ** retries;
|
|
36
|
+
const jittered = Math.round(nominal * (1 - autoRetryJitter + 2 * autoRetryJitter * random()));
|
|
37
|
+
if (fault.retryAfterMs === undefined)
|
|
38
|
+
return jittered;
|
|
39
|
+
if (fault.retryAfterMs > retryAfterCeilingMs)
|
|
40
|
+
return undefined;
|
|
41
|
+
// The provider's own wait is the floor; ours still spreads the burst above it.
|
|
42
|
+
return Math.max(fault.retryAfterMs, jittered);
|
|
43
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { isProviderError } from "../ports/model.js";
|
|
2
|
+
import { attempt } from "./attempt.js";
|
|
3
|
+
export async function standaloneLlm(deps, call) {
|
|
4
|
+
const port = deps.registry.llm(call.provider);
|
|
5
|
+
const started = deps.clock.now().getTime();
|
|
6
|
+
const answer = await wrapped(deps, call, async (signal, progress) => {
|
|
7
|
+
let text = "";
|
|
8
|
+
let usage = null;
|
|
9
|
+
let limits;
|
|
10
|
+
for await (const event of port.complete({
|
|
11
|
+
model: call.model,
|
|
12
|
+
...(call.thinking === undefined ? {} : { thinking: call.thinking }),
|
|
13
|
+
messages: call.messages,
|
|
14
|
+
signal,
|
|
15
|
+
})) {
|
|
16
|
+
signal.throwIfAborted();
|
|
17
|
+
// Every event is a sign of life, so the idle clock restarts here.
|
|
18
|
+
progress();
|
|
19
|
+
if (event.type === "delta")
|
|
20
|
+
text += event.text;
|
|
21
|
+
else if (event.type === "done") {
|
|
22
|
+
usage = event.usage;
|
|
23
|
+
limits = event.limits;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { text, usage, ...(limits === undefined ? {} : { limits }) };
|
|
27
|
+
}, { kind: "llm", streaming: port.capabilities.streams });
|
|
28
|
+
meter(deps, call, {
|
|
29
|
+
kind: "llm",
|
|
30
|
+
provider: call.provider,
|
|
31
|
+
model: answer.usage?.model ?? call.model,
|
|
32
|
+
...tokens(answer.usage ?? undefined),
|
|
33
|
+
wallMs: Math.max(0, deps.clock.now().getTime() - started),
|
|
34
|
+
...(answer.limits === undefined ? {} : { limits: answer.limits }),
|
|
35
|
+
});
|
|
36
|
+
return answer;
|
|
37
|
+
}
|
|
38
|
+
export async function standaloneImage(deps, call) {
|
|
39
|
+
const port = deps.registry.image(call.provider);
|
|
40
|
+
const started = deps.clock.now().getTime();
|
|
41
|
+
const image = await wrapped(deps, call, (signal) => port.generate({
|
|
42
|
+
model: call.model,
|
|
43
|
+
prompt: call.prompt,
|
|
44
|
+
aspect: call.aspect,
|
|
45
|
+
signal,
|
|
46
|
+
}), { kind: "image", ...(port.timeoutMs === undefined ? {} : { timeoutMs: port.timeoutMs }) });
|
|
47
|
+
meter(deps, call, {
|
|
48
|
+
kind: "image",
|
|
49
|
+
provider: call.provider,
|
|
50
|
+
model: call.model,
|
|
51
|
+
images: 1,
|
|
52
|
+
size: call.aspect,
|
|
53
|
+
...tokens(image.usage),
|
|
54
|
+
wallMs: Math.max(0, deps.clock.now().getTime() - started),
|
|
55
|
+
...(image.limits === undefined ? {} : { limits: image.limits }),
|
|
56
|
+
});
|
|
57
|
+
return image;
|
|
58
|
+
}
|
|
59
|
+
async function wrapped(deps, use, call, opts) {
|
|
60
|
+
// The wrapper names its log lines by project and stage; these name what the call was for.
|
|
61
|
+
const label = `${use.purpose}:${use.owner.kind}:${use.owner.id}`;
|
|
62
|
+
let next = 0;
|
|
63
|
+
const attempts = {
|
|
64
|
+
start: () => {
|
|
65
|
+
next += 1;
|
|
66
|
+
return String(next);
|
|
67
|
+
},
|
|
68
|
+
end: () => { },
|
|
69
|
+
};
|
|
70
|
+
const stage = opts.kind === "image" ? "images" : "article";
|
|
71
|
+
try {
|
|
72
|
+
const result = await attempt({
|
|
73
|
+
work: {
|
|
74
|
+
projectId: label,
|
|
75
|
+
revisionId: label,
|
|
76
|
+
workId: label,
|
|
77
|
+
stageId: label,
|
|
78
|
+
kind: stage,
|
|
79
|
+
fingerprint: label,
|
|
80
|
+
},
|
|
81
|
+
maySubmit: () => true,
|
|
82
|
+
clock: deps.clock,
|
|
83
|
+
log: deps.log,
|
|
84
|
+
attempts,
|
|
85
|
+
projectId: label,
|
|
86
|
+
stage,
|
|
87
|
+
stageId: label,
|
|
88
|
+
signal: use.signal ?? new AbortController().signal,
|
|
89
|
+
}, call, opts);
|
|
90
|
+
if (!result.ok)
|
|
91
|
+
throw new Error("The provider did not take the request.");
|
|
92
|
+
return result.value;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw standaloneError(error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// The wrapper's own advice points at a stage's buttons ("use Try again"); a standalone call
|
|
99
|
+
// has none, and each caller says itself where to go next, so that advice is dropped.
|
|
100
|
+
function standaloneError(error) {
|
|
101
|
+
if (!isProviderError(error))
|
|
102
|
+
return error;
|
|
103
|
+
const kept = error.message
|
|
104
|
+
.replace(/,? then use (?:Retry stage|Try again)(?:, or choose another model in the Providers section of Edit project)?/g, "")
|
|
105
|
+
.split(/(?<=\.)\s+/)
|
|
106
|
+
.filter((sentence) => !/Retry stage|Try again|Edit project/.test(sentence))
|
|
107
|
+
.join(" ")
|
|
108
|
+
.trim();
|
|
109
|
+
if (kept === error.message || kept === "")
|
|
110
|
+
return error;
|
|
111
|
+
return Object.assign(new Error(kept, { cause: error }), { fault: error.fault });
|
|
112
|
+
}
|
|
113
|
+
function tokens(usage) {
|
|
114
|
+
if (usage === undefined)
|
|
115
|
+
return {};
|
|
116
|
+
return {
|
|
117
|
+
tokensIn: usage.inputTokens,
|
|
118
|
+
tokensOut: usage.outputTokens,
|
|
119
|
+
...(usage.cachedInputTokens === undefined ? {} : { cachedTokens: usage.cachedInputTokens }),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Metering never fails a call: the answer is already paid for.
|
|
123
|
+
function meter(deps, use, call) {
|
|
124
|
+
if (deps.meter === undefined)
|
|
125
|
+
return;
|
|
126
|
+
try {
|
|
127
|
+
deps.meter.record({ ...call, owner: use.owner, purpose: use.purpose });
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
deps.log.write("warn", "usage.record", {
|
|
131
|
+
detail: `${use.purpose} for ${use.owner.kind} ${use.owner.id}: ${error instanceof Error ? error.message : String(error)}`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -29,9 +29,35 @@ export function ownsCurrentWork(db, work, pieceId) {
|
|
|
29
29
|
.prepare(`SELECT 1 FROM revision_work_reservations r JOIN project_heads h ON h.project_id=r.project_id AND h.revision_id=r.revision_id JOIN project_revisions v ON v.id=r.revision_id JOIN json_each(v.fingerprints) f ON f.key=COALESCE(r.logical_key,r.work_key) AND f.value=COALESCE(r.desired_fingerprint,r.fingerprint) WHERE r.project_id=? AND r.work_id=? AND r.piece_id IS ? AND (r.piece_id IS NULL OR EXISTS(SELECT 1 FROM revision_work_pieces p WHERE p.id=r.piece_id AND p.work_id=r.work_id AND p.work_key=r.work_key AND p.fingerprint=r.fingerprint))`)
|
|
30
30
|
.get(work.projectId, work.workId, pieceId) !== undefined);
|
|
31
31
|
}
|
|
32
|
-
export function finishWork(db, work, state, reason) {
|
|
32
|
+
export function finishWork(db, work, state, reason, kind = null) {
|
|
33
33
|
if (!workExists(db, work))
|
|
34
34
|
return;
|
|
35
35
|
const normalized = state === "provided" || state === "skipped" ? "done" : state;
|
|
36
|
-
db.prepare(`UPDATE revision_work SET state=?,failure_reason=?,dispatch_state=CASE WHEN ?='pending' THEN 'held' ELSE dispatch_state END WHERE id=?`).run(normalized, reason, normalized, work.workId);
|
|
36
|
+
db.prepare(`UPDATE revision_work SET state=?,failure_reason=?,failure_kind=?,retry_at=NULL,dispatch_state=CASE WHEN ?='pending' THEN 'held' ELSE dispatch_state END WHERE id=?`).run(normalized, reason, normalized === "failed" ? kind : null, normalized, work.workId);
|
|
37
|
+
}
|
|
38
|
+
// How many times the automatic retry already put this step back to wait.
|
|
39
|
+
export function autoRetriesOf(db, work) {
|
|
40
|
+
const row = db.prepare("SELECT auto_retries FROM revision_work WHERE id=?").get(work.workId);
|
|
41
|
+
return typeof row?.auto_retries === "number" ? row.auto_retries : 0;
|
|
42
|
+
}
|
|
43
|
+
// A running step that failed on something time can fix goes back to `pending` with the time
|
|
44
|
+
// it may start again. It stays dispatchable, unlike a paused one, and its unfinished pieces
|
|
45
|
+
// may be sent again: the failure that ended them was not a lost submission.
|
|
46
|
+
export function deferWork(db, work, at, reason, kind) {
|
|
47
|
+
if (!workExists(db, work))
|
|
48
|
+
return false;
|
|
49
|
+
const moved = db
|
|
50
|
+
.prepare(`UPDATE revision_work SET state='pending',retry_at=?,auto_retries=auto_retries+1,failure_reason=?,failure_kind=? WHERE id=? AND state='running' AND dispatch_state='allowed'`)
|
|
51
|
+
.run(at, reason, kind, work.workId).changes === 1;
|
|
52
|
+
if (moved)
|
|
53
|
+
db.prepare("UPDATE revision_work_pieces SET state='pending' WHERE work_id=? AND state IN ('failed','running')").run(work.workId);
|
|
54
|
+
return moved;
|
|
55
|
+
}
|
|
56
|
+
// The projects with a wait that has run out, their wait cleared so the next tick may claim
|
|
57
|
+
// them. A paused project keeps its wait: Resume ticks it, and nothing else should.
|
|
58
|
+
export function dueRetries(db, now) {
|
|
59
|
+
const rows = db
|
|
60
|
+
.prepare(`UPDATE revision_work SET retry_at=NULL WHERE state='pending' AND retry_at IS NOT NULL AND retry_at<=? AND NOT EXISTS(SELECT 1 FROM project_controls c WHERE c.project_id=revision_work.project_id AND c.paused=1) RETURNING project_id`)
|
|
61
|
+
.all(now);
|
|
62
|
+
return [...new Set(rows.map((row) => String(row.project_id)))];
|
|
37
63
|
}
|