videobook 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/bin/videobook.js +7 -0
- package/dist/acestep/client.js +203 -0
- package/dist/acestep/config.js +62 -0
- package/dist/acestep/errors.js +8 -0
- package/dist/acestep/process.js +175 -0
- package/dist/alive-captions/analyze.js +117 -0
- package/dist/alive-captions/audio.js +179 -0
- package/dist/alive-captions/frames.js +59 -0
- package/dist/alive-captions/model.js +37 -0
- package/dist/alive-captions/schema.js +49 -0
- package/dist/alive-captions/track.js +9 -0
- package/dist/api-key.js +33 -0
- package/dist/app-state.js +56 -0
- package/dist/app.js +257 -0
- package/dist/archive-b2.js +332 -0
- package/dist/archive-store.js +169 -0
- package/dist/artifact-label.js +36 -0
- package/dist/artifact-refs.js +49 -0
- package/dist/asset-tag-extraction.js +169 -0
- package/dist/asset-tags.js +228 -0
- package/dist/asset-usage.js +23 -0
- package/dist/audio-cue.js +71 -0
- package/dist/audio-encoding.js +32 -0
- package/dist/audio-sync-request.js +1 -0
- package/dist/audio-sync.js +223 -0
- package/dist/audio-trim-range.js +31 -0
- package/dist/book-context.js +31 -0
- package/dist/book-manager.js +374 -0
- package/dist/catalog-gc.js +26 -0
- package/dist/chain-executor.js +815 -0
- package/dist/chain-generation.js +115 -0
- package/dist/chain-plan.js +746 -0
- package/dist/chain-step-review.js +60 -0
- package/dist/character-name.js +25 -0
- package/dist/chat-generation-routing.js +73 -0
- package/dist/chat-generation-settings.js +78 -0
- package/dist/chat-models.js +44 -0
- package/dist/chat-prompt-rewrite.js +67 -0
- package/dist/chat-router.js +628 -0
- package/dist/chat-view-actions.js +49 -0
- package/dist/cli/args.js +242 -0
- package/dist/cli/config-command.js +103 -0
- package/dist/cli/doctor-command.js +289 -0
- package/dist/cli/entry.js +2 -0
- package/dist/cli/key-report.js +16 -0
- package/dist/cli/main.js +104 -0
- package/dist/cli/mcp-command.js +88 -0
- package/dist/cli/mcp-relay.js +51 -0
- package/dist/cli/open-browser.js +14 -0
- package/dist/cli/run-command.js +231 -0
- package/dist/cli/serve-command.js +62 -0
- package/dist/cli/setup-command.js +60 -0
- package/dist/cli/stdio-guard.js +8 -0
- package/dist/cold-open-model.js +7 -0
- package/dist/cold-open.js +79 -0
- package/dist/command-reference.js +95 -0
- package/dist/content-store.js +74 -0
- package/dist/cost.js +8 -0
- package/dist/crop-geometry.js +23 -0
- package/dist/dancing-workflow.js +447 -0
- package/dist/data-home.js +44 -0
- package/dist/diagnostics.js +109 -0
- package/dist/dialog-quality.js +34 -0
- package/dist/downloads-dest.js +56 -0
- package/dist/editing-moments.js +194 -0
- package/dist/elevenlabs-audio-extension.js +66 -0
- package/dist/engine.js +54 -0
- package/dist/env-file.js +91 -0
- package/dist/env-secrets.js +148 -0
- package/dist/ffmpeg-binary.js +12 -0
- package/dist/ffmpeg.js +425 -0
- package/dist/ffprobe-binary.js +77 -0
- package/dist/forget-asset.js +216 -0
- package/dist/frame-utils.js +27 -0
- package/dist/generation-credit-error.js +11 -0
- package/dist/generation-input-mentions.js +117 -0
- package/dist/generation-input-policy.js +68 -0
- package/dist/generation-reference-mentions.js +97 -0
- package/dist/goal-characters.js +49 -0
- package/dist/goal-plan-v3.js +273 -0
- package/dist/goal-plan.js +220 -0
- package/dist/goal-planner-prompt.js +28 -0
- package/dist/goal-prompt-budget.js +4 -0
- package/dist/goal-reference-images.js +25 -0
- package/dist/goal-state.js +261 -0
- package/dist/goal-stills.js +153 -0
- package/dist/goal-store.js +144 -0
- package/dist/goal-styles.js +144 -0
- package/dist/goal-v3-runtime.js +157 -0
- package/dist/guides/character_schema_guide.md +70 -0
- package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
- package/dist/guides/grok_imagine_prompting_guide.md +72 -0
- package/dist/guides/image_reproduction_guide.md +84 -0
- package/dist/guides/kling_3_0_prompting_guide.md +136 -0
- package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
- package/dist/guides/seedance_2_prompting_guide.md +172 -0
- package/dist/http-access.js +49 -0
- package/dist/http-byte-range.js +31 -0
- package/dist/hydration.js +56 -0
- package/dist/image-compress.js +77 -0
- package/dist/image-edit-default.js +1 -0
- package/dist/image-edit-models.js +41 -0
- package/dist/image-edit-settings.js +11 -0
- package/dist/image-region-edit.js +32 -0
- package/dist/image-subject-map.js +523 -0
- package/dist/image.js +46 -0
- package/dist/index.js +12 -0
- package/dist/job-deadline.js +60 -0
- package/dist/jobs/_alibaba-upload.js +30 -0
- package/dist/jobs/_audio-chunks.js +31 -0
- package/dist/jobs/_download-into-asset.js +15 -0
- package/dist/jobs/_fal-shared.js +22 -0
- package/dist/jobs/_fal-upload.js +35 -0
- package/dist/jobs/_image-edit-adapter.js +213 -0
- package/dist/jobs/_kie-poll.js +119 -0
- package/dist/jobs/_kie-upload.js +32 -0
- package/dist/jobs/_lease.js +163 -0
- package/dist/jobs/_persist-output.js +19 -0
- package/dist/jobs/_storage-retry.js +21 -0
- package/dist/jobs/_transcribe.js +245 -0
- package/dist/jobs/_translate-transcript.js +270 -0
- package/dist/jobs/analyze-alive-captions.js +10 -0
- package/dist/jobs/analyze-editing-moments.js +104 -0
- package/dist/jobs/analyze-jokes.js +143 -0
- package/dist/jobs/analyze-keyword-placement.js +155 -0
- package/dist/jobs/analyze-music.js +14 -0
- package/dist/jobs/analyze-video.js +282 -0
- package/dist/jobs/apply-cuts.js +83 -0
- package/dist/jobs/apply-sfx.js +107 -0
- package/dist/jobs/archive-clip.js +458 -0
- package/dist/jobs/assemble-goal.js +98 -0
- package/dist/jobs/audio-boost.js +71 -0
- package/dist/jobs/audio-mix.js +161 -0
- package/dist/jobs/backfill-asset-tags.js +144 -0
- package/dist/jobs/burn-watermark.js +316 -0
- package/dist/jobs/change-video-speed.js +176 -0
- package/dist/jobs/clean-video-audio.js +207 -0
- package/dist/jobs/cold-open.js +166 -0
- package/dist/jobs/combine-goal.js +108 -0
- package/dist/jobs/complete-provider-task.js +185 -0
- package/dist/jobs/copy-library-asset.js +33 -0
- package/dist/jobs/cost-estimate-assessment.js +113 -0
- package/dist/jobs/crop-image.js +47 -0
- package/dist/jobs/crop-video.js +71 -0
- package/dist/jobs/describe-image.js +181 -0
- package/dist/jobs/detect-scenes.js +33 -0
- package/dist/jobs/doctor-fix.js +100 -0
- package/dist/jobs/download-video.js +427 -0
- package/dist/jobs/enqueue-search-index.js +24 -0
- package/dist/jobs/even-split.js +122 -0
- package/dist/jobs/export-sequence.js +365 -0
- package/dist/jobs/extend-audio.js +229 -0
- package/dist/jobs/extract-audio.js +72 -0
- package/dist/jobs/extract-frame.js +58 -0
- package/dist/jobs/extract-notebook-audio.js +82 -0
- package/dist/jobs/extract-notebook-frame.js +93 -0
- package/dist/jobs/extract-video-clip.js +8 -0
- package/dist/jobs/extract-video-poster.js +55 -0
- package/dist/jobs/extract-waveform.js +25 -0
- package/dist/jobs/finish-goal.js +255 -0
- package/dist/jobs/forget-asset.js +11 -0
- package/dist/jobs/generate-acestep-cover.js +219 -0
- package/dist/jobs/generate-media.js +70 -0
- package/dist/jobs/image-subjects.js +1059 -0
- package/dist/jobs/index.js +18 -0
- package/dist/jobs/inject-first-frame.js +175 -0
- package/dist/jobs/isolate-vocals.js +69 -0
- package/dist/jobs/kie-paygate.js +177 -0
- package/dist/jobs/label-music-sections.js +5 -0
- package/dist/jobs/osg-placement.js +135 -0
- package/dist/jobs/pad-video.js +72 -0
- package/dist/jobs/plan-goal.js +97 -0
- package/dist/jobs/post-record.js +21 -0
- package/dist/jobs/post-to-x.js +21 -0
- package/dist/jobs/prepare-search-reference.js +190 -0
- package/dist/jobs/prepare-temporal-search.js +16 -0
- package/dist/jobs/process-audio.js +67 -0
- package/dist/jobs/process-image.js +58 -0
- package/dist/jobs/process-upload.js +166 -0
- package/dist/jobs/process-video.js +438 -0
- package/dist/jobs/register-all.js +161 -0
- package/dist/jobs/relayout-notebook.js +78 -0
- package/dist/jobs/remove-sound.js +299 -0
- package/dist/jobs/render-project.js +380 -0
- package/dist/jobs/render-qr-outro.js +138 -0
- package/dist/jobs/render-timeline.js +283 -0
- package/dist/jobs/repair-search-indexes.js +42 -0
- package/dist/jobs/replace-video-audio.js +64 -0
- package/dist/jobs/restore-archive-clip.js +53 -0
- package/dist/jobs/reverse-video.js +53 -0
- package/dist/jobs/rewrite-script.js +173 -0
- package/dist/jobs/run-notebook.js +3335 -0
- package/dist/jobs/semantic-index.js +797 -0
- package/dist/jobs/smooth-scene-concat.js +257 -0
- package/dist/jobs/splice-video.js +280 -0
- package/dist/jobs/split-video.js +127 -0
- package/dist/jobs/suggest-cuts.js +149 -0
- package/dist/jobs/suggest-sfx.js +135 -0
- package/dist/jobs/sync-video-to-audio.js +329 -0
- package/dist/jobs/tag-asset.js +266 -0
- package/dist/jobs/task-failure.js +16 -0
- package/dist/jobs/timewarp-video.js +152 -0
- package/dist/jobs/transcribe-video-audio.js +117 -0
- package/dist/jobs/transcribe.js +35 -0
- package/dist/jobs/translate-transcript.js +55 -0
- package/dist/jobs/trim-audio.js +133 -0
- package/dist/jobs/trim-video.js +291 -0
- package/dist/jobs/xai-paygate.js +26 -0
- package/dist/keyword-placement-cache.js +74 -0
- package/dist/lazy-render.js +142 -0
- package/dist/library-browse.js +78 -0
- package/dist/lightning-defaults.js +206 -0
- package/dist/load-env.js +12 -0
- package/dist/local-search-models.js +206 -0
- package/dist/local-upload.js +318 -0
- package/dist/logger.js +69 -0
- package/dist/media/adapters/alibaba.js +20 -0
- package/dist/media/adapters/elevenlabs.js +58 -0
- package/dist/media/adapters/fal.js +45 -0
- package/dist/media/adapters/index.js +26 -0
- package/dist/media/adapters/kie.js +120 -0
- package/dist/media/adapters/pending.js +21 -0
- package/dist/media/adapters/types.js +5 -0
- package/dist/media/adapters/xai.js +18 -0
- package/dist/media/catalog.js +236 -0
- package/dist/media/complete.js +191 -0
- package/dist/media/cost.js +45 -0
- package/dist/media/descriptor.js +157 -0
- package/dist/media/endpoints.js +11 -0
- package/dist/media/frame-options.js +18 -0
- package/dist/media/insert-video-timing.js +17 -0
- package/dist/media/json-schema.js +75 -0
- package/dist/media/legacy-task-types.js +129 -0
- package/dist/media/listing.js +24 -0
- package/dist/media/model-id.js +14 -0
- package/dist/media/preview.js +86 -0
- package/dist/media/prompt-dialects.js +65 -0
- package/dist/media/ref-fields.js +106 -0
- package/dist/media/refs.js +131 -0
- package/dist/media/request.js +188 -0
- package/dist/media/types.js +6 -0
- package/dist/media/wire.js +161 -0
- package/dist/media-library.js +535 -0
- package/dist/media-output-path.js +33 -0
- package/dist/multyculty-workflow.js +757 -0
- package/dist/notebook-action-catalog.js +608 -0
- package/dist/notebook-cell-outputs.js +726 -0
- package/dist/notebook-credit-retry.js +45 -0
- package/dist/notebook-fixtures.js +8 -0
- package/dist/notebook-generation-contract.js +77 -0
- package/dist/notebook-generation-controls.js +145 -0
- package/dist/notebook-goal-layout.js +40 -0
- package/dist/notebook-graph.js +37 -0
- package/dist/notebook-image-edit.js +84 -0
- package/dist/notebook-initial-generation-prompt.js +222 -0
- package/dist/notebook-live-analysis.js +941 -0
- package/dist/notebook-media-ops.js +579 -0
- package/dist/notebook-mutations.js +20 -0
- package/dist/notebook-placement.js +41 -0
- package/dist/notebook-provider-failure.js +87 -0
- package/dist/notebook-published-post.js +26 -0
- package/dist/notebook-reference-resolver.js +227 -0
- package/dist/notebook-relayout.js +270 -0
- package/dist/notebook-run-plan.js +519 -0
- package/dist/notebook-shots.js +162 -0
- package/dist/notebook-workflow.js +856 -0
- package/dist/one-password.js +200 -0
- package/dist/openai-intelligence.js +99 -0
- package/dist/original-media-file.js +12 -0
- package/dist/osg-assets.js +36 -0
- package/dist/osg-placement-cache.js +64 -0
- package/dist/osg-settings.js +27 -0
- package/dist/paid-tools.js +22 -0
- package/dist/platform-guidance.js +26 -0
- package/dist/posts/dropbox-auth.js +91 -0
- package/dist/posts/dropbox-upload.js +132 -0
- package/dist/posts/icloud-drive.js +134 -0
- package/dist/posts/icloud-status.js +64 -0
- package/dist/posts/post-contract.js +2 -0
- package/dist/posts/post-copy-format.js +43 -0
- package/dist/posts/post-copy.js +193 -0
- package/dist/posts/post-folder.js +18 -0
- package/dist/posts/post-metrics.js +119 -0
- package/dist/posts/post-store.js +340 -0
- package/dist/posts/x-post-target.js +43 -0
- package/dist/preview-payload.js +90 -0
- package/dist/prompt-surfaces.js +22 -0
- package/dist/provider-prompt-transform.js +77 -0
- package/dist/provider-status-link.js +21 -0
- package/dist/provider-task-record.js +78 -0
- package/dist/provider-task-status.js +47 -0
- package/dist/provider-usage.js +294 -0
- package/dist/queue.js +810 -0
- package/dist/reference-video-duration.js +17 -0
- package/dist/remotion/BlurFillVideo.js +51 -0
- package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
- package/dist/remotion/OsgOverlay.js +30 -0
- package/dist/remotion/QrOutroComposition.js +47 -0
- package/dist/remotion/RenderComposition.js +11 -0
- package/dist/remotion/browser.js +59 -0
- package/dist/remotion/constants.js +35 -0
- package/dist/remotion/index.js +66 -0
- package/dist/remotion/keyword-graphics.js +151 -0
- package/dist/remotion/keyword-placement.js +182 -0
- package/dist/remotion/osg-spec.js +198 -0
- package/dist/remotion/public/qr-outro-impact.wav +0 -0
- package/dist/remotion/qr-outro-audio.js +1 -0
- package/dist/remotion/qr-outro-spec.js +211 -0
- package/dist/remotion/shared-bundle.js +37 -0
- package/dist/remotion/subtitles/AliveCaptions.js +83 -0
- package/dist/remotion/subtitles/Highlight.js +58 -0
- package/dist/remotion/subtitles/Impact.js +67 -0
- package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
- package/dist/remotion/subtitles/alive-layout.js +321 -0
- package/dist/remotion/subtitles/alive-types.js +17 -0
- package/dist/remotion/subtitles/chunker.js +68 -0
- package/dist/remotion/subtitles/segments.js +42 -0
- package/dist/remotion/subtitles/styles.js +4 -0
- package/dist/remotion/subtitles/types.js +1 -0
- package/dist/remotion/util.js +18 -0
- package/dist/remotion-cancellation.js +15 -0
- package/dist/rename-helpers.js +204 -0
- package/dist/rhythm/analyze.js +128 -0
- package/dist/rhythm/contract.js +14 -0
- package/dist/rhythm/draft-model.js +19 -0
- package/dist/rhythm/energy.js +133 -0
- package/dist/rhythm/fft.js +44 -0
- package/dist/rhythm/model.js +71 -0
- package/dist/rhythm/music-analysis.js +124 -0
- package/dist/rhythm/music-draft.js +261 -0
- package/dist/rhythm/onset.js +101 -0
- package/dist/rhythm/run-worker.js +51 -0
- package/dist/rhythm/score.js +47 -0
- package/dist/rhythm/section-labels.js +168 -0
- package/dist/rhythm/sequence-rhythm.js +67 -0
- package/dist/rhythm/synthesize.js +103 -0
- package/dist/rhythm/timing.js +98 -0
- package/dist/rhythm/track.js +320 -0
- package/dist/rhythm/visual-fit.js +4 -0
- package/dist/rhythm/worker.js +25 -0
- package/dist/scene-detect.js +200 -0
- package/dist/scene-transition.js +12 -0
- package/dist/scheduled-posts.js +200 -0
- package/dist/search-index-coverage.js +13 -0
- package/dist/search-index-media.js +71 -0
- package/dist/search-reference-store.js +60 -0
- package/dist/search-scoring.js +17 -0
- package/dist/serve.js +125 -0
- package/dist/server-lock.js +143 -0
- package/dist/server.js +42 -0
- package/dist/srt.js +16 -0
- package/dist/tag-backfill-state.js +121 -0
- package/dist/tag-settings.js +30 -0
- package/dist/tag-vocabulary.js +237 -0
- package/dist/test-queue-cleanup.js +1 -0
- package/dist/time-parse.js +50 -0
- package/dist/timeline-helpers.js +554 -0
- package/dist/timewarp.js +87 -0
- package/dist/tools/_image-inputs.js +9 -0
- package/dist/tools/_media-refs.js +84 -0
- package/dist/tools/_shared.js +59 -0
- package/dist/tools/_suggest-shared.js +279 -0
- package/dist/tools/add-timeline-audio.js +73 -0
- package/dist/tools/alive-captions.js +157 -0
- package/dist/tools/analyze-jokes.js +56 -0
- package/dist/tools/analyze-video.js +66 -0
- package/dist/tools/animate-keywords.js +217 -0
- package/dist/tools/api-status.js +42 -0
- package/dist/tools/application-commands.js +34 -0
- package/dist/tools/apply-cuts.js +112 -0
- package/dist/tools/apply-sfx.js +152 -0
- package/dist/tools/apply-watermark.js +670 -0
- package/dist/tools/archive-clip.js +74 -0
- package/dist/tools/asset-history.js +61 -0
- package/dist/tools/asset-manifest.js +24 -0
- package/dist/tools/asset-tags.js +151 -0
- package/dist/tools/assign-slot.js +97 -0
- package/dist/tools/audio-boost.js +107 -0
- package/dist/tools/audio-mix.js +121 -0
- package/dist/tools/book-usage.js +16 -0
- package/dist/tools/chain.js +136 -0
- package/dist/tools/change-video-speed.js +105 -0
- package/dist/tools/chat-history.js +5 -0
- package/dist/tools/chat.js +848 -0
- package/dist/tools/clean-video-audio.js +97 -0
- package/dist/tools/clear-failed-tasks.js +66 -0
- package/dist/tools/cold-open.js +114 -0
- package/dist/tools/command-plan.js +1680 -0
- package/dist/tools/commands.js +1666 -0
- package/dist/tools/concat-images.js +236 -0
- package/dist/tools/connect-service.js +55 -0
- package/dist/tools/copy-file-to-clipboard.js +58 -0
- package/dist/tools/create-character.js +114 -0
- package/dist/tools/create-qart.js +253 -0
- package/dist/tools/create-qr-outro.js +149 -0
- package/dist/tools/create-qrbtf.js +215 -0
- package/dist/tools/create-watermark.js +97 -0
- package/dist/tools/crop-asset.js +18 -0
- package/dist/tools/crop-image.js +120 -0
- package/dist/tools/crop-video.js +97 -0
- package/dist/tools/delete-asset.js +104 -0
- package/dist/tools/describe-image.js +69 -0
- package/dist/tools/detect-scenes.js +44 -0
- package/dist/tools/doctor.js +126 -0
- package/dist/tools/download-video.js +114 -0
- package/dist/tools/duplicate-asset.js +184 -0
- package/dist/tools/edit-timeline.js +59 -0
- package/dist/tools/editing-moments.js +105 -0
- package/dist/tools/estimate-cost.js +171 -0
- package/dist/tools/even-split.js +295 -0
- package/dist/tools/export-asset-file.js +22 -0
- package/dist/tools/export-asset.js +140 -0
- package/dist/tools/export-sequence.js +250 -0
- package/dist/tools/export-to-downloads.js +54 -0
- package/dist/tools/export-transcript-srt.js +79 -0
- package/dist/tools/extend-audio.js +100 -0
- package/dist/tools/extract-audio.js +174 -0
- package/dist/tools/extract-frame.js +108 -0
- package/dist/tools/extract-notebook-audio.js +173 -0
- package/dist/tools/extract-notebook-frame.js +160 -0
- package/dist/tools/extract-video-clip.js +101 -0
- package/dist/tools/generate-media.js +157 -0
- package/dist/tools/generation-payload.js +48 -0
- package/dist/tools/get-timeline.js +12 -0
- package/dist/tools/get-watermark-config.js +38 -0
- package/dist/tools/goal.js +644 -0
- package/dist/tools/image-subjects.js +711 -0
- package/dist/tools/index.js +323 -0
- package/dist/tools/inject-first-frame.js +154 -0
- package/dist/tools/invert-image.js +70 -0
- package/dist/tools/isolate-vocals.js +66 -0
- package/dist/tools/list-assets.js +98 -0
- package/dist/tools/list-cell-generations.js +225 -0
- package/dist/tools/list-elevenlabs-voices.js +163 -0
- package/dist/tools/log-wrapper.js +29 -0
- package/dist/tools/make-cover-song.js +95 -0
- package/dist/tools/mark-asset-seen.js +23 -0
- package/dist/tools/media-library.js +245 -0
- package/dist/tools/move-timeline-audio.js +35 -0
- package/dist/tools/music-analysis.js +60 -0
- package/dist/tools/music-draft.js +29 -0
- package/dist/tools/pad-video.js +87 -0
- package/dist/tools/post-record.js +83 -0
- package/dist/tools/post.js +168 -0
- package/dist/tools/prompt-history.js +164 -0
- package/dist/tools/provider-key-status.js +248 -0
- package/dist/tools/qrbtf-render.js +849 -0
- package/dist/tools/read-asset-metadata.js +26 -0
- package/dist/tools/read-file-at-commit.js +29 -0
- package/dist/tools/read-transcript.js +41 -0
- package/dist/tools/reconcile-project-work.js +1452 -0
- package/dist/tools/recover-asset.js +28 -0
- package/dist/tools/remove-sound.js +72 -0
- package/dist/tools/remove-timeline-audio.js +34 -0
- package/dist/tools/remove-timeline-slot.js +40 -0
- package/dist/tools/rename-all.js +434 -0
- package/dist/tools/rename-asset.js +34 -0
- package/dist/tools/render-settings.js +47 -0
- package/dist/tools/render-timeline.js +120 -0
- package/dist/tools/render.js +384 -0
- package/dist/tools/reorder-character-images.js +45 -0
- package/dist/tools/replace-video-audio.js +133 -0
- package/dist/tools/restore-archive-clips.js +209 -0
- package/dist/tools/restore-asset-version.js +37 -0
- package/dist/tools/retry-generation.js +54 -0
- package/dist/tools/reverse-video.js +74 -0
- package/dist/tools/rewrite-prompt.js +388 -0
- package/dist/tools/rewrite-script.js +67 -0
- package/dist/tools/schema-from-apicity.js +66 -0
- package/dist/tools/search-archive.js +150 -0
- package/dist/tools/semantic-search.js +1214 -0
- package/dist/tools/sequence-rhythm.js +43 -0
- package/dist/tools/set-character-metadata.js +84 -0
- package/dist/tools/set-env-secret.js +40 -0
- package/dist/tools/set-osg.js +143 -0
- package/dist/tools/show-folder.js +37 -0
- package/dist/tools/smooth-scene-concat.js +122 -0
- package/dist/tools/splice-video.js +149 -0
- package/dist/tools/split-video.js +202 -0
- package/dist/tools/suggest-cuts.js +45 -0
- package/dist/tools/suggest-sfx.js +43 -0
- package/dist/tools/sync-video-to-audio.js +115 -0
- package/dist/tools/tag-backfill.js +177 -0
- package/dist/tools/timewarp-video.js +85 -0
- package/dist/tools/tool-def.js +1 -0
- package/dist/tools/tool-json-schema.js +116 -0
- package/dist/tools/transcribe.js +90 -0
- package/dist/tools/translate-subtitles.js +59 -0
- package/dist/tools/trim-audio.js +139 -0
- package/dist/tools/trim-video.js +168 -0
- package/dist/tools/update-script.js +25 -0
- package/dist/tools/update-timeline-audio.js +40 -0
- package/dist/tools/upload-file.js +41 -0
- package/dist/tools/verify-x-mentions.js +141 -0
- package/dist/tools/video-editing-command-contract.js +291 -0
- package/dist/tools/video-source-reference.js +30 -0
- package/dist/tools/video-time-input.js +18 -0
- package/dist/tools/videobook-domain.js +2021 -0
- package/dist/tools/wait-for-asset-ready.js +143 -0
- package/dist/tools/workspace-actions.js +54 -0
- package/dist/transcript-projection.js +202 -0
- package/dist/transcript-srt.js +33 -0
- package/dist/upload.js +76 -0
- package/dist/usage-providers.js +25 -0
- package/dist/video-analysis-projection.js +46 -0
- package/dist/video-poster.js +33 -0
- package/dist/video-proxy.js +99 -0
- package/dist/vision-regions.js +111 -0
- package/dist/voice-actions.js +90 -0
- package/dist/voice-audio.js +55 -0
- package/dist/voice-config.js +122 -0
- package/dist/voice-continuation.js +54 -0
- package/dist/voice-contract.js +1 -0
- package/dist/voice-http.js +145 -0
- package/dist/voice-reply-progress.js +55 -0
- package/dist/voice-session.js +992 -0
- package/dist/watermark-badge.js +259 -0
- package/dist/watermark-config.js +111 -0
- package/dist/watermark-handle-position.js +55 -0
- package/dist/watermark-platform.js +15 -0
- package/dist/watermark-timing.js +16 -0
- package/dist/workspace-action-catalog.js +315 -0
- package/dist/workspace-action-contract.js +21 -0
- package/dist/workspace-action-groups.js +98 -0
- package/dist/workspace-action-surfaces.js +7 -0
- package/dist/x/account.js +88 -0
- package/dist/x/authenticated-handle.js +30 -0
- package/dist/x/oauth-flow.js +191 -0
- package/dist/x/post-contract.js +1 -0
- package/dist/x/post-metadata.js +44 -0
- package/dist/x/post-to-x.js +456 -0
- package/dist/x/published-post-cell.js +90 -0
- package/dist/x/x-tokens.js +69 -0
- package/dist/ytdlp-binary.js +65 -0
- package/dist/zod-slots.js +334 -0
- package/package.json +84 -4
- package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
- package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
- package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
- package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
- package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
- package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
- package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
- package/ui/assets/index-DzPAnCUj.css +1 -0
- package/ui/assets/index-kS1905s5.js +37 -0
- package/ui/assets/inspector-MlzQbTVo.js +1 -0
- package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
- package/ui/assets/music-video-Cbe80kmQ.js +1 -0
- package/ui/assets/music-video-DPaiKKEq.css +1 -0
- package/ui/assets/ui-Oirr9VOf.css +1 -0
- package/ui/assets/ui-xo_Y1yQg.js +2 -0
- package/ui/assets/vendor-D2rfiZ5j.css +1 -0
- package/ui/assets/vendor-DfTCWgKm.js +19 -0
- package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
- package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
- package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
- package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
- package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
- package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
- package/ui/assets/videobook-Qlp9HQFu.js +20 -0
- package/ui/favicon.svg +8 -0
- package/ui/index.html +126 -0
|
@@ -0,0 +1,2021 @@
|
|
|
1
|
+
import { migrateLegacyPosts, notebookPosts } from "../posts/post-store.js";
|
|
2
|
+
import { notebookCellOccupiedSlots, notebookOccupiedSlots } from "videobook-engine/notebook-grid";
|
|
3
|
+
import { nextHorizontalSlotFrom, NOTEBOOK_CELL_TYPES, NOTEBOOK_GRID_ROW_COUNT, NOTEBOOK_GRID_COLUMN_COUNT, notebookGridTag, } from "videobook-engine";
|
|
4
|
+
import { slotRight } from "../notebook-placement.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { v7 as uuidv7 } from "uuid";
|
|
7
|
+
import { ensureGenerateAudioOutput, normalizeLegacyExtractCells, } from "../notebook-cell-outputs.js";
|
|
8
|
+
import { mutateNotebook, withNotebookMutation } from "../notebook-mutations.js";
|
|
9
|
+
import { jobIdForKey, queue, stopBookQueue } from "../queue.js";
|
|
10
|
+
import { claimNotebookGenerationPlan, failQueuedNotebookCells, generationPlanClaimOutputCellId, notebookCellDefinitionFingerprint, notebookCellIsPaid, notebookCellIsRunnable, notebookCellMediaType, markNotebookCellQueued, prepareNotebookGeneration, } from "../jobs/run-notebook.js";
|
|
11
|
+
import { buildGenerationInsertPlan, continueQueuedInsert, } from "./command-plan.js";
|
|
12
|
+
import { assessToolCallEstimate, logUnavailableCostEstimate, } from "../jobs/cost-estimate-assessment.js";
|
|
13
|
+
import { estimateCostDef } from "./estimate-cost.js";
|
|
14
|
+
import { pairForCell } from "./list-cell-generations.js";
|
|
15
|
+
import { advertisedAs } from "./tool-json-schema.js";
|
|
16
|
+
import { listNotebookActions, markDescendantsStale, normalizeCellActionFields, normalizeLegacyAnalyzeDocument, selectedCellDetails, validateCellConfiguration, } from "../notebook-action-catalog.js";
|
|
17
|
+
import { hydrateNotebookProviderFailures } from "../notebook-provider-failure.js";
|
|
18
|
+
import { buildSevenStageScaffold } from "../notebook-workflow.js";
|
|
19
|
+
import { previewGeneration } from "../media/preview.js";
|
|
20
|
+
import { generationInputMentions, remapGenerationInputPrompt } from "../generation-input-mentions.js";
|
|
21
|
+
import { applyPreviewPlaceholderUrls } from "../preview-payload.js";
|
|
22
|
+
import { jobProviderTaskStatus } from "../provider-task-status.js";
|
|
23
|
+
import { enqueueNotebookRunPlan, notebookRunPlanRecords, planNotebookRun, planSelectedNotebookSubgraph, } from "../notebook-run-plan.js";
|
|
24
|
+
import { reconcileInitialGenerationPrompt, refreshInitialGenerationPrompts, seedInitialGenerationPrompt, } from "../notebook-initial-generation-prompt.js";
|
|
25
|
+
import { notebookConnectionEdges, notebookConnectionWouldCreateCycle } from "../notebook-graph.js";
|
|
26
|
+
const bookSchema = { book_id: z.string().uuid() };
|
|
27
|
+
const cellTypeSchema = z.enum(NOTEBOOK_CELL_TYPES);
|
|
28
|
+
const GOAL_TILE_CREATE_ERROR = "Goal tiles are created by goal mode. Start one with /goal and a brief.";
|
|
29
|
+
function moveGoalTile(cell, patch) {
|
|
30
|
+
const changed = Object.entries(patch).filter(([key, value]) => key !== "slot" && value !== undefined);
|
|
31
|
+
if (changed.length > 0)
|
|
32
|
+
throw new Error("Edit a goal's plan from its tile in goal mode");
|
|
33
|
+
return patch.slot === undefined ? cell : { ...cell, slot: patch.slot };
|
|
34
|
+
}
|
|
35
|
+
const notebookGridSlotSchema = z.object({
|
|
36
|
+
row: z.number().int().min(0).max(NOTEBOOK_GRID_ROW_COUNT - 1),
|
|
37
|
+
column: z.number().int().min(0).max(NOTEBOOK_GRID_COLUMN_COUNT - 1),
|
|
38
|
+
});
|
|
39
|
+
function notebookConnectionError(notebook, sourceId, targetId, targetInput) {
|
|
40
|
+
const source = notebook.cells.find((cell) => cell.id === sourceId);
|
|
41
|
+
const target = notebook.cells.find((cell) => cell.id === targetId);
|
|
42
|
+
if (!source)
|
|
43
|
+
return `Source cell not found: ${sourceId}`;
|
|
44
|
+
if (!target)
|
|
45
|
+
return `Target cell not found: ${targetId}`;
|
|
46
|
+
if (source.type === "post" || target.type === "post")
|
|
47
|
+
return "Post connections are frozen; use create_post or update_post to choose a source";
|
|
48
|
+
if (notebookConnectionWouldCreateCycle(notebook.edges, sourceId, targetId)) {
|
|
49
|
+
return `Connecting ${notebookGridTag(source.slot)} to ${notebookGridTag(target.slot)} would create a cycle`;
|
|
50
|
+
}
|
|
51
|
+
if (target.type === "image"
|
|
52
|
+
&& targetInput !== "media"
|
|
53
|
+
&& targetInput !== "segment") {
|
|
54
|
+
return "Image cells accept media or segment inputs";
|
|
55
|
+
}
|
|
56
|
+
if (target.type === "analysis") {
|
|
57
|
+
if (targetInput !== "result") {
|
|
58
|
+
return "Analysis result cells accept only the result input";
|
|
59
|
+
}
|
|
60
|
+
if (source.type !== "analyze") {
|
|
61
|
+
return "Analysis result cells require an Analyze source";
|
|
62
|
+
}
|
|
63
|
+
const existing = notebook.edges.find((edge) => edge.source === source.id && edge.targetInput === "result");
|
|
64
|
+
return existing && existing.target !== target.id
|
|
65
|
+
? "Analyze cells can have only one Analysis result"
|
|
66
|
+
: null;
|
|
67
|
+
}
|
|
68
|
+
if (target.type === "extract_audio"
|
|
69
|
+
|| target.inputs?.mediaRole === "extract-audio-output") {
|
|
70
|
+
if (targetInput !== "video" && targetInput !== "segment") {
|
|
71
|
+
return "Extract audio cells accept only the video input";
|
|
72
|
+
}
|
|
73
|
+
return notebookCellMediaType(source) === "video"
|
|
74
|
+
&& source.type !== "split_video"
|
|
75
|
+
? null
|
|
76
|
+
: "Extract audio cells require a video-producing source";
|
|
77
|
+
}
|
|
78
|
+
if (target.type === "split_video") {
|
|
79
|
+
if (targetInput === "analysis") {
|
|
80
|
+
return source.type === "analysis"
|
|
81
|
+
? null
|
|
82
|
+
: "Split video analysis input requires an Analysis result cell";
|
|
83
|
+
}
|
|
84
|
+
if (targetInput !== "video") {
|
|
85
|
+
return "Split video cells accept video and analysis inputs";
|
|
86
|
+
}
|
|
87
|
+
return notebookCellMediaType(source) === "video"
|
|
88
|
+
&& source.type !== "split_video"
|
|
89
|
+
? null
|
|
90
|
+
: "Split video cells require a video-producing source";
|
|
91
|
+
}
|
|
92
|
+
if (target.type === "prompt" && targetInput === "analysis") {
|
|
93
|
+
return source.type === "analysis"
|
|
94
|
+
? null
|
|
95
|
+
: "Prompt analysis input requires an Analysis result cell";
|
|
96
|
+
}
|
|
97
|
+
if (targetInput === "media"
|
|
98
|
+
&& target.type === "audio"
|
|
99
|
+
&& source.type !== "extract_audio"
|
|
100
|
+
&& source.type !== "generate_audio") {
|
|
101
|
+
return "Audio media outputs require an extract audio or generate audio source";
|
|
102
|
+
}
|
|
103
|
+
if (targetInput === "media"
|
|
104
|
+
&& target.type === "video"
|
|
105
|
+
&& source.type !== "split_video"
|
|
106
|
+
&& source.type !== "generate_video") {
|
|
107
|
+
return "Video media outputs require a split video or generate video source";
|
|
108
|
+
}
|
|
109
|
+
if (targetInput === "media"
|
|
110
|
+
&& target.type === "image"
|
|
111
|
+
&& source.type !== "generate_image") {
|
|
112
|
+
return "Image media outputs require a generate image source";
|
|
113
|
+
}
|
|
114
|
+
if (targetInput === "segment"
|
|
115
|
+
&& target.type === "image"
|
|
116
|
+
&& (notebookCellMediaType(source) !== "video"
|
|
117
|
+
|| source.type === "split_video")) {
|
|
118
|
+
return "Image frame outputs require a video-producing source";
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
function prepareNotebookConnection(engine, notebook, sourceId, targetId, targetInput) {
|
|
123
|
+
const edges = notebookConnectionEdges(notebook.cells, notebook.edges, sourceId, targetId);
|
|
124
|
+
const prepared = edges && edges !== notebook.edges
|
|
125
|
+
? { ...notebook, edges: [...edges] }
|
|
126
|
+
: notebook;
|
|
127
|
+
const error = notebookConnectionError(prepared, sourceId, targetId, targetInput);
|
|
128
|
+
if (error)
|
|
129
|
+
throw new Error(error);
|
|
130
|
+
if (prepared !== notebook) {
|
|
131
|
+
const source = notebook.cells.find((cell) => cell.id === sourceId);
|
|
132
|
+
const artifact = engine.artifacts.get(source.outputArtifactId);
|
|
133
|
+
if (!artifact.ok || artifact.value.kind !== "image") {
|
|
134
|
+
throw new Error(`${notebookGridTag(source.slot)} needs an available image before it can be reused`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return prepared;
|
|
138
|
+
}
|
|
139
|
+
function markCellStale(notebook, cellId) {
|
|
140
|
+
const execution = { ...(notebook.execution ?? {}) };
|
|
141
|
+
const current = execution[cellId];
|
|
142
|
+
execution[cellId] = current
|
|
143
|
+
&& typeof current === "object"
|
|
144
|
+
&& !Array.isArray(current)
|
|
145
|
+
? { ...current, stale: true }
|
|
146
|
+
: { stale: true };
|
|
147
|
+
return {
|
|
148
|
+
...notebook,
|
|
149
|
+
execution,
|
|
150
|
+
cells: notebook.cells.map((cell) => cell.id === cellId
|
|
151
|
+
? {
|
|
152
|
+
...cell,
|
|
153
|
+
outputArtifactId: undefined,
|
|
154
|
+
inputs: {
|
|
155
|
+
...(cell.inputs ?? {}),
|
|
156
|
+
state: "pending",
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
: cell),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function markCellAndDescendantsStale(notebook, cellId) {
|
|
163
|
+
return markCellStale(markDescendantsStale(notebook, cellId), cellId);
|
|
164
|
+
}
|
|
165
|
+
const cellReferenceSchema = z.object({
|
|
166
|
+
id: z.string().uuid(),
|
|
167
|
+
kind: z.enum([
|
|
168
|
+
"artifact",
|
|
169
|
+
"stream",
|
|
170
|
+
"source-range",
|
|
171
|
+
"transcript",
|
|
172
|
+
"sequence",
|
|
173
|
+
"cell-output",
|
|
174
|
+
]),
|
|
175
|
+
target_id: z.string().min(1),
|
|
176
|
+
snapshot: z.record(z.string(), z.unknown()),
|
|
177
|
+
ordinal: z.number().int().nonnegative(),
|
|
178
|
+
});
|
|
179
|
+
const rationalSchema = z.object({
|
|
180
|
+
numerator: z.number().int().positive(),
|
|
181
|
+
denominator: z.number().int().positive(),
|
|
182
|
+
});
|
|
183
|
+
const sourceRangeSchema = z.object({
|
|
184
|
+
streamId: z.string().min(1),
|
|
185
|
+
objectHash: z.string().min(1),
|
|
186
|
+
startTick: z.number().int().nonnegative(),
|
|
187
|
+
durationTicks: z.number().int().positive(),
|
|
188
|
+
timeBase: rationalSchema,
|
|
189
|
+
});
|
|
190
|
+
const searchLocationSchema = z.discriminatedUnion("kind", [
|
|
191
|
+
z.object({
|
|
192
|
+
kind: z.literal("still"),
|
|
193
|
+
artifactId: z.string().uuid(),
|
|
194
|
+
sourcePath: z.string().min(1),
|
|
195
|
+
objectHash: z.string().min(1),
|
|
196
|
+
}),
|
|
197
|
+
z.object({
|
|
198
|
+
kind: z.literal("timed"),
|
|
199
|
+
artifactId: z.string().uuid(),
|
|
200
|
+
range: sourceRangeSchema,
|
|
201
|
+
}),
|
|
202
|
+
z.object({
|
|
203
|
+
kind: z.literal("document"),
|
|
204
|
+
artifactId: z.string().uuid(),
|
|
205
|
+
sourcePath: z.string().min(1),
|
|
206
|
+
objectHash: z.string().min(1),
|
|
207
|
+
startUtf8Byte: z.number().int().nonnegative(),
|
|
208
|
+
endUtf8Byte: z.number().int().positive(),
|
|
209
|
+
}),
|
|
210
|
+
]);
|
|
211
|
+
const searchQuerySchema = z.record(z.string(), z.unknown());
|
|
212
|
+
const searchSignalSchema = z.object({
|
|
213
|
+
kind: z.enum(["visual", "speech", "ocr", "audio", "metadata", "exact", "near"]),
|
|
214
|
+
rank: z.number().int().positive(),
|
|
215
|
+
score: z.number().optional(),
|
|
216
|
+
explanation: z.string().optional(),
|
|
217
|
+
});
|
|
218
|
+
const pinnedSearchResultSchema = advertisedAs(z.object({
|
|
219
|
+
id: z.string().uuid(),
|
|
220
|
+
artifact_id: z.string().uuid(),
|
|
221
|
+
object_hash: z.string().min(1),
|
|
222
|
+
location: searchLocationSchema,
|
|
223
|
+
representative_tick: z.number().int().nonnegative().optional(),
|
|
224
|
+
query: searchQuerySchema,
|
|
225
|
+
signals: z.array(searchSignalSchema),
|
|
226
|
+
selected_revision: z.string().min(1),
|
|
227
|
+
ordinal: z.number().int().nonnegative(),
|
|
228
|
+
created_at: z.number().int().nonnegative(),
|
|
229
|
+
}), {
|
|
230
|
+
type: "object",
|
|
231
|
+
description: "A pinned result copied unchanged from get_notebook; pin new ones with pin_search_moment",
|
|
232
|
+
});
|
|
233
|
+
const cellInputSchema = z.object({
|
|
234
|
+
id: z.string().uuid().optional(),
|
|
235
|
+
type: cellTypeSchema,
|
|
236
|
+
label: z.string().optional(),
|
|
237
|
+
slot: notebookGridSlotSchema,
|
|
238
|
+
output_entity_id: z.string().uuid().optional(),
|
|
239
|
+
prompt: z.string().optional(),
|
|
240
|
+
provider: z.string().optional(),
|
|
241
|
+
model: z.string().optional(),
|
|
242
|
+
operation: z.string().optional(),
|
|
243
|
+
tool: z.string().optional(),
|
|
244
|
+
inputs: z.record(z.string(), z.unknown()).optional(),
|
|
245
|
+
output_artifact_id: z.string().uuid().optional(),
|
|
246
|
+
references: z.array(cellReferenceSchema).optional(),
|
|
247
|
+
pinned_results: z.array(pinnedSearchResultSchema).optional(),
|
|
248
|
+
});
|
|
249
|
+
function referenceFromInput(input) {
|
|
250
|
+
return {
|
|
251
|
+
id: input.id,
|
|
252
|
+
kind: input.kind,
|
|
253
|
+
targetId: input.target_id,
|
|
254
|
+
snapshot: input.snapshot,
|
|
255
|
+
ordinal: input.ordinal,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function pinnedResultFromInput(input) {
|
|
259
|
+
return {
|
|
260
|
+
id: input.id,
|
|
261
|
+
artifactId: input.artifact_id,
|
|
262
|
+
objectHash: input.object_hash,
|
|
263
|
+
location: input.location,
|
|
264
|
+
...(input.representative_tick === undefined
|
|
265
|
+
? {}
|
|
266
|
+
: { representativeTick: input.representative_tick }),
|
|
267
|
+
query: input.query,
|
|
268
|
+
signals: input.signals,
|
|
269
|
+
selectedRevision: input.selected_revision,
|
|
270
|
+
ordinal: input.ordinal,
|
|
271
|
+
createdAt: input.created_at,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function jobsByIdOrKey(engine, bookId, ids, keys) {
|
|
275
|
+
const byId = ids.map((id) => ({ id, jobKey: null }));
|
|
276
|
+
const byKey = keys.flatMap((jobKey) => {
|
|
277
|
+
const id = jobIdForKey(jobKey, bookId);
|
|
278
|
+
return id === null ? [] : [{ id, jobKey }];
|
|
279
|
+
});
|
|
280
|
+
return [...byId, ...byKey].flatMap(({ id, jobKey }) => {
|
|
281
|
+
const job = engine.jobs.queue.get(id);
|
|
282
|
+
return job ? [{ job, jobKey }] : [];
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function selectJobs(engine, args) {
|
|
286
|
+
const ids = Array.isArray(args.job_ids) ? args.job_ids.map(Number) : null;
|
|
287
|
+
const keys = Array.isArray(args.job_keys) ? args.job_keys.map(String) : null;
|
|
288
|
+
if (!ids && !keys)
|
|
289
|
+
return engine.jobs.queue.list({ limit: Number(args.limit) || 200 }).map((job) => ({ job, jobKey: null }));
|
|
290
|
+
return jobsByIdOrKey(engine, String(args.book_id), ids ?? [], keys ?? []);
|
|
291
|
+
}
|
|
292
|
+
function json(value) {
|
|
293
|
+
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
294
|
+
}
|
|
295
|
+
function failure(message) {
|
|
296
|
+
return { isError: true, content: [{ type: "text", text: message }] };
|
|
297
|
+
}
|
|
298
|
+
function notebookGenerationPlans(notebook) {
|
|
299
|
+
return (notebook.generationPlans ?? []);
|
|
300
|
+
}
|
|
301
|
+
async function persistNotebookGenerationPlan(engine, notebookId, next) {
|
|
302
|
+
const result = await mutateNotebook(engine, notebookId, "persist_generation_plan", (notebook) => ({
|
|
303
|
+
...notebook,
|
|
304
|
+
generationPlans: [
|
|
305
|
+
...notebookGenerationPlans(notebook)
|
|
306
|
+
.filter((candidate) => candidate.planId !== next.planId),
|
|
307
|
+
next,
|
|
308
|
+
],
|
|
309
|
+
}), {
|
|
310
|
+
planId: next.planId,
|
|
311
|
+
cellId: next.cellId,
|
|
312
|
+
status: next.status,
|
|
313
|
+
outputArtifactId: next.outputArtifactId,
|
|
314
|
+
error: next.error,
|
|
315
|
+
});
|
|
316
|
+
if (!result.ok)
|
|
317
|
+
throw new Error(result.error.message);
|
|
318
|
+
}
|
|
319
|
+
async function notebookGenerationPlan(engine, notebook, cellId, sourceSurface, actor, destination) {
|
|
320
|
+
const cell = notebook.cells.find((candidate) => candidate.id === cellId);
|
|
321
|
+
if (!cell || !notebookCellIsPaid(cell)) {
|
|
322
|
+
throw new Error("Choose a configured generation cell");
|
|
323
|
+
}
|
|
324
|
+
const prepared = await prepareNotebookGeneration(engine, notebook, cell, engine.book.get().bookId);
|
|
325
|
+
const assessment = await assessToolCallEstimate(engine, prepared.tool, prepared.input);
|
|
326
|
+
if (!assessment.available) {
|
|
327
|
+
await logUnavailableCostEstimate({
|
|
328
|
+
assessment,
|
|
329
|
+
engine,
|
|
330
|
+
correlation: {
|
|
331
|
+
bookId: engine.book.get().bookId,
|
|
332
|
+
notebookId: notebook.id,
|
|
333
|
+
cellId: cell.id,
|
|
334
|
+
tool: prepared.tool,
|
|
335
|
+
provider: prepared.provider,
|
|
336
|
+
model: prepared.model,
|
|
337
|
+
stage: "plan-generation",
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
const estimatedCost = assessment.available ? assessment.usd : undefined;
|
|
342
|
+
const estimateUnavailable = !assessment.available;
|
|
343
|
+
const cellDefinitionFingerprint = notebookCellDefinitionFingerprint(notebook, cell);
|
|
344
|
+
if (cell.operation === "rewrite_prompt") {
|
|
345
|
+
const planId = uuidv7();
|
|
346
|
+
const enriched = {
|
|
347
|
+
planVersion: 1,
|
|
348
|
+
planId,
|
|
349
|
+
sourceSurface,
|
|
350
|
+
actor,
|
|
351
|
+
bookId: engine.book.get().bookId,
|
|
352
|
+
summary: `Rewrite ${notebookGridTag(cell.slot)} for Seedance 2`,
|
|
353
|
+
normalizedReferences: [{
|
|
354
|
+
kind: "notebook",
|
|
355
|
+
targetId: notebook.id,
|
|
356
|
+
revision: engine.head,
|
|
357
|
+
}],
|
|
358
|
+
stages: [{
|
|
359
|
+
stageId: `${planId}:queued:0`,
|
|
360
|
+
kind: "queued",
|
|
361
|
+
summary: `Run ${prepared.tool}`,
|
|
362
|
+
jobType: "generation",
|
|
363
|
+
tool: "run_notebook_cell",
|
|
364
|
+
input: {
|
|
365
|
+
notebook_id: notebook.id,
|
|
366
|
+
cell_id: cell.id,
|
|
367
|
+
generation_tool: prepared.tool,
|
|
368
|
+
generation_input: prepared.input,
|
|
369
|
+
},
|
|
370
|
+
provider: prepared.provider,
|
|
371
|
+
model: prepared.model,
|
|
372
|
+
...(estimateUnavailable
|
|
373
|
+
? { estimateUnavailable: true }
|
|
374
|
+
: { estimatedCostUsd: estimatedCost }),
|
|
375
|
+
insertsOutput: false,
|
|
376
|
+
}],
|
|
377
|
+
confirmation: {
|
|
378
|
+
required: true,
|
|
379
|
+
policy: "always",
|
|
380
|
+
reason: estimateUnavailable
|
|
381
|
+
? "Prompt rewrite pricing is unavailable and requires explicit confirmation"
|
|
382
|
+
: `This prompt rewrite can incur up to $${Number(estimatedCost).toFixed(4)}`,
|
|
383
|
+
},
|
|
384
|
+
cellDefinitionFingerprint,
|
|
385
|
+
};
|
|
386
|
+
const now = new Date().toISOString();
|
|
387
|
+
await persistNotebookGenerationPlan(engine, notebook.id, {
|
|
388
|
+
planId,
|
|
389
|
+
cellId: cell.id,
|
|
390
|
+
status: "planned",
|
|
391
|
+
plan: enriched,
|
|
392
|
+
createdAt: now,
|
|
393
|
+
updatedAt: now,
|
|
394
|
+
});
|
|
395
|
+
return enriched;
|
|
396
|
+
}
|
|
397
|
+
const sequence = engine.sequences.getPrimary();
|
|
398
|
+
const outputTrackKind = cell.type === "generate_audio"
|
|
399
|
+
? "audio"
|
|
400
|
+
: "video";
|
|
401
|
+
const track = destination.trackId
|
|
402
|
+
? sequence.tracks.find((candidate) => candidate.trackId === destination.trackId)
|
|
403
|
+
: sequence.tracks.find((candidate) => candidate.kind === outputTrackKind
|
|
404
|
+
&& candidate.enabled
|
|
405
|
+
&& !candidate.locked);
|
|
406
|
+
if (!track || track.kind !== outputTrackKind) {
|
|
407
|
+
throw new Error(`No editable ${outputTrackKind} track is available for the generated output`);
|
|
408
|
+
}
|
|
409
|
+
const plan = buildGenerationInsertPlan(engine, {
|
|
410
|
+
tool: prepared.tool,
|
|
411
|
+
input: {
|
|
412
|
+
notebook_id: notebook.id,
|
|
413
|
+
cell_id: cell.id,
|
|
414
|
+
generation_tool: prepared.tool,
|
|
415
|
+
generation_input: prepared.input,
|
|
416
|
+
},
|
|
417
|
+
provider: prepared.provider,
|
|
418
|
+
model: prepared.model,
|
|
419
|
+
...(estimateUnavailable
|
|
420
|
+
? { estimate_unavailable: true }
|
|
421
|
+
: { estimated_cost_usd: estimatedCost }),
|
|
422
|
+
track_id: track.trackId,
|
|
423
|
+
timeline_start_frame: destination.timelineStartFrame,
|
|
424
|
+
...(destination.durationFrames === undefined
|
|
425
|
+
? {}
|
|
426
|
+
: { duration_frames: destination.durationFrames }),
|
|
427
|
+
}, sourceSurface, actor);
|
|
428
|
+
const stages = Array.isArray(plan.stages)
|
|
429
|
+
? plan.stages.map((stage) => {
|
|
430
|
+
if (!stage || typeof stage !== "object" || Array.isArray(stage))
|
|
431
|
+
return stage;
|
|
432
|
+
return {
|
|
433
|
+
...stage,
|
|
434
|
+
tool: "run_notebook_cell",
|
|
435
|
+
};
|
|
436
|
+
})
|
|
437
|
+
: [];
|
|
438
|
+
const enriched = {
|
|
439
|
+
...plan,
|
|
440
|
+
cellDefinitionFingerprint,
|
|
441
|
+
normalizedReferences: [
|
|
442
|
+
...(Array.isArray(plan.normalizedReferences)
|
|
443
|
+
? plan.normalizedReferences
|
|
444
|
+
: []),
|
|
445
|
+
{
|
|
446
|
+
kind: "notebook",
|
|
447
|
+
targetId: notebook.id,
|
|
448
|
+
revision: engine.head,
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
stages,
|
|
452
|
+
};
|
|
453
|
+
const now = new Date().toISOString();
|
|
454
|
+
await persistNotebookGenerationPlan(engine, notebook.id, {
|
|
455
|
+
planId: String(enriched.planId),
|
|
456
|
+
cellId: cell.id,
|
|
457
|
+
status: "planned",
|
|
458
|
+
plan: enriched,
|
|
459
|
+
createdAt: now,
|
|
460
|
+
updatedAt: now,
|
|
461
|
+
});
|
|
462
|
+
return enriched;
|
|
463
|
+
}
|
|
464
|
+
function cellFromInput(input) {
|
|
465
|
+
return {
|
|
466
|
+
...(input.id ? { id: input.id } : {}),
|
|
467
|
+
type: input.type,
|
|
468
|
+
...(input.label === undefined ? {} : { label: input.label }),
|
|
469
|
+
slot: input.slot,
|
|
470
|
+
...(input.output_entity_id
|
|
471
|
+
? { outputEntityId: input.output_entity_id }
|
|
472
|
+
: {}),
|
|
473
|
+
...(input.prompt === undefined ? {} : { prompt: input.prompt }),
|
|
474
|
+
...(input.provider === undefined ? {} : { provider: input.provider }),
|
|
475
|
+
...(input.model === undefined ? {} : { model: input.model }),
|
|
476
|
+
...(input.operation === undefined ? {} : { operation: input.operation }),
|
|
477
|
+
...(input.tool === undefined ? {} : { tool: input.tool }),
|
|
478
|
+
...(input.inputs === undefined ? {} : { inputs: input.inputs }),
|
|
479
|
+
...(input.output_artifact_id ? { outputArtifactId: input.output_artifact_id } : {}),
|
|
480
|
+
...(input.references === undefined
|
|
481
|
+
? {}
|
|
482
|
+
: { references: input.references.map(referenceFromInput) }),
|
|
483
|
+
...(input.pinned_results === undefined
|
|
484
|
+
? {}
|
|
485
|
+
: { pinnedResults: input.pinned_results.map(pinnedResultFromInput) }),
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function cellPayload(cell) {
|
|
489
|
+
return {
|
|
490
|
+
id: cell.id,
|
|
491
|
+
type: cell.type,
|
|
492
|
+
...(cell.label === undefined ? {} : { label: cell.label }),
|
|
493
|
+
address: notebookGridTag(cell.slot),
|
|
494
|
+
slot: cell.slot,
|
|
495
|
+
...(cell.outputEntityId
|
|
496
|
+
? { output_entity_id: cell.outputEntityId }
|
|
497
|
+
: {}),
|
|
498
|
+
...(cell.prompt === undefined ? {} : { prompt: cell.prompt }),
|
|
499
|
+
...(cell.provider === undefined ? {} : { provider: cell.provider }),
|
|
500
|
+
...(cell.model === undefined ? {} : { model: cell.model }),
|
|
501
|
+
...(cell.operation === undefined ? {} : { operation: cell.operation }),
|
|
502
|
+
...(cell.tool === undefined ? {} : { tool: cell.tool }),
|
|
503
|
+
...(cell.inputs === undefined ? {} : { inputs: cell.inputs }),
|
|
504
|
+
...(cell.outputArtifactId ? { output_artifact_id: cell.outputArtifactId } : {}),
|
|
505
|
+
...(cell.references === undefined
|
|
506
|
+
? {}
|
|
507
|
+
: {
|
|
508
|
+
references: cell.references.map((reference) => ({
|
|
509
|
+
id: reference.id,
|
|
510
|
+
kind: reference.kind,
|
|
511
|
+
target_id: reference.targetId,
|
|
512
|
+
snapshot: reference.snapshot,
|
|
513
|
+
ordinal: reference.ordinal,
|
|
514
|
+
})),
|
|
515
|
+
}),
|
|
516
|
+
...(cell.pinnedResults === undefined
|
|
517
|
+
? {}
|
|
518
|
+
: {
|
|
519
|
+
pinned_results: cell.pinnedResults.map((result) => ({
|
|
520
|
+
id: result.id,
|
|
521
|
+
artifact_id: result.artifactId,
|
|
522
|
+
object_hash: result.objectHash,
|
|
523
|
+
location: result.location,
|
|
524
|
+
...(result.representativeTick === undefined
|
|
525
|
+
? {}
|
|
526
|
+
: { representative_tick: result.representativeTick }),
|
|
527
|
+
query: result.query,
|
|
528
|
+
signals: result.signals,
|
|
529
|
+
selected_revision: result.selectedRevision,
|
|
530
|
+
ordinal: result.ordinal,
|
|
531
|
+
created_at: result.createdAt,
|
|
532
|
+
})),
|
|
533
|
+
}),
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function notebookPayload(engine, notebook) {
|
|
537
|
+
const normalized = hydrateNotebookProviderFailures(engine, normalizeLegacyAnalyzeDocument(normalizeLegacyExtractCells(notebook)));
|
|
538
|
+
return {
|
|
539
|
+
id: normalized.id,
|
|
540
|
+
name: normalized.name,
|
|
541
|
+
revision: engine.head,
|
|
542
|
+
created_at: normalized.createdAt,
|
|
543
|
+
...(normalized.description === undefined
|
|
544
|
+
? {}
|
|
545
|
+
: { description: normalized.description }),
|
|
546
|
+
...(normalized.lifecycleState === undefined
|
|
547
|
+
? {}
|
|
548
|
+
: { lifecycleState: normalized.lifecycleState }),
|
|
549
|
+
...(normalized.workflowVersion === undefined
|
|
550
|
+
? {}
|
|
551
|
+
: { workflowVersion: normalized.workflowVersion }),
|
|
552
|
+
...(normalized.analysisRevision === undefined
|
|
553
|
+
? {}
|
|
554
|
+
: { analysisRevision: normalized.analysisRevision }),
|
|
555
|
+
...(normalized.fixture === undefined ? {} : { fixture: normalized.fixture }),
|
|
556
|
+
execution: normalized.execution ?? {},
|
|
557
|
+
generationPlans: normalized.generationPlans ?? [],
|
|
558
|
+
notebookRunPlans: normalized.notebookRunPlans ?? [],
|
|
559
|
+
cells: normalized.cells.map(cellPayload),
|
|
560
|
+
posts: notebookPosts(engine, normalized),
|
|
561
|
+
edges: normalized.edges,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
const entityKindSchema = z.enum(["prompt", "scene", "character"]);
|
|
565
|
+
function entityDefs(engine) {
|
|
566
|
+
return [
|
|
567
|
+
{
|
|
568
|
+
name: "create_entity",
|
|
569
|
+
description: "Create a reusable prompt, scene or character",
|
|
570
|
+
scope: "book",
|
|
571
|
+
schema: {
|
|
572
|
+
...bookSchema,
|
|
573
|
+
kind: entityKindSchema,
|
|
574
|
+
name: z.string().min(1),
|
|
575
|
+
description: z.string().optional(),
|
|
576
|
+
prompt: z.string().optional(),
|
|
577
|
+
data: z.record(z.string(), z.unknown()).optional(),
|
|
578
|
+
},
|
|
579
|
+
handler: async (args) => {
|
|
580
|
+
const result = await engine.entities.create(args.kind, String(args.name), {
|
|
581
|
+
...(typeof args.description === "string" ? { description: args.description } : {}),
|
|
582
|
+
...(typeof args.prompt === "string" ? { prompt: args.prompt } : {}),
|
|
583
|
+
data: args.data,
|
|
584
|
+
});
|
|
585
|
+
return result.ok ? json(result.value) : failure(result.error.message);
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
name: "list_entities",
|
|
590
|
+
description: "List reusable prompts, scenes and characters, optionally of one kind",
|
|
591
|
+
scope: "book",
|
|
592
|
+
schema: { ...bookSchema, kind: entityKindSchema.optional() },
|
|
593
|
+
handler: async (args) => json(engine.entities.list(args.kind)),
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
name: "get_entity",
|
|
597
|
+
description: "Read a reusable prompt, scene or character",
|
|
598
|
+
scope: "book",
|
|
599
|
+
schema: { ...bookSchema, entity_id: z.string().uuid() },
|
|
600
|
+
handler: async (args) => {
|
|
601
|
+
const result = engine.entities.read(String(args.entity_id));
|
|
602
|
+
return result.ok ? json(result.value) : failure(result.error.message);
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
name: "update_entity",
|
|
607
|
+
description: "Replace a reusable prompt, scene or character with an edited copy from get_entity",
|
|
608
|
+
scope: "book",
|
|
609
|
+
schema: { ...bookSchema, entity: z.record(z.string(), z.unknown()) },
|
|
610
|
+
handler: async (args) => {
|
|
611
|
+
const result = await engine.entities.write(args.entity);
|
|
612
|
+
return result.ok ? json(result.value) : failure(result.error.message);
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
name: "delete_entity",
|
|
617
|
+
description: "Delete a reusable prompt, scene or character",
|
|
618
|
+
scope: "book",
|
|
619
|
+
schema: { ...bookSchema, entity_id: z.string().uuid() },
|
|
620
|
+
handler: async (args) => {
|
|
621
|
+
const result = await engine.entities.delete(String(args.entity_id));
|
|
622
|
+
return result.ok ? json(result.value) : failure(result.error.message);
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
];
|
|
626
|
+
}
|
|
627
|
+
function refreshGenerationInputPrompt(previous, next, cellId) {
|
|
628
|
+
const seeded = seedInitialGenerationPrompt(next, cellId);
|
|
629
|
+
const before = previous.cells.find((cell) => cell.id === cellId);
|
|
630
|
+
const after = seeded.cells.find((cell) => cell.id === cellId);
|
|
631
|
+
if (!after?.prompt || before?.inputs?.generationMode !== "r2v" || after.prompt !== before.prompt)
|
|
632
|
+
return seeded;
|
|
633
|
+
const prompt = remapGenerationInputPrompt(after.prompt, generationInputMentions(cellId, previous.cells, previous.edges), generationInputMentions(cellId, seeded.cells, seeded.edges));
|
|
634
|
+
return prompt === after.prompt ? seeded : {
|
|
635
|
+
...seeded,
|
|
636
|
+
cells: seeded.cells.map((cell) => cell.id === cellId ? { ...cell, prompt } : cell),
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
export function videobookDomainDefs(engine, books) {
|
|
640
|
+
return [
|
|
641
|
+
{
|
|
642
|
+
name: "list_books",
|
|
643
|
+
description: "List all Videobook books",
|
|
644
|
+
scope: "global",
|
|
645
|
+
schema: { sort: z.enum(["newest", "oldest"]).optional() },
|
|
646
|
+
handler: async (args) => {
|
|
647
|
+
await books.ready();
|
|
648
|
+
return json(books.list({
|
|
649
|
+
...(args.sort === "newest" || args.sort === "oldest" ? { sort: args.sort } : {}),
|
|
650
|
+
}));
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: "create_book",
|
|
655
|
+
description: "Create a book with a Main notebook. Project names are normalized to lowercase slugs with hyphens.",
|
|
656
|
+
scope: "global",
|
|
657
|
+
schema: { name: z.string().min(1).describe("Project slug, such as my-project. Input is normalized to lowercase with hyphens.") },
|
|
658
|
+
handler: async (args) => {
|
|
659
|
+
const result = await books.create(String(args.name));
|
|
660
|
+
return result.ok ? json(result.value) : failure(result.error.message);
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
name: "rename_book",
|
|
665
|
+
description: "Rename a book. Project names are normalized to lowercase slugs with hyphens.",
|
|
666
|
+
scope: "global",
|
|
667
|
+
schema: { ...bookSchema, name: z.string().min(1).describe("Project slug, such as my-project. Input is normalized to lowercase with hyphens.") },
|
|
668
|
+
handler: async (args) => {
|
|
669
|
+
const result = await books.rename(String(args.book_id), String(args.name));
|
|
670
|
+
return result.ok ? json(result.value) : failure(result.error.message);
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
name: "delete_book",
|
|
675
|
+
description: "Permanently delete a book after typed name confirmation",
|
|
676
|
+
scope: "global",
|
|
677
|
+
schema: { ...bookSchema, confirm_name: z.string().min(1) },
|
|
678
|
+
handler: async (args) => {
|
|
679
|
+
const result = await books.delete(String(args.book_id), {
|
|
680
|
+
confirmName: String(args.confirm_name),
|
|
681
|
+
beforeDelete: async (book) => stopBookQueue(book.bookId),
|
|
682
|
+
});
|
|
683
|
+
return result.ok ? json(result.value) : failure(result.error.message);
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
name: "get_book",
|
|
688
|
+
description: "Read the current book identity",
|
|
689
|
+
scope: "book",
|
|
690
|
+
schema: bookSchema,
|
|
691
|
+
handler: async () => json(engine.book.get()),
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
name: "check_duplicate_bytes",
|
|
695
|
+
description: "Find existing book files with the same immutable content hash before import",
|
|
696
|
+
scope: "book",
|
|
697
|
+
schema: {
|
|
698
|
+
...bookSchema,
|
|
699
|
+
object_hash: z.string().regex(/^[0-9a-f]{64}$/i),
|
|
700
|
+
},
|
|
701
|
+
handler: async (args) => {
|
|
702
|
+
const objectHash = String(args.object_hash).toLowerCase();
|
|
703
|
+
const matches = [];
|
|
704
|
+
for (const artifact of engine.artifacts.list()) {
|
|
705
|
+
const manifest = await engine.files.manifest(artifact.artifactId);
|
|
706
|
+
if (!manifest.ok)
|
|
707
|
+
continue;
|
|
708
|
+
for (const file of manifest.value.files) {
|
|
709
|
+
if (file.objectHash.toLowerCase() !== objectHash)
|
|
710
|
+
continue;
|
|
711
|
+
matches.push({
|
|
712
|
+
artifact_id: artifact.artifactId,
|
|
713
|
+
...(artifact.label === undefined ? {} : { label: artifact.label }),
|
|
714
|
+
kind: artifact.kind,
|
|
715
|
+
filename: file.name,
|
|
716
|
+
object_hash: file.objectHash,
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return json({ object_hash: objectHash, matches });
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
name: "list_notebooks",
|
|
725
|
+
description: "List canonical notebook documents",
|
|
726
|
+
scope: "book",
|
|
727
|
+
schema: bookSchema,
|
|
728
|
+
handler: async () => json(engine.notebooks.list().map((notebook) => notebookPayload(engine, notebook))),
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
name: "get_primary_sequence",
|
|
732
|
+
description: "Read the canonical primary sequence",
|
|
733
|
+
scope: "book",
|
|
734
|
+
schema: bookSchema,
|
|
735
|
+
handler: async () => json(engine.sequences.getPrimary()),
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
name: "add_sequence_track",
|
|
739
|
+
description: "Append a canonical video, audio, or caption track",
|
|
740
|
+
scope: "book",
|
|
741
|
+
schema: {
|
|
742
|
+
...bookSchema,
|
|
743
|
+
kind: z.enum(["video", "audio", "caption"]),
|
|
744
|
+
name: z.string().min(1).optional(),
|
|
745
|
+
},
|
|
746
|
+
handler: async (args) => {
|
|
747
|
+
const sequence = engine.sequences.getPrimary();
|
|
748
|
+
const result = await engine.sequences.addTrack(sequence.sequenceId, {
|
|
749
|
+
kind: args.kind,
|
|
750
|
+
...(typeof args.name === "string" ? { name: args.name } : {}),
|
|
751
|
+
});
|
|
752
|
+
return result.ok ? json(result.value) : failure(result.error.message);
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
name: "remove_sequence_track",
|
|
757
|
+
description: "Remove an empty canonical track while preserving required track kinds",
|
|
758
|
+
scope: "book",
|
|
759
|
+
schema: {
|
|
760
|
+
...bookSchema,
|
|
761
|
+
track_id: z.string().uuid(),
|
|
762
|
+
},
|
|
763
|
+
handler: async (args) => {
|
|
764
|
+
const result = await engine.sequences.removeTrack(String(args.track_id));
|
|
765
|
+
return result.ok ? json(result.value) : failure(result.error.message);
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
name: "plan_notebook_generation",
|
|
770
|
+
description: "Plan a notebook prompt, image, or video generation with provider, model, cost, and revision-pinned execution",
|
|
771
|
+
scope: "book",
|
|
772
|
+
schema: {
|
|
773
|
+
...bookSchema,
|
|
774
|
+
notebook_id: z.string().uuid(),
|
|
775
|
+
cell_id: z.string().uuid(),
|
|
776
|
+
source_surface: z.enum(["ui", "slash", "chat"]),
|
|
777
|
+
actor: z.string().min(1),
|
|
778
|
+
track_id: z.string().uuid().optional(),
|
|
779
|
+
timeline_start_frame: z.number().int().nonnegative(),
|
|
780
|
+
duration_frames: z.number().int().positive().optional(),
|
|
781
|
+
},
|
|
782
|
+
handler: async (args) => {
|
|
783
|
+
try {
|
|
784
|
+
const notebook = engine.notebooks.read(String(args.notebook_id));
|
|
785
|
+
if (!notebook.ok)
|
|
786
|
+
return failure(notebook.error.message);
|
|
787
|
+
return json(await notebookGenerationPlan(engine, notebook.value, String(args.cell_id), args.source_surface, String(args.actor), {
|
|
788
|
+
...(typeof args.track_id === "string"
|
|
789
|
+
? { trackId: args.track_id }
|
|
790
|
+
: {}),
|
|
791
|
+
timelineStartFrame: Number(args.timeline_start_frame),
|
|
792
|
+
...(typeof args.duration_frames === "number"
|
|
793
|
+
? { durationFrames: args.duration_frames }
|
|
794
|
+
: {}),
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
catch (error) {
|
|
798
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
name: "list_notebook_generation_plans",
|
|
804
|
+
description: "List durable generation plans and their retryable lifecycle state",
|
|
805
|
+
scope: "book",
|
|
806
|
+
schema: {
|
|
807
|
+
...bookSchema,
|
|
808
|
+
notebook_id: z.string().uuid(),
|
|
809
|
+
},
|
|
810
|
+
handler: async (args) => {
|
|
811
|
+
const notebook = engine.notebooks.read(String(args.notebook_id));
|
|
812
|
+
return notebook.ok
|
|
813
|
+
? json(notebookGenerationPlans(notebook.value))
|
|
814
|
+
: failure(notebook.error.message);
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
name: "plan_notebook_run",
|
|
819
|
+
description: "Plan every missing or stale notebook cell with aggregate provider cost and media disclosure",
|
|
820
|
+
scope: "book",
|
|
821
|
+
schema: {
|
|
822
|
+
...bookSchema,
|
|
823
|
+
notebook_id: z.string().uuid(),
|
|
824
|
+
source_surface: z.enum(["ui", "slash", "chat"]),
|
|
825
|
+
actor: z.string().min(1),
|
|
826
|
+
force: z.boolean().optional(),
|
|
827
|
+
},
|
|
828
|
+
handler: async (args) => {
|
|
829
|
+
try {
|
|
830
|
+
const notebook = engine.notebooks.read(String(args.notebook_id));
|
|
831
|
+
if (!notebook.ok)
|
|
832
|
+
return failure(notebook.error.message);
|
|
833
|
+
return json(await planNotebookRun(engine, notebook.value, args.source_surface, String(args.actor), { force: args.force === true }));
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
name: "plan_notebook_subgraph",
|
|
842
|
+
description: "Plan the selected cell plus required stale ancestors, with paid-stage fingerprints, cost, and confirmation",
|
|
843
|
+
scope: "book",
|
|
844
|
+
schema: {
|
|
845
|
+
...bookSchema,
|
|
846
|
+
notebook_id: z.string().uuid(),
|
|
847
|
+
cell_id: z.string().uuid(),
|
|
848
|
+
source_surface: z.enum(["ui", "slash", "chat"]),
|
|
849
|
+
actor: z.string().min(1),
|
|
850
|
+
},
|
|
851
|
+
handler: async (args) => {
|
|
852
|
+
try {
|
|
853
|
+
const notebook = engine.notebooks.read(String(args.notebook_id));
|
|
854
|
+
if (!notebook.ok)
|
|
855
|
+
return failure(notebook.error.message);
|
|
856
|
+
return json(await planSelectedNotebookSubgraph(engine, notebook.value, String(args.cell_id), args.source_surface, String(args.actor)));
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
name: "list_notebook_run_plans",
|
|
865
|
+
description: "List durable aggregate workbook run plans and their lifecycle state",
|
|
866
|
+
scope: "book",
|
|
867
|
+
schema: {
|
|
868
|
+
...bookSchema,
|
|
869
|
+
notebook_id: z.string().uuid(),
|
|
870
|
+
},
|
|
871
|
+
handler: async (args) => {
|
|
872
|
+
const notebook = engine.notebooks.read(String(args.notebook_id));
|
|
873
|
+
return notebook.ok
|
|
874
|
+
? json(notebookRunPlanRecords(notebook.value))
|
|
875
|
+
: failure(notebook.error.message);
|
|
876
|
+
},
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
name: "enqueue_notebook_run",
|
|
880
|
+
description: "Revalidate and enqueue a persisted aggregate workbook plan",
|
|
881
|
+
scope: "book",
|
|
882
|
+
schema: {
|
|
883
|
+
...bookSchema,
|
|
884
|
+
notebook_id: z.string().uuid(),
|
|
885
|
+
plan_id: z.string().uuid(),
|
|
886
|
+
actor: z.string().min(1),
|
|
887
|
+
confirm_unpriced: z.boolean(),
|
|
888
|
+
},
|
|
889
|
+
handler: async (args) => {
|
|
890
|
+
try {
|
|
891
|
+
return json(await enqueueNotebookRunPlan(engine, String(args.notebook_id), String(args.plan_id), {
|
|
892
|
+
actor: String(args.actor),
|
|
893
|
+
confirmUnpriced: args.confirm_unpriced === true,
|
|
894
|
+
}));
|
|
895
|
+
}
|
|
896
|
+
catch (error) {
|
|
897
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
name: "revise_notebook_generation_plan",
|
|
903
|
+
description: "Retry, switch the allowed generation model, or omit a preserved notebook generation plan",
|
|
904
|
+
scope: "book",
|
|
905
|
+
schema: {
|
|
906
|
+
...bookSchema,
|
|
907
|
+
notebook_id: z.string().uuid(),
|
|
908
|
+
plan_id: z.string().uuid(),
|
|
909
|
+
action: z.enum(["retry", "switch", "omit"]),
|
|
910
|
+
model: z.string().min(1).optional(),
|
|
911
|
+
source_surface: z.enum(["ui", "slash", "chat"]),
|
|
912
|
+
actor: z.string().min(1),
|
|
913
|
+
},
|
|
914
|
+
handler: async (args) => {
|
|
915
|
+
try {
|
|
916
|
+
const notebookResult = engine.notebooks.read(String(args.notebook_id));
|
|
917
|
+
if (!notebookResult.ok)
|
|
918
|
+
return failure(notebookResult.error.message);
|
|
919
|
+
let notebook = notebookResult.value;
|
|
920
|
+
const existing = notebookGenerationPlans(notebook).find((candidate) => candidate.planId === String(args.plan_id));
|
|
921
|
+
if (!existing)
|
|
922
|
+
return failure("Generation plan not found");
|
|
923
|
+
if (args.action === "omit") {
|
|
924
|
+
await persistNotebookGenerationPlan(engine, notebook.id, {
|
|
925
|
+
...existing,
|
|
926
|
+
status: "omitted",
|
|
927
|
+
updatedAt: new Date().toISOString(),
|
|
928
|
+
});
|
|
929
|
+
return json({
|
|
930
|
+
kind: "generation-omitted",
|
|
931
|
+
planId: existing.planId,
|
|
932
|
+
outputArtifactId: existing.outputArtifactId,
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
if (args.action === "switch") {
|
|
936
|
+
if (typeof args.model !== "string") {
|
|
937
|
+
return failure("Switching requires an allowed generation model");
|
|
938
|
+
}
|
|
939
|
+
const nextModel = String(args.model);
|
|
940
|
+
const updated = await mutateNotebook(engine, notebook.id, "switch_generation_model", (document) => ({
|
|
941
|
+
...document,
|
|
942
|
+
cells: document.cells.map((cell) => cell.id === existing.cellId
|
|
943
|
+
? {
|
|
944
|
+
...cell,
|
|
945
|
+
model: nextModel,
|
|
946
|
+
// Model switch can pass a generation tool name; keep tool in sync.
|
|
947
|
+
...(nextModel.startsWith("generate_")
|
|
948
|
+
|| nextModel.startsWith("extend_")
|
|
949
|
+
|| nextModel.startsWith("upscale_")
|
|
950
|
+
? { tool: nextModel }
|
|
951
|
+
: {}),
|
|
952
|
+
}
|
|
953
|
+
: cell),
|
|
954
|
+
}), {
|
|
955
|
+
sourcePlanId: existing.planId,
|
|
956
|
+
cellId: existing.cellId,
|
|
957
|
+
model: args.model,
|
|
958
|
+
});
|
|
959
|
+
if (!updated.ok)
|
|
960
|
+
return failure(updated.error.message);
|
|
961
|
+
const refreshed = engine.notebooks.read(notebook.id);
|
|
962
|
+
if (!refreshed.ok)
|
|
963
|
+
return failure(refreshed.error.message);
|
|
964
|
+
notebook = refreshed.value;
|
|
965
|
+
}
|
|
966
|
+
const stages = Array.isArray(existing.plan.stages)
|
|
967
|
+
? existing.plan.stages
|
|
968
|
+
: [];
|
|
969
|
+
const queued = stages.find((stage) => stage
|
|
970
|
+
&& typeof stage === "object"
|
|
971
|
+
&& !Array.isArray(stage)
|
|
972
|
+
&& stage.kind === "queued");
|
|
973
|
+
const continuation = queued?.continuation
|
|
974
|
+
&& typeof queued.continuation === "object"
|
|
975
|
+
&& !Array.isArray(queued.continuation)
|
|
976
|
+
? queued.continuation
|
|
977
|
+
: null;
|
|
978
|
+
const destination = continuation?.destination
|
|
979
|
+
&& typeof continuation.destination === "object"
|
|
980
|
+
&& !Array.isArray(continuation.destination)
|
|
981
|
+
? continuation.destination
|
|
982
|
+
: null;
|
|
983
|
+
if (!destination
|
|
984
|
+
|| typeof destination.timelineStartFrame !== "number") {
|
|
985
|
+
return failure("The preserved generation destination is invalid");
|
|
986
|
+
}
|
|
987
|
+
const next = await notebookGenerationPlan(engine, notebook, existing.cellId, args.source_surface, String(args.actor), {
|
|
988
|
+
...(typeof destination.trackId === "string"
|
|
989
|
+
? { trackId: destination.trackId }
|
|
990
|
+
: {}),
|
|
991
|
+
timelineStartFrame: destination.timelineStartFrame,
|
|
992
|
+
...(typeof destination.durationFrames === "number"
|
|
993
|
+
? { durationFrames: destination.durationFrames }
|
|
994
|
+
: {}),
|
|
995
|
+
});
|
|
996
|
+
return json({
|
|
997
|
+
...next,
|
|
998
|
+
parentActionId: existing.planId,
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
catch (error) {
|
|
1002
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
name: "continue_notebook_generation",
|
|
1008
|
+
description: "Create a reviewable insertion plan for a completed generation or report a stale destination",
|
|
1009
|
+
scope: "book",
|
|
1010
|
+
schema: {
|
|
1011
|
+
...bookSchema,
|
|
1012
|
+
notebook_id: z.string().uuid(),
|
|
1013
|
+
plan_id: z.string().uuid(),
|
|
1014
|
+
actor: z.string().min(1),
|
|
1015
|
+
},
|
|
1016
|
+
handler: async (args) => {
|
|
1017
|
+
try {
|
|
1018
|
+
const notebook = engine.notebooks.read(String(args.notebook_id));
|
|
1019
|
+
if (!notebook.ok)
|
|
1020
|
+
return failure(notebook.error.message);
|
|
1021
|
+
const existing = notebookGenerationPlans(notebook.value).find((candidate) => candidate.planId === String(args.plan_id));
|
|
1022
|
+
if (!existing)
|
|
1023
|
+
return failure("Generation plan not found");
|
|
1024
|
+
if (!existing.outputArtifactId) {
|
|
1025
|
+
return failure("Generation output is not ready");
|
|
1026
|
+
}
|
|
1027
|
+
const continuation = await continueQueuedInsert(engine, existing.plan, existing.outputArtifactId, String(args.actor));
|
|
1028
|
+
if (continuation.kind !== "stale-continuation") {
|
|
1029
|
+
await persistNotebookGenerationPlan(engine, notebook.value.id, {
|
|
1030
|
+
...existing,
|
|
1031
|
+
status: "continued",
|
|
1032
|
+
updatedAt: new Date().toISOString(),
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
return json(continuation);
|
|
1036
|
+
}
|
|
1037
|
+
catch (error) {
|
|
1038
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
name: "create_notebook",
|
|
1044
|
+
description: "Create an empty notebook",
|
|
1045
|
+
scope: "book",
|
|
1046
|
+
schema: { ...bookSchema, name: z.string().min(1) },
|
|
1047
|
+
handler: async (args) => {
|
|
1048
|
+
const result = await engine.notebooks.create(String(args.name));
|
|
1049
|
+
return result.ok ? json(notebookPayload(engine, result.value)) : failure(result.error.message);
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
name: "get_notebook",
|
|
1054
|
+
description: "Read a notebook graph",
|
|
1055
|
+
scope: "book",
|
|
1056
|
+
schema: { ...bookSchema, notebook_id: z.string().uuid() },
|
|
1057
|
+
handler: async (args) => {
|
|
1058
|
+
await migrateLegacyPosts(engine);
|
|
1059
|
+
const result = engine.notebooks.read(String(args.notebook_id));
|
|
1060
|
+
return result.ok ? json(notebookPayload(engine, result.value)) : failure(result.error.message);
|
|
1061
|
+
},
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
name: "list_notebook_actions",
|
|
1065
|
+
description: "List the deterministic notebook action catalog for runnable cells",
|
|
1066
|
+
scope: "book",
|
|
1067
|
+
schema: {
|
|
1068
|
+
...bookSchema,
|
|
1069
|
+
cell_type: cellTypeSchema.optional(),
|
|
1070
|
+
},
|
|
1071
|
+
handler: async (args) => {
|
|
1072
|
+
const actions = listNotebookActions()
|
|
1073
|
+
.filter((action) => args.cell_type
|
|
1074
|
+
? action.cellTypes.includes(args.cell_type)
|
|
1075
|
+
: true);
|
|
1076
|
+
return json({ actions });
|
|
1077
|
+
},
|
|
1078
|
+
},
|
|
1079
|
+
{
|
|
1080
|
+
name: "get_notebook_cell_details",
|
|
1081
|
+
description: "Return selected-cell details including raw Dolt row, edges, and action validation",
|
|
1082
|
+
scope: "book",
|
|
1083
|
+
schema: {
|
|
1084
|
+
...bookSchema,
|
|
1085
|
+
notebook_id: z.string().uuid(),
|
|
1086
|
+
cell_id: z.string().uuid(),
|
|
1087
|
+
},
|
|
1088
|
+
handler: async (args) => {
|
|
1089
|
+
await migrateLegacyPosts(engine);
|
|
1090
|
+
const result = engine.notebooks.read(String(args.notebook_id));
|
|
1091
|
+
if (!result.ok)
|
|
1092
|
+
return failure(result.error.message);
|
|
1093
|
+
try {
|
|
1094
|
+
const document = hydrateNotebookProviderFailures(engine, normalizeLegacyExtractCells(result.value));
|
|
1095
|
+
const { generateCell } = pairForCell(document, String(args.cell_id));
|
|
1096
|
+
const generationsResult = engine.generations.listForCell(String(args.notebook_id), generateCell.id);
|
|
1097
|
+
return json(selectedCellDetails(String(args.notebook_id), document, String(args.cell_id), generationsResult.ok ? generationsResult.value : []));
|
|
1098
|
+
}
|
|
1099
|
+
catch (cause) {
|
|
1100
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
name: "ensure_notebook_workflow_scaffold",
|
|
1106
|
+
description: "Ensure the seven-stage VIDEO→CONCAT workflow scaffold exists",
|
|
1107
|
+
scope: "book",
|
|
1108
|
+
schema: {
|
|
1109
|
+
...bookSchema,
|
|
1110
|
+
notebook_id: z.string().uuid(),
|
|
1111
|
+
source_artifact_id: z.string().uuid().optional(),
|
|
1112
|
+
},
|
|
1113
|
+
handler: async (args) => {
|
|
1114
|
+
const result = await mutateNotebook(engine, String(args.notebook_id), "ensure_notebook_workflow_scaffold", (notebook) => buildSevenStageScaffold(engine, notebook, typeof args.source_artifact_id === "string"
|
|
1115
|
+
? args.source_artifact_id
|
|
1116
|
+
: undefined));
|
|
1117
|
+
if (!result.ok)
|
|
1118
|
+
return failure(result.error.message);
|
|
1119
|
+
const read = engine.notebooks.read(String(args.notebook_id));
|
|
1120
|
+
return read.ok
|
|
1121
|
+
? json(notebookPayload(engine, read.value))
|
|
1122
|
+
: failure(read.error.message);
|
|
1123
|
+
},
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
name: "rename_notebook",
|
|
1127
|
+
description: "Rename a notebook",
|
|
1128
|
+
scope: "book",
|
|
1129
|
+
schema: { ...bookSchema, notebook_id: z.string().uuid(), name: z.string().min(1) },
|
|
1130
|
+
handler: async (args) => {
|
|
1131
|
+
const result = await mutateNotebook(engine, String(args.notebook_id), "rename_notebook", (notebook) => ({ ...notebook, name: String(args.name) }));
|
|
1132
|
+
return result.ok ? json({ notebook_id: args.notebook_id, revision: result.value.hash }) : failure(result.error.message);
|
|
1133
|
+
},
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
name: "delete_notebook",
|
|
1137
|
+
description: "Delete a notebook and its cells, edges, and runs",
|
|
1138
|
+
scope: "book",
|
|
1139
|
+
schema: { ...bookSchema, notebook_id: z.string().uuid() },
|
|
1140
|
+
handler: async (args) => {
|
|
1141
|
+
const result = await engine.notebooks.delete(String(args.notebook_id));
|
|
1142
|
+
return result.ok ? json(result.value) : failure(result.error.message);
|
|
1143
|
+
},
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
name: "create_notebook_cell",
|
|
1147
|
+
description: "Create a UUIDv7 notebook cell",
|
|
1148
|
+
scope: "book",
|
|
1149
|
+
schema: {
|
|
1150
|
+
...bookSchema,
|
|
1151
|
+
notebook_id: z.string().uuid(),
|
|
1152
|
+
cell: cellInputSchema,
|
|
1153
|
+
expected_revision: z.string().min(1).optional(),
|
|
1154
|
+
},
|
|
1155
|
+
handler: async (args) => {
|
|
1156
|
+
if (args.cell.type === "post")
|
|
1157
|
+
return failure("Use create_post to freeze source media and create a Post cell");
|
|
1158
|
+
if (args.cell.type === "goal")
|
|
1159
|
+
return failure(GOAL_TILE_CREATE_ERROR);
|
|
1160
|
+
let cell = engine.notebooks.createCell(cellFromInput(args.cell));
|
|
1161
|
+
let outputCell = null;
|
|
1162
|
+
let outputEdge = null;
|
|
1163
|
+
try {
|
|
1164
|
+
const result = await mutateNotebook(engine, String(args.notebook_id), "create_notebook_cell", (notebook) => {
|
|
1165
|
+
if (typeof args.expected_revision === "string"
|
|
1166
|
+
&& args.expected_revision !== engine.head) {
|
|
1167
|
+
throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1168
|
+
}
|
|
1169
|
+
cell = normalizeCellActionFields(cell, notebook);
|
|
1170
|
+
const withCell = seedInitialGenerationPrompt({
|
|
1171
|
+
...notebook,
|
|
1172
|
+
cells: [...notebook.cells, cell],
|
|
1173
|
+
}, cell.id);
|
|
1174
|
+
cell = withCell.cells.find((candidate) => candidate.id === cell.id);
|
|
1175
|
+
if (cell.type === "analyze") {
|
|
1176
|
+
const cells = withCell.cells;
|
|
1177
|
+
outputCell = engine.notebooks.createCell({
|
|
1178
|
+
type: "analysis",
|
|
1179
|
+
...(cell.label === undefined ? {} : { label: cell.label }),
|
|
1180
|
+
slot: nextHorizontalSlotFrom(slotRight(cell.slot), notebookOccupiedSlots(cells)),
|
|
1181
|
+
inputs: {
|
|
1182
|
+
analysisRole: "result",
|
|
1183
|
+
producerCellId: cell.id,
|
|
1184
|
+
state: "pending",
|
|
1185
|
+
},
|
|
1186
|
+
});
|
|
1187
|
+
outputEdge = engine.notebooks.createEdge({
|
|
1188
|
+
source: cell.id,
|
|
1189
|
+
target: outputCell.id,
|
|
1190
|
+
targetInput: "result",
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
if (cell.type === "generate_audio") {
|
|
1194
|
+
const ensured = ensureGenerateAudioOutput(engine, withCell, cell.id);
|
|
1195
|
+
cell = ensured.document.cells.find((candidate) => candidate.id === cell.id) ?? cell;
|
|
1196
|
+
outputCell = ensured.outputCell;
|
|
1197
|
+
outputEdge = ensured.outputEdge;
|
|
1198
|
+
return ensured.document;
|
|
1199
|
+
}
|
|
1200
|
+
return {
|
|
1201
|
+
...withCell,
|
|
1202
|
+
cells: [
|
|
1203
|
+
...withCell.cells,
|
|
1204
|
+
...(outputCell ? [outputCell] : []),
|
|
1205
|
+
],
|
|
1206
|
+
edges: [
|
|
1207
|
+
...withCell.edges,
|
|
1208
|
+
...(outputEdge ? [outputEdge] : []),
|
|
1209
|
+
],
|
|
1210
|
+
};
|
|
1211
|
+
}, { cellId: cell.id });
|
|
1212
|
+
return result.ok
|
|
1213
|
+
? json({
|
|
1214
|
+
...cellPayload(cell),
|
|
1215
|
+
...(outputCell ? { output_cell: cellPayload(outputCell) } : {}),
|
|
1216
|
+
...(outputEdge ? { output_edge: outputEdge } : {}),
|
|
1217
|
+
})
|
|
1218
|
+
: failure(result.error.message);
|
|
1219
|
+
}
|
|
1220
|
+
catch (cause) {
|
|
1221
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
name: "update_notebook_cell",
|
|
1227
|
+
description: "Update one notebook cell",
|
|
1228
|
+
scope: "book",
|
|
1229
|
+
schema: {
|
|
1230
|
+
...bookSchema,
|
|
1231
|
+
notebook_id: z.string().uuid(),
|
|
1232
|
+
cell_id: z.string().uuid(),
|
|
1233
|
+
cell: cellInputSchema.partial(),
|
|
1234
|
+
expected_revision: z.string().min(1).optional(),
|
|
1235
|
+
},
|
|
1236
|
+
handler: async (args) => {
|
|
1237
|
+
if (typeof args.expected_revision === "string"
|
|
1238
|
+
&& args.expected_revision !== engine.head) {
|
|
1239
|
+
return failure(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1240
|
+
}
|
|
1241
|
+
const patch = args.cell;
|
|
1242
|
+
try {
|
|
1243
|
+
const result = await mutateNotebook(engine, String(args.notebook_id), "update_notebook_cell", (notebook) => {
|
|
1244
|
+
if (typeof args.expected_revision === "string"
|
|
1245
|
+
&& args.expected_revision !== engine.head) {
|
|
1246
|
+
throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1247
|
+
}
|
|
1248
|
+
const document = notebook;
|
|
1249
|
+
let found = false;
|
|
1250
|
+
const cells = notebook.cells.map((cell) => {
|
|
1251
|
+
if (cell.id !== args.cell_id)
|
|
1252
|
+
return cell;
|
|
1253
|
+
found = true;
|
|
1254
|
+
if (patch.type === "post" && cell.type !== "post")
|
|
1255
|
+
throw new Error("Use create_post to freeze source media and create a Post cell");
|
|
1256
|
+
if (cell.type === "post") {
|
|
1257
|
+
if (Object.keys(patch).some((key) => key !== "slot" && key !== "label"))
|
|
1258
|
+
throw new Error("Use update_post to edit publication details");
|
|
1259
|
+
return { ...cell, ...(patch.slot ? { slot: patch.slot } : {}), ...(patch.label !== undefined ? { label: patch.label } : {}) };
|
|
1260
|
+
}
|
|
1261
|
+
if (cell.type === "goal")
|
|
1262
|
+
return moveGoalTile(cell, patch);
|
|
1263
|
+
if (patch.type && patch.type !== cell.type) {
|
|
1264
|
+
const isUnconnected = !notebook.edges.some((edge) => edge.source === cell.id || edge.target === cell.id);
|
|
1265
|
+
const replacement = typeof patch.output_artifact_id === "string"
|
|
1266
|
+
? engine.artifacts.get(patch.output_artifact_id)
|
|
1267
|
+
: null;
|
|
1268
|
+
const isDownloadImageReplacement = cell.type === "video"
|
|
1269
|
+
&& patch.type === "image"
|
|
1270
|
+
&& cell.inputs?.sourceOperation === "download_video"
|
|
1271
|
+
&& patch.output_artifact_id !== cell.outputArtifactId
|
|
1272
|
+
&& replacement?.ok === true
|
|
1273
|
+
&& replacement.value.kind === "image"
|
|
1274
|
+
&& isUnconnected;
|
|
1275
|
+
if (!isDownloadImageReplacement) {
|
|
1276
|
+
throw new Error("Cell type cannot be changed after creation");
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
const normalized = normalizeCellActionFields({
|
|
1280
|
+
...cell,
|
|
1281
|
+
...(patch.type === undefined ? {} : { type: patch.type }),
|
|
1282
|
+
...(patch.label === undefined ? {} : { label: patch.label }),
|
|
1283
|
+
...(patch.slot === undefined ? {} : { slot: patch.slot }),
|
|
1284
|
+
...(patch.output_entity_id === undefined
|
|
1285
|
+
? {}
|
|
1286
|
+
: { outputEntityId: patch.output_entity_id }),
|
|
1287
|
+
...(patch.prompt === undefined ? {} : { prompt: patch.prompt }),
|
|
1288
|
+
...(patch.provider === undefined ? {} : { provider: patch.provider }),
|
|
1289
|
+
...(patch.model === undefined ? {} : { model: patch.model }),
|
|
1290
|
+
...(patch.operation === undefined ? {} : { operation: patch.operation }),
|
|
1291
|
+
...(patch.tool === undefined ? {} : { tool: patch.tool }),
|
|
1292
|
+
...(patch.inputs === undefined ? {} : { inputs: patch.inputs }),
|
|
1293
|
+
...(patch.output_artifact_id === undefined ? {} : { outputArtifactId: patch.output_artifact_id }),
|
|
1294
|
+
...(patch.references === undefined
|
|
1295
|
+
? {}
|
|
1296
|
+
: { references: patch.references.map(referenceFromInput) }),
|
|
1297
|
+
...(patch.pinned_results === undefined
|
|
1298
|
+
? {}
|
|
1299
|
+
: { pinnedResults: patch.pinned_results.map(pinnedResultFromInput) }),
|
|
1300
|
+
}, document);
|
|
1301
|
+
const next = reconcileInitialGenerationPrompt(cell, normalized);
|
|
1302
|
+
const validation = validateCellConfiguration(next, document);
|
|
1303
|
+
if (!validation.ok)
|
|
1304
|
+
throw new Error(validation.message);
|
|
1305
|
+
return next;
|
|
1306
|
+
});
|
|
1307
|
+
if (!found)
|
|
1308
|
+
throw new Error(`Cell not found: ${String(args.cell_id)}`);
|
|
1309
|
+
const updated = refreshInitialGenerationPrompts({
|
|
1310
|
+
...document,
|
|
1311
|
+
cells,
|
|
1312
|
+
});
|
|
1313
|
+
return markDescendantsStale(updated, String(args.cell_id));
|
|
1314
|
+
}, { cellId: args.cell_id });
|
|
1315
|
+
return result.ok
|
|
1316
|
+
? json({ cell_id: args.cell_id, revision: result.value.hash })
|
|
1317
|
+
: failure(result.error.message);
|
|
1318
|
+
}
|
|
1319
|
+
catch (cause) {
|
|
1320
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
1321
|
+
}
|
|
1322
|
+
},
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
name: "relayout_notebook",
|
|
1326
|
+
description: "Recompute notebook cell slots with directional placement: video stacks vertical near column 1, image/frame chains horizontal, compact near @a1. Migrates legacy generate_video / generate_image inline artifacts into output cells.",
|
|
1327
|
+
scope: "book",
|
|
1328
|
+
recordsOwnActivity: true,
|
|
1329
|
+
schema: {
|
|
1330
|
+
...bookSchema,
|
|
1331
|
+
notebook_id: z.string().uuid(),
|
|
1332
|
+
expected_revision: z.string().min(1).optional(),
|
|
1333
|
+
},
|
|
1334
|
+
handler: async (args) => {
|
|
1335
|
+
try {
|
|
1336
|
+
const notebookId = String(args.notebook_id);
|
|
1337
|
+
if (typeof args.expected_revision === "string"
|
|
1338
|
+
&& args.expected_revision !== engine.head) {
|
|
1339
|
+
return failure(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1340
|
+
}
|
|
1341
|
+
const before = engine.notebooks.read(notebookId);
|
|
1342
|
+
if (!before.ok)
|
|
1343
|
+
return failure(before.error.message);
|
|
1344
|
+
const jobId = `relayout-notebook-${notebookId}-${Date.now()}`;
|
|
1345
|
+
const result = await queue.enqueueAndWait(jobId, {
|
|
1346
|
+
type: "relayout_notebook",
|
|
1347
|
+
bookId: String(args.book_id),
|
|
1348
|
+
notebookId,
|
|
1349
|
+
expectedRevision: args.expected_revision,
|
|
1350
|
+
});
|
|
1351
|
+
return json(result);
|
|
1352
|
+
}
|
|
1353
|
+
catch (cause) {
|
|
1354
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
1355
|
+
}
|
|
1356
|
+
},
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
name: "move_notebook_cells",
|
|
1360
|
+
description: "Persist notebook cell grid slots",
|
|
1361
|
+
scope: "book",
|
|
1362
|
+
recordsOwnActivity: true,
|
|
1363
|
+
schema: {
|
|
1364
|
+
...bookSchema,
|
|
1365
|
+
notebook_id: z.string().uuid(),
|
|
1366
|
+
slots: z.array(notebookGridSlotSchema.extend({
|
|
1367
|
+
id: z.string().uuid(),
|
|
1368
|
+
})).min(1),
|
|
1369
|
+
expected_revision: z.string().min(1).optional(),
|
|
1370
|
+
},
|
|
1371
|
+
handler: async (args) => {
|
|
1372
|
+
const requested = args.slots;
|
|
1373
|
+
try {
|
|
1374
|
+
const result = await mutateNotebook(engine, String(args.notebook_id), "move_notebook_cells", (notebook) => {
|
|
1375
|
+
if (typeof args.expected_revision === "string"
|
|
1376
|
+
&& args.expected_revision !== engine.head) {
|
|
1377
|
+
throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1378
|
+
}
|
|
1379
|
+
const slots = new Map(requested.map((slot) => [slot.id, slot]));
|
|
1380
|
+
if (slots.size !== requested.length) {
|
|
1381
|
+
throw new Error("Each moved notebook cell must appear once");
|
|
1382
|
+
}
|
|
1383
|
+
const cellsById = new Map(notebook.cells.map((cell) => [cell.id, cell]));
|
|
1384
|
+
for (const id of slots.keys()) {
|
|
1385
|
+
if (!cellsById.has(id))
|
|
1386
|
+
throw new Error(`Cell not found: ${id}`);
|
|
1387
|
+
}
|
|
1388
|
+
if (requested.length === 1) {
|
|
1389
|
+
const destination = requested[0];
|
|
1390
|
+
const source = cellsById.get(destination.id);
|
|
1391
|
+
const footprint = new Set(notebookCellOccupiedSlots({ type: source.type, slot: destination })
|
|
1392
|
+
.map((slot) => `${slot.row}:${slot.column}`));
|
|
1393
|
+
const occupied = notebook.cells.find((cell) => cell.id !== source.id
|
|
1394
|
+
&& notebookCellOccupiedSlots(cell).some((slot) => footprint.has(`${slot.row}:${slot.column}`)));
|
|
1395
|
+
if (occupied && (source.type === "timeline" || occupied.type === "timeline")) {
|
|
1396
|
+
throw new Error("Choose an empty row for the timeline and keep its occupied row free");
|
|
1397
|
+
}
|
|
1398
|
+
const moved = {
|
|
1399
|
+
...notebook,
|
|
1400
|
+
cells: notebook.cells.map((cell) => {
|
|
1401
|
+
if (cell.id === source.id) {
|
|
1402
|
+
return {
|
|
1403
|
+
...cell,
|
|
1404
|
+
slot: { row: destination.row, column: destination.column },
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
return cell.id === occupied?.id
|
|
1408
|
+
? { ...cell, slot: source.slot }
|
|
1409
|
+
: cell;
|
|
1410
|
+
}),
|
|
1411
|
+
};
|
|
1412
|
+
return refreshInitialGenerationPrompts(moved);
|
|
1413
|
+
}
|
|
1414
|
+
const targetKeys = new Set();
|
|
1415
|
+
for (const slot of requested) {
|
|
1416
|
+
const key = `${slot.row}:${slot.column}`;
|
|
1417
|
+
if (targetKeys.has(key)) {
|
|
1418
|
+
throw new Error(`Notebook grid square ${notebookGridTag(slot)} is targeted twice`);
|
|
1419
|
+
}
|
|
1420
|
+
targetKeys.add(key);
|
|
1421
|
+
}
|
|
1422
|
+
const movingIds = new Set(slots.keys());
|
|
1423
|
+
const collision = notebook.cells.find((cell) => !movingIds.has(cell.id)
|
|
1424
|
+
&& targetKeys.has(`${cell.slot.row}:${cell.slot.column}`));
|
|
1425
|
+
if (collision) {
|
|
1426
|
+
throw new Error(`Notebook grid square ${notebookGridTag(collision.slot)} is occupied`);
|
|
1427
|
+
}
|
|
1428
|
+
const moved = {
|
|
1429
|
+
...notebook,
|
|
1430
|
+
cells: notebook.cells.map((cell) => {
|
|
1431
|
+
const slot = slots.get(cell.id);
|
|
1432
|
+
return slot
|
|
1433
|
+
? { ...cell, slot: { row: slot.row, column: slot.column } }
|
|
1434
|
+
: cell;
|
|
1435
|
+
}),
|
|
1436
|
+
};
|
|
1437
|
+
return refreshInitialGenerationPrompts(moved);
|
|
1438
|
+
}, { cellIds: requested.map((slot) => slot.id) });
|
|
1439
|
+
return result.ok
|
|
1440
|
+
? json({ notebook_id: args.notebook_id, revision: result.value.hash })
|
|
1441
|
+
: failure(result.error.message);
|
|
1442
|
+
}
|
|
1443
|
+
catch (cause) {
|
|
1444
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
1445
|
+
}
|
|
1446
|
+
},
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
name: "delete_notebook_cell",
|
|
1450
|
+
description: "Delete one notebook cell and incident edges",
|
|
1451
|
+
scope: "book",
|
|
1452
|
+
schema: {
|
|
1453
|
+
...bookSchema,
|
|
1454
|
+
notebook_id: z.string().uuid(),
|
|
1455
|
+
cell_id: z.string().uuid(),
|
|
1456
|
+
expected_revision: z.string().min(1).optional(),
|
|
1457
|
+
},
|
|
1458
|
+
handler: async (args) => {
|
|
1459
|
+
if (typeof args.expected_revision === "string"
|
|
1460
|
+
&& args.expected_revision !== engine.head) {
|
|
1461
|
+
return failure(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1462
|
+
}
|
|
1463
|
+
const notebookId = String(args.notebook_id);
|
|
1464
|
+
const cellId = String(args.cell_id);
|
|
1465
|
+
try {
|
|
1466
|
+
const result = await withNotebookMutation(notebookId, async () => {
|
|
1467
|
+
if (typeof args.expected_revision === "string"
|
|
1468
|
+
&& args.expected_revision !== engine.head) {
|
|
1469
|
+
throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1470
|
+
}
|
|
1471
|
+
return await engine.notebooks.removeCell(notebookId, cellId);
|
|
1472
|
+
});
|
|
1473
|
+
return result.ok ? json({ cell_id: cellId, revision: result.value.hash }) : failure(result.error.message);
|
|
1474
|
+
}
|
|
1475
|
+
catch (cause) {
|
|
1476
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
1477
|
+
}
|
|
1478
|
+
},
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
name: "connect_notebook_cells",
|
|
1482
|
+
description: "Create a UUIDv7 notebook edge",
|
|
1483
|
+
scope: "book",
|
|
1484
|
+
schema: {
|
|
1485
|
+
...bookSchema,
|
|
1486
|
+
notebook_id: z.string().uuid(),
|
|
1487
|
+
source_cell_id: z.string().uuid(),
|
|
1488
|
+
target_cell_id: z.string().uuid(),
|
|
1489
|
+
target_input: z.string().min(1),
|
|
1490
|
+
expected_revision: z.string().min(1).optional(),
|
|
1491
|
+
},
|
|
1492
|
+
handler: async (args) => {
|
|
1493
|
+
const edge = engine.notebooks.createEdge({
|
|
1494
|
+
source: String(args.source_cell_id),
|
|
1495
|
+
target: String(args.target_cell_id),
|
|
1496
|
+
targetInput: String(args.target_input),
|
|
1497
|
+
});
|
|
1498
|
+
try {
|
|
1499
|
+
const result = await mutateNotebook(engine, String(args.notebook_id), "connect_notebook_cells", (notebook) => {
|
|
1500
|
+
if (typeof args.expected_revision === "string"
|
|
1501
|
+
&& args.expected_revision !== engine.head) {
|
|
1502
|
+
throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1503
|
+
}
|
|
1504
|
+
const prepared = prepareNotebookConnection(engine, notebook, edge.source, edge.target, edge.targetInput);
|
|
1505
|
+
return refreshGenerationInputPrompt(notebook, {
|
|
1506
|
+
...prepared,
|
|
1507
|
+
edges: [...prepared.edges, edge],
|
|
1508
|
+
}, edge.target);
|
|
1509
|
+
}, { sourceCellId: edge.source, targetCellId: edge.target, edgeId: edge.id });
|
|
1510
|
+
return result.ok ? json(edge) : failure(result.error.message);
|
|
1511
|
+
}
|
|
1512
|
+
catch (cause) {
|
|
1513
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
1514
|
+
}
|
|
1515
|
+
},
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
name: "disconnect_notebook_cells",
|
|
1519
|
+
description: "Delete a notebook edge",
|
|
1520
|
+
scope: "book",
|
|
1521
|
+
schema: { ...bookSchema, notebook_id: z.string().uuid(), edge_id: z.string().uuid() },
|
|
1522
|
+
handler: async (args) => {
|
|
1523
|
+
const edgeId = String(args.edge_id);
|
|
1524
|
+
const result = await mutateNotebook(engine, String(args.notebook_id), "disconnect_notebook_cells", (notebook) => {
|
|
1525
|
+
const removed = notebook.edges.find((edge) => edge.id === edgeId);
|
|
1526
|
+
if (removed && notebook.cells.some((cell) => cell.id === removed.target && cell.type === "post"))
|
|
1527
|
+
throw new Error("A Post keeps its frozen source connection");
|
|
1528
|
+
const disconnected = {
|
|
1529
|
+
...notebook,
|
|
1530
|
+
edges: notebook.edges.filter((edge) => edge.id !== edgeId),
|
|
1531
|
+
};
|
|
1532
|
+
return removed
|
|
1533
|
+
? refreshGenerationInputPrompt(notebook, disconnected, removed.target)
|
|
1534
|
+
: disconnected;
|
|
1535
|
+
}, { edgeId });
|
|
1536
|
+
return result.ok ? json({ edge_id: edgeId, revision: result.value.hash }) : failure(result.error.message);
|
|
1537
|
+
},
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
name: "set_notebook_cell_input",
|
|
1541
|
+
description: "Atomically replace or clear one named notebook cell input",
|
|
1542
|
+
scope: "book",
|
|
1543
|
+
schema: {
|
|
1544
|
+
...bookSchema,
|
|
1545
|
+
notebook_id: z.string().uuid(),
|
|
1546
|
+
target_cell_id: z.string().uuid(),
|
|
1547
|
+
target_input: z.string().min(1),
|
|
1548
|
+
source_cell_id: z.string().uuid().nullable(),
|
|
1549
|
+
expected_revision: z.string().min(1).optional(),
|
|
1550
|
+
},
|
|
1551
|
+
handler: async (args) => {
|
|
1552
|
+
if (typeof args.expected_revision === "string"
|
|
1553
|
+
&& args.expected_revision !== engine.head) {
|
|
1554
|
+
return failure(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1555
|
+
}
|
|
1556
|
+
const targetCellId = String(args.target_cell_id);
|
|
1557
|
+
const targetInput = String(args.target_input);
|
|
1558
|
+
const sourceCellId = typeof args.source_cell_id === "string"
|
|
1559
|
+
? args.source_cell_id
|
|
1560
|
+
: null;
|
|
1561
|
+
let edgeId = null;
|
|
1562
|
+
try {
|
|
1563
|
+
const result = await mutateNotebook(engine, String(args.notebook_id), "set_notebook_cell_input", (notebook) => {
|
|
1564
|
+
if (typeof args.expected_revision === "string"
|
|
1565
|
+
&& args.expected_revision !== engine.head) {
|
|
1566
|
+
throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1567
|
+
}
|
|
1568
|
+
const target = notebook.cells.find((cell) => cell.id === targetCellId);
|
|
1569
|
+
if (!target) {
|
|
1570
|
+
throw new Error(`Target cell not found: ${targetCellId}`);
|
|
1571
|
+
}
|
|
1572
|
+
if (target.type === "post")
|
|
1573
|
+
throw new Error("Use update_post to choose a Post source");
|
|
1574
|
+
const existing = notebook.edges.filter((edge) => edge.target === targetCellId
|
|
1575
|
+
&& edge.targetInput === targetInput);
|
|
1576
|
+
const retained = notebook.edges.filter((edge) => edge.target !== targetCellId
|
|
1577
|
+
|| edge.targetInput !== targetInput);
|
|
1578
|
+
let edges = retained;
|
|
1579
|
+
if (sourceCellId) {
|
|
1580
|
+
const prepared = prepareNotebookConnection(engine, { ...notebook, edges: retained }, sourceCellId, targetCellId, targetInput);
|
|
1581
|
+
const matching = existing.find((edge) => edge.source === sourceCellId);
|
|
1582
|
+
const edge = matching ?? engine.notebooks.createEdge({
|
|
1583
|
+
source: sourceCellId,
|
|
1584
|
+
target: targetCellId,
|
|
1585
|
+
targetInput,
|
|
1586
|
+
});
|
|
1587
|
+
edgeId = edge.id;
|
|
1588
|
+
edges = [...prepared.edges, edge];
|
|
1589
|
+
}
|
|
1590
|
+
const updated = { ...notebook, edges };
|
|
1591
|
+
const marked = markCellAndDescendantsStale(updated, targetCellId);
|
|
1592
|
+
return refreshGenerationInputPrompt(notebook, marked, targetCellId);
|
|
1593
|
+
}, {
|
|
1594
|
+
targetCellId,
|
|
1595
|
+
targetInput,
|
|
1596
|
+
sourceCellId,
|
|
1597
|
+
edgeId,
|
|
1598
|
+
});
|
|
1599
|
+
return result.ok
|
|
1600
|
+
? json({
|
|
1601
|
+
target_cell_id: targetCellId,
|
|
1602
|
+
target_input: targetInput,
|
|
1603
|
+
source_cell_id: sourceCellId,
|
|
1604
|
+
edge_id: edgeId,
|
|
1605
|
+
revision: result.value.hash,
|
|
1606
|
+
})
|
|
1607
|
+
: failure(result.error.message);
|
|
1608
|
+
}
|
|
1609
|
+
catch (cause) {
|
|
1610
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
1611
|
+
}
|
|
1612
|
+
},
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
name: "preview_notebook_generation",
|
|
1616
|
+
description: "Preview the exact provider payload a generation cell would submit, without running it",
|
|
1617
|
+
scope: "book",
|
|
1618
|
+
schema: {
|
|
1619
|
+
...bookSchema,
|
|
1620
|
+
notebook_id: z.string().uuid(),
|
|
1621
|
+
cell_id: z.string().uuid(),
|
|
1622
|
+
draft: z.object({
|
|
1623
|
+
type: cellTypeSchema.optional(),
|
|
1624
|
+
label: z.string().optional(),
|
|
1625
|
+
slot: notebookGridSlotSchema.optional(),
|
|
1626
|
+
prompt: z.string(),
|
|
1627
|
+
provider: z.string().min(1),
|
|
1628
|
+
model: z.string().min(1),
|
|
1629
|
+
operation: z.string().min(1),
|
|
1630
|
+
tool: z.string().min(1),
|
|
1631
|
+
inputs: z.record(z.string(), z.unknown()).optional(),
|
|
1632
|
+
}).optional(),
|
|
1633
|
+
connections: z.array(z.object({
|
|
1634
|
+
source_cell_id: z.string().uuid(),
|
|
1635
|
+
target_input: z.string().min(1),
|
|
1636
|
+
})).optional(),
|
|
1637
|
+
},
|
|
1638
|
+
handler: async (args) => {
|
|
1639
|
+
const notebook = engine.notebooks.read(String(args.notebook_id));
|
|
1640
|
+
if (!notebook.ok)
|
|
1641
|
+
return failure(notebook.error.message);
|
|
1642
|
+
try {
|
|
1643
|
+
const draft = args.draft;
|
|
1644
|
+
const cellId = String(args.cell_id);
|
|
1645
|
+
const cell = notebook.value.cells.find((candidate) => candidate.id === cellId);
|
|
1646
|
+
if (!cell && (!draft?.type || !draft.slot)) {
|
|
1647
|
+
return failure("Notebook cell not found");
|
|
1648
|
+
}
|
|
1649
|
+
const baseCell = cell ?? {
|
|
1650
|
+
id: cellId,
|
|
1651
|
+
type: draft.type,
|
|
1652
|
+
slot: draft.slot,
|
|
1653
|
+
};
|
|
1654
|
+
const previewCell = draft
|
|
1655
|
+
? { ...baseCell, ...draft, id: cellId }
|
|
1656
|
+
: baseCell;
|
|
1657
|
+
const connections = args.connections;
|
|
1658
|
+
const previewWithCell = {
|
|
1659
|
+
...notebook.value,
|
|
1660
|
+
cells: [
|
|
1661
|
+
...notebook.value.cells.filter((candidate) => candidate.id !== cellId),
|
|
1662
|
+
previewCell,
|
|
1663
|
+
],
|
|
1664
|
+
};
|
|
1665
|
+
let previewNotebook = connections
|
|
1666
|
+
? {
|
|
1667
|
+
...previewWithCell,
|
|
1668
|
+
edges: notebook.value.edges.filter((edge) => edge.target !== cellId),
|
|
1669
|
+
}
|
|
1670
|
+
: previewWithCell;
|
|
1671
|
+
for (const connection of connections ?? []) {
|
|
1672
|
+
previewNotebook = prepareNotebookConnection(engine, previewNotebook, connection.source_cell_id, cellId, connection.target_input);
|
|
1673
|
+
previewNotebook = {
|
|
1674
|
+
...previewNotebook,
|
|
1675
|
+
edges: [...previewNotebook.edges, engine.notebooks.createEdge({
|
|
1676
|
+
source: connection.source_cell_id,
|
|
1677
|
+
target: cellId,
|
|
1678
|
+
targetInput: connection.target_input,
|
|
1679
|
+
})],
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
const validation = validateCellConfiguration(previewCell, previewNotebook);
|
|
1683
|
+
if (!validation.ok)
|
|
1684
|
+
return failure(validation.message);
|
|
1685
|
+
const prepared = await prepareNotebookGeneration(engine, previewNotebook, previewCell, String(args.book_id), { allowReferenceIssues: true });
|
|
1686
|
+
const promptLeaks = [
|
|
1687
|
+
...prepared.mentions.unresolved.map((reference) => ({
|
|
1688
|
+
token: reference.mention,
|
|
1689
|
+
code: reference.code,
|
|
1690
|
+
message: reference.message,
|
|
1691
|
+
})),
|
|
1692
|
+
...prepared.mentions.unconnected.map((reference) => ({
|
|
1693
|
+
token: reference.mention,
|
|
1694
|
+
code: "not_connected",
|
|
1695
|
+
message: reference.message,
|
|
1696
|
+
})),
|
|
1697
|
+
];
|
|
1698
|
+
const mediaPreview = promptLeaks.length === 0 && !prepared.imageEdit
|
|
1699
|
+
? previewGeneration(prepared.input)
|
|
1700
|
+
: null;
|
|
1701
|
+
if (mediaPreview && !mediaPreview.ok)
|
|
1702
|
+
return failure(mediaPreview.error);
|
|
1703
|
+
const translated = promptLeaks.length === 0
|
|
1704
|
+
? prepared.imageEdit
|
|
1705
|
+
? { payload: prepared.imageEdit.providerPayload, provider_path: undefined }
|
|
1706
|
+
: mediaPreview?.ok
|
|
1707
|
+
? { payload: mediaPreview.preview.payload, provider_path: mediaPreview.preview.providerPath }
|
|
1708
|
+
: null
|
|
1709
|
+
: null;
|
|
1710
|
+
const previewPayload = mediaPreview?.ok
|
|
1711
|
+
? { payload: mediaPreview.preview.payload, placeholders: mediaPreview.preview.placeholders }
|
|
1712
|
+
: translated
|
|
1713
|
+
? applyPreviewPlaceholderUrls(translated.payload)
|
|
1714
|
+
: null;
|
|
1715
|
+
const costResult = await estimateCostDef(engine).handler({
|
|
1716
|
+
calls: [{
|
|
1717
|
+
tool_name: prepared.tool,
|
|
1718
|
+
input: prepared.input,
|
|
1719
|
+
}],
|
|
1720
|
+
});
|
|
1721
|
+
const costText = costResult.content.find((item) => item.type === "text")?.text;
|
|
1722
|
+
const costValue = !costResult.isError && costText
|
|
1723
|
+
? JSON.parse(costText)
|
|
1724
|
+
: {};
|
|
1725
|
+
return json({
|
|
1726
|
+
tool: prepared.tool,
|
|
1727
|
+
provider: prepared.provider,
|
|
1728
|
+
model: prepared.model,
|
|
1729
|
+
...(translated?.provider_path
|
|
1730
|
+
? { provider_path: translated.provider_path }
|
|
1731
|
+
: {}),
|
|
1732
|
+
payload: promptLeaks.length > 0
|
|
1733
|
+
? null
|
|
1734
|
+
: previewPayload?.payload ?? prepared.input,
|
|
1735
|
+
estimate: costValue.estimate ?? null,
|
|
1736
|
+
mentions: {
|
|
1737
|
+
prompt_leaks: promptLeaks,
|
|
1738
|
+
url_placeholders: previewPayload?.placeholders ?? [],
|
|
1739
|
+
},
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
catch (cause) {
|
|
1743
|
+
return failure(cause instanceof Error ? cause.message : String(cause));
|
|
1744
|
+
}
|
|
1745
|
+
},
|
|
1746
|
+
},
|
|
1747
|
+
{
|
|
1748
|
+
name: "run_notebook_cell",
|
|
1749
|
+
description: "Run one selected notebook cell using saved upstream media. retry_failed_cell_id replaces an output that failed only because provider credits ran out.",
|
|
1750
|
+
scope: "book",
|
|
1751
|
+
schema: {
|
|
1752
|
+
...bookSchema,
|
|
1753
|
+
notebook_id: z.string().uuid(),
|
|
1754
|
+
cell_id: z.string().uuid(),
|
|
1755
|
+
retry_failed_cell_id: z.string().uuid().optional().describe("Delete this out-of-credits output when reserving its replacement; requires a generation plan"),
|
|
1756
|
+
plan: z.record(z.string(), z.unknown()).optional(),
|
|
1757
|
+
actor: z.string().min(1).optional(),
|
|
1758
|
+
expected_revision: z.string().min(1).optional(),
|
|
1759
|
+
},
|
|
1760
|
+
handler: async (args) => {
|
|
1761
|
+
if (typeof args.expected_revision === "string"
|
|
1762
|
+
&& args.expected_revision !== engine.head) {
|
|
1763
|
+
return failure(`Stale notebook revision: expected ${args.expected_revision}`);
|
|
1764
|
+
}
|
|
1765
|
+
const notebook = engine.notebooks.read(String(args.notebook_id));
|
|
1766
|
+
if (!notebook.ok)
|
|
1767
|
+
return failure(notebook.error.message);
|
|
1768
|
+
const cell = notebook.value.cells.find((candidate) => candidate.id === String(args.cell_id));
|
|
1769
|
+
if (!cell)
|
|
1770
|
+
return failure("Notebook cell not found");
|
|
1771
|
+
if (!notebookCellIsRunnable(cell)) {
|
|
1772
|
+
return failure("Media cells are passive outputs and cannot be run");
|
|
1773
|
+
}
|
|
1774
|
+
const requestedPlan = args.plan;
|
|
1775
|
+
let generationPlan;
|
|
1776
|
+
let generationPlanRecord;
|
|
1777
|
+
if (requestedPlan) {
|
|
1778
|
+
const record = notebookGenerationPlans(notebook.value).find((candidate) => candidate.planId === requestedPlan.planId
|
|
1779
|
+
&& candidate.cellId === cell.id);
|
|
1780
|
+
if (!record)
|
|
1781
|
+
return failure("Generation plan is not preserved in this notebook");
|
|
1782
|
+
if (record.status !== "planned") {
|
|
1783
|
+
return failure(`Generation plan is already ${record.status}`);
|
|
1784
|
+
}
|
|
1785
|
+
const expected = record.plan.cellDefinitionFingerprint
|
|
1786
|
+
?? record.plan.cellFingerprint;
|
|
1787
|
+
const actual = notebookCellDefinitionFingerprint(notebook.value, cell);
|
|
1788
|
+
if (expected !== actual) {
|
|
1789
|
+
return failure("Generation plan is stale for the selected cell");
|
|
1790
|
+
}
|
|
1791
|
+
generationPlan = record.plan;
|
|
1792
|
+
generationPlanRecord = record;
|
|
1793
|
+
}
|
|
1794
|
+
if (notebookCellIsPaid(cell) && !generationPlan) {
|
|
1795
|
+
return failure("Paid notebook cells require a persisted generation plan");
|
|
1796
|
+
}
|
|
1797
|
+
if (args.retry_failed_cell_id && !generationPlan) {
|
|
1798
|
+
return failure("Retrying an out-of-credits output requires a persisted generation plan");
|
|
1799
|
+
}
|
|
1800
|
+
const runId = uuidv7();
|
|
1801
|
+
const jobId = `run-notebook-${String(args.notebook_id)}-${String(args.cell_id)}-${runId}`;
|
|
1802
|
+
try {
|
|
1803
|
+
if (generationPlanRecord) {
|
|
1804
|
+
generationPlan = await claimNotebookGenerationPlan(engine, String(args.notebook_id), String(args.cell_id), generationPlanRecord.planId, runId, typeof args.retry_failed_cell_id === "string" ? args.retry_failed_cell_id : undefined);
|
|
1805
|
+
}
|
|
1806
|
+
else {
|
|
1807
|
+
await markNotebookCellQueued(engine, String(args.notebook_id), String(args.cell_id), runId);
|
|
1808
|
+
}
|
|
1809
|
+
const outputCellId = generationPlan
|
|
1810
|
+
? generationPlanClaimOutputCellId(generationPlan)
|
|
1811
|
+
: null;
|
|
1812
|
+
await queue.enqueue(jobId, {
|
|
1813
|
+
type: "run_notebook",
|
|
1814
|
+
bookId: String(args.book_id),
|
|
1815
|
+
notebookId: String(args.notebook_id),
|
|
1816
|
+
cellIds: [String(args.cell_id)],
|
|
1817
|
+
force: true,
|
|
1818
|
+
runId,
|
|
1819
|
+
...(outputCellId ? { outputCellId } : {}),
|
|
1820
|
+
...(generationPlan
|
|
1821
|
+
? {
|
|
1822
|
+
generationPlan,
|
|
1823
|
+
actor: typeof args.actor === "string" ? args.actor : "videobook-user",
|
|
1824
|
+
jobId,
|
|
1825
|
+
}
|
|
1826
|
+
: {}),
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
catch (cause) {
|
|
1830
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
1831
|
+
const generationPlanId = typeof generationPlan?.planId === "string"
|
|
1832
|
+
? generationPlan.planId
|
|
1833
|
+
: undefined;
|
|
1834
|
+
await failQueuedNotebookCells(engine, String(args.notebook_id), {
|
|
1835
|
+
cellIds: [String(args.cell_id)],
|
|
1836
|
+
runId,
|
|
1837
|
+
error: message,
|
|
1838
|
+
...(generationPlanId ? { generationPlanId } : {}),
|
|
1839
|
+
}).catch(() => undefined);
|
|
1840
|
+
return failure(message);
|
|
1841
|
+
}
|
|
1842
|
+
const outputCellId = generationPlan
|
|
1843
|
+
? generationPlanClaimOutputCellId(generationPlan)
|
|
1844
|
+
: null;
|
|
1845
|
+
return json({
|
|
1846
|
+
job_id: jobId,
|
|
1847
|
+
run_id: runId,
|
|
1848
|
+
...(generationPlan ? { plan_id: generationPlan.planId } : {}),
|
|
1849
|
+
...(outputCellId ? { output_cell_id: outputCellId } : {}),
|
|
1850
|
+
});
|
|
1851
|
+
},
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
name: "run_notebook",
|
|
1855
|
+
description: "Run missing, stale, or explicitly selected notebook cells topologically",
|
|
1856
|
+
scope: "book",
|
|
1857
|
+
schema: {
|
|
1858
|
+
...bookSchema,
|
|
1859
|
+
notebook_id: z.string().uuid(),
|
|
1860
|
+
cell_ids: z.array(z.string().uuid()).min(1).max(100).optional(),
|
|
1861
|
+
actor: z.string().min(1).optional(),
|
|
1862
|
+
},
|
|
1863
|
+
handler: async (args) => {
|
|
1864
|
+
const cellIds = Array.isArray(args.cell_ids)
|
|
1865
|
+
? [...new Set(args.cell_ids.map(String))]
|
|
1866
|
+
: undefined;
|
|
1867
|
+
if (cellIds) {
|
|
1868
|
+
const notebook = engine.notebooks.read(String(args.notebook_id));
|
|
1869
|
+
if (!notebook.ok)
|
|
1870
|
+
return failure(notebook.error.message);
|
|
1871
|
+
for (const cellId of cellIds) {
|
|
1872
|
+
const cell = notebook.value.cells.find((candidate) => candidate.id === cellId);
|
|
1873
|
+
if (!cell)
|
|
1874
|
+
return failure(`Notebook cell not found: ${cellId}`);
|
|
1875
|
+
if (!notebookCellIsRunnable(cell)) {
|
|
1876
|
+
return failure(`Notebook cell ${notebookGridTag(cell.slot)} is not runnable`);
|
|
1877
|
+
}
|
|
1878
|
+
if (notebookCellIsPaid(cell)) {
|
|
1879
|
+
return failure(`Paid notebook cell ${notebookGridTag(cell.slot)} requires its own persisted generation plan`);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
const runId = uuidv7();
|
|
1884
|
+
const jobId = `run-notebook-${String(args.notebook_id)}-${runId}`;
|
|
1885
|
+
try {
|
|
1886
|
+
if (cellIds) {
|
|
1887
|
+
for (const cellId of cellIds) {
|
|
1888
|
+
await markNotebookCellQueued(engine, String(args.notebook_id), cellId, runId);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
await queue.enqueue(jobId, {
|
|
1892
|
+
type: "run_notebook",
|
|
1893
|
+
bookId: String(args.book_id),
|
|
1894
|
+
notebookId: String(args.notebook_id),
|
|
1895
|
+
runId,
|
|
1896
|
+
force: cellIds !== undefined,
|
|
1897
|
+
...(cellIds ? { cellIds } : {}),
|
|
1898
|
+
...(typeof args.actor === "string" ? { actor: args.actor } : {}),
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
catch (cause) {
|
|
1902
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
1903
|
+
await failQueuedNotebookCells(engine, String(args.notebook_id), {
|
|
1904
|
+
cellIds: cellIds ?? [],
|
|
1905
|
+
runId,
|
|
1906
|
+
error: message,
|
|
1907
|
+
}).catch(() => undefined);
|
|
1908
|
+
return failure(message);
|
|
1909
|
+
}
|
|
1910
|
+
return json({
|
|
1911
|
+
job_id: jobId,
|
|
1912
|
+
run_id: runId,
|
|
1913
|
+
...(cellIds ? { cell_ids: cellIds } : {}),
|
|
1914
|
+
});
|
|
1915
|
+
},
|
|
1916
|
+
},
|
|
1917
|
+
{
|
|
1918
|
+
name: "list_jobs",
|
|
1919
|
+
description: "List persistent jobs for a book. Filter by numeric job_ids, or by job_keys, the job_id strings other tools return while the server that queued them is running.",
|
|
1920
|
+
scope: "book",
|
|
1921
|
+
schema: {
|
|
1922
|
+
...bookSchema,
|
|
1923
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
1924
|
+
job_ids: z.array(z.number().int().positive()).min(1).max(100).optional(),
|
|
1925
|
+
job_keys: z.array(z.string().min(1).max(500)).min(1).max(100).optional(),
|
|
1926
|
+
},
|
|
1927
|
+
handler: async (args) => {
|
|
1928
|
+
const jobs = selectJobs(engine, args);
|
|
1929
|
+
return json(await Promise.all(jobs.map(async ({ job, jobKey }) => ({
|
|
1930
|
+
...job, ...(jobKey ? { jobKey } : {}), providerTask: await jobProviderTaskStatus(engine, job),
|
|
1931
|
+
}))));
|
|
1932
|
+
},
|
|
1933
|
+
},
|
|
1934
|
+
{
|
|
1935
|
+
name: "cancel_job",
|
|
1936
|
+
description: "Cancel a queued or running persistent job",
|
|
1937
|
+
scope: "book",
|
|
1938
|
+
schema: {
|
|
1939
|
+
...bookSchema,
|
|
1940
|
+
job_id: z.number().int().positive(),
|
|
1941
|
+
reason: z.string().min(1).max(500).optional(),
|
|
1942
|
+
},
|
|
1943
|
+
handler: async (args) => {
|
|
1944
|
+
const jobId = Number(args.job_id);
|
|
1945
|
+
const job = engine.jobs.queue.get(jobId);
|
|
1946
|
+
if (!job)
|
|
1947
|
+
return failure(`Job ${jobId} was not found`);
|
|
1948
|
+
const cancelled = await engine.jobs.queue.abort(jobId, typeof args.reason === "string" ? args.reason : "Cancelled by user");
|
|
1949
|
+
if (!cancelled) {
|
|
1950
|
+
return failure(`Job ${jobId} is already ${job.state}`);
|
|
1951
|
+
}
|
|
1952
|
+
return json(engine.jobs.queue.get(jobId));
|
|
1953
|
+
},
|
|
1954
|
+
},
|
|
1955
|
+
{
|
|
1956
|
+
name: "retry_job",
|
|
1957
|
+
description: "Re-enqueue a failed or cancelled persistent job with the same durable payload",
|
|
1958
|
+
scope: "book",
|
|
1959
|
+
schema: {
|
|
1960
|
+
...bookSchema,
|
|
1961
|
+
job_id: z.number().int().positive(),
|
|
1962
|
+
},
|
|
1963
|
+
handler: async (args) => {
|
|
1964
|
+
const jobId = Number(args.job_id);
|
|
1965
|
+
const job = engine.jobs.queue.get(jobId);
|
|
1966
|
+
if (!job)
|
|
1967
|
+
return failure(`Job ${jobId} was not found`);
|
|
1968
|
+
if (job.state !== "failed" && job.state !== "aborted") {
|
|
1969
|
+
return failure(`Job ${jobId} is ${job.state}, not retryable`);
|
|
1970
|
+
}
|
|
1971
|
+
const retryId = `retry-${job.type}-${uuidv7()}`;
|
|
1972
|
+
await queue.enqueue(retryId, {
|
|
1973
|
+
...job.payload,
|
|
1974
|
+
type: job.type,
|
|
1975
|
+
bookId: engine.book.get().bookId,
|
|
1976
|
+
});
|
|
1977
|
+
return json({ status: "enqueued", retry_job_id: retryId, source_job_id: jobId });
|
|
1978
|
+
},
|
|
1979
|
+
},
|
|
1980
|
+
{
|
|
1981
|
+
name: "list_book_actions",
|
|
1982
|
+
description: "List authored semantic revisions for a book",
|
|
1983
|
+
scope: "book",
|
|
1984
|
+
schema: { ...bookSchema, limit: z.number().int().positive().max(10_000).optional(), cursor: z.string().optional() },
|
|
1985
|
+
handler: async (args) => {
|
|
1986
|
+
const limit = typeof args.limit === "number" ? args.limit : 100;
|
|
1987
|
+
return json({
|
|
1988
|
+
actions: engine.history.revisions(limit),
|
|
1989
|
+
nextCursor: null,
|
|
1990
|
+
});
|
|
1991
|
+
},
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
name: "get_history_action",
|
|
1995
|
+
description: "Read one authored semantic revision",
|
|
1996
|
+
scope: "book",
|
|
1997
|
+
schema: { ...bookSchema, action_id: z.string().min(1) },
|
|
1998
|
+
handler: async (args) => {
|
|
1999
|
+
const revision = engine.history.resolveRevision(String(args.action_id));
|
|
2000
|
+
return revision
|
|
2001
|
+
? json(revision)
|
|
2002
|
+
: failure(`Revision not found: ${String(args.action_id)}`);
|
|
2003
|
+
},
|
|
2004
|
+
},
|
|
2005
|
+
{
|
|
2006
|
+
name: "storage_status",
|
|
2007
|
+
description: "Read catalog and object publication status",
|
|
2008
|
+
scope: "book",
|
|
2009
|
+
schema: bookSchema,
|
|
2010
|
+
handler: async () => json(engine.storage.status()),
|
|
2011
|
+
},
|
|
2012
|
+
{
|
|
2013
|
+
name: "storage_sync",
|
|
2014
|
+
description: "Publish a book's pending content-addressed objects and catalog",
|
|
2015
|
+
scope: "book",
|
|
2016
|
+
schema: bookSchema,
|
|
2017
|
+
handler: async () => json(await engine.storage.backup()),
|
|
2018
|
+
},
|
|
2019
|
+
...entityDefs(engine),
|
|
2020
|
+
];
|
|
2021
|
+
}
|