@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
package/src/generate.ts
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto"
|
|
2
|
+
import { link, rm, writeFile } from "node:fs/promises"
|
|
3
|
+
import { dirname, extname, resolve } from "node:path"
|
|
4
|
+
import { SlopcameraCloudError } from "./cloud-errors.js"
|
|
5
|
+
|
|
6
|
+
export const slopcameraGatewayApiBaseUrl =
|
|
7
|
+
"https://ai-gateway.vercel.sh/v4/ai" as const
|
|
8
|
+
|
|
9
|
+
export const slopcameraImageModels = Object.freeze([
|
|
10
|
+
"openai/gpt-image-1.5",
|
|
11
|
+
"recraft/recraft-v4.1-utility",
|
|
12
|
+
] as const)
|
|
13
|
+
|
|
14
|
+
export const slopcameraResponseMediaTypes = Object.freeze([
|
|
15
|
+
"image/png",
|
|
16
|
+
"image/jpeg",
|
|
17
|
+
"image/webp",
|
|
18
|
+
] as const)
|
|
19
|
+
|
|
20
|
+
export const slopcameraMaximumPromptBytes = 32 * 1024
|
|
21
|
+
export const slopcameraMaximumRawImageBytes = 64 * 1024 * 1024
|
|
22
|
+
|
|
23
|
+
export type SlopcameraImageModel = string
|
|
24
|
+
export type SlopcameraResponseMediaType =
|
|
25
|
+
(typeof slopcameraResponseMediaTypes)[number]
|
|
26
|
+
export type SlopcameraGatewayCredentialSource =
|
|
27
|
+
| "AI_GATEWAY_API_KEY"
|
|
28
|
+
| "VERCEL_OIDC_TOKEN"
|
|
29
|
+
|
|
30
|
+
export interface SlopcameraGatewayCredentialStatus {
|
|
31
|
+
readonly available: boolean
|
|
32
|
+
readonly source: SlopcameraGatewayCredentialSource | null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface GenerateSlopcameraImageInput {
|
|
36
|
+
readonly model: SlopcameraImageModel
|
|
37
|
+
readonly prompt: string
|
|
38
|
+
readonly signal?: AbortSignal
|
|
39
|
+
readonly timeoutMs?: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface GeneratedSlopcameraImage {
|
|
43
|
+
readonly image: {
|
|
44
|
+
readonly base64: string
|
|
45
|
+
readonly mediaType: SlopcameraResponseMediaType
|
|
46
|
+
}
|
|
47
|
+
readonly model: SlopcameraImageModel
|
|
48
|
+
readonly provider: "vercel-ai-gateway"
|
|
49
|
+
readonly requestId: string
|
|
50
|
+
readonly warnings: readonly string[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface GeneratedSlopcameraImageFile {
|
|
54
|
+
readonly bytes: number
|
|
55
|
+
readonly mediaType: SlopcameraResponseMediaType
|
|
56
|
+
readonly model: SlopcameraImageModel
|
|
57
|
+
readonly outputPath: string
|
|
58
|
+
readonly provider: "vercel-ai-gateway"
|
|
59
|
+
readonly requestId: string
|
|
60
|
+
readonly sha256: string
|
|
61
|
+
readonly warnings: readonly string[]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type SlopcameraEnvironment = Readonly<Record<string, string | undefined>>
|
|
65
|
+
type SlopcameraGatewayFetch = (
|
|
66
|
+
input: string | URL | Request,
|
|
67
|
+
init?: RequestInit,
|
|
68
|
+
) => Promise<Response>
|
|
69
|
+
|
|
70
|
+
interface GatewayProvider {
|
|
71
|
+
imageModel(modelId: string): unknown
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface GatewayRuntime {
|
|
75
|
+
createGateway(settings: Readonly<{
|
|
76
|
+
apiKey: string
|
|
77
|
+
baseURL: typeof slopcameraGatewayApiBaseUrl
|
|
78
|
+
fetch: SlopcameraGatewayFetch
|
|
79
|
+
}>): GatewayProvider
|
|
80
|
+
generateImage(input: Readonly<Record<string, unknown>>): Promise<unknown>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface SlopcameraGenerateDependencies {
|
|
84
|
+
readonly environment?: SlopcameraEnvironment
|
|
85
|
+
readonly fetch?: SlopcameraGatewayFetch
|
|
86
|
+
readonly loadRuntime?: () => Promise<GatewayRuntime>
|
|
87
|
+
readonly maximumResponseBytes?: number
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const defaultGenerationTimeoutMs = 5 * 60_000
|
|
91
|
+
const maximumGenerationTimeoutMs = 30 * 60_000
|
|
92
|
+
const defaultMaximumGatewayResponseBytes = 96 * 1024 * 1024
|
|
93
|
+
|
|
94
|
+
function invalidArgument(message: string): never {
|
|
95
|
+
throw new SlopcameraCloudError("INVALID_ARGUMENT", message)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function credentialValue(value: string): string {
|
|
99
|
+
if (
|
|
100
|
+
value.length < 16 ||
|
|
101
|
+
value.length > 16 * 1024 ||
|
|
102
|
+
value.trim() !== value ||
|
|
103
|
+
/[^\x21-\x7e]/u.test(value)
|
|
104
|
+
) {
|
|
105
|
+
throw new SlopcameraCloudError(
|
|
106
|
+
"AUTHENTICATION_REQUIRED",
|
|
107
|
+
"The selected Vercel AI Gateway credential is invalid.",
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
return value
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function environment(
|
|
114
|
+
injected: SlopcameraEnvironment | undefined,
|
|
115
|
+
): SlopcameraEnvironment {
|
|
116
|
+
return injected ?? process.env
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function resolveGatewayCredential(
|
|
120
|
+
injected: SlopcameraEnvironment | undefined,
|
|
121
|
+
): Readonly<{
|
|
122
|
+
source: SlopcameraGatewayCredentialSource
|
|
123
|
+
token: string
|
|
124
|
+
}> {
|
|
125
|
+
const values = environment(injected)
|
|
126
|
+
if (values.AI_GATEWAY_API_KEY !== undefined) {
|
|
127
|
+
return {
|
|
128
|
+
source: "AI_GATEWAY_API_KEY",
|
|
129
|
+
token: credentialValue(values.AI_GATEWAY_API_KEY),
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (values.VERCEL_OIDC_TOKEN !== undefined) {
|
|
133
|
+
return {
|
|
134
|
+
source: "VERCEL_OIDC_TOKEN",
|
|
135
|
+
token: credentialValue(values.VERCEL_OIDC_TOKEN),
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw new SlopcameraCloudError(
|
|
139
|
+
"AUTHENTICATION_REQUIRED",
|
|
140
|
+
"Set AI_GATEWAY_API_KEY or run Slopcamera through `vercel env run -- …` with VERCEL_OIDC_TOKEN available.",
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function slopcameraGatewayCredentialStatus(
|
|
145
|
+
injected?: SlopcameraEnvironment,
|
|
146
|
+
): SlopcameraGatewayCredentialStatus {
|
|
147
|
+
const values = environment(injected)
|
|
148
|
+
if (values.AI_GATEWAY_API_KEY !== undefined) {
|
|
149
|
+
credentialValue(values.AI_GATEWAY_API_KEY)
|
|
150
|
+
return { available: true, source: "AI_GATEWAY_API_KEY" }
|
|
151
|
+
}
|
|
152
|
+
if (values.VERCEL_OIDC_TOKEN !== undefined) {
|
|
153
|
+
credentialValue(values.VERCEL_OIDC_TOKEN)
|
|
154
|
+
return { available: true, source: "VERCEL_OIDC_TOKEN" }
|
|
155
|
+
}
|
|
156
|
+
return { available: false, source: null }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function validateModel(value: unknown): string {
|
|
160
|
+
if (
|
|
161
|
+
typeof value !== "string" ||
|
|
162
|
+
value.length < 3 ||
|
|
163
|
+
value.length > 256 ||
|
|
164
|
+
!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(value)
|
|
165
|
+
) {
|
|
166
|
+
invalidArgument("Model must be a bounded Vercel AI Gateway provider/model id.")
|
|
167
|
+
}
|
|
168
|
+
return value
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function validatePrompt(value: unknown): string {
|
|
172
|
+
if (
|
|
173
|
+
typeof value !== "string" ||
|
|
174
|
+
value.trim().length === 0 ||
|
|
175
|
+
/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u.test(value) ||
|
|
176
|
+
Buffer.byteLength(value, "utf8") > slopcameraMaximumPromptBytes
|
|
177
|
+
) {
|
|
178
|
+
invalidArgument(
|
|
179
|
+
`Prompt must be non-empty and no more than ${slopcameraMaximumPromptBytes} UTF-8 bytes.`,
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
return value
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function validateTimeout(value: number | undefined): number {
|
|
186
|
+
const timeout = value ?? defaultGenerationTimeoutMs
|
|
187
|
+
if (
|
|
188
|
+
!Number.isSafeInteger(timeout) ||
|
|
189
|
+
timeout < 1_000 ||
|
|
190
|
+
timeout > maximumGenerationTimeoutMs
|
|
191
|
+
) {
|
|
192
|
+
invalidArgument(
|
|
193
|
+
`timeoutMs must be an integer from 1000 through ${maximumGenerationTimeoutMs}.`,
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
return timeout
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
200
|
+
return (
|
|
201
|
+
(typeof value === "object" && value !== null) ||
|
|
202
|
+
typeof value === "function"
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function parseFunction(
|
|
207
|
+
value: unknown,
|
|
208
|
+
): (input: Readonly<Record<string, unknown>>) => Promise<unknown> {
|
|
209
|
+
if (typeof value !== "function") {
|
|
210
|
+
throw new SlopcameraCloudError(
|
|
211
|
+
"GENERATION_FAILED",
|
|
212
|
+
"The Vercel AI Gateway runtime is unavailable.",
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
return value as (
|
|
216
|
+
input: Readonly<Record<string, unknown>>,
|
|
217
|
+
) => Promise<unknown>
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function disableAiSdkWarningLogging(): void {
|
|
221
|
+
(
|
|
222
|
+
globalThis as typeof globalThis & {
|
|
223
|
+
AI_SDK_LOG_WARNINGS?: false
|
|
224
|
+
}
|
|
225
|
+
).AI_SDK_LOG_WARNINGS = false
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function assertGenerationActive(signal: AbortSignal): void {
|
|
229
|
+
if (signal.aborted) {
|
|
230
|
+
throw new SlopcameraCloudError(
|
|
231
|
+
"GENERATION_FAILED",
|
|
232
|
+
"Vercel AI Gateway image generation was cancelled or exceeded its deadline.",
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function loadDefaultGatewayRuntime(): Promise<GatewayRuntime> {
|
|
238
|
+
let aiModule: unknown
|
|
239
|
+
let gatewayModule: unknown
|
|
240
|
+
try {
|
|
241
|
+
;[aiModule, gatewayModule] = await Promise.all([
|
|
242
|
+
import("ai-v7"),
|
|
243
|
+
import("@ai-sdk/gateway-v4"),
|
|
244
|
+
])
|
|
245
|
+
} catch {
|
|
246
|
+
throw new SlopcameraCloudError(
|
|
247
|
+
"GENERATION_FAILED",
|
|
248
|
+
"The Vercel AI Gateway runtime is unavailable.",
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
if (!isObject(aiModule) || !isObject(gatewayModule)) {
|
|
252
|
+
throw new SlopcameraCloudError(
|
|
253
|
+
"GENERATION_FAILED",
|
|
254
|
+
"The Vercel AI Gateway runtime is unavailable.",
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
const generateImage = parseFunction(aiModule.generateImage)
|
|
258
|
+
if (typeof gatewayModule.createGateway !== "function") {
|
|
259
|
+
throw new SlopcameraCloudError(
|
|
260
|
+
"GENERATION_FAILED",
|
|
261
|
+
"The Vercel AI Gateway runtime is unavailable.",
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
const createGateway = gatewayModule.createGateway as (
|
|
265
|
+
settings: Readonly<Record<string, unknown>>,
|
|
266
|
+
) => unknown
|
|
267
|
+
return {
|
|
268
|
+
createGateway: settings => {
|
|
269
|
+
const provider = createGateway(settings)
|
|
270
|
+
if (!isObject(provider) || typeof provider.imageModel !== "function") {
|
|
271
|
+
throw new SlopcameraCloudError(
|
|
272
|
+
"GENERATION_FAILED",
|
|
273
|
+
"The Vercel AI Gateway runtime is unavailable.",
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
const imageModel = provider.imageModel as (modelId: string) => unknown
|
|
277
|
+
return { imageModel: modelId => imageModel.call(provider, modelId) }
|
|
278
|
+
},
|
|
279
|
+
generateImage,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function gatewayUrl(input: string | URL | Request): URL {
|
|
284
|
+
try {
|
|
285
|
+
if (input instanceof Request) return new URL(input.url)
|
|
286
|
+
if (input instanceof URL) return new URL(input.href)
|
|
287
|
+
return new URL(input, slopcameraGatewayApiBaseUrl)
|
|
288
|
+
} catch {
|
|
289
|
+
throw new SlopcameraCloudError(
|
|
290
|
+
"GENERATION_FAILED",
|
|
291
|
+
"The Vercel AI Gateway request was rejected.",
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function canonicalGatewayInput(
|
|
297
|
+
input: string | URL | Request,
|
|
298
|
+
url: URL,
|
|
299
|
+
): URL | Request {
|
|
300
|
+
if (!(input instanceof Request)) return url
|
|
301
|
+
|
|
302
|
+
// Snapshot the Request's internal data, then bind it to the URL that was
|
|
303
|
+
// actually validated. Never forward a foreign Request subclass directly.
|
|
304
|
+
const snapshot = new Request(input)
|
|
305
|
+
const body = snapshot.method === "GET" || snapshot.method === "HEAD"
|
|
306
|
+
? undefined
|
|
307
|
+
: snapshot.body ?? undefined
|
|
308
|
+
const requestInit: RequestInit & { duplex?: "half" } = {
|
|
309
|
+
headers: snapshot.headers,
|
|
310
|
+
method: snapshot.method,
|
|
311
|
+
signal: snapshot.signal,
|
|
312
|
+
...(body === undefined ? {} : { body, duplex: "half" }),
|
|
313
|
+
}
|
|
314
|
+
return new Request(url.href, requestInit)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function boundedResponse(response: Response, maximumBytes: number): Response {
|
|
318
|
+
const declared = response.headers.get("content-length")
|
|
319
|
+
if (declared !== null) {
|
|
320
|
+
const value = Number(declared)
|
|
321
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > maximumBytes) {
|
|
322
|
+
void response.body?.cancel().catch(() => undefined)
|
|
323
|
+
throw new SlopcameraCloudError(
|
|
324
|
+
"GENERATION_INVALID_RESPONSE",
|
|
325
|
+
"Vercel AI Gateway returned an invalid bounded response.",
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (response.body === null) return response
|
|
330
|
+
const reader = response.body.getReader()
|
|
331
|
+
let bytes = 0
|
|
332
|
+
const body = new ReadableStream<Uint8Array>({
|
|
333
|
+
cancel: async reason => await reader.cancel(reason),
|
|
334
|
+
pull: async controller => {
|
|
335
|
+
try {
|
|
336
|
+
const next = await reader.read()
|
|
337
|
+
if (next.done) {
|
|
338
|
+
reader.releaseLock()
|
|
339
|
+
controller.close()
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
bytes += next.value.byteLength
|
|
343
|
+
if (bytes > maximumBytes) {
|
|
344
|
+
await reader.cancel().catch(() => undefined)
|
|
345
|
+
controller.error(
|
|
346
|
+
new SlopcameraCloudError(
|
|
347
|
+
"GENERATION_INVALID_RESPONSE",
|
|
348
|
+
"Vercel AI Gateway returned an invalid bounded response.",
|
|
349
|
+
),
|
|
350
|
+
)
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
controller.enqueue(next.value)
|
|
354
|
+
} catch {
|
|
355
|
+
controller.error(
|
|
356
|
+
new SlopcameraCloudError(
|
|
357
|
+
"GENERATION_INVALID_RESPONSE",
|
|
358
|
+
"Vercel AI Gateway returned an invalid bounded response.",
|
|
359
|
+
),
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
})
|
|
364
|
+
return new Response(body, {
|
|
365
|
+
headers: response.headers,
|
|
366
|
+
status: response.status,
|
|
367
|
+
statusText: response.statusText,
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function createFixedGatewayFetch(
|
|
372
|
+
options: Readonly<{
|
|
373
|
+
fetch?: SlopcameraGatewayFetch
|
|
374
|
+
maximumResponseBytes?: number
|
|
375
|
+
}> = {},
|
|
376
|
+
): SlopcameraGatewayFetch {
|
|
377
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch
|
|
378
|
+
const maximumResponseBytes =
|
|
379
|
+
options.maximumResponseBytes ?? defaultMaximumGatewayResponseBytes
|
|
380
|
+
if (
|
|
381
|
+
!Number.isSafeInteger(maximumResponseBytes) ||
|
|
382
|
+
maximumResponseBytes < 1 ||
|
|
383
|
+
maximumResponseBytes > 1024 * 1024 * 1024
|
|
384
|
+
) {
|
|
385
|
+
invalidArgument("maximumResponseBytes is outside the supported range.")
|
|
386
|
+
}
|
|
387
|
+
const fixed = new URL(slopcameraGatewayApiBaseUrl)
|
|
388
|
+
return async (input, init) => {
|
|
389
|
+
const url = gatewayUrl(input)
|
|
390
|
+
if (
|
|
391
|
+
url.origin !== fixed.origin ||
|
|
392
|
+
(url.pathname !== fixed.pathname &&
|
|
393
|
+
!url.pathname.startsWith(`${fixed.pathname}/`)) ||
|
|
394
|
+
url.username !== "" ||
|
|
395
|
+
url.password !== ""
|
|
396
|
+
) {
|
|
397
|
+
throw new SlopcameraCloudError(
|
|
398
|
+
"GENERATION_FAILED",
|
|
399
|
+
"The Vercel AI Gateway request was rejected.",
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
const canonicalInput = canonicalGatewayInput(input, url)
|
|
403
|
+
let response: Response
|
|
404
|
+
try {
|
|
405
|
+
response = await fetchImplementation(canonicalInput, {
|
|
406
|
+
...init,
|
|
407
|
+
redirect: "error",
|
|
408
|
+
})
|
|
409
|
+
} catch (error) {
|
|
410
|
+
if (error instanceof SlopcameraCloudError) throw error
|
|
411
|
+
throw new SlopcameraCloudError(
|
|
412
|
+
"GENERATION_FAILED",
|
|
413
|
+
"Vercel AI Gateway image generation failed; the request was not retried.",
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
if (response.redirected || (response.status >= 300 && response.status < 400)) {
|
|
417
|
+
await response.body?.cancel().catch(() => undefined)
|
|
418
|
+
throw new SlopcameraCloudError(
|
|
419
|
+
"GENERATION_FAILED",
|
|
420
|
+
"The Vercel AI Gateway request was rejected.",
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
return boundedResponse(response, maximumResponseBytes)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function combineSignals(
|
|
428
|
+
caller: AbortSignal | undefined,
|
|
429
|
+
timeoutMs: number,
|
|
430
|
+
): Readonly<{
|
|
431
|
+
dispose(): void
|
|
432
|
+
interruption: Promise<never>
|
|
433
|
+
signal: AbortSignal
|
|
434
|
+
}> {
|
|
435
|
+
const controller = new AbortController()
|
|
436
|
+
const abort = (): void => controller.abort(caller?.reason)
|
|
437
|
+
let rejectInterruption: ((error: SlopcameraCloudError) => void) | undefined
|
|
438
|
+
const interruption = new Promise<never>((_resolve, reject) => {
|
|
439
|
+
rejectInterruption = reject
|
|
440
|
+
})
|
|
441
|
+
const rejectOnAbort = (): void => {
|
|
442
|
+
rejectInterruption?.(new SlopcameraCloudError(
|
|
443
|
+
"GENERATION_FAILED",
|
|
444
|
+
"Vercel AI Gateway image generation was cancelled or exceeded its deadline.",
|
|
445
|
+
))
|
|
446
|
+
}
|
|
447
|
+
controller.signal.addEventListener("abort", rejectOnAbort, { once: true })
|
|
448
|
+
if (caller?.aborted === true) abort()
|
|
449
|
+
caller?.addEventListener("abort", abort, { once: true })
|
|
450
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
451
|
+
return {
|
|
452
|
+
dispose: () => {
|
|
453
|
+
clearTimeout(timer)
|
|
454
|
+
caller?.removeEventListener("abort", abort)
|
|
455
|
+
controller.signal.removeEventListener("abort", rejectOnAbort)
|
|
456
|
+
rejectInterruption = undefined
|
|
457
|
+
},
|
|
458
|
+
interruption,
|
|
459
|
+
signal: controller.signal,
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function mediaType(value: unknown): SlopcameraResponseMediaType {
|
|
464
|
+
if (
|
|
465
|
+
typeof value !== "string" ||
|
|
466
|
+
!slopcameraResponseMediaTypes.includes(value as SlopcameraResponseMediaType)
|
|
467
|
+
) {
|
|
468
|
+
throw new SlopcameraCloudError(
|
|
469
|
+
"GENERATION_INVALID_RESPONSE",
|
|
470
|
+
"Vercel AI Gateway returned an unsupported image type.",
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
return value as SlopcameraResponseMediaType
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function validImageBytes(
|
|
477
|
+
bytes: Uint8Array,
|
|
478
|
+
type: SlopcameraResponseMediaType,
|
|
479
|
+
): boolean {
|
|
480
|
+
if (
|
|
481
|
+
bytes.byteLength < 12 ||
|
|
482
|
+
bytes.byteLength > slopcameraMaximumRawImageBytes
|
|
483
|
+
) {
|
|
484
|
+
return false
|
|
485
|
+
}
|
|
486
|
+
if (type === "image/png") {
|
|
487
|
+
return Buffer.from(bytes.subarray(0, 8)).equals(
|
|
488
|
+
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
|
489
|
+
)
|
|
490
|
+
}
|
|
491
|
+
if (type === "image/jpeg") {
|
|
492
|
+
return (
|
|
493
|
+
bytes[0] === 0xff &&
|
|
494
|
+
bytes[1] === 0xd8 &&
|
|
495
|
+
bytes.at(-2) === 0xff &&
|
|
496
|
+
bytes.at(-1) === 0xd9
|
|
497
|
+
)
|
|
498
|
+
}
|
|
499
|
+
return (
|
|
500
|
+
bytes.byteLength >= 16 &&
|
|
501
|
+
Buffer.from(bytes.subarray(0, 4)).toString("ascii") === "RIFF" &&
|
|
502
|
+
Buffer.from(bytes).readUInt32LE(4) === bytes.byteLength - 8 &&
|
|
503
|
+
Buffer.from(bytes.subarray(8, 12)).toString("ascii") === "WEBP" &&
|
|
504
|
+
["VP8 ", "VP8L", "VP8X"].includes(
|
|
505
|
+
Buffer.from(bytes.subarray(12, 16)).toString("ascii"),
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function warningReceipt(value: unknown): string {
|
|
511
|
+
let type = "provider-warning"
|
|
512
|
+
let detail = "Provider warning"
|
|
513
|
+
if (typeof value === "string") {
|
|
514
|
+
detail = value
|
|
515
|
+
} else if (isObject(value)) {
|
|
516
|
+
if (typeof value.type === "string" && value.type.length <= 64) {
|
|
517
|
+
type = value.type.replace(/[^a-z0-9._-]/giu, "-") || type
|
|
518
|
+
}
|
|
519
|
+
if (typeof value.message === "string") detail = value.message
|
|
520
|
+
else if (typeof value.details === "string") detail = value.details
|
|
521
|
+
}
|
|
522
|
+
return `${type} sha256:${createHash("sha256").update(detail).digest("hex")}`
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function parseResult(
|
|
526
|
+
value: unknown,
|
|
527
|
+
model: string,
|
|
528
|
+
): Readonly<{ bytes: Uint8Array; response: GeneratedSlopcameraImage }> {
|
|
529
|
+
if (!isObject(value) || !Array.isArray(value.images) || value.images.length !== 1) {
|
|
530
|
+
throw new SlopcameraCloudError(
|
|
531
|
+
"GENERATION_INVALID_RESPONSE",
|
|
532
|
+
"Vercel AI Gateway did not return exactly one image.",
|
|
533
|
+
)
|
|
534
|
+
}
|
|
535
|
+
const image = value.images[0]
|
|
536
|
+
if (!isObject(image) || !(image.uint8Array instanceof Uint8Array)) {
|
|
537
|
+
throw new SlopcameraCloudError(
|
|
538
|
+
"GENERATION_INVALID_RESPONSE",
|
|
539
|
+
"Vercel AI Gateway returned an invalid bounded image.",
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
const type = mediaType(image.mediaType)
|
|
543
|
+
if (!validImageBytes(image.uint8Array, type)) {
|
|
544
|
+
throw new SlopcameraCloudError(
|
|
545
|
+
"GENERATION_INVALID_RESPONSE",
|
|
546
|
+
"Vercel AI Gateway returned an invalid bounded image.",
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
const gateway = isObject(value.providerMetadata) &&
|
|
550
|
+
isObject(value.providerMetadata.gateway)
|
|
551
|
+
? value.providerMetadata.gateway
|
|
552
|
+
: undefined
|
|
553
|
+
const foreignGenerationId = gateway !== undefined &&
|
|
554
|
+
typeof gateway.generationId === "string" &&
|
|
555
|
+
gateway.generationId.length > 0 &&
|
|
556
|
+
gateway.generationId.length <= 256 &&
|
|
557
|
+
!/[\u0000-\u001f\u007f]/u.test(gateway.generationId)
|
|
558
|
+
? gateway.generationId
|
|
559
|
+
: randomUUID()
|
|
560
|
+
const requestId = `sha256:${createHash("sha256")
|
|
561
|
+
.update("slopcamera.gateway-generation-id/v1\0")
|
|
562
|
+
.update(foreignGenerationId)
|
|
563
|
+
.digest("hex")}`
|
|
564
|
+
const warnings = Array.isArray(value.warnings)
|
|
565
|
+
? value.warnings.slice(0, 100).map(warningReceipt)
|
|
566
|
+
: []
|
|
567
|
+
return {
|
|
568
|
+
bytes: image.uint8Array,
|
|
569
|
+
response: {
|
|
570
|
+
image: {
|
|
571
|
+
base64: Buffer.from(image.uint8Array).toString("base64"),
|
|
572
|
+
mediaType: type,
|
|
573
|
+
},
|
|
574
|
+
model,
|
|
575
|
+
provider: "vercel-ai-gateway",
|
|
576
|
+
requestId,
|
|
577
|
+
warnings,
|
|
578
|
+
},
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async function performGeneration(
|
|
583
|
+
input: GenerateSlopcameraImageInput,
|
|
584
|
+
dependencies: SlopcameraGenerateDependencies,
|
|
585
|
+
): Promise<Readonly<{
|
|
586
|
+
bytes: Uint8Array
|
|
587
|
+
response: GeneratedSlopcameraImage
|
|
588
|
+
}>> {
|
|
589
|
+
const model = validateModel(input.model)
|
|
590
|
+
const prompt = validatePrompt(input.prompt)
|
|
591
|
+
const credential = resolveGatewayCredential(dependencies.environment)
|
|
592
|
+
const timeout = combineSignals(input.signal, validateTimeout(input.timeoutMs))
|
|
593
|
+
try {
|
|
594
|
+
const generation = (async () => {
|
|
595
|
+
assertGenerationActive(timeout.signal)
|
|
596
|
+
// Raw AI SDK warnings may contain provider-controlled details. Slopcamera
|
|
597
|
+
// emits only allowlisted warning kinds and message hashes.
|
|
598
|
+
disableAiSdkWarningLogging()
|
|
599
|
+
const runtime = await (dependencies.loadRuntime ?? loadDefaultGatewayRuntime)()
|
|
600
|
+
assertGenerationActive(timeout.signal)
|
|
601
|
+
const provider = runtime.createGateway({
|
|
602
|
+
apiKey: credential.token,
|
|
603
|
+
baseURL: slopcameraGatewayApiBaseUrl,
|
|
604
|
+
fetch: createFixedGatewayFetch({
|
|
605
|
+
...(dependencies.fetch === undefined ? {} : { fetch: dependencies.fetch }),
|
|
606
|
+
...(dependencies.maximumResponseBytes === undefined
|
|
607
|
+
? {}
|
|
608
|
+
: { maximumResponseBytes: dependencies.maximumResponseBytes }),
|
|
609
|
+
}),
|
|
610
|
+
})
|
|
611
|
+
assertGenerationActive(timeout.signal)
|
|
612
|
+
const generated = await runtime.generateImage({
|
|
613
|
+
abortSignal: timeout.signal,
|
|
614
|
+
maxRetries: 0,
|
|
615
|
+
model: provider.imageModel(model),
|
|
616
|
+
n: 1,
|
|
617
|
+
prompt,
|
|
618
|
+
})
|
|
619
|
+
return parseResult(generated, model)
|
|
620
|
+
})()
|
|
621
|
+
return await Promise.race([generation, timeout.interruption])
|
|
622
|
+
} catch (error) {
|
|
623
|
+
if (error instanceof SlopcameraCloudError) throw error
|
|
624
|
+
throw new SlopcameraCloudError(
|
|
625
|
+
"GENERATION_FAILED",
|
|
626
|
+
"Vercel AI Gateway image generation failed; the request was not retried.",
|
|
627
|
+
)
|
|
628
|
+
} finally {
|
|
629
|
+
timeout.dispose()
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export async function generateSlopcameraImage(
|
|
634
|
+
input: GenerateSlopcameraImageInput,
|
|
635
|
+
dependencies: SlopcameraGenerateDependencies = {},
|
|
636
|
+
): Promise<GeneratedSlopcameraImage> {
|
|
637
|
+
return (await performGeneration(input, dependencies)).response
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function expectedMediaType(outputPath: string): SlopcameraResponseMediaType {
|
|
641
|
+
const extension = extname(outputPath).toLocaleLowerCase("en-US")
|
|
642
|
+
if (extension === ".png") return "image/png"
|
|
643
|
+
if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg"
|
|
644
|
+
if (extension === ".webp") return "image/webp"
|
|
645
|
+
invalidArgument("Output path must end in .png, .jpg, .jpeg, or .webp.")
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function atomicImageWrite(
|
|
649
|
+
outputPath: string,
|
|
650
|
+
bytes: Uint8Array,
|
|
651
|
+
): Promise<string> {
|
|
652
|
+
const absolutePath = resolve(outputPath)
|
|
653
|
+
const temporaryPath = resolve(
|
|
654
|
+
dirname(absolutePath),
|
|
655
|
+
`.${randomUUID()}.slopcamera-generate.tmp`,
|
|
656
|
+
)
|
|
657
|
+
try {
|
|
658
|
+
await writeFile(temporaryPath, bytes, { flag: "wx" })
|
|
659
|
+
// A same-directory hard link atomically publishes only when the output is
|
|
660
|
+
// absent. Concurrent writers cannot replace one another's selected asset.
|
|
661
|
+
await link(temporaryPath, absolutePath)
|
|
662
|
+
return absolutePath
|
|
663
|
+
} catch {
|
|
664
|
+
throw new SlopcameraCloudError(
|
|
665
|
+
"OUTPUT_WRITE_FAILED",
|
|
666
|
+
"Slopcamera could not atomically write the generated image.",
|
|
667
|
+
)
|
|
668
|
+
} finally {
|
|
669
|
+
await rm(temporaryPath, { force: true }).catch(() => undefined)
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export async function generateSlopcameraImageFile(
|
|
674
|
+
input: GenerateSlopcameraImageInput & { readonly outputPath: string },
|
|
675
|
+
dependencies: SlopcameraGenerateDependencies = {},
|
|
676
|
+
): Promise<GeneratedSlopcameraImageFile> {
|
|
677
|
+
if (
|
|
678
|
+
typeof input.outputPath !== "string" ||
|
|
679
|
+
input.outputPath.length < 1 ||
|
|
680
|
+
input.outputPath.length > 4_096 ||
|
|
681
|
+
input.outputPath.includes("\0")
|
|
682
|
+
) {
|
|
683
|
+
invalidArgument("Output path must be a non-empty local path.")
|
|
684
|
+
}
|
|
685
|
+
const expected = expectedMediaType(input.outputPath)
|
|
686
|
+
const generated = await performGeneration(input, dependencies)
|
|
687
|
+
if (generated.response.image.mediaType !== expected) {
|
|
688
|
+
throw new SlopcameraCloudError(
|
|
689
|
+
"GENERATION_INVALID_RESPONSE",
|
|
690
|
+
`Generated ${generated.response.image.mediaType} does not match the requested ${expected} output path.`,
|
|
691
|
+
)
|
|
692
|
+
}
|
|
693
|
+
const outputPath = await atomicImageWrite(input.outputPath, generated.bytes)
|
|
694
|
+
return {
|
|
695
|
+
bytes: generated.bytes.byteLength,
|
|
696
|
+
mediaType: generated.response.image.mediaType,
|
|
697
|
+
model: generated.response.model,
|
|
698
|
+
outputPath,
|
|
699
|
+
provider: generated.response.provider,
|
|
700
|
+
requestId: generated.response.requestId,
|
|
701
|
+
sha256: createHash("sha256").update(generated.bytes).digest("hex"),
|
|
702
|
+
warnings: generated.response.warnings,
|
|
703
|
+
}
|
|
704
|
+
}
|