@hraness/slopcamera 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +60 -0
- package/PRIVACY.md +46 -0
- package/README.md +311 -0
- package/SECURITY.md +21 -0
- package/apps/desktop/README.md +966 -0
- package/apps/desktop/analysis/protocol.ts +223 -0
- package/apps/desktop/application/capability-binding.ts +594 -0
- package/apps/desktop/application/context.ts +97 -0
- package/apps/desktop/application/creative-iteration.ts +1385 -0
- package/apps/desktop/application/default-registry.ts +146 -0
- package/apps/desktop/application/directing-contract.ts +218 -0
- package/apps/desktop/application/directing-plan.ts +178 -0
- package/apps/desktop/application/errors.ts +18 -0
- package/apps/desktop/application/gateway-port.ts +867 -0
- package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
- package/apps/desktop/application/html-overlay-integrity.ts +337 -0
- package/apps/desktop/application/html-overlay-renderer.ts +45 -0
- package/apps/desktop/application/index.ts +13 -0
- package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
- package/apps/desktop/application/operation-effects.ts +94 -0
- package/apps/desktop/application/operation.ts +224 -0
- package/apps/desktop/application/operations/analysis/faces.ts +244 -0
- package/apps/desktop/application/operations/analysis/music.ts +248 -0
- package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
- package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
- package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
- package/apps/desktop/application/operations/analysis/shared.ts +202 -0
- package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
- package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
- package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
- package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
- package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
- package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
- package/apps/desktop/application/operations/gateway/image.ts +41 -0
- package/apps/desktop/application/operations/gateway/index.ts +17 -0
- package/apps/desktop/application/operations/gateway/shared.ts +122 -0
- package/apps/desktop/application/operations/gateway/speech.ts +41 -0
- package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
- package/apps/desktop/application/operations/gateway/video.ts +41 -0
- package/apps/desktop/application/operations/index.ts +31 -0
- package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
- package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
- package/apps/desktop/application/operations/iteration/select.ts +155 -0
- package/apps/desktop/application/operations/iteration/shared.ts +250 -0
- package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
- package/apps/desktop/application/operations/media/binding.ts +79 -0
- package/apps/desktop/application/operations/media/capabilities.ts +112 -0
- package/apps/desktop/application/operations/media/color-grade.ts +343 -0
- package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
- package/apps/desktop/application/operations/media/index.ts +8 -0
- package/apps/desktop/application/operations/media/ingest.ts +303 -0
- package/apps/desktop/application/operations/media/overlay.ts +729 -0
- package/apps/desktop/application/operations/media/shared.ts +845 -0
- package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
- package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
- package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
- package/apps/desktop/application/operations/project/snapshot.ts +95 -0
- package/apps/desktop/application/operations/recording/index.ts +5 -0
- package/apps/desktop/application/operations/recording/pause.ts +34 -0
- package/apps/desktop/application/operations/recording/resume.ts +34 -0
- package/apps/desktop/application/operations/recording/shared.ts +199 -0
- package/apps/desktop/application/operations/recording/start.ts +169 -0
- package/apps/desktop/application/operations/recording/stop.ts +34 -0
- package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
- package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
- package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
- package/apps/desktop/application/operations/render/project-plan.ts +939 -0
- package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
- package/apps/desktop/application/operations/render/project.ts +1922 -0
- package/apps/desktop/application/operations/shared.ts +67 -0
- package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
- package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
- package/apps/desktop/application/operations/spatial-project.ts +209 -0
- package/apps/desktop/application/operations/spatial-render.ts +415 -0
- package/apps/desktop/application/operations/spatial-scene.ts +97 -0
- package/apps/desktop/application/operations/studio.ts +143 -0
- package/apps/desktop/application/output-publication-lease.ts +163 -0
- package/apps/desktop/application/project-publication-lease.ts +233 -0
- package/apps/desktop/application/project-store.ts +196 -0
- package/apps/desktop/application/receipts.ts +533 -0
- package/apps/desktop/application/registry.ts +182 -0
- package/apps/desktop/application/spatial-assets.ts +675 -0
- package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
- package/apps/desktop/application/spatial-project-authority.ts +35 -0
- package/apps/desktop/application/spatial-project-lease.ts +126 -0
- package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
- package/apps/desktop/application/spatial-project-store.ts +286 -0
- package/apps/desktop/application/spatial-render-projection.ts +201 -0
- package/apps/desktop/application/spatial-render.ts +677 -0
- package/apps/desktop/application/spatial-spz.ts +44 -0
- package/apps/desktop/application/spatial-world-import.ts +135 -0
- package/apps/desktop/application/spatial-world-provenance.ts +46 -0
- package/apps/desktop/application/studio-port.ts +58 -0
- package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
- package/apps/desktop/capture/protocol.ts +689 -0
- package/apps/desktop/cli/analyzer.ts +406 -0
- package/apps/desktop/cli/args.ts +3181 -0
- package/apps/desktop/cli/asset-ingest.ts +747 -0
- package/apps/desktop/cli/atomic-render-effects.ts +210 -0
- package/apps/desktop/cli/atomic-render-platform.ts +205 -0
- package/apps/desktop/cli/atomic-render.ts +50 -0
- package/apps/desktop/cli/audio-analysis.ts +252 -0
- package/apps/desktop/cli/bounded-map.ts +39 -0
- package/apps/desktop/cli/bundle-service.ts +256 -0
- package/apps/desktop/cli/capabilities.ts +212 -0
- package/apps/desktop/cli/capture-bundle.ts +1117 -0
- package/apps/desktop/cli/command-host-resources.ts +251 -0
- package/apps/desktop/cli/commands.ts +6836 -0
- package/apps/desktop/cli/directing-blob.ts +291 -0
- package/apps/desktop/cli/directing-media.ts +517 -0
- package/apps/desktop/cli/directing-quote.ts +89 -0
- package/apps/desktop/cli/directing-service.ts +288 -0
- package/apps/desktop/cli/emoji-assets.ts +538 -0
- package/apps/desktop/cli/errors.ts +29 -0
- package/apps/desktop/cli/face-analysis-service.ts +940 -0
- package/apps/desktop/cli/filter-script.ts +76 -0
- package/apps/desktop/cli/gateway-application-port.ts +1142 -0
- package/apps/desktop/cli/gateway-credential.ts +81 -0
- package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
- package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
- package/apps/desktop/cli/gateway-media-service.ts +3106 -0
- package/apps/desktop/cli/gateway-media-signature.ts +122 -0
- package/apps/desktop/cli/gateway-provider-options.ts +562 -0
- package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
- package/apps/desktop/cli/help.ts +501 -0
- package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
- package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
- package/apps/desktop/cli/io.ts +290 -0
- package/apps/desktop/cli/main.ts +123 -0
- package/apps/desktop/cli/media-effects-service.ts +885 -0
- package/apps/desktop/cli/media-ingest-model.ts +364 -0
- package/apps/desktop/cli/media-ingest-platform.ts +227 -0
- package/apps/desktop/cli/media-ingest-program.ts +200 -0
- package/apps/desktop/cli/media-ingest.ts +30 -0
- package/apps/desktop/cli/music-analysis-service.ts +434 -0
- package/apps/desktop/cli/mutation-lock.ts +530 -0
- package/apps/desktop/cli/native-assets.d.ts +12 -0
- package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
- package/apps/desktop/cli/overlay-playback.ts +177 -0
- package/apps/desktop/cli/paths.ts +265 -0
- package/apps/desktop/cli/portable-surface.ts +242 -0
- package/apps/desktop/cli/project-camera-receipt.ts +112 -0
- package/apps/desktop/cli/project-face-camera.ts +394 -0
- package/apps/desktop/cli/project-inactivity-service.ts +552 -0
- package/apps/desktop/cli/project-media-integrity.ts +121 -0
- package/apps/desktop/cli/project-renderer.ts +1526 -0
- package/apps/desktop/cli/project-service.ts +519 -0
- package/apps/desktop/cli/project-state-transaction.ts +520 -0
- package/apps/desktop/cli/query-limits.ts +1 -0
- package/apps/desktop/cli/recording-controller.ts +1567 -0
- package/apps/desktop/cli/recording-daemon.ts +931 -0
- package/apps/desktop/cli/recording-ref.ts +56 -0
- package/apps/desktop/cli/renamed-environment.ts +6 -0
- package/apps/desktop/cli/renderer.ts +1439 -0
- package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
- package/apps/desktop/cli/scene-analysis-service.ts +879 -0
- package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
- package/apps/desktop/cli/spatial-project-service.ts +90 -0
- package/apps/desktop/cli/spatial-scene-service.ts +105 -0
- package/apps/desktop/cli/spatial-world-service.ts +39 -0
- package/apps/desktop/cli/speech-analysis-service.ts +682 -0
- package/apps/desktop/cli/studio-assemble.ts +58 -0
- package/apps/desktop/cli/studio-command.ts +60 -0
- package/apps/desktop/cli/studio-custody.ts +60 -0
- package/apps/desktop/cli/studio-encode.ts +216 -0
- package/apps/desktop/cli/studio-exr.ts +52 -0
- package/apps/desktop/cli/studio-files.ts +179 -0
- package/apps/desktop/cli/studio-output-validation.ts +162 -0
- package/apps/desktop/cli/studio-process.ts +145 -0
- package/apps/desktop/cli/studio-runtime.ts +77 -0
- package/apps/desktop/cli/studio-scaffold.ts +70 -0
- package/apps/desktop/cli/studio-service.ts +301 -0
- package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
- package/apps/desktop/cli/studio-template-names.ts +3 -0
- package/apps/desktop/cli/studio-workflow.ts +19 -0
- package/apps/desktop/cli/time.ts +25 -0
- package/apps/desktop/cli/workflow-code.ts +584 -0
- package/apps/desktop/cli/workflow-runs.ts +576 -0
- package/apps/desktop/code/advanced.ts +139 -0
- package/apps/desktop/code/application-node-planner.ts +1288 -0
- package/apps/desktop/code/compiler.ts +329 -0
- package/apps/desktop/code/contracts.ts +13 -0
- package/apps/desktop/code/creative-recipes.ts +94 -0
- package/apps/desktop/code/define-workflow.ts +115 -0
- package/apps/desktop/code/editing.ts +284 -0
- package/apps/desktop/code/file-candidate-provenance.ts +420 -0
- package/apps/desktop/code/file-candidate.ts +69 -0
- package/apps/desktop/code/graph-builder.ts +10 -0
- package/apps/desktop/code/host-resource-policy.ts +55 -0
- package/apps/desktop/code/plan-contracts.ts +159 -0
- package/apps/desktop/code/planning.ts +361 -0
- package/apps/desktop/code/public.ts +134 -0
- package/apps/desktop/code/reference-projection.ts +62 -0
- package/apps/desktop/code/run-contracts.ts +466 -0
- package/apps/desktop/code/run-store.ts +2478 -0
- package/apps/desktop/code/runtime-identity.ts +328 -0
- package/apps/desktop/code/scheduler.ts +3067 -0
- package/apps/desktop/code/semantic-builder.ts +1436 -0
- package/apps/desktop/code/source-bundle.ts +1641 -0
- package/apps/desktop/code/source-typecheck.ts +211 -0
- package/apps/desktop/code/testing.ts +79 -0
- package/apps/desktop/code/worker-client.ts +1675 -0
- package/apps/desktop/code/worker-effects.ts +141 -0
- package/apps/desktop/code/worker-entry.ts +435 -0
- package/apps/desktop/code/worker-lease-guardian.ts +179 -0
- package/apps/desktop/code/worker-process-identity.ts +484 -0
- package/apps/desktop/code/worker-protocol.ts +211 -0
- package/apps/desktop/code/workflow-effects.ts +132 -0
- package/apps/desktop/contracts/analysis.ts +949 -0
- package/apps/desktop/contracts/edit.ts +576 -0
- package/apps/desktop/contracts/index.ts +9 -0
- package/apps/desktop/contracts/media-effects.ts +139 -0
- package/apps/desktop/contracts/project-render.ts +644 -0
- package/apps/desktop/contracts/project.ts +740 -0
- package/apps/desktop/contracts/recording.ts +1434 -0
- package/apps/desktop/contracts/render.ts +334 -0
- package/apps/desktop/contracts/runtime.ts +155 -0
- package/apps/desktop/contracts/spatial-compositor.ts +58 -0
- package/apps/desktop/contracts/spatial-project.ts +159 -0
- package/apps/desktop/contracts/spatial-world.ts +48 -0
- package/apps/desktop/contracts/video-effects.ts +120 -0
- package/apps/desktop/core/alignment-apply.ts +400 -0
- package/apps/desktop/core/audio-alignment.ts +466 -0
- package/apps/desktop/core/auto-zoom.ts +209 -0
- package/apps/desktop/core/canonical-json.ts +14 -0
- package/apps/desktop/core/emoji.ts +210 -0
- package/apps/desktop/core/events.ts +409 -0
- package/apps/desktop/core/face-framing.ts +533 -0
- package/apps/desktop/core/face-tracks.ts +326 -0
- package/apps/desktop/core/inactivity.ts +137 -0
- package/apps/desktop/core/index.ts +30 -0
- package/apps/desktop/core/intervals.ts +123 -0
- package/apps/desktop/core/montage.ts +236 -0
- package/apps/desktop/core/music-analysis.ts +518 -0
- package/apps/desktop/core/plan.ts +244 -0
- package/apps/desktop/core/project-auto-zoom.ts +75 -0
- package/apps/desktop/core/project-camera-spatial.ts +676 -0
- package/apps/desktop/core/project-camera.ts +403 -0
- package/apps/desktop/core/project-captions.ts +888 -0
- package/apps/desktop/core/project-inactivity.ts +125 -0
- package/apps/desktop/core/project-metadata.ts +927 -0
- package/apps/desktop/core/project-plan.ts +375 -0
- package/apps/desktop/core/project-render-plan.ts +303 -0
- package/apps/desktop/core/project-time.ts +469 -0
- package/apps/desktop/core/render-plan.ts +442 -0
- package/apps/desktop/core/scene-cache.ts +174 -0
- package/apps/desktop/core/scene-sampling.ts +448 -0
- package/apps/desktop/core/spatial-compositor.ts +22 -0
- package/apps/desktop/core/spatial-durability.ts +99 -0
- package/apps/desktop/core/spatial-project.ts +169 -0
- package/apps/desktop/core/spatial-shot-clock.ts +32 -0
- package/apps/desktop/core/speech-analysis.ts +333 -0
- package/apps/desktop/core/storage.ts +970 -0
- package/apps/desktop/core/time-map.ts +91 -0
- package/apps/desktop/core/time.ts +61 -0
- package/apps/desktop/core/video-effects.ts +660 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
- package/apps/desktop/dist/cli/main.js +5086 -0
- package/apps/desktop/html-overlay/catalog.ts +171 -0
- package/apps/desktop/html-overlay/contracts.ts +372 -0
- package/apps/desktop/html-overlay/execution-profile.ts +77 -0
- package/apps/desktop/html-overlay/index.ts +98 -0
- package/apps/desktop/html-overlay/libraries.ts +263 -0
- package/apps/desktop/html-overlay/random.ts +49 -0
- package/apps/desktop/html-overlay/runtime.ts +813 -0
- package/apps/desktop/html-overlay/scaffolds.ts +792 -0
- package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
- package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
- package/apps/desktop/html-overlay/spatial.ts +720 -0
- package/apps/desktop/studio/assets/README.md +39 -0
- package/apps/desktop/studio/assets/contracts.ts +129 -0
- package/apps/desktop/studio/assets/network.ts +64 -0
- package/apps/desktop/studio/assets/poly-haven.ts +148 -0
- package/apps/desktop/studio/drivers/blender_driver.py +466 -0
- package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
- package/apps/desktop/studio/education/driver.py +308 -0
- package/apps/desktop/studio/education/lesson.py +180 -0
- package/apps/desktop/studio/education/toolkit.py +249 -0
- package/apps/desktop/workflows/chaptered-demo.ts +48 -0
- package/apps/desktop/workflows/creative-iteration.ts +107 -0
- package/apps/desktop/workflows/creative-selection.ts +92 -0
- package/apps/desktop/workflows/fragments.ts +486 -0
- package/apps/desktop/workflows/index.ts +93 -0
- package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
- package/apps/desktop/workflows/scene-direction.ts +12 -0
- package/apps/desktop/workflows/social-variants.ts +75 -0
- package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
- package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
- package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
- package/dist/cli.js +479 -0
- package/dist/code/advanced.js +270 -0
- package/dist/code/index.js +2240 -0
- package/dist/generate.js +24 -0
- package/dist/host-resources.js +26 -0
- package/dist/index-42zsesc1.js +2257 -0
- package/dist/index-7308egqr.js +48 -0
- package/dist/index-8txs6fkn.js +712 -0
- package/dist/index-mcy8z0br.js +192 -0
- package/dist/index-p63wavx0.js +2029 -0
- package/dist/index-r7gdhmsp.js +424 -0
- package/dist/index-sh6xbav6.js +1219 -0
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index-z7239b4h.js +2363 -0
- package/dist/index-zfeg9hmw.js +2013 -0
- package/dist/index.js +241 -0
- package/dist/operations.js +29 -0
- package/dist/skill-install-dh0ntqba.js +10 -0
- package/dist/vectorize/worker.js +142 -0
- package/dist/workflow.js +16 -0
- package/docs/README.md +38 -0
- package/docs/studio.md +215 -0
- package/examples/studio/blender/README.md +26 -0
- package/examples/studio/blender/character.py +152 -0
- package/examples/studio/blender/cloth.py +101 -0
- package/examples/studio/blender/color_chart.py +29 -0
- package/examples/studio/blender/fluid.py +78 -0
- package/examples/studio/blender/import_model.py +74 -0
- package/examples/studio/blender/product.py +53 -0
- package/examples/studio/blender/shaded_street.py +481 -0
- package/examples/studio/blender/studio_scene.py +147 -0
- package/examples/studio/cadquery/README.md +13 -0
- package/examples/studio/cadquery/bracket.py +36 -0
- package/examples/studio/cadquery/import_step.py +12 -0
- package/examples/studio/education/README.md +46 -0
- package/examples/studio/education/lesson.json +99 -0
- package/examples/studio/education/scene.py +7 -0
- package/examples/studio/hybrid-scene.ts +96 -0
- package/examples/studio/native-workflow.ts +14 -0
- package/examples/studio/vgpu/README.md +64 -0
- package/examples/studio/vgpu/heat-field.mjs +290 -0
- package/package.json +304 -0
- package/packages/scene/src/index.ts +1 -0
- package/packages/scene/src/scene.ts +215 -0
- package/schema/diagram.schema.json +334 -0
- package/skills/slopcamera/SKILL.md +45 -0
- package/skills/slopcamera/agents/openai.yaml +4 -0
- package/skills/slopcamera/references/customization.md +77 -0
- package/skills/slopcamera/references/diagrams.md +153 -0
- package/skills/slopcamera/references/directed-scenes.md +42 -0
- package/skills/slopcamera/references/directing-video.md +18 -0
- package/skills/slopcamera/references/educational-video.md +26 -0
- package/skills/slopcamera/references/gateway-media.md +134 -0
- package/skills/slopcamera/references/install.md +62 -0
- package/skills/slopcamera/references/native-studio.md +31 -0
- package/skills/slopcamera/references/reference-led-3d.md +140 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
- package/skills/slopcamera/references/vectorization.md +29 -0
- package/skills/slopcamera/references/video-projects.md +187 -0
- package/skills/slopcamera/references/visual-communication.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +45 -0
- package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
- package/src/artifacts.ts +106 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
- package/src/assets.d.ts +9 -0
- package/src/cli.ts +601 -0
- package/src/cloud-errors.ts +26 -0
- package/src/code/advanced.ts +10 -0
- package/src/code/boundary.ts +21 -0
- package/src/code/canonical-json.ts +129 -0
- package/src/code/compiler.ts +953 -0
- package/src/code/contracts.ts +576 -0
- package/src/code/define-workflow.ts +334 -0
- package/src/code/errors.ts +41 -0
- package/src/code/graph-builder.ts +945 -0
- package/src/code/index.ts +121 -0
- package/src/code/json-snapshot.ts +899 -0
- package/src/code/json-utf8.ts +88 -0
- package/src/code/portable-builder.ts +136 -0
- package/src/code/projection.ts +378 -0
- package/src/code/public-operations.ts +450 -0
- package/src/code/runtime.ts +580 -0
- package/src/code/sha256.ts +36 -0
- package/src/config.ts +180 -0
- package/src/desktop.ts +253 -0
- package/src/fs.ts +10 -0
- package/src/generate.ts +704 -0
- package/src/host-resource-posix.ts +161 -0
- package/src/host-resources.ts +1749 -0
- package/src/icons.ts +58 -0
- package/src/index.ts +200 -0
- package/src/label-layout.ts +130 -0
- package/src/layout.ts +413 -0
- package/src/lint.ts +127 -0
- package/src/mcp/boundary.ts +354 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/server.ts +272 -0
- package/src/mcp/tools.ts +963 -0
- package/src/mcp/types.ts +71 -0
- package/src/operations.ts +775 -0
- package/src/parse.ts +861 -0
- package/src/process-environment.ts +17 -0
- package/src/render.ts +437 -0
- package/src/skill-install.ts +49 -0
- package/src/spatial-scene/authoring.ts +27 -0
- package/src/spatial-scene/camera-track.ts +83 -0
- package/src/spatial-scene/contracts.ts +277 -0
- package/src/spatial-scene/evaluate.ts +145 -0
- package/src/spatial-scene/gltf.ts +558 -0
- package/src/spatial-scene/identity.ts +260 -0
- package/src/spatial-scene/index.ts +10 -0
- package/src/spatial-scene/inspect.ts +68 -0
- package/src/spatial-scene/math.ts +280 -0
- package/src/spatial-scene/patch.ts +131 -0
- package/src/spatial-scene/time.ts +53 -0
- package/src/studio/contracts.ts +129 -0
- package/src/studio/index.ts +2 -0
- package/src/studio/plan.ts +156 -0
- package/src/studio/shared.ts +41 -0
- package/src/theme.ts +68 -0
- package/src/tldr.ts +474 -0
- package/src/types.ts +212 -0
- package/src/vectorize/archive.ts +101 -0
- package/src/vectorize/command.ts +797 -0
- package/src/vectorize/index.ts +27 -0
- package/src/vectorize/limits.ts +61 -0
- package/src/vectorize/metrics.ts +351 -0
- package/src/vectorize/pixels.ts +323 -0
- package/src/vectorize/supervisor.ts +339 -0
- package/src/vectorize/svg.ts +188 -0
- package/src/vectorize/tool.ts +542 -0
- package/src/vectorize/types.ts +113 -0
- package/src/vectorize/vectorize.ts +634 -0
- package/src/vectorize/worker-protocol.ts +40 -0
- package/src/vectorize/worker.ts +209 -0
- package/src/version.ts +1 -0
- package/src/workflow.ts +409 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
chmod,
|
|
5
|
+
lstat,
|
|
6
|
+
mkdir,
|
|
7
|
+
open,
|
|
8
|
+
rename,
|
|
9
|
+
rm,
|
|
10
|
+
} from "node:fs/promises";
|
|
11
|
+
import { basename, isAbsolute, join, relative, sep } from "node:path";
|
|
12
|
+
|
|
13
|
+
import { canonicalJson } from "../core/canonical-json";
|
|
14
|
+
import type {
|
|
15
|
+
GatewayJsonValue,
|
|
16
|
+
GatewayMediaKind,
|
|
17
|
+
} from "./gateway-media-catalog";
|
|
18
|
+
import { gatewayMediaBytesMatchType } from "./gateway-media-signature";
|
|
19
|
+
|
|
20
|
+
const MAXIMUM_GENERATED_FILE_BYTES = 512 * 1024 * 1024;
|
|
21
|
+
const MAXIMUM_GENERATED_TOTAL_BYTES = 1024 * 1024 * 1024;
|
|
22
|
+
const MAXIMUM_OUTPUTS = 32;
|
|
23
|
+
const MAXIMUM_RECEIPT_BYTES = 256 * 1024;
|
|
24
|
+
|
|
25
|
+
export interface GatewayGeneratedFile {
|
|
26
|
+
readonly mediaType: string;
|
|
27
|
+
readonly uint8Array: Uint8Array;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface GatewayMediaInputDigest {
|
|
31
|
+
readonly bytes: number;
|
|
32
|
+
readonly mediaType: string;
|
|
33
|
+
readonly role: string;
|
|
34
|
+
readonly sha256: string;
|
|
35
|
+
readonly source: "inline" | "url";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GatewayMediaReceiptOutput {
|
|
39
|
+
readonly bytes: number;
|
|
40
|
+
readonly file: string;
|
|
41
|
+
readonly mediaType: string;
|
|
42
|
+
readonly sha256: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface GatewayMediaRoutingReceipt {
|
|
46
|
+
readonly attemptCount: number;
|
|
47
|
+
readonly attempts: readonly Readonly<{
|
|
48
|
+
readonly configuredTimeoutMs?: number;
|
|
49
|
+
readonly credentialType?: string;
|
|
50
|
+
readonly error?: string;
|
|
51
|
+
readonly model?: string;
|
|
52
|
+
readonly provider?: string;
|
|
53
|
+
readonly providerTimeout?: boolean;
|
|
54
|
+
readonly statusCode?: number;
|
|
55
|
+
readonly success?: boolean;
|
|
56
|
+
}>[];
|
|
57
|
+
readonly attemptsTruncated: boolean;
|
|
58
|
+
readonly clientMaxRetries: 0;
|
|
59
|
+
readonly gatewayProviderFailover: "may-attempt-multiple-providers";
|
|
60
|
+
readonly generationId?: string;
|
|
61
|
+
readonly providerCount: number;
|
|
62
|
+
readonly providers: readonly string[];
|
|
63
|
+
readonly providersTruncated: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface GatewayMediaReceipt {
|
|
67
|
+
readonly catalog: Readonly<{
|
|
68
|
+
snapshotId: string;
|
|
69
|
+
status: "fresh" | "stale";
|
|
70
|
+
}>;
|
|
71
|
+
readonly createdAt: string;
|
|
72
|
+
readonly inputs: readonly GatewayMediaInputDigest[];
|
|
73
|
+
readonly kind:
|
|
74
|
+
| "slopcamera.gateway-media-receipt"
|
|
75
|
+
| "studio.gateway-media-receipt";
|
|
76
|
+
readonly localValidation: Readonly<{
|
|
77
|
+
readonly decodeValidatedOutputs: number;
|
|
78
|
+
readonly failureSha256?: string;
|
|
79
|
+
readonly signatureOnlyOutputs: number;
|
|
80
|
+
readonly status: "decode-failed" | "decode-passed" | "signature-only";
|
|
81
|
+
}>;
|
|
82
|
+
readonly model: string;
|
|
83
|
+
readonly nextCommands: readonly string[];
|
|
84
|
+
readonly operation: "image.generate" | "speech.generate" | "transcription.create" | "video.generate";
|
|
85
|
+
readonly outputs: readonly GatewayMediaReceiptOutput[];
|
|
86
|
+
readonly request: Readonly<Record<string, GatewayJsonValue>>;
|
|
87
|
+
readonly routing: GatewayMediaRoutingReceipt;
|
|
88
|
+
readonly sampleFulfillment?: Readonly<{
|
|
89
|
+
readonly produced: number;
|
|
90
|
+
readonly requested: number;
|
|
91
|
+
readonly status: "complete" | "overproduced" | "partial";
|
|
92
|
+
}>;
|
|
93
|
+
readonly schemaVersion: 1;
|
|
94
|
+
readonly warnings: readonly string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface GatewayMediaArtifactBundle {
|
|
98
|
+
readonly directory: string;
|
|
99
|
+
readonly outputs: readonly Readonly<GatewayMediaReceiptOutput & { readonly path: string }>[];
|
|
100
|
+
readonly receipt: GatewayMediaReceipt;
|
|
101
|
+
readonly receiptPath: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface GatewayMediaArtifactStore {
|
|
105
|
+
commit(input: Readonly<{
|
|
106
|
+
catalog: GatewayMediaReceipt["catalog"];
|
|
107
|
+
createdAt: string;
|
|
108
|
+
files: readonly GatewayGeneratedFile[];
|
|
109
|
+
inputs: readonly GatewayMediaInputDigest[];
|
|
110
|
+
mediaKind: GatewayMediaKind;
|
|
111
|
+
model: string;
|
|
112
|
+
operation: GatewayMediaReceipt["operation"];
|
|
113
|
+
request: Readonly<Record<string, GatewayJsonValue>>;
|
|
114
|
+
routing: GatewayMediaRoutingReceipt;
|
|
115
|
+
signal: AbortSignal;
|
|
116
|
+
sampleFulfillment?: Readonly<{
|
|
117
|
+
readonly produced: number;
|
|
118
|
+
readonly requested: number;
|
|
119
|
+
}>;
|
|
120
|
+
warnings: readonly string[];
|
|
121
|
+
}>): Promise<GatewayMediaArtifactBundle>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type GatewayMediaArtifactErrorCode =
|
|
125
|
+
| "artifact-invalid"
|
|
126
|
+
| "artifact-unavailable"
|
|
127
|
+
| "output-too-large"
|
|
128
|
+
| "unsafe-output";
|
|
129
|
+
|
|
130
|
+
const ERROR_MESSAGES: Readonly<Record<GatewayMediaArtifactErrorCode, string>> = {
|
|
131
|
+
"artifact-invalid": "The generated Gateway media output is invalid.",
|
|
132
|
+
"artifact-unavailable": "The generated Gateway media output could not be saved.",
|
|
133
|
+
"output-too-large": "The generated Gateway media output exceeds its local size limit.",
|
|
134
|
+
"unsafe-output": "The Gateway media output directory is unsafe.",
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export class GatewayMediaArtifactError extends Error {
|
|
138
|
+
readonly code: GatewayMediaArtifactErrorCode;
|
|
139
|
+
|
|
140
|
+
constructor(code: GatewayMediaArtifactErrorCode) {
|
|
141
|
+
super(ERROR_MESSAGES[code]);
|
|
142
|
+
this.code = code;
|
|
143
|
+
this.name = "GatewayMediaArtifactError";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function canonicalTimestamp(value: string): string {
|
|
148
|
+
const milliseconds = Date.parse(value);
|
|
149
|
+
if (!Number.isFinite(milliseconds) || new Date(milliseconds).toISOString() !== value) {
|
|
150
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
151
|
+
}
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function validateMediaType(value: string): string {
|
|
156
|
+
if (
|
|
157
|
+
value.length < 3
|
|
158
|
+
|| value.length > 128
|
|
159
|
+
|| !/^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/iu.test(value)
|
|
160
|
+
) {
|
|
161
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
162
|
+
}
|
|
163
|
+
return value.toLocaleLowerCase("en-US");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function extensionForMediaType(mediaType: string): string {
|
|
167
|
+
const extensions: Readonly<Record<string, string>> = {
|
|
168
|
+
"audio/aac": ".aac",
|
|
169
|
+
"audio/aiff": ".aiff",
|
|
170
|
+
"audio/alaw": ".alaw",
|
|
171
|
+
"audio/basic": ".au",
|
|
172
|
+
"audio/flac": ".flac",
|
|
173
|
+
"audio/l16": ".pcm",
|
|
174
|
+
"audio/m4a": ".m4a",
|
|
175
|
+
"audio/mp4": ".m4a",
|
|
176
|
+
"audio/mpeg": ".mp3",
|
|
177
|
+
"audio/mulaw": ".mulaw",
|
|
178
|
+
"audio/ogg": ".ogg",
|
|
179
|
+
"audio/opus": ".opus",
|
|
180
|
+
"audio/pcm": ".pcm",
|
|
181
|
+
"audio/wav": ".wav",
|
|
182
|
+
"audio/webm": ".weba",
|
|
183
|
+
"audio/x-wav": ".wav",
|
|
184
|
+
"application/json": ".json",
|
|
185
|
+
"application/x-subrip": ".srt",
|
|
186
|
+
"image/avif": ".avif",
|
|
187
|
+
"image/bmp": ".bmp",
|
|
188
|
+
"image/gif": ".gif",
|
|
189
|
+
"image/jpeg": ".jpg",
|
|
190
|
+
"image/png": ".png",
|
|
191
|
+
"image/svg+xml": ".svg",
|
|
192
|
+
"image/tiff": ".tiff",
|
|
193
|
+
"image/webp": ".webp",
|
|
194
|
+
"text/plain": ".txt",
|
|
195
|
+
"text/vtt": ".vtt",
|
|
196
|
+
"video/mp4": ".mp4",
|
|
197
|
+
"video/mpeg": ".mpeg",
|
|
198
|
+
"video/quicktime": ".mov",
|
|
199
|
+
"video/webm": ".webm",
|
|
200
|
+
"video/x-matroska": ".mkv",
|
|
201
|
+
"video/x-msvideo": ".avi",
|
|
202
|
+
};
|
|
203
|
+
return extensions[mediaType] ?? ".bin";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function boundedWarning(value: string): string {
|
|
207
|
+
const normalized = [...value].map((character) => {
|
|
208
|
+
const codePoint = character.codePointAt(0);
|
|
209
|
+
return codePoint !== undefined && (
|
|
210
|
+
codePoint <= 8
|
|
211
|
+
|| codePoint === 11
|
|
212
|
+
|| codePoint === 12
|
|
213
|
+
|| (codePoint >= 14 && codePoint <= 31)
|
|
214
|
+
|| codePoint === 127
|
|
215
|
+
)
|
|
216
|
+
? " "
|
|
217
|
+
: character;
|
|
218
|
+
}).join("");
|
|
219
|
+
return normalized.slice(0, 2_000);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function validateDigest(value: GatewayMediaInputDigest): GatewayMediaInputDigest {
|
|
223
|
+
if (
|
|
224
|
+
!Number.isSafeInteger(value.bytes)
|
|
225
|
+
|| value.bytes < 0
|
|
226
|
+
|| !/^[a-f0-9]{64}$/u.test(value.sha256)
|
|
227
|
+
|| value.role.length < 1
|
|
228
|
+
|| value.role.length > 128
|
|
229
|
+
|| (value.source !== "inline" && value.source !== "url")
|
|
230
|
+
|| (value.source === "inline" && value.bytes < 1)
|
|
231
|
+
|| (value.source === "url" && value.bytes !== 0)
|
|
232
|
+
) {
|
|
233
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
bytes: value.bytes,
|
|
237
|
+
mediaType: validateMediaType(value.mediaType),
|
|
238
|
+
role: value.role,
|
|
239
|
+
sha256: value.sha256,
|
|
240
|
+
source: value.source,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
245
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function validateRouting(value: unknown): GatewayMediaRoutingReceipt {
|
|
249
|
+
const boundedString = (candidate: unknown): string | undefined => {
|
|
250
|
+
if (candidate === undefined) return undefined;
|
|
251
|
+
if (
|
|
252
|
+
typeof candidate !== "string"
|
|
253
|
+
|| candidate.length < 1
|
|
254
|
+
|| candidate.length > 256
|
|
255
|
+
) {
|
|
256
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
257
|
+
}
|
|
258
|
+
return boundedWarning(candidate);
|
|
259
|
+
};
|
|
260
|
+
if (!isRecord(value)) {
|
|
261
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
262
|
+
}
|
|
263
|
+
const attemptsValue = value.attempts;
|
|
264
|
+
const providersValue = value.providers;
|
|
265
|
+
if (
|
|
266
|
+
!Array.isArray(attemptsValue)
|
|
267
|
+
|| attemptsValue.length > 32
|
|
268
|
+
|| !Number.isSafeInteger(value.attemptCount)
|
|
269
|
+
|| (value.attemptCount as number) < attemptsValue.length
|
|
270
|
+
|| (value.attemptCount as number) > 1_000_000
|
|
271
|
+
|| typeof value.attemptsTruncated !== "boolean"
|
|
272
|
+
|| value.attemptsTruncated
|
|
273
|
+
!== ((value.attemptCount as number) !== attemptsValue.length)
|
|
274
|
+
|| value.clientMaxRetries !== 0
|
|
275
|
+
|| value.gatewayProviderFailover !== "may-attempt-multiple-providers"
|
|
276
|
+
|| !Array.isArray(providersValue)
|
|
277
|
+
|| providersValue.length > 32
|
|
278
|
+
|| !Number.isSafeInteger(value.providerCount)
|
|
279
|
+
|| (value.providerCount as number) < providersValue.length
|
|
280
|
+
|| (value.providerCount as number) > 1_000_000
|
|
281
|
+
|| typeof value.providersTruncated !== "boolean"
|
|
282
|
+
|| value.providersTruncated
|
|
283
|
+
!== ((value.providerCount as number) !== providersValue.length)
|
|
284
|
+
) {
|
|
285
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
286
|
+
}
|
|
287
|
+
const providers = providersValue.map((provider) => {
|
|
288
|
+
const parsed = boundedString(provider);
|
|
289
|
+
if (parsed === undefined) {
|
|
290
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
291
|
+
}
|
|
292
|
+
return parsed;
|
|
293
|
+
});
|
|
294
|
+
const attempts = attemptsValue.map((candidate) => {
|
|
295
|
+
if (!isRecord(candidate)) {
|
|
296
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
297
|
+
}
|
|
298
|
+
const configuredTimeoutMs = candidate.configuredTimeoutMs;
|
|
299
|
+
const credentialType = boundedString(candidate.credentialType);
|
|
300
|
+
const error = boundedString(candidate.error);
|
|
301
|
+
const model = boundedString(candidate.model);
|
|
302
|
+
const provider = boundedString(candidate.provider);
|
|
303
|
+
const statusCode = candidate.statusCode;
|
|
304
|
+
if (
|
|
305
|
+
(
|
|
306
|
+
configuredTimeoutMs !== undefined
|
|
307
|
+
&& (
|
|
308
|
+
!Number.isSafeInteger(configuredTimeoutMs)
|
|
309
|
+
|| (configuredTimeoutMs as number) < 0
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
|| (
|
|
313
|
+
candidate.credentialType !== undefined
|
|
314
|
+
&& credentialType === undefined
|
|
315
|
+
)
|
|
316
|
+
|| (
|
|
317
|
+
candidate.error !== undefined
|
|
318
|
+
&& (
|
|
319
|
+
error === undefined
|
|
320
|
+
|| !/^sha256:[a-f0-9]{64}$/u.test(error)
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|| (candidate.model !== undefined && model === undefined)
|
|
324
|
+
|| (candidate.provider !== undefined && provider === undefined)
|
|
325
|
+
|| (
|
|
326
|
+
candidate.providerTimeout !== undefined
|
|
327
|
+
&& typeof candidate.providerTimeout !== "boolean"
|
|
328
|
+
)
|
|
329
|
+
|| (
|
|
330
|
+
statusCode !== undefined
|
|
331
|
+
&& (
|
|
332
|
+
!Number.isSafeInteger(statusCode)
|
|
333
|
+
|| (statusCode as number) < 0
|
|
334
|
+
|| (statusCode as number) > 999
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|| (
|
|
338
|
+
candidate.success !== undefined
|
|
339
|
+
&& typeof candidate.success !== "boolean"
|
|
340
|
+
)
|
|
341
|
+
) {
|
|
342
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
...(configuredTimeoutMs === undefined
|
|
346
|
+
? {}
|
|
347
|
+
: { configuredTimeoutMs: configuredTimeoutMs as number }),
|
|
348
|
+
...(credentialType === undefined ? {} : { credentialType }),
|
|
349
|
+
...(error === undefined ? {} : { error }),
|
|
350
|
+
...(model === undefined ? {} : { model }),
|
|
351
|
+
...(provider === undefined ? {} : { provider }),
|
|
352
|
+
...(candidate.providerTimeout === undefined
|
|
353
|
+
? {}
|
|
354
|
+
: { providerTimeout: candidate.providerTimeout }),
|
|
355
|
+
...(statusCode === undefined
|
|
356
|
+
? {}
|
|
357
|
+
: { statusCode: statusCode as number }),
|
|
358
|
+
...(candidate.success === undefined
|
|
359
|
+
? {}
|
|
360
|
+
: { success: candidate.success }),
|
|
361
|
+
};
|
|
362
|
+
});
|
|
363
|
+
const generationId = boundedString(value.generationId);
|
|
364
|
+
if (value.generationId !== undefined && generationId === undefined) {
|
|
365
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
attemptCount: value.attemptCount as number,
|
|
369
|
+
attempts,
|
|
370
|
+
attemptsTruncated: value.attemptsTruncated,
|
|
371
|
+
clientMaxRetries: 0,
|
|
372
|
+
gatewayProviderFailover: "may-attempt-multiple-providers",
|
|
373
|
+
...(generationId === undefined ? {} : { generationId }),
|
|
374
|
+
providerCount: value.providerCount as number,
|
|
375
|
+
providers: [...new Set(providers)],
|
|
376
|
+
providersTruncated: value.providersTruncated,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function syncDirectory(path: string): Promise<void> {
|
|
381
|
+
const handle = await open(
|
|
382
|
+
path,
|
|
383
|
+
constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | (constants.O_NOFOLLOW ?? 0),
|
|
384
|
+
);
|
|
385
|
+
try {
|
|
386
|
+
await handle.sync();
|
|
387
|
+
} finally {
|
|
388
|
+
await handle.close();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function writePrivateFile(
|
|
393
|
+
path: string,
|
|
394
|
+
body: Uint8Array | string,
|
|
395
|
+
): Promise<void> {
|
|
396
|
+
const handle = await open(
|
|
397
|
+
path,
|
|
398
|
+
constants.O_CREAT
|
|
399
|
+
| constants.O_EXCL
|
|
400
|
+
| constants.O_WRONLY
|
|
401
|
+
| (constants.O_NOFOLLOW ?? 0),
|
|
402
|
+
0o600,
|
|
403
|
+
);
|
|
404
|
+
try {
|
|
405
|
+
await handle.writeFile(body);
|
|
406
|
+
await handle.sync();
|
|
407
|
+
} finally {
|
|
408
|
+
await handle.close();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function operationStem(operation: GatewayMediaReceipt["operation"]): string {
|
|
413
|
+
switch (operation) {
|
|
414
|
+
case "image.generate":
|
|
415
|
+
return "image";
|
|
416
|
+
case "speech.generate":
|
|
417
|
+
return "speech";
|
|
418
|
+
case "transcription.create":
|
|
419
|
+
return "transcript";
|
|
420
|
+
case "video.generate":
|
|
421
|
+
return "video";
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function shellArgument(value: string): string {
|
|
426
|
+
return /^[A-Za-z0-9_./:@%+=,-]+$/u.test(value)
|
|
427
|
+
? value
|
|
428
|
+
: `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const NON_SELF_DESCRIBING_SPEECH_MEDIA_TYPES = new Set([
|
|
432
|
+
"audio/alaw",
|
|
433
|
+
"audio/basic",
|
|
434
|
+
"audio/l16",
|
|
435
|
+
"audio/mulaw",
|
|
436
|
+
"audio/pcm",
|
|
437
|
+
]);
|
|
438
|
+
|
|
439
|
+
export function gatewayMediaTypeIsSelfDescribing(
|
|
440
|
+
mediaType: string,
|
|
441
|
+
): boolean {
|
|
442
|
+
return /^(?:audio|image|video)\//u.test(mediaType)
|
|
443
|
+
&& !NON_SELF_DESCRIBING_SPEECH_MEDIA_TYPES.has(mediaType);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function nextCommands(
|
|
447
|
+
operation: GatewayMediaReceipt["operation"],
|
|
448
|
+
finalDirectory: string,
|
|
449
|
+
outputs: readonly GatewayMediaReceiptOutput[],
|
|
450
|
+
repositoryRoot: string,
|
|
451
|
+
): readonly string[] {
|
|
452
|
+
const role = operation === "speech.generate"
|
|
453
|
+
? "dialogue"
|
|
454
|
+
: operation === "transcription.create"
|
|
455
|
+
? undefined
|
|
456
|
+
: "b-roll";
|
|
457
|
+
return role === undefined
|
|
458
|
+
? []
|
|
459
|
+
: outputs
|
|
460
|
+
.filter(output => (
|
|
461
|
+
operation !== "speech.generate"
|
|
462
|
+
|| !NON_SELF_DESCRIBING_SPEECH_MEDIA_TYPES.has(output.mediaType)
|
|
463
|
+
))
|
|
464
|
+
.map(output => (
|
|
465
|
+
`slopcamera project add <project> ${
|
|
466
|
+
shellArgument(
|
|
467
|
+
relative(
|
|
468
|
+
repositoryRoot,
|
|
469
|
+
join(finalDirectory, output.file),
|
|
470
|
+
).split(sep).join("/"),
|
|
471
|
+
)
|
|
472
|
+
} --role ${role}`
|
|
473
|
+
));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function runDirectoryName(
|
|
477
|
+
operation: GatewayMediaReceipt["operation"],
|
|
478
|
+
createdAt: string,
|
|
479
|
+
randomId: string,
|
|
480
|
+
): string {
|
|
481
|
+
return `${createdAt.replace(/[-:.]/gu, "").replace("Z", "Z")}-${operationStem(operation)}-${randomId}`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function validateFile(
|
|
485
|
+
file: GatewayGeneratedFile,
|
|
486
|
+
): Readonly<GatewayGeneratedFile & { readonly mediaType: string }> {
|
|
487
|
+
if (!(file.uint8Array instanceof Uint8Array) || file.uint8Array.byteLength < 1) {
|
|
488
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
489
|
+
}
|
|
490
|
+
if (file.uint8Array.byteLength > MAXIMUM_GENERATED_FILE_BYTES) {
|
|
491
|
+
throw new GatewayMediaArtifactError("output-too-large");
|
|
492
|
+
}
|
|
493
|
+
const mediaType = validateMediaType(file.mediaType);
|
|
494
|
+
if (
|
|
495
|
+
/^(?:audio|image|video)\//u.test(mediaType)
|
|
496
|
+
&& !gatewayMediaBytesMatchType(file.uint8Array, mediaType)
|
|
497
|
+
) {
|
|
498
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
mediaType,
|
|
502
|
+
uint8Array: file.uint8Array,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function sampleFulfillment(
|
|
507
|
+
value: Readonly<{ readonly produced: number; readonly requested: number }>,
|
|
508
|
+
): NonNullable<GatewayMediaReceipt["sampleFulfillment"]> {
|
|
509
|
+
if (
|
|
510
|
+
!Number.isSafeInteger(value.requested)
|
|
511
|
+
|| value.requested < 1
|
|
512
|
+
|| value.requested > MAXIMUM_OUTPUTS
|
|
513
|
+
|| !Number.isSafeInteger(value.produced)
|
|
514
|
+
|| value.produced < 1
|
|
515
|
+
|| value.produced > MAXIMUM_OUTPUTS
|
|
516
|
+
) {
|
|
517
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
produced: value.produced,
|
|
521
|
+
requested: value.requested,
|
|
522
|
+
status: value.produced === value.requested
|
|
523
|
+
? "complete"
|
|
524
|
+
: value.produced < value.requested
|
|
525
|
+
? "partial"
|
|
526
|
+
: "overproduced",
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export function createFileGatewayMediaArtifactStore(options: Readonly<{
|
|
531
|
+
beforePublication?: () => Promise<void>;
|
|
532
|
+
outputRoot: string;
|
|
533
|
+
randomId?: () => string;
|
|
534
|
+
repositoryRoot: string;
|
|
535
|
+
validateMediaFile?: (
|
|
536
|
+
file: Readonly<GatewayGeneratedFile & {
|
|
537
|
+
readonly path: string;
|
|
538
|
+
}>,
|
|
539
|
+
signal: AbortSignal,
|
|
540
|
+
) => Promise<void>;
|
|
541
|
+
}>): GatewayMediaArtifactStore {
|
|
542
|
+
if (!isAbsolute(options.outputRoot) || !isAbsolute(options.repositoryRoot)) {
|
|
543
|
+
throw new GatewayMediaArtifactError("unsafe-output");
|
|
544
|
+
}
|
|
545
|
+
const outputRelative = relative(options.repositoryRoot, options.outputRoot);
|
|
546
|
+
if (
|
|
547
|
+
outputRelative.length === 0
|
|
548
|
+
|| outputRelative === ".."
|
|
549
|
+
|| outputRelative.startsWith(`..${sep}`)
|
|
550
|
+
|| isAbsolute(outputRelative)
|
|
551
|
+
) {
|
|
552
|
+
throw new GatewayMediaArtifactError("unsafe-output");
|
|
553
|
+
}
|
|
554
|
+
const randomId = options.randomId ?? (() => randomUUID().slice(0, 12));
|
|
555
|
+
return {
|
|
556
|
+
commit: async (input) => {
|
|
557
|
+
input.signal.throwIfAborted();
|
|
558
|
+
if (input.files.length < 1 || input.files.length > MAXIMUM_OUTPUTS) {
|
|
559
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
560
|
+
}
|
|
561
|
+
const files = input.files.map(validateFile);
|
|
562
|
+
const totalBytes = files.reduce((sum, file) => sum + file.uint8Array.byteLength, 0);
|
|
563
|
+
if (totalBytes > MAXIMUM_GENERATED_TOTAL_BYTES) {
|
|
564
|
+
throw new GatewayMediaArtifactError("output-too-large");
|
|
565
|
+
}
|
|
566
|
+
const createdAt = canonicalTimestamp(input.createdAt);
|
|
567
|
+
const identifier = randomId();
|
|
568
|
+
if (!/^[a-z0-9_-]{4,64}$/iu.test(identifier)) {
|
|
569
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
570
|
+
}
|
|
571
|
+
const directoryName = runDirectoryName(input.operation, createdAt, identifier);
|
|
572
|
+
const finalDirectory = join(options.outputRoot, directoryName);
|
|
573
|
+
const stagingDirectory = join(options.outputRoot, `.${directoryName}.tmp`);
|
|
574
|
+
let published = false;
|
|
575
|
+
const receiptOutputs: GatewayMediaReceiptOutput[] = [];
|
|
576
|
+
const stagedFiles: Readonly<GatewayGeneratedFile & {
|
|
577
|
+
readonly path: string;
|
|
578
|
+
}>[] = [];
|
|
579
|
+
try {
|
|
580
|
+
await mkdir(options.outputRoot, { mode: 0o700, recursive: true });
|
|
581
|
+
const rootDetails = await lstat(options.outputRoot);
|
|
582
|
+
if (rootDetails.isSymbolicLink() || !rootDetails.isDirectory()) {
|
|
583
|
+
throw new GatewayMediaArtifactError("unsafe-output");
|
|
584
|
+
}
|
|
585
|
+
await chmod(options.outputRoot, 0o700);
|
|
586
|
+
input.signal.throwIfAborted();
|
|
587
|
+
await mkdir(stagingDirectory, { mode: 0o700 });
|
|
588
|
+
for (const [index, file] of files.entries()) {
|
|
589
|
+
input.signal.throwIfAborted();
|
|
590
|
+
const fileName = `${operationStem(input.operation)}-${String(index + 1).padStart(2, "0")}${extensionForMediaType(file.mediaType)}`;
|
|
591
|
+
const outputPath = join(stagingDirectory, fileName);
|
|
592
|
+
await writePrivateFile(outputPath, file.uint8Array);
|
|
593
|
+
stagedFiles.push({
|
|
594
|
+
mediaType: file.mediaType,
|
|
595
|
+
path: outputPath,
|
|
596
|
+
uint8Array: file.uint8Array,
|
|
597
|
+
});
|
|
598
|
+
receiptOutputs.push({
|
|
599
|
+
bytes: file.uint8Array.byteLength,
|
|
600
|
+
file: fileName,
|
|
601
|
+
mediaType: file.mediaType,
|
|
602
|
+
sha256: createHash("sha256").update(file.uint8Array).digest("hex"),
|
|
603
|
+
});
|
|
604
|
+
input.signal.throwIfAborted();
|
|
605
|
+
}
|
|
606
|
+
const decodeCandidates = stagedFiles.filter(file => (
|
|
607
|
+
gatewayMediaTypeIsSelfDescribing(file.mediaType)
|
|
608
|
+
));
|
|
609
|
+
let localValidation: GatewayMediaReceipt["localValidation"];
|
|
610
|
+
if (
|
|
611
|
+
decodeCandidates.length === 0
|
|
612
|
+
|| options.validateMediaFile === undefined
|
|
613
|
+
) {
|
|
614
|
+
localValidation = {
|
|
615
|
+
decodeValidatedOutputs: 0,
|
|
616
|
+
signatureOnlyOutputs: stagedFiles.length,
|
|
617
|
+
status: "signature-only",
|
|
618
|
+
};
|
|
619
|
+
} else {
|
|
620
|
+
try {
|
|
621
|
+
for (const file of decodeCandidates) {
|
|
622
|
+
input.signal.throwIfAborted();
|
|
623
|
+
await options.validateMediaFile(file, input.signal);
|
|
624
|
+
input.signal.throwIfAborted();
|
|
625
|
+
}
|
|
626
|
+
localValidation = {
|
|
627
|
+
decodeValidatedOutputs: decodeCandidates.length,
|
|
628
|
+
signatureOnlyOutputs:
|
|
629
|
+
stagedFiles.length - decodeCandidates.length,
|
|
630
|
+
status: "decode-passed",
|
|
631
|
+
};
|
|
632
|
+
} catch (error) {
|
|
633
|
+
input.signal.throwIfAborted();
|
|
634
|
+
const failureSource = error instanceof Error
|
|
635
|
+
? `${error.name}\u0000${error.message}`
|
|
636
|
+
: typeof error === "string"
|
|
637
|
+
? error
|
|
638
|
+
: typeof error;
|
|
639
|
+
localValidation = {
|
|
640
|
+
decodeValidatedOutputs: 0,
|
|
641
|
+
failureSha256: createHash("sha256")
|
|
642
|
+
.update(failureSource, "utf8")
|
|
643
|
+
.digest("hex"),
|
|
644
|
+
signatureOnlyOutputs:
|
|
645
|
+
stagedFiles.length - decodeCandidates.length,
|
|
646
|
+
status: "decode-failed",
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
input.signal.throwIfAborted();
|
|
651
|
+
const receipt: GatewayMediaReceipt = {
|
|
652
|
+
catalog: input.catalog,
|
|
653
|
+
createdAt,
|
|
654
|
+
inputs: input.inputs.map(validateDigest),
|
|
655
|
+
kind: "slopcamera.gateway-media-receipt",
|
|
656
|
+
localValidation,
|
|
657
|
+
model: input.model,
|
|
658
|
+
nextCommands: localValidation.status === "decode-passed"
|
|
659
|
+
? nextCommands(
|
|
660
|
+
input.operation,
|
|
661
|
+
finalDirectory,
|
|
662
|
+
receiptOutputs,
|
|
663
|
+
options.repositoryRoot,
|
|
664
|
+
)
|
|
665
|
+
: [],
|
|
666
|
+
operation: input.operation,
|
|
667
|
+
outputs: receiptOutputs,
|
|
668
|
+
request: input.request,
|
|
669
|
+
routing: validateRouting(input.routing),
|
|
670
|
+
...(input.sampleFulfillment === undefined
|
|
671
|
+
? {}
|
|
672
|
+
: {
|
|
673
|
+
sampleFulfillment: sampleFulfillment(
|
|
674
|
+
input.sampleFulfillment,
|
|
675
|
+
),
|
|
676
|
+
}),
|
|
677
|
+
schemaVersion: 1,
|
|
678
|
+
warnings: input.warnings.slice(0, 100).map(boundedWarning),
|
|
679
|
+
};
|
|
680
|
+
let receiptSource: string;
|
|
681
|
+
try {
|
|
682
|
+
receiptSource = `${canonicalJson(receipt)}\n`;
|
|
683
|
+
} catch {
|
|
684
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
685
|
+
}
|
|
686
|
+
if (new TextEncoder().encode(receiptSource).byteLength > MAXIMUM_RECEIPT_BYTES) {
|
|
687
|
+
throw new GatewayMediaArtifactError("artifact-invalid");
|
|
688
|
+
}
|
|
689
|
+
await writePrivateFile(join(stagingDirectory, "receipt.json"), receiptSource);
|
|
690
|
+
await syncDirectory(stagingDirectory);
|
|
691
|
+
input.signal.throwIfAborted();
|
|
692
|
+
await options.beforePublication?.();
|
|
693
|
+
input.signal.throwIfAborted();
|
|
694
|
+
await rename(stagingDirectory, finalDirectory);
|
|
695
|
+
published = true;
|
|
696
|
+
await syncDirectory(options.outputRoot);
|
|
697
|
+
return {
|
|
698
|
+
directory: finalDirectory,
|
|
699
|
+
outputs: receiptOutputs.map(output => ({
|
|
700
|
+
...output,
|
|
701
|
+
path: join(finalDirectory, output.file),
|
|
702
|
+
})),
|
|
703
|
+
receipt,
|
|
704
|
+
receiptPath: join(finalDirectory, "receipt.json"),
|
|
705
|
+
};
|
|
706
|
+
} catch (error) {
|
|
707
|
+
if (!published) input.signal.throwIfAborted();
|
|
708
|
+
if (error instanceof GatewayMediaArtifactError) throw error;
|
|
709
|
+
throw new GatewayMediaArtifactError("artifact-unavailable");
|
|
710
|
+
} finally {
|
|
711
|
+
await rm(stagingDirectory, { force: true, recursive: true }).catch(() => undefined);
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export function displayGatewayArtifactFile(
|
|
718
|
+
bundle: GatewayMediaArtifactBundle,
|
|
719
|
+
file: string,
|
|
720
|
+
): string {
|
|
721
|
+
const name = basename(file);
|
|
722
|
+
return bundle.outputs.find(output => output.file === name)?.path ?? file;
|
|
723
|
+
}
|