@nodetool-ai/agents 0.7.0-rc.22 → 0.8.1
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/README.md +35 -0
- package/dist/.nodetool-build-stamp +1 -0
- package/dist/analysis/audio-dsp.d.ts +223 -0
- package/dist/analysis/audio-dsp.d.ts.map +1 -0
- package/dist/analysis/audio-dsp.js +642 -0
- package/dist/analysis/audio-dsp.js.map +1 -0
- package/dist/analysis/media-decode.d.ts +85 -0
- package/dist/analysis/media-decode.d.ts.map +1 -0
- package/dist/analysis/media-decode.js +199 -0
- package/dist/analysis/media-decode.js.map +1 -0
- package/dist/analysis/mediabunny-runtime.d.ts +15 -0
- package/dist/analysis/mediabunny-runtime.d.ts.map +1 -0
- package/dist/analysis/mediabunny-runtime.js +30 -0
- package/dist/analysis/mediabunny-runtime.js.map +1 -0
- package/dist/analysis/video-frames.d.ts +120 -0
- package/dist/analysis/video-frames.d.ts.map +1 -0
- package/dist/analysis/video-frames.js +288 -0
- package/dist/analysis/video-frames.js.map +1 -0
- package/dist/apify/assets.d.ts +80 -0
- package/dist/apify/assets.d.ts.map +1 -0
- package/dist/apify/assets.js +192 -0
- package/dist/apify/assets.js.map +1 -0
- package/dist/apify/catalog.d.ts +44 -0
- package/dist/apify/catalog.d.ts.map +1 -0
- package/dist/apify/catalog.js +108 -0
- package/dist/apify/catalog.js.map +1 -0
- package/dist/apify/client.d.ts +137 -0
- package/dist/apify/client.d.ts.map +1 -0
- package/dist/apify/client.js +346 -0
- package/dist/apify/client.js.map +1 -0
- package/dist/apify/errors.d.ts +85 -0
- package/dist/apify/errors.d.ts.map +1 -0
- package/dist/apify/errors.js +90 -0
- package/dist/apify/errors.js.map +1 -0
- package/dist/apify/normalize.d.ts +131 -0
- package/dist/apify/normalize.d.ts.map +1 -0
- package/dist/apify/normalize.js +202 -0
- package/dist/apify/normalize.js.map +1 -0
- package/dist/apify/policy.d.ts +136 -0
- package/dist/apify/policy.d.ts.map +1 -0
- package/dist/apify/policy.js +223 -0
- package/dist/apify/policy.js.map +1 -0
- package/dist/apify/run.d.ts +86 -0
- package/dist/apify/run.d.ts.map +1 -0
- package/dist/apify/run.js +158 -0
- package/dist/apify/run.js.map +1 -0
- package/dist/app-build/author.d.ts +67 -0
- package/dist/app-build/author.d.ts.map +1 -0
- package/dist/app-build/author.js +231 -0
- package/dist/app-build/author.js.map +1 -0
- package/dist/app-build/bridge.d.ts +130 -0
- package/dist/app-build/bridge.d.ts.map +1 -0
- package/dist/app-build/bridge.js +647 -0
- package/dist/app-build/bridge.js.map +1 -0
- package/dist/app-build/build-service.d.ts +66 -0
- package/dist/app-build/build-service.d.ts.map +1 -0
- package/dist/app-build/build-service.js +249 -0
- package/dist/app-build/build-service.js.map +1 -0
- package/dist/app-build/build.d.ts +115 -0
- package/dist/app-build/build.d.ts.map +1 -0
- package/dist/app-build/build.js +901 -0
- package/dist/app-build/build.js.map +1 -0
- package/dist/app-build/interactions.d.ts +20 -0
- package/dist/app-build/interactions.d.ts.map +1 -0
- package/dist/app-build/interactions.js +121 -0
- package/dist/app-build/interactions.js.map +1 -0
- package/dist/app-build/judge.d.ts +113 -0
- package/dist/app-build/judge.d.ts.map +1 -0
- package/dist/app-build/judge.js +318 -0
- package/dist/app-build/judge.js.map +1 -0
- package/dist/app-build/markdown.d.ts +10 -0
- package/dist/app-build/markdown.d.ts.map +1 -0
- package/dist/app-build/markdown.js +65 -0
- package/dist/app-build/markdown.js.map +1 -0
- package/dist/app-build/spec.d.ts +228 -0
- package/dist/app-build/spec.d.ts.map +1 -0
- package/dist/app-build/spec.js +651 -0
- package/dist/app-build/spec.js.map +1 -0
- package/dist/app-build/tool-loop.d.ts +84 -0
- package/dist/app-build/tool-loop.d.ts.map +1 -0
- package/dist/app-build/tool-loop.js +113 -0
- package/dist/app-build/tool-loop.js.map +1 -0
- package/dist/app-build/types.d.ts +215 -0
- package/dist/app-build/types.d.ts.map +1 -0
- package/dist/app-build/types.js +28 -0
- package/dist/app-build/types.js.map +1 -0
- package/dist/author-graph.d.ts +91 -0
- package/dist/author-graph.d.ts.map +1 -0
- package/dist/author-graph.js +319 -0
- package/dist/author-graph.js.map +1 -0
- package/dist/background-subtasks.d.ts +79 -0
- package/dist/background-subtasks.d.ts.map +1 -0
- package/dist/background-subtasks.js +139 -0
- package/dist/background-subtasks.js.map +1 -0
- package/dist/blender-gate.d.ts +21 -0
- package/dist/blender-gate.d.ts.map +1 -0
- package/dist/blender-gate.js +27 -0
- package/dist/blender-gate.js.map +1 -0
- package/dist/browser-gate.d.ts +28 -0
- package/dist/browser-gate.d.ts.map +1 -0
- package/dist/browser-gate.js +34 -0
- package/dist/browser-gate.js.map +1 -0
- package/dist/capabilities/adapters.d.ts +36 -0
- package/dist/capabilities/adapters.d.ts.map +1 -0
- package/dist/capabilities/adapters.js +44 -0
- package/dist/capabilities/adapters.js.map +1 -0
- package/dist/capabilities/agents.d.ts +78 -0
- package/dist/capabilities/agents.d.ts.map +1 -0
- package/dist/capabilities/agents.js +366 -0
- package/dist/capabilities/agents.js.map +1 -0
- package/dist/capabilities/agents.specs.d.ts +27 -0
- package/dist/capabilities/agents.specs.d.ts.map +1 -0
- package/dist/capabilities/agents.specs.js +243 -0
- package/dist/capabilities/agents.specs.js.map +1 -0
- package/dist/capabilities/analysis.d.ts +93 -0
- package/dist/capabilities/analysis.d.ts.map +1 -0
- package/dist/capabilities/analysis.js +812 -0
- package/dist/capabilities/analysis.js.map +1 -0
- package/dist/capabilities/analysis.specs.d.ts +39 -0
- package/dist/capabilities/analysis.specs.d.ts.map +1 -0
- package/dist/capabilities/analysis.specs.js +217 -0
- package/dist/capabilities/analysis.specs.js.map +1 -0
- package/dist/capabilities/apify.d.ts +34 -0
- package/dist/capabilities/apify.d.ts.map +1 -0
- package/dist/capabilities/apify.js +449 -0
- package/dist/capabilities/apify.js.map +1 -0
- package/dist/capabilities/apify.specs.d.ts +28 -0
- package/dist/capabilities/apify.specs.d.ts.map +1 -0
- package/dist/capabilities/apify.specs.js +232 -0
- package/dist/capabilities/apify.specs.js.map +1 -0
- package/dist/capabilities/apps.d.ts +40 -0
- package/dist/capabilities/apps.d.ts.map +1 -0
- package/dist/capabilities/apps.js +413 -0
- package/dist/capabilities/apps.js.map +1 -0
- package/dist/capabilities/apps.specs.d.ts +23 -0
- package/dist/capabilities/apps.specs.d.ts.map +1 -0
- package/dist/capabilities/apps.specs.js +236 -0
- package/dist/capabilities/apps.specs.js.map +1 -0
- package/dist/capabilities/args.d.ts +65 -0
- package/dist/capabilities/args.d.ts.map +1 -0
- package/dist/capabilities/args.js +218 -0
- package/dist/capabilities/args.js.map +1 -0
- package/dist/capabilities/assets.d.ts +45 -0
- package/dist/capabilities/assets.d.ts.map +1 -0
- package/dist/capabilities/assets.js +952 -0
- package/dist/capabilities/assets.js.map +1 -0
- package/dist/capabilities/assets.specs.d.ts +54 -0
- package/dist/capabilities/assets.specs.d.ts.map +1 -0
- package/dist/capabilities/assets.specs.js +291 -0
- package/dist/capabilities/assets.specs.js.map +1 -0
- package/dist/capabilities/background-generation.d.ts +14 -0
- package/dist/capabilities/background-generation.d.ts.map +1 -0
- package/dist/capabilities/background-generation.js +29 -0
- package/dist/capabilities/background-generation.js.map +1 -0
- package/dist/capabilities/browser.d.ts +30 -0
- package/dist/capabilities/browser.d.ts.map +1 -0
- package/dist/capabilities/browser.js +250 -0
- package/dist/capabilities/browser.js.map +1 -0
- package/dist/capabilities/browser.specs.d.ts +37 -0
- package/dist/capabilities/browser.specs.d.ts.map +1 -0
- package/dist/capabilities/browser.specs.js +173 -0
- package/dist/capabilities/browser.specs.js.map +1 -0
- package/dist/capabilities/code-grading.d.ts +68 -0
- package/dist/capabilities/code-grading.d.ts.map +1 -0
- package/dist/capabilities/code-grading.js +75 -0
- package/dist/capabilities/code-grading.js.map +1 -0
- package/dist/capabilities/code.d.ts +56 -0
- package/dist/capabilities/code.d.ts.map +1 -0
- package/dist/capabilities/code.js +327 -0
- package/dist/capabilities/code.js.map +1 -0
- package/dist/capabilities/code.specs.d.ts +22 -0
- package/dist/capabilities/code.specs.d.ts.map +1 -0
- package/dist/capabilities/code.specs.js +175 -0
- package/dist/capabilities/code.specs.js.map +1 -0
- package/dist/capabilities/collections.d.ts +44 -0
- package/dist/capabilities/collections.d.ts.map +1 -0
- package/dist/capabilities/collections.js +432 -0
- package/dist/capabilities/collections.js.map +1 -0
- package/dist/capabilities/collections.specs.d.ts +25 -0
- package/dist/capabilities/collections.specs.d.ts.map +1 -0
- package/dist/capabilities/collections.specs.js +299 -0
- package/dist/capabilities/collections.specs.js.map +1 -0
- package/dist/capabilities/compositions.d.ts +57 -0
- package/dist/capabilities/compositions.d.ts.map +1 -0
- package/dist/capabilities/compositions.js +384 -0
- package/dist/capabilities/compositions.js.map +1 -0
- package/dist/capabilities/compositions.specs.d.ts +22 -0
- package/dist/capabilities/compositions.specs.d.ts.map +1 -0
- package/dist/capabilities/compositions.specs.js +126 -0
- package/dist/capabilities/compositions.specs.js.map +1 -0
- package/dist/capabilities/concurrency.d.ts +12 -0
- package/dist/capabilities/concurrency.d.ts.map +1 -0
- package/dist/capabilities/concurrency.js +30 -0
- package/dist/capabilities/concurrency.js.map +1 -0
- package/dist/capabilities/costs.d.ts +20 -0
- package/dist/capabilities/costs.d.ts.map +1 -0
- package/dist/capabilities/costs.js +40 -0
- package/dist/capabilities/costs.js.map +1 -0
- package/dist/capabilities/costs.specs.d.ts +12 -0
- package/dist/capabilities/costs.specs.d.ts.map +1 -0
- package/dist/capabilities/costs.specs.js +35 -0
- package/dist/capabilities/costs.specs.js.map +1 -0
- package/dist/capabilities/dispatcher.d.ts +57 -0
- package/dist/capabilities/dispatcher.d.ts.map +1 -0
- package/dist/capabilities/dispatcher.js +117 -0
- package/dist/capabilities/dispatcher.js.map +1 -0
- package/dist/capabilities/documents.d.ts +25 -0
- package/dist/capabilities/documents.d.ts.map +1 -0
- package/dist/capabilities/documents.js +278 -0
- package/dist/capabilities/documents.js.map +1 -0
- package/dist/capabilities/documents.specs.d.ts +18 -0
- package/dist/capabilities/documents.specs.d.ts.map +1 -0
- package/dist/capabilities/documents.specs.js +201 -0
- package/dist/capabilities/documents.specs.js.map +1 -0
- package/dist/capabilities/email.d.ts +23 -0
- package/dist/capabilities/email.d.ts.map +1 -0
- package/dist/capabilities/email.js +226 -0
- package/dist/capabilities/email.js.map +1 -0
- package/dist/capabilities/email.specs.d.ts +16 -0
- package/dist/capabilities/email.specs.d.ts.map +1 -0
- package/dist/capabilities/email.specs.js +102 -0
- package/dist/capabilities/email.specs.js.map +1 -0
- package/dist/capabilities/entities.d.ts +52 -0
- package/dist/capabilities/entities.d.ts.map +1 -0
- package/dist/capabilities/entities.js +478 -0
- package/dist/capabilities/entities.js.map +1 -0
- package/dist/capabilities/entities.specs.d.ts +25 -0
- package/dist/capabilities/entities.specs.d.ts.map +1 -0
- package/dist/capabilities/entities.specs.js +247 -0
- package/dist/capabilities/entities.specs.js.map +1 -0
- package/dist/capabilities/example-timelines.d.ts +3 -0
- package/dist/capabilities/example-timelines.d.ts.map +1 -0
- package/dist/capabilities/example-timelines.js +191 -0
- package/dist/capabilities/example-timelines.js.map +1 -0
- package/dist/capabilities/example-timelines.specs.d.ts +5 -0
- package/dist/capabilities/example-timelines.specs.d.ts.map +1 -0
- package/dist/capabilities/example-timelines.specs.js +53 -0
- package/dist/capabilities/example-timelines.specs.js.map +1 -0
- package/dist/capabilities/files.d.ts +53 -0
- package/dist/capabilities/files.d.ts.map +1 -0
- package/dist/capabilities/files.js +1172 -0
- package/dist/capabilities/files.js.map +1 -0
- package/dist/capabilities/files.specs.d.ts +28 -0
- package/dist/capabilities/files.specs.d.ts.map +1 -0
- package/dist/capabilities/files.specs.js +259 -0
- package/dist/capabilities/files.specs.js.map +1 -0
- package/dist/capabilities/flow.d.ts +93 -0
- package/dist/capabilities/flow.d.ts.map +1 -0
- package/dist/capabilities/flow.js +285 -0
- package/dist/capabilities/flow.js.map +1 -0
- package/dist/capabilities/flow.specs.d.ts +25 -0
- package/dist/capabilities/flow.specs.d.ts.map +1 -0
- package/dist/capabilities/flow.specs.js +114 -0
- package/dist/capabilities/flow.specs.js.map +1 -0
- package/dist/capabilities/game-asset-source.d.ts +31 -0
- package/dist/capabilities/game-asset-source.d.ts.map +1 -0
- package/dist/capabilities/game-asset-source.js +81 -0
- package/dist/capabilities/game-asset-source.js.map +1 -0
- package/dist/capabilities/game.d.ts +12 -0
- package/dist/capabilities/game.d.ts.map +1 -0
- package/dist/capabilities/game.js +1262 -0
- package/dist/capabilities/game.js.map +1 -0
- package/dist/capabilities/game.specs.d.ts +3 -0
- package/dist/capabilities/game.specs.d.ts.map +1 -0
- package/dist/capabilities/game.specs.js +96 -0
- package/dist/capabilities/game.specs.js.map +1 -0
- package/dist/capabilities/gate-from-context.d.ts +39 -0
- package/dist/capabilities/gate-from-context.d.ts.map +1 -0
- package/dist/capabilities/gate-from-context.js +97 -0
- package/dist/capabilities/gate-from-context.js.map +1 -0
- package/dist/capabilities/gate-tools.d.ts +18 -0
- package/dist/capabilities/gate-tools.d.ts.map +1 -0
- package/dist/capabilities/gate-tools.js +66 -0
- package/dist/capabilities/gate-tools.js.map +1 -0
- package/dist/capabilities/generations.d.ts +79 -0
- package/dist/capabilities/generations.d.ts.map +1 -0
- package/dist/capabilities/generations.js +382 -0
- package/dist/capabilities/generations.js.map +1 -0
- package/dist/capabilities/generations.specs.d.ts +62 -0
- package/dist/capabilities/generations.specs.d.ts.map +1 -0
- package/dist/capabilities/generations.specs.js +230 -0
- package/dist/capabilities/generations.specs.js.map +1 -0
- package/dist/capabilities/google.d.ts +50 -0
- package/dist/capabilities/google.d.ts.map +1 -0
- package/dist/capabilities/google.js +246 -0
- package/dist/capabilities/google.js.map +1 -0
- package/dist/capabilities/google.specs.d.ts +52 -0
- package/dist/capabilities/google.specs.d.ts.map +1 -0
- package/dist/capabilities/google.specs.js +391 -0
- package/dist/capabilities/google.specs.js.map +1 -0
- package/dist/capabilities/html-text.d.ts +28 -0
- package/dist/capabilities/html-text.d.ts.map +1 -0
- package/dist/capabilities/html-text.js +76 -0
- package/dist/capabilities/html-text.js.map +1 -0
- package/dist/capabilities/index.d.ts +19 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +14 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/capabilities/invoke.d.ts +89 -0
- package/dist/capabilities/invoke.d.ts.map +1 -0
- package/dist/capabilities/invoke.js +214 -0
- package/dist/capabilities/invoke.js.map +1 -0
- package/dist/capabilities/jobs.d.ts +32 -0
- package/dist/capabilities/jobs.d.ts.map +1 -0
- package/dist/capabilities/jobs.js +123 -0
- package/dist/capabilities/jobs.js.map +1 -0
- package/dist/capabilities/jobs.specs.d.ts +20 -0
- package/dist/capabilities/jobs.specs.d.ts.map +1 -0
- package/dist/capabilities/jobs.specs.js +103 -0
- package/dist/capabilities/jobs.specs.js.map +1 -0
- package/dist/capabilities/js-scripts.d.ts +90 -0
- package/dist/capabilities/js-scripts.d.ts.map +1 -0
- package/dist/capabilities/js-scripts.js +700 -0
- package/dist/capabilities/js-scripts.js.map +1 -0
- package/dist/capabilities/js-scripts.specs.d.ts +50 -0
- package/dist/capabilities/js-scripts.specs.d.ts.map +1 -0
- package/dist/capabilities/js-scripts.specs.js +344 -0
- package/dist/capabilities/js-scripts.specs.js.map +1 -0
- package/dist/capabilities/lazy-tool.d.ts +42 -0
- package/dist/capabilities/lazy-tool.d.ts.map +1 -0
- package/dist/capabilities/lazy-tool.js +88 -0
- package/dist/capabilities/lazy-tool.js.map +1 -0
- package/dist/capabilities/local-browser.d.ts +24 -0
- package/dist/capabilities/local-browser.d.ts.map +1 -0
- package/dist/capabilities/local-browser.js +105 -0
- package/dist/capabilities/local-browser.js.map +1 -0
- package/dist/capabilities/media.d.ts +121 -0
- package/dist/capabilities/media.d.ts.map +1 -0
- package/dist/capabilities/media.js +2217 -0
- package/dist/capabilities/media.js.map +1 -0
- package/dist/capabilities/media.specs.d.ts +65 -0
- package/dist/capabilities/media.specs.d.ts.map +1 -0
- package/dist/capabilities/media.specs.js +779 -0
- package/dist/capabilities/media.specs.js.map +1 -0
- package/dist/capabilities/memory.d.ts +31 -0
- package/dist/capabilities/memory.d.ts.map +1 -0
- package/dist/capabilities/memory.js +403 -0
- package/dist/capabilities/memory.js.map +1 -0
- package/dist/capabilities/memory.specs.d.ts +55 -0
- package/dist/capabilities/memory.specs.d.ts.map +1 -0
- package/dist/capabilities/memory.specs.js +208 -0
- package/dist/capabilities/memory.specs.js.map +1 -0
- package/dist/capabilities/model3d.d.ts +29 -0
- package/dist/capabilities/model3d.d.ts.map +1 -0
- package/dist/capabilities/model3d.js +482 -0
- package/dist/capabilities/model3d.js.map +1 -0
- package/dist/capabilities/model3d.specs.d.ts +28 -0
- package/dist/capabilities/model3d.specs.d.ts.map +1 -0
- package/dist/capabilities/model3d.specs.js +305 -0
- package/dist/capabilities/model3d.specs.js.map +1 -0
- package/dist/capabilities/models.d.ts +138 -0
- package/dist/capabilities/models.d.ts.map +1 -0
- package/dist/capabilities/models.js +803 -0
- package/dist/capabilities/models.js.map +1 -0
- package/dist/capabilities/models.specs.d.ts +21 -0
- package/dist/capabilities/models.specs.d.ts.map +1 -0
- package/dist/capabilities/models.specs.js +141 -0
- package/dist/capabilities/models.specs.js.map +1 -0
- package/dist/capabilities/nodes.d.ts +26 -0
- package/dist/capabilities/nodes.d.ts.map +1 -0
- package/dist/capabilities/nodes.js +266 -0
- package/dist/capabilities/nodes.js.map +1 -0
- package/dist/capabilities/nodes.specs.d.ts +20 -0
- package/dist/capabilities/nodes.specs.d.ts.map +1 -0
- package/dist/capabilities/nodes.specs.js +106 -0
- package/dist/capabilities/nodes.specs.js.map +1 -0
- package/dist/capabilities/packs.d.ts +69 -0
- package/dist/capabilities/packs.d.ts.map +1 -0
- package/dist/capabilities/packs.js +246 -0
- package/dist/capabilities/packs.js.map +1 -0
- package/dist/capabilities/packs.specs.d.ts +27 -0
- package/dist/capabilities/packs.specs.d.ts.map +1 -0
- package/dist/capabilities/packs.specs.js +64 -0
- package/dist/capabilities/packs.specs.js.map +1 -0
- package/dist/capabilities/project-scope.d.ts +12 -0
- package/dist/capabilities/project-scope.d.ts.map +1 -0
- package/dist/capabilities/project-scope.js +19 -0
- package/dist/capabilities/project-scope.js.map +1 -0
- package/dist/capabilities/projects.d.ts +23 -0
- package/dist/capabilities/projects.d.ts.map +1 -0
- package/dist/capabilities/projects.js +206 -0
- package/dist/capabilities/projects.js.map +1 -0
- package/dist/capabilities/projects.specs.d.ts +23 -0
- package/dist/capabilities/projects.specs.d.ts.map +1 -0
- package/dist/capabilities/projects.specs.js +118 -0
- package/dist/capabilities/projects.specs.js.map +1 -0
- package/dist/capabilities/registry.d.ts +109 -0
- package/dist/capabilities/registry.d.ts.map +1 -0
- package/dist/capabilities/registry.js +445 -0
- package/dist/capabilities/registry.js.map +1 -0
- package/dist/capabilities/script-link.d.ts +22 -0
- package/dist/capabilities/script-link.d.ts.map +1 -0
- package/dist/capabilities/script-link.js +35 -0
- package/dist/capabilities/script-link.js.map +1 -0
- package/dist/capabilities/scripts.d.ts +43 -0
- package/dist/capabilities/scripts.d.ts.map +1 -0
- package/dist/capabilities/scripts.js +1789 -0
- package/dist/capabilities/scripts.js.map +1 -0
- package/dist/capabilities/scripts.specs.d.ts +40 -0
- package/dist/capabilities/scripts.specs.d.ts.map +1 -0
- package/dist/capabilities/scripts.specs.js +319 -0
- package/dist/capabilities/scripts.specs.js.map +1 -0
- package/dist/capabilities/serpapi.d.ts +42 -0
- package/dist/capabilities/serpapi.d.ts.map +1 -0
- package/dist/capabilities/serpapi.js +262 -0
- package/dist/capabilities/serpapi.js.map +1 -0
- package/dist/capabilities/serpapi.specs.d.ts +24 -0
- package/dist/capabilities/serpapi.specs.d.ts.map +1 -0
- package/dist/capabilities/serpapi.specs.js +174 -0
- package/dist/capabilities/serpapi.specs.js.map +1 -0
- package/dist/capabilities/settings.d.ts +48 -0
- package/dist/capabilities/settings.d.ts.map +1 -0
- package/dist/capabilities/settings.js +226 -0
- package/dist/capabilities/settings.js.map +1 -0
- package/dist/capabilities/settings.specs.d.ts +42 -0
- package/dist/capabilities/settings.specs.d.ts.map +1 -0
- package/dist/capabilities/settings.specs.js +126 -0
- package/dist/capabilities/settings.specs.js.map +1 -0
- package/dist/capabilities/shared.d.ts +35 -0
- package/dist/capabilities/shared.d.ts.map +1 -0
- package/dist/capabilities/shared.js +165 -0
- package/dist/capabilities/shared.js.map +1 -0
- package/dist/capabilities/shared.specs.d.ts +19 -0
- package/dist/capabilities/shared.specs.d.ts.map +1 -0
- package/dist/capabilities/shared.specs.js +134 -0
- package/dist/capabilities/shared.specs.js.map +1 -0
- package/dist/capabilities/sketches.d.ts +52 -0
- package/dist/capabilities/sketches.d.ts.map +1 -0
- package/dist/capabilities/sketches.js +1163 -0
- package/dist/capabilities/sketches.js.map +1 -0
- package/dist/capabilities/sketches.specs.d.ts +44 -0
- package/dist/capabilities/sketches.specs.d.ts.map +1 -0
- package/dist/capabilities/sketches.specs.js +392 -0
- package/dist/capabilities/sketches.specs.js.map +1 -0
- package/dist/capabilities/skills.d.ts +28 -0
- package/dist/capabilities/skills.d.ts.map +1 -0
- package/dist/capabilities/skills.js +269 -0
- package/dist/capabilities/skills.js.map +1 -0
- package/dist/capabilities/skills.specs.d.ts +17 -0
- package/dist/capabilities/skills.specs.d.ts.map +1 -0
- package/dist/capabilities/skills.specs.js +108 -0
- package/dist/capabilities/skills.specs.js.map +1 -0
- package/dist/capabilities/storyboards.d.ts +68 -0
- package/dist/capabilities/storyboards.d.ts.map +1 -0
- package/dist/capabilities/storyboards.js +2246 -0
- package/dist/capabilities/storyboards.js.map +1 -0
- package/dist/capabilities/storyboards.specs.d.ts +42 -0
- package/dist/capabilities/storyboards.specs.d.ts.map +1 -0
- package/dist/capabilities/storyboards.specs.js +418 -0
- package/dist/capabilities/storyboards.specs.js.map +1 -0
- package/dist/capabilities/threads.d.ts +25 -0
- package/dist/capabilities/threads.d.ts.map +1 -0
- package/dist/capabilities/threads.js +240 -0
- package/dist/capabilities/threads.js.map +1 -0
- package/dist/capabilities/threads.specs.d.ts +26 -0
- package/dist/capabilities/threads.specs.d.ts.map +1 -0
- package/dist/capabilities/threads.specs.js +108 -0
- package/dist/capabilities/threads.specs.js.map +1 -0
- package/dist/capabilities/timeline-audio-bake.d.ts +114 -0
- package/dist/capabilities/timeline-audio-bake.d.ts.map +1 -0
- package/dist/capabilities/timeline-audio-bake.js +188 -0
- package/dist/capabilities/timeline-audio-bake.js.map +1 -0
- package/dist/capabilities/timeline-bake.d.ts +21 -0
- package/dist/capabilities/timeline-bake.d.ts.map +1 -0
- package/dist/capabilities/timeline-bake.js +118 -0
- package/dist/capabilities/timeline-bake.js.map +1 -0
- package/dist/capabilities/timeline-isolate-subject.d.ts +157 -0
- package/dist/capabilities/timeline-isolate-subject.d.ts.map +1 -0
- package/dist/capabilities/timeline-isolate-subject.js +293 -0
- package/dist/capabilities/timeline-isolate-subject.js.map +1 -0
- package/dist/capabilities/timeline-native-media.d.ts +4 -0
- package/dist/capabilities/timeline-native-media.d.ts.map +1 -0
- package/dist/capabilities/timeline-native-media.js +920 -0
- package/dist/capabilities/timeline-native-media.js.map +1 -0
- package/dist/capabilities/timeline-track-object.d.ts +83 -0
- package/dist/capabilities/timeline-track-object.d.ts.map +1 -0
- package/dist/capabilities/timeline-track-object.js +195 -0
- package/dist/capabilities/timeline-track-object.js.map +1 -0
- package/dist/capabilities/timelines.d.ts +190 -0
- package/dist/capabilities/timelines.d.ts.map +1 -0
- package/dist/capabilities/timelines.js +2326 -0
- package/dist/capabilities/timelines.js.map +1 -0
- package/dist/capabilities/timelines.specs.d.ts +134 -0
- package/dist/capabilities/timelines.specs.d.ts.map +1 -0
- package/dist/capabilities/timelines.specs.js +1362 -0
- package/dist/capabilities/timelines.specs.js.map +1 -0
- package/dist/capabilities/types.d.ts +197 -0
- package/dist/capabilities/types.d.ts.map +1 -0
- package/dist/capabilities/types.js +19 -0
- package/dist/capabilities/types.js.map +1 -0
- package/dist/capabilities/ui.d.ts +38 -0
- package/dist/capabilities/ui.d.ts.map +1 -0
- package/dist/capabilities/ui.js +128 -0
- package/dist/capabilities/ui.js.map +1 -0
- package/dist/capabilities/ui.specs.d.ts +22 -0
- package/dist/capabilities/ui.specs.d.ts.map +1 -0
- package/dist/capabilities/ui.specs.js +59 -0
- package/dist/capabilities/ui.specs.js.map +1 -0
- package/dist/capabilities/video-production-acceptance.d.ts +27 -0
- package/dist/capabilities/video-production-acceptance.d.ts.map +1 -0
- package/dist/capabilities/video-production-acceptance.js +41 -0
- package/dist/capabilities/video-production-acceptance.js.map +1 -0
- package/dist/capabilities/video-production.d.ts +179 -0
- package/dist/capabilities/video-production.d.ts.map +1 -0
- package/dist/capabilities/video-production.js +1492 -0
- package/dist/capabilities/video-production.js.map +1 -0
- package/dist/capabilities/video-production.specs.d.ts +13 -0
- package/dist/capabilities/video-production.specs.d.ts.map +1 -0
- package/dist/capabilities/video-production.specs.js +256 -0
- package/dist/capabilities/video-production.specs.js.map +1 -0
- package/dist/capabilities/web.d.ts +86 -0
- package/dist/capabilities/web.d.ts.map +1 -0
- package/dist/capabilities/web.js +850 -0
- package/dist/capabilities/web.js.map +1 -0
- package/dist/capabilities/web.specs.d.ts +33 -0
- package/dist/capabilities/web.specs.d.ts.map +1 -0
- package/dist/capabilities/web.specs.js +286 -0
- package/dist/capabilities/web.specs.js.map +1 -0
- package/dist/capabilities/workflows.d.ts +58 -0
- package/dist/capabilities/workflows.d.ts.map +1 -0
- package/dist/capabilities/workflows.js +1530 -0
- package/dist/capabilities/workflows.js.map +1 -0
- package/dist/capabilities/workflows.specs.d.ts +63 -0
- package/dist/capabilities/workflows.specs.d.ts.map +1 -0
- package/dist/capabilities/workflows.specs.js +852 -0
- package/dist/capabilities/workflows.specs.js.map +1 -0
- package/dist/code-gen/analyze.d.ts +26 -0
- package/dist/code-gen/analyze.d.ts.map +1 -0
- package/dist/code-gen/analyze.js +79 -0
- package/dist/code-gen/analyze.js.map +1 -0
- package/dist/code-gen/port-types.d.ts +58 -0
- package/dist/code-gen/port-types.d.ts.map +1 -0
- package/dist/code-gen/port-types.js +115 -0
- package/dist/code-gen/port-types.js.map +1 -0
- package/dist/code-gen/prompt.d.ts +37 -0
- package/dist/code-gen/prompt.d.ts.map +1 -0
- package/dist/code-gen/prompt.js +129 -0
- package/dist/code-gen/prompt.js.map +1 -0
- package/dist/code-gen/sandbox-manifest.d.ts +120 -0
- package/dist/code-gen/sandbox-manifest.d.ts.map +1 -0
- package/dist/code-gen/sandbox-manifest.js +1023 -0
- package/dist/code-gen/sandbox-manifest.js.map +1 -0
- package/dist/code-gen/sandbox-prompt.d.ts +26 -0
- package/dist/code-gen/sandbox-prompt.d.ts.map +1 -0
- package/dist/code-gen/sandbox-prompt.js +126 -0
- package/dist/code-gen/sandbox-prompt.js.map +1 -0
- package/dist/code-planner.d.ts +72 -0
- package/dist/code-planner.d.ts.map +1 -0
- package/dist/code-planner.js +271 -0
- package/dist/code-planner.js.map +1 -0
- package/dist/codeact/action-diagnostics.d.ts +35 -0
- package/dist/codeact/action-diagnostics.d.ts.map +1 -0
- package/dist/codeact/action-diagnostics.js +147 -0
- package/dist/codeact/action-diagnostics.js.map +1 -0
- package/dist/codeact/capability-modules.d.ts +66 -0
- package/dist/codeact/capability-modules.d.ts.map +1 -0
- package/dist/codeact/capability-modules.js +278 -0
- package/dist/codeact/capability-modules.js.map +1 -0
- package/dist/codeact/chat-codeact.d.ts +104 -0
- package/dist/codeact/chat-codeact.d.ts.map +1 -0
- package/dist/codeact/chat-codeact.js +424 -0
- package/dist/codeact/chat-codeact.js.map +1 -0
- package/dist/codeact/codeact-executor.d.ts +247 -0
- package/dist/codeact/codeact-executor.d.ts.map +1 -0
- package/dist/codeact/codeact-executor.js +1176 -0
- package/dist/codeact/codeact-executor.js.map +1 -0
- package/dist/codeact/compact-ids.d.ts +28 -0
- package/dist/codeact/compact-ids.d.ts.map +1 -0
- package/dist/codeact/compact-ids.js +77 -0
- package/dist/codeact/compact-ids.js.map +1 -0
- package/dist/codeact/execute-code-contract.d.ts +97 -0
- package/dist/codeact/execute-code-contract.d.ts.map +1 -0
- package/dist/codeact/execute-code-contract.js +225 -0
- package/dist/codeact/execute-code-contract.js.map +1 -0
- package/dist/codeact/fabric-package.d.ts +29 -0
- package/dist/codeact/fabric-package.d.ts.map +1 -0
- package/dist/codeact/fabric-package.js +62 -0
- package/dist/codeact/fabric-package.js.map +1 -0
- package/dist/codeact/flow-package.d.ts +31 -0
- package/dist/codeact/flow-package.d.ts.map +1 -0
- package/dist/codeact/flow-package.js +80 -0
- package/dist/codeact/flow-package.js.map +1 -0
- package/dist/codeact/graph-dsl-package.d.ts +42 -0
- package/dist/codeact/graph-dsl-package.d.ts.map +1 -0
- package/dist/codeact/graph-dsl-package.js +100 -0
- package/dist/codeact/graph-dsl-package.js.map +1 -0
- package/dist/codeact/graph-model.d.ts +30 -0
- package/dist/codeact/graph-model.d.ts.map +1 -0
- package/dist/codeact/graph-model.js +408 -0
- package/dist/codeact/graph-model.js.map +1 -0
- package/dist/codeact/mcp-guest-contract.d.ts +66 -0
- package/dist/codeact/mcp-guest-contract.d.ts.map +1 -0
- package/dist/codeact/mcp-guest-contract.js +108 -0
- package/dist/codeact/mcp-guest-contract.js.map +1 -0
- package/dist/codeact/nodetool-api.d.ts +60 -0
- package/dist/codeact/nodetool-api.d.ts.map +1 -0
- package/dist/codeact/nodetool-api.js +2056 -0
- package/dist/codeact/nodetool-api.js.map +1 -0
- package/dist/codeact/prompt.d.ts +88 -0
- package/dist/codeact/prompt.d.ts.map +1 -0
- package/dist/codeact/prompt.js +326 -0
- package/dist/codeact/prompt.js.map +1 -0
- package/dist/codeact/sandbox-package-docs.d.ts +48 -0
- package/dist/codeact/sandbox-package-docs.d.ts.map +1 -0
- package/dist/codeact/sandbox-package-docs.js +55 -0
- package/dist/codeact/sandbox-package-docs.js.map +1 -0
- package/dist/codeact/sandbox-package-listing.d.ts +81 -0
- package/dist/codeact/sandbox-package-listing.d.ts.map +1 -0
- package/dist/codeact/sandbox-package-listing.js +101 -0
- package/dist/codeact/sandbox-package-listing.js.map +1 -0
- package/dist/codeact/sandbox-packages.d.ts +72 -0
- package/dist/codeact/sandbox-packages.d.ts.map +1 -0
- package/dist/codeact/sandbox-packages.js +230 -0
- package/dist/codeact/sandbox-packages.js.map +1 -0
- package/dist/codeact/timeline-package.d.ts +5 -0
- package/dist/codeact/timeline-package.d.ts.map +1 -0
- package/dist/codeact/timeline-package.js +10 -0
- package/dist/codeact/timeline-package.js.map +1 -0
- package/dist/codeact/tool-api.d.ts +197 -0
- package/dist/codeact/tool-api.d.ts.map +1 -0
- package/dist/codeact/tool-api.js +445 -0
- package/dist/codeact/tool-api.js.map +1 -0
- package/dist/codeact/tools-prelude.d.ts +32 -0
- package/dist/codeact/tools-prelude.d.ts.map +1 -0
- package/dist/codeact/tools-prelude.js +65 -0
- package/dist/codeact/tools-prelude.js.map +1 -0
- package/dist/constants.d.ts +47 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +64 -2
- package/dist/constants.js.map +1 -1
- package/dist/custom-animation-bake.d.ts +57 -0
- package/dist/custom-animation-bake.d.ts.map +1 -0
- package/dist/custom-animation-bake.js +88 -0
- package/dist/custom-animation-bake.js.map +1 -0
- package/dist/dynamic-slots.d.ts +72 -0
- package/dist/dynamic-slots.d.ts.map +1 -0
- package/dist/dynamic-slots.js +0 -0
- package/dist/dynamic-slots.js.map +1 -0
- package/dist/evals/app-build-cases.d.ts +35 -0
- package/dist/evals/app-build-cases.d.ts.map +1 -0
- package/dist/evals/app-build-cases.js +722 -0
- package/dist/evals/app-build-cases.js.map +1 -0
- package/dist/evals/app-build-eval.d.ts +208 -0
- package/dist/evals/app-build-eval.d.ts.map +1 -0
- package/dist/evals/app-build-eval.js +518 -0
- package/dist/evals/app-build-eval.js.map +1 -0
- package/dist/evals/app-build-plan.d.ts +76 -0
- package/dist/evals/app-build-plan.d.ts.map +1 -0
- package/dist/evals/app-build-plan.js +140 -0
- package/dist/evals/app-build-plan.js.map +1 -0
- package/dist/evals/code-gen-cases.d.ts +13 -0
- package/dist/evals/code-gen-cases.d.ts.map +1 -0
- package/dist/evals/code-gen-cases.js +124 -0
- package/dist/evals/code-gen-cases.js.map +1 -0
- package/dist/evals/code-gen-eval.d.ts +109 -0
- package/dist/evals/code-gen-eval.d.ts.map +1 -0
- package/dist/evals/code-gen-eval.js +299 -0
- package/dist/evals/code-gen-eval.js.map +1 -0
- package/dist/evals/codeact-api-cases.d.ts +14 -0
- package/dist/evals/codeact-api-cases.d.ts.map +1 -0
- package/dist/evals/codeact-api-cases.js +29 -0
- package/dist/evals/codeact-api-cases.js.map +1 -0
- package/dist/evals/codeact-api-core.d.ts +23 -0
- package/dist/evals/codeact-api-core.d.ts.map +1 -0
- package/dist/evals/codeact-api-core.js +1411 -0
- package/dist/evals/codeact-api-core.js.map +1 -0
- package/dist/evals/codeact-api-surfaces.d.ts +25 -0
- package/dist/evals/codeact-api-surfaces.d.ts.map +1 -0
- package/dist/evals/codeact-api-surfaces.js +2191 -0
- package/dist/evals/codeact-api-surfaces.js.map +1 -0
- package/dist/evals/codeact-cases.d.ts +76 -0
- package/dist/evals/codeact-cases.d.ts.map +1 -0
- package/dist/evals/codeact-cases.js +151 -0
- package/dist/evals/codeact-cases.js.map +1 -0
- package/dist/evals/codeact-eval.d.ts +66 -0
- package/dist/evals/codeact-eval.d.ts.map +1 -0
- package/dist/evals/codeact-eval.js +250 -0
- package/dist/evals/codeact-eval.js.map +1 -0
- package/dist/evals/codeact-sandbox-pack-cases.d.ts +31 -0
- package/dist/evals/codeact-sandbox-pack-cases.d.ts.map +1 -0
- package/dist/evals/codeact-sandbox-pack-cases.js +219 -0
- package/dist/evals/codeact-sandbox-pack-cases.js.map +1 -0
- package/dist/evals/escalation-cases.d.ts +22 -0
- package/dist/evals/escalation-cases.d.ts.map +1 -0
- package/dist/evals/escalation-cases.js +343 -0
- package/dist/evals/escalation-cases.js.map +1 -0
- package/dist/evals/escalation.d.ts +92 -0
- package/dist/evals/escalation.d.ts.map +1 -0
- package/dist/evals/escalation.js +152 -0
- package/dist/evals/escalation.js.map +1 -0
- package/dist/evals/goal-judge.d.ts +51 -0
- package/dist/evals/goal-judge.d.ts.map +1 -0
- package/dist/evals/goal-judge.js +152 -0
- package/dist/evals/goal-judge.js.map +1 -0
- package/dist/evals/graph-e2e-cases.d.ts +21 -0
- package/dist/evals/graph-e2e-cases.d.ts.map +1 -0
- package/dist/evals/graph-e2e-cases.js +262 -0
- package/dist/evals/graph-e2e-cases.js.map +1 -0
- package/dist/evals/graph-e2e-eval.d.ts +202 -0
- package/dist/evals/graph-e2e-eval.d.ts.map +1 -0
- package/dist/evals/graph-e2e-eval.js +438 -0
- package/dist/evals/graph-e2e-eval.js.map +1 -0
- package/dist/evals/graph-planner-cases.d.ts +12 -0
- package/dist/evals/graph-planner-cases.d.ts.map +1 -0
- package/dist/evals/graph-planner-cases.js +256 -0
- package/dist/evals/graph-planner-cases.js.map +1 -0
- package/dist/evals/graph-planner-eval.d.ts +178 -0
- package/dist/evals/graph-planner-eval.d.ts.map +1 -0
- package/dist/evals/graph-planner-eval.js +495 -0
- package/dist/evals/graph-planner-eval.js.map +1 -0
- package/dist/evals/planner-tools.d.ts +21 -0
- package/dist/evals/planner-tools.d.ts.map +1 -0
- package/dist/evals/planner-tools.js +66 -0
- package/dist/evals/planner-tools.js.map +1 -0
- package/dist/evals/subtask-cases.d.ts +88 -0
- package/dist/evals/subtask-cases.d.ts.map +1 -0
- package/dist/evals/subtask-cases.js +322 -0
- package/dist/evals/subtask-cases.js.map +1 -0
- package/dist/evals/subtask-eval.d.ts +129 -0
- package/dist/evals/subtask-eval.d.ts.map +1 -0
- package/dist/evals/subtask-eval.js +397 -0
- package/dist/evals/subtask-eval.js.map +1 -0
- package/dist/evals/surfaces/app.d.ts +19 -0
- package/dist/evals/surfaces/app.d.ts.map +1 -0
- package/dist/evals/surfaces/app.js +341 -0
- package/dist/evals/surfaces/app.js.map +1 -0
- package/dist/evals/surfaces/creative-pipeline.d.ts +203 -0
- package/dist/evals/surfaces/creative-pipeline.d.ts.map +1 -0
- package/dist/evals/surfaces/creative-pipeline.js +909 -0
- package/dist/evals/surfaces/creative-pipeline.js.map +1 -0
- package/dist/evals/surfaces/game.d.ts +5 -0
- package/dist/evals/surfaces/game.d.ts.map +1 -0
- package/dist/evals/surfaces/game.js +42 -0
- package/dist/evals/surfaces/game.js.map +1 -0
- package/dist/evals/surfaces/js-script.d.ts +66 -0
- package/dist/evals/surfaces/js-script.d.ts.map +1 -0
- package/dist/evals/surfaces/js-script.js +407 -0
- package/dist/evals/surfaces/js-script.js.map +1 -0
- package/dist/evals/surfaces/memory.d.ts +34 -0
- package/dist/evals/surfaces/memory.d.ts.map +1 -0
- package/dist/evals/surfaces/memory.js +264 -0
- package/dist/evals/surfaces/memory.js.map +1 -0
- package/dist/evals/surfaces/model3d.d.ts +45 -0
- package/dist/evals/surfaces/model3d.d.ts.map +1 -0
- package/dist/evals/surfaces/model3d.js +346 -0
- package/dist/evals/surfaces/model3d.js.map +1 -0
- package/dist/evals/surfaces/script.d.ts +96 -0
- package/dist/evals/surfaces/script.d.ts.map +1 -0
- package/dist/evals/surfaces/script.js +709 -0
- package/dist/evals/surfaces/script.js.map +1 -0
- package/dist/evals/surfaces/sketch.d.ts +124 -0
- package/dist/evals/surfaces/sketch.d.ts.map +1 -0
- package/dist/evals/surfaces/sketch.js +1298 -0
- package/dist/evals/surfaces/sketch.js.map +1 -0
- package/dist/evals/surfaces/storyboard.d.ts +97 -0
- package/dist/evals/surfaces/storyboard.d.ts.map +1 -0
- package/dist/evals/surfaces/storyboard.js +693 -0
- package/dist/evals/surfaces/storyboard.js.map +1 -0
- package/dist/evals/surfaces/timeline.d.ts +416 -0
- package/dist/evals/surfaces/timeline.d.ts.map +1 -0
- package/dist/evals/surfaces/timeline.js +3267 -0
- package/dist/evals/surfaces/timeline.js.map +1 -0
- package/dist/evals/task-planner-cases.d.ts +57 -0
- package/dist/evals/task-planner-cases.d.ts.map +1 -0
- package/dist/evals/task-planner-cases.js +117 -0
- package/dist/evals/task-planner-cases.js.map +1 -0
- package/dist/evals/task-planner-eval.d.ts +101 -0
- package/dist/evals/task-planner-eval.d.ts.map +1 -0
- package/dist/evals/task-planner-eval.js +380 -0
- package/dist/evals/task-planner-eval.js.map +1 -0
- package/dist/evals/tool-loop-bridge.d.ts +57 -0
- package/dist/evals/tool-loop-bridge.d.ts.map +1 -0
- package/dist/evals/tool-loop-bridge.js +129 -0
- package/dist/evals/tool-loop-bridge.js.map +1 -0
- package/dist/evals/tool-loop-cases.d.ts +21 -0
- package/dist/evals/tool-loop-cases.d.ts.map +1 -0
- package/dist/evals/tool-loop-cases.js +401 -0
- package/dist/evals/tool-loop-cases.js.map +1 -0
- package/dist/evals/tool-loop-eval.d.ts +234 -0
- package/dist/evals/tool-loop-eval.d.ts.map +1 -0
- package/dist/evals/tool-loop-eval.js +373 -0
- package/dist/evals/tool-loop-eval.js.map +1 -0
- package/dist/evals/tool-loop-permission.d.ts +43 -0
- package/dist/evals/tool-loop-permission.d.ts.map +1 -0
- package/dist/evals/tool-loop-permission.js +76 -0
- package/dist/evals/tool-loop-permission.js.map +1 -0
- package/dist/game-examples.d.ts +447 -0
- package/dist/game-examples.d.ts.map +1 -0
- package/dist/game-examples.js +187 -0
- package/dist/game-examples.js.map +1 -0
- package/dist/graph-builder.d.ts +55 -3
- package/dist/graph-builder.d.ts.map +1 -1
- package/dist/graph-builder.js +159 -4
- package/dist/graph-builder.js.map +1 -1
- package/dist/graph-dsl-core.d.ts +25 -0
- package/dist/graph-dsl-core.d.ts.map +1 -0
- package/dist/graph-dsl-core.js +312 -0
- package/dist/graph-dsl-core.js.map +1 -0
- package/dist/graph-dsl.d.ts +30 -0
- package/dist/graph-dsl.d.ts.map +1 -0
- package/dist/graph-dsl.js +116 -0
- package/dist/graph-dsl.js.map +1 -0
- package/dist/host-binaries.d.ts +6 -0
- package/dist/host-binaries.d.ts.map +1 -0
- package/dist/host-binaries.js +6 -0
- package/dist/host-binaries.js.map +1 -0
- package/dist/host-binary-guard.d.ts +100 -0
- package/dist/host-binary-guard.d.ts.map +1 -0
- package/dist/host-binary-guard.js +205 -0
- package/dist/host-binary-guard.js.map +1 -0
- package/dist/host-modules/aws.d.ts +57 -0
- package/dist/host-modules/aws.d.ts.map +1 -0
- package/dist/host-modules/aws.js +309 -0
- package/dist/host-modules/aws.js.map +1 -0
- package/dist/host-modules/chrono.d.ts +15 -0
- package/dist/host-modules/chrono.d.ts.map +1 -0
- package/dist/host-modules/chrono.js +35 -0
- package/dist/host-modules/chrono.js.map +1 -0
- package/dist/host-modules/csv.d.ts +17 -0
- package/dist/host-modules/csv.d.ts.map +1 -0
- package/dist/host-modules/csv.js +64 -0
- package/dist/host-modules/csv.js.map +1 -0
- package/dist/host-modules/diff.d.ts +9 -0
- package/dist/host-modules/diff.d.ts.map +1 -0
- package/dist/host-modules/diff.js +26 -0
- package/dist/host-modules/diff.js.map +1 -0
- package/dist/host-modules/dispatcher.d.ts +41 -0
- package/dist/host-modules/dispatcher.d.ts.map +1 -0
- package/dist/host-modules/dispatcher.js +99 -0
- package/dist/host-modules/dispatcher.js.map +1 -0
- package/dist/host-modules/docx.d.ts +19 -0
- package/dist/host-modules/docx.d.ts.map +1 -0
- package/dist/host-modules/docx.js +120 -0
- package/dist/host-modules/docx.js.map +1 -0
- package/dist/host-modules/epub.d.ts +48 -0
- package/dist/host-modules/epub.d.ts.map +1 -0
- package/dist/host-modules/epub.js +100 -0
- package/dist/host-modules/epub.js.map +1 -0
- package/dist/host-modules/exif.d.ts +8 -0
- package/dist/host-modules/exif.d.ts.map +1 -0
- package/dist/host-modules/exif.js +19 -0
- package/dist/host-modules/exif.js.map +1 -0
- package/dist/host-modules/expr.d.ts +16 -0
- package/dist/host-modules/expr.d.ts.map +1 -0
- package/dist/host-modules/expr.js +18 -0
- package/dist/host-modules/expr.js.map +1 -0
- package/dist/host-modules/fabric.d.ts +46 -0
- package/dist/host-modules/fabric.d.ts.map +1 -0
- package/dist/host-modules/fabric.js +178 -0
- package/dist/host-modules/fabric.js.map +1 -0
- package/dist/host-modules/html.d.ts +51 -0
- package/dist/host-modules/html.d.ts.map +1 -0
- package/dist/host-modules/html.js +198 -0
- package/dist/host-modules/html.js.map +1 -0
- package/dist/host-modules/ics.d.ts +9 -0
- package/dist/host-modules/ics.d.ts.map +1 -0
- package/dist/host-modules/ics.js +35 -0
- package/dist/host-modules/ics.js.map +1 -0
- package/dist/host-modules/limits.d.ts +41 -0
- package/dist/host-modules/limits.d.ts.map +1 -0
- package/dist/host-modules/limits.js +76 -0
- package/dist/host-modules/limits.js.map +1 -0
- package/dist/host-modules/mammoth.d.ts +11 -0
- package/dist/host-modules/mammoth.d.ts.map +1 -0
- package/dist/host-modules/mammoth.js +29 -0
- package/dist/host-modules/mammoth.js.map +1 -0
- package/dist/host-modules/notion.d.ts +45 -0
- package/dist/host-modules/notion.d.ts.map +1 -0
- package/dist/host-modules/notion.js +128 -0
- package/dist/host-modules/notion.js.map +1 -0
- package/dist/host-modules/ocr.d.ts +25 -0
- package/dist/host-modules/ocr.d.ts.map +1 -0
- package/dist/host-modules/ocr.js +74 -0
- package/dist/host-modules/ocr.js.map +1 -0
- package/dist/host-modules/pdf.d.ts +21 -0
- package/dist/host-modules/pdf.d.ts.map +1 -0
- package/dist/host-modules/pdf.js +53 -0
- package/dist/host-modules/pdf.js.map +1 -0
- package/dist/host-modules/pdflib.d.ts +17 -0
- package/dist/host-modules/pdflib.d.ts.map +1 -0
- package/dist/host-modules/pdflib.js +138 -0
- package/dist/host-modules/pdflib.js.map +1 -0
- package/dist/host-modules/pptx.d.ts +19 -0
- package/dist/host-modules/pptx.d.ts.map +1 -0
- package/dist/host-modules/pptx.js +67 -0
- package/dist/host-modules/pptx.js.map +1 -0
- package/dist/host-modules/pptxgen.d.ts +13 -0
- package/dist/host-modules/pptxgen.d.ts.map +1 -0
- package/dist/host-modules/pptxgen.js +98 -0
- package/dist/host-modules/pptxgen.js.map +1 -0
- package/dist/host-modules/prepared-request.d.ts +41 -0
- package/dist/host-modules/prepared-request.d.ts.map +1 -0
- package/dist/host-modules/prepared-request.js +104 -0
- package/dist/host-modules/prepared-request.js.map +1 -0
- package/dist/host-modules/registry.d.ts +31 -0
- package/dist/host-modules/registry.d.ts.map +1 -0
- package/dist/host-modules/registry.js +193 -0
- package/dist/host-modules/registry.js.map +1 -0
- package/dist/host-modules/subtitle.d.ts +15 -0
- package/dist/host-modules/subtitle.d.ts.map +1 -0
- package/dist/host-modules/subtitle.js +45 -0
- package/dist/host-modules/subtitle.js.map +1 -0
- package/dist/host-modules/supabase.d.ts +42 -0
- package/dist/host-modules/supabase.d.ts.map +1 -0
- package/dist/host-modules/supabase.js +141 -0
- package/dist/host-modules/supabase.js.map +1 -0
- package/dist/host-modules/tfjs.d.ts +42 -0
- package/dist/host-modules/tfjs.d.ts.map +1 -0
- package/dist/host-modules/tfjs.js +186 -0
- package/dist/host-modules/tfjs.js.map +1 -0
- package/dist/host-modules/tokens.d.ts +17 -0
- package/dist/host-modules/tokens.d.ts.map +1 -0
- package/dist/host-modules/tokens.js +74 -0
- package/dist/host-modules/tokens.js.map +1 -0
- package/dist/host-modules/twilio.d.ts +34 -0
- package/dist/host-modules/twilio.d.ts.map +1 -0
- package/dist/host-modules/twilio.js +67 -0
- package/dist/host-modules/twilio.js.map +1 -0
- package/dist/host-modules/xlsx.d.ts +26 -0
- package/dist/host-modules/xlsx.d.ts.map +1 -0
- package/dist/host-modules/xlsx.js +323 -0
- package/dist/host-modules/xlsx.js.map +1 -0
- package/dist/host-modules/xml.d.ts +23 -0
- package/dist/host-modules/xml.d.ts.map +1 -0
- package/dist/host-modules/xml.js +38 -0
- package/dist/host-modules/xml.js.map +1 -0
- package/dist/host-modules/zip.d.ts +24 -0
- package/dist/host-modules/zip.d.ts.map +1 -0
- package/dist/host-modules/zip.js +70 -0
- package/dist/host-modules/zip.js.map +1 -0
- package/dist/index.d.ts +197 -60
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +153 -60
- package/dist/index.js.map +1 -1
- package/dist/js-sandbox-worker/host.d.ts +136 -0
- package/dist/js-sandbox-worker/host.d.ts.map +1 -0
- package/dist/js-sandbox-worker/host.js +482 -0
- package/dist/js-sandbox-worker/host.js.map +1 -0
- package/dist/js-sandbox-worker/interpreter.d.ts +276 -0
- package/dist/js-sandbox-worker/interpreter.d.ts.map +1 -0
- package/dist/js-sandbox-worker/interpreter.js +1201 -0
- package/dist/js-sandbox-worker/interpreter.js.map +1 -0
- package/dist/js-sandbox-worker/protocol.d.ts +168 -0
- package/dist/js-sandbox-worker/protocol.d.ts.map +1 -0
- package/dist/js-sandbox-worker/protocol.js +217 -0
- package/dist/js-sandbox-worker/protocol.js.map +1 -0
- package/dist/js-sandbox-worker/worker-entry.d.ts +26 -0
- package/dist/js-sandbox-worker/worker-entry.d.ts.map +1 -0
- package/dist/js-sandbox-worker/worker-entry.js +307 -0
- package/dist/js-sandbox-worker/worker-entry.js.map +1 -0
- package/dist/js-sandbox.d.ts +455 -15
- package/dist/js-sandbox.d.ts.map +1 -1
- package/dist/js-sandbox.js +1961 -309
- package/dist/js-sandbox.js.map +1 -1
- package/dist/js-script-app-runner.d.ts +9 -0
- package/dist/js-script-app-runner.d.ts.map +1 -0
- package/dist/js-script-app-runner.js +49 -0
- package/dist/js-script-app-runner.js.map +1 -0
- package/dist/js-script-sandbox.d.ts +33 -0
- package/dist/js-script-sandbox.d.ts.map +1 -0
- package/dist/js-script-sandbox.js +82 -0
- package/dist/js-script-sandbox.js.map +1 -0
- package/dist/jtbd/friction.d.ts +26 -0
- package/dist/jtbd/friction.d.ts.map +1 -0
- package/dist/jtbd/friction.js +169 -0
- package/dist/jtbd/friction.js.map +1 -0
- package/dist/jtbd/index.d.ts +6 -0
- package/dist/jtbd/index.d.ts.map +1 -0
- package/dist/jtbd/index.js +6 -0
- package/dist/jtbd/index.js.map +1 -0
- package/dist/jtbd/optimize.d.ts +71 -0
- package/dist/jtbd/optimize.d.ts.map +1 -0
- package/dist/jtbd/optimize.js +176 -0
- package/dist/jtbd/optimize.js.map +1 -0
- package/dist/jtbd/registry.d.ts +29 -0
- package/dist/jtbd/registry.d.ts.map +1 -0
- package/dist/jtbd/registry.js +451 -0
- package/dist/jtbd/registry.js.map +1 -0
- package/dist/jtbd/run.d.ts +70 -0
- package/dist/jtbd/run.d.ts.map +1 -0
- package/dist/jtbd/run.js +177 -0
- package/dist/jtbd/run.js.map +1 -0
- package/dist/jtbd/types.d.ts +141 -0
- package/dist/jtbd/types.d.ts.map +1 -0
- package/dist/jtbd/types.js +25 -0
- package/dist/jtbd/types.js.map +1 -0
- package/dist/model-prompting-skills.d.ts +45 -0
- package/dist/model-prompting-skills.d.ts.map +1 -0
- package/dist/model-prompting-skills.js +116 -0
- package/dist/model-prompting-skills.js.map +1 -0
- package/dist/network-guard.d.ts +50 -0
- package/dist/network-guard.d.ts.map +1 -0
- package/dist/network-guard.js +106 -0
- package/dist/network-guard.js.map +1 -0
- package/dist/normalize-model-properties.d.ts +26 -0
- package/dist/normalize-model-properties.d.ts.map +1 -0
- package/dist/normalize-model-properties.js +58 -0
- package/dist/normalize-model-properties.js.map +1 -0
- package/dist/output-format.d.ts +5 -1
- package/dist/output-format.d.ts.map +1 -1
- package/dist/output-format.js.map +1 -1
- package/dist/parallel-task-executor.d.ts +68 -37
- package/dist/parallel-task-executor.d.ts.map +1 -1
- package/dist/parallel-task-executor.js +234 -203
- package/dist/parallel-task-executor.js.map +1 -1
- package/dist/prompts/background-subtask-prompt.d.ts +14 -0
- package/dist/prompts/background-subtask-prompt.d.ts.map +1 -0
- package/dist/prompts/background-subtask-prompt.js +64 -0
- package/dist/prompts/background-subtask-prompt.js.map +1 -0
- package/dist/prompts/product-knowledge.d.ts +3 -0
- package/dist/prompts/product-knowledge.d.ts.map +1 -0
- package/dist/prompts/product-knowledge.js +79 -0
- package/dist/prompts/product-knowledge.js.map +1 -0
- package/dist/prompts/read-only-search-prompt.d.ts +33 -0
- package/dist/prompts/read-only-search-prompt.d.ts.map +1 -0
- package/dist/prompts/read-only-search-prompt.js +109 -0
- package/dist/prompts/read-only-search-prompt.js.map +1 -0
- package/dist/prompts/sandbox-pack-catalog.d.ts +23 -0
- package/dist/prompts/sandbox-pack-catalog.d.ts.map +1 -0
- package/dist/prompts/sandbox-pack-catalog.js +40 -0
- package/dist/prompts/sandbox-pack-catalog.js.map +1 -0
- package/dist/prompts/security-monitor-prompt.d.ts +32 -0
- package/dist/prompts/security-monitor-prompt.d.ts.map +1 -0
- package/dist/prompts/security-monitor-prompt.js +99 -0
- package/dist/prompts/security-monitor-prompt.js.map +1 -0
- package/dist/prompts/workflow-authoring-knowledge.d.ts +77 -0
- package/dist/prompts/workflow-authoring-knowledge.d.ts.map +1 -0
- package/dist/prompts/workflow-authoring-knowledge.js +319 -0
- package/dist/prompts/workflow-authoring-knowledge.js.map +1 -0
- package/dist/run-policy.d.ts +25 -0
- package/dist/run-policy.d.ts.map +1 -0
- package/dist/run-policy.js +54 -0
- package/dist/run-policy.js.map +1 -0
- package/dist/sandbox-av-media.d.ts +33 -0
- package/dist/sandbox-av-media.d.ts.map +1 -0
- package/dist/sandbox-av-media.js +666 -0
- package/dist/sandbox-av-media.js.map +1 -0
- package/dist/sandbox-bootstrap-modules.d.ts +54 -0
- package/dist/sandbox-bootstrap-modules.d.ts.map +1 -0
- package/dist/sandbox-bootstrap-modules.js +104 -0
- package/dist/sandbox-bootstrap-modules.js.map +1 -0
- package/dist/sandbox-bytes.d.ts +43 -0
- package/dist/sandbox-bytes.d.ts.map +1 -0
- package/dist/sandbox-bytes.js +61 -0
- package/dist/sandbox-bytes.js.map +1 -0
- package/dist/sandbox-canvas-api.d.ts +30 -0
- package/dist/sandbox-canvas-api.d.ts.map +1 -0
- package/dist/sandbox-canvas-api.js +81 -0
- package/dist/sandbox-canvas-api.js.map +1 -0
- package/dist/sandbox-constants.d.ts +5 -0
- package/dist/sandbox-constants.d.ts.map +1 -0
- package/dist/sandbox-constants.js +13 -0
- package/dist/sandbox-constants.js.map +1 -0
- package/dist/sandbox-json-transport.d.ts +107 -0
- package/dist/sandbox-json-transport.d.ts.map +1 -0
- package/dist/sandbox-json-transport.js +324 -0
- package/dist/sandbox-json-transport.js.map +1 -0
- package/dist/sandbox-media-handle.d.ts +78 -0
- package/dist/sandbox-media-handle.d.ts.map +1 -0
- package/dist/sandbox-media-handle.js +114 -0
- package/dist/sandbox-media-handle.js.map +1 -0
- package/dist/sandbox-media-ref.d.ts +134 -0
- package/dist/sandbox-media-ref.d.ts.map +1 -0
- package/dist/sandbox-media-ref.js +442 -0
- package/dist/sandbox-media-ref.js.map +1 -0
- package/dist/sandbox-media.d.ts +177 -0
- package/dist/sandbox-media.d.ts.map +1 -0
- package/dist/sandbox-media.js +963 -0
- package/dist/sandbox-media.js.map +1 -0
- package/dist/sandbox-module-fixtures.d.ts +445 -0
- package/dist/sandbox-module-fixtures.d.ts.map +1 -0
- package/dist/sandbox-module-fixtures.js +214 -0
- package/dist/sandbox-module-fixtures.js.map +1 -0
- package/dist/sandbox-toolbelt.d.ts +13 -0
- package/dist/sandbox-toolbelt.d.ts.map +1 -0
- package/dist/sandbox-toolbelt.js +64 -0
- package/dist/sandbox-toolbelt.js.map +1 -0
- package/dist/security-monitor.d.ts +89 -0
- package/dist/security-monitor.d.ts.map +1 -0
- package/dist/security-monitor.js +307 -0
- package/dist/security-monitor.js.map +1 -0
- package/dist/serpapi/catalog.d.ts +101 -0
- package/dist/serpapi/catalog.d.ts.map +1 -0
- package/dist/serpapi/catalog.js +246 -0
- package/dist/serpapi/catalog.js.map +1 -0
- package/dist/serpapi/client.d.ts +81 -0
- package/dist/serpapi/client.d.ts.map +1 -0
- package/dist/serpapi/client.js +228 -0
- package/dist/serpapi/client.js.map +1 -0
- package/dist/serpapi/errors.d.ts +49 -0
- package/dist/serpapi/errors.d.ts.map +1 -0
- package/dist/serpapi/errors.js +64 -0
- package/dist/serpapi/errors.js.map +1 -0
- package/dist/serpapi/normalize.d.ts +58 -0
- package/dist/serpapi/normalize.d.ts.map +1 -0
- package/dist/serpapi/normalize.js +140 -0
- package/dist/serpapi/normalize.js.map +1 -0
- package/dist/skill-prompt.d.ts +27 -0
- package/dist/skill-prompt.d.ts.map +1 -0
- package/dist/skill-prompt.js +61 -0
- package/dist/skill-prompt.js.map +1 -0
- package/dist/step-executor.d.ts +90 -77
- package/dist/step-executor.d.ts.map +1 -1
- package/dist/step-executor.js +384 -690
- package/dist/step-executor.js.map +1 -1
- package/dist/subagent.d.ts +275 -0
- package/dist/subagent.d.ts.map +1 -0
- package/dist/subagent.js +444 -0
- package/dist/subagent.js.map +1 -0
- package/dist/supervisor/prompt.d.ts +12 -0
- package/dist/supervisor/prompt.d.ts.map +1 -0
- package/dist/supervisor/prompt.js +51 -0
- package/dist/supervisor/prompt.js.map +1 -0
- package/dist/supervisor/supervisor-agent.d.ts +72 -0
- package/dist/supervisor/supervisor-agent.d.ts.map +1 -0
- package/dist/supervisor/supervisor-agent.js +185 -0
- package/dist/supervisor/supervisor-agent.js.map +1 -0
- package/dist/supervisor/tools.d.ts +56 -0
- package/dist/supervisor/tools.d.ts.map +1 -0
- package/dist/supervisor/tools.js +83 -0
- package/dist/supervisor/tools.js.map +1 -0
- package/dist/supervisor/verdict-schema.d.ts +18 -0
- package/dist/supervisor/verdict-schema.d.ts.map +1 -0
- package/dist/supervisor/verdict-schema.js +58 -0
- package/dist/supervisor/verdict-schema.js.map +1 -0
- package/dist/system-skills.d.ts +68 -0
- package/dist/system-skills.d.ts.map +1 -0
- package/dist/system-skills.js +168 -0
- package/dist/system-skills.js.map +1 -0
- package/dist/task-executor.d.ts +90 -40
- package/dist/task-executor.d.ts.map +1 -1
- package/dist/task-executor.js +259 -282
- package/dist/task-executor.js.map +1 -1
- package/dist/task-planner.d.ts +18 -12
- package/dist/task-planner.d.ts.map +1 -1
- package/dist/task-planner.js +342 -328
- package/dist/task-planner.js.map +1 -1
- package/dist/timeline-preview/compare.d.ts +27 -0
- package/dist/timeline-preview/compare.d.ts.map +1 -0
- package/dist/timeline-preview/compare.js +71 -0
- package/dist/timeline-preview/compare.js.map +1 -0
- package/dist/timeline-preview/frames.d.ts +167 -0
- package/dist/timeline-preview/frames.d.ts.map +1 -0
- package/dist/timeline-preview/frames.js +646 -0
- package/dist/timeline-preview/frames.js.map +1 -0
- package/dist/timeline-preview/model3d.d.ts +67 -0
- package/dist/timeline-preview/model3d.d.ts.map +1 -0
- package/dist/timeline-preview/model3d.js +165 -0
- package/dist/timeline-preview/model3d.js.map +1 -0
- package/dist/timeline-preview/rasterize.d.ts +33 -0
- package/dist/timeline-preview/rasterize.d.ts.map +1 -0
- package/dist/timeline-preview/rasterize.js +83 -0
- package/dist/timeline-preview/rasterize.js.map +1 -0
- package/dist/timeline-preview/sheet.d.ts +38 -0
- package/dist/timeline-preview/sheet.d.ts.map +1 -0
- package/dist/timeline-preview/sheet.js +72 -0
- package/dist/timeline-preview/sheet.js.map +1 -0
- package/dist/tools/asset-persist.d.ts +29 -0
- package/dist/tools/asset-persist.d.ts.map +1 -0
- package/dist/tools/asset-persist.js +78 -0
- package/dist/tools/asset-persist.js.map +1 -0
- package/dist/tools/base-tool.d.ts +52 -5
- package/dist/tools/base-tool.d.ts.map +1 -1
- package/dist/tools/base-tool.js +180 -3
- package/dist/tools/base-tool.js.map +1 -1
- package/dist/tools/binary-output.d.ts +54 -0
- package/dist/tools/binary-output.d.ts.map +1 -0
- package/dist/tools/binary-output.js +127 -0
- package/dist/tools/binary-output.js.map +1 -0
- package/dist/tools/browser-tools.d.ts +5 -29
- package/dist/tools/browser-tools.d.ts.map +1 -1
- package/dist/tools/browser-tools.js +5 -205
- package/dist/tools/browser-tools.js.map +1 -1
- package/dist/tools/builtin-tools.d.ts +52 -0
- package/dist/tools/builtin-tools.d.ts.map +1 -0
- package/dist/tools/builtin-tools.js +276 -0
- package/dist/tools/builtin-tools.js.map +1 -0
- package/dist/tools/control-tool.d.ts +2 -5
- package/dist/tools/control-tool.d.ts.map +1 -1
- package/dist/tools/control-tool.js +18 -15
- package/dist/tools/control-tool.js.map +1 -1
- package/dist/tools/dataseo-tools.d.ts +34 -32
- package/dist/tools/dataseo-tools.d.ts.map +1 -1
- package/dist/tools/dataseo-tools.js +148 -233
- package/dist/tools/dataseo-tools.js.map +1 -1
- package/dist/tools/external-capability-tools.d.ts +36 -0
- package/dist/tools/external-capability-tools.d.ts.map +1 -0
- package/dist/tools/external-capability-tools.js +41 -0
- package/dist/tools/external-capability-tools.js.map +1 -0
- package/dist/tools/external-mcp-tools.d.ts +139 -0
- package/dist/tools/external-mcp-tools.d.ts.map +1 -0
- package/dist/tools/external-mcp-tools.js +552 -0
- package/dist/tools/external-mcp-tools.js.map +1 -0
- package/dist/tools/finish-step-tool.d.ts +1 -1
- package/dist/tools/finish-step-tool.d.ts.map +1 -1
- package/dist/tools/finish-step-tool.js +3 -3
- package/dist/tools/finish-step-tool.js.map +1 -1
- package/dist/tools/google-tools.d.ts +28 -39
- package/dist/tools/google-tools.d.ts.map +1 -1
- package/dist/tools/google-tools.js +51 -164
- package/dist/tools/google-tools.js.map +1 -1
- package/dist/tools/google-workspace-tools.d.ts +23 -0
- package/dist/tools/google-workspace-tools.d.ts.map +1 -0
- package/dist/tools/google-workspace-tools.js +26 -0
- package/dist/tools/google-workspace-tools.js.map +1 -0
- package/dist/tools/graph-validation-registry.d.ts +33 -0
- package/dist/tools/graph-validation-registry.d.ts.map +1 -0
- package/dist/tools/graph-validation-registry.js +54 -0
- package/dist/tools/graph-validation-registry.js.map +1 -0
- package/dist/tools/http-tools.d.ts +5 -53
- package/dist/tools/http-tools.d.ts.map +1 -1
- package/dist/tools/http-tools.js +5 -177
- package/dist/tools/http-tools.js.map +1 -1
- package/dist/tools/image-injection.d.ts +51 -0
- package/dist/tools/image-injection.d.ts.map +1 -0
- package/dist/tools/image-injection.js +85 -0
- package/dist/tools/image-injection.js.map +1 -0
- package/dist/tools/mcp-tool-support.d.ts +203 -0
- package/dist/tools/mcp-tool-support.d.ts.map +1 -0
- package/dist/tools/mcp-tool-support.js +536 -0
- package/dist/tools/mcp-tool-support.js.map +1 -0
- package/dist/tools/mcp-tools.d.ts +104 -390
- package/dist/tools/mcp-tools.d.ts.map +1 -1
- package/dist/tools/mcp-tools.js +128 -600
- package/dist/tools/mcp-tools.js.map +1 -1
- package/dist/tools/media-tools.d.ts +9 -272
- package/dist/tools/media-tools.d.ts.map +1 -1
- package/dist/tools/media-tools.js +11 -676
- package/dist/tools/media-tools.js.map +1 -1
- package/dist/tools/memory-tools.d.ts +36 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +52 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/openai-tools.d.ts +28 -68
- package/dist/tools/openai-tools.d.ts.map +1 -1
- package/dist/tools/openai-tools.js +51 -181
- package/dist/tools/openai-tools.js.map +1 -1
- package/dist/tools/plan-builder-tools.d.ts +141 -7
- package/dist/tools/plan-builder-tools.d.ts.map +1 -1
- package/dist/tools/plan-builder-tools.js +159 -86
- package/dist/tools/plan-builder-tools.js.map +1 -1
- package/dist/tools/run-search-tool.d.ts +82 -0
- package/dist/tools/run-search-tool.d.ts.map +1 -0
- package/dist/tools/run-search-tool.js +121 -0
- package/dist/tools/run-search-tool.js.map +1 -0
- package/dist/tools/run-subtask-tool.d.ts +49 -0
- package/dist/tools/run-subtask-tool.d.ts.map +1 -0
- package/dist/tools/run-subtask-tool.js +88 -0
- package/dist/tools/run-subtask-tool.js.map +1 -0
- package/dist/tools/search-tools.d.ts +5 -36
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +5 -215
- package/dist/tools/search-tools.js.map +1 -1
- package/dist/tools/serp-providers/apify-provider.d.ts +18 -0
- package/dist/tools/serp-providers/apify-provider.d.ts.map +1 -0
- package/dist/tools/serp-providers/apify-provider.js +93 -0
- package/dist/tools/serp-providers/apify-provider.js.map +1 -0
- package/dist/tools/serp-providers/brave-provider.d.ts +34 -0
- package/dist/tools/serp-providers/brave-provider.d.ts.map +1 -0
- package/dist/tools/serp-providers/brave-provider.js +86 -0
- package/dist/tools/serp-providers/brave-provider.js.map +1 -0
- package/dist/tools/serp-providers/dataforseo-provider.d.ts +14 -1
- package/dist/tools/serp-providers/dataforseo-provider.d.ts.map +1 -1
- package/dist/tools/serp-providers/dataforseo-provider.js +0 -9
- package/dist/tools/serp-providers/dataforseo-provider.js.map +1 -1
- package/dist/tools/serp-providers/gemini-provider.d.ts +45 -0
- package/dist/tools/serp-providers/gemini-provider.d.ts.map +1 -0
- package/dist/tools/serp-providers/gemini-provider.js +148 -0
- package/dist/tools/serp-providers/gemini-provider.js.map +1 -0
- package/dist/tools/serp-providers/index.d.ts +61 -0
- package/dist/tools/serp-providers/index.d.ts.map +1 -1
- package/dist/tools/serp-providers/index.js +119 -3
- package/dist/tools/serp-providers/index.js.map +1 -1
- package/dist/tools/serp-providers/openai-provider.d.ts +58 -0
- package/dist/tools/serp-providers/openai-provider.d.ts.map +1 -0
- package/dist/tools/serp-providers/openai-provider.js +113 -0
- package/dist/tools/serp-providers/openai-provider.js.map +1 -0
- package/dist/tools/serp-providers/serpapi-provider.d.ts +9 -5
- package/dist/tools/serp-providers/serpapi-provider.d.ts.map +1 -1
- package/dist/tools/serp-providers/serpapi-provider.js +26 -44
- package/dist/tools/serp-providers/serpapi-provider.js.map +1 -1
- package/dist/tools/serp-tool-factory.d.ts +27 -0
- package/dist/tools/serp-tool-factory.d.ts.map +1 -0
- package/dist/tools/serp-tool-factory.js +56 -0
- package/dist/tools/serp-tool-factory.js.map +1 -0
- package/dist/tools/shared-tools.d.ts +33 -0
- package/dist/tools/shared-tools.d.ts.map +1 -0
- package/dist/tools/shared-tools.js +39 -0
- package/dist/tools/shared-tools.js.map +1 -0
- package/dist/tools/start-subtask-tool.d.ts +51 -0
- package/dist/tools/start-subtask-tool.d.ts.map +1 -0
- package/dist/tools/start-subtask-tool.js +189 -0
- package/dist/tools/start-subtask-tool.js.map +1 -0
- package/dist/tools/submit-code-tool.d.ts +159 -0
- package/dist/tools/submit-code-tool.d.ts.map +1 -0
- package/dist/tools/submit-code-tool.js +209 -0
- package/dist/tools/submit-code-tool.js.map +1 -0
- package/dist/tools/subtask-fields.d.ts +11 -0
- package/dist/tools/subtask-fields.d.ts.map +1 -0
- package/dist/tools/subtask-fields.js +11 -0
- package/dist/tools/subtask-fields.js.map +1 -0
- package/dist/tools/todo-tools.d.ts +15 -0
- package/dist/tools/todo-tools.d.ts.map +1 -0
- package/dist/tools/todo-tools.js +15 -0
- package/dist/tools/todo-tools.js.map +1 -0
- package/dist/tools/tool-permissions.d.ts +40 -0
- package/dist/tools/tool-permissions.d.ts.map +1 -0
- package/dist/tools/tool-permissions.js +52 -0
- package/dist/tools/tool-permissions.js.map +1 -0
- package/dist/tools/tool-search.d.ts +39 -0
- package/dist/tools/tool-search.d.ts.map +1 -0
- package/dist/tools/tool-search.js +108 -0
- package/dist/tools/tool-search.js.map +1 -0
- package/dist/tools/vector-tool-support.d.ts +15 -0
- package/dist/tools/vector-tool-support.d.ts.map +1 -0
- package/dist/tools/vector-tool-support.js +133 -0
- package/dist/tools/vector-tool-support.js.map +1 -0
- package/dist/tools/vector-tools.d.ts +13 -220
- package/dist/tools/vector-tools.d.ts.map +1 -1
- package/dist/tools/vector-tools.js +7 -578
- package/dist/tools/vector-tools.js.map +1 -1
- package/dist/tools/wait-subtasks-tool.d.ts +27 -0
- package/dist/tools/wait-subtasks-tool.d.ts.map +1 -0
- package/dist/tools/wait-subtasks-tool.js +66 -0
- package/dist/tools/wait-subtasks-tool.js.map +1 -0
- package/dist/tsconfig.tests.tmp.tsbuildinfo +1 -0
- package/dist/types.d.ts +16 -31
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/dag-scheduler.d.ts +85 -0
- package/dist/utils/dag-scheduler.d.ts.map +1 -0
- package/dist/utils/dag-scheduler.js +180 -0
- package/dist/utils/dag-scheduler.js.map +1 -0
- package/dist/utils/json-parser.d.ts +32 -1
- package/dist/utils/json-parser.d.ts.map +1 -1
- package/dist/utils/json-parser.js +145 -10
- package/dist/utils/json-parser.js.map +1 -1
- package/dist/utils/json-schema-validate.d.ts +29 -0
- package/dist/utils/json-schema-validate.d.ts.map +1 -0
- package/dist/utils/json-schema-validate.js +228 -0
- package/dist/utils/json-schema-validate.js.map +1 -0
- package/dist/utils/link-abort.d.ts +13 -0
- package/dist/utils/link-abort.d.ts.map +1 -0
- package/dist/utils/link-abort.js +23 -0
- package/dist/utils/link-abort.js.map +1 -0
- package/dist/utils/merge-generators.d.ts +58 -0
- package/dist/utils/merge-generators.d.ts.map +1 -0
- package/dist/utils/merge-generators.js +148 -0
- package/dist/utils/merge-generators.js.map +1 -0
- package/dist/utils/remove-base64-images.d.ts +0 -5
- package/dist/utils/remove-base64-images.d.ts.map +1 -1
- package/dist/utils/remove-base64-images.js.map +1 -1
- package/dist/utils/step-failure.d.ts +20 -0
- package/dist/utils/step-failure.d.ts.map +1 -0
- package/dist/utils/step-failure.js +43 -0
- package/dist/utils/step-failure.js.map +1 -0
- package/dist/utils/think-tags.d.ts +16 -0
- package/dist/utils/think-tags.d.ts.map +1 -0
- package/dist/utils/think-tags.js +23 -0
- package/dist/utils/think-tags.js.map +1 -0
- package/dist/utils/type-guards.d.ts +26 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +44 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/dist/utils/ui-event-buffer.d.ts +18 -0
- package/dist/utils/ui-event-buffer.d.ts.map +1 -0
- package/dist/utils/ui-event-buffer.js +18 -0
- package/dist/utils/ui-event-buffer.js.map +1 -0
- package/dist/utils/wrap-generators-parallel.d.ts +0 -5
- package/dist/utils/wrap-generators-parallel.d.ts.map +1 -1
- package/dist/utils/wrap-generators-parallel.js +3 -6
- package/dist/utils/wrap-generators-parallel.js.map +1 -1
- package/dist/wasm-sandbox/host.d.ts +106 -0
- package/dist/wasm-sandbox/host.d.ts.map +1 -0
- package/dist/wasm-sandbox/host.js +409 -0
- package/dist/wasm-sandbox/host.js.map +1 -0
- package/dist/wasm-sandbox/workers.d.ts +43 -0
- package/dist/wasm-sandbox/workers.d.ts.map +1 -0
- package/dist/wasm-sandbox/workers.js +150 -0
- package/dist/wasm-sandbox/workers.js.map +1 -0
- package/dist/workspace-paths.d.ts +40 -0
- package/dist/workspace-paths.d.ts.map +1 -0
- package/dist/workspace-paths.js +102 -0
- package/dist/workspace-paths.js.map +1 -0
- package/dist/yt-dlp-gate.d.ts +19 -0
- package/dist/yt-dlp-gate.d.ts.map +1 -0
- package/dist/yt-dlp-gate.js +22 -0
- package/dist/yt-dlp-gate.js.map +1 -0
- package/package.json +112 -14
- package/dist/agent-executor.d.ts +0 -62
- package/dist/agent-executor.d.ts.map +0 -1
- package/dist/agent-executor.js +0 -324
- package/dist/agent-executor.js.map +0 -1
- package/dist/agent-step-executor.d.ts +0 -28
- package/dist/agent-step-executor.d.ts.map +0 -1
- package/dist/agent-step-executor.js +0 -99
- package/dist/agent-step-executor.js.map +0 -1
- package/dist/agent-workflow-runner.d.ts +0 -31
- package/dist/agent-workflow-runner.d.ts.map +0 -1
- package/dist/agent-workflow-runner.js +0 -124
- package/dist/agent-workflow-runner.js.map +0 -1
- package/dist/agent.d.ts +0 -115
- package/dist/agent.d.ts.map +0 -1
- package/dist/agent.js +0 -587
- package/dist/agent.js.map +0 -1
- package/dist/base-agent.d.ts +0 -42
- package/dist/base-agent.d.ts.map +0 -1
- package/dist/base-agent.js +0 -29
- package/dist/base-agent.js.map +0 -1
- package/dist/graph-planner.d.ts +0 -56
- package/dist/graph-planner.d.ts.map +0 -1
- package/dist/graph-planner.js +0 -311
- package/dist/graph-planner.js.map +0 -1
- package/dist/multi-mode-agent.d.ts +0 -86
- package/dist/multi-mode-agent.d.ts.map +0 -1
- package/dist/multi-mode-agent.js +0 -476
- package/dist/multi-mode-agent.js.map +0 -1
- package/dist/prompts/graph-planner-prompt.d.ts +0 -48
- package/dist/prompts/graph-planner-prompt.d.ts.map +0 -1
- package/dist/prompts/graph-planner-prompt.js +0 -180
- package/dist/prompts/graph-planner-prompt.js.map +0 -1
- package/dist/reject-agentic-provider.d.ts +0 -21
- package/dist/reject-agentic-provider.d.ts.map +0 -1
- package/dist/reject-agentic-provider.js +0 -32
- package/dist/reject-agentic-provider.js.map +0 -1
- package/dist/simple-agent.d.ts +0 -29
- package/dist/simple-agent.d.ts.map +0 -1
- package/dist/simple-agent.js +0 -54
- package/dist/simple-agent.js.map +0 -1
- package/dist/sub-agent-planner.d.ts +0 -33
- package/dist/sub-agent-planner.d.ts.map +0 -1
- package/dist/sub-agent-planner.js +0 -203
- package/dist/sub-agent-planner.js.map +0 -1
- package/dist/team/db-task-board.d.ts +0 -54
- package/dist/team/db-task-board.d.ts.map +0 -1
- package/dist/team/db-task-board.js +0 -361
- package/dist/team/db-task-board.js.map +0 -1
- package/dist/team/edge-message-bus.d.ts +0 -51
- package/dist/team/edge-message-bus.d.ts.map +0 -1
- package/dist/team/edge-message-bus.js +0 -169
- package/dist/team/edge-message-bus.js.map +0 -1
- package/dist/team/edge-task-board.d.ts +0 -77
- package/dist/team/edge-task-board.d.ts.map +0 -1
- package/dist/team/edge-task-board.js +0 -140
- package/dist/team/edge-task-board.js.map +0 -1
- package/dist/team/index.d.ts +0 -16
- package/dist/team/index.d.ts.map +0 -1
- package/dist/team/index.js +0 -17
- package/dist/team/index.js.map +0 -1
- package/dist/team/message-bus.d.ts +0 -58
- package/dist/team/message-bus.d.ts.map +0 -1
- package/dist/team/message-bus.js +0 -151
- package/dist/team/message-bus.js.map +0 -1
- package/dist/team/task-board.d.ts +0 -106
- package/dist/team/task-board.d.ts.map +0 -1
- package/dist/team/task-board.js +0 -370
- package/dist/team/task-board.js.map +0 -1
- package/dist/team/team-executor.d.ts +0 -72
- package/dist/team/team-executor.d.ts.map +0 -1
- package/dist/team/team-executor.js +0 -466
- package/dist/team/team-executor.js.map +0 -1
- package/dist/team/team-tools.d.ts +0 -252
- package/dist/team/team-tools.d.ts.map +0 -1
- package/dist/team/team-tools.js +0 -412
- package/dist/team/team-tools.js.map +0 -1
- package/dist/team/types.d.ts +0 -198
- package/dist/team/types.d.ts.map +0 -1
- package/dist/team/types.js +0 -8
- package/dist/team/types.js.map +0 -1
- package/dist/tools/add-edge-tool.d.ts +0 -18
- package/dist/tools/add-edge-tool.d.ts.map +0 -1
- package/dist/tools/add-edge-tool.js +0 -92
- package/dist/tools/add-edge-tool.js.map +0 -1
- package/dist/tools/add-node-tool.d.ts +0 -22
- package/dist/tools/add-node-tool.d.ts.map +0 -1
- package/dist/tools/add-node-tool.js +0 -128
- package/dist/tools/add-node-tool.js.map +0 -1
- package/dist/tools/asset-tools.d.ts +0 -58
- package/dist/tools/asset-tools.d.ts.map +0 -1
- package/dist/tools/asset-tools.js +0 -191
- package/dist/tools/asset-tools.js.map +0 -1
- package/dist/tools/calculator-tool.d.ts +0 -24
- package/dist/tools/calculator-tool.d.ts.map +0 -1
- package/dist/tools/calculator-tool.js +0 -64
- package/dist/tools/calculator-tool.js.map +0 -1
- package/dist/tools/claude-code-tools.d.ts +0 -96
- package/dist/tools/claude-code-tools.d.ts.map +0 -1
- package/dist/tools/claude-code-tools.js +0 -463
- package/dist/tools/claude-code-tools.js.map +0 -1
- package/dist/tools/code-tools.d.ts +0 -26
- package/dist/tools/code-tools.d.ts.map +0 -1
- package/dist/tools/code-tools.js +0 -82
- package/dist/tools/code-tools.js.map +0 -1
- package/dist/tools/create-plan-tool.d.ts +0 -29
- package/dist/tools/create-plan-tool.d.ts.map +0 -1
- package/dist/tools/create-plan-tool.js +0 -355
- package/dist/tools/create-plan-tool.js.map +0 -1
- package/dist/tools/create-task-tool.d.ts +0 -28
- package/dist/tools/create-task-tool.d.ts.map +0 -1
- package/dist/tools/create-task-tool.js +0 -254
- package/dist/tools/create-task-tool.js.map +0 -1
- package/dist/tools/email-tools.d.ts +0 -76
- package/dist/tools/email-tools.d.ts.map +0 -1
- package/dist/tools/email-tools.js +0 -259
- package/dist/tools/email-tools.js.map +0 -1
- package/dist/tools/filesystem-tools.d.ts +0 -77
- package/dist/tools/filesystem-tools.d.ts.map +0 -1
- package/dist/tools/filesystem-tools.js +0 -334
- package/dist/tools/filesystem-tools.js.map +0 -1
- package/dist/tools/find-model-tool.d.ts +0 -25
- package/dist/tools/find-model-tool.d.ts.map +0 -1
- package/dist/tools/find-model-tool.js +0 -236
- package/dist/tools/find-model-tool.js.map +0 -1
- package/dist/tools/finish-graph-tool.d.ts +0 -23
- package/dist/tools/finish-graph-tool.d.ts.map +0 -1
- package/dist/tools/finish-graph-tool.js +0 -46
- package/dist/tools/finish-graph-tool.js.map +0 -1
- package/dist/tools/js-code-tool.d.ts +0 -30
- package/dist/tools/js-code-tool.d.ts.map +0 -1
- package/dist/tools/js-code-tool.js +0 -98
- package/dist/tools/js-code-tool.js.map +0 -1
- package/dist/tools/local-get-node-info-tool.d.ts +0 -19
- package/dist/tools/local-get-node-info-tool.d.ts.map +0 -1
- package/dist/tools/local-get-node-info-tool.js +0 -75
- package/dist/tools/local-get-node-info-tool.js.map +0 -1
- package/dist/tools/local-list-nodes-tool.d.ts +0 -19
- package/dist/tools/local-list-nodes-tool.d.ts.map +0 -1
- package/dist/tools/local-list-nodes-tool.js +0 -69
- package/dist/tools/local-list-nodes-tool.js.map +0 -1
- package/dist/tools/local-search-nodes-tool.d.ts +0 -21
- package/dist/tools/local-search-nodes-tool.d.ts.map +0 -1
- package/dist/tools/local-search-nodes-tool.js +0 -111
- package/dist/tools/local-search-nodes-tool.js.map +0 -1
- package/dist/tools/math-tools.d.ts +0 -156
- package/dist/tools/math-tools.d.ts.map +0 -1
- package/dist/tools/math-tools.js +0 -422
- package/dist/tools/math-tools.js.map +0 -1
- package/dist/tools/model-tools.d.ts +0 -25
- package/dist/tools/model-tools.d.ts.map +0 -1
- package/dist/tools/model-tools.js +0 -48
- package/dist/tools/model-tools.js.map +0 -1
- package/dist/tools/pdf-tools.d.ts +0 -152
- package/dist/tools/pdf-tools.d.ts.map +0 -1
- package/dist/tools/pdf-tools.js +0 -339
- package/dist/tools/pdf-tools.js.map +0 -1
- package/dist/tools/tool-registry.d.ts +0 -9
- package/dist/tools/tool-registry.d.ts.map +0 -1
- package/dist/tools/tool-registry.js +0 -17
- package/dist/tools/tool-registry.js.map +0 -1
- package/dist/tools/workspace-tools.d.ts +0 -58
- package/dist/tools/workspace-tools.d.ts.map +0 -1
- package/dist/tools/workspace-tools.js +0 -152
- package/dist/tools/workspace-tools.js.map +0 -1
|
@@ -0,0 +1,3267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless bridge for the timeline / video editor tool-loop eval.
|
|
3
|
+
*
|
|
4
|
+
* The real frontend tools (`web/src/lib/tools/builtin/timeline.ts`) delegate to
|
|
5
|
+
* a `TimelineAgentHandler` the live `TimelineEditor` registers per open
|
|
6
|
+
* sequence on `timelineAgentBridge` — it mutates Zustand document/UI/playback
|
|
7
|
+
* stores and (for `ui_timeline_get_clip_frames`) samples rendered video
|
|
8
|
+
* frames. None of that can run under Node. This bridge reimplements the
|
|
9
|
+
* *effects* of the non-rendering tools against a plain in-memory sequence
|
|
10
|
+
* (tracks + clips from `@nodetool-ai/timeline`), so a model can drive the same
|
|
11
|
+
* `ui_timeline_*` tool surface headlessly.
|
|
12
|
+
*
|
|
13
|
+
* What it does NOT fork is the tool *contract*: names, descriptions, and Zod
|
|
14
|
+
* parameter shapes are copied verbatim from the builtin file (minus the
|
|
15
|
+
* `timeline_id` parameter — this bridge holds a single implicit sequence, so
|
|
16
|
+
* there is nothing to disambiguate). `ui_timeline_get_clip_frames` is
|
|
17
|
+
* intentionally excluded: it requires real rendered video frames and has no
|
|
18
|
+
* meaningful headless equivalent.
|
|
19
|
+
*
|
|
20
|
+
* Real cut/preset logic is reused from the pure `@nodetool-ai/timeline`
|
|
21
|
+
* package (`splitClip`, `ANIMATION_PRESETS`, `makeClip`/`makeTrack`) rather
|
|
22
|
+
* than reimplemented, so this bridge exercises the same behavior the live
|
|
23
|
+
* editor does.
|
|
24
|
+
*/
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
import { isShortResourceId } from "@nodetool-ai/protocol";
|
|
27
|
+
import { parseWithTypeCoercion } from "@nodetool-ai/runtime";
|
|
28
|
+
import { splitClip, trimClip, ANIMATION_PRESETS, ANIMATED_PROPERTIES, buildBakedAnimation, CUSTOM_ANIMATION_CONTRACT, CUSTOM_ANIMATION_PRESET_ID, normalizeCustomCurves, resolveCustomMask, computeModel3DBakeHash, makeClip, makeClipVersion, makeTrack, DEFAULT_TEXT_CLIP_DURATION_MS, DEFAULT_MEDIA_CLIP_DURATION_MS, DEFAULT_MODEL3D_CLIP_DURATION_MS, DEFAULT_MODEL3D_CLIP_NAME, model3dStyleWithPatch, shapeStyleWithDefaults, assertAuthorableFontFamily, textStyleWithDefaults, moveTrackOrder, mediaTypeForContentType, trackTypeForMediaType, STAGGER_UNITS, parseStaggerUnit, typewriterTiming, parseEasing, DEFAULT_BEAT_TOLERANCE_MS, buildBeatGrid, beatCountToCover, snapClipsToGrid, staggerClipAnimations, isGroupClip, moveGroup, ungroup, trimGroup, resliceTracksForSplitClip, resliceTracksForTrimmedClip, applyTransitionAtCutCandidate, planTransitionAtCut, instantiateComposition, DEFAULT_MIDI_INSTRUMENT, DEFAULT_TEMPO, MIDI_INSTRUMENT_PRESETS, createMidiNote, findInstrumentPreset, formatBarsBeats, presetIdForInstrument, quantizeNotes, scaleVelocity, sortNotes, transposeNotes, validateNotes, visibleNotes, rescaleClipsForTempo, resolveTempo, captureMediaEditSourceContext, composeGenerativeTakePatch, createMediaEditRequest, ensureBaselineTake, applyTakeToClip, selectGeneratedMatteVersion, activeTakeIdOf, selectTake, renameTake as renameTakeOnClip, deleteTake as deleteTakeOnClip, adaptSequenceFormat, addReframeKeyframe, clearReframe, isMediaTrackStale, mediaTrackCanDriveReframe } from "@nodetool-ai/timeline";
|
|
29
|
+
import { computeActiveLayers, countTextStaggerUnits, parseSvgPath } from "@nodetool-ai/timeline/scene";
|
|
30
|
+
import { APPLY_TRANSITION_AT_CUT_DESCRIPTION, applyTransitionAtCutParams, buildEffect, buildMask, buildTimeRemap, buildTransition, resolveDeleteTrackArgs, resolveMoveTrackArgs, resolveShapeArg, setBakedAnimationParams, SET_BAKED_ANIMATION_DESCRIPTION, setGeneratedMatteParams, SET_GENERATED_MATTE_DESCRIPTION, textStyleParams, textStylePatchParams } from "@nodetool-ai/protocol/api-schemas/timeline-tool-params.js";
|
|
31
|
+
import { buildTimelineToolContracts, liftCustomAnimation, rejectUnknownClipParams } from "@nodetool-ai/protocol/api-schemas/timeline-tool-contracts.js";
|
|
32
|
+
import { uiToolParams } from "@nodetool-ai/protocol/api-schemas/ui-tool-contract.js";
|
|
33
|
+
import { findSystemSkill } from "../../system-skills.js";
|
|
34
|
+
/**
|
|
35
|
+
* The `ui_timeline_*` contracts, shared with the browser registry
|
|
36
|
+
* (`web/src/lib/tools/builtin/timeline.ts`). The vocabulary comes from
|
|
37
|
+
* `@nodetool-ai/timeline`, which the protocol package sits below.
|
|
38
|
+
*/
|
|
39
|
+
const CONTRACTS = buildTimelineToolContracts({
|
|
40
|
+
staggerUnits: STAGGER_UNITS,
|
|
41
|
+
animatedProperties: ANIMATED_PROPERTIES,
|
|
42
|
+
beatToleranceMs: DEFAULT_BEAT_TOLERANCE_MS
|
|
43
|
+
});
|
|
44
|
+
const generatedTransitionCandidateParams = applyTransitionAtCutParams;
|
|
45
|
+
const transitionCandidateParams = z.object({
|
|
46
|
+
candidate_id: z.string().trim().min(1)
|
|
47
|
+
});
|
|
48
|
+
const transitionCandidateLifecycleParams = applyTransitionAtCutParams
|
|
49
|
+
.partial()
|
|
50
|
+
.extend({ candidate_id: z.string().trim().min(1).optional() })
|
|
51
|
+
.superRefine((value, context) => {
|
|
52
|
+
if (value.candidate_id === undefined &&
|
|
53
|
+
(value.outgoingClipId === undefined || value.incomingClipId === undefined)) {
|
|
54
|
+
context.addIssue({
|
|
55
|
+
code: "custom",
|
|
56
|
+
message: "Provide outgoingClipId and incomingClipId to create a candidate, or candidate_id to apply one."
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
/** Units a failed lookup names before it stops and points at get_state. */
|
|
61
|
+
const MAX_LISTED_UNITS = 12;
|
|
62
|
+
/**
|
|
63
|
+
* Tools that read the sequence without changing it. Everything else is an
|
|
64
|
+
* edit, which is what {@link previewedAfterLastEdit} measures "last" against.
|
|
65
|
+
*/
|
|
66
|
+
export const TIMELINE_READ_ONLY_TOOLS = [
|
|
67
|
+
"ui_timeline_get_state",
|
|
68
|
+
"ui_timeline_list_animation_presets",
|
|
69
|
+
"ui_timeline_select_clip",
|
|
70
|
+
"ui_timeline_preview_transition_candidate",
|
|
71
|
+
"ui_timeline_seek",
|
|
72
|
+
"preview_timeline_frame"
|
|
73
|
+
];
|
|
74
|
+
/** Whether the last edit in a transcript is followed by a preview call. */
|
|
75
|
+
export function previewedAfterLastEdit(toolLog) {
|
|
76
|
+
const lastEdit = toolLog.reduce((index, name, i) => (TIMELINE_READ_ONLY_TOOLS.includes(name) ? index : i), -1);
|
|
77
|
+
if (lastEdit === -1)
|
|
78
|
+
return false;
|
|
79
|
+
return toolLog.indexOf("preview_timeline_frame", lastEdit + 1) !== -1;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether a preview landed inside a motion rather than beside it.
|
|
83
|
+
*
|
|
84
|
+
* {@link previewedAfterLastEdit} reads the transcript's shape: a preview call
|
|
85
|
+
* came last. That passes on a look at 0ms, where an entrance has not started —
|
|
86
|
+
* the endpoints tell you nothing, which is what the skill's "sample the middle
|
|
87
|
+
* of a motion" is about. This reads the timecodes instead: at least one falls
|
|
88
|
+
* strictly inside the window of an animation the document now carries.
|
|
89
|
+
*
|
|
90
|
+
* Every animation in a graded final state is one the run authored, since the
|
|
91
|
+
* seeded worlds carry none.
|
|
92
|
+
*/
|
|
93
|
+
export function previewedMidMotion(previewTimesMs, clips, canvas) {
|
|
94
|
+
if (previewTimesMs.length === 0)
|
|
95
|
+
return false;
|
|
96
|
+
return clips.some((clip) => (clip.animations ?? []).some((animation) => {
|
|
97
|
+
const window = animationWindow(clip, animation, canvas);
|
|
98
|
+
if (!(window.endMs > window.startMs))
|
|
99
|
+
return false;
|
|
100
|
+
return previewTimesMs.some((timeMs) => timeMs > window.startMs && timeMs < window.endMs);
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* How long an animation is in motion for: its own window, widened by the
|
|
105
|
+
* stagger's last unit. `durationMs` unset takes the preset's default.
|
|
106
|
+
*/
|
|
107
|
+
function motionSpanMs(clip, animation, canvas) {
|
|
108
|
+
const preset = ANIMATION_PRESETS.find((p) => p.id === animation.preset);
|
|
109
|
+
const durationMs = animation.durationMs ?? preset?.defaultDurationMs ?? 0;
|
|
110
|
+
const stagger = animation.stagger;
|
|
111
|
+
if (!stagger || !(stagger.offsetMs > 0))
|
|
112
|
+
return durationMs;
|
|
113
|
+
const units = staggerUnitsOf(clip, stagger.unit, canvas);
|
|
114
|
+
if (units < 2)
|
|
115
|
+
return durationMs;
|
|
116
|
+
return durationMs + stagger.offsetMs * (units - 1);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* When an animation runs, in timeline ms. `delayMs` offsets an `in` and an
|
|
120
|
+
* `emphasis` from the clip's start and an `out` backwards from its end, which
|
|
121
|
+
* is the role rule the skill states. A `loop` runs for the whole clip.
|
|
122
|
+
*/
|
|
123
|
+
export function animationWindow(clip, animation, canvas) {
|
|
124
|
+
const clipEndMs = clip.startMs + clip.durationMs;
|
|
125
|
+
if (animation.role === "loop") {
|
|
126
|
+
return { startMs: clip.startMs, endMs: clipEndMs };
|
|
127
|
+
}
|
|
128
|
+
const delayMs = animation.delayMs ?? 0;
|
|
129
|
+
const spanMs = motionSpanMs(clip, animation, canvas);
|
|
130
|
+
if (animation.role === "out") {
|
|
131
|
+
const endMs = clipEndMs - delayMs;
|
|
132
|
+
return { startMs: endMs - spanMs, endMs };
|
|
133
|
+
}
|
|
134
|
+
const startMs = clip.startMs + delayMs;
|
|
135
|
+
return { startMs, endMs: startMs + spanMs };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Whether a staggered animation finishes inside its clip: the last unit
|
|
139
|
+
* starts `offsetMs × (units − 1)` in and still runs the full `durationMs`.
|
|
140
|
+
* An animation with no stagger, or one on a clip that splits into fewer than
|
|
141
|
+
* two units, fits by construction — it is one block.
|
|
142
|
+
*/
|
|
143
|
+
export function staggerSpanFitsClip(clip, animation, canvas) {
|
|
144
|
+
const stagger = animation.stagger;
|
|
145
|
+
if (!stagger || !(stagger.offsetMs > 0))
|
|
146
|
+
return true;
|
|
147
|
+
const units = staggerUnitsOf(clip, stagger.unit, canvas);
|
|
148
|
+
if (units < 2)
|
|
149
|
+
return true;
|
|
150
|
+
const preset = ANIMATION_PRESETS.find((p) => p.id === animation.preset);
|
|
151
|
+
const durationMs = animation.durationMs ?? preset?.defaultDurationMs ?? 0;
|
|
152
|
+
const span = (animation.delayMs ?? 0) + durationMs + stagger.offsetMs * (units - 1);
|
|
153
|
+
return span <= clip.durationMs;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* How many units a clip's text splits into for a stagger unit. Line counting
|
|
157
|
+
* wraps against the sequence size; with no text measurer every authored
|
|
158
|
+
* paragraph is one line, which is what a headless surface can know.
|
|
159
|
+
*/
|
|
160
|
+
export function staggerUnitsOf(clip, unit, canvas) {
|
|
161
|
+
const style = clip.textStyle;
|
|
162
|
+
const parsed = parseStaggerUnit(unit);
|
|
163
|
+
// An unknown unit compiles as a plain block animation, so it splits into
|
|
164
|
+
// nothing — same answer as a clip with no text.
|
|
165
|
+
if (!style || !parsed)
|
|
166
|
+
return 0;
|
|
167
|
+
return countTextStaggerUnits(style, canvas, parsed);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The easing an animation actually runs with: its own, else the preset's, else
|
|
171
|
+
* the role default (`in` decelerates, `out` accelerates).
|
|
172
|
+
*/
|
|
173
|
+
export function effectiveEasing(animation) {
|
|
174
|
+
if (animation.easing)
|
|
175
|
+
return animation.easing;
|
|
176
|
+
const preset = ANIMATION_PRESETS.find((p) => p.id === animation.preset);
|
|
177
|
+
if (preset?.defaultEasing)
|
|
178
|
+
return preset.defaultEasing;
|
|
179
|
+
switch (animation.role) {
|
|
180
|
+
case "in":
|
|
181
|
+
return "easeOut";
|
|
182
|
+
case "out":
|
|
183
|
+
return "easeIn";
|
|
184
|
+
case "emphasis":
|
|
185
|
+
return "easeInOut";
|
|
186
|
+
default:
|
|
187
|
+
return "linear";
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** How far either side of the curve a slope is measured over. */
|
|
191
|
+
const EASING_SLOPE_STEP = 0.02;
|
|
192
|
+
/**
|
|
193
|
+
* Whether an easing decelerates into its landing.
|
|
194
|
+
*
|
|
195
|
+
* The `easeOut` family qualifies by name: its endpoints are exact, and
|
|
196
|
+
* `easeOutBounce` deliberately accelerates into its last bounce, which a slope
|
|
197
|
+
* reading at t=1 would score as an ease-in. Everything else in the grammar is
|
|
198
|
+
* measured — the curve's slope entering the landing against its slope leaving
|
|
199
|
+
* the start — so `cubic-bezier(0.16,1,0.3,1)`, the deceleration the skill
|
|
200
|
+
* recommends for entrances, passes and `cubic-bezier(0.7,0,0.84,0)`, the exit
|
|
201
|
+
* curve beside it, does not. An easing outside the grammar eases linearly and
|
|
202
|
+
* does not decelerate.
|
|
203
|
+
*/
|
|
204
|
+
export function easingDecelerates(easing) {
|
|
205
|
+
const text = easing.trim();
|
|
206
|
+
if (/^easeOut/.test(text))
|
|
207
|
+
return true;
|
|
208
|
+
const curve = parseEasing(text);
|
|
209
|
+
if (!curve)
|
|
210
|
+
return false;
|
|
211
|
+
const entry = (curve(EASING_SLOPE_STEP) - curve(0)) / EASING_SLOPE_STEP;
|
|
212
|
+
const landing = (curve(1) - curve(1 - EASING_SLOPE_STEP)) / EASING_SLOPE_STEP;
|
|
213
|
+
return landing < entry;
|
|
214
|
+
}
|
|
215
|
+
function tool(name, description, parameters, impl) {
|
|
216
|
+
return {
|
|
217
|
+
name,
|
|
218
|
+
description,
|
|
219
|
+
parameters,
|
|
220
|
+
execute: (args) => {
|
|
221
|
+
const parsed = parseWithTypeCoercion(parameters, args ?? {});
|
|
222
|
+
return impl(parsed);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* `set_clip_params` takes the style as a patch merged over the clip's own, so
|
|
228
|
+
* every field is optional. The shared contract still declares the whole-bag
|
|
229
|
+
* `textStyleParams`, which refuses `{textStyle: {fontFamily}}` on a finished
|
|
230
|
+
* title. Both hosts override that one field; when protocol carries
|
|
231
|
+
* `textStylePatchParams` the two overrides go together.
|
|
232
|
+
*/
|
|
233
|
+
const CONTRACT_SHAPE_OVERRIDES = {
|
|
234
|
+
ui_timeline_set_clip_params: { textStyle: textStylePatchParams.optional() }
|
|
235
|
+
};
|
|
236
|
+
/** A tool whose name, description and argument shape both hosts share. */
|
|
237
|
+
function sharedTool(name, impl) {
|
|
238
|
+
const contract = CONTRACTS[name];
|
|
239
|
+
const shape = { ...contract.shape, ...CONTRACT_SHAPE_OVERRIDES[name] };
|
|
240
|
+
// No host fields: this bridge drives one implicit sequence, so there is no
|
|
241
|
+
// `timeline_id` to disambiguate.
|
|
242
|
+
return tool(name, contract.description, uiToolParams({ ...contract, shape }), impl);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Keys a caller wraps the whole patch in.
|
|
246
|
+
*
|
|
247
|
+
* `set_clip_params` reads its fields off the op itself — `{op, target,
|
|
248
|
+
* textStyle}` — but a REST-shaped guess sends `{op, target, params: {…}}`, and
|
|
249
|
+
* that used to be refused as an unknown key with the real fields hidden one
|
|
250
|
+
* level down inside it. The wrapper says nothing the op does not already know,
|
|
251
|
+
* so it is unwrapped rather than argued with.
|
|
252
|
+
*/
|
|
253
|
+
const CLIP_PARAM_WRAPPERS = ["params", "patch", "props", "properties"];
|
|
254
|
+
/** Lift a patch a caller nested under a wrapper key onto the op itself. */
|
|
255
|
+
export function unwrapClipParams(patch) {
|
|
256
|
+
const wrapper = CLIP_PARAM_WRAPPERS.find((key) => isRecordValue(patch[key]));
|
|
257
|
+
if (!wrapper)
|
|
258
|
+
return patch;
|
|
259
|
+
const { [wrapper]: nested, ...rest } = patch;
|
|
260
|
+
// The op's own keys win: a caller that sent both meant the one it spelled
|
|
261
|
+
// out, and silently preferring the wrapper would drop it.
|
|
262
|
+
return { ...nested, ...rest };
|
|
263
|
+
}
|
|
264
|
+
const isRecordValue = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
265
|
+
function mergeClipTransform(current, patch) {
|
|
266
|
+
return {
|
|
267
|
+
position: { x: 0, y: 0, ...current?.position, ...patch.position },
|
|
268
|
+
scale: { x: 1, y: 1, ...current?.scale, ...patch.scale },
|
|
269
|
+
rotation: patch.rotation ?? current?.rotation ?? 0,
|
|
270
|
+
rotationX: patch.rotationX ?? current?.rotationX,
|
|
271
|
+
rotationY: patch.rotationY ?? current?.rotationY,
|
|
272
|
+
perspective: patch.perspective ?? current?.perspective,
|
|
273
|
+
anchor: { x: 0.5, y: 0.5, ...current?.anchor, ...patch.anchor }
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* A text style patch, merged over the clip's own and checked for a family that
|
|
278
|
+
* names no face.
|
|
279
|
+
*
|
|
280
|
+
* A clip that carries no text style yet still needs the three fields that make
|
|
281
|
+
* one drawable, so the merged result goes through the same schema the whole
|
|
282
|
+
* bag does rather than being stored half-built.
|
|
283
|
+
*/
|
|
284
|
+
function mergeTextStyle(clip, patch) {
|
|
285
|
+
const merged = { ...(clip.textStyle ?? {}), ...patch };
|
|
286
|
+
const parsed = textStyleParams.safeParse(merged);
|
|
287
|
+
if (!parsed.success) {
|
|
288
|
+
const missing = parsed.error.issues
|
|
289
|
+
.filter((issue) => issue.code === "invalid_type")
|
|
290
|
+
.map((issue) => issue.path.join("."));
|
|
291
|
+
throw new Error(missing.length > 0
|
|
292
|
+
? `Clip "${clip.name}" has no text style yet, so this patch cannot ` +
|
|
293
|
+
`stand on its own — it still needs ${missing.join(", ")}.`
|
|
294
|
+
: `textStyle: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`);
|
|
295
|
+
}
|
|
296
|
+
assertAuthorableFontFamily(parsed.data.fontFamily);
|
|
297
|
+
return parsed.data;
|
|
298
|
+
}
|
|
299
|
+
function capitalize(s) {
|
|
300
|
+
return s.length > 0 ? s[0].toUpperCase() + s.slice(1) : s;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Units a staggered animation splits into — a text clip's word count, which is
|
|
304
|
+
* what a custom body reads off `inputs.staggerCount`. Zero when the clip does
|
|
305
|
+
* not stagger, matching the un-staggered block case.
|
|
306
|
+
*/
|
|
307
|
+
function staggerUnitCount(clip, stagger) {
|
|
308
|
+
if (!stagger || clip.mediaType !== "text")
|
|
309
|
+
return 0;
|
|
310
|
+
const text = clip.textStyle?.text ?? "";
|
|
311
|
+
const words = text.trim().split(/\s+/).filter((word) => word.length > 0);
|
|
312
|
+
return words.length;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Build an in-memory timeline bridge whose tools share the `ui_timeline_*`
|
|
316
|
+
* contract but run headlessly against a plain sequence of tracks/clips (no
|
|
317
|
+
* Zustand stores, no rendering).
|
|
318
|
+
*/
|
|
319
|
+
export function createTimelineToolBridge(initial = {}) {
|
|
320
|
+
const seed = initial.sequence;
|
|
321
|
+
const resolveAsset = initial.resolveAsset;
|
|
322
|
+
const bakeAnimation = initial.bakeAnimation;
|
|
323
|
+
const bakeModel3DClip = initial.bakeModel3DClip;
|
|
324
|
+
const loadComposition = initial.loadComposition;
|
|
325
|
+
const retargetFormat = initial.retargetFormat;
|
|
326
|
+
const generateTransition = initial.generateTransition;
|
|
327
|
+
const sequenceId = initial.sequenceId ?? "seq_eval";
|
|
328
|
+
const sequenceName = initial.sequenceName ?? "Sequence";
|
|
329
|
+
const projectId = initial.projectId ?? "";
|
|
330
|
+
const fps = seed?.fps ?? initial.fps ?? 30;
|
|
331
|
+
const width = seed?.width ?? initial.width ?? 1920;
|
|
332
|
+
const height = seed?.height ?? initial.height ?? 1080;
|
|
333
|
+
let playheadMs = 0;
|
|
334
|
+
let selectedClipIds = [];
|
|
335
|
+
// Absent until a midi track or `set_tempo` puts one there — a document with
|
|
336
|
+
// no midi in it should not grow a tempo field it never asked for.
|
|
337
|
+
let tempo = seed?.tempo;
|
|
338
|
+
let trackSeq = 0;
|
|
339
|
+
let clipSeq = 0;
|
|
340
|
+
let animSeq = 0;
|
|
341
|
+
let markerSeq = 0;
|
|
342
|
+
let versionSeq = 0;
|
|
343
|
+
let transitionSeq = 0;
|
|
344
|
+
const tracks = [];
|
|
345
|
+
let clips = [];
|
|
346
|
+
let markers = [];
|
|
347
|
+
// Document-level, like markers: a track is named by `clipId`, and a clip
|
|
348
|
+
// follows one through its own `trackBinding` (P0 AI Video, Phase 2).
|
|
349
|
+
let mediaTracks = [];
|
|
350
|
+
let transitionCandidates = [];
|
|
351
|
+
let auditionedTransitionCandidateId;
|
|
352
|
+
let appliedTransitionCandidates = [];
|
|
353
|
+
const derivedSequences = [];
|
|
354
|
+
let setup = seed?.setup
|
|
355
|
+
? structuredClone(seed.setup)
|
|
356
|
+
: null;
|
|
357
|
+
let beatSeq = 0;
|
|
358
|
+
const toolLog = [];
|
|
359
|
+
const previewTimesMs = [];
|
|
360
|
+
const previewedLayerKinds = new Set();
|
|
361
|
+
// Ids the sequence already uses. A seeded document brings its own, which the
|
|
362
|
+
// `track_1`/`clip_1` counters would otherwise collide with on the first edit.
|
|
363
|
+
const usedIds = new Set();
|
|
364
|
+
const mint = (prefix, next) => {
|
|
365
|
+
let id = `${prefix}_${next()}`;
|
|
366
|
+
while (usedIds.has(id))
|
|
367
|
+
id = `${prefix}_${next()}`;
|
|
368
|
+
usedIds.add(id);
|
|
369
|
+
return id;
|
|
370
|
+
};
|
|
371
|
+
const nextTrackId = () => mint("track", () => ++trackSeq);
|
|
372
|
+
const nextClipId = () => mint("clip", () => ++clipSeq);
|
|
373
|
+
const nextAnimId = () => mint("anim", () => ++animSeq);
|
|
374
|
+
const nextMarkerId = () => mint("marker", () => ++markerSeq);
|
|
375
|
+
const nextVersionId = () => mint("version", () => ++versionSeq);
|
|
376
|
+
const nextTransitionId = () => mint("transition", () => ++transitionSeq);
|
|
377
|
+
const clipEnd = (clip) => clip.startMs + clip.durationMs;
|
|
378
|
+
function isAmbiguousCut(outgoing, incoming) {
|
|
379
|
+
const cutTimeMs = incoming.startMs;
|
|
380
|
+
return clips.some((candidate) => {
|
|
381
|
+
if (candidate.id === outgoing.id ||
|
|
382
|
+
candidate.id === incoming.id ||
|
|
383
|
+
candidate.trackId !== outgoing.trackId) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
return ((candidate.startMs <= cutTimeMs && clipEnd(candidate) > cutTimeMs) ||
|
|
387
|
+
candidate.startMs === cutTimeMs);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function transitionSource(outgoing, incoming, durationMs) {
|
|
391
|
+
return {
|
|
392
|
+
sequenceId,
|
|
393
|
+
outgoingClipId: outgoing.id,
|
|
394
|
+
incomingClipId: incoming.id,
|
|
395
|
+
outgoingTakeId: activeTakeIdOf(outgoing),
|
|
396
|
+
incomingTakeId: activeTakeIdOf(incoming),
|
|
397
|
+
outgoingAssetId: outgoing.currentAssetId,
|
|
398
|
+
incomingAssetId: incoming.currentAssetId,
|
|
399
|
+
outgoingTransition: outgoing.transitionIn
|
|
400
|
+
? structuredClone(outgoing.transitionIn)
|
|
401
|
+
: undefined,
|
|
402
|
+
incomingTransition: incoming.transitionIn
|
|
403
|
+
? structuredClone(incoming.transitionIn)
|
|
404
|
+
: undefined,
|
|
405
|
+
outgoingStartMs: outgoing.startMs,
|
|
406
|
+
outgoingDurationMs: outgoing.durationMs,
|
|
407
|
+
incomingStartMs: incoming.startMs,
|
|
408
|
+
incomingDurationMs: incoming.durationMs,
|
|
409
|
+
cutTimeMs: incoming.startMs,
|
|
410
|
+
durationMs
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function assertFreshTransitionCandidate(candidate) {
|
|
414
|
+
const outgoing = clips.find((clip) => clip.id === candidate.source.outgoingClipId);
|
|
415
|
+
const incoming = clips.find((clip) => clip.id === candidate.source.incomingClipId);
|
|
416
|
+
if (!outgoing || !incoming) {
|
|
417
|
+
throw new Error("The generated transition candidate is stale because its selected clip no longer exists.");
|
|
418
|
+
}
|
|
419
|
+
if (isAmbiguousCut(outgoing, incoming)) {
|
|
420
|
+
throw new Error("The generated transition candidate is stale because the selected cut is ambiguous.");
|
|
421
|
+
}
|
|
422
|
+
const source = transitionSource(outgoing, incoming, candidate.source.durationMs);
|
|
423
|
+
if (JSON.stringify(source) !== JSON.stringify(candidate.source)) {
|
|
424
|
+
throw new Error("The generated transition candidate is stale because the selected clips or cut changed.");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function createTransitionCandidate(input) {
|
|
428
|
+
const outgoingClipId = input.outgoingClipId;
|
|
429
|
+
const incomingClipId = input.incomingClipId;
|
|
430
|
+
const outgoing = resolveClip(outgoingClipId);
|
|
431
|
+
const incoming = resolveClip(incomingClipId);
|
|
432
|
+
if (isAmbiguousCut(outgoing, incoming)) {
|
|
433
|
+
throw new Error("The selected cut is ambiguous. Choose two adjacent clips with no other clip crossing the cut.");
|
|
434
|
+
}
|
|
435
|
+
const planned = planTransitionAtCut(clips, {
|
|
436
|
+
outgoingClipId: outgoing.id,
|
|
437
|
+
incomingClipId: incoming.id,
|
|
438
|
+
durationMs: input.durationMs,
|
|
439
|
+
overlapMs: input.overlapMs,
|
|
440
|
+
type: input.type,
|
|
441
|
+
easing: input.easing,
|
|
442
|
+
color: input.color,
|
|
443
|
+
direction: input.direction,
|
|
444
|
+
softness: input.softness
|
|
445
|
+
});
|
|
446
|
+
if (!planned.ok)
|
|
447
|
+
throw new Error(planned.error);
|
|
448
|
+
if (!generateTransition) {
|
|
449
|
+
throw new Error("Selected-cut transition generation is unavailable because this surface has no provider generator.");
|
|
450
|
+
}
|
|
451
|
+
const source = transitionSource(outgoing, incoming, planned.candidate.durationMs);
|
|
452
|
+
const request = {
|
|
453
|
+
durationMs: planned.candidate.durationMs,
|
|
454
|
+
overlapMs: planned.candidate.overlapMs,
|
|
455
|
+
type: planned.candidate.type,
|
|
456
|
+
easing: input.easing,
|
|
457
|
+
color: input.color,
|
|
458
|
+
direction: input.direction,
|
|
459
|
+
softness: input.softness
|
|
460
|
+
};
|
|
461
|
+
const generated = await generateTransition({
|
|
462
|
+
sequenceId,
|
|
463
|
+
source,
|
|
464
|
+
request
|
|
465
|
+
});
|
|
466
|
+
if (generated.generationId.trim().length === 0 ||
|
|
467
|
+
generated.assetId.trim().length === 0) {
|
|
468
|
+
throw new Error("Selected-cut transition generation returned no generation id or asset.");
|
|
469
|
+
}
|
|
470
|
+
const id = nextTransitionId();
|
|
471
|
+
const candidate = {
|
|
472
|
+
id,
|
|
473
|
+
generationId: generated.generationId,
|
|
474
|
+
kind: "generated_transition_at_cut",
|
|
475
|
+
status: "success",
|
|
476
|
+
asset: {
|
|
477
|
+
id: generated.assetId,
|
|
478
|
+
contentType: generated.contentType ?? "video/mp4"
|
|
479
|
+
},
|
|
480
|
+
source,
|
|
481
|
+
request,
|
|
482
|
+
operation: {
|
|
483
|
+
op: "apply_generated_transition_at_cut",
|
|
484
|
+
candidateId: id,
|
|
485
|
+
outgoingClipId: outgoing.id,
|
|
486
|
+
incomingClipId: incoming.id,
|
|
487
|
+
cutTimeMs: incoming.startMs,
|
|
488
|
+
durationMs: planned.candidate.durationMs,
|
|
489
|
+
undoable: true
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
transitionCandidates = [...transitionCandidates, candidate];
|
|
493
|
+
return candidate;
|
|
494
|
+
}
|
|
495
|
+
function applyTransitionCandidate(candidateId) {
|
|
496
|
+
const candidate = transitionCandidates.find((entry) => entry.id === candidateId);
|
|
497
|
+
if (!candidate) {
|
|
498
|
+
throw new Error(`No generated transition candidate named "${candidateId}" exists.`);
|
|
499
|
+
}
|
|
500
|
+
assertFreshTransitionCandidate(candidate);
|
|
501
|
+
if (appliedTransitionCandidates.some((entry) => entry.id === candidate.id)) {
|
|
502
|
+
throw new Error(`Generated transition candidate "${candidate.id}" was already applied.`);
|
|
503
|
+
}
|
|
504
|
+
const planned = planTransitionAtCut(clips, {
|
|
505
|
+
outgoingClipId: candidate.source.outgoingClipId,
|
|
506
|
+
incomingClipId: candidate.source.incomingClipId,
|
|
507
|
+
...candidate.request
|
|
508
|
+
});
|
|
509
|
+
if (!planned.ok)
|
|
510
|
+
throw new Error(planned.error);
|
|
511
|
+
const applied = applyTransitionAtCutCandidate(clips, planned.candidate);
|
|
512
|
+
if (!applied.ok)
|
|
513
|
+
throw new Error(applied.error);
|
|
514
|
+
clips = applied.clips;
|
|
515
|
+
appliedTransitionCandidates = [
|
|
516
|
+
...appliedTransitionCandidates,
|
|
517
|
+
structuredClone(candidate)
|
|
518
|
+
];
|
|
519
|
+
if (auditionedTransitionCandidateId === candidate.id) {
|
|
520
|
+
auditionedTransitionCandidateId = undefined;
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
candidate,
|
|
524
|
+
operation: candidate.operation,
|
|
525
|
+
description: `Apply generated transition candidate "${candidate.id}" to the selected cut as one undoable operation.`
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function addTrackInternal(type, name) {
|
|
529
|
+
const index = tracks.length;
|
|
530
|
+
const track = makeTrack({
|
|
531
|
+
id: nextTrackId(),
|
|
532
|
+
type,
|
|
533
|
+
name: name ?? `${capitalize(type)} ${index + 1}`,
|
|
534
|
+
index
|
|
535
|
+
});
|
|
536
|
+
if (type === "midi") {
|
|
537
|
+
// The track owns the voice, so a midi track without one plays nothing.
|
|
538
|
+
track.instrument = DEFAULT_MIDI_INSTRUMENT;
|
|
539
|
+
// Ticks are read against the tempo, so the first midi track is where a
|
|
540
|
+
// document that never had one gets it — written down rather than left
|
|
541
|
+
// implicit, so the next reader does not have to know the default.
|
|
542
|
+
tempo ??= DEFAULT_TEMPO;
|
|
543
|
+
}
|
|
544
|
+
tracks.push(track);
|
|
545
|
+
return track;
|
|
546
|
+
}
|
|
547
|
+
/** The midi track a clip or instrument op names, refusing any other type. */
|
|
548
|
+
function resolveMidiTrack(idOrName) {
|
|
549
|
+
const track = resolveTrack(idOrName);
|
|
550
|
+
if (track.type !== "midi") {
|
|
551
|
+
throw new Error(`Track "${track.name}" is a ${track.type} track — notes are played by a midi track's instrument. ${validUnits(tracks.filter((t) => t.type === "midi"), "midi track")}`);
|
|
552
|
+
}
|
|
553
|
+
return track;
|
|
554
|
+
}
|
|
555
|
+
/** The midi clip a note op names, refusing any other kind. */
|
|
556
|
+
function resolveMidiClip(target) {
|
|
557
|
+
const clip = resolveClip(target);
|
|
558
|
+
if (clip.mediaType !== "midi") {
|
|
559
|
+
throw new Error(`Clip "${clip.name}" is a ${clip.mediaType} clip — only a midi clip carries notes. Place one with add_midi_clip.`);
|
|
560
|
+
}
|
|
561
|
+
return clip;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* The voice an instrument argument names: `{preset}` resolved to the sound
|
|
565
|
+
* it stands for, or a spelled-out instrument taken as it came.
|
|
566
|
+
*
|
|
567
|
+
* The preset's object is copied onto the track rather than referenced, so
|
|
568
|
+
* the document keeps the sound it was given even if the shipped table
|
|
569
|
+
* changes under it.
|
|
570
|
+
*/
|
|
571
|
+
function resolveInstrumentArg(raw) {
|
|
572
|
+
const value = (raw ?? {});
|
|
573
|
+
const presetId = value["preset"];
|
|
574
|
+
if (typeof presetId !== "string") {
|
|
575
|
+
return value;
|
|
576
|
+
}
|
|
577
|
+
const preset = findInstrumentPreset(presetId);
|
|
578
|
+
if (!preset) {
|
|
579
|
+
throw new Error(`No instrument preset named "${presetId}". Valid presets: ${MIDI_INSTRUMENT_PRESETS.map((p) => `${p.id} ("${p.name}")`).join(", ")}.`);
|
|
580
|
+
}
|
|
581
|
+
return structuredClone(preset.instrument);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* The notes a caller sent, minted and checked as one list.
|
|
585
|
+
*
|
|
586
|
+
* Every problem is reported at once: an agent that fixes one note only to be
|
|
587
|
+
* told about the next spends a call per note.
|
|
588
|
+
*/
|
|
589
|
+
function buildNotes(raw, clipName) {
|
|
590
|
+
const notes = (raw ?? []).map((note) => createMidiNote({
|
|
591
|
+
id: note["id"],
|
|
592
|
+
pitch: note["pitch"],
|
|
593
|
+
velocity: note["velocity"],
|
|
594
|
+
startTick: note["start_tick"],
|
|
595
|
+
durationTick: note["duration_tick"]
|
|
596
|
+
}));
|
|
597
|
+
const problems = validateNotes(notes);
|
|
598
|
+
if (problems.length > 0) {
|
|
599
|
+
throw new Error(`Clip "${clipName}" was not given its notes — ${problems.length} problem(s): ` +
|
|
600
|
+
problems
|
|
601
|
+
.map((p) => (p.noteId ? `${p.noteId}: ${p.message}` : p.message))
|
|
602
|
+
.join(" "));
|
|
603
|
+
}
|
|
604
|
+
return sortNotes(notes);
|
|
605
|
+
}
|
|
606
|
+
function findOrCreateTrack(type) {
|
|
607
|
+
const existing = tracks.find((t) => t.type === type);
|
|
608
|
+
if (existing)
|
|
609
|
+
return existing;
|
|
610
|
+
return addTrackInternal(type);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* The ids and names a caller could have used, for the message a failed
|
|
614
|
+
* lookup throws. Capped, because a long cut has hundreds of clips and an
|
|
615
|
+
* error listing all of them is one an agent stops reading.
|
|
616
|
+
*/
|
|
617
|
+
function validUnits(units, kind) {
|
|
618
|
+
if (units.length === 0)
|
|
619
|
+
return `The timeline has no ${kind}s yet.`;
|
|
620
|
+
const shown = units
|
|
621
|
+
.slice(0, MAX_LISTED_UNITS)
|
|
622
|
+
.map((u) => `${u.id} ("${u.name}")`)
|
|
623
|
+
.join(", ");
|
|
624
|
+
const rest = units.length - MAX_LISTED_UNITS;
|
|
625
|
+
return rest > 0
|
|
626
|
+
? `Valid ${kind}s: ${shown}, and ${rest} more — call get_state for the full list.`
|
|
627
|
+
: `Valid ${kind}s: ${shown}.`;
|
|
628
|
+
}
|
|
629
|
+
function resolveTrack(idOrName) {
|
|
630
|
+
const byId = tracks.find((t) => t.id === idOrName);
|
|
631
|
+
if (byId)
|
|
632
|
+
return byId;
|
|
633
|
+
if (isShortResourceId(idOrName)) {
|
|
634
|
+
const matches = tracks.filter((track) => track.id.startsWith(idOrName));
|
|
635
|
+
if (matches.length === 1) {
|
|
636
|
+
return matches[0];
|
|
637
|
+
}
|
|
638
|
+
if (matches.length > 1) {
|
|
639
|
+
throw new Error(`Short track id "${idOrName}" matches more than one track. Use the full id or name.`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const lower = idOrName.toLowerCase();
|
|
643
|
+
const byName = tracks.find((t) => t.name.toLowerCase() === lower);
|
|
644
|
+
if (byName)
|
|
645
|
+
return byName;
|
|
646
|
+
throw new Error(`No track found matching "${idOrName}". ${validUnits(tracks, "track")}`);
|
|
647
|
+
}
|
|
648
|
+
function trackEndMs(trackId) {
|
|
649
|
+
return clips
|
|
650
|
+
.filter((c) => c.trackId === trackId)
|
|
651
|
+
.reduce((m, c) => Math.max(m, c.startMs + c.durationMs), 0);
|
|
652
|
+
}
|
|
653
|
+
function resolveClip(target) {
|
|
654
|
+
if (target.toLowerCase() === "selected") {
|
|
655
|
+
if (selectedClipIds.length !== 1) {
|
|
656
|
+
throw new Error(`"selected" requires exactly one selected clip (currently ${selectedClipIds.length}).`);
|
|
657
|
+
}
|
|
658
|
+
const clip = clips.find((c) => c.id === selectedClipIds[0]);
|
|
659
|
+
if (!clip)
|
|
660
|
+
throw new Error("Selected clip no longer exists.");
|
|
661
|
+
return clip;
|
|
662
|
+
}
|
|
663
|
+
const byId = clips.find((c) => c.id === target);
|
|
664
|
+
if (byId)
|
|
665
|
+
return byId;
|
|
666
|
+
if (isShortResourceId(target)) {
|
|
667
|
+
const matches = clips.filter((clip) => clip.id.startsWith(target));
|
|
668
|
+
if (matches.length === 1) {
|
|
669
|
+
return matches[0];
|
|
670
|
+
}
|
|
671
|
+
if (matches.length > 1) {
|
|
672
|
+
throw new Error(`Short clip id "${target}" matches more than one clip. Use the full id or name.`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const lower = target.toLowerCase();
|
|
676
|
+
const byName = clips.find((c) => c.name.toLowerCase() === lower);
|
|
677
|
+
if (byName)
|
|
678
|
+
return byName;
|
|
679
|
+
throw new Error(`No clip found matching "${target}". ${validUnits(clips, "clip")}`);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* The body of `trim_clip`, shared with `set_clip_params`: a caller that sends
|
|
683
|
+
* `durationMs` alongside a style change means the same edit either way, and
|
|
684
|
+
* two copies of the group-trim rule would drift.
|
|
685
|
+
*/
|
|
686
|
+
function applyTrim(clip, patch) {
|
|
687
|
+
// A group carries what it holds (D4): shortening one pulls its children
|
|
688
|
+
// inside the window that leaves, rather than leaving them hanging past an
|
|
689
|
+
// edge nothing draws.
|
|
690
|
+
if (isGroupClip(clip) && patch.durationMs !== undefined) {
|
|
691
|
+
clips = trimGroup(clips, clip.id, "end", patch.durationMs - clip.durationMs);
|
|
692
|
+
return clips.find((c) => c.id === clip.id);
|
|
693
|
+
}
|
|
694
|
+
let next = clip;
|
|
695
|
+
if (patch.durationMs !== undefined) {
|
|
696
|
+
if (patch.durationMs <= 0) {
|
|
697
|
+
throw new Error(`durationMs must be greater than 0 (got ${patch.durationMs}); delete the clip instead of trimming it to nothing`);
|
|
698
|
+
}
|
|
699
|
+
next = trimClip(clip, "end", patch.durationMs - clip.durationMs);
|
|
700
|
+
clips = clips.map((entry) => entry.id === clip.id ? next : entry);
|
|
701
|
+
}
|
|
702
|
+
if (patch.inPointMs !== undefined)
|
|
703
|
+
next.inPointMs = patch.inPointMs;
|
|
704
|
+
if (patch.outPointMs !== undefined)
|
|
705
|
+
next.outPointMs = patch.outPointMs;
|
|
706
|
+
return next;
|
|
707
|
+
}
|
|
708
|
+
function applyTrimStart(clip, startMs) {
|
|
709
|
+
const deltaMs = clip.startMs - Math.max(0, startMs);
|
|
710
|
+
if (deltaMs === 0)
|
|
711
|
+
return clip;
|
|
712
|
+
if (isGroupClip(clip)) {
|
|
713
|
+
clips = trimGroup(clips, clip.id, "start", deltaMs);
|
|
714
|
+
return clips.find((entry) => entry.id === clip.id);
|
|
715
|
+
}
|
|
716
|
+
const next = trimClip(clip, "start", deltaMs);
|
|
717
|
+
clips = clips.map((entry) => entry.id === clip.id ? next : entry);
|
|
718
|
+
return next;
|
|
719
|
+
}
|
|
720
|
+
/** The body of `move_clip`, shared with `set_clip_params`. */
|
|
721
|
+
function applyMove(clip, patch) {
|
|
722
|
+
// Moving a group moves what it holds by the same delta (D4). Children keep
|
|
723
|
+
// their own tracks, so their z-order is untouched (I9) — only the group
|
|
724
|
+
// itself takes a new `trackId`.
|
|
725
|
+
let moved = clip;
|
|
726
|
+
if (isGroupClip(clip) && patch.startMs !== undefined) {
|
|
727
|
+
const nextStartMs = Math.max(0, patch.startMs);
|
|
728
|
+
clips = moveGroup(clips, clip.id, nextStartMs - clip.startMs);
|
|
729
|
+
moved = clips.find((c) => c.id === clip.id);
|
|
730
|
+
}
|
|
731
|
+
else if (patch.startMs !== undefined) {
|
|
732
|
+
clip.startMs = Math.max(0, patch.startMs);
|
|
733
|
+
}
|
|
734
|
+
if (patch.trackId !== undefined) {
|
|
735
|
+
moved.trackId = resolveTrack(patch.trackId).id;
|
|
736
|
+
}
|
|
737
|
+
return moved;
|
|
738
|
+
}
|
|
739
|
+
/** Resolve a marker by id, or by case-insensitive label. */
|
|
740
|
+
function resolveMarker(target) {
|
|
741
|
+
const byId = markers.find((m) => m.id === target);
|
|
742
|
+
if (byId)
|
|
743
|
+
return byId;
|
|
744
|
+
const lower = target.toLowerCase();
|
|
745
|
+
const byLabel = markers.find((m) => m.label.toLowerCase() === lower);
|
|
746
|
+
if (byLabel)
|
|
747
|
+
return byLabel;
|
|
748
|
+
const known = markers
|
|
749
|
+
.map((m) => `${m.id} ("${m.label}") at ${m.timeMs}ms`)
|
|
750
|
+
.join(", ");
|
|
751
|
+
throw new Error(`No marker matches "${target}". Use a marker id or its label. ` +
|
|
752
|
+
(known.length > 0
|
|
753
|
+
? `Markers: ${known}.`
|
|
754
|
+
: "This sequence has no markers yet."));
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Resolve the clips one beat op addresses: the named ones, or every clip.
|
|
758
|
+
*
|
|
759
|
+
* A target that matches nothing comes back as a miss rather than throwing —
|
|
760
|
+
* a batch op that dies on one bad name hides what the other targets did.
|
|
761
|
+
*/
|
|
762
|
+
function resolveSnapTargets(targets) {
|
|
763
|
+
if (!targets || targets.length === 0) {
|
|
764
|
+
return { clips: [...clips], missing: [] };
|
|
765
|
+
}
|
|
766
|
+
const resolved = [];
|
|
767
|
+
const missing = [];
|
|
768
|
+
for (const target of targets) {
|
|
769
|
+
try {
|
|
770
|
+
const clip = resolveClip(target);
|
|
771
|
+
if (!resolved.includes(clip))
|
|
772
|
+
resolved.push(clip);
|
|
773
|
+
}
|
|
774
|
+
catch {
|
|
775
|
+
// Recorded as a skip in the op's own report, with the reason.
|
|
776
|
+
missing.push(target);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return { clips: resolved, missing };
|
|
780
|
+
}
|
|
781
|
+
function serializeTrack(t) {
|
|
782
|
+
return {
|
|
783
|
+
id: t.id,
|
|
784
|
+
name: t.name,
|
|
785
|
+
type: t.type,
|
|
786
|
+
index: t.index,
|
|
787
|
+
visible: t.visible,
|
|
788
|
+
locked: t.locked,
|
|
789
|
+
muted: t.muted ?? false,
|
|
790
|
+
solo: t.solo ?? false,
|
|
791
|
+
clipCount: clips.filter((c) => c.trackId === t.id).length,
|
|
792
|
+
// The voice every clip on this track plays. Absent on every other type.
|
|
793
|
+
instrument: t.instrument,
|
|
794
|
+
// Which shipped voice that is, when it is one. A track stores the
|
|
795
|
+
// instrument rather than the id it was picked from, so this is read
|
|
796
|
+
// back by sound — an instrument edited away from a preset reports none.
|
|
797
|
+
presetId: t.instrument ? presetIdForInstrument(t.instrument) : undefined
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Build one `preset: "custom"` animation. `curves` are checked and stored;
|
|
802
|
+
* `code` is baked into curves first, by the host that supplied a baker. Both
|
|
803
|
+
* paths end at `normalizeCustomCurves`, the single gate the compiler and the
|
|
804
|
+
* validator also run, so what is stored is what will render.
|
|
805
|
+
*/
|
|
806
|
+
async function buildCustomAnimation(clip, input) {
|
|
807
|
+
const code = typeof input.code === "string" ? input.code.trim() : "";
|
|
808
|
+
const hasCurves = input.curves !== undefined;
|
|
809
|
+
const hasCode = code !== "";
|
|
810
|
+
if (hasCurves && hasCode) {
|
|
811
|
+
throw new Error('A "custom" animation takes exactly one of `curves` and `code`; both were given. Pass the keyframes, or the body that produces them.');
|
|
812
|
+
}
|
|
813
|
+
if (!hasCurves && !hasCode) {
|
|
814
|
+
throw new Error('A "custom" animation needs `curves` or `code`. Accepted shape: ' +
|
|
815
|
+
'{role: "in", preset: "custom", curves: [{property: "offsetY", keyframes: [{t: 0, value: 160}, {t: 1, value: 0}]}]}' +
|
|
816
|
+
' — `t` runs 0..1 over the window. `code` is a JS body baked into curves instead. Either may also be nested under `custom`.');
|
|
817
|
+
}
|
|
818
|
+
// Curves are normalized to 0..1 over the window, so a custom animation
|
|
819
|
+
// with no duration of its own spans the clip and nothing is cropped.
|
|
820
|
+
const durationMs = input.durationMs ?? clip.durationMs;
|
|
821
|
+
let curves;
|
|
822
|
+
let maskInput;
|
|
823
|
+
if (hasCurves) {
|
|
824
|
+
const normalized = normalizeCustomCurves(input.curves);
|
|
825
|
+
if (!normalized.ok)
|
|
826
|
+
throw new Error(normalized.error);
|
|
827
|
+
curves = normalized.curves;
|
|
828
|
+
maskInput = input.mask;
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
if (!bakeAnimation) {
|
|
832
|
+
throw new Error('This surface cannot run `code`: no animation baker is wired to it. Pass `curves` instead, or bake the body through POST /api/timelines/animations/bake.');
|
|
833
|
+
}
|
|
834
|
+
const baked = await bakeAnimation({
|
|
835
|
+
code,
|
|
836
|
+
role: input.role,
|
|
837
|
+
durationMs,
|
|
838
|
+
clipDurationMs: clip.durationMs,
|
|
839
|
+
canvas: { width, height },
|
|
840
|
+
params: input.params,
|
|
841
|
+
staggerCount: staggerUnitCount(clip, input.stagger)
|
|
842
|
+
});
|
|
843
|
+
if (!baked.ok || !baked.curves) {
|
|
844
|
+
throw new Error(baked.error ?? "The animation body returned no curves.");
|
|
845
|
+
}
|
|
846
|
+
const normalized = normalizeCustomCurves(baked.curves);
|
|
847
|
+
if (!normalized.ok)
|
|
848
|
+
throw new Error(normalized.error);
|
|
849
|
+
curves = normalized.curves;
|
|
850
|
+
maskInput = baked.mask ?? input.mask;
|
|
851
|
+
}
|
|
852
|
+
const mask = resolveCustomMask(curves, maskInput);
|
|
853
|
+
if (!mask.ok)
|
|
854
|
+
throw new Error(mask.error);
|
|
855
|
+
const custom = {
|
|
856
|
+
curves,
|
|
857
|
+
bakedAt: new Date().toISOString()
|
|
858
|
+
};
|
|
859
|
+
if (hasCode)
|
|
860
|
+
custom.code = code;
|
|
861
|
+
if (mask.mask)
|
|
862
|
+
custom.mask = mask.mask;
|
|
863
|
+
return {
|
|
864
|
+
id: nextAnimId(),
|
|
865
|
+
role: input.role,
|
|
866
|
+
preset: CUSTOM_ANIMATION_PRESET_ID,
|
|
867
|
+
durationMs,
|
|
868
|
+
delayMs: input.delayMs,
|
|
869
|
+
easing: input.easing,
|
|
870
|
+
params: input.params,
|
|
871
|
+
stagger: input.stagger,
|
|
872
|
+
custom
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function serializeClip(c) {
|
|
876
|
+
const track = tracks.find((t) => t.id === c.trackId);
|
|
877
|
+
return {
|
|
878
|
+
id: c.id,
|
|
879
|
+
name: c.name,
|
|
880
|
+
trackId: c.trackId,
|
|
881
|
+
trackName: track?.name ?? null,
|
|
882
|
+
mediaType: c.mediaType,
|
|
883
|
+
sourceType: c.sourceType,
|
|
884
|
+
startMs: c.startMs,
|
|
885
|
+
durationMs: c.durationMs,
|
|
886
|
+
endMs: c.startMs + c.durationMs,
|
|
887
|
+
inPointMs: c.inPointMs,
|
|
888
|
+
outPointMs: c.outPointMs,
|
|
889
|
+
status: c.status,
|
|
890
|
+
currentAssetId: c.currentAssetId,
|
|
891
|
+
activeTakeId: activeTakeIdOf(c),
|
|
892
|
+
takeCount: c.versions?.length ?? 0,
|
|
893
|
+
prompt: c.prompt,
|
|
894
|
+
provider: c.provider,
|
|
895
|
+
model: c.model,
|
|
896
|
+
voice: c.voice,
|
|
897
|
+
animations: (c.animations ?? []).map((a) => ({
|
|
898
|
+
role: a.role,
|
|
899
|
+
preset: a.preset
|
|
900
|
+
})),
|
|
901
|
+
hidden: c.hidden ?? false,
|
|
902
|
+
muted: c.muted ?? false,
|
|
903
|
+
locked: c.locked,
|
|
904
|
+
opacity: c.opacity,
|
|
905
|
+
transform: c.transform,
|
|
906
|
+
textStyle: c.textStyle,
|
|
907
|
+
shapeStyle: c.shapeStyle,
|
|
908
|
+
captionStyle: c.caption?.style,
|
|
909
|
+
transitionIn: c.transitionIn,
|
|
910
|
+
mask: c.mask,
|
|
911
|
+
matte: c.matte,
|
|
912
|
+
timeRemap: c.timeRemap,
|
|
913
|
+
effects: c.effects,
|
|
914
|
+
parentId: c.parentId,
|
|
915
|
+
reframe: c.reframe
|
|
916
|
+
? {
|
|
917
|
+
mode: c.reframe.mode,
|
|
918
|
+
trackId: c.reframe.trackId,
|
|
919
|
+
safeMargin: c.reframe.safeMargin,
|
|
920
|
+
smoothing: c.reframe.smoothing,
|
|
921
|
+
sampleCount: c.reframe.samples?.length ?? 0,
|
|
922
|
+
keyframeCount: c.reframe.keyframes?.length ?? 0
|
|
923
|
+
}
|
|
924
|
+
: undefined,
|
|
925
|
+
// The notes themselves are the clip's bulk — a phrase is hundreds of
|
|
926
|
+
// them — so state reports how many there are and how many the window
|
|
927
|
+
// actually plays. `set_notes` sends the list; nothing reads it back.
|
|
928
|
+
noteCount: c.notes?.length,
|
|
929
|
+
audibleNoteCount: c.mediaType === "midi"
|
|
930
|
+
? visibleNotes(c, resolveTempo({ tempo }).bpm).length
|
|
931
|
+
: undefined,
|
|
932
|
+
// Where the phrase starts in musical time, so placing the next one is
|
|
933
|
+
// reading a bar number rather than dividing milliseconds by the tempo.
|
|
934
|
+
startBarsBeats: c.mediaType === "midi"
|
|
935
|
+
? formatBarsBeats(c.startMs, resolveTempo({ tempo }))
|
|
936
|
+
: undefined
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
// Seed from a real sequence when one was handed over, otherwise from the
|
|
940
|
+
// shorthand. Cloned, so the bridge never writes through to the caller's state.
|
|
941
|
+
if (seed) {
|
|
942
|
+
for (const track of seed.tracks) {
|
|
943
|
+
const copy = structuredClone(track);
|
|
944
|
+
usedIds.add(copy.id);
|
|
945
|
+
tracks.push(copy);
|
|
946
|
+
}
|
|
947
|
+
for (const clip of seed.clips) {
|
|
948
|
+
const copy = structuredClone(clip);
|
|
949
|
+
usedIds.add(copy.id);
|
|
950
|
+
for (const animation of copy.animations ?? [])
|
|
951
|
+
usedIds.add(animation.id);
|
|
952
|
+
clips.push(copy);
|
|
953
|
+
}
|
|
954
|
+
for (const marker of seed.markers ?? []) {
|
|
955
|
+
const copy = structuredClone(marker);
|
|
956
|
+
usedIds.add(copy.id);
|
|
957
|
+
markers.push(copy);
|
|
958
|
+
}
|
|
959
|
+
for (const mediaTrack of seed.mediaTracks ?? []) {
|
|
960
|
+
const copy = structuredClone(mediaTrack);
|
|
961
|
+
usedIds.add(copy.id);
|
|
962
|
+
mediaTracks.push(copy);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
// Seed initial tracks and clips.
|
|
966
|
+
for (const t of seed ? [] : (initial.tracks ?? [])) {
|
|
967
|
+
addTrackInternal(t.type, t.name);
|
|
968
|
+
}
|
|
969
|
+
for (const c of seed ? [] : (initial.clips ?? [])) {
|
|
970
|
+
const track = tracks[c.trackIndex];
|
|
971
|
+
if (!track) {
|
|
972
|
+
throw new Error(`Initial clip "${c.name}" references trackIndex ${c.trackIndex}, but only ${tracks.length} track(s) exist.`);
|
|
973
|
+
}
|
|
974
|
+
clips.push(makeClip({
|
|
975
|
+
id: nextClipId(),
|
|
976
|
+
trackId: track.id,
|
|
977
|
+
name: c.name,
|
|
978
|
+
startMs: c.startMs,
|
|
979
|
+
durationMs: c.durationMs,
|
|
980
|
+
mediaType: c.mediaType ?? "video",
|
|
981
|
+
sourceType: "imported",
|
|
982
|
+
status: "generated"
|
|
983
|
+
}));
|
|
984
|
+
}
|
|
985
|
+
/** Resolve a beat by id, or by its 1-based position in the plan. */
|
|
986
|
+
function resolveBeat(target) {
|
|
987
|
+
const beats = setup?.beats ?? [];
|
|
988
|
+
const byId = beats.find((beat) => beat.id === target);
|
|
989
|
+
if (byId)
|
|
990
|
+
return byId;
|
|
991
|
+
const position = Number.parseInt(target, 10);
|
|
992
|
+
const byPosition = beats[position - 1];
|
|
993
|
+
if (Number.isFinite(position) && byPosition)
|
|
994
|
+
return byPosition;
|
|
995
|
+
throw new Error(`No beat matches "${target}". Use a beat id or its 1-based position. ` +
|
|
996
|
+
(beats.length > 0
|
|
997
|
+
? `This plan has ${beats.length} beats.`
|
|
998
|
+
: "This sequence has no beat plan yet; run ui_timeline_plan_beats first."));
|
|
999
|
+
}
|
|
1000
|
+
const tools = [
|
|
1001
|
+
sharedTool("ui_timeline_get_state", async () => {
|
|
1002
|
+
const durationMs = clips.reduce((m, c) => Math.max(m, c.startMs + c.durationMs), 0);
|
|
1003
|
+
return {
|
|
1004
|
+
ok: true,
|
|
1005
|
+
sequenceId,
|
|
1006
|
+
fps,
|
|
1007
|
+
width,
|
|
1008
|
+
height,
|
|
1009
|
+
durationMs,
|
|
1010
|
+
playheadMs,
|
|
1011
|
+
selectedClipIds: [...selectedClipIds],
|
|
1012
|
+
// Absent when the document carries none: a reader that needs a
|
|
1013
|
+
// number reads DEFAULT_TEMPO, and saying so beats printing a tempo
|
|
1014
|
+
// the document does not store.
|
|
1015
|
+
tempo,
|
|
1016
|
+
tracks: tracks.map(serializeTrack),
|
|
1017
|
+
clips: clips.map(serializeClip),
|
|
1018
|
+
mediaTracks: mediaTracks.map((track) => ({
|
|
1019
|
+
id: track.id,
|
|
1020
|
+
clipId: track.clipId,
|
|
1021
|
+
sourceAssetId: track.sourceAssetId,
|
|
1022
|
+
name: track.name,
|
|
1023
|
+
kind: track.kind,
|
|
1024
|
+
sourceStartMs: track.sourceStartMs,
|
|
1025
|
+
sourceEndMs: track.sourceEndMs,
|
|
1026
|
+
sampleCount: track.samples.length,
|
|
1027
|
+
confidence: track.confidence,
|
|
1028
|
+
status: track.status,
|
|
1029
|
+
provenance: track.provenance
|
|
1030
|
+
})),
|
|
1031
|
+
markers: markers.map((m) => ({ ...m }))
|
|
1032
|
+
};
|
|
1033
|
+
}),
|
|
1034
|
+
sharedTool("ui_timeline_add_track", async ({ type, name }) => {
|
|
1035
|
+
const track = addTrackInternal(type, name);
|
|
1036
|
+
return { ok: true, track: serializeTrack(track) };
|
|
1037
|
+
}),
|
|
1038
|
+
sharedTool("ui_timeline_move_track", async (args) => {
|
|
1039
|
+
const { target, toIndex, before, after } = resolveMoveTrackArgs(args);
|
|
1040
|
+
const track = resolveTrack(target);
|
|
1041
|
+
const destination = {};
|
|
1042
|
+
if (toIndex !== undefined)
|
|
1043
|
+
destination.toIndex = toIndex;
|
|
1044
|
+
if (before !== undefined) {
|
|
1045
|
+
destination.beforeId = resolveTrack(before).id;
|
|
1046
|
+
}
|
|
1047
|
+
if (after !== undefined) {
|
|
1048
|
+
destination.afterId = resolveTrack(after).id;
|
|
1049
|
+
}
|
|
1050
|
+
const orderedIds = moveTrackOrder(tracks, track.id, destination);
|
|
1051
|
+
const byId = new Map(tracks.map((t) => [t.id, t]));
|
|
1052
|
+
// The array order is what `get_state` prints, so keep it and the
|
|
1053
|
+
// indices saying the same thing.
|
|
1054
|
+
tracks.length = 0;
|
|
1055
|
+
orderedIds.forEach((id, i) => {
|
|
1056
|
+
const moved = byId.get(id);
|
|
1057
|
+
moved.index = i;
|
|
1058
|
+
tracks.push(moved);
|
|
1059
|
+
});
|
|
1060
|
+
return {
|
|
1061
|
+
ok: true,
|
|
1062
|
+
track: serializeTrack(track),
|
|
1063
|
+
tracks: tracks.map(serializeTrack)
|
|
1064
|
+
};
|
|
1065
|
+
}),
|
|
1066
|
+
sharedTool("ui_timeline_add_text_clip", async ({ text, name, transform, trackId, startMs, durationMs, opacity, style, ...loose }) => {
|
|
1067
|
+
const track = trackId
|
|
1068
|
+
? resolveTrack(trackId)
|
|
1069
|
+
: findOrCreateTrack("overlay");
|
|
1070
|
+
// `style` wins over a top-level twin: a caller that sent both meant the
|
|
1071
|
+
// bag it named.
|
|
1072
|
+
const s = {
|
|
1073
|
+
...loose,
|
|
1074
|
+
...(style ??
|
|
1075
|
+
{})
|
|
1076
|
+
};
|
|
1077
|
+
const clip = makeClip({
|
|
1078
|
+
id: nextClipId(),
|
|
1079
|
+
trackId: track.id,
|
|
1080
|
+
name: name ?? text,
|
|
1081
|
+
startMs: startMs ?? trackEndMs(track.id),
|
|
1082
|
+
durationMs: durationMs ?? DEFAULT_TEXT_CLIP_DURATION_MS,
|
|
1083
|
+
mediaType: "text",
|
|
1084
|
+
sourceType: "imported",
|
|
1085
|
+
status: "generated",
|
|
1086
|
+
textStyle: textStyleWithDefaults(text, s)
|
|
1087
|
+
});
|
|
1088
|
+
if (opacity !== undefined)
|
|
1089
|
+
clip.opacity = opacity;
|
|
1090
|
+
if (transform)
|
|
1091
|
+
clip.transform = mergeClipTransform(clip.transform, transform);
|
|
1092
|
+
clips.push(clip);
|
|
1093
|
+
selectedClipIds = [clip.id];
|
|
1094
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1095
|
+
}),
|
|
1096
|
+
sharedTool("ui_timeline_delete_track", async (args) => {
|
|
1097
|
+
const { target, deleteClips } = resolveDeleteTrackArgs(args);
|
|
1098
|
+
const track = resolveTrack(target);
|
|
1099
|
+
const onIt = clips.filter((c) => c.trackId === track.id);
|
|
1100
|
+
if (onIt.length > 0 && !deleteClips) {
|
|
1101
|
+
throw new Error(`Track "${track.name}" still holds ${onIt.length} clip(s): ` +
|
|
1102
|
+
`${onIt.map((c) => c.id).join(", ")}. Move them first, or pass ` +
|
|
1103
|
+
"deleteClips: true to delete them with the track.");
|
|
1104
|
+
}
|
|
1105
|
+
const removedClipIds = onIt.map((c) => c.id);
|
|
1106
|
+
const kept = clips.filter((c) => c.trackId !== track.id);
|
|
1107
|
+
clips.length = 0;
|
|
1108
|
+
clips.push(...kept);
|
|
1109
|
+
// A parent that went with the track would leave its children pointing
|
|
1110
|
+
// at a clip that no longer exists, which the validator reads as a
|
|
1111
|
+
// broken document rather than a deletion.
|
|
1112
|
+
for (const clip of clips) {
|
|
1113
|
+
if (clip.parentId && removedClipIds.includes(clip.parentId)) {
|
|
1114
|
+
delete clip.parentId;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
selectedClipIds = selectedClipIds.filter((id) => !removedClipIds.includes(id));
|
|
1118
|
+
const remaining = tracks.filter((t) => t.id !== track.id);
|
|
1119
|
+
tracks.length = 0;
|
|
1120
|
+
// Index is z-order, so the stack has to close over the gap.
|
|
1121
|
+
remaining.forEach((t, i) => {
|
|
1122
|
+
t.index = i;
|
|
1123
|
+
tracks.push(t);
|
|
1124
|
+
});
|
|
1125
|
+
return {
|
|
1126
|
+
ok: true,
|
|
1127
|
+
deleted: { id: track.id, name: track.name, type: track.type },
|
|
1128
|
+
deletedClipIds: removedClipIds,
|
|
1129
|
+
tracks: tracks.map(serializeTrack)
|
|
1130
|
+
};
|
|
1131
|
+
}),
|
|
1132
|
+
sharedTool("ui_timeline_add_media_clip", async ({ asset, trackId, startMs, durationMs, name, transform }) => {
|
|
1133
|
+
if (!resolveAsset) {
|
|
1134
|
+
throw new Error("This timeline surface cannot look up assets, so an existing asset cannot be placed here.");
|
|
1135
|
+
}
|
|
1136
|
+
const ref = asset;
|
|
1137
|
+
const found = await resolveAsset(ref);
|
|
1138
|
+
if (!found) {
|
|
1139
|
+
throw new Error(`No asset found for "${ref}". Pass an asset id or an asset:// URI from list_assets.`);
|
|
1140
|
+
}
|
|
1141
|
+
const mediaType = mediaTypeForContentType(found.contentType);
|
|
1142
|
+
if (!mediaType) {
|
|
1143
|
+
throw new Error(`Asset "${found.name}" is ${found.contentType}, which is not video, image, or audio and cannot go on a timeline.`);
|
|
1144
|
+
}
|
|
1145
|
+
if (mediaType !== "image" && durationMs === undefined && !found.durationMs) {
|
|
1146
|
+
throw new Error(`Asset "${found.name}" has no known duration. Supply durationMs or import media that can be probed.`);
|
|
1147
|
+
}
|
|
1148
|
+
const track = trackId
|
|
1149
|
+
? resolveTrack(trackId)
|
|
1150
|
+
: findOrCreateTrack(trackTypeForMediaType(mediaType));
|
|
1151
|
+
const init = {
|
|
1152
|
+
id: nextClipId(),
|
|
1153
|
+
trackId: track.id,
|
|
1154
|
+
name: name ?? found.name,
|
|
1155
|
+
startMs: startMs ?? trackEndMs(track.id),
|
|
1156
|
+
durationMs: durationMs ??
|
|
1157
|
+
found.durationMs ??
|
|
1158
|
+
DEFAULT_MEDIA_CLIP_DURATION_MS,
|
|
1159
|
+
mediaType,
|
|
1160
|
+
sourceType: "imported",
|
|
1161
|
+
status: "generated",
|
|
1162
|
+
currentAssetId: found.id
|
|
1163
|
+
};
|
|
1164
|
+
if (found.thumbnailAssetId) {
|
|
1165
|
+
init.thumbnailAssetId = found.thumbnailAssetId;
|
|
1166
|
+
}
|
|
1167
|
+
const clip = makeClip(init);
|
|
1168
|
+
if (transform)
|
|
1169
|
+
clip.transform = mergeClipTransform(clip.transform, transform);
|
|
1170
|
+
clips.push(clip);
|
|
1171
|
+
selectedClipIds = [clip.id];
|
|
1172
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1173
|
+
}),
|
|
1174
|
+
sharedTool("ui_timeline_add_shape_clip", async ({ shape, shapeStyle, name, transform, trackId, startMs, durationMs, opacity, ...loose }) => {
|
|
1175
|
+
const track = trackId
|
|
1176
|
+
? resolveTrack(trackId)
|
|
1177
|
+
: findOrCreateTrack("overlay");
|
|
1178
|
+
const shapeArg = resolveShapeArg(shape, shapeStyle, loose);
|
|
1179
|
+
const clip = makeClip({
|
|
1180
|
+
id: nextClipId(),
|
|
1181
|
+
trackId: track.id,
|
|
1182
|
+
name: name ?? capitalize(shapeArg.kind),
|
|
1183
|
+
startMs: startMs ?? trackEndMs(track.id),
|
|
1184
|
+
durationMs: durationMs ?? DEFAULT_TEXT_CLIP_DURATION_MS,
|
|
1185
|
+
mediaType: "shape",
|
|
1186
|
+
sourceType: "imported",
|
|
1187
|
+
status: "generated",
|
|
1188
|
+
shapeStyle: shapeStyleWithDefaults(shapeArg)
|
|
1189
|
+
});
|
|
1190
|
+
if (opacity !== undefined)
|
|
1191
|
+
clip.opacity = opacity;
|
|
1192
|
+
if (transform)
|
|
1193
|
+
clip.transform = mergeClipTransform(clip.transform, transform);
|
|
1194
|
+
clips.push(clip);
|
|
1195
|
+
selectedClipIds = [clip.id];
|
|
1196
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1197
|
+
}),
|
|
1198
|
+
sharedTool("ui_timeline_add_model3d_clip", async ({ assetId, trackId, startMs, durationMs, style, transform }) => {
|
|
1199
|
+
// 3D is picture (D1), so it lands where a title lands.
|
|
1200
|
+
const track = trackId
|
|
1201
|
+
? resolveTrack(trackId)
|
|
1202
|
+
: findOrCreateTrack("overlay");
|
|
1203
|
+
const clip = makeClip({
|
|
1204
|
+
id: nextClipId(),
|
|
1205
|
+
trackId: track.id,
|
|
1206
|
+
name: DEFAULT_MODEL3D_CLIP_NAME,
|
|
1207
|
+
startMs: startMs ?? trackEndMs(track.id),
|
|
1208
|
+
durationMs: durationMs ??
|
|
1209
|
+
DEFAULT_MODEL3D_CLIP_DURATION_MS,
|
|
1210
|
+
mediaType: "model3d",
|
|
1211
|
+
sourceType: "imported",
|
|
1212
|
+
status: "generated",
|
|
1213
|
+
currentAssetId: assetId.trim(),
|
|
1214
|
+
model3dStyle: model3dStyleWithPatch(undefined, style)
|
|
1215
|
+
});
|
|
1216
|
+
if (transform)
|
|
1217
|
+
clip.transform = mergeClipTransform(clip.transform, transform);
|
|
1218
|
+
clips.push(clip);
|
|
1219
|
+
selectedClipIds = [clip.id];
|
|
1220
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1221
|
+
}),
|
|
1222
|
+
sharedTool("ui_timeline_set_model3d_style", async ({ target, patch }) => {
|
|
1223
|
+
const clip = resolveClip(target);
|
|
1224
|
+
if (clip.mediaType !== "model3d") {
|
|
1225
|
+
throw new Error(`Clip "${clip.name}" is a ${clip.mediaType} clip, not a 3D clip — ` +
|
|
1226
|
+
"model3dStyle names a camera, an animation and lighting for a " +
|
|
1227
|
+
"glTF, and nothing else reads it.");
|
|
1228
|
+
}
|
|
1229
|
+
clip.model3dStyle = model3dStyleWithPatch(clip.model3dStyle, patch);
|
|
1230
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1231
|
+
}),
|
|
1232
|
+
sharedTool("ui_timeline_bake_model3d_clip", async ({ target }) => {
|
|
1233
|
+
const clip = resolveClip(target);
|
|
1234
|
+
if (clip.mediaType !== "model3d") {
|
|
1235
|
+
throw new Error(`Clip "${clip.name}" is a ${clip.mediaType} clip, not a 3D clip — ` +
|
|
1236
|
+
"only a 3D clip has a camera, lighting and a glTF to render.");
|
|
1237
|
+
}
|
|
1238
|
+
const style = clip.model3dStyle;
|
|
1239
|
+
if (!style) {
|
|
1240
|
+
throw new Error(`Clip "${clip.name}" has no model3dStyle, so nothing names the ` +
|
|
1241
|
+
"camera, lighting or animation a bake would render.");
|
|
1242
|
+
}
|
|
1243
|
+
if (!clip.currentAssetId) {
|
|
1244
|
+
throw new Error(`Clip "${clip.name}" has no glTF asset to bake — a 3D clip draws ` +
|
|
1245
|
+
"its asset the way an image clip draws its image.");
|
|
1246
|
+
}
|
|
1247
|
+
const sequence = { fps, width, height };
|
|
1248
|
+
const dependencyHash = computeModel3DBakeHash(clip, sequence);
|
|
1249
|
+
if (!bakeModel3DClip) {
|
|
1250
|
+
return {
|
|
1251
|
+
ok: true,
|
|
1252
|
+
clip: serializeClip(clip),
|
|
1253
|
+
bakeStarted: false,
|
|
1254
|
+
note: "This surface has no Blender renderer, so nothing was baked."
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
const baked = await bakeModel3DClip({ clip, sequence, dependencyHash });
|
|
1258
|
+
clip.model3dStyle = {
|
|
1259
|
+
...style,
|
|
1260
|
+
bake: { assetId: baked.assetId, dependencyHash }
|
|
1261
|
+
};
|
|
1262
|
+
const now = new Date().toISOString();
|
|
1263
|
+
clip.versions = [
|
|
1264
|
+
...(clip.versions ?? []),
|
|
1265
|
+
makeClipVersion({
|
|
1266
|
+
id: nextVersionId(),
|
|
1267
|
+
createdAt: now,
|
|
1268
|
+
workflowUpdatedAt: now,
|
|
1269
|
+
jobId: baked.jobId ?? "",
|
|
1270
|
+
assetId: baked.assetId,
|
|
1271
|
+
dependencyHash
|
|
1272
|
+
})
|
|
1273
|
+
];
|
|
1274
|
+
return { ok: true, clip: serializeClip(clip), bakeStarted: true };
|
|
1275
|
+
}),
|
|
1276
|
+
sharedTool("ui_timeline_generate_clip", async ({ kind, prompt, trackId, startMs, durationMs, provider, model, voice, width: clipWidth, height: clipHeight, aspectRatio, resolution, autoGenerate }) => {
|
|
1277
|
+
const mediaType = kind === "text-to-video"
|
|
1278
|
+
? "video"
|
|
1279
|
+
: kind === "text-to-image"
|
|
1280
|
+
? "image"
|
|
1281
|
+
: "audio";
|
|
1282
|
+
const track = trackId
|
|
1283
|
+
? resolveTrack(trackId)
|
|
1284
|
+
: kind === "text-to-audio"
|
|
1285
|
+
? findOrCreateTrack("audio")
|
|
1286
|
+
: kind === "text-to-video"
|
|
1287
|
+
? findOrCreateTrack("video")
|
|
1288
|
+
: (tracks.find((t) => t.type === "video" || t.type === "overlay") ??
|
|
1289
|
+
findOrCreateTrack("video"));
|
|
1290
|
+
const generationStarted = autoGenerate !== false;
|
|
1291
|
+
const clip = makeClip({
|
|
1292
|
+
id: nextClipId(),
|
|
1293
|
+
trackId: track.id,
|
|
1294
|
+
name: prompt,
|
|
1295
|
+
startMs: startMs ?? trackEndMs(track.id),
|
|
1296
|
+
durationMs: durationMs ??
|
|
1297
|
+
(kind === "text-to-audio" ? 3000 : 5000),
|
|
1298
|
+
mediaType,
|
|
1299
|
+
sourceType: "generated",
|
|
1300
|
+
bindingKind: kind,
|
|
1301
|
+
status: generationStarted ? "generating" : "draft",
|
|
1302
|
+
prompt: prompt,
|
|
1303
|
+
provider: provider,
|
|
1304
|
+
model: model,
|
|
1305
|
+
voice: voice,
|
|
1306
|
+
width: clipWidth,
|
|
1307
|
+
height: clipHeight,
|
|
1308
|
+
aspectRatio: aspectRatio,
|
|
1309
|
+
resolution: resolution
|
|
1310
|
+
});
|
|
1311
|
+
clips.push(clip);
|
|
1312
|
+
selectedClipIds = [clip.id];
|
|
1313
|
+
const result = { ok: true, clip: serializeClip(clip), generationStarted };
|
|
1314
|
+
if (!generationStarted) {
|
|
1315
|
+
result.note = "Generation not started (autoGenerate=false).";
|
|
1316
|
+
}
|
|
1317
|
+
return result;
|
|
1318
|
+
}),
|
|
1319
|
+
sharedTool("ui_timeline_generatively_edit_clip", async ({ clip_id, instruction, provider, model }) => {
|
|
1320
|
+
// The P0 request captures a durable destination. Unlike general
|
|
1321
|
+
// timeline actions, this intentionally refuses names and selection.
|
|
1322
|
+
const clip = clips.find((candidate) => candidate.id === clip_id);
|
|
1323
|
+
if (!clip) {
|
|
1324
|
+
throw new Error(`No clip with id "${clip_id}" exists on this timeline. Call ui_timeline_get_state and pass the clip id.`);
|
|
1325
|
+
}
|
|
1326
|
+
const baseline = ensureBaselineTake(clip);
|
|
1327
|
+
if (baseline !== clip) {
|
|
1328
|
+
clips = clips.map((candidate) => candidate.id === clip.id ? baseline : candidate);
|
|
1329
|
+
}
|
|
1330
|
+
const source = captureMediaEditSourceContext(sequenceId, baseline);
|
|
1331
|
+
if (!source.ok)
|
|
1332
|
+
throw new Error(source.error);
|
|
1333
|
+
// Eval hosts have no selected model catalog. These deterministic
|
|
1334
|
+
// placeholders only stand in for a real provider's returned asset;
|
|
1335
|
+
// browser and server execution keep their normal model validation.
|
|
1336
|
+
const resolvedProvider = provider ?? "headless";
|
|
1337
|
+
const resolvedModel = model ?? "headless-video-to-video";
|
|
1338
|
+
const request = createMediaEditRequest({
|
|
1339
|
+
sourceContext: source.context,
|
|
1340
|
+
instruction: instruction,
|
|
1341
|
+
provider: resolvedProvider,
|
|
1342
|
+
model: resolvedModel
|
|
1343
|
+
});
|
|
1344
|
+
const generationId = nextVersionId();
|
|
1345
|
+
const now = new Date().toISOString();
|
|
1346
|
+
const next = composeGenerativeTakePatch(baseline, "restyle", {
|
|
1347
|
+
assetId: `generative://${baseline.id}/${generationId}`,
|
|
1348
|
+
jobId: generationId,
|
|
1349
|
+
createdAt: now,
|
|
1350
|
+
workflowUpdatedAt: now,
|
|
1351
|
+
provider: request.provider,
|
|
1352
|
+
model: request.model,
|
|
1353
|
+
prompt: request.instruction,
|
|
1354
|
+
durationMs: request.sourceContext.timelineDurationMs,
|
|
1355
|
+
mediaEdit: request
|
|
1356
|
+
});
|
|
1357
|
+
clips = clips.map((candidate) => candidate.id === baseline.id ? next : candidate);
|
|
1358
|
+
const take = next.versions?.find((candidate) => candidate.id === generationId);
|
|
1359
|
+
if (!take) {
|
|
1360
|
+
throw new Error("The AI edit candidate could not be recorded.");
|
|
1361
|
+
}
|
|
1362
|
+
return {
|
|
1363
|
+
ok: true,
|
|
1364
|
+
requestId: generationId,
|
|
1365
|
+
generationId,
|
|
1366
|
+
activeTakeId: activeTakeIdOf(next) ?? null,
|
|
1367
|
+
candidate: {
|
|
1368
|
+
id: take.id,
|
|
1369
|
+
status: take.status,
|
|
1370
|
+
source: take.source
|
|
1371
|
+
},
|
|
1372
|
+
clip: serializeClip(next)
|
|
1373
|
+
};
|
|
1374
|
+
}),
|
|
1375
|
+
sharedTool("ui_timeline_apply_take", async ({ clip_id, take_id }) => {
|
|
1376
|
+
const clip = clips.find((candidate) => candidate.id === clip_id);
|
|
1377
|
+
if (!clip) {
|
|
1378
|
+
throw new Error(`No clip with id "${clip_id}" exists on this timeline. Call ui_timeline_get_state and pass the clip id.`);
|
|
1379
|
+
}
|
|
1380
|
+
const take = (clip.versions ?? []).find((candidate) => candidate.id === take_id);
|
|
1381
|
+
if (!take?.mediaEdit) {
|
|
1382
|
+
throw new Error(`Take "${take_id}" is not an AI edit candidate for "${clip.name}". Apply only the candidate returned by ui_timeline_generatively_edit_clip.`);
|
|
1383
|
+
}
|
|
1384
|
+
const result = applyTakeToClip(clip, take_id);
|
|
1385
|
+
if (result.error)
|
|
1386
|
+
throw new Error(result.error);
|
|
1387
|
+
clips = clips.map((candidate) => candidate.id === clip.id ? result.clip : candidate);
|
|
1388
|
+
return { ok: true, clip: serializeClip(result.clip) };
|
|
1389
|
+
}),
|
|
1390
|
+
sharedTool("ui_timeline_split_clip", async ({ target, atMs }) => {
|
|
1391
|
+
const clip = resolveClip(target);
|
|
1392
|
+
const at = atMs ?? playheadMs;
|
|
1393
|
+
const [left, right] = splitClip(clip, at);
|
|
1394
|
+
left.id = nextClipId();
|
|
1395
|
+
right.id = nextClipId();
|
|
1396
|
+
const idx = clips.findIndex((c) => c.id === clip.id);
|
|
1397
|
+
clips.splice(idx, 1, left, right);
|
|
1398
|
+
selectedClipIds = selectedClipIds.filter((id) => id !== clip.id);
|
|
1399
|
+
mediaTracks = resliceTracksForSplitClip(mediaTracks, clip.id, left, right, nextTrackId);
|
|
1400
|
+
return { ok: true, clips: [serializeClip(left), serializeClip(right)] };
|
|
1401
|
+
}),
|
|
1402
|
+
sharedTool("ui_timeline_trim_clip", async ({ target, durationMs, inPointMs, outPointMs }) => {
|
|
1403
|
+
const trimmed = applyTrim(resolveClip(target), {
|
|
1404
|
+
durationMs: durationMs,
|
|
1405
|
+
inPointMs: inPointMs,
|
|
1406
|
+
outPointMs: outPointMs
|
|
1407
|
+
});
|
|
1408
|
+
mediaTracks = resliceTracksForTrimmedClip(mediaTracks, trimmed);
|
|
1409
|
+
return { ok: true, clip: serializeClip(trimmed) };
|
|
1410
|
+
}),
|
|
1411
|
+
sharedTool("ui_timeline_move_clip", async ({ target, startMs, trackId }) => {
|
|
1412
|
+
const moved = applyMove(resolveClip(target), {
|
|
1413
|
+
startMs: startMs,
|
|
1414
|
+
trackId: trackId
|
|
1415
|
+
});
|
|
1416
|
+
return { ok: true, clip: serializeClip(moved) };
|
|
1417
|
+
}),
|
|
1418
|
+
sharedTool("ui_timeline_delete_clip", async ({ target }) => {
|
|
1419
|
+
const clip = resolveClip(target);
|
|
1420
|
+
// Deleting a group deletes the parent, not the picture: its children
|
|
1421
|
+
// stay where they are and stop inheriting (D4). Leaving them with a
|
|
1422
|
+
// `parentId` nothing answers is what the validator calls a dangling
|
|
1423
|
+
// parent.
|
|
1424
|
+
const remaining = isGroupClip(clip) ? ungroup(clips, clip.id) : clips;
|
|
1425
|
+
clips = remaining.filter((c) => c.id !== clip.id);
|
|
1426
|
+
selectedClipIds = selectedClipIds.filter((id) => id !== clip.id);
|
|
1427
|
+
return { ok: true, deleted: serializeClip(clip) };
|
|
1428
|
+
}),
|
|
1429
|
+
sharedTool("ui_timeline_duplicate_clip", async ({ target, gapMs }) => {
|
|
1430
|
+
const src = resolveClip(target);
|
|
1431
|
+
const copy = {
|
|
1432
|
+
...src,
|
|
1433
|
+
id: nextClipId(),
|
|
1434
|
+
startMs: src.startMs + src.durationMs + (gapMs ?? 0),
|
|
1435
|
+
versions: [],
|
|
1436
|
+
animations: src.animations?.map((a) => ({ ...a, id: nextAnimId() }))
|
|
1437
|
+
};
|
|
1438
|
+
clips.push(copy);
|
|
1439
|
+
selectedClipIds = [copy.id];
|
|
1440
|
+
return { ok: true, clip: serializeClip(copy) };
|
|
1441
|
+
}),
|
|
1442
|
+
sharedTool("ui_timeline_set_clip_params", async ({ target, ...wrapped }) => {
|
|
1443
|
+
const patch = unwrapClipParams(wrapped);
|
|
1444
|
+
let clip = resolveClip(target);
|
|
1445
|
+
rejectUnknownClipParams(patch);
|
|
1446
|
+
// Timing belongs to move_clip and trim_clip, but a caller sending it
|
|
1447
|
+
// here means one edit either way — so apply it through the same code
|
|
1448
|
+
// rather than dropping it or making them call twice.
|
|
1449
|
+
clip = applyTrim(clip, {
|
|
1450
|
+
durationMs: patch.durationMs,
|
|
1451
|
+
inPointMs: patch.inPointMs,
|
|
1452
|
+
outPointMs: patch.outPointMs
|
|
1453
|
+
});
|
|
1454
|
+
clip = applyMove(clip, {
|
|
1455
|
+
startMs: patch.startMs,
|
|
1456
|
+
trackId: patch.trackId
|
|
1457
|
+
});
|
|
1458
|
+
// The style is a patch over what the clip already carries: changing one
|
|
1459
|
+
// field used to mean re-sending the whole bag, and re-sending it is how
|
|
1460
|
+
// the four fields the caller did not mean to touch get overwritten.
|
|
1461
|
+
if (patch.textStyle !== undefined) {
|
|
1462
|
+
patch.textStyle = mergeTextStyle(clip, patch.textStyle);
|
|
1463
|
+
}
|
|
1464
|
+
if (patch.fontSizePx !== undefined) {
|
|
1465
|
+
// Shorthand for the one text field callers reach for by name.
|
|
1466
|
+
const size = patch.fontSizePx;
|
|
1467
|
+
const style = (patch.textStyle ?? clip.textStyle);
|
|
1468
|
+
if (!style) {
|
|
1469
|
+
throw new Error(`Clip "${clip.name}" carries no text to size; fontSizePx applies to a text clip's textStyle.`);
|
|
1470
|
+
}
|
|
1471
|
+
patch.textStyle = { ...style, fontSizePx: size };
|
|
1472
|
+
}
|
|
1473
|
+
if (patch.name !== undefined)
|
|
1474
|
+
clip.name = patch.name;
|
|
1475
|
+
if (patch.transform !== undefined)
|
|
1476
|
+
clip.transform = mergeClipTransform(clip.transform, patch.transform);
|
|
1477
|
+
if (patch.opacity !== undefined)
|
|
1478
|
+
clip.opacity = patch.opacity;
|
|
1479
|
+
if (patch.speedMultiplier !== undefined)
|
|
1480
|
+
clip.speedMultiplier = patch.speedMultiplier;
|
|
1481
|
+
if (patch.volumeDb !== undefined)
|
|
1482
|
+
clip.volumeDb = patch.volumeDb;
|
|
1483
|
+
if (patch.fadeInMs !== undefined)
|
|
1484
|
+
clip.fadeInMs = patch.fadeInMs;
|
|
1485
|
+
if (patch.fadeOutMs !== undefined)
|
|
1486
|
+
clip.fadeOutMs = patch.fadeOutMs;
|
|
1487
|
+
if (patch.fadeInShape !== undefined)
|
|
1488
|
+
clip.fadeInShape = patch.fadeInShape;
|
|
1489
|
+
if (patch.fadeOutShape !== undefined)
|
|
1490
|
+
clip.fadeOutShape = patch.fadeOutShape;
|
|
1491
|
+
if (patch.blendMode !== undefined)
|
|
1492
|
+
clip.blendMode = patch.blendMode;
|
|
1493
|
+
if (patch.borderRadius !== undefined)
|
|
1494
|
+
clip.borderRadius = patch.borderRadius;
|
|
1495
|
+
if (patch.hidden !== undefined)
|
|
1496
|
+
clip.hidden = patch.hidden;
|
|
1497
|
+
if (patch.muted !== undefined)
|
|
1498
|
+
clip.muted = patch.muted;
|
|
1499
|
+
if (patch.locked !== undefined)
|
|
1500
|
+
clip.locked = patch.locked;
|
|
1501
|
+
if (patch.textStyle !== undefined)
|
|
1502
|
+
clip.textStyle = patch.textStyle;
|
|
1503
|
+
if (patch.shapeStyle !== undefined)
|
|
1504
|
+
clip.shapeStyle = patch.shapeStyle;
|
|
1505
|
+
if (patch.captionStyle !== undefined) {
|
|
1506
|
+
// The style rides on the clip's caption, so a clip with no words to
|
|
1507
|
+
// draw has nowhere to put it. Say so rather than storing a look
|
|
1508
|
+
// nothing renders.
|
|
1509
|
+
if (!clip.caption) {
|
|
1510
|
+
throw new Error(`Clip "${clip.name}" carries no caption to style.`);
|
|
1511
|
+
}
|
|
1512
|
+
clip.caption = {
|
|
1513
|
+
...clip.caption,
|
|
1514
|
+
style: patch.captionStyle
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1518
|
+
}),
|
|
1519
|
+
sharedTool("ui_timeline_add_group", async ({ name, startMs, durationMs, trackId, children, transform }) => {
|
|
1520
|
+
// Resolve every child before anything is written: a half-applied group
|
|
1521
|
+
// leaves the caller with an empty group and no idea which of its clips
|
|
1522
|
+
// moved.
|
|
1523
|
+
const targets = (children ?? []).map((ref) => resolveClip(ref));
|
|
1524
|
+
const track = trackId
|
|
1525
|
+
? resolveTrack(trackId)
|
|
1526
|
+
: findOrCreateTrack("overlay");
|
|
1527
|
+
const group = makeClip({
|
|
1528
|
+
id: nextClipId(),
|
|
1529
|
+
trackId: track.id,
|
|
1530
|
+
name: name,
|
|
1531
|
+
startMs: startMs,
|
|
1532
|
+
durationMs: durationMs,
|
|
1533
|
+
mediaType: "group",
|
|
1534
|
+
sourceType: "imported",
|
|
1535
|
+
status: "generated"
|
|
1536
|
+
});
|
|
1537
|
+
if (transform)
|
|
1538
|
+
group.transform = mergeClipTransform(group.transform, transform);
|
|
1539
|
+
clips.push(group);
|
|
1540
|
+
for (const child of targets) {
|
|
1541
|
+
child.parentId = group.id;
|
|
1542
|
+
}
|
|
1543
|
+
selectedClipIds = [group.id];
|
|
1544
|
+
return {
|
|
1545
|
+
ok: true,
|
|
1546
|
+
clip: serializeClip(group),
|
|
1547
|
+
children: targets.map((c) => c.id)
|
|
1548
|
+
};
|
|
1549
|
+
}),
|
|
1550
|
+
sharedTool("ui_timeline_set_parent", async ({ target, parentId }) => {
|
|
1551
|
+
const clip = resolveClip(target);
|
|
1552
|
+
if (parentId === null) {
|
|
1553
|
+
delete clip.parentId;
|
|
1554
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1555
|
+
}
|
|
1556
|
+
const parent = resolveClip(parentId);
|
|
1557
|
+
if (parent.mediaType !== "group") {
|
|
1558
|
+
throw new Error(`"${parent.name}" is a ${parent.mediaType} clip, not a group — parent to a clip created with add_group. ${validUnits(clips.filter((c) => c.mediaType === "group"), "group")}`);
|
|
1559
|
+
}
|
|
1560
|
+
// A cycle renders unparented and warns, so refusing it here is the
|
|
1561
|
+
// only place it can still be fixed.
|
|
1562
|
+
let cursor = parent;
|
|
1563
|
+
while (cursor) {
|
|
1564
|
+
if (cursor.id === clip.id) {
|
|
1565
|
+
throw new Error(`"${parent.name}" is inside "${clip.name}" — parenting them would make a cycle.`);
|
|
1566
|
+
}
|
|
1567
|
+
const next = cursor.parentId;
|
|
1568
|
+
cursor = next ? clips.find((c) => c.id === next) : undefined;
|
|
1569
|
+
}
|
|
1570
|
+
clip.parentId = parent.id;
|
|
1571
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1572
|
+
}),
|
|
1573
|
+
sharedTool("ui_timeline_set_transition", async ({ target, transition }) => {
|
|
1574
|
+
const clip = resolveClip(target);
|
|
1575
|
+
if (transition === null) {
|
|
1576
|
+
delete clip.transitionIn;
|
|
1577
|
+
}
|
|
1578
|
+
else {
|
|
1579
|
+
clip.transitionIn = buildTransition(transition);
|
|
1580
|
+
}
|
|
1581
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1582
|
+
}),
|
|
1583
|
+
tool("ui_timeline_generate_transition_at_cut", "Generate an inactive cut-level transition candidate for two explicitly adjacent clips. This does not change either clip or create a clip take. Preview the returned candidate before applying it.", generatedTransitionCandidateParams, async (args) => {
|
|
1584
|
+
const candidate = await createTransitionCandidate(args);
|
|
1585
|
+
return {
|
|
1586
|
+
ok: true,
|
|
1587
|
+
requestId: candidate.generationId,
|
|
1588
|
+
generationId: candidate.generationId,
|
|
1589
|
+
candidate,
|
|
1590
|
+
source: candidate.source
|
|
1591
|
+
};
|
|
1592
|
+
}),
|
|
1593
|
+
tool("ui_timeline_preview_transition_candidate", "Audition one generated cut-level transition candidate without changing the timeline document. Refuses candidates whose selected clips, source takes, or cut are stale.", transitionCandidateParams, async ({ candidate_id }) => {
|
|
1594
|
+
const candidate = transitionCandidates.find((entry) => entry.id === candidate_id);
|
|
1595
|
+
if (!candidate) {
|
|
1596
|
+
throw new Error(`No generated transition candidate named "${candidate_id}" exists.`);
|
|
1597
|
+
}
|
|
1598
|
+
assertFreshTransitionCandidate(candidate);
|
|
1599
|
+
auditionedTransitionCandidateId = candidate.id;
|
|
1600
|
+
return { ok: true, previewOnly: true, candidate };
|
|
1601
|
+
}),
|
|
1602
|
+
tool("ui_timeline_apply_transition_at_cut", `${APPLY_TRANSITION_AT_CUT_DESCRIPTION} With clip ids and timing, this creates an inactive cut-level candidate. Apply a returned candidate_id explicitly to commit it as one undoable operation.`, transitionCandidateLifecycleParams, async (args) => {
|
|
1603
|
+
if (args.candidate_id !== undefined) {
|
|
1604
|
+
const applied = applyTransitionCandidate(args.candidate_id);
|
|
1605
|
+
return { ok: true, applied: true, ...applied };
|
|
1606
|
+
}
|
|
1607
|
+
const candidate = await createTransitionCandidate(args);
|
|
1608
|
+
return {
|
|
1609
|
+
ok: true,
|
|
1610
|
+
applied: false,
|
|
1611
|
+
requestId: candidate.generationId,
|
|
1612
|
+
generationId: candidate.generationId,
|
|
1613
|
+
candidate,
|
|
1614
|
+
source: candidate.source,
|
|
1615
|
+
next: "Preview with ui_timeline_preview_transition_candidate, then call this tool with candidate_id to apply."
|
|
1616
|
+
};
|
|
1617
|
+
}),
|
|
1618
|
+
sharedTool("ui_timeline_set_mask", async ({ target, mask }) => {
|
|
1619
|
+
const clip = resolveClip(target);
|
|
1620
|
+
if (mask === null) {
|
|
1621
|
+
delete clip.mask;
|
|
1622
|
+
}
|
|
1623
|
+
else {
|
|
1624
|
+
clip.mask = buildMask(mask, parseSvgPath);
|
|
1625
|
+
}
|
|
1626
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1627
|
+
}),
|
|
1628
|
+
sharedTool("ui_timeline_set_matte", async ({ target, matte }) => {
|
|
1629
|
+
const clip = resolveClip(target);
|
|
1630
|
+
if (matte === null) {
|
|
1631
|
+
delete clip.matte;
|
|
1632
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1633
|
+
}
|
|
1634
|
+
const input = matte;
|
|
1635
|
+
const source = resolveClip(input.source);
|
|
1636
|
+
if (source.id === clip.id) {
|
|
1637
|
+
throw new Error(`"${clip.name}" cannot be its own matte source — name another clip.`);
|
|
1638
|
+
}
|
|
1639
|
+
const matteOut = {
|
|
1640
|
+
sourceClipId: source.id,
|
|
1641
|
+
mode: input.mode
|
|
1642
|
+
};
|
|
1643
|
+
if (input.invert !== undefined)
|
|
1644
|
+
matteOut.invert = input.invert;
|
|
1645
|
+
clip.matte = matteOut;
|
|
1646
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1647
|
+
}),
|
|
1648
|
+
// Headless-only: the inspector's own controls write these knobs into the
|
|
1649
|
+
// store. Both this and the ops module's `set_generated_matte` reach
|
|
1650
|
+
// `selectGeneratedMatteVersion`, so the version list cannot fork.
|
|
1651
|
+
tool("ui_timeline_set_generated_matte", SET_GENERATED_MATTE_DESCRIPTION, setGeneratedMatteParams, async (args) => {
|
|
1652
|
+
const input = args;
|
|
1653
|
+
const clip = resolveClip(input.target);
|
|
1654
|
+
if (input.clear === true) {
|
|
1655
|
+
delete clip.generatedMatte;
|
|
1656
|
+
return {
|
|
1657
|
+
ok: true,
|
|
1658
|
+
clip: serializeClip(clip),
|
|
1659
|
+
generatedMatte: null
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
if (!clip.generatedMatte) {
|
|
1663
|
+
throw new Error(`"${clip.name}" carries no generated matte. Run isolate_subject ` +
|
|
1664
|
+
"on it first; this op only adjusts one that exists.");
|
|
1665
|
+
}
|
|
1666
|
+
if (input.selectVersionAssetId !== undefined) {
|
|
1667
|
+
clip.generatedMatte = selectGeneratedMatteVersion(clip, input.selectVersionAssetId).generatedMatte;
|
|
1668
|
+
}
|
|
1669
|
+
const matte = clip.generatedMatte;
|
|
1670
|
+
if (input.invert !== undefined)
|
|
1671
|
+
matte.invert = input.invert;
|
|
1672
|
+
if (input.strength !== undefined) {
|
|
1673
|
+
matte.strength = Math.min(1, Math.max(0, input.strength));
|
|
1674
|
+
}
|
|
1675
|
+
if (input.featherPx !== undefined) {
|
|
1676
|
+
matte.featherPx = Math.max(0, input.featherPx);
|
|
1677
|
+
}
|
|
1678
|
+
return { ok: true, clip: serializeClip(clip), generatedMatte: matte };
|
|
1679
|
+
}),
|
|
1680
|
+
sharedTool("ui_timeline_set_time_remap", async ({ target, timeRemap }) => {
|
|
1681
|
+
const clip = resolveClip(target);
|
|
1682
|
+
if (timeRemap === null) {
|
|
1683
|
+
delete clip.timeRemap;
|
|
1684
|
+
}
|
|
1685
|
+
else {
|
|
1686
|
+
clip.timeRemap = buildTimeRemap(timeRemap);
|
|
1687
|
+
}
|
|
1688
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1689
|
+
}),
|
|
1690
|
+
sharedTool("ui_timeline_set_effects", async ({ target, effects }) => {
|
|
1691
|
+
const clip = resolveClip(target);
|
|
1692
|
+
const list = effects.map(buildEffect);
|
|
1693
|
+
if (list.length === 0) {
|
|
1694
|
+
delete clip.effects;
|
|
1695
|
+
}
|
|
1696
|
+
else {
|
|
1697
|
+
clip.effects = list;
|
|
1698
|
+
}
|
|
1699
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1700
|
+
}),
|
|
1701
|
+
sharedTool("ui_timeline_set_clip_binding", async ({ target, ...patch }) => {
|
|
1702
|
+
const clip = resolveClip(target);
|
|
1703
|
+
if (clip.sourceType !== "generated") {
|
|
1704
|
+
throw new Error(`"${clip.name}" is not a generated clip — ui_timeline_set_clip_binding only applies to clips created with ui_timeline_generate_clip.`);
|
|
1705
|
+
}
|
|
1706
|
+
if (patch.prompt !== undefined)
|
|
1707
|
+
clip.prompt = patch.prompt;
|
|
1708
|
+
if (patch.negativePrompt !== undefined)
|
|
1709
|
+
clip.negativePrompt = patch.negativePrompt;
|
|
1710
|
+
if (patch.provider !== undefined)
|
|
1711
|
+
clip.provider = patch.provider;
|
|
1712
|
+
if (patch.model !== undefined)
|
|
1713
|
+
clip.model = patch.model;
|
|
1714
|
+
if (patch.voice !== undefined)
|
|
1715
|
+
clip.voice = patch.voice;
|
|
1716
|
+
if (patch.width !== undefined)
|
|
1717
|
+
clip.width = patch.width;
|
|
1718
|
+
if (patch.height !== undefined)
|
|
1719
|
+
clip.height = patch.height;
|
|
1720
|
+
if (patch.aspectRatio !== undefined)
|
|
1721
|
+
clip.aspectRatio = patch.aspectRatio;
|
|
1722
|
+
if (patch.resolution !== undefined)
|
|
1723
|
+
clip.resolution = patch.resolution;
|
|
1724
|
+
if (patch.strength !== undefined)
|
|
1725
|
+
clip.strength = patch.strength;
|
|
1726
|
+
if (patch.numInferenceSteps !== undefined)
|
|
1727
|
+
clip.numInferenceSteps = patch.numInferenceSteps;
|
|
1728
|
+
if (patch.regenerate)
|
|
1729
|
+
clip.status = "queued";
|
|
1730
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1731
|
+
}),
|
|
1732
|
+
sharedTool("ui_timeline_animate_clip", async ({ target, mode, animations }) => {
|
|
1733
|
+
const clip = resolveClip(target);
|
|
1734
|
+
const inputs = animations;
|
|
1735
|
+
const built = [];
|
|
1736
|
+
for (const input of inputs) {
|
|
1737
|
+
if (input.preset === CUSTOM_ANIMATION_PRESET_ID) {
|
|
1738
|
+
// `{preset: "custom", custom: {curves}}` reads as naturally as the
|
|
1739
|
+
// flat form, so lift it rather than refusing it.
|
|
1740
|
+
built.push(await buildCustomAnimation(clip, liftCustomAnimation(input)));
|
|
1741
|
+
continue;
|
|
1742
|
+
}
|
|
1743
|
+
const preset = ANIMATION_PRESETS.find((p) => p.id === input.preset);
|
|
1744
|
+
if (!preset) {
|
|
1745
|
+
const ids = ANIMATION_PRESETS.map((p) => p.id).join(", ");
|
|
1746
|
+
throw new Error(`Unknown animation preset "${input.preset}". Valid presets: ${ids}, ${CUSTOM_ANIMATION_PRESET_ID}.`);
|
|
1747
|
+
}
|
|
1748
|
+
if (!preset.roles.includes(input.role)) {
|
|
1749
|
+
throw new Error(`Preset "${input.preset}" does not support role "${input.role}". Valid roles for "${input.preset}": ${preset.roles.join(", ")}.`);
|
|
1750
|
+
}
|
|
1751
|
+
if (input.preset === "typewriter" && clip.mediaType !== "text") {
|
|
1752
|
+
throw new Error('Preset "typewriter" requires a text clip.');
|
|
1753
|
+
}
|
|
1754
|
+
const timing = input.preset === "typewriter"
|
|
1755
|
+
? typewriterTiming(clip.textStyle?.text ?? "", clip.durationMs - (input.delayMs ?? 0), input.durationMs, input.stagger)
|
|
1756
|
+
: null;
|
|
1757
|
+
built.push({
|
|
1758
|
+
id: nextAnimId(),
|
|
1759
|
+
role: input.role,
|
|
1760
|
+
preset: input.preset,
|
|
1761
|
+
durationMs: timing?.durationMs ?? input.durationMs ?? preset.defaultDurationMs,
|
|
1762
|
+
delayMs: input.delayMs,
|
|
1763
|
+
easing: input.easing,
|
|
1764
|
+
params: input.params,
|
|
1765
|
+
stagger: timing?.stagger ?? input.stagger,
|
|
1766
|
+
caret: input.preset === "typewriter" ? input.caret : undefined
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
clip.animations =
|
|
1770
|
+
mode === "add"
|
|
1771
|
+
? [...(clip.animations ?? []), ...built]
|
|
1772
|
+
: built;
|
|
1773
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1774
|
+
}),
|
|
1775
|
+
// Headless-only: the browser writes curves through the inspector, not
|
|
1776
|
+
// through an agent call. Both this and the ops module's `set_baked_animation`
|
|
1777
|
+
// go through `buildBakedAnimation`, so the replace rule cannot fork.
|
|
1778
|
+
tool("ui_timeline_set_baked_animation", SET_BAKED_ANIMATION_DESCRIPTION, setBakedAnimationParams, async ({ target, animation }) => {
|
|
1779
|
+
const clip = resolveClip(target);
|
|
1780
|
+
const outcome = buildBakedAnimation(clip, animation, nextAnimId);
|
|
1781
|
+
clip.animations = outcome.animations;
|
|
1782
|
+
return {
|
|
1783
|
+
ok: true,
|
|
1784
|
+
clip: serializeClip(clip),
|
|
1785
|
+
animationId: outcome.animationId,
|
|
1786
|
+
keyframeCount: outcome.animation.custom?.curves[0]?.keyframes.length ?? 0,
|
|
1787
|
+
replaced: outcome.replaced
|
|
1788
|
+
};
|
|
1789
|
+
}),
|
|
1790
|
+
sharedTool("ui_timeline_stagger_animations", async ({ clip_ids, offset_ms }) => {
|
|
1791
|
+
const selected = clip_ids.map(resolveClip);
|
|
1792
|
+
const ids = selected.map((clip) => clip.id);
|
|
1793
|
+
if (new Set(ids).size !== ids.length) {
|
|
1794
|
+
throw new Error("clip_ids must contain distinct clip IDs.");
|
|
1795
|
+
}
|
|
1796
|
+
for (const clip of selected) {
|
|
1797
|
+
if (!clip.animations?.length)
|
|
1798
|
+
throw new Error(`Clip "${clip.name}" has no animations to stagger.`);
|
|
1799
|
+
}
|
|
1800
|
+
const staggered = staggerClipAnimations(clips, ids, offset_ms);
|
|
1801
|
+
const byId = new Map(staggered.map((clip) => [clip.id, clip]));
|
|
1802
|
+
clips.splice(0, clips.length, ...staggered);
|
|
1803
|
+
return { ok: true, clips: ids.map((id) => serializeClip(byId.get(id))) };
|
|
1804
|
+
}),
|
|
1805
|
+
sharedTool("ui_timeline_clear_animations", async ({ target, role }) => {
|
|
1806
|
+
const clip = resolveClip(target);
|
|
1807
|
+
clip.animations = role
|
|
1808
|
+
? (clip.animations ?? []).filter((a) => a.role !== role)
|
|
1809
|
+
: [];
|
|
1810
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
1811
|
+
}),
|
|
1812
|
+
sharedTool("ui_timeline_list_animation_presets", async () => {
|
|
1813
|
+
const presets = ANIMATION_PRESETS.map((p) => ({
|
|
1814
|
+
id: p.id,
|
|
1815
|
+
roles: p.roles,
|
|
1816
|
+
defaultDurationMs: p.defaultDurationMs,
|
|
1817
|
+
defaultEasing: p.defaultEasing,
|
|
1818
|
+
params: p.params,
|
|
1819
|
+
describe: p.describe
|
|
1820
|
+
}));
|
|
1821
|
+
return {
|
|
1822
|
+
ok: true,
|
|
1823
|
+
presets,
|
|
1824
|
+
custom: CUSTOM_ANIMATION_CONTRACT,
|
|
1825
|
+
properties: CUSTOM_ANIMATION_CONTRACT.properties
|
|
1826
|
+
};
|
|
1827
|
+
}),
|
|
1828
|
+
sharedTool("ui_timeline_select_clip", async ({ target }) => {
|
|
1829
|
+
const t = target;
|
|
1830
|
+
if (!t) {
|
|
1831
|
+
selectedClipIds = [];
|
|
1832
|
+
return { ok: true, selected: null };
|
|
1833
|
+
}
|
|
1834
|
+
const clip = resolveClip(t);
|
|
1835
|
+
selectedClipIds = [clip.id];
|
|
1836
|
+
return { ok: true, selected: serializeClip(clip) };
|
|
1837
|
+
}),
|
|
1838
|
+
sharedTool("ui_timeline_seek", async ({ timeMs }) => {
|
|
1839
|
+
playheadMs = Math.max(0, timeMs);
|
|
1840
|
+
return { ok: true, playheadMs };
|
|
1841
|
+
}),
|
|
1842
|
+
sharedTool("ui_timeline_add_marker", async ({ timeMs, label, color, note }) => {
|
|
1843
|
+
const at = timeMs;
|
|
1844
|
+
if (at < 0) {
|
|
1845
|
+
throw new Error(`A marker cannot sit before zero; got ${at}ms.`);
|
|
1846
|
+
}
|
|
1847
|
+
const marker = {
|
|
1848
|
+
id: nextMarkerId(),
|
|
1849
|
+
timeMs: Math.round(at),
|
|
1850
|
+
label: label ?? ""
|
|
1851
|
+
};
|
|
1852
|
+
if (color !== undefined)
|
|
1853
|
+
marker.color = color;
|
|
1854
|
+
if (note !== undefined)
|
|
1855
|
+
marker.note = note;
|
|
1856
|
+
markers.push(marker);
|
|
1857
|
+
return { ok: true, marker: { ...marker } };
|
|
1858
|
+
}),
|
|
1859
|
+
// ── Guided video flow (PRD § 8.6) ───────────────────────────────────
|
|
1860
|
+
sharedTool("ui_timeline_set_setup", async ({ stage, brief, format, voiceover }) => {
|
|
1861
|
+
setup = {
|
|
1862
|
+
stage: stage ?? setup?.stage ?? "idea",
|
|
1863
|
+
brief: brief ?? setup?.brief ?? "",
|
|
1864
|
+
format: format ?? setup?.format,
|
|
1865
|
+
// Absent leaves the choice unsaid; `false` is a deliberate silence,
|
|
1866
|
+
// so it is written rather than read as "nothing passed".
|
|
1867
|
+
voiceover: voiceover ?? setup?.voiceover,
|
|
1868
|
+
beats: setup?.beats
|
|
1869
|
+
};
|
|
1870
|
+
return { ok: true, setup: structuredClone(setup) };
|
|
1871
|
+
}),
|
|
1872
|
+
sharedTool("ui_timeline_plan_beats", async ({ beats }) => {
|
|
1873
|
+
// There is no Director on this surface — the caller *is* the model, so
|
|
1874
|
+
// it writes the plan rather than asking for one. Refusing without
|
|
1875
|
+
// `beats` says which half of the shared contract this host implements.
|
|
1876
|
+
const written = beats;
|
|
1877
|
+
if (!written || written.length === 0) {
|
|
1878
|
+
throw new Error("This surface has no Director: pass `beats` with the plan you want. Each beat needs a prompt and a durationMs.");
|
|
1879
|
+
}
|
|
1880
|
+
const planned = written.map((beat) => ({
|
|
1881
|
+
id: `beat_${++beatSeq}`,
|
|
1882
|
+
prompt: beat.prompt,
|
|
1883
|
+
duration_ms: Math.max(1, Math.round(beat.durationMs)),
|
|
1884
|
+
transition: beat.transition,
|
|
1885
|
+
voiceover: beat.voiceover,
|
|
1886
|
+
music: beat.music
|
|
1887
|
+
}));
|
|
1888
|
+
setup = {
|
|
1889
|
+
stage: "review",
|
|
1890
|
+
brief: setup?.brief ?? "",
|
|
1891
|
+
format: setup?.format,
|
|
1892
|
+
voiceover: setup?.voiceover,
|
|
1893
|
+
beats: planned
|
|
1894
|
+
};
|
|
1895
|
+
// Named in the answer because it is the contract: the plan is text,
|
|
1896
|
+
// and costs nothing until generate_from_beats runs (D4).
|
|
1897
|
+
return {
|
|
1898
|
+
ok: true,
|
|
1899
|
+
beats: planned.map((beat) => ({ ...beat })),
|
|
1900
|
+
clipsCreated: 0,
|
|
1901
|
+
jobsStarted: 0
|
|
1902
|
+
};
|
|
1903
|
+
}),
|
|
1904
|
+
sharedTool("ui_timeline_update_beat", async ({ beat, prompt, durationMs, transition, voiceover, music }) => {
|
|
1905
|
+
const found = resolveBeat(beat);
|
|
1906
|
+
const next = { ...found };
|
|
1907
|
+
if (prompt !== undefined)
|
|
1908
|
+
next.prompt = prompt;
|
|
1909
|
+
if (durationMs !== undefined) {
|
|
1910
|
+
next.duration_ms = Math.max(1, Math.round(durationMs));
|
|
1911
|
+
}
|
|
1912
|
+
if (transition !== undefined) {
|
|
1913
|
+
next.transition = transition ?? undefined;
|
|
1914
|
+
}
|
|
1915
|
+
if (voiceover !== undefined)
|
|
1916
|
+
next.voiceover = voiceover;
|
|
1917
|
+
if (music !== undefined)
|
|
1918
|
+
next.music = music;
|
|
1919
|
+
setup = {
|
|
1920
|
+
stage: setup?.stage ?? "review",
|
|
1921
|
+
brief: setup?.brief ?? "",
|
|
1922
|
+
format: setup?.format,
|
|
1923
|
+
voiceover: setup?.voiceover,
|
|
1924
|
+
beats: (setup?.beats ?? []).map((candidate) => candidate.id === found.id ? next : candidate)
|
|
1925
|
+
};
|
|
1926
|
+
return { ok: true, beat: { ...next } };
|
|
1927
|
+
}),
|
|
1928
|
+
sharedTool("ui_timeline_remove_beat", async ({ beat }) => {
|
|
1929
|
+
// Resolved before it is dropped, so the answer names what went and an
|
|
1930
|
+
// unresolvable target says so rather than reporting a silent success.
|
|
1931
|
+
const found = resolveBeat(beat);
|
|
1932
|
+
setup = {
|
|
1933
|
+
stage: setup?.stage ?? "review",
|
|
1934
|
+
brief: setup?.brief ?? "",
|
|
1935
|
+
format: setup?.format,
|
|
1936
|
+
voiceover: setup?.voiceover,
|
|
1937
|
+
beats: (setup?.beats ?? []).filter((candidate) => candidate.id !== found.id)
|
|
1938
|
+
};
|
|
1939
|
+
return { ok: true, removed: { ...found } };
|
|
1940
|
+
}),
|
|
1941
|
+
sharedTool("ui_timeline_generate_from_beats", async ({ model, provider, voice, music }) => {
|
|
1942
|
+
const plan = setup?.beats ?? [];
|
|
1943
|
+
if (plan.length === 0) {
|
|
1944
|
+
throw new Error("There is no beat plan to generate from; call ui_timeline_plan_beats first.");
|
|
1945
|
+
}
|
|
1946
|
+
const videoTrack = findOrCreateTrack("video");
|
|
1947
|
+
const wantsMusic = music ??
|
|
1948
|
+
plan.some((beat) => beat.music === true);
|
|
1949
|
+
const voiceTrack = voice !== undefined && plan.some((beat) => (beat.voiceover ?? "") !== "")
|
|
1950
|
+
? findOrCreateTrack("audio")
|
|
1951
|
+
: null;
|
|
1952
|
+
const videoClipIds = [];
|
|
1953
|
+
const voiceoverClipIds = [];
|
|
1954
|
+
const planned = [];
|
|
1955
|
+
let startMs = 0;
|
|
1956
|
+
for (const [index, beat] of plan.entries()) {
|
|
1957
|
+
const durationMs = Math.max(1, Math.round(beat.duration_ms));
|
|
1958
|
+
const clip = makeClip({
|
|
1959
|
+
id: nextClipId(),
|
|
1960
|
+
trackId: videoTrack.id,
|
|
1961
|
+
name: `Beat ${index + 1}`,
|
|
1962
|
+
startMs,
|
|
1963
|
+
durationMs,
|
|
1964
|
+
mediaType: "video",
|
|
1965
|
+
sourceType: "generated",
|
|
1966
|
+
bindingKind: "text-to-video",
|
|
1967
|
+
prompt: beat.prompt,
|
|
1968
|
+
provider: provider,
|
|
1969
|
+
model: model,
|
|
1970
|
+
beatId: beat.id,
|
|
1971
|
+
status: "draft"
|
|
1972
|
+
});
|
|
1973
|
+
if (beat.transition) {
|
|
1974
|
+
clip.transitionIn = buildTransition({
|
|
1975
|
+
type: beat.transition,
|
|
1976
|
+
durationMs: 500
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
clips.push(clip);
|
|
1980
|
+
videoClipIds.push(clip.id);
|
|
1981
|
+
const line = (beat.voiceover ?? "").trim();
|
|
1982
|
+
if (voiceTrack && line.length > 0) {
|
|
1983
|
+
const voiceClip = makeClip({
|
|
1984
|
+
id: nextClipId(),
|
|
1985
|
+
trackId: voiceTrack.id,
|
|
1986
|
+
name: `Beat ${index + 1} voiceover`,
|
|
1987
|
+
startMs,
|
|
1988
|
+
durationMs,
|
|
1989
|
+
mediaType: "audio",
|
|
1990
|
+
sourceType: "generated",
|
|
1991
|
+
bindingKind: "text-to-audio",
|
|
1992
|
+
prompt: line,
|
|
1993
|
+
voice: voice,
|
|
1994
|
+
beatId: beat.id,
|
|
1995
|
+
status: "draft"
|
|
1996
|
+
});
|
|
1997
|
+
clips.push(voiceClip);
|
|
1998
|
+
voiceoverClipIds.push(voiceClip.id);
|
|
1999
|
+
}
|
|
2000
|
+
planned.push({ ...beat, clip_id: clip.id });
|
|
2001
|
+
startMs += durationMs;
|
|
2002
|
+
}
|
|
2003
|
+
let musicClipId = null;
|
|
2004
|
+
if (wantsMusic) {
|
|
2005
|
+
// One bed under the whole cut, never one per beat: overlapping music
|
|
2006
|
+
// clips would play at once.
|
|
2007
|
+
const musicTrack = findOrCreateTrack("audio");
|
|
2008
|
+
const musicClip = makeClip({
|
|
2009
|
+
id: nextClipId(),
|
|
2010
|
+
trackId: musicTrack.id,
|
|
2011
|
+
name: "Music",
|
|
2012
|
+
startMs: 0,
|
|
2013
|
+
durationMs: startMs,
|
|
2014
|
+
mediaType: "audio",
|
|
2015
|
+
sourceType: "generated",
|
|
2016
|
+
bindingKind: "text-to-audio",
|
|
2017
|
+
prompt: `Instrumental score under: ${setup?.brief ?? ""}`,
|
|
2018
|
+
status: "draft"
|
|
2019
|
+
});
|
|
2020
|
+
clips.push(musicClip);
|
|
2021
|
+
musicClipId = musicClip.id;
|
|
2022
|
+
}
|
|
2023
|
+
setup = {
|
|
2024
|
+
stage: "done",
|
|
2025
|
+
brief: setup?.brief ?? "",
|
|
2026
|
+
format: setup?.format,
|
|
2027
|
+
voiceover: setup?.voiceover,
|
|
2028
|
+
beats: planned
|
|
2029
|
+
};
|
|
2030
|
+
return {
|
|
2031
|
+
ok: true,
|
|
2032
|
+
videoClipIds,
|
|
2033
|
+
voiceoverClipIds,
|
|
2034
|
+
musicClipId,
|
|
2035
|
+
// Nothing renders headlessly: this authors the cut, and the clips are
|
|
2036
|
+
// drafts until a renderer runs them.
|
|
2037
|
+
startedClipIds: [],
|
|
2038
|
+
note: "Clips created as drafts — this surface renders nothing."
|
|
2039
|
+
};
|
|
2040
|
+
}),
|
|
2041
|
+
sharedTool("ui_timeline_delete_marker", async ({ target }) => {
|
|
2042
|
+
const marker = resolveMarker(target);
|
|
2043
|
+
markers = markers.filter((m) => m.id !== marker.id);
|
|
2044
|
+
return { ok: true, deleted: { ...marker } };
|
|
2045
|
+
}),
|
|
2046
|
+
sharedTool("ui_timeline_set_markers_from_beats", async ({ onsets_ms, bpm, offset_ms, count, label }) => {
|
|
2047
|
+
const grid = buildBeatGrid({
|
|
2048
|
+
onsetsMs: onsets_ms,
|
|
2049
|
+
bpm: bpm,
|
|
2050
|
+
offsetMs: offset_ms,
|
|
2051
|
+
count: count
|
|
2052
|
+
});
|
|
2053
|
+
const stem = (label ?? "Beat").trim() || "Beat";
|
|
2054
|
+
const taken = new Set(markers.map((m) => m.timeMs));
|
|
2055
|
+
const added = [];
|
|
2056
|
+
const skipped = [];
|
|
2057
|
+
for (const [index, timeMs] of grid.entries()) {
|
|
2058
|
+
if (taken.has(timeMs)) {
|
|
2059
|
+
skipped.push(timeMs);
|
|
2060
|
+
continue;
|
|
2061
|
+
}
|
|
2062
|
+
const marker = {
|
|
2063
|
+
id: nextMarkerId(),
|
|
2064
|
+
timeMs,
|
|
2065
|
+
label: `${stem} ${index + 1}`
|
|
2066
|
+
};
|
|
2067
|
+
markers.push(marker);
|
|
2068
|
+
taken.add(timeMs);
|
|
2069
|
+
added.push(marker);
|
|
2070
|
+
}
|
|
2071
|
+
return {
|
|
2072
|
+
ok: true,
|
|
2073
|
+
grid: { count: grid.length, firstMs: grid[0], lastMs: grid[grid.length - 1] },
|
|
2074
|
+
added: added.map((m) => ({ ...m })),
|
|
2075
|
+
skipped_times_ms: skipped,
|
|
2076
|
+
markers: markers.length
|
|
2077
|
+
};
|
|
2078
|
+
}),
|
|
2079
|
+
sharedTool("ui_timeline_snap_to_beats", async ({ targets, onsets_ms, bpm, offset_ms, tolerance_ms, mode, action }) => {
|
|
2080
|
+
const named = targets === undefined || targets === "all"
|
|
2081
|
+
? undefined
|
|
2082
|
+
: targets;
|
|
2083
|
+
const { clips: targeted, missing } = resolveSnapTargets(named);
|
|
2084
|
+
const offsetMs = offset_ms ?? 0;
|
|
2085
|
+
// A tempo grid has to reach the last boundary being snapped, so its
|
|
2086
|
+
// length comes from the targets rather than from the caller.
|
|
2087
|
+
const reachMs = targeted.reduce((end, clip) => Math.max(end, clip.startMs + clip.durationMs), 0);
|
|
2088
|
+
const grid = buildBeatGrid({
|
|
2089
|
+
onsetsMs: onsets_ms,
|
|
2090
|
+
bpm: bpm,
|
|
2091
|
+
offsetMs: offset_ms,
|
|
2092
|
+
count: bpm === undefined
|
|
2093
|
+
? undefined
|
|
2094
|
+
: beatCountToCover(bpm, offsetMs, reachMs)
|
|
2095
|
+
});
|
|
2096
|
+
const options = {};
|
|
2097
|
+
if (tolerance_ms !== undefined) {
|
|
2098
|
+
options.toleranceMs = tolerance_ms;
|
|
2099
|
+
}
|
|
2100
|
+
if (mode !== undefined)
|
|
2101
|
+
options.mode = mode;
|
|
2102
|
+
if (action !== undefined)
|
|
2103
|
+
options.action = action;
|
|
2104
|
+
const result = snapClipsToGrid(targeted.map((clip) => ({
|
|
2105
|
+
id: clip.id,
|
|
2106
|
+
startMs: clip.startMs,
|
|
2107
|
+
durationMs: clip.durationMs
|
|
2108
|
+
})), grid, options);
|
|
2109
|
+
const byId = new Map(targeted.map((clip) => [clip.id, clip]));
|
|
2110
|
+
const reported = result.clips.map((entry) => {
|
|
2111
|
+
const clip = byId.get(entry.clipId);
|
|
2112
|
+
if (entry.snapped && clip) {
|
|
2113
|
+
// Through the same ops the caller would use: a group carries its
|
|
2114
|
+
// children (D4) and a trim carries the source points, neither of
|
|
2115
|
+
// which a raw startMs/durationMs write does.
|
|
2116
|
+
try {
|
|
2117
|
+
if (entry.after.durationMs === entry.before.durationMs) {
|
|
2118
|
+
applyMove(clip, { startMs: entry.after.startMs });
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
let trimmed = clip;
|
|
2122
|
+
if (entry.after.startMs !== entry.before.startMs) {
|
|
2123
|
+
trimmed = applyTrimStart(clip, entry.after.startMs);
|
|
2124
|
+
}
|
|
2125
|
+
applyTrim(trimmed, { durationMs: entry.after.durationMs });
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
catch (error) {
|
|
2129
|
+
return {
|
|
2130
|
+
...entry,
|
|
2131
|
+
snapped: false,
|
|
2132
|
+
after: entry.before,
|
|
2133
|
+
delta: { startMs: 0, endMs: 0 },
|
|
2134
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
2135
|
+
clipName: clip.name
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
return { ...entry, clipName: clip?.name ?? null };
|
|
2140
|
+
});
|
|
2141
|
+
// A name nothing matched is a skip like any other: the caller has to
|
|
2142
|
+
// see it in the same list, not infer it from a shorter one.
|
|
2143
|
+
for (const target of missing) {
|
|
2144
|
+
reported.push({
|
|
2145
|
+
clipId: target,
|
|
2146
|
+
clipName: null,
|
|
2147
|
+
snapped: false,
|
|
2148
|
+
before: { startMs: 0, endMs: 0, durationMs: 0 },
|
|
2149
|
+
after: { startMs: 0, endMs: 0, durationMs: 0 },
|
|
2150
|
+
delta: { startMs: 0, endMs: 0 },
|
|
2151
|
+
reason: `no clip matches "${target}"`
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
return {
|
|
2155
|
+
ok: true,
|
|
2156
|
+
grid: {
|
|
2157
|
+
count: grid.length,
|
|
2158
|
+
firstMs: grid[0],
|
|
2159
|
+
lastMs: grid[grid.length - 1]
|
|
2160
|
+
},
|
|
2161
|
+
toleranceMs: result.toleranceMs,
|
|
2162
|
+
mode: result.mode,
|
|
2163
|
+
action: result.action,
|
|
2164
|
+
snapped: result.snapped,
|
|
2165
|
+
skipped: result.skipped + missing.length,
|
|
2166
|
+
clips: reported
|
|
2167
|
+
};
|
|
2168
|
+
}),
|
|
2169
|
+
// ── MIDI ────────────────────────────────────────────────────────────
|
|
2170
|
+
// Notes live on the clip, the instrument on the track, and the tempo on
|
|
2171
|
+
// the document, so each gets its own op.
|
|
2172
|
+
sharedTool("ui_timeline_add_midi_clip", async ({ track, start_ms, duration_ms, name, notes }) => {
|
|
2173
|
+
const midiTrack = resolveMidiTrack(track);
|
|
2174
|
+
const clipName = name ?? "Phrase";
|
|
2175
|
+
const clip = makeClip({
|
|
2176
|
+
id: nextClipId(),
|
|
2177
|
+
trackId: midiTrack.id,
|
|
2178
|
+
name: clipName,
|
|
2179
|
+
startMs: start_ms,
|
|
2180
|
+
durationMs: duration_ms,
|
|
2181
|
+
mediaType: "midi",
|
|
2182
|
+
sourceType: "imported",
|
|
2183
|
+
status: "generated",
|
|
2184
|
+
notes: buildNotes(notes, clipName)
|
|
2185
|
+
});
|
|
2186
|
+
clips.push(clip);
|
|
2187
|
+
selectedClipIds = [clip.id];
|
|
2188
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
2189
|
+
}),
|
|
2190
|
+
sharedTool("ui_timeline_set_notes", async ({ clip: target, notes }) => {
|
|
2191
|
+
const clip = resolveMidiClip(target);
|
|
2192
|
+
// Built before anything is written: a refused list must leave the clip
|
|
2193
|
+
// exactly as it was, not half replaced.
|
|
2194
|
+
clip.notes = buildNotes(notes, clip.name);
|
|
2195
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
2196
|
+
}),
|
|
2197
|
+
sharedTool("ui_timeline_set_tempo", async ({ bpm, offset_ms, beats_per_bar, beat_unit }) => {
|
|
2198
|
+
const previous = resolveTempo({ tempo });
|
|
2199
|
+
const next = {
|
|
2200
|
+
bpm: bpm,
|
|
2201
|
+
offsetMs: offset_ms ?? previous.offsetMs,
|
|
2202
|
+
timeSignature: {
|
|
2203
|
+
beatsPerBar: beats_per_bar ??
|
|
2204
|
+
previous.timeSignature.beatsPerBar,
|
|
2205
|
+
beatUnit: beat_unit ?? previous.timeSignature.beatUnit
|
|
2206
|
+
}
|
|
2207
|
+
};
|
|
2208
|
+
// Milliseconds are the master clock, so the stored ms of every midi
|
|
2209
|
+
// clip move and nothing else on the timeline does.
|
|
2210
|
+
clips = rescaleClipsForTempo(clips, tracks, previous, next);
|
|
2211
|
+
tempo = next;
|
|
2212
|
+
return {
|
|
2213
|
+
ok: true,
|
|
2214
|
+
tempo: next,
|
|
2215
|
+
previousTempo: previous,
|
|
2216
|
+
rescaledClipIds: clips
|
|
2217
|
+
.filter((c) => c.mediaType === "midi")
|
|
2218
|
+
.map((c) => c.id)
|
|
2219
|
+
};
|
|
2220
|
+
}),
|
|
2221
|
+
sharedTool("ui_timeline_set_track_instrument", async ({ track, instrument }) => {
|
|
2222
|
+
const midiTrack = resolveMidiTrack(track);
|
|
2223
|
+
// Resolved before the track is touched, so an unknown preset id leaves
|
|
2224
|
+
// the voice the track already had.
|
|
2225
|
+
midiTrack.instrument = resolveInstrumentArg(instrument);
|
|
2226
|
+
return { ok: true, track: serializeTrack(midiTrack) };
|
|
2227
|
+
}),
|
|
2228
|
+
// The note edits. Each runs the whole list through one pure function from
|
|
2229
|
+
// `@nodetool-ai/timeline` and replaces it — the ids survive, so a
|
|
2230
|
+
// selection and an undo in the editor still point at the same notes.
|
|
2231
|
+
sharedTool("ui_timeline_transpose_clip", async ({ clip: target, semitones }) => {
|
|
2232
|
+
const clip = resolveMidiClip(target);
|
|
2233
|
+
clip.notes = transposeNotes(clip.notes ?? [], semitones);
|
|
2234
|
+
return {
|
|
2235
|
+
ok: true,
|
|
2236
|
+
clip: serializeClip(clip),
|
|
2237
|
+
noteCount: clip.notes.length
|
|
2238
|
+
};
|
|
2239
|
+
}),
|
|
2240
|
+
sharedTool("ui_timeline_quantize_notes", async ({ clip: clipRef, division, strength, target }) => {
|
|
2241
|
+
const clip = resolveMidiClip(clipRef);
|
|
2242
|
+
const before = clip.notes ?? [];
|
|
2243
|
+
const options = { division: division };
|
|
2244
|
+
if (strength !== undefined)
|
|
2245
|
+
options.strength = strength;
|
|
2246
|
+
if (target !== undefined)
|
|
2247
|
+
options.target = target;
|
|
2248
|
+
const quantized = quantizeNotes(before, options);
|
|
2249
|
+
// The notes come back in the order they went in, so this pairs each
|
|
2250
|
+
// one with itself. A phrase already on the grid reports 0 moved
|
|
2251
|
+
// instead of reading as an edit that changed something.
|
|
2252
|
+
const movedNoteCount = quantized.filter((note, i) => note.startTick !== before[i].startTick ||
|
|
2253
|
+
note.durationTick !== before[i].durationTick).length;
|
|
2254
|
+
clip.notes = quantized;
|
|
2255
|
+
return {
|
|
2256
|
+
ok: true,
|
|
2257
|
+
clip: serializeClip(clip),
|
|
2258
|
+
noteCount: quantized.length,
|
|
2259
|
+
movedNoteCount
|
|
2260
|
+
};
|
|
2261
|
+
}),
|
|
2262
|
+
sharedTool("ui_timeline_scale_velocity", async ({ clip: target, factor }) => {
|
|
2263
|
+
const clip = resolveMidiClip(target);
|
|
2264
|
+
clip.notes = scaleVelocity(clip.notes ?? [], factor);
|
|
2265
|
+
return {
|
|
2266
|
+
ok: true,
|
|
2267
|
+
clip: serializeClip(clip),
|
|
2268
|
+
noteCount: clip.notes.length
|
|
2269
|
+
};
|
|
2270
|
+
}),
|
|
2271
|
+
tool("ui_timeline_insert_composition", "Insert a stored composition — a group of clips with named parameters (a lower third, a title card, a callout) — at a timecode. Its children keep the layering the template declares: each template track becomes an overlay track of its own, front-most on top, reused across insertions. `params` overrides the template's defaults by name; anything omitted keeps its default. Call list_compositions for the ids and their parameters.", z.object({
|
|
2272
|
+
composition_id: z
|
|
2273
|
+
.string()
|
|
2274
|
+
.describe("Composition id, from list_compositions."),
|
|
2275
|
+
startMs: z
|
|
2276
|
+
.number()
|
|
2277
|
+
.describe("Where the group starts on the timeline, in ms."),
|
|
2278
|
+
trackId: z
|
|
2279
|
+
.string()
|
|
2280
|
+
.optional()
|
|
2281
|
+
.describe("Track for the group clip itself. Its children still get their own overlay tracks. Defaults to an overlay track."),
|
|
2282
|
+
params: z
|
|
2283
|
+
.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
|
|
2284
|
+
.optional()
|
|
2285
|
+
.describe("Parameter overrides by name, e.g. {name: \"Ada Lovelace\"}. A name the template does not declare, or a value of the wrong type, is refused.")
|
|
2286
|
+
}), async ({ composition_id, startMs, trackId, params }) => {
|
|
2287
|
+
if (!loadComposition) {
|
|
2288
|
+
throw new Error("This surface has no composition library, so insert_composition cannot resolve a template.");
|
|
2289
|
+
}
|
|
2290
|
+
const id = composition_id;
|
|
2291
|
+
const composition = await loadComposition.get(id);
|
|
2292
|
+
if (!composition) {
|
|
2293
|
+
const available = await loadComposition.listIds();
|
|
2294
|
+
throw new Error(`No composition with id "${id}". ` +
|
|
2295
|
+
(available.length > 0
|
|
2296
|
+
? `Available: ${available.join(", ")}.`
|
|
2297
|
+
: "This install has none — save one with save_composition."));
|
|
2298
|
+
}
|
|
2299
|
+
const minted = instantiateComposition(composition, {
|
|
2300
|
+
startMs: startMs,
|
|
2301
|
+
params: params,
|
|
2302
|
+
newId: nextClipId
|
|
2303
|
+
});
|
|
2304
|
+
// Template track ids are names, not document ids. Two clips overlapping
|
|
2305
|
+
// on one track auto-dissolve into each other, so each template track
|
|
2306
|
+
// becomes a track of its own — created front-most first, because the
|
|
2307
|
+
// lowest track index draws on top (I9).
|
|
2308
|
+
const templateTracks = [];
|
|
2309
|
+
for (const child of composition.children) {
|
|
2310
|
+
if (!templateTracks.includes(child.trackId)) {
|
|
2311
|
+
templateTracks.push(child.trackId);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
const mapped = new Map();
|
|
2315
|
+
for (const name of [...templateTracks].reverse()) {
|
|
2316
|
+
const existing = tracks.find((t) => t.type === "overlay" && t.name === name);
|
|
2317
|
+
mapped.set(name, (existing ?? addTrackInternal("overlay", name)).id);
|
|
2318
|
+
}
|
|
2319
|
+
// The group draws nothing, but it still occupies its track's timeline,
|
|
2320
|
+
// and a group sharing a track with one of its children reads as an
|
|
2321
|
+
// overlap. It gets a track named after the composition instead.
|
|
2322
|
+
const groupTrack = trackId
|
|
2323
|
+
? resolveTrack(trackId)
|
|
2324
|
+
: (tracks.find((t) => t.type === "overlay" && t.name === composition.name) ?? addTrackInternal("overlay", composition.name));
|
|
2325
|
+
const [group, ...children] = minted;
|
|
2326
|
+
group.trackId = groupTrack.id;
|
|
2327
|
+
for (const [index, child] of children.entries()) {
|
|
2328
|
+
child.trackId =
|
|
2329
|
+
mapped.get(composition.children[index].trackId) ?? groupTrack.id;
|
|
2330
|
+
}
|
|
2331
|
+
clips.push(group, ...children);
|
|
2332
|
+
selectedClipIds = [group.id];
|
|
2333
|
+
return {
|
|
2334
|
+
ok: true,
|
|
2335
|
+
compositionId: composition.id,
|
|
2336
|
+
clip: serializeClip(group),
|
|
2337
|
+
children: children.map((child) => serializeClip(child)),
|
|
2338
|
+
params: group.compositionParams ?? {}
|
|
2339
|
+
};
|
|
2340
|
+
}),
|
|
2341
|
+
// Headless-only: the editor's ClipVersionHistory panel writes take
|
|
2342
|
+
// switches, renames and deletes straight into TimelineStore via
|
|
2343
|
+
// restoreVersion/renameTake/deleteTake. This op reaches the same
|
|
2344
|
+
// `selectTake`/`renameTake`/`deleteTake` helpers, so the take list cannot
|
|
2345
|
+
// fork between the two hosts (P0 AI Video, PRD § 8.10).
|
|
2346
|
+
tool("ui_timeline_list_takes", "List a clip's take history (clip.versions): id, label, source, " +
|
|
2347
|
+
"provider, model, createdAt, costCredits, durationMs, status, " +
|
|
2348
|
+
"favorite, and which one is active.", z.object({ target: z.string().describe("Clip id or name.") }), async ({ target }) => {
|
|
2349
|
+
const clip = resolveClip(target);
|
|
2350
|
+
const versions = clip.versions ?? [];
|
|
2351
|
+
const activeId = activeTakeIdOf(clip);
|
|
2352
|
+
return {
|
|
2353
|
+
ok: true,
|
|
2354
|
+
takes: versions.map((v) => ({
|
|
2355
|
+
id: v.id,
|
|
2356
|
+
label: v.label,
|
|
2357
|
+
source: v.source,
|
|
2358
|
+
provider: v.provider,
|
|
2359
|
+
model: v.model,
|
|
2360
|
+
createdAt: v.createdAt,
|
|
2361
|
+
costCredits: v.costCredits,
|
|
2362
|
+
durationMs: v.durationMs,
|
|
2363
|
+
status: v.status,
|
|
2364
|
+
favorite: v.favorite,
|
|
2365
|
+
active: v.id === activeId
|
|
2366
|
+
})),
|
|
2367
|
+
activeTakeId: activeId ?? null
|
|
2368
|
+
};
|
|
2369
|
+
}),
|
|
2370
|
+
tool("ui_timeline_select_take", "Make a stored take current on a clip — the same switch " +
|
|
2371
|
+
"restoreVersion does in the editor. Refuses a take that is not " +
|
|
2372
|
+
'`status: "success"`.', z.object({
|
|
2373
|
+
target: z.string().describe("Clip id or name."),
|
|
2374
|
+
takeId: z.string().describe("A take id, from list_takes.")
|
|
2375
|
+
}), async ({ target, takeId }) => {
|
|
2376
|
+
const clip = resolveClip(target);
|
|
2377
|
+
if (clip.mediaType === "model3d") {
|
|
2378
|
+
throw new Error(`Clip "${clip.name}" is a 3D clip — its version history includes ` +
|
|
2379
|
+
"bake renders that must not replace the glTF source. Take " +
|
|
2380
|
+
"selection is not available for model3d clips yet.");
|
|
2381
|
+
}
|
|
2382
|
+
const version = (clip.versions ?? []).find((v) => v.id === takeId);
|
|
2383
|
+
if (!version) {
|
|
2384
|
+
throw new Error(`No take "${takeId}" on "${clip.name}". ` +
|
|
2385
|
+
validUnits((clip.versions ?? []).map((v) => ({
|
|
2386
|
+
id: v.id,
|
|
2387
|
+
name: v.label ?? v.id
|
|
2388
|
+
})), "take"));
|
|
2389
|
+
}
|
|
2390
|
+
if (version.status !== "success") {
|
|
2391
|
+
throw new Error(`Take "${takeId}" on "${clip.name}" did not finish successfully ` +
|
|
2392
|
+
`(status: ${version.status}) — only a successful take can be selected.`);
|
|
2393
|
+
}
|
|
2394
|
+
const next = selectTake(clip, takeId);
|
|
2395
|
+
clips = clips.map((c) => (c.id === clip.id ? next : c));
|
|
2396
|
+
return { ok: true, clip: serializeClip(next) };
|
|
2397
|
+
}),
|
|
2398
|
+
tool("ui_timeline_rename_take", "Set a take's display label.", z.object({
|
|
2399
|
+
target: z.string().describe("Clip id or name."),
|
|
2400
|
+
takeId: z.string().describe("A take id, from list_takes."),
|
|
2401
|
+
label: z.string().describe("The new display label.")
|
|
2402
|
+
}), async ({ target, takeId, label }) => {
|
|
2403
|
+
const clip = resolveClip(target);
|
|
2404
|
+
if (!(clip.versions ?? []).some((v) => v.id === takeId)) {
|
|
2405
|
+
throw new Error(`No take "${takeId}" on "${clip.name}". ` +
|
|
2406
|
+
validUnits((clip.versions ?? []).map((v) => ({
|
|
2407
|
+
id: v.id,
|
|
2408
|
+
name: v.label ?? v.id
|
|
2409
|
+
})), "take"));
|
|
2410
|
+
}
|
|
2411
|
+
const next = renameTakeOnClip(clip, takeId, label);
|
|
2412
|
+
clips = clips.map((c) => (c.id === clip.id ? next : c));
|
|
2413
|
+
return { ok: true, clip: serializeClip(next) };
|
|
2414
|
+
}),
|
|
2415
|
+
tool("ui_timeline_delete_take", "Remove a take from a clip's history. Refuses when `takeId` is the " +
|
|
2416
|
+
"active take — select_take a different take first, then delete the " +
|
|
2417
|
+
"one you no longer want; a clip must always keep at least one take.", z.object({
|
|
2418
|
+
target: z.string().describe("Clip id or name."),
|
|
2419
|
+
takeId: z.string().describe("A take id, from list_takes.")
|
|
2420
|
+
}), async ({ target, takeId }) => {
|
|
2421
|
+
const clip = resolveClip(target);
|
|
2422
|
+
const { clip: next, error } = deleteTakeOnClip(clip, takeId);
|
|
2423
|
+
if (error)
|
|
2424
|
+
throw new Error(error);
|
|
2425
|
+
clips = clips.map((c) => (c.id === clip.id ? next : c));
|
|
2426
|
+
return { ok: true, clip: serializeClip(next) };
|
|
2427
|
+
}),
|
|
2428
|
+
// Subject/object tracks (P0 AI Video, Phase 2). The async provider call
|
|
2429
|
+
// that fills a track's samples is the `track_object` capability, not an
|
|
2430
|
+
// op — these four are the structural half over it, and they carry the
|
|
2431
|
+
// same semantics as `applyTimelineOp`'s own cases so the two hosts cannot
|
|
2432
|
+
// fork (`packages/timeline/src/ops/apply.ts`).
|
|
2433
|
+
tool("ui_timeline_list_tracks", "List the document's subject/object tracks — id, clipId, name, kind, " +
|
|
2434
|
+
"status, source window and sample count. The samples themselves are " +
|
|
2435
|
+
"not returned: a track carries one per frame it covers, which is a " +
|
|
2436
|
+
"wall of numbers no caller reads through a listing.", z.object({
|
|
2437
|
+
target: z
|
|
2438
|
+
.string()
|
|
2439
|
+
.optional()
|
|
2440
|
+
.describe("A clip id or name to filter by. Omit for every track.")
|
|
2441
|
+
}), async ({ target }) => {
|
|
2442
|
+
const clipId = typeof target === "string" && target.trim() !== ""
|
|
2443
|
+
? resolveClip(target).id
|
|
2444
|
+
: undefined;
|
|
2445
|
+
const listed = mediaTracks.filter((t) => clipId === undefined || t.clipId === clipId);
|
|
2446
|
+
return {
|
|
2447
|
+
ok: true,
|
|
2448
|
+
tracks: listed.map((t) => ({
|
|
2449
|
+
id: t.id,
|
|
2450
|
+
clipId: t.clipId,
|
|
2451
|
+
name: t.name,
|
|
2452
|
+
kind: t.kind,
|
|
2453
|
+
status: t.status,
|
|
2454
|
+
sourceStartMs: t.sourceStartMs,
|
|
2455
|
+
sourceEndMs: t.sourceEndMs,
|
|
2456
|
+
sampleCount: t.samples.length,
|
|
2457
|
+
confidence: t.confidence
|
|
2458
|
+
}))
|
|
2459
|
+
};
|
|
2460
|
+
}),
|
|
2461
|
+
tool("ui_timeline_delete_track_object", "Delete a subject/object track and unbind every clip that was " +
|
|
2462
|
+
"following it — a clip left pointing at a track that is gone reads " +
|
|
2463
|
+
"as bound while following nothing.", z.object({
|
|
2464
|
+
trackId: z.string().describe("A track id, from list_tracks.")
|
|
2465
|
+
}), async ({ trackId }) => {
|
|
2466
|
+
const track = mediaTracks.find((t) => t.id === trackId);
|
|
2467
|
+
if (!track) {
|
|
2468
|
+
throw new Error(`No track "${trackId}". ` + validUnits(mediaTracks, "track"));
|
|
2469
|
+
}
|
|
2470
|
+
mediaTracks = mediaTracks.filter((t) => t.id !== trackId);
|
|
2471
|
+
clips = clips.map((c) => {
|
|
2472
|
+
if (c.trackBinding?.trackId !== trackId)
|
|
2473
|
+
return c;
|
|
2474
|
+
const { trackBinding: _dropped, ...rest } = c;
|
|
2475
|
+
return rest;
|
|
2476
|
+
});
|
|
2477
|
+
return { ok: true, deleted: { id: track.id, name: track.name } };
|
|
2478
|
+
}),
|
|
2479
|
+
tool("ui_timeline_bind_to_track", "Make a clip follow a subject/object track, so its position moves " +
|
|
2480
|
+
'with the tracked subject. Only "position" and "position_scale" are ' +
|
|
2481
|
+
"implemented; the other modes are named by the document format for a " +
|
|
2482
|
+
"later phase and are refused here rather than silently doing nothing.", z.object({
|
|
2483
|
+
target: z.string().describe("Clip id or name."),
|
|
2484
|
+
trackId: z.string().describe("A track id, from list_tracks."),
|
|
2485
|
+
mode: z
|
|
2486
|
+
.string()
|
|
2487
|
+
.describe('"position" or "position_scale".'),
|
|
2488
|
+
offset: z
|
|
2489
|
+
.object({ x: z.number(), y: z.number() })
|
|
2490
|
+
.optional()
|
|
2491
|
+
.describe("Canvas-pixel offset added on top of the tracked point."),
|
|
2492
|
+
scale: z
|
|
2493
|
+
.number()
|
|
2494
|
+
.optional()
|
|
2495
|
+
.describe('Scale multiplier, "position_scale" only.'),
|
|
2496
|
+
rotationOffset: z
|
|
2497
|
+
.number()
|
|
2498
|
+
.optional()
|
|
2499
|
+
.describe("Radians added to the clip's rotation."),
|
|
2500
|
+
smoothing: z
|
|
2501
|
+
.number()
|
|
2502
|
+
.optional()
|
|
2503
|
+
.describe("0..1; higher follows the track more loosely.")
|
|
2504
|
+
}), async ({ target, trackId, mode, offset, scale, rotationOffset, smoothing }) => {
|
|
2505
|
+
const clip = resolveClip(target);
|
|
2506
|
+
if (!mediaTracks.some((t) => t.id === trackId)) {
|
|
2507
|
+
throw new Error(`No track "${trackId}". ` + validUnits(mediaTracks, "track"));
|
|
2508
|
+
}
|
|
2509
|
+
if (mode !== "position" && mode !== "position_scale") {
|
|
2510
|
+
throw new Error(`bind_to_track mode "${String(mode)}" is not implemented yet — ` +
|
|
2511
|
+
`only "position" and "position_scale" affect rendering in this ` +
|
|
2512
|
+
"build. The field accepts the other modes so a document can " +
|
|
2513
|
+
"name the intent without a schema migration later, but binding " +
|
|
2514
|
+
"to one today would be a no-op, so the call is refused instead.");
|
|
2515
|
+
}
|
|
2516
|
+
const binding = {
|
|
2517
|
+
trackId: trackId,
|
|
2518
|
+
mode: mode
|
|
2519
|
+
};
|
|
2520
|
+
if (offset !== undefined) {
|
|
2521
|
+
binding.offset = offset;
|
|
2522
|
+
}
|
|
2523
|
+
if (scale !== undefined)
|
|
2524
|
+
binding.scale = scale;
|
|
2525
|
+
if (rotationOffset !== undefined) {
|
|
2526
|
+
binding.rotationOffset = rotationOffset;
|
|
2527
|
+
}
|
|
2528
|
+
if (smoothing !== undefined)
|
|
2529
|
+
binding.smoothing = smoothing;
|
|
2530
|
+
const next = { ...clip, trackBinding: binding };
|
|
2531
|
+
clips = clips.map((c) => (c.id === clip.id ? next : c));
|
|
2532
|
+
return { ok: true, clip: serializeClip(next) };
|
|
2533
|
+
}),
|
|
2534
|
+
tool("ui_timeline_unbind_track", "Stop a clip following a track. A clip that follows none is a no-op.", z.object({ target: z.string().describe("Clip id or name.") }), async ({ target }) => {
|
|
2535
|
+
const clip = resolveClip(target);
|
|
2536
|
+
if (!clip.trackBinding)
|
|
2537
|
+
return { ok: true, clip: serializeClip(clip) };
|
|
2538
|
+
const { trackBinding: _dropped, ...next } = clip;
|
|
2539
|
+
clips = clips.map((c) => (c.id === clip.id ? next : c));
|
|
2540
|
+
return { ok: true, clip: serializeClip(next) };
|
|
2541
|
+
}),
|
|
2542
|
+
sharedTool("ui_timeline_retarget_format", async ({ aspect_ratio, strategy, safe_margin, track_ids }) => {
|
|
2543
|
+
if (track_ids) {
|
|
2544
|
+
for (const [clipId, trackId] of Object.entries(track_ids)) {
|
|
2545
|
+
const clip = clips.find((candidate) => candidate.id === clipId);
|
|
2546
|
+
if (!clip) {
|
|
2547
|
+
throw new Error(`track_ids names unknown clip "${clipId}". ` +
|
|
2548
|
+
validUnits(clips, "clip"));
|
|
2549
|
+
}
|
|
2550
|
+
const track = mediaTracks.find((candidate) => candidate.id === trackId);
|
|
2551
|
+
if (!track || track.clipId !== clip.id) {
|
|
2552
|
+
throw new Error(`Track "${trackId}" does not belong to clip "${clip.name}". ` +
|
|
2553
|
+
validUnits(mediaTracks.filter((candidate) => candidate.clipId === clip.id), "track"));
|
|
2554
|
+
}
|
|
2555
|
+
if (track.status !== "ready") {
|
|
2556
|
+
throw new Error(`Track "${track.name}" is ${track.status}; a format adaptation can only follow a ready track.`);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
const now = new Date().toISOString();
|
|
2561
|
+
const source = {
|
|
2562
|
+
id: sequenceId,
|
|
2563
|
+
projectId,
|
|
2564
|
+
name: sequenceName,
|
|
2565
|
+
fps,
|
|
2566
|
+
width,
|
|
2567
|
+
height,
|
|
2568
|
+
durationMs: clips.reduce((end, clip) => Math.max(end, clip.startMs + clip.durationMs), 0),
|
|
2569
|
+
tracks: tracks.map((track) => structuredClone(track)),
|
|
2570
|
+
clips: clips.map((clip) => structuredClone(clip)),
|
|
2571
|
+
markers: markers.map((marker) => structuredClone(marker)),
|
|
2572
|
+
mediaTracks: mediaTracks.map((track) => structuredClone(track)),
|
|
2573
|
+
createdAt: now,
|
|
2574
|
+
updatedAt: now
|
|
2575
|
+
};
|
|
2576
|
+
if (seed?.transcript !== undefined) {
|
|
2577
|
+
source.transcript = structuredClone(seed.transcript);
|
|
2578
|
+
}
|
|
2579
|
+
if (seed?.scriptEnabled !== undefined) {
|
|
2580
|
+
source.scriptEnabled = seed.scriptEnabled;
|
|
2581
|
+
}
|
|
2582
|
+
if (seed?.templateId !== undefined)
|
|
2583
|
+
source.templateId = seed.templateId;
|
|
2584
|
+
if (tempo)
|
|
2585
|
+
source.tempo = structuredClone(tempo);
|
|
2586
|
+
source.camera2d = structuredClone(seed?.camera2d ?? null);
|
|
2587
|
+
if (setup)
|
|
2588
|
+
source.setup = structuredClone(setup);
|
|
2589
|
+
const adapted = adaptSequenceFormat(source, aspect_ratio, {
|
|
2590
|
+
strategy: strategy,
|
|
2591
|
+
safeMargin: safe_margin,
|
|
2592
|
+
trackIdByClipId: track_ids
|
|
2593
|
+
});
|
|
2594
|
+
const stored = retargetFormat
|
|
2595
|
+
? await retargetFormat(structuredClone(adapted.sequence))
|
|
2596
|
+
: { sequenceId: adapted.sequence.id, name: adapted.sequence.name };
|
|
2597
|
+
derivedSequences.push(structuredClone(adapted.sequence));
|
|
2598
|
+
return {
|
|
2599
|
+
ok: true,
|
|
2600
|
+
sourceSequenceId: sequenceId,
|
|
2601
|
+
sequenceId: stored.sequenceId,
|
|
2602
|
+
name: stored.name ?? adapted.sequence.name,
|
|
2603
|
+
width: adapted.sequence.width,
|
|
2604
|
+
height: adapted.sequence.height,
|
|
2605
|
+
strategy,
|
|
2606
|
+
croppedClipIds: adapted.croppedClipIds
|
|
2607
|
+
};
|
|
2608
|
+
}),
|
|
2609
|
+
sharedTool("ui_timeline_set_reframe_subject", async ({ clip_id, track_id, safe_margin, smoothing }) => {
|
|
2610
|
+
const clip = resolveClip(clip_id);
|
|
2611
|
+
const track = mediaTracks.find((candidate) => candidate.id === track_id);
|
|
2612
|
+
if (!track || track.clipId !== clip.id) {
|
|
2613
|
+
throw new Error(`No track "${String(track_id)}" belongs to clip "${clip.name}". ` +
|
|
2614
|
+
validUnits(mediaTracks.filter((candidate) => candidate.clipId === clip.id), "track"));
|
|
2615
|
+
}
|
|
2616
|
+
if (track.status !== "ready") {
|
|
2617
|
+
throw new Error(`Track "${track.name}" is ${track.status}; Smart Reframe can only follow a ready track.`);
|
|
2618
|
+
}
|
|
2619
|
+
if (!mediaTrackCanDriveReframe(track) ||
|
|
2620
|
+
isMediaTrackStale(track, clip)) {
|
|
2621
|
+
throw new Error(`Track "${track.name}" has no current analysis for clip "${clip.name}".`);
|
|
2622
|
+
}
|
|
2623
|
+
const previous = clip.reframe;
|
|
2624
|
+
const reframe = {
|
|
2625
|
+
mode: "track",
|
|
2626
|
+
trackId: track.id,
|
|
2627
|
+
sourceAssetId: clip.currentAssetId
|
|
2628
|
+
};
|
|
2629
|
+
if (safe_margin !== undefined)
|
|
2630
|
+
reframe.safeMargin = safe_margin;
|
|
2631
|
+
else if (previous?.safeMargin !== undefined) {
|
|
2632
|
+
reframe.safeMargin = previous.safeMargin;
|
|
2633
|
+
}
|
|
2634
|
+
if (smoothing !== undefined)
|
|
2635
|
+
reframe.smoothing = smoothing;
|
|
2636
|
+
else if (previous?.smoothing !== undefined) {
|
|
2637
|
+
reframe.smoothing = previous.smoothing;
|
|
2638
|
+
}
|
|
2639
|
+
if (previous?.keyframes) {
|
|
2640
|
+
reframe.keyframes = structuredClone(previous.keyframes);
|
|
2641
|
+
}
|
|
2642
|
+
if (previous?.sourceWidth !== undefined) {
|
|
2643
|
+
reframe.sourceWidth = previous.sourceWidth;
|
|
2644
|
+
}
|
|
2645
|
+
if (previous?.sourceHeight !== undefined) {
|
|
2646
|
+
reframe.sourceHeight = previous.sourceHeight;
|
|
2647
|
+
}
|
|
2648
|
+
const next = { ...clip, reframe };
|
|
2649
|
+
clips = clips.map((candidate) => candidate.id === clip.id ? next : candidate);
|
|
2650
|
+
return { ok: true, clip: serializeClip(next) };
|
|
2651
|
+
}),
|
|
2652
|
+
sharedTool("ui_timeline_add_reframe_keyframe", async ({ clip_id, source_ms, x, y, zoom }) => {
|
|
2653
|
+
const clip = resolveClip(clip_id);
|
|
2654
|
+
if (!clip.reframe) {
|
|
2655
|
+
throw new Error(`Clip "${clip.name}" has no Smart Reframe path. Retarget the format or set_reframe_subject first.`);
|
|
2656
|
+
}
|
|
2657
|
+
const keyframe = {
|
|
2658
|
+
sourceMs: source_ms,
|
|
2659
|
+
x: x,
|
|
2660
|
+
y: y
|
|
2661
|
+
};
|
|
2662
|
+
if (zoom !== undefined) {
|
|
2663
|
+
keyframe.zoom = zoom;
|
|
2664
|
+
}
|
|
2665
|
+
const next = {
|
|
2666
|
+
...clip,
|
|
2667
|
+
reframe: addReframeKeyframe(clip.reframe, keyframe)
|
|
2668
|
+
};
|
|
2669
|
+
clips = clips.map((candidate) => candidate.id === clip.id ? next : candidate);
|
|
2670
|
+
return { ok: true, clip: serializeClip(next), keyframe };
|
|
2671
|
+
}),
|
|
2672
|
+
sharedTool("ui_timeline_clear_reframe", async ({ clip_id }) => {
|
|
2673
|
+
const clip = resolveClip(clip_id);
|
|
2674
|
+
const next = clearReframe(clip);
|
|
2675
|
+
clips = clips.map((candidate) => candidate.id === clip.id ? next : candidate);
|
|
2676
|
+
return { ok: true, clip: serializeClip(next) };
|
|
2677
|
+
})
|
|
2678
|
+
];
|
|
2679
|
+
// Opt-in, and outside the `ui_timeline_` prefix on purpose: `edit_timeline`
|
|
2680
|
+
// builds this same bridge and matches its ops by that prefix, so a preview
|
|
2681
|
+
// tool it can never call has no business in its op catalogue. A case that
|
|
2682
|
+
// grades "did it look at the frame" asks for it.
|
|
2683
|
+
if (initial.preview) {
|
|
2684
|
+
tools.push(tool("preview_timeline_frame", "Look at the sequence at one or more timecodes: what is on screen, layered top of the stack first, with each layer's opacity and track index. This surface has no rasterizer, so it reports the layer stack rather than pixels — enough to see a layer that is covered, missing, or drawing nothing. Call it after an edit, before you say you are done.", z.object({
|
|
2685
|
+
times_ms: z
|
|
2686
|
+
.array(z.number().nonnegative())
|
|
2687
|
+
.min(1)
|
|
2688
|
+
.max(8)
|
|
2689
|
+
.describe("Absolute timeline positions to look at, in ms.")
|
|
2690
|
+
}), async ({ times_ms }) => {
|
|
2691
|
+
const frames = times_ms.map((timeMs) => ({
|
|
2692
|
+
time_ms: timeMs,
|
|
2693
|
+
layers: computeActiveLayers(tracks, clips, timeMs)
|
|
2694
|
+
.map((layer) => ({
|
|
2695
|
+
clip_id: layer.clipId,
|
|
2696
|
+
clip_name: clips.find((c) => c.id === layer.clipId)?.name ?? layer.clipId,
|
|
2697
|
+
kind: layer.kind,
|
|
2698
|
+
track_index: layer.trackIndex,
|
|
2699
|
+
z_index: 1000 - layer.trackIndex,
|
|
2700
|
+
opacity: layer.opacity,
|
|
2701
|
+
text: layer.textStyle?.text
|
|
2702
|
+
}))
|
|
2703
|
+
// Top of the stack first, the order the skill's report describes.
|
|
2704
|
+
.sort((a, b) => b.z_index - a.z_index)
|
|
2705
|
+
}));
|
|
2706
|
+
previewTimesMs.push(...times_ms);
|
|
2707
|
+
for (const frame of frames) {
|
|
2708
|
+
for (const layer of frame.layers) {
|
|
2709
|
+
previewedLayerKinds.add(layer.kind);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
return { ok: true, width, height, frames };
|
|
2713
|
+
}));
|
|
2714
|
+
}
|
|
2715
|
+
// Recorded rather than counted: a predicate asks where the last edit sits
|
|
2716
|
+
// relative to a preview, which a tally cannot answer. The push happens
|
|
2717
|
+
// before the call, so a tool that throws is still in the transcript.
|
|
2718
|
+
const recorded = tools.map((entry) => ({
|
|
2719
|
+
...entry,
|
|
2720
|
+
execute: (args) => {
|
|
2721
|
+
toolLog.push(entry.name);
|
|
2722
|
+
return entry.execute(args);
|
|
2723
|
+
}
|
|
2724
|
+
}));
|
|
2725
|
+
return {
|
|
2726
|
+
tools: recorded,
|
|
2727
|
+
finalState: () => ({
|
|
2728
|
+
fps,
|
|
2729
|
+
width,
|
|
2730
|
+
height,
|
|
2731
|
+
durationMs: clips.reduce((m, c) => Math.max(m, c.startMs + c.durationMs), 0),
|
|
2732
|
+
playheadMs,
|
|
2733
|
+
tracks: tracks.map((t) => ({
|
|
2734
|
+
id: t.id,
|
|
2735
|
+
name: t.name,
|
|
2736
|
+
type: t.type,
|
|
2737
|
+
index: t.index
|
|
2738
|
+
})),
|
|
2739
|
+
clips: clips.map((c) => ({
|
|
2740
|
+
id: c.id,
|
|
2741
|
+
name: c.name,
|
|
2742
|
+
trackId: c.trackId,
|
|
2743
|
+
mediaType: c.mediaType,
|
|
2744
|
+
startMs: c.startMs,
|
|
2745
|
+
durationMs: c.durationMs,
|
|
2746
|
+
prompt: c.prompt,
|
|
2747
|
+
animations: (c.animations ?? []).map((a) => ({
|
|
2748
|
+
role: a.role,
|
|
2749
|
+
preset: a.preset
|
|
2750
|
+
}))
|
|
2751
|
+
})),
|
|
2752
|
+
documentTracks: tracks.map((t) => structuredClone(t)),
|
|
2753
|
+
documentClips: clips.map((c) => structuredClone(c)),
|
|
2754
|
+
markers: markers.map((m) => structuredClone(m)),
|
|
2755
|
+
mediaTracks: mediaTracks.map((t) => structuredClone(t)),
|
|
2756
|
+
derivedSequences: derivedSequences.map((sequence) => structuredClone(sequence)),
|
|
2757
|
+
tempo: tempo ? structuredClone(tempo) : undefined,
|
|
2758
|
+
camera2d: structuredClone(seed?.camera2d ?? null),
|
|
2759
|
+
setup: setup ? structuredClone(setup) : null,
|
|
2760
|
+
transitionCandidates: structuredClone(transitionCandidates),
|
|
2761
|
+
auditionedTransitionCandidateId,
|
|
2762
|
+
appliedTransitionCandidates: structuredClone(appliedTransitionCandidates),
|
|
2763
|
+
toolLog: [...toolLog],
|
|
2764
|
+
previewTimesMs: [...previewTimesMs],
|
|
2765
|
+
previewedLayerKinds: [...previewedLayerKinds]
|
|
2766
|
+
})
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
const TIMELINE_SYSTEM_PROMPT = `You are an assistant driving a timeline / video editor through UI tools.
|
|
2770
|
+
|
|
2771
|
+
Use the ui_timeline_* tools to inspect and modify the sequence:
|
|
2772
|
+
- Call ui_timeline_get_state first to see what's already there and get track/clip ids and names.
|
|
2773
|
+
- Add content with ui_timeline_add_text_clip, ui_timeline_add_shape_clip, or ui_timeline_generate_clip; add tracks with ui_timeline_add_track when needed.
|
|
2774
|
+
- Address existing clips by id, name, or "selected" with ui_timeline_split_clip, ui_timeline_trim_clip, ui_timeline_move_clip, ui_timeline_delete_clip, ui_timeline_duplicate_clip, ui_timeline_set_clip_params, ui_timeline_set_clip_binding, ui_timeline_set_transition, ui_timeline_set_time_remap, ui_timeline_animate_clip, ui_timeline_clear_animations, ui_timeline_select_clip.
|
|
2775
|
+
- Use ui_timeline_generatively_edit_clip for a nondestructive video edit. Pass the exact clip_id, an instruction, and an optional provider/model. It captures the clip's playable source window, returns an inactive candidate and generationId, and never changes the active take. After that candidate succeeds, call ui_timeline_apply_take with the same clip_id and the returned take_id to promote it in one undoable action. Do not request extend, object, range, or operation fields: the instruction is the edit target.
|
|
2776
|
+
- Before animating a clip, call ui_timeline_list_animation_presets to discover the exact preset ids, allowed roles, and params.
|
|
2777
|
+
- For motion no preset covers, animate with preset "custom" and pass curves — [{property, keyframes: [{t, value}]}], where t runs 0..1 over the animation window. list_animation_presets reports which properties a curve may drive.
|
|
2778
|
+
- ui_timeline_seek moves the playhead (useful before a playhead-relative split).
|
|
2779
|
+
- Use ui_timeline_list_tracks to inspect source-time subject tracks. ui_timeline_set_reframe_subject makes one ready track drive a clip's crop; ui_timeline_add_reframe_keyframe adds a manual source-time correction, and ui_timeline_clear_reframe removes only that framing state.
|
|
2780
|
+
- Generate a selected-cut transition with ui_timeline_generate_transition_at_cut. It returns an inactive cut-level candidate without changing either source clip. Audition it with ui_timeline_preview_transition_candidate, then explicitly apply it with ui_timeline_apply_transition_at_cut and the returned candidate_id. Never use a normal clip take or ordinary built-in transition as the generated result, and re-read state if the cut may have changed.
|
|
2781
|
+
- ui_timeline_retarget_format creates a new sequence for one target aspect ratio and leaves this sequence unchanged. Run it once per portrait, square, or other adaptation, then continue editing the returned sequence id.
|
|
2782
|
+
- For a played part: add a midi track, place phrases with ui_timeline_add_midi_clip (notes in ticks from the clip's content start, 960 ticks = a quarter note), rewrite them with ui_timeline_set_notes, pick the synth with ui_timeline_set_track_instrument — either a named voice ({"preset": "bass"}: saw-lead, square-lead, soft-pad, pluck, bass, bell) or every field spelled out — and set the speed once with ui_timeline_set_tempo, which rescales the midi clips and leaves picture and audio where they are.
|
|
2783
|
+
- Edit a phrase you already placed without resending it: ui_timeline_transpose_clip moves every note by whole semitones, ui_timeline_quantize_notes snaps onsets to a note grid (1/4, 1/8, 1/16, 1/32, 1/8T, 1/16T; strength below 1 keeps some of the feel) and reports how many notes moved, ui_timeline_scale_velocity multiplies how hard they are struck. get_state reports each midi clip's startBarsBeats, so the next phrase goes on a bar line.
|
|
2784
|
+
- Flag moments with ui_timeline_add_marker / ui_timeline_delete_marker. To cut to music, lay the grid down with ui_timeline_set_markers_from_beats and put clip boundaries on it with ui_timeline_snap_to_beats, then read its per-clip report — a clip further than the tolerance from every beat is left alone and says so.
|
|
2785
|
+
|
|
2786
|
+
- Look at what you made with preview_timeline_frame before you stop.
|
|
2787
|
+
|
|
2788
|
+
Call one tool at a time and use the result before the next call. When the objective is fully satisfied, STOP calling tools and give a one-line summary.
|
|
2789
|
+
${motionGraphicsSection()}`;
|
|
2790
|
+
/**
|
|
2791
|
+
* The shipped `motion-graphics` skill, verbatim, plus the two lines that
|
|
2792
|
+
* reconcile it with this surface. The eval measures motion the skill teaches,
|
|
2793
|
+
* so the model gets the same document a product agent gets rather than a
|
|
2794
|
+
* paraphrase that drifts from it. Empty when the build ships no skills —
|
|
2795
|
+
* a skill file missing is not a reason to fail every timeline case.
|
|
2796
|
+
*/
|
|
2797
|
+
function motionGraphicsSection() {
|
|
2798
|
+
const skill = findSystemSkill("motion-graphics")?.content;
|
|
2799
|
+
if (!skill)
|
|
2800
|
+
return "";
|
|
2801
|
+
return `
|
|
2802
|
+
---
|
|
2803
|
+
|
|
2804
|
+
The motion-graphics craft, as shipped. It names the capability tools: read \`get_timeline\` as ui_timeline_get_state and every \`edit_timeline\` op as the matching ui_timeline_* tool. \`preview_timeline_frame\` is here and reports the layer stack rather than pixels.
|
|
2805
|
+
|
|
2806
|
+
${skill}`;
|
|
2807
|
+
}
|
|
2808
|
+
export const TIMELINE_TOOL_LOOP_CASES = [
|
|
2809
|
+
{
|
|
2810
|
+
id: "titles-with-motion",
|
|
2811
|
+
description: "Add a text clip and give it a fade-in entrance animation",
|
|
2812
|
+
objective: "Add a text clip that says 'Hello' and give it a fade-in entrance animation.",
|
|
2813
|
+
createBridge: () => createTimelineToolBridge(),
|
|
2814
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
2815
|
+
expect: {
|
|
2816
|
+
requiredTools: ["ui_timeline_add_text_clip", "ui_timeline_animate_clip"],
|
|
2817
|
+
ordering: [["ui_timeline_add_text_clip", "ui_timeline_animate_clip"]],
|
|
2818
|
+
noErrorResults: true,
|
|
2819
|
+
minToolCalls: 2,
|
|
2820
|
+
maxToolCalls: 12,
|
|
2821
|
+
finalState: [
|
|
2822
|
+
{
|
|
2823
|
+
name: "hasAnimatedTextClip",
|
|
2824
|
+
detail: "no text clip with an 'in' animation",
|
|
2825
|
+
test: (s) => s.clips.some((c) => c.mediaType === "text" &&
|
|
2826
|
+
c.animations.some((a) => a.role === "in"))
|
|
2827
|
+
}
|
|
2828
|
+
]
|
|
2829
|
+
}
|
|
2830
|
+
},
|
|
2831
|
+
{
|
|
2832
|
+
id: "generate-and-arrange",
|
|
2833
|
+
description: "Generate a video clip and move it to a specific start time",
|
|
2834
|
+
objective: "Add a video track, generate a text-to-video clip on it, then move the clip to start at 2000ms on the timeline.",
|
|
2835
|
+
createBridge: () => createTimelineToolBridge(),
|
|
2836
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
2837
|
+
expect: {
|
|
2838
|
+
requiredTools: ["ui_timeline_generate_clip", "ui_timeline_move_clip"],
|
|
2839
|
+
noErrorResults: true,
|
|
2840
|
+
minToolCalls: 2,
|
|
2841
|
+
maxToolCalls: 12,
|
|
2842
|
+
finalState: [
|
|
2843
|
+
{
|
|
2844
|
+
name: "hasGeneratedVideoClipAt2000",
|
|
2845
|
+
detail: "no generated video clip at startMs=2000 with a prompt",
|
|
2846
|
+
test: (s) => s.clips.some((c) => c.mediaType === "video" && c.startMs === 2000 && !!c.prompt)
|
|
2847
|
+
}
|
|
2848
|
+
]
|
|
2849
|
+
}
|
|
2850
|
+
},
|
|
2851
|
+
{
|
|
2852
|
+
id: "cut-and-trim",
|
|
2853
|
+
description: "Split a clip and delete the second half",
|
|
2854
|
+
objective: "The timeline has one video clip named 'shot' running from 0ms to 6000ms. Split it at 3000ms and delete the second half.",
|
|
2855
|
+
createBridge: () => createTimelineToolBridge({
|
|
2856
|
+
tracks: [{ type: "video" }],
|
|
2857
|
+
clips: [
|
|
2858
|
+
{
|
|
2859
|
+
name: "shot",
|
|
2860
|
+
trackIndex: 0,
|
|
2861
|
+
mediaType: "video",
|
|
2862
|
+
startMs: 0,
|
|
2863
|
+
durationMs: 6000
|
|
2864
|
+
}
|
|
2865
|
+
]
|
|
2866
|
+
}),
|
|
2867
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
2868
|
+
userPrompt: "Objective: The timeline has one video clip named 'shot' running from 0ms to 6000ms. Split it at 3000ms and delete the second half.",
|
|
2869
|
+
expect: {
|
|
2870
|
+
requiredTools: ["ui_timeline_split_clip", "ui_timeline_delete_clip"],
|
|
2871
|
+
noErrorResults: true,
|
|
2872
|
+
minToolCalls: 2,
|
|
2873
|
+
maxToolCalls: 12,
|
|
2874
|
+
finalState: [
|
|
2875
|
+
{
|
|
2876
|
+
name: "oneClipLeftAt3000ms",
|
|
2877
|
+
detail: "expected exactly 1 clip with durationMs 3000",
|
|
2878
|
+
test: (s) => s.clips.length === 1 && s.clips[0].durationMs === 3000
|
|
2879
|
+
}
|
|
2880
|
+
]
|
|
2881
|
+
}
|
|
2882
|
+
},
|
|
2883
|
+
{
|
|
2884
|
+
id: "keyframed-slide",
|
|
2885
|
+
description: "Keyframe an entrance with a custom animation instead of a preset",
|
|
2886
|
+
objective: "Add a text clip that says 'Launch' and give it a keyframed entrance: over the first 800ms it rises 120 pixels into place, from offsetY 120 down to 0. Use a custom animation with explicit keyframes, not a preset.",
|
|
2887
|
+
createBridge: () => createTimelineToolBridge(),
|
|
2888
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
2889
|
+
expect: {
|
|
2890
|
+
requiredTools: [
|
|
2891
|
+
"ui_timeline_add_text_clip",
|
|
2892
|
+
"ui_timeline_animate_clip"
|
|
2893
|
+
],
|
|
2894
|
+
ordering: [["ui_timeline_add_text_clip", "ui_timeline_animate_clip"]],
|
|
2895
|
+
noErrorResults: true,
|
|
2896
|
+
minToolCalls: 2,
|
|
2897
|
+
maxToolCalls: 12,
|
|
2898
|
+
finalState: [
|
|
2899
|
+
{
|
|
2900
|
+
name: "hasCustomOffsetYCurve",
|
|
2901
|
+
detail: "no text clip carrying a custom 'in' animation whose offsetY curve ends at 0",
|
|
2902
|
+
test: (s) => s.documentClips.some((clip) => {
|
|
2903
|
+
if (clip.mediaType !== "text")
|
|
2904
|
+
return false;
|
|
2905
|
+
const animation = (clip.animations ?? []).find((a) => a.role === "in" && a.preset === "custom");
|
|
2906
|
+
const curve = animation?.custom?.curves.find((c) => c.property === "offsetY");
|
|
2907
|
+
if (!curve)
|
|
2908
|
+
return false;
|
|
2909
|
+
const keyframes = curve.keyframes;
|
|
2910
|
+
const first = keyframes[0];
|
|
2911
|
+
const last = keyframes[keyframes.length - 1];
|
|
2912
|
+
// A rise into place: starts below, lands on the layout
|
|
2913
|
+
// position, and the window is the 800ms that was asked for.
|
|
2914
|
+
return (first.t === 0 &&
|
|
2915
|
+
last.t === 1 &&
|
|
2916
|
+
first.value >= 100 &&
|
|
2917
|
+
last.value === 0 &&
|
|
2918
|
+
animation?.durationMs === 800);
|
|
2919
|
+
})
|
|
2920
|
+
}
|
|
2921
|
+
]
|
|
2922
|
+
}
|
|
2923
|
+
},
|
|
2924
|
+
{
|
|
2925
|
+
id: "kinetic-title-staggered",
|
|
2926
|
+
description: "Stagger a title's words so the entrance still finishes inside the clip",
|
|
2927
|
+
objective: "Put the title 'MAKE IT MOVE' on screen for 2500ms and have the words arrive one after another instead of all at once. The whole entrance has to be over while the card is still up.",
|
|
2928
|
+
createBridge: () => createTimelineToolBridge({ preview: true }),
|
|
2929
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
2930
|
+
expect: {
|
|
2931
|
+
requiredTools: [
|
|
2932
|
+
"ui_timeline_add_text_clip",
|
|
2933
|
+
"ui_timeline_animate_clip"
|
|
2934
|
+
],
|
|
2935
|
+
ordering: [["ui_timeline_add_text_clip", "ui_timeline_animate_clip"]],
|
|
2936
|
+
noErrorResults: true,
|
|
2937
|
+
minToolCalls: 2,
|
|
2938
|
+
maxToolCalls: 14,
|
|
2939
|
+
finalState: [
|
|
2940
|
+
{
|
|
2941
|
+
name: "staggerSpanFitsTheCard",
|
|
2942
|
+
detail: "no text clip whose entrance staggers over at least two units and finishes inside the clip",
|
|
2943
|
+
test: (s) => s.documentClips.some((clip) => {
|
|
2944
|
+
if (clip.mediaType !== "text")
|
|
2945
|
+
return false;
|
|
2946
|
+
const entrance = (clip.animations ?? []).find((a) => a.role === "in" && a.stagger);
|
|
2947
|
+
const stagger = entrance?.stagger;
|
|
2948
|
+
if (!entrance || !stagger)
|
|
2949
|
+
return false;
|
|
2950
|
+
return (staggerUnitsOf(clip, stagger.unit, s) >= 2 &&
|
|
2951
|
+
staggerSpanFitsClip(clip, entrance, s));
|
|
2952
|
+
})
|
|
2953
|
+
}
|
|
2954
|
+
]
|
|
2955
|
+
}
|
|
2956
|
+
},
|
|
2957
|
+
{
|
|
2958
|
+
id: "lower-third-layered",
|
|
2959
|
+
description: "Put a scrim behind a name plate and keep both inside the shot",
|
|
2960
|
+
objective: "The shot named 'Host' runs from 0ms to 6000ms. While it is on screen, put the name 'Maya Chen' on the picture with a dark bar behind the words so they stay readable against the shot. Both have to sit inside that shot's window.",
|
|
2961
|
+
createBridge: () => createTimelineToolBridge({
|
|
2962
|
+
preview: true,
|
|
2963
|
+
tracks: [{ type: "video", name: "Picture" }],
|
|
2964
|
+
clips: [
|
|
2965
|
+
{
|
|
2966
|
+
name: "Host",
|
|
2967
|
+
trackIndex: 0,
|
|
2968
|
+
mediaType: "video",
|
|
2969
|
+
startMs: 0,
|
|
2970
|
+
durationMs: 6000
|
|
2971
|
+
}
|
|
2972
|
+
]
|
|
2973
|
+
}),
|
|
2974
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
2975
|
+
expect: {
|
|
2976
|
+
requiredTools: [
|
|
2977
|
+
"ui_timeline_add_text_clip",
|
|
2978
|
+
"ui_timeline_add_shape_clip"
|
|
2979
|
+
],
|
|
2980
|
+
noErrorResults: true,
|
|
2981
|
+
minToolCalls: 2,
|
|
2982
|
+
maxToolCalls: 14,
|
|
2983
|
+
finalState: [
|
|
2984
|
+
{
|
|
2985
|
+
name: "scrimBehindTextInsideTheShot",
|
|
2986
|
+
detail: "no shape clip drawn over the picture and under the text, sharing frames with it inside 0-6000ms",
|
|
2987
|
+
test: (s) => {
|
|
2988
|
+
const indexOf = (trackId) => s.tracks.find((t) => t.id === trackId)?.index ?? -1;
|
|
2989
|
+
const inShot = (c) => c.startMs >= 0 && c.startMs + c.durationMs <= 6000;
|
|
2990
|
+
const overlaps = (a, b) => a.startMs < b.startMs + b.durationMs &&
|
|
2991
|
+
b.startMs < a.startMs + a.durationMs;
|
|
2992
|
+
const texts = s.clips.filter((c) => c.mediaType === "text" && inShot(c));
|
|
2993
|
+
const shapes = s.clips.filter((c) => c.mediaType === "shape" && inShot(c));
|
|
2994
|
+
const picture = s.clips.filter((c) => c.mediaType === "video");
|
|
2995
|
+
// Lowest index draws on top, so the scrim sits between the two:
|
|
2996
|
+
// over the shot it darkens, under the words it backs. A scrim
|
|
2997
|
+
// that never shares a frame with the text backs nothing.
|
|
2998
|
+
return texts.some((text) => shapes.some((shape) => indexOf(shape.trackId) > indexOf(text.trackId) &&
|
|
2999
|
+
overlaps(shape, text) &&
|
|
3000
|
+
picture.some((shot) => indexOf(shot.trackId) > indexOf(shape.trackId))));
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
]
|
|
3004
|
+
}
|
|
3005
|
+
},
|
|
3006
|
+
{
|
|
3007
|
+
id: "entrance-decelerates",
|
|
3008
|
+
description: "Every entrance eases out or springs, never accelerates in",
|
|
3009
|
+
objective: "Add two title cards, 'Chapter One' and 'Chapter Two', and bring each one on so it arrives and settles rather than speeding up as it lands.",
|
|
3010
|
+
createBridge: () => createTimelineToolBridge({ preview: true }),
|
|
3011
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
3012
|
+
expect: {
|
|
3013
|
+
requiredTools: ["ui_timeline_animate_clip"],
|
|
3014
|
+
noErrorResults: true,
|
|
3015
|
+
minToolCalls: 2,
|
|
3016
|
+
maxToolCalls: 16,
|
|
3017
|
+
finalState: [
|
|
3018
|
+
{
|
|
3019
|
+
name: "everyEntranceDecelerates",
|
|
3020
|
+
detail: "an 'in' animation runs on an easing that is neither an ease-out family nor a spring",
|
|
3021
|
+
test: (s) => {
|
|
3022
|
+
const entrances = s.documentClips.flatMap((clip) => (clip.animations ?? []).filter((a) => a.role === "in"));
|
|
3023
|
+
return (entrances.length >= 2 &&
|
|
3024
|
+
entrances.every((a) => easingDecelerates(effectiveEasing(a))));
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
]
|
|
3028
|
+
}
|
|
3029
|
+
},
|
|
3030
|
+
{
|
|
3031
|
+
id: "beat-cut",
|
|
3032
|
+
description: "Move every picture boundary onto a named musical onset",
|
|
3033
|
+
objective: "The music hits at 0ms, 2000ms, 4000ms and 6000ms. My three shots — A, B and C — are roughly laid out and none of the cuts land on those hits. Put every cut on a hit, keeping the shots back to back with no gap.",
|
|
3034
|
+
createBridge: () => createTimelineToolBridge({
|
|
3035
|
+
preview: true,
|
|
3036
|
+
tracks: [{ type: "video", name: "Picture" }],
|
|
3037
|
+
clips: [
|
|
3038
|
+
{
|
|
3039
|
+
name: "A",
|
|
3040
|
+
trackIndex: 0,
|
|
3041
|
+
mediaType: "video",
|
|
3042
|
+
startMs: 0,
|
|
3043
|
+
durationMs: 2180
|
|
3044
|
+
},
|
|
3045
|
+
{
|
|
3046
|
+
name: "B",
|
|
3047
|
+
trackIndex: 0,
|
|
3048
|
+
mediaType: "video",
|
|
3049
|
+
startMs: 2180,
|
|
3050
|
+
durationMs: 2080
|
|
3051
|
+
},
|
|
3052
|
+
{
|
|
3053
|
+
name: "C",
|
|
3054
|
+
trackIndex: 0,
|
|
3055
|
+
mediaType: "video",
|
|
3056
|
+
startMs: 4260,
|
|
3057
|
+
durationMs: 1740
|
|
3058
|
+
}
|
|
3059
|
+
]
|
|
3060
|
+
}),
|
|
3061
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
3062
|
+
expect: {
|
|
3063
|
+
noErrorResults: true,
|
|
3064
|
+
minToolCalls: 1,
|
|
3065
|
+
maxToolCalls: 16,
|
|
3066
|
+
finalState: [
|
|
3067
|
+
{
|
|
3068
|
+
name: "everyBoundaryOnAnOnset",
|
|
3069
|
+
detail: "a picture clip's start or end is further than 60ms from 0/2000/4000/6000ms",
|
|
3070
|
+
test: (s) => {
|
|
3071
|
+
const onsets = [0, 2000, 4000, 6000];
|
|
3072
|
+
const onBeat = (ms) => onsets.some((onset) => Math.abs(ms - onset) <= 60);
|
|
3073
|
+
const picture = s.clips.filter((c) => c.mediaType === "video");
|
|
3074
|
+
return (picture.length === 3 &&
|
|
3075
|
+
picture.every((c) => onBeat(c.startMs) && onBeat(c.startMs + c.durationMs)));
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
]
|
|
3079
|
+
}
|
|
3080
|
+
},
|
|
3081
|
+
{
|
|
3082
|
+
id: "looked-before-done",
|
|
3083
|
+
description: "Check the frame after the last edit, before reporting done",
|
|
3084
|
+
objective: "Add an end card that says 'END' starting at 4000ms, then look at what is actually on screen there before you tell me it is finished.",
|
|
3085
|
+
createBridge: () => createTimelineToolBridge({ preview: true }),
|
|
3086
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
3087
|
+
expect: {
|
|
3088
|
+
requiredTools: ["ui_timeline_add_text_clip", "preview_timeline_frame"],
|
|
3089
|
+
noErrorResults: true,
|
|
3090
|
+
minToolCalls: 2,
|
|
3091
|
+
maxToolCalls: 12,
|
|
3092
|
+
finalState: [
|
|
3093
|
+
{
|
|
3094
|
+
name: "previewedAfterTheLastEdit",
|
|
3095
|
+
detail: "the run's last edit is not followed by a preview_timeline_frame call",
|
|
3096
|
+
test: (s) => s.clips.some((c) => c.mediaType === "text") &&
|
|
3097
|
+
previewedAfterLastEdit(s.toolLog) &&
|
|
3098
|
+
previewedMidMotion(s.previewTimesMs, s.documentClips, s)
|
|
3099
|
+
}
|
|
3100
|
+
]
|
|
3101
|
+
}
|
|
3102
|
+
},
|
|
3103
|
+
{
|
|
3104
|
+
id: "video-flow-plan-then-generate",
|
|
3105
|
+
description: "Plan a video's beats, then cut it — the guided flow through the tools",
|
|
3106
|
+
objective: "This is a 15-second ad for a paper boat. Write the brief onto the sequence, plan four beats — the second and fourth each carry a voiceover line — then generate the video from that plan with model 'nodetool/kling-turbo' and voice 'alloy'.",
|
|
3107
|
+
createBridge: () => createTimelineToolBridge(),
|
|
3108
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
3109
|
+
expect: {
|
|
3110
|
+
requiredTools: [
|
|
3111
|
+
"ui_timeline_plan_beats",
|
|
3112
|
+
"ui_timeline_generate_from_beats"
|
|
3113
|
+
],
|
|
3114
|
+
noErrorResults: true,
|
|
3115
|
+
minToolCalls: 2,
|
|
3116
|
+
maxToolCalls: 12,
|
|
3117
|
+
finalState: [
|
|
3118
|
+
{
|
|
3119
|
+
name: "onePictureClipPerBeat",
|
|
3120
|
+
detail: "the picture track does not hold exactly one clip per planned beat",
|
|
3121
|
+
test: (s) => (s.setup?.beats?.length ?? 0) > 0 &&
|
|
3122
|
+
s.clips.filter((c) => c.mediaType === "video").length ===
|
|
3123
|
+
(s.setup?.beats?.length ?? 0)
|
|
3124
|
+
},
|
|
3125
|
+
{
|
|
3126
|
+
name: "oneVoiceoverClipPerVoicedBeat",
|
|
3127
|
+
detail: "the voiceover clips do not match the beats that carry a line",
|
|
3128
|
+
test: (s) => {
|
|
3129
|
+
const voiced = (s.setup?.beats ?? []).filter((beat) => (beat.voiceover ?? "").trim().length > 0).length;
|
|
3130
|
+
const audio = s.documentClips.filter((c) => c.bindingKind === "text-to-audio" && c.beatId).length;
|
|
3131
|
+
return voiced > 0 && audio === voiced;
|
|
3132
|
+
}
|
|
3133
|
+
},
|
|
3134
|
+
{
|
|
3135
|
+
name: "flowFinished",
|
|
3136
|
+
detail: "the flow's stage is not done after generating",
|
|
3137
|
+
test: (s) => s.setup?.stage === "done"
|
|
3138
|
+
}
|
|
3139
|
+
]
|
|
3140
|
+
}
|
|
3141
|
+
},
|
|
3142
|
+
{
|
|
3143
|
+
id: "three-d-turntable",
|
|
3144
|
+
description: "Put a glTF on the timeline, spin its camera, then look",
|
|
3145
|
+
objective: "Put the 3D model asset 'asset_lantern_glb' on an overlay track for 4 seconds, make its camera go all the way round the model over that time, and then look at the frame in the middle to check the model is actually on screen.",
|
|
3146
|
+
createBridge: () => createTimelineToolBridge({ preview: true }),
|
|
3147
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
3148
|
+
expect: {
|
|
3149
|
+
requiredTools: [
|
|
3150
|
+
"ui_timeline_add_model3d_clip",
|
|
3151
|
+
"ui_timeline_animate_clip",
|
|
3152
|
+
"preview_timeline_frame"
|
|
3153
|
+
],
|
|
3154
|
+
noErrorResults: true,
|
|
3155
|
+
minToolCalls: 3,
|
|
3156
|
+
maxToolCalls: 14,
|
|
3157
|
+
finalState: [
|
|
3158
|
+
{
|
|
3159
|
+
name: "modelClipCarriesAnOrbit",
|
|
3160
|
+
detail: "no model3d clip holding the asset with a loop animation driving its camera",
|
|
3161
|
+
test: (s) => s.documentClips.some((clip) => clip.mediaType === "model3d" &&
|
|
3162
|
+
clip.currentAssetId === "asset_lantern_glb" &&
|
|
3163
|
+
clip.model3dStyle !== undefined &&
|
|
3164
|
+
(clip.animations ?? []).some((a) => a.role === "loop"))
|
|
3165
|
+
},
|
|
3166
|
+
{
|
|
3167
|
+
// The document can say a 3D clip exists; only the report says one
|
|
3168
|
+
// was drawn — which is the whole point of looking before stopping.
|
|
3169
|
+
name: "previewReportedTheModel",
|
|
3170
|
+
detail: "no preview reported a model3d layer",
|
|
3171
|
+
test: (s) => s.previewedLayerKinds.includes("model3d")
|
|
3172
|
+
}
|
|
3173
|
+
]
|
|
3174
|
+
}
|
|
3175
|
+
},
|
|
3176
|
+
{
|
|
3177
|
+
id: "trimmed-video-edit-candidate-apply",
|
|
3178
|
+
description: "Edit a trimmed source into an inactive candidate, inspect it, then apply it",
|
|
3179
|
+
objective: "The timeline contains a trimmed imported video clip. Edit that exact clip to make the station deserted at night, inspect the inactive candidate, then explicitly apply the returned take while keeping the current cut.",
|
|
3180
|
+
createBridge: () => createTimelineToolBridge({
|
|
3181
|
+
sequenceId: "sequence-trimmed-edit",
|
|
3182
|
+
sequence: {
|
|
3183
|
+
tracks: [
|
|
3184
|
+
{
|
|
3185
|
+
id: "track-video",
|
|
3186
|
+
name: "Video",
|
|
3187
|
+
type: "video",
|
|
3188
|
+
index: 0,
|
|
3189
|
+
visible: true,
|
|
3190
|
+
locked: false
|
|
3191
|
+
}
|
|
3192
|
+
],
|
|
3193
|
+
clips: [
|
|
3194
|
+
{
|
|
3195
|
+
id: "clip-trimmed",
|
|
3196
|
+
trackId: "track-video",
|
|
3197
|
+
name: "Station",
|
|
3198
|
+
startMs: 1200,
|
|
3199
|
+
durationMs: 4000,
|
|
3200
|
+
inPointMs: 40000,
|
|
3201
|
+
outPointMs: 44000,
|
|
3202
|
+
mediaType: "video",
|
|
3203
|
+
sourceType: "imported",
|
|
3204
|
+
status: "generated",
|
|
3205
|
+
locked: false,
|
|
3206
|
+
currentAssetId: "asset-original",
|
|
3207
|
+
activeTakeId: "take-original",
|
|
3208
|
+
versions: [
|
|
3209
|
+
{
|
|
3210
|
+
id: "take-original",
|
|
3211
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
3212
|
+
workflowUpdatedAt: "2026-01-01T00:00:00.000Z",
|
|
3213
|
+
jobId: "original-job",
|
|
3214
|
+
assetId: "asset-original",
|
|
3215
|
+
dependencyHash: "",
|
|
3216
|
+
paramOverridesSnapshot: {},
|
|
3217
|
+
durationMs: 4000,
|
|
3218
|
+
status: "success",
|
|
3219
|
+
source: "imported",
|
|
3220
|
+
sourceMapping: {
|
|
3221
|
+
inPointMs: 40000,
|
|
3222
|
+
outPointMs: 44000,
|
|
3223
|
+
speedMultiplier: 1,
|
|
3224
|
+
speedBaked: false
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
]
|
|
3228
|
+
}
|
|
3229
|
+
]
|
|
3230
|
+
}
|
|
3231
|
+
}),
|
|
3232
|
+
systemPrompt: TIMELINE_SYSTEM_PROMPT,
|
|
3233
|
+
expect: {
|
|
3234
|
+
requiredTools: [
|
|
3235
|
+
"ui_timeline_generatively_edit_clip",
|
|
3236
|
+
"ui_timeline_list_takes",
|
|
3237
|
+
"ui_timeline_apply_take"
|
|
3238
|
+
],
|
|
3239
|
+
ordering: [
|
|
3240
|
+
["ui_timeline_generatively_edit_clip", "ui_timeline_list_takes"],
|
|
3241
|
+
["ui_timeline_list_takes", "ui_timeline_apply_take"]
|
|
3242
|
+
],
|
|
3243
|
+
noErrorResults: true,
|
|
3244
|
+
minToolCalls: 3,
|
|
3245
|
+
maxToolCalls: 8,
|
|
3246
|
+
finalState: [
|
|
3247
|
+
{
|
|
3248
|
+
name: "appliedEditPreservesTheTimelineCut",
|
|
3249
|
+
detail: "the edit candidate was not applied to the trimmed clip with its timeline placement intact",
|
|
3250
|
+
test: (s) => {
|
|
3251
|
+
const clip = s.documentClips.find((candidate) => candidate.id === "clip-trimmed");
|
|
3252
|
+
const active = clip?.versions?.find((take) => take.id === clip.activeTakeId);
|
|
3253
|
+
return (clip?.startMs === 1200 &&
|
|
3254
|
+
clip.durationMs === 4000 &&
|
|
3255
|
+
clip.currentAssetId?.startsWith("generative://clip-trimmed/") ===
|
|
3256
|
+
true &&
|
|
3257
|
+
clip.inPointMs === 0 &&
|
|
3258
|
+
clip.outPointMs === 4000 &&
|
|
3259
|
+
active?.mediaEdit?.sourceContext.sourceStartMs === 40000 &&
|
|
3260
|
+
active.mediaEdit.sourceContext.sourceEndMs === 44000);
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
]
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
];
|
|
3267
|
+
//# sourceMappingURL=timeline.js.map
|