@nodetool-ai/websocket 0.8.2 → 0.8.4-a24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/.nodetool-build-stamp +1 -1
- package/dist/agent/run-node-tool.d.ts.map +1 -1
- package/dist/agent/run-node-tool.js +11 -1
- package/dist/agent/run-node-tool.js.map +1 -1
- package/dist/chat-prediction-forwarder.d.ts +5 -3
- package/dist/chat-prediction-forwarder.d.ts.map +1 -1
- package/dist/chat-prediction-forwarder.js +6 -4
- package/dist/chat-prediction-forwarder.js.map +1 -1
- package/dist/chat-turn-registry.d.ts +2 -1
- package/dist/chat-turn-registry.d.ts.map +1 -1
- package/dist/chat-turn-registry.js +8 -1
- package/dist/chat-turn-registry.js.map +1 -1
- package/dist/collection-api.d.ts.map +1 -1
- package/dist/collection-api.js +3 -5
- package/dist/collection-api.js.map +1 -1
- package/dist/custom-providers.d.ts.map +1 -1
- package/dist/custom-providers.js +6 -1
- package/dist/custom-providers.js.map +1 -1
- package/dist/e2e-server.js +1 -1
- package/dist/e2e-server.js.map +1 -1
- package/dist/example-workflows.d.ts.map +1 -1
- package/dist/example-workflows.js +7 -1
- package/dist/example-workflows.js.map +1 -1
- package/dist/fake-runtime.d.ts +54 -8
- package/dist/fake-runtime.d.ts.map +1 -1
- package/dist/fake-runtime.js +228 -15
- package/dist/fake-runtime.js.map +1 -1
- package/dist/file-api.d.ts.map +1 -1
- package/dist/file-api.js +6 -8
- package/dist/file-api.js.map +1 -1
- package/dist/generation-recovery.d.ts.map +1 -1
- package/dist/generation-recovery.js +7 -4
- package/dist/generation-recovery.js.map +1 -1
- package/dist/headless-job-runner.d.ts.map +1 -1
- package/dist/headless-job-runner.js +8 -2
- package/dist/headless-job-runner.js.map +1 -1
- package/dist/http-api.d.ts +4 -2
- package/dist/http-api.d.ts.map +1 -1
- package/dist/http-api.js +67 -16
- package/dist/http-api.js.map +1 -1
- package/dist/http-body-schemas.d.ts +2 -0
- package/dist/http-body-schemas.d.ts.map +1 -1
- package/dist/http-body-schemas.js +8 -1
- package/dist/http-body-schemas.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/job-control.d.ts +8 -0
- package/dist/job-control.d.ts.map +1 -1
- package/dist/job-control.js +12 -0
- package/dist/job-control.js.map +1 -1
- package/dist/lib/admin.d.ts +7 -0
- package/dist/lib/admin.d.ts.map +1 -0
- package/dist/lib/admin.js +19 -0
- package/dist/lib/admin.js.map +1 -0
- package/dist/lib/app-instances-service.d.ts +35 -0
- package/dist/lib/app-instances-service.d.ts.map +1 -1
- package/dist/lib/app-instances-service.js +3 -4
- package/dist/lib/app-instances-service.js.map +1 -1
- package/dist/lib/app-project-export.d.ts +12 -0
- package/dist/lib/app-project-export.d.ts.map +1 -0
- package/dist/lib/app-project-export.js +42 -0
- package/dist/lib/app-project-export.js.map +1 -0
- package/dist/lib/applications-service.d.ts.map +1 -1
- package/dist/lib/applications-service.js +7 -2
- package/dist/lib/applications-service.js.map +1 -1
- package/dist/lib/asset-export.d.ts +7 -3
- package/dist/lib/asset-export.d.ts.map +1 -1
- package/dist/lib/asset-export.js +16 -8
- package/dist/lib/asset-export.js.map +1 -1
- package/dist/lib/asset-paths.d.ts +1 -1
- package/dist/lib/asset-paths.js +1 -1
- package/dist/lib/asset-response.d.ts.map +1 -1
- package/dist/lib/asset-response.js +13 -6
- package/dist/lib/asset-response.js.map +1 -1
- package/dist/lib/cloud-profile.d.ts +10 -0
- package/dist/lib/cloud-profile.d.ts.map +1 -0
- package/dist/lib/cloud-profile.js +13 -0
- package/dist/lib/cloud-profile.js.map +1 -0
- package/dist/lib/example-apps.d.ts +3 -0
- package/dist/lib/example-apps.d.ts.map +1 -1
- package/dist/lib/example-apps.js +3 -2
- package/dist/lib/example-apps.js.map +1 -1
- package/dist/lib/http-user-id.d.ts +2 -0
- package/dist/lib/http-user-id.d.ts.map +1 -0
- package/dist/lib/http-user-id.js +4 -0
- package/dist/lib/http-user-id.js.map +1 -0
- package/dist/lib/json-body.d.ts +11 -0
- package/dist/lib/json-body.d.ts.map +1 -0
- package/dist/lib/json-body.js +13 -0
- package/dist/lib/json-body.js.map +1 -0
- package/dist/lib/project-archive-host.d.ts +14 -0
- package/dist/lib/project-archive-host.d.ts.map +1 -0
- package/dist/lib/project-archive-host.js +24 -0
- package/dist/lib/project-archive-host.js.map +1 -0
- package/dist/lib/project-archive.d.ts +103 -0
- package/dist/lib/project-archive.d.ts.map +1 -0
- package/dist/lib/project-archive.js +610 -0
- package/dist/lib/project-archive.js.map +1 -0
- package/dist/lib/public-routes.d.ts +15 -0
- package/dist/lib/public-routes.d.ts.map +1 -1
- package/dist/lib/public-routes.js +27 -0
- package/dist/lib/public-routes.js.map +1 -1
- package/dist/lib/public-workflow-share.d.ts +12 -1
- package/dist/lib/public-workflow-share.d.ts.map +1 -1
- package/dist/lib/public-workflow-share.js +45 -2
- package/dist/lib/public-workflow-share.js.map +1 -1
- package/dist/lib/python-metadata-roots.d.ts +27 -0
- package/dist/lib/python-metadata-roots.d.ts.map +1 -0
- package/dist/lib/python-metadata-roots.js +92 -0
- package/dist/lib/python-metadata-roots.js.map +1 -0
- package/dist/lib/runtime-detection.d.ts +1 -1
- package/dist/lib/runtime-detection.d.ts.map +1 -1
- package/dist/lib/runtime-detection.js +2 -1
- package/dist/lib/runtime-detection.js.map +1 -1
- package/dist/lib/server-auth-token.d.ts +18 -0
- package/dist/lib/server-auth-token.d.ts.map +1 -0
- package/dist/lib/server-auth-token.js +28 -0
- package/dist/lib/server-auth-token.js.map +1 -0
- package/dist/lib/thumbnail.d.ts.map +1 -1
- package/dist/lib/thumbnail.js +3 -1
- package/dist/lib/thumbnail.js.map +1 -1
- package/dist/lib/timeline-bundle.d.ts +70 -0
- package/dist/lib/timeline-bundle.d.ts.map +1 -1
- package/dist/lib/timeline-bundle.js +28 -10
- package/dist/lib/timeline-bundle.js.map +1 -1
- package/dist/lib/timeline-media-export.d.ts +18 -0
- package/dist/lib/timeline-media-export.d.ts.map +1 -0
- package/dist/lib/timeline-media-export.js +66 -0
- package/dist/lib/timeline-media-export.js.map +1 -0
- package/dist/lib/timeline-premiere-import.d.ts +24 -0
- package/dist/lib/timeline-premiere-import.d.ts.map +1 -0
- package/dist/lib/timeline-premiere-import.js +112 -0
- package/dist/lib/timeline-premiere-import.js.map +1 -0
- package/dist/lib/timeline-premiere.d.ts +38 -0
- package/dist/lib/timeline-premiere.d.ts.map +1 -0
- package/dist/lib/timeline-premiere.js +87 -0
- package/dist/lib/timeline-premiere.js.map +1 -0
- package/dist/lib/zip-stream.d.ts +3 -0
- package/dist/lib/zip-stream.d.ts.map +1 -1
- package/dist/lib/zip-stream.js +6 -0
- package/dist/lib/zip-stream.js.map +1 -1
- package/dist/mcp-agent-tools.d.ts +48 -0
- package/dist/mcp-agent-tools.d.ts.map +1 -1
- package/dist/mcp-agent-tools.js +15 -2
- package/dist/mcp-agent-tools.js.map +1 -1
- package/dist/mcp-server.d.ts +7 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +163 -16
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-skill-prompts.d.ts +23 -0
- package/dist/mcp-skill-prompts.d.ts.map +1 -0
- package/dist/mcp-skill-prompts.js +91 -0
- package/dist/mcp-skill-prompts.js.map +1 -0
- package/dist/mcp-tool-deps.d.ts.map +1 -1
- package/dist/mcp-tool-deps.js +8 -1
- package/dist/mcp-tool-deps.js.map +1 -1
- package/dist/model-download-runtime.d.ts.map +1 -1
- package/dist/model-download-runtime.js +2 -1
- package/dist/model-download-runtime.js.map +1 -1
- package/dist/models-api.d.ts.map +1 -1
- package/dist/models-api.js +6 -2
- package/dist/models-api.js.map +1 -1
- package/dist/node-registry-setup.d.ts +8 -5
- package/dist/node-registry-setup.d.ts.map +1 -1
- package/dist/node-registry-setup.js +22 -10
- package/dist/node-registry-setup.js.map +1 -1
- package/dist/oauth-api.d.ts.map +1 -1
- package/dist/oauth-api.js +45 -23
- package/dist/oauth-api.js.map +1 -1
- package/dist/openai-api.d.ts.map +1 -1
- package/dist/openai-api.js +10 -2
- package/dist/openai-api.js.map +1 -1
- package/dist/pack-source.d.ts +15 -0
- package/dist/pack-source.d.ts.map +1 -0
- package/dist/pack-source.js +85 -0
- package/dist/pack-source.js.map +1 -0
- package/dist/plugins/websocket.d.ts.map +1 -1
- package/dist/plugins/websocket.js +31 -5
- package/dist/plugins/websocket.js.map +1 -1
- package/dist/routes/applications.d.ts +1 -0
- package/dist/routes/applications.d.ts.map +1 -1
- package/dist/routes/applications.js +24 -0
- package/dist/routes/applications.js.map +1 -1
- package/dist/routes/assets.d.ts.map +1 -1
- package/dist/routes/assets.js +31 -41
- package/dist/routes/assets.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +6 -0
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/documents.d.ts.map +1 -1
- package/dist/routes/documents.js +32 -1
- package/dist/routes/documents.js.map +1 -1
- package/dist/routes/fal-pricing-estimate.d.ts.map +1 -1
- package/dist/routes/fal-pricing-estimate.js +2 -0
- package/dist/routes/fal-pricing-estimate.js.map +1 -1
- package/dist/routes/files.d.ts.map +1 -1
- package/dist/routes/files.js +2 -0
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/games.d.ts +38 -0
- package/dist/routes/games.d.ts.map +1 -0
- package/dist/routes/games.js +159 -0
- package/dist/routes/games.js.map +1 -0
- package/dist/routes/integrations.d.ts.map +1 -1
- package/dist/routes/integrations.js +22 -26
- package/dist/routes/integrations.js.map +1 -1
- package/dist/routes/js-scripts.d.ts +1 -1
- package/dist/routes/js-scripts.d.ts.map +1 -1
- package/dist/routes/js-scripts.js +1 -1
- package/dist/routes/js-scripts.js.map +1 -1
- package/dist/routes/kie-pricing.js +1 -1
- package/dist/routes/kie-pricing.js.map +1 -1
- package/dist/routes/nodes.d.ts.map +1 -1
- package/dist/routes/nodes.js +13 -1
- package/dist/routes/nodes.js.map +1 -1
- package/dist/routes/project-archive.d.ts +18 -0
- package/dist/routes/project-archive.d.ts.map +1 -0
- package/dist/routes/project-archive.js +98 -0
- package/dist/routes/project-archive.js.map +1 -0
- package/dist/routes/storage.d.ts.map +1 -1
- package/dist/routes/storage.js +2 -0
- package/dist/routes/storage.js.map +1 -1
- package/dist/routes/storyboards.d.ts.map +1 -1
- package/dist/routes/storyboards.js +6 -3
- package/dist/routes/storyboards.js.map +1 -1
- package/dist/routes/timelines.d.ts +8 -4
- package/dist/routes/timelines.d.ts.map +1 -1
- package/dist/routes/timelines.js +209 -16
- package/dist/routes/timelines.js.map +1 -1
- package/dist/screenshot-docs-seed.d.ts +24 -0
- package/dist/screenshot-docs-seed.d.ts.map +1 -0
- package/dist/screenshot-docs-seed.js +421 -0
- package/dist/screenshot-docs-seed.js.map +1 -0
- package/dist/screenshot-projects.d.ts.map +1 -1
- package/dist/screenshot-projects.js +9 -9
- package/dist/screenshot-projects.js.map +1 -1
- package/dist/screenshot-server.js +274 -17
- package/dist/screenshot-server.js.map +1 -1
- package/dist/sdk/sdk-node-type-inventory-service.d.ts +1 -1
- package/dist/sdk/sdk-node-type-inventory-service.d.ts.map +1 -1
- package/dist/sdk/sdk-node-type-inventory-service.js +4 -4
- package/dist/sdk/sdk-node-type-inventory-service.js.map +1 -1
- package/dist/sdk/sdk-v1-service.js +1 -1
- package/dist/sdk/sdk-v1-service.js.map +1 -1
- package/dist/server.d.ts +7 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1482 -1487
- package/dist/server.js.map +1 -1
- package/dist/session/chat-prompt.d.ts.map +1 -1
- package/dist/session/chat-prompt.js +6 -0
- package/dist/session/chat-prompt.js.map +1 -1
- package/dist/session/chat-turn.d.ts +10 -0
- package/dist/session/chat-turn.d.ts.map +1 -1
- package/dist/session/chat-turn.js +103 -20
- package/dist/session/chat-turn.js.map +1 -1
- package/dist/session/client-tool-gate.d.ts +23 -0
- package/dist/session/client-tool-gate.d.ts.map +1 -0
- package/dist/session/client-tool-gate.js +59 -0
- package/dist/session/client-tool-gate.js.map +1 -0
- package/dist/session/commands.d.ts +14 -0
- package/dist/session/commands.d.ts.map +1 -1
- package/dist/session/commands.js +113 -14
- package/dist/session/commands.js.map +1 -1
- package/dist/session/inference.d.ts +25 -2
- package/dist/session/inference.d.ts.map +1 -1
- package/dist/session/inference.js +105 -21
- package/dist/session/inference.js.map +1 -1
- package/dist/session/job-execution.d.ts +0 -1
- package/dist/session/job-execution.d.ts.map +1 -1
- package/dist/session/job-execution.js +43 -39
- package/dist/session/job-execution.js.map +1 -1
- package/dist/storage-api.d.ts +0 -4
- package/dist/storage-api.d.ts.map +1 -1
- package/dist/storage-api.js +3 -26
- package/dist/storage-api.js.map +1 -1
- package/dist/test-ui-server.d.ts +15 -3
- package/dist/test-ui-server.d.ts.map +1 -1
- package/dist/test-ui-server.js +74 -12
- package/dist/test-ui-server.js.map +1 -1
- package/dist/triggers/dispatcher.d.ts.map +1 -1
- package/dist/triggers/dispatcher.js +24 -4
- package/dist/triggers/dispatcher.js.map +1 -1
- package/dist/triggers/file-watch.d.ts +2 -2
- package/dist/triggers/file-watch.d.ts.map +1 -1
- package/dist/triggers/file-watch.js +44 -21
- package/dist/triggers/file-watch.js.map +1 -1
- package/dist/triggers/scheduler.d.ts.map +1 -1
- package/dist/triggers/scheduler.js +7 -5
- package/dist/triggers/scheduler.js.map +1 -1
- package/dist/trpc/error-formatter.d.ts.map +1 -1
- package/dist/trpc/error-formatter.js +2 -1
- package/dist/trpc/error-formatter.js.map +1 -1
- package/dist/trpc/routers/app-instances.d.ts +63 -0
- package/dist/trpc/routers/app-instances.d.ts.map +1 -1
- package/dist/trpc/routers/app-runs.d.ts +28 -0
- package/dist/trpc/routers/app-runs.d.ts.map +1 -1
- package/dist/trpc/routers/applications.d.ts +7 -0
- package/dist/trpc/routers/applications.d.ts.map +1 -1
- package/dist/trpc/routers/assets.d.ts +8 -0
- package/dist/trpc/routers/assets.d.ts.map +1 -1
- package/dist/trpc/routers/assets.js +16 -5
- package/dist/trpc/routers/assets.js.map +1 -1
- package/dist/trpc/routers/collections.d.ts.map +1 -1
- package/dist/trpc/routers/collections.js +16 -10
- package/dist/trpc/routers/collections.js.map +1 -1
- package/dist/trpc/routers/credits.d.ts.map +1 -1
- package/dist/trpc/routers/credits.js +11 -1
- package/dist/trpc/routers/credits.js.map +1 -1
- package/dist/trpc/routers/extension.d.ts +17 -3
- package/dist/trpc/routers/extension.d.ts.map +1 -1
- package/dist/trpc/routers/extension.js +40 -7
- package/dist/trpc/routers/extension.js.map +1 -1
- package/dist/trpc/routers/gameDraftHistory.d.ts +1 -2
- package/dist/trpc/routers/gameDraftHistory.d.ts.map +1 -1
- package/dist/trpc/routers/gameDraftHistory.js +1 -1
- package/dist/trpc/routers/gameDraftHistory.js.map +1 -1
- package/dist/trpc/routers/games.d.ts +20478 -24667
- package/dist/trpc/routers/games.d.ts.map +1 -1
- package/dist/trpc/routers/games.js +325 -143
- package/dist/trpc/routers/games.js.map +1 -1
- package/dist/trpc/routers/jobs.d.ts +7 -0
- package/dist/trpc/routers/jobs.d.ts.map +1 -1
- package/dist/trpc/routers/jobs.js +10 -3
- package/dist/trpc/routers/jobs.js.map +1 -1
- package/dist/trpc/routers/js-scripts.d.ts.map +1 -1
- package/dist/trpc/routers/js-scripts.js +18 -2
- package/dist/trpc/routers/js-scripts.js.map +1 -1
- package/dist/trpc/routers/messages.d.ts +10 -0
- package/dist/trpc/routers/messages.d.ts.map +1 -1
- package/dist/trpc/routers/messages.js +16 -2
- package/dist/trpc/routers/messages.js.map +1 -1
- package/dist/trpc/routers/models.d.ts.map +1 -1
- package/dist/trpc/routers/models.js +58 -37
- package/dist/trpc/routers/models.js.map +1 -1
- package/dist/trpc/routers/packs.d.ts.map +1 -1
- package/dist/trpc/routers/packs.js +19 -5
- package/dist/trpc/routers/packs.js.map +1 -1
- package/dist/trpc/routers/projects.d.ts +66 -2
- package/dist/trpc/routers/projects.d.ts.map +1 -1
- package/dist/trpc/routers/projects.js +19 -2
- package/dist/trpc/routers/projects.js.map +1 -1
- package/dist/trpc/routers/settings.d.ts.map +1 -1
- package/dist/trpc/routers/settings.js +30 -5
- package/dist/trpc/routers/settings.js.map +1 -1
- package/dist/trpc/routers/sketch.d.ts +30 -0
- package/dist/trpc/routers/sketch.d.ts.map +1 -1
- package/dist/trpc/routers/sketch.js +2 -2
- package/dist/trpc/routers/sketch.js.map +1 -1
- package/dist/trpc/routers/storyboards.d.ts +171 -1
- package/dist/trpc/routers/storyboards.d.ts.map +1 -1
- package/dist/trpc/routers/storyboards.js +8 -5
- package/dist/trpc/routers/storyboards.js.map +1 -1
- package/dist/trpc/routers/threads.d.ts.map +1 -1
- package/dist/trpc/routers/threads.js +11 -2
- package/dist/trpc/routers/threads.js.map +1 -1
- package/dist/trpc/routers/timeline.d.ts +379 -0
- package/dist/trpc/routers/timeline.d.ts.map +1 -1
- package/dist/trpc/routers/timeline.js +15 -2
- package/dist/trpc/routers/timeline.js.map +1 -1
- package/dist/trpc/routers/triggers.d.ts.map +1 -1
- package/dist/trpc/routers/triggers.js +18 -3
- package/dist/trpc/routers/triggers.js.map +1 -1
- package/dist/trpc/routers/users.d.ts +3 -2
- package/dist/trpc/routers/users.d.ts.map +1 -1
- package/dist/trpc/routers/users.js +25 -27
- package/dist/trpc/routers/users.js.map +1 -1
- package/dist/trpc/routers/worker.d.ts.map +1 -1
- package/dist/trpc/routers/worker.js +37 -14
- package/dist/trpc/routers/worker.js.map +1 -1
- package/dist/trpc/routers/workflows.d.ts +190 -0
- package/dist/trpc/routers/workflows.d.ts.map +1 -1
- package/dist/trpc/routers/workflows.js +56 -15
- package/dist/trpc/routers/workflows.js.map +1 -1
- package/dist/trpc/sandbox-coverage.d.ts.map +1 -1
- package/dist/trpc/sandbox-coverage.js +34 -0
- package/dist/trpc/sandbox-coverage.js.map +1 -1
- package/dist/websocket-client-session.d.ts +2 -2
- package/dist/websocket-client-session.d.ts.map +1 -1
- package/dist/websocket-client-session.js +9 -3
- package/dist/websocket-client-session.js.map +1 -1
- package/dist/worker-settings-env.d.ts +23 -0
- package/dist/worker-settings-env.d.ts.map +1 -0
- package/dist/worker-settings-env.js +47 -0
- package/dist/worker-settings-env.js.map +1 -0
- package/dist/workflow-interface-service.d.ts +7 -0
- package/dist/workflow-interface-service.d.ts.map +1 -1
- package/package.json +51 -46
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import
|
|
4
|
+
import { loadSharpOrThrow } from "@nodetool-ai/runtime";
|
|
5
|
+
import { importPack } from "@nodetool-ai/node-catalog";
|
|
5
6
|
import { createLogger, getManagedWorkspaceDir, managedWorkspaceKey, workspaceStorageKind } from "@nodetool-ai/config";
|
|
6
|
-
import { AmbiguousGameIdError, InvalidGameDocumentError, Asset, Game, Project, Workspace } from "@nodetool-ai/models";
|
|
7
|
-
import { exampleGameSummary, anyGameAssetBinding, gameAssetBinding, gameAssetBinding3D, gamePreparedCollider3D, gameModelImportSettings3D, parseGameDocument, anyGameDocument
|
|
8
|
-
import { createTopDownRoomGame, createNative3DGame, anyGameDocumentOp
|
|
9
|
-
import {
|
|
10
|
-
import { createCapabilityRun, gateFromContext } from "@nodetool-ai/agents";
|
|
7
|
+
import { AmbiguousGameIdError, InvalidGameDocumentError, MissingGameDraftSourceError, Asset, Game, Project, Workspace } from "@nodetool-ai/models";
|
|
8
|
+
import { exampleGameSummary, anyGameAssetBinding, gameAssetBinding, gameAssetBinding3D, gameAssetSiblingRebinds, gamePreparedCollider3D, gameModelImportSettings3D, parseGameDocument, anyGameDocument, installExampleGameInput, stagedGameCandidateRecord, stagedGameCandidateRecordPath } from "@nodetool-ai/protocol";
|
|
9
|
+
import { createTopDownRoomGame, createNative3DGame, anyGameDocumentOp, GameOpError, decodePreparedGameCollider3D, trackGameAuthoringEdits, validateAnyGame } from "@nodetool-ai/game-runtime";
|
|
10
|
+
import { contextSecretAvailability, createCapabilityRun, gateFromContext } from "@nodetool-ai/agents";
|
|
11
11
|
import { gameAuthoringCandidate, gameAuthoringProgram, gameAuthoringConflict } from "@nodetool-ai/protocol";
|
|
12
12
|
import { PERMISSION_GATE_CONTEXT_KEY, ProcessingContext, headlessGate } from "@nodetool-ai/runtime";
|
|
13
13
|
import { getAssetAdapter } from "../../lib/storage.js";
|
|
14
14
|
import { getAssetStorageKey, retrieveAssetBytes } from "../../lib/asset-paths.js";
|
|
15
15
|
import { getExampleGameBundle, installExampleGameAssets, listExampleGames } from "../../lib/example-games.js";
|
|
16
16
|
import { workspaceFromRow } from "../../lib/workflow-workspace.js";
|
|
17
|
+
import { loadConfiguredProviders } from "../../configured-providers.js";
|
|
18
|
+
import { createRuntimeContext } from "../../session/model-interfaces.js";
|
|
17
19
|
import { ApiErrorCode } from "../../error-codes.js";
|
|
18
20
|
import { router } from "../index.js";
|
|
19
21
|
import { protectedProcedure } from "../middleware.js";
|
|
20
22
|
import { throwApiError } from "../error-formatter.js";
|
|
21
23
|
import { boundedGameDraftHistory, MAX_GAME_DRAFT_OPS } from "./gameDraftHistory.js";
|
|
22
24
|
const log = createLogger("nodetool.games");
|
|
25
|
+
const gameProcedure = protectedProcedure.use(async ({ next }) => {
|
|
26
|
+
const result = await next();
|
|
27
|
+
if (!result.ok && result.error.cause instanceof MissingGameDraftSourceError) {
|
|
28
|
+
throwApiError(ApiErrorCode.SERVICE_UNAVAILABLE, result.error.cause.message, "PRECONDITION_FAILED");
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
});
|
|
23
32
|
const idInput = z.object({ id: z.string() });
|
|
24
33
|
const gameInfo = z.object({
|
|
25
34
|
id: z.string(),
|
|
@@ -32,6 +41,8 @@ const gameInfo = z.object({
|
|
|
32
41
|
createdAt: z.string(),
|
|
33
42
|
updatedAt: z.string()
|
|
34
43
|
});
|
|
44
|
+
const gameDocument = anyGameDocument;
|
|
45
|
+
const gameDocumentOp = anyGameDocumentOp;
|
|
35
46
|
const gameWithDocument = z.object({ game: gameInfo, document: gameDocument });
|
|
36
47
|
const authoringPreview = z.object({
|
|
37
48
|
candidate: gameAuthoringCandidate,
|
|
@@ -41,6 +52,27 @@ const authoringPreview = z.object({
|
|
|
41
52
|
changed_dependencies: z.array(z.string()),
|
|
42
53
|
restart_required: z.boolean()
|
|
43
54
|
});
|
|
55
|
+
const assetBrowserInfo = z.object({
|
|
56
|
+
dimension: z.enum(["2d", "3d"]),
|
|
57
|
+
draft_updated_at: z.string(),
|
|
58
|
+
candidate_workspace_id: z.string(),
|
|
59
|
+
candidates: z.array(z.object({
|
|
60
|
+
digest: z.string(), extension: z.string(), media_kind: z.enum(["image", "audio", "font", "model", "collider"]), path: z.string(),
|
|
61
|
+
size: z.number(), modified_at: z.string(), bound_slots: z.array(z.string()), slot: z.string().optional(), prompt: z.string().optional(),
|
|
62
|
+
source: z.string().optional(), recorded: z.boolean()
|
|
63
|
+
})),
|
|
64
|
+
slot_requests: z.record(z.string(), z.object({ kind: z.string(), prompt: z.string().optional(),
|
|
65
|
+
preparation: z.record(z.string(), z.unknown()).optional(), source: z.string() }))
|
|
66
|
+
});
|
|
67
|
+
const assetFrame = z.object({ x: z.number(), y: z.number(), width: z.number(), height: z.number() });
|
|
68
|
+
const generatedAssetResult = z.object({
|
|
69
|
+
error: z.string().optional(),
|
|
70
|
+
binding: anyGameAssetBinding.optional(),
|
|
71
|
+
frames: z.array(assetFrame).optional(),
|
|
72
|
+
tiles: z.array(z.object({ mask: z.number().int(), frame: assetFrame })).optional()
|
|
73
|
+
}).loose();
|
|
74
|
+
const stagedCandidateBinding = z.object({ error: z.string().optional(), binding: anyGameAssetBinding.optional() }).loose();
|
|
75
|
+
const generatedAsset = gameWithDocument.extend({ stagedDigest: z.string().nullable(), installed: z.boolean() });
|
|
44
76
|
const gameRevisionInfo = z.object({ revision: z.string(), modifiedAt: z.number(), current: z.boolean(), message: z.string().nullable() });
|
|
45
77
|
function info(game) {
|
|
46
78
|
return {
|
|
@@ -63,8 +95,18 @@ function authoringRun(userId) {
|
|
|
63
95
|
context.set(PERMISSION_GATE_CONTEXT_KEY, headlessGate("game.authoring"));
|
|
64
96
|
return createCapabilityRun({ context, gate: gateFromContext(context, "game.authoring") });
|
|
65
97
|
}
|
|
98
|
+
/** A run that can reach the user's configured media providers, for editor-initiated asset generation. */
|
|
99
|
+
async function assetGenerationRun(userId, game, workspace) {
|
|
100
|
+
const context = createRuntimeContext({ jobId: `game-asset-${randomUUID()}`, userId, projectId: game.project_id, workspace });
|
|
101
|
+
context.set(PERMISSION_GATE_CONTEXT_KEY, headlessGate("game.assets"));
|
|
102
|
+
return createCapabilityRun({ context, gate: gateFromContext(context, "game.assets"), projectId: game.project_id,
|
|
103
|
+
availableSecrets: contextSecretAvailability(context), providers: await loadConfiguredProviders(userId) });
|
|
104
|
+
}
|
|
66
105
|
function checkAuthoringResult(result) {
|
|
67
106
|
if (result !== null && typeof result === "object" && "error" in result && typeof result.error === "string") {
|
|
107
|
+
if ("code" in result && result.code === "game_draft_source_unavailable") {
|
|
108
|
+
throwApiError(ApiErrorCode.SERVICE_UNAVAILABLE, result.error, "PRECONDITION_FAILED");
|
|
109
|
+
}
|
|
68
110
|
const code = /stale|concurrent|changed since/i.test(result.error)
|
|
69
111
|
? ApiErrorCode.ALREADY_EXISTS : ApiErrorCode.INVALID_INPUT;
|
|
70
112
|
throwApiError(code, result.error);
|
|
@@ -100,7 +142,8 @@ async function gameWorkspace(userId, game) {
|
|
|
100
142
|
}
|
|
101
143
|
return workspace;
|
|
102
144
|
}
|
|
103
|
-
|
|
145
|
+
const CONCURRENT_DRAFT_EDIT = "Game draft was modified concurrently";
|
|
146
|
+
async function persistGameAsset(userId, game, workspace, draftUpdatedAt, slot, binding, bytes, extension, contentType, siblingBindings) {
|
|
104
147
|
await workspace.write(`${game.source_root}/assets/${binding.digest}.${extension}`, bytes, contentType);
|
|
105
148
|
const installed = await Asset.create({
|
|
106
149
|
user_id: userId, project_id: game.project_id, parent_id: userId,
|
|
@@ -116,9 +159,12 @@ async function persistGameAsset(userId, game, workspace, draftUpdatedAt, slot, b
|
|
|
116
159
|
try {
|
|
117
160
|
uri = await storage.store(getAssetStorageKey(userId, installed.id, contentType), bytes, contentType);
|
|
118
161
|
const op = gameDocumentOp.parse({ op: "bind_asset", slot, binding: { ...binding, assetId: installed.id } });
|
|
119
|
-
const
|
|
162
|
+
const parent = siblingBindings ? gameAssetBinding.safeParse({ ...binding, assetId: installed.id }) : null;
|
|
163
|
+
const siblings = siblingBindings && parent?.success
|
|
164
|
+
? Object.entries(siblingBindings(parent.data)).map(([sibling, siblingBinding]) => gameDocumentOp.parse({ op: "bind_asset", slot: sibling, binding: siblingBinding })) : [];
|
|
165
|
+
const saved = await Game.updateDraft(userId, game.id, draftUpdatedAt, [op, ...siblings], workspace, { actor: "user" });
|
|
120
166
|
if (!saved) {
|
|
121
|
-
throwApiError(ApiErrorCode.ALREADY_EXISTS,
|
|
167
|
+
throwApiError(ApiErrorCode.ALREADY_EXISTS, CONCURRENT_DRAFT_EDIT);
|
|
122
168
|
}
|
|
123
169
|
return { game: info(saved.game), document: saved.document };
|
|
124
170
|
}
|
|
@@ -279,8 +325,168 @@ async function createGame(userId, projectId, name, source, dimension = "2d") {
|
|
|
279
325
|
throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game already exists");
|
|
280
326
|
return { game: info(game), document };
|
|
281
327
|
}
|
|
328
|
+
const installCandidateInput = idInput.extend({
|
|
329
|
+
baseRevision: z.string().optional(),
|
|
330
|
+
baseUpdatedAt: z.string().optional(),
|
|
331
|
+
slot: z.string().min(1),
|
|
332
|
+
candidateWorkspaceId: z.string().optional(),
|
|
333
|
+
binding: anyGameAssetBinding
|
|
334
|
+
});
|
|
335
|
+
function siblingBindingsFor(slot, assets, options) {
|
|
336
|
+
const { frames, tiles } = options;
|
|
337
|
+
if (frames?.length) {
|
|
338
|
+
return (installed) => Object.fromEntries(frames.map((frame, index) => [`${slot}.frame.${index}`, { ...installed, frame }]));
|
|
339
|
+
}
|
|
340
|
+
if (tiles?.length) {
|
|
341
|
+
return (installed) => Object.fromEntries(tiles.map((tile) => [`${slot}.tile.${tile.mask}`, { ...installed, frame: tile.frame }]));
|
|
342
|
+
}
|
|
343
|
+
if (!options.replaceInPlace) {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
return (installed) => Object.fromEntries(gameAssetSiblingRebinds(assets, slot, installed).ops.map((op) => [op.slot, op.binding]));
|
|
347
|
+
}
|
|
348
|
+
/** Install a staged candidate's bytes into the draft, with the sibling bindings `options` names. */
|
|
349
|
+
async function installCandidateBinding(userId, input, options = {}) {
|
|
350
|
+
const ctx = { userId };
|
|
351
|
+
const game = await ownedGame(ctx.userId, input.id);
|
|
352
|
+
const workspace = await gameWorkspace(ctx.userId, game);
|
|
353
|
+
const candidateRow = input.candidateWorkspaceId
|
|
354
|
+
? await Workspace.find(ctx.userId, input.candidateWorkspaceId)
|
|
355
|
+
: null;
|
|
356
|
+
if (input.candidateWorkspaceId && (!candidateRow || candidateRow.project_id !== game.project_id)) {
|
|
357
|
+
throwApiError(ApiErrorCode.NOT_FOUND, "Candidate workspace not found");
|
|
358
|
+
}
|
|
359
|
+
const candidateWorkspace = candidateRow ? workspaceFromRow(candidateRow) : workspace;
|
|
360
|
+
if (!candidateWorkspace) {
|
|
361
|
+
throwApiError(ApiErrorCode.SERVICE_UNAVAILABLE, "Candidate workspace storage is unavailable");
|
|
362
|
+
}
|
|
363
|
+
const digest = input.binding.digest;
|
|
364
|
+
if (!/^[a-f0-9]{64}$/.test(digest)) {
|
|
365
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, "Invalid candidate digest");
|
|
366
|
+
}
|
|
367
|
+
if (input.binding.mediaKind === "font" && !input.binding.fontFormat) {
|
|
368
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, "Font binding needs a TrueType or OpenType format");
|
|
369
|
+
}
|
|
370
|
+
if (input.binding.mediaKind === "hdri") {
|
|
371
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, "HDRI candidates cannot be installed until HDRI preparation verifies their bytes and dimensions");
|
|
372
|
+
}
|
|
373
|
+
const formats = input.binding.mediaKind === "model"
|
|
374
|
+
? [["glb", "model/gltf-binary"]]
|
|
375
|
+
: input.binding.mediaKind === "collider"
|
|
376
|
+
? [["json", "application/json"]]
|
|
377
|
+
: input.binding.mediaKind === "audio"
|
|
378
|
+
? [["wav", "audio/wav"], ["mp3", "audio/mpeg"], ["ogg", "audio/ogg"]]
|
|
379
|
+
: input.binding.mediaKind === "font"
|
|
380
|
+
? [[input.binding.fontFormat, `font/${input.binding.fontFormat}`]]
|
|
381
|
+
: [["png", "image/png"], ["jpg", "image/jpeg"], ["webp", "image/webp"]];
|
|
382
|
+
let candidate = null;
|
|
383
|
+
for (const [extension, contentType] of formats) {
|
|
384
|
+
if (!extension || !contentType)
|
|
385
|
+
continue;
|
|
386
|
+
const path = `${game.source_root}/assets/${digest}.${extension}`;
|
|
387
|
+
const bytes = await candidateWorkspace.read(path);
|
|
388
|
+
if (bytes) {
|
|
389
|
+
candidate = { extension, contentType, bytes };
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (!candidate || createHash("sha256").update(candidate.bytes).digest("hex") !== digest) {
|
|
394
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, "Candidate asset is missing or changed");
|
|
395
|
+
}
|
|
396
|
+
const { extension, contentType, bytes } = candidate;
|
|
397
|
+
let binding = input.binding;
|
|
398
|
+
if (binding.mediaKind === "model") {
|
|
399
|
+
const { prepareGameModelBinding3D } = await importPack("game", "@nodetool-ai/game-renderer/preparation3d");
|
|
400
|
+
const prepared = await prepareGameModelBinding3D(bytes, { assetId: binding.assetId, expectedDigest: digest });
|
|
401
|
+
if (!prepared.ok) {
|
|
402
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, prepared.diagnostics.map((issue) => issue.message).join("; "));
|
|
403
|
+
}
|
|
404
|
+
const sourceAsset = binding.sourceAssetId ? await Asset.find(ctx.userId, binding.sourceAssetId) : null;
|
|
405
|
+
if (binding.sourceAssetId && !sourceAsset) {
|
|
406
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, "Model source asset is not owned");
|
|
407
|
+
}
|
|
408
|
+
const canonical = { ...prepared.binding, required: binding.required };
|
|
409
|
+
if (binding.provenance) {
|
|
410
|
+
canonical.provenance = binding.provenance;
|
|
411
|
+
}
|
|
412
|
+
if (binding.importSettings) {
|
|
413
|
+
canonical.importSettings = binding.importSettings;
|
|
414
|
+
}
|
|
415
|
+
if (binding.sourceDigest) {
|
|
416
|
+
canonical.sourceDigest = binding.sourceDigest;
|
|
417
|
+
}
|
|
418
|
+
if (sourceAsset) {
|
|
419
|
+
canonical.sourceAssetId = sourceAsset.id;
|
|
420
|
+
}
|
|
421
|
+
binding = canonical;
|
|
422
|
+
}
|
|
423
|
+
else if (binding.mediaKind === "collider") {
|
|
424
|
+
let geometry;
|
|
425
|
+
try {
|
|
426
|
+
geometry = JSON.parse(new TextDecoder().decode(bytes));
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, "Prepared collider is not valid JSON");
|
|
430
|
+
}
|
|
431
|
+
const parsed = gamePreparedCollider3D.safeParse(geometry);
|
|
432
|
+
if (!parsed.success || binding.shape === "triangleMesh" && !parsed.data.indices?.length) {
|
|
433
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, "Prepared collider has invalid vertices or triangle indices");
|
|
434
|
+
}
|
|
435
|
+
const { vertices, indices } = parsed.data;
|
|
436
|
+
const minimum = { x: Infinity, y: Infinity, z: Infinity };
|
|
437
|
+
const maximum = { x: -Infinity, y: -Infinity, z: -Infinity };
|
|
438
|
+
for (let index = 0; index < vertices.length; index += 3) {
|
|
439
|
+
minimum.x = Math.min(minimum.x, vertices[index]);
|
|
440
|
+
maximum.x = Math.max(maximum.x, vertices[index]);
|
|
441
|
+
minimum.y = Math.min(minimum.y, vertices[index + 1]);
|
|
442
|
+
maximum.y = Math.max(maximum.y, vertices[index + 1]);
|
|
443
|
+
minimum.z = Math.min(minimum.z, vertices[index + 2]);
|
|
444
|
+
maximum.z = Math.max(maximum.z, vertices[index + 2]);
|
|
445
|
+
}
|
|
446
|
+
binding = { ...binding, preparationVersion: "1", bounds: { min: minimum, max: maximum }, vertices: vertices.length / 3, triangles: (indices?.length ?? 0) / 3 };
|
|
447
|
+
try {
|
|
448
|
+
await decodePreparedGameCollider3D(bytes, gameAssetBinding3D.options[1].parse(binding));
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, error instanceof Error ? error.message : "Prepared collider is invalid");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (input.baseRevision && game.current_revision !== input.baseRevision) {
|
|
455
|
+
throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game was modified concurrently");
|
|
456
|
+
}
|
|
457
|
+
const draft = await Game.readDraft(ctx.userId, game.id, workspace);
|
|
458
|
+
if (!draft)
|
|
459
|
+
throwApiError(ApiErrorCode.NOT_FOUND, "Game draft not found");
|
|
460
|
+
if (input.baseUpdatedAt && draft.game.draft_updated_at !== input.baseUpdatedAt) {
|
|
461
|
+
throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
|
|
462
|
+
}
|
|
463
|
+
const targetBinding = draft.document.schemaVersion === 3 ? gameAssetBinding3D.safeParse(binding) :
|
|
464
|
+
gameAssetBinding.safeParse(binding);
|
|
465
|
+
if (!targetBinding.success || draft.document.schemaVersion !== 3 && (binding.mediaKind === "model" || binding.mediaKind === "collider")) {
|
|
466
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, "Asset binding does not match the game dimension");
|
|
467
|
+
}
|
|
468
|
+
return persistGameAsset(ctx.userId, game, workspace, draft.game.draft_updated_at, input.slot, targetBinding.data, bytes, extension, contentType, draft.document.schemaVersion !== 3 ? siblingBindingsFor(input.slot, draft.document.assets, options) : undefined);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Install into whichever draft is current when the bytes are ready. An editor
|
|
472
|
+
* session saves while a generation runs, so a slot binding must not depend on
|
|
473
|
+
* the draft token the request started from. The write itself stays a
|
|
474
|
+
* compare-and-swap, retried when a save lands between the read and the write.
|
|
475
|
+
*/
|
|
476
|
+
async function installOnLatestDraft(userId, input, options) {
|
|
477
|
+
for (let attempt = 1;; attempt++) {
|
|
478
|
+
try {
|
|
479
|
+
return await installCandidateBinding(userId, input, options);
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
if (attempt >= 3 || !(error instanceof Error) || error.message !== CONCURRENT_DRAFT_EDIT) {
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
282
488
|
export const gamesRouter = router({
|
|
283
|
-
list:
|
|
489
|
+
list: gameProcedure
|
|
284
490
|
.input(z.object({ projectId: z.string() }))
|
|
285
491
|
.output(z.array(gameInfo))
|
|
286
492
|
.query(async ({ ctx, input }) => {
|
|
@@ -289,7 +495,7 @@ export const gamesRouter = router({
|
|
|
289
495
|
}
|
|
290
496
|
return (await Game.listByProject(ctx.userId, input.projectId)).map(info);
|
|
291
497
|
}),
|
|
292
|
-
create:
|
|
498
|
+
create: gameProcedure
|
|
293
499
|
.input(z.object({ projectId: z.string(), name: z.string().min(1).max(200), dimension: z.enum(["2d", "3d"]).optional(), document: gameDocument.optional() }))
|
|
294
500
|
.output(gameWithDocument)
|
|
295
501
|
.mutation(({ ctx, input }) => {
|
|
@@ -298,10 +504,10 @@ export const gamesRouter = router({
|
|
|
298
504
|
}
|
|
299
505
|
return createGame(ctx.userId, input.projectId, input.name, input.document, input.dimension);
|
|
300
506
|
}),
|
|
301
|
-
examples:
|
|
507
|
+
examples: gameProcedure
|
|
302
508
|
.output(z.array(exampleGameSummary))
|
|
303
509
|
.query(({ ctx }) => listExampleGames(ctx.apiOptions)),
|
|
304
|
-
example:
|
|
510
|
+
example: gameProcedure
|
|
305
511
|
.input(z.object({ slug: z.string().min(1) }))
|
|
306
512
|
.output(z.object({ name: z.string(), document: gameDocument }))
|
|
307
513
|
.query(({ ctx, input }) => {
|
|
@@ -311,7 +517,7 @@ export const gamesRouter = router({
|
|
|
311
517
|
}
|
|
312
518
|
return { name: bundle.name, document: bundle.document };
|
|
313
519
|
}),
|
|
314
|
-
installExample:
|
|
520
|
+
installExample: gameProcedure
|
|
315
521
|
.input(installExampleGameInput)
|
|
316
522
|
.output(gameWithDocument)
|
|
317
523
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -331,7 +537,7 @@ export const gamesRouter = router({
|
|
|
331
537
|
throw error;
|
|
332
538
|
}
|
|
333
539
|
}),
|
|
334
|
-
get:
|
|
540
|
+
get: gameProcedure
|
|
335
541
|
.input(idInput.extend({ revision: z.string().optional() }))
|
|
336
542
|
.output(gameWithDocument)
|
|
337
543
|
.query(async ({ ctx, input }) => {
|
|
@@ -339,7 +545,7 @@ export const gamesRouter = router({
|
|
|
339
545
|
const workspace = await gameWorkspace(ctx.userId, game);
|
|
340
546
|
return { game: info(game), document: await readRevision(workspace, game, input.revision ?? game.current_revision) };
|
|
341
547
|
}),
|
|
342
|
-
getDraft:
|
|
548
|
+
getDraft: gameProcedure
|
|
343
549
|
.input(idInput)
|
|
344
550
|
.output(gameWithDocument)
|
|
345
551
|
.query(async ({ ctx, input }) => {
|
|
@@ -349,7 +555,7 @@ export const gamesRouter = router({
|
|
|
349
555
|
throwApiError(ApiErrorCode.NOT_FOUND, "Game draft not found");
|
|
350
556
|
return { game: info(draft.game), document: draft.document };
|
|
351
557
|
}),
|
|
352
|
-
saveDraft:
|
|
558
|
+
saveDraft: gameProcedure
|
|
353
559
|
.input(idInput.extend({ baseUpdatedAt: z.string(), ops: z.array(gameDocumentOp).min(1).max(MAX_GAME_DRAFT_OPS) }))
|
|
354
560
|
.output(gameWithDocument)
|
|
355
561
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -370,7 +576,25 @@ export const gamesRouter = router({
|
|
|
370
576
|
throw error;
|
|
371
577
|
}
|
|
372
578
|
}),
|
|
373
|
-
|
|
579
|
+
saveDraftDocument: gameProcedure
|
|
580
|
+
.input(idInput.extend({ baseUpdatedAt: z.string(), document: gameDocument }))
|
|
581
|
+
.output(gameWithDocument)
|
|
582
|
+
.mutation(async ({ ctx, input }) => {
|
|
583
|
+
const game = await ownedGame(ctx.userId, input.id);
|
|
584
|
+
try {
|
|
585
|
+
const saved = await Game.saveDraftDocument(ctx.userId, game.id, input.baseUpdatedAt, input.document, await gameWorkspace(ctx.userId, game));
|
|
586
|
+
if (!saved)
|
|
587
|
+
throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
|
|
588
|
+
return { game: info(saved.game), document: saved.document };
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
if (error instanceof InvalidGameDocumentError) {
|
|
592
|
+
throwApiError(ApiErrorCode.INVALID_INPUT, error.message);
|
|
593
|
+
}
|
|
594
|
+
throw error;
|
|
595
|
+
}
|
|
596
|
+
}),
|
|
597
|
+
previewAuthoring: gameProcedure
|
|
374
598
|
.input(idInput.extend({ program: gameAuthoringProgram.optional() }).strict())
|
|
375
599
|
.output(authoringPreview)
|
|
376
600
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -383,7 +607,7 @@ export const gamesRouter = router({
|
|
|
383
607
|
checkAuthoringResult(result);
|
|
384
608
|
return authoringPreview.parse(result);
|
|
385
609
|
}),
|
|
386
|
-
applyAuthoring:
|
|
610
|
+
applyAuthoring: gameProcedure
|
|
387
611
|
.input(idInput.extend({ candidate: gameAuthoringCandidate }).strict())
|
|
388
612
|
.output(gameWithDocument)
|
|
389
613
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -398,7 +622,7 @@ export const gamesRouter = router({
|
|
|
398
622
|
}
|
|
399
623
|
return { game: info(draft.game), document: draft.document };
|
|
400
624
|
}),
|
|
401
|
-
draftChanges:
|
|
625
|
+
draftChanges: gameProcedure
|
|
402
626
|
.input(idInput)
|
|
403
627
|
.output(z.array(z.object({
|
|
404
628
|
id: z.string(), actor: z.enum(["agent", "user"]), threadId: z.string().nullable(),
|
|
@@ -410,7 +634,7 @@ export const gamesRouter = router({
|
|
|
410
634
|
const game = await ownedGame(ctx.userId, input.id);
|
|
411
635
|
return boundedGameDraftHistory((await Game.listDraftChanges(ctx.userId, game.id)).map(({ gameId: _gameId, ...change }) => change));
|
|
412
636
|
}),
|
|
413
|
-
draftBeforeChange:
|
|
637
|
+
draftBeforeChange: gameProcedure
|
|
414
638
|
.input(idInput.extend({ changeId: z.string() }))
|
|
415
639
|
.output(gameDocument)
|
|
416
640
|
.query(async ({ ctx, input }) => {
|
|
@@ -420,7 +644,7 @@ export const gamesRouter = router({
|
|
|
420
644
|
throwApiError(ApiErrorCode.NOT_FOUND, "Game draft change not found");
|
|
421
645
|
return document;
|
|
422
646
|
}),
|
|
423
|
-
revisions:
|
|
647
|
+
revisions: gameProcedure
|
|
424
648
|
.input(idInput)
|
|
425
649
|
.output(z.array(gameRevisionInfo))
|
|
426
650
|
.query(async ({ ctx, input }) => {
|
|
@@ -435,11 +659,11 @@ export const gamesRouter = router({
|
|
|
435
659
|
});
|
|
436
660
|
return revisions.sort((a, b) => b.modifiedAt - a.modifiedAt);
|
|
437
661
|
}),
|
|
438
|
-
publish:
|
|
662
|
+
publish: gameProcedure
|
|
439
663
|
.input(idInput.extend({ baseRevision: z.string(), baseUpdatedAt: z.string().optional(), expectedDigest: z.string().regex(/^[a-f0-9]{64}$/).optional(), document: gameDocument.optional(), message: z.string().trim().max(500).optional() }))
|
|
440
664
|
.output(gameWithDocument)
|
|
441
665
|
.mutation(async ({ ctx, input }) => publishDocument(ctx.userId, await ownedGame(ctx.userId, input.id), input.baseRevision, input.document, input.message, false, input.baseUpdatedAt, input.expectedDigest)),
|
|
442
|
-
restore:
|
|
666
|
+
restore: gameProcedure
|
|
443
667
|
.input(idInput.extend({ baseRevision: z.string(), baseUpdatedAt: z.string(), revision: z.string() }))
|
|
444
668
|
.output(gameWithDocument)
|
|
445
669
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -448,7 +672,7 @@ export const gamesRouter = router({
|
|
|
448
672
|
const oldDocument = await readRevision(workspace, game, input.revision);
|
|
449
673
|
return publishDocument(ctx.userId, game, input.baseRevision, oldDocument, undefined, true, input.baseUpdatedAt);
|
|
450
674
|
}),
|
|
451
|
-
restoreDraft:
|
|
675
|
+
restoreDraft: gameProcedure
|
|
452
676
|
.input(idInput.extend({ baseUpdatedAt: z.string(), revision: z.string() }))
|
|
453
677
|
.output(gameWithDocument)
|
|
454
678
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -468,7 +692,7 @@ export const gamesRouter = router({
|
|
|
468
692
|
throw error;
|
|
469
693
|
}
|
|
470
694
|
}),
|
|
471
|
-
installAsset:
|
|
695
|
+
installAsset: gameProcedure
|
|
472
696
|
.input(idInput.extend({ baseRevision: z.string().optional(), baseUpdatedAt: z.string().optional(), slot: z.string().min(1),
|
|
473
697
|
assetId: z.string(), expectedDigest: z.string().optional(), importSettings: gameModelImportSettings3D.partial().optional(),
|
|
474
698
|
dependencyAssetIds: z.record(z.string(), z.string()).refine((value) => Object.keys(value).length <= 320, "Too many model dependencies").optional() }))
|
|
@@ -503,6 +727,7 @@ export const gamesRouter = router({
|
|
|
503
727
|
throwApiError(ApiErrorCode.INVALID_INPUT, is3D ? "Model content digest changed" : "Image content digest changed");
|
|
504
728
|
}
|
|
505
729
|
if (draft.document.schemaVersion === 3) {
|
|
730
|
+
const { normalizeGameModel3D } = await importPack("game", "@nodetool-ai/game-renderer/preparation3d");
|
|
506
731
|
const normalized = await normalizeGameModel3D(bytes, {
|
|
507
732
|
assetId: asset.id, sourceAssetId: asset.id, importSettings: input.importSettings,
|
|
508
733
|
provenance: asset.workflow_id ?? undefined,
|
|
@@ -527,6 +752,7 @@ export const gamesRouter = router({
|
|
|
527
752
|
}
|
|
528
753
|
return persistGameAsset(ctx.userId, game, workspace, draft.game.draft_updated_at, input.slot, normalized.binding, normalized.bytes, "glb", "model/gltf-binary");
|
|
529
754
|
}
|
|
755
|
+
const sharp = await loadSharpOrThrow();
|
|
530
756
|
const metadata = await sharp(bytes).metadata();
|
|
531
757
|
if (!metadata.width || !metadata.height) {
|
|
532
758
|
throwApiError(ApiErrorCode.INVALID_INPUT, "Image dimensions are unavailable");
|
|
@@ -545,130 +771,86 @@ export const gamesRouter = router({
|
|
|
545
771
|
}
|
|
546
772
|
return { game: info(saved.game), document: saved.document };
|
|
547
773
|
}),
|
|
548
|
-
installCandidate:
|
|
774
|
+
installCandidate: gameProcedure
|
|
775
|
+
.input(installCandidateInput)
|
|
776
|
+
.output(gameWithDocument)
|
|
777
|
+
.mutation(async ({ ctx, input }) => installCandidateBinding(ctx.userId, input)),
|
|
778
|
+
/** Staged candidates and per-slot generation requests for the asset browser. The catalog itself is computed client-side from the draft. */
|
|
779
|
+
assetBrowser: gameProcedure
|
|
780
|
+
.input(idInput)
|
|
781
|
+
.output(assetBrowserInfo)
|
|
782
|
+
.query(async ({ ctx, input }) => {
|
|
783
|
+
const game = await ownedGame(ctx.userId, input.id);
|
|
784
|
+
const result = await authoringRun(ctx.userId).invoke("browse_native_game_assets", { game_id: game.id });
|
|
785
|
+
checkAuthoringResult(result);
|
|
786
|
+
return assetBrowserInfo.parse(result);
|
|
787
|
+
}),
|
|
788
|
+
/**
|
|
789
|
+
* Generate an asset for one slot through `generate_game_asset`, record the
|
|
790
|
+
* prompt beside the staged bytes, and bind it in place on the draft that is
|
|
791
|
+
* current when the bytes are ready. The editor keeps saving while a
|
|
792
|
+
* generation runs, so the request carries no draft token: a sheet's frame
|
|
793
|
+
* bindings, or the slot's existing frame and tile bindings, are written in
|
|
794
|
+
* the same compare-and-swap as the slot, as the user who asked. A
|
|
795
|
+
* provider and model pin the generator; without them the capability picks one.
|
|
796
|
+
*/
|
|
797
|
+
generateAsset: gameProcedure
|
|
549
798
|
.input(idInput.extend({
|
|
550
|
-
baseRevision: z.string().optional(),
|
|
551
|
-
baseUpdatedAt: z.string().optional(),
|
|
552
799
|
slot: z.string().min(1),
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
.
|
|
800
|
+
kind: z.enum(["image", "audio", "music"]),
|
|
801
|
+
prompt: z.string().trim().min(1).max(4000),
|
|
802
|
+
preparation: z.record(z.string(), z.unknown()).optional(),
|
|
803
|
+
provider: z.string().min(1).optional(),
|
|
804
|
+
model: z.string().min(1).optional()
|
|
805
|
+
}).strict().refine((value) => (value.provider === undefined) === (value.model === undefined), "Pass provider and model together"))
|
|
806
|
+
.output(generatedAsset)
|
|
557
807
|
.mutation(async ({ ctx, input }) => {
|
|
558
808
|
const game = await ownedGame(ctx.userId, input.id);
|
|
559
809
|
const workspace = await gameWorkspace(ctx.userId, game);
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
: input.
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
throwApiError(ApiErrorCode.INVALID_INPUT, "Candidate asset is missing or changed");
|
|
599
|
-
}
|
|
600
|
-
const { extension, contentType, bytes } = candidate;
|
|
601
|
-
let binding = input.binding;
|
|
602
|
-
if (binding.mediaKind === "model") {
|
|
603
|
-
const prepared = await prepareGameModelBinding3D(bytes, { assetId: binding.assetId, expectedDigest: digest });
|
|
604
|
-
if (!prepared.ok) {
|
|
605
|
-
throwApiError(ApiErrorCode.INVALID_INPUT, prepared.diagnostics.map((issue) => issue.message).join("; "));
|
|
606
|
-
}
|
|
607
|
-
const sourceAsset = binding.sourceAssetId ? await Asset.find(ctx.userId, binding.sourceAssetId) : null;
|
|
608
|
-
if (binding.sourceAssetId && !sourceAsset) {
|
|
609
|
-
throwApiError(ApiErrorCode.INVALID_INPUT, "Model source asset is not owned");
|
|
610
|
-
}
|
|
611
|
-
const canonical = { ...prepared.binding, required: binding.required };
|
|
612
|
-
if (binding.provenance) {
|
|
613
|
-
canonical.provenance = binding.provenance;
|
|
614
|
-
}
|
|
615
|
-
if (binding.importSettings) {
|
|
616
|
-
canonical.importSettings = binding.importSettings;
|
|
617
|
-
}
|
|
618
|
-
if (binding.sourceDigest) {
|
|
619
|
-
canonical.sourceDigest = binding.sourceDigest;
|
|
620
|
-
}
|
|
621
|
-
if (sourceAsset) {
|
|
622
|
-
canonical.sourceAssetId = sourceAsset.id;
|
|
623
|
-
}
|
|
624
|
-
binding = canonical;
|
|
625
|
-
}
|
|
626
|
-
else if (binding.mediaKind === "collider") {
|
|
627
|
-
let geometry;
|
|
628
|
-
try {
|
|
629
|
-
geometry = JSON.parse(new TextDecoder().decode(bytes));
|
|
630
|
-
}
|
|
631
|
-
catch {
|
|
632
|
-
throwApiError(ApiErrorCode.INVALID_INPUT, "Prepared collider is not valid JSON");
|
|
633
|
-
}
|
|
634
|
-
const parsed = gamePreparedCollider3D.safeParse(geometry);
|
|
635
|
-
if (!parsed.success || binding.shape === "triangleMesh" && !parsed.data.indices?.length) {
|
|
636
|
-
throwApiError(ApiErrorCode.INVALID_INPUT, "Prepared collider has invalid vertices or triangle indices");
|
|
637
|
-
}
|
|
638
|
-
const { vertices, indices } = parsed.data;
|
|
639
|
-
const minimum = { x: Infinity, y: Infinity, z: Infinity };
|
|
640
|
-
const maximum = { x: -Infinity, y: -Infinity, z: -Infinity };
|
|
641
|
-
for (let index = 0; index < vertices.length; index += 3) {
|
|
642
|
-
minimum.x = Math.min(minimum.x, vertices[index]);
|
|
643
|
-
maximum.x = Math.max(maximum.x, vertices[index]);
|
|
644
|
-
minimum.y = Math.min(minimum.y, vertices[index + 1]);
|
|
645
|
-
maximum.y = Math.max(maximum.y, vertices[index + 1]);
|
|
646
|
-
minimum.z = Math.min(minimum.z, vertices[index + 2]);
|
|
647
|
-
maximum.z = Math.max(maximum.z, vertices[index + 2]);
|
|
648
|
-
}
|
|
649
|
-
binding = { ...binding, preparationVersion: "1", bounds: { min: minimum, max: maximum }, vertices: vertices.length / 3, triangles: (indices?.length ?? 0) / 3 };
|
|
650
|
-
try {
|
|
651
|
-
await decodePreparedGameCollider3D(bytes, gameAssetBinding3D.options[1].parse(binding));
|
|
652
|
-
}
|
|
653
|
-
catch (error) {
|
|
654
|
-
throwApiError(ApiErrorCode.INVALID_INPUT, error instanceof Error ? error.message : "Prepared collider is invalid");
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
if (input.baseRevision && game.current_revision !== input.baseRevision) {
|
|
658
|
-
throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game was modified concurrently");
|
|
659
|
-
}
|
|
660
|
-
const draft = await Game.readDraft(ctx.userId, game.id, workspace);
|
|
661
|
-
if (!draft)
|
|
662
|
-
throwApiError(ApiErrorCode.NOT_FOUND, "Game draft not found");
|
|
663
|
-
if (input.baseUpdatedAt && draft.game.draft_updated_at !== input.baseUpdatedAt) {
|
|
664
|
-
throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
|
|
810
|
+
const args = { game_id: game.id, slot: input.slot, kind: input.kind, prompt: input.prompt, install: false };
|
|
811
|
+
if (input.preparation) {
|
|
812
|
+
args["preparation"] = input.preparation;
|
|
813
|
+
}
|
|
814
|
+
if (input.provider && input.model) {
|
|
815
|
+
args["provider"] = input.provider;
|
|
816
|
+
args["model"] = input.model;
|
|
817
|
+
}
|
|
818
|
+
const run = await assetGenerationRun(ctx.userId, game, workspace);
|
|
819
|
+
const result = generatedAssetResult.safeParse(await run.invoke("generate_game_asset", args));
|
|
820
|
+
if (!result.success) {
|
|
821
|
+
throwApiError(ApiErrorCode.INTERNAL_ERROR, "Asset generation returned an invalid result");
|
|
822
|
+
}
|
|
823
|
+
checkAuthoringResult(result.data);
|
|
824
|
+
const { binding, frames, tiles } = result.data;
|
|
825
|
+
if (!binding) {
|
|
826
|
+
throwApiError(ApiErrorCode.INTERNAL_ERROR, "Asset generation did not stage an asset");
|
|
827
|
+
}
|
|
828
|
+
await workspace.write(stagedGameCandidateRecordPath(game.source_root, binding.digest), JSON.stringify(stagedGameCandidateRecord.parse({
|
|
829
|
+
version: 1, digest: binding.digest, slot: input.slot, prompt: input.prompt, source: "generate", stagedAt: new Date().toISOString(), binding
|
|
830
|
+
})), "application/json");
|
|
831
|
+
const installed = await installOnLatestDraft(ctx.userId, { id: game.id, slot: input.slot, binding }, { replaceInPlace: true, frames, tiles });
|
|
832
|
+
return { ...installed, stagedDigest: binding.digest, installed: true };
|
|
833
|
+
}),
|
|
834
|
+
/** Bind a staged candidate to a slot on the current draft. A 2D slot's frame and tile bindings move onto the new bytes. */
|
|
835
|
+
installStagedCandidate: gameProcedure
|
|
836
|
+
.input(idInput.extend({
|
|
837
|
+
slot: z.string().min(1),
|
|
838
|
+
digest: z.string().regex(/^[a-f0-9]{64}$/)
|
|
839
|
+
}).strict())
|
|
840
|
+
.output(gameWithDocument)
|
|
841
|
+
.mutation(async ({ ctx, input }) => {
|
|
842
|
+
const game = await ownedGame(ctx.userId, input.id);
|
|
843
|
+
const picked = stagedCandidateBinding.safeParse(await authoringRun(ctx.userId).invoke("browse_native_game_assets", {
|
|
844
|
+
game_id: game.id, digest: input.digest, slot: input.slot
|
|
845
|
+
}));
|
|
846
|
+
if (!picked.success) {
|
|
847
|
+
throwApiError(ApiErrorCode.INTERNAL_ERROR, "Candidate lookup returned an invalid result");
|
|
665
848
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
throwApiError(ApiErrorCode.INVALID_INPUT, "Asset binding does not match the game dimension");
|
|
849
|
+
checkAuthoringResult(picked.data);
|
|
850
|
+
if (!picked.data.binding) {
|
|
851
|
+
throwApiError(ApiErrorCode.NOT_FOUND, "Staged candidate not found");
|
|
670
852
|
}
|
|
671
|
-
return
|
|
853
|
+
return installOnLatestDraft(ctx.userId, { id: game.id, slot: input.slot, binding: picked.data.binding }, { replaceInPlace: true });
|
|
672
854
|
})
|
|
673
855
|
});
|
|
674
856
|
//# sourceMappingURL=games.js.map
|