@nodetool-ai/websocket 0.8.1 → 0.8.3

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.
Files changed (471) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/chat-turn-registry.d.ts +2 -1
  3. package/dist/chat-turn-registry.d.ts.map +1 -1
  4. package/dist/chat-turn-registry.js +8 -1
  5. package/dist/chat-turn-registry.js.map +1 -1
  6. package/dist/collection-api.d.ts.map +1 -1
  7. package/dist/collection-api.js +41 -6
  8. package/dist/collection-api.js.map +1 -1
  9. package/dist/custom-providers.d.ts.map +1 -1
  10. package/dist/custom-providers.js +6 -1
  11. package/dist/custom-providers.js.map +1 -1
  12. package/dist/error-traces.d.ts +56 -0
  13. package/dist/error-traces.d.ts.map +1 -0
  14. package/dist/error-traces.js +179 -0
  15. package/dist/error-traces.js.map +1 -0
  16. package/dist/example-workflows.d.ts.map +1 -1
  17. package/dist/example-workflows.js +6 -0
  18. package/dist/example-workflows.js.map +1 -1
  19. package/dist/fake-runtime.d.ts +41 -6
  20. package/dist/fake-runtime.d.ts.map +1 -1
  21. package/dist/fake-runtime.js +247 -16
  22. package/dist/fake-runtime.js.map +1 -1
  23. package/dist/file-api.d.ts.map +1 -1
  24. package/dist/file-api.js +6 -8
  25. package/dist/file-api.js.map +1 -1
  26. package/dist/generation-recovery.d.ts.map +1 -1
  27. package/dist/generation-recovery.js +32 -7
  28. package/dist/generation-recovery.js.map +1 -1
  29. package/dist/headless-job-runner.d.ts.map +1 -1
  30. package/dist/headless-job-runner.js +5 -1
  31. package/dist/headless-job-runner.js.map +1 -1
  32. package/dist/http-api.d.ts +3 -1
  33. package/dist/http-api.d.ts.map +1 -1
  34. package/dist/http-api.js +66 -9
  35. package/dist/http-api.js.map +1 -1
  36. package/dist/http-body-schemas.d.ts +2 -0
  37. package/dist/http-body-schemas.d.ts.map +1 -1
  38. package/dist/http-body-schemas.js +8 -1
  39. package/dist/http-body-schemas.js.map +1 -1
  40. package/dist/index.d.ts +0 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +0 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/interrupted-jobs.d.ts +14 -0
  45. package/dist/interrupted-jobs.d.ts.map +1 -0
  46. package/dist/interrupted-jobs.js +28 -0
  47. package/dist/interrupted-jobs.js.map +1 -0
  48. package/dist/lib/admin.d.ts +7 -0
  49. package/dist/lib/admin.d.ts.map +1 -0
  50. package/dist/lib/admin.js +20 -0
  51. package/dist/lib/admin.js.map +1 -0
  52. package/dist/lib/app-debug-service.js +1 -1
  53. package/dist/lib/app-debug-service.js.map +1 -1
  54. package/dist/lib/app-instances-service.d.ts +1319 -0
  55. package/dist/lib/app-instances-service.d.ts.map +1 -0
  56. package/dist/lib/app-instances-service.js +377 -0
  57. package/dist/lib/app-instances-service.js.map +1 -0
  58. package/dist/lib/app-project-export.d.ts +12 -0
  59. package/dist/lib/app-project-export.d.ts.map +1 -0
  60. package/dist/lib/app-project-export.js +42 -0
  61. package/dist/lib/app-project-export.js.map +1 -0
  62. package/dist/lib/app-run-cancellation.d.ts +4 -0
  63. package/dist/lib/app-run-cancellation.d.ts.map +1 -0
  64. package/dist/lib/app-run-cancellation.js +28 -0
  65. package/dist/lib/app-run-cancellation.js.map +1 -0
  66. package/dist/lib/applications-service.d.ts +182 -1
  67. package/dist/lib/applications-service.d.ts.map +1 -1
  68. package/dist/lib/applications-service.js +27 -6
  69. package/dist/lib/applications-service.js.map +1 -1
  70. package/dist/lib/asset-export.d.ts +6 -2
  71. package/dist/lib/asset-export.d.ts.map +1 -1
  72. package/dist/lib/asset-export.js +15 -7
  73. package/dist/lib/asset-export.js.map +1 -1
  74. package/dist/lib/asset-paths.d.ts +7 -11
  75. package/dist/lib/asset-paths.d.ts.map +1 -1
  76. package/dist/lib/asset-paths.js +8 -56
  77. package/dist/lib/asset-paths.js.map +1 -1
  78. package/dist/lib/asset-response.d.ts.map +1 -1
  79. package/dist/lib/asset-response.js +3 -1
  80. package/dist/lib/asset-response.js.map +1 -1
  81. package/dist/lib/audio-peaks.d.ts.map +1 -1
  82. package/dist/lib/audio-peaks.js +5 -1
  83. package/dist/lib/audio-peaks.js.map +1 -1
  84. package/dist/lib/bridge.d.ts.map +1 -1
  85. package/dist/lib/bridge.js.map +1 -1
  86. package/dist/lib/cloud-profile.d.ts +10 -0
  87. package/dist/lib/cloud-profile.d.ts.map +1 -0
  88. package/dist/lib/cloud-profile.js +13 -0
  89. package/dist/lib/cloud-profile.js.map +1 -0
  90. package/dist/lib/document-model-interfaces.d.ts.map +1 -1
  91. package/dist/lib/document-model-interfaces.js +9 -8
  92. package/dist/lib/document-model-interfaces.js.map +1 -1
  93. package/dist/lib/example-apps.d.ts +9 -0
  94. package/dist/lib/example-apps.d.ts.map +1 -1
  95. package/dist/lib/example-apps.js +61 -14
  96. package/dist/lib/example-apps.js.map +1 -1
  97. package/dist/lib/http-tracing.d.ts +12 -0
  98. package/dist/lib/http-tracing.d.ts.map +1 -0
  99. package/dist/lib/http-tracing.js +81 -0
  100. package/dist/lib/http-tracing.js.map +1 -0
  101. package/dist/lib/http-user-id.d.ts +2 -0
  102. package/dist/lib/http-user-id.d.ts.map +1 -0
  103. package/dist/lib/http-user-id.js +4 -0
  104. package/dist/lib/http-user-id.js.map +1 -0
  105. package/dist/lib/json-body.d.ts +11 -0
  106. package/dist/lib/json-body.d.ts.map +1 -0
  107. package/dist/lib/json-body.js +13 -0
  108. package/dist/lib/json-body.js.map +1 -0
  109. package/dist/lib/media.d.ts +1 -6
  110. package/dist/lib/media.d.ts.map +1 -1
  111. package/dist/lib/media.js +8 -44
  112. package/dist/lib/media.js.map +1 -1
  113. package/dist/lib/public-routes.d.ts +22 -0
  114. package/dist/lib/public-routes.d.ts.map +1 -1
  115. package/dist/lib/public-routes.js +41 -0
  116. package/dist/lib/public-routes.js.map +1 -1
  117. package/dist/lib/public-workflow-share.d.ts +31 -0
  118. package/dist/lib/public-workflow-share.d.ts.map +1 -0
  119. package/dist/lib/public-workflow-share.js +88 -0
  120. package/dist/lib/public-workflow-share.js.map +1 -0
  121. package/dist/lib/python-metadata-roots.d.ts +27 -0
  122. package/dist/lib/python-metadata-roots.d.ts.map +1 -0
  123. package/dist/lib/python-metadata-roots.js +92 -0
  124. package/dist/lib/python-metadata-roots.js.map +1 -0
  125. package/dist/lib/run-trace-live-delivery.d.ts +25 -0
  126. package/dist/lib/run-trace-live-delivery.d.ts.map +1 -0
  127. package/dist/lib/run-trace-live-delivery.js +91 -0
  128. package/dist/lib/run-trace-live-delivery.js.map +1 -0
  129. package/dist/lib/runtime-detection.d.ts +1 -1
  130. package/dist/lib/runtime-detection.d.ts.map +1 -1
  131. package/dist/lib/runtime-detection.js +2 -1
  132. package/dist/lib/runtime-detection.js.map +1 -1
  133. package/dist/lib/server-auth-token.d.ts +18 -0
  134. package/dist/lib/server-auth-token.d.ts.map +1 -0
  135. package/dist/lib/server-auth-token.js +28 -0
  136. package/dist/lib/server-auth-token.js.map +1 -0
  137. package/dist/lib/test-ui-run-routes.d.ts +8 -0
  138. package/dist/lib/test-ui-run-routes.d.ts.map +1 -0
  139. package/dist/lib/test-ui-run-routes.js +42 -0
  140. package/dist/lib/test-ui-run-routes.js.map +1 -0
  141. package/dist/lib/thumbnail.d.ts.map +1 -1
  142. package/dist/lib/thumbnail.js +12 -9
  143. package/dist/lib/thumbnail.js.map +1 -1
  144. package/dist/lib/timeline-bundle.d.ts +159 -16
  145. package/dist/lib/timeline-bundle.d.ts.map +1 -1
  146. package/dist/lib/timeline-bundle.js +34 -6
  147. package/dist/lib/timeline-bundle.js.map +1 -1
  148. package/dist/lib/timeline-media-export.d.ts +18 -0
  149. package/dist/lib/timeline-media-export.d.ts.map +1 -0
  150. package/dist/lib/timeline-media-export.js +66 -0
  151. package/dist/lib/timeline-media-export.js.map +1 -0
  152. package/dist/lib/timeline-premiere-import.d.ts +24 -0
  153. package/dist/lib/timeline-premiere-import.d.ts.map +1 -0
  154. package/dist/lib/timeline-premiere-import.js +102 -0
  155. package/dist/lib/timeline-premiere-import.js.map +1 -0
  156. package/dist/lib/timeline-premiere.d.ts +38 -0
  157. package/dist/lib/timeline-premiere.d.ts.map +1 -0
  158. package/dist/lib/timeline-premiere.js +87 -0
  159. package/dist/lib/timeline-premiere.js.map +1 -0
  160. package/dist/lib/video-proxy.d.ts.map +1 -1
  161. package/dist/lib/video-proxy.js +3 -2
  162. package/dist/lib/video-proxy.js.map +1 -1
  163. package/dist/lib/workflow-service.d.ts.map +1 -1
  164. package/dist/lib/workflow-service.js +4 -1
  165. package/dist/lib/workflow-service.js.map +1 -1
  166. package/dist/mcp-agent-tools.d.ts.map +1 -1
  167. package/dist/mcp-agent-tools.js +95 -162
  168. package/dist/mcp-agent-tools.js.map +1 -1
  169. package/dist/mcp-client-config.d.ts +64 -0
  170. package/dist/mcp-client-config.d.ts.map +1 -0
  171. package/dist/mcp-client-config.js +353 -0
  172. package/dist/mcp-client-config.js.map +1 -0
  173. package/dist/mcp-server.d.ts +7 -0
  174. package/dist/mcp-server.d.ts.map +1 -1
  175. package/dist/mcp-server.js +163 -16
  176. package/dist/mcp-server.js.map +1 -1
  177. package/dist/mcp-skill-prompts.d.ts +23 -0
  178. package/dist/mcp-skill-prompts.d.ts.map +1 -0
  179. package/dist/mcp-skill-prompts.js +91 -0
  180. package/dist/mcp-skill-prompts.js.map +1 -0
  181. package/dist/mcp-tool-deps.d.ts.map +1 -1
  182. package/dist/mcp-tool-deps.js +8 -1
  183. package/dist/mcp-tool-deps.js.map +1 -1
  184. package/dist/models-api.d.ts +10 -0
  185. package/dist/models-api.d.ts.map +1 -1
  186. package/dist/models-api.js +29 -2
  187. package/dist/models-api.js.map +1 -1
  188. package/dist/node-registry-setup.d.ts +1 -1
  189. package/dist/node-registry-setup.d.ts.map +1 -1
  190. package/dist/node-registry-setup.js +21 -114
  191. package/dist/node-registry-setup.js.map +1 -1
  192. package/dist/oauth-api.d.ts.map +1 -1
  193. package/dist/oauth-api.js +45 -23
  194. package/dist/oauth-api.js.map +1 -1
  195. package/dist/openai-api.d.ts.map +1 -1
  196. package/dist/openai-api.js +10 -2
  197. package/dist/openai-api.js.map +1 -1
  198. package/dist/plugins/websocket.d.ts.map +1 -1
  199. package/dist/plugins/websocket.js +27 -59
  200. package/dist/plugins/websocket.js.map +1 -1
  201. package/dist/python-bridge-gate.d.ts +28 -0
  202. package/dist/python-bridge-gate.d.ts.map +1 -0
  203. package/dist/python-bridge-gate.js +36 -0
  204. package/dist/python-bridge-gate.js.map +1 -0
  205. package/dist/routes/app-runs.d.ts +4 -0
  206. package/dist/routes/app-runs.d.ts.map +1 -0
  207. package/dist/routes/app-runs.js +173 -0
  208. package/dist/routes/app-runs.js.map +1 -0
  209. package/dist/routes/applications.d.ts +1 -0
  210. package/dist/routes/applications.d.ts.map +1 -1
  211. package/dist/routes/applications.js +24 -0
  212. package/dist/routes/applications.js.map +1 -1
  213. package/dist/routes/assets.d.ts.map +1 -1
  214. package/dist/routes/assets.js +31 -41
  215. package/dist/routes/assets.js.map +1 -1
  216. package/dist/routes/config.d.ts.map +1 -1
  217. package/dist/routes/config.js +6 -0
  218. package/dist/routes/config.js.map +1 -1
  219. package/dist/routes/documents.d.ts.map +1 -1
  220. package/dist/routes/documents.js +24 -0
  221. package/dist/routes/documents.js.map +1 -1
  222. package/dist/routes/fal-pricing-estimate.d.ts.map +1 -1
  223. package/dist/routes/fal-pricing-estimate.js +2 -0
  224. package/dist/routes/fal-pricing-estimate.js.map +1 -1
  225. package/dist/routes/files.d.ts.map +1 -1
  226. package/dist/routes/files.js +2 -0
  227. package/dist/routes/files.js.map +1 -1
  228. package/dist/routes/games.d.ts +38 -0
  229. package/dist/routes/games.d.ts.map +1 -0
  230. package/dist/routes/games.js +158 -0
  231. package/dist/routes/games.js.map +1 -0
  232. package/dist/routes/integrations.d.ts.map +1 -1
  233. package/dist/routes/integrations.js +22 -26
  234. package/dist/routes/integrations.js.map +1 -1
  235. package/dist/routes/js-scripts.d.ts +10 -4
  236. package/dist/routes/js-scripts.d.ts.map +1 -1
  237. package/dist/routes/js-scripts.js +451 -69
  238. package/dist/routes/js-scripts.js.map +1 -1
  239. package/dist/routes/nodes.d.ts.map +1 -1
  240. package/dist/routes/nodes.js +34 -1
  241. package/dist/routes/nodes.js.map +1 -1
  242. package/dist/routes/public-shared-workflows.d.ts +16 -0
  243. package/dist/routes/public-shared-workflows.d.ts.map +1 -0
  244. package/dist/routes/public-shared-workflows.js +55 -0
  245. package/dist/routes/public-shared-workflows.js.map +1 -0
  246. package/dist/routes/run-spans.d.ts +4 -0
  247. package/dist/routes/run-spans.d.ts.map +1 -0
  248. package/dist/routes/run-spans.js +53 -0
  249. package/dist/routes/run-spans.js.map +1 -0
  250. package/dist/routes/storage.d.ts.map +1 -1
  251. package/dist/routes/storage.js +2 -0
  252. package/dist/routes/storage.js.map +1 -1
  253. package/dist/routes/storyboards.d.ts.map +1 -1
  254. package/dist/routes/storyboards.js +6 -3
  255. package/dist/routes/storyboards.js.map +1 -1
  256. package/dist/routes/timeline-fonts.d.ts +22 -0
  257. package/dist/routes/timeline-fonts.d.ts.map +1 -1
  258. package/dist/routes/timeline-fonts.js +138 -7
  259. package/dist/routes/timeline-fonts.js.map +1 -1
  260. package/dist/routes/timelines.d.ts +8 -4
  261. package/dist/routes/timelines.d.ts.map +1 -1
  262. package/dist/routes/timelines.js +209 -16
  263. package/dist/routes/timelines.js.map +1 -1
  264. package/dist/screenshot-docs-seed.d.ts +24 -0
  265. package/dist/screenshot-docs-seed.d.ts.map +1 -0
  266. package/dist/screenshot-docs-seed.js +414 -0
  267. package/dist/screenshot-docs-seed.js.map +1 -0
  268. package/dist/screenshot-projects.d.ts.map +1 -1
  269. package/dist/screenshot-projects.js +9 -9
  270. package/dist/screenshot-projects.js.map +1 -1
  271. package/dist/screenshot-server.js +114 -20
  272. package/dist/screenshot-server.js.map +1 -1
  273. package/dist/sdk/sdk-model-download-service.d.ts.map +1 -1
  274. package/dist/sdk/sdk-model-download-service.js +5 -2
  275. package/dist/sdk/sdk-model-download-service.js.map +1 -1
  276. package/dist/server.js +173 -109
  277. package/dist/server.js.map +1 -1
  278. package/dist/session/app-run-fold.d.ts +7 -0
  279. package/dist/session/app-run-fold.d.ts.map +1 -0
  280. package/dist/session/app-run-fold.js +45 -0
  281. package/dist/session/app-run-fold.js.map +1 -0
  282. package/dist/session/chat-prompt.d.ts.map +1 -1
  283. package/dist/session/chat-prompt.js +37 -15
  284. package/dist/session/chat-prompt.js.map +1 -1
  285. package/dist/session/chat-turn.d.ts +11 -0
  286. package/dist/session/chat-turn.d.ts.map +1 -1
  287. package/dist/session/chat-turn.js +270 -116
  288. package/dist/session/chat-turn.js.map +1 -1
  289. package/dist/session/commands.d.ts +14 -0
  290. package/dist/session/commands.d.ts.map +1 -1
  291. package/dist/session/commands.js +119 -14
  292. package/dist/session/commands.js.map +1 -1
  293. package/dist/session/inference.d.ts +30 -2
  294. package/dist/session/inference.d.ts.map +1 -1
  295. package/dist/session/inference.js +165 -16
  296. package/dist/session/inference.js.map +1 -1
  297. package/dist/session/job-execution.d.ts +7 -1
  298. package/dist/session/job-execution.d.ts.map +1 -1
  299. package/dist/session/job-execution.js +329 -42
  300. package/dist/session/job-execution.js.map +1 -1
  301. package/dist/session/model-interfaces.d.ts +2 -1
  302. package/dist/session/model-interfaces.d.ts.map +1 -1
  303. package/dist/session/model-interfaces.js +1 -0
  304. package/dist/session/model-interfaces.js.map +1 -1
  305. package/dist/storage-api.d.ts +0 -4
  306. package/dist/storage-api.d.ts.map +1 -1
  307. package/dist/storage-api.js +3 -26
  308. package/dist/storage-api.js.map +1 -1
  309. package/dist/storage-retention.d.ts +5 -0
  310. package/dist/storage-retention.d.ts.map +1 -1
  311. package/dist/storage-retention.js +22 -1
  312. package/dist/storage-retention.js.map +1 -1
  313. package/dist/test-ui-server.d.ts +11 -0
  314. package/dist/test-ui-server.d.ts.map +1 -1
  315. package/dist/test-ui-server.js +84 -6
  316. package/dist/test-ui-server.js.map +1 -1
  317. package/dist/timeline-generation-recovery.d.ts +4 -0
  318. package/dist/timeline-generation-recovery.d.ts.map +1 -0
  319. package/dist/timeline-generation-recovery.js +72 -0
  320. package/dist/timeline-generation-recovery.js.map +1 -0
  321. package/dist/triggers/dispatcher.d.ts.map +1 -1
  322. package/dist/triggers/dispatcher.js +26 -6
  323. package/dist/triggers/dispatcher.js.map +1 -1
  324. package/dist/triggers/file-watch.d.ts.map +1 -1
  325. package/dist/triggers/file-watch.js +35 -19
  326. package/dist/triggers/file-watch.js.map +1 -1
  327. package/dist/triggers/scheduler.d.ts.map +1 -1
  328. package/dist/triggers/scheduler.js +7 -5
  329. package/dist/triggers/scheduler.js.map +1 -1
  330. package/dist/triggers/stores.js +8 -8
  331. package/dist/triggers/stores.js.map +1 -1
  332. package/dist/triggers/webhook-route.js +2 -2
  333. package/dist/triggers/webhook-route.js.map +1 -1
  334. package/dist/trpc/error-formatter.d.ts.map +1 -1
  335. package/dist/trpc/error-formatter.js +2 -1
  336. package/dist/trpc/error-formatter.js.map +1 -1
  337. package/dist/trpc/error-logging.d.ts +10 -0
  338. package/dist/trpc/error-logging.d.ts.map +1 -1
  339. package/dist/trpc/error-logging.js +21 -0
  340. package/dist/trpc/error-logging.js.map +1 -1
  341. package/dist/trpc/router.d.ts +100 -31028
  342. package/dist/trpc/router.d.ts.map +1 -1
  343. package/dist/trpc/router.js +12 -3
  344. package/dist/trpc/router.js.map +1 -1
  345. package/dist/trpc/routers/app-instances.d.ts +2332 -0
  346. package/dist/trpc/routers/app-instances.d.ts.map +1 -0
  347. package/dist/trpc/routers/app-instances.js +52 -0
  348. package/dist/trpc/routers/app-instances.js.map +1 -0
  349. package/dist/trpc/routers/app-runs.d.ts +1120 -0
  350. package/dist/trpc/routers/app-runs.d.ts.map +1 -0
  351. package/dist/trpc/routers/app-runs.js +28 -0
  352. package/dist/trpc/routers/app-runs.js.map +1 -0
  353. package/dist/trpc/routers/applications.d.ts +1102 -0
  354. package/dist/trpc/routers/applications.d.ts.map +1 -1
  355. package/dist/trpc/routers/applications.js +5 -2
  356. package/dist/trpc/routers/applications.js.map +1 -1
  357. package/dist/trpc/routers/assets.d.ts +18 -0
  358. package/dist/trpc/routers/assets.d.ts.map +1 -1
  359. package/dist/trpc/routers/assets.js +20 -5
  360. package/dist/trpc/routers/assets.js.map +1 -1
  361. package/dist/trpc/routers/bug-reports.d.ts +33 -0
  362. package/dist/trpc/routers/bug-reports.d.ts.map +1 -0
  363. package/dist/trpc/routers/bug-reports.js +69 -0
  364. package/dist/trpc/routers/bug-reports.js.map +1 -0
  365. package/dist/trpc/routers/collections.d.ts.map +1 -1
  366. package/dist/trpc/routers/collections.js +16 -10
  367. package/dist/trpc/routers/collections.js.map +1 -1
  368. package/dist/trpc/routers/credits.d.ts.map +1 -1
  369. package/dist/trpc/routers/credits.js +11 -1
  370. package/dist/trpc/routers/credits.js.map +1 -1
  371. package/dist/trpc/routers/documents.d.ts.map +1 -1
  372. package/dist/trpc/routers/documents.js +8 -2
  373. package/dist/trpc/routers/documents.js.map +1 -1
  374. package/dist/trpc/routers/error-traces.d.ts +132 -0
  375. package/dist/trpc/routers/error-traces.d.ts.map +1 -0
  376. package/dist/trpc/routers/error-traces.js +118 -0
  377. package/dist/trpc/routers/error-traces.js.map +1 -0
  378. package/dist/trpc/routers/extension.d.ts +3 -3
  379. package/dist/trpc/routers/extension.d.ts.map +1 -1
  380. package/dist/trpc/routers/extension.js +13 -9
  381. package/dist/trpc/routers/extension.js.map +1 -1
  382. package/dist/trpc/routers/gameDraftHistory.d.ts +7 -0
  383. package/dist/trpc/routers/gameDraftHistory.d.ts.map +1 -0
  384. package/dist/trpc/routers/gameDraftHistory.js +40 -0
  385. package/dist/trpc/routers/gameDraftHistory.js.map +1 -0
  386. package/dist/trpc/routers/games.d.ts +59974 -5414
  387. package/dist/trpc/routers/games.d.ts.map +1 -1
  388. package/dist/trpc/routers/games.js +531 -136
  389. package/dist/trpc/routers/games.js.map +1 -1
  390. package/dist/trpc/routers/jobs.d.ts +42 -0
  391. package/dist/trpc/routers/jobs.d.ts.map +1 -1
  392. package/dist/trpc/routers/jobs.js +89 -4
  393. package/dist/trpc/routers/jobs.js.map +1 -1
  394. package/dist/trpc/routers/js-scripts.d.ts.map +1 -1
  395. package/dist/trpc/routers/js-scripts.js +18 -2
  396. package/dist/trpc/routers/js-scripts.js.map +1 -1
  397. package/dist/trpc/routers/mcp-config.d.ts +3 -1
  398. package/dist/trpc/routers/mcp-config.d.ts.map +1 -1
  399. package/dist/trpc/routers/mcp-config.js +46 -208
  400. package/dist/trpc/routers/mcp-config.js.map +1 -1
  401. package/dist/trpc/routers/messages.d.ts +10 -0
  402. package/dist/trpc/routers/messages.d.ts.map +1 -1
  403. package/dist/trpc/routers/messages.js +16 -2
  404. package/dist/trpc/routers/messages.js.map +1 -1
  405. package/dist/trpc/routers/models.d.ts +130 -0
  406. package/dist/trpc/routers/models.d.ts.map +1 -1
  407. package/dist/trpc/routers/models.js +119 -61
  408. package/dist/trpc/routers/models.js.map +1 -1
  409. package/dist/trpc/routers/packs.d.ts.map +1 -1
  410. package/dist/trpc/routers/packs.js +18 -4
  411. package/dist/trpc/routers/packs.js.map +1 -1
  412. package/dist/trpc/routers/projects.d.ts +66 -2
  413. package/dist/trpc/routers/projects.d.ts.map +1 -1
  414. package/dist/trpc/routers/projects.js +19 -2
  415. package/dist/trpc/routers/projects.js.map +1 -1
  416. package/dist/trpc/routers/runs.d.ts +472 -0
  417. package/dist/trpc/routers/runs.d.ts.map +1 -0
  418. package/dist/trpc/routers/runs.js +40 -0
  419. package/dist/trpc/routers/runs.js.map +1 -0
  420. package/dist/trpc/routers/settings.d.ts +3 -0
  421. package/dist/trpc/routers/settings.d.ts.map +1 -1
  422. package/dist/trpc/routers/settings.js +32 -5
  423. package/dist/trpc/routers/settings.js.map +1 -1
  424. package/dist/trpc/routers/sketch.d.ts +44 -0
  425. package/dist/trpc/routers/sketch.d.ts.map +1 -1
  426. package/dist/trpc/routers/sketch.js +2 -2
  427. package/dist/trpc/routers/sketch.js.map +1 -1
  428. package/dist/trpc/routers/storyboards.d.ts +903 -1
  429. package/dist/trpc/routers/storyboards.d.ts.map +1 -1
  430. package/dist/trpc/routers/storyboards.js +8 -5
  431. package/dist/trpc/routers/storyboards.js.map +1 -1
  432. package/dist/trpc/routers/threads.d.ts.map +1 -1
  433. package/dist/trpc/routers/threads.js +8 -2
  434. package/dist/trpc/routers/threads.js.map +1 -1
  435. package/dist/trpc/routers/timeline.d.ts +647 -63
  436. package/dist/trpc/routers/timeline.d.ts.map +1 -1
  437. package/dist/trpc/routers/timeline.js +193 -4
  438. package/dist/trpc/routers/timeline.js.map +1 -1
  439. package/dist/trpc/routers/triggers.d.ts.map +1 -1
  440. package/dist/trpc/routers/triggers.js +18 -3
  441. package/dist/trpc/routers/triggers.js.map +1 -1
  442. package/dist/trpc/routers/users.d.ts +3 -2
  443. package/dist/trpc/routers/users.d.ts.map +1 -1
  444. package/dist/trpc/routers/users.js +25 -27
  445. package/dist/trpc/routers/users.js.map +1 -1
  446. package/dist/trpc/routers/worker.d.ts +10 -9
  447. package/dist/trpc/routers/worker.d.ts.map +1 -1
  448. package/dist/trpc/routers/worker.js +40 -16
  449. package/dist/trpc/routers/worker.js.map +1 -1
  450. package/dist/trpc/routers/workflows.d.ts +202 -3
  451. package/dist/trpc/routers/workflows.d.ts.map +1 -1
  452. package/dist/trpc/routers/workflows.js +72 -12
  453. package/dist/trpc/routers/workflows.js.map +1 -1
  454. package/dist/trpc/sandbox-coverage.d.ts.map +1 -1
  455. package/dist/trpc/sandbox-coverage.js +119 -0
  456. package/dist/trpc/sandbox-coverage.js.map +1 -1
  457. package/dist/websocket-client-session.d.ts +4 -2
  458. package/dist/websocket-client-session.d.ts.map +1 -1
  459. package/dist/websocket-client-session.js +23 -3
  460. package/dist/websocket-client-session.js.map +1 -1
  461. package/dist/worker-settings-env.d.ts +23 -0
  462. package/dist/worker-settings-env.d.ts.map +1 -0
  463. package/dist/worker-settings-env.js +47 -0
  464. package/dist/worker-settings-env.js.map +1 -0
  465. package/dist/workflow-interface-service.d.ts +7 -0
  466. package/dist/workflow-interface-service.d.ts.map +1 -1
  467. package/package.json +58 -41
  468. package/dist/extension-cdp-bridge.d.ts +0 -85
  469. package/dist/extension-cdp-bridge.d.ts.map +0 -1
  470. package/dist/extension-cdp-bridge.js +0 -163
  471. package/dist/extension-cdp-bridge.js.map +0 -1
@@ -2,18 +2,33 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { join } from "node:path";
3
3
  import { z } from "zod";
4
4
  import sharp from "sharp";
5
- import { getManagedWorkspaceDir, managedWorkspaceKey, workspaceStorageKind } from "@nodetool-ai/config";
6
- import { AmbiguousGameIdError, Asset, Game, Project, Workspace } from "@nodetool-ai/models";
7
- import { exampleGameSummary, gameAssetBinding, gameDocument, installExampleGameInput } from "@nodetool-ai/protocol/game.js";
8
- import { createTopDownRoomGame, gameDocumentOp, GameOpError, validateGame } from "@nodetool-ai/game-runtime";
5
+ import { createLogger, getManagedWorkspaceDir, managedWorkspaceKey, workspaceStorageKind } from "@nodetool-ai/config";
6
+ import { AmbiguousGameIdError, InvalidGameDocumentError, MissingGameDraftSourceError, Asset, Game, Project, Workspace } from "@nodetool-ai/models";
7
+ import { exampleGameSummary, anyGameAssetBinding, gameAssetBinding, gameAssetBinding3D, gameAssetSiblingRebinds, gamePreparedCollider3D, gameModelImportSettings3D, parseGameDocument, anyGameDocument as gameDocument, installExampleGameInput, stagedGameCandidateRecord, stagedGameCandidateRecordPath } from "@nodetool-ai/protocol";
8
+ import { createTopDownRoomGame, createNative3DGame, anyGameDocumentOp as gameDocumentOp, GameOpError, decodePreparedGameCollider3D, trackGameAuthoringEdits, validateAnyGame } from "@nodetool-ai/game-runtime";
9
+ import { normalizeGameModel3D, prepareGameModelBinding3D } from "@nodetool-ai/game-renderer/preparation3d";
10
+ import { contextSecretAvailability, createCapabilityRun, gateFromContext } from "@nodetool-ai/agents";
11
+ import { gameAuthoringCandidate, gameAuthoringProgram, gameAuthoringConflict } from "@nodetool-ai/protocol";
12
+ import { PERMISSION_GATE_CONTEXT_KEY, ProcessingContext, headlessGate } from "@nodetool-ai/runtime";
9
13
  import { getAssetAdapter } from "../../lib/storage.js";
10
14
  import { getAssetStorageKey, retrieveAssetBytes } from "../../lib/asset-paths.js";
11
- import { installExampleGameAssets, listExampleGames } from "../../lib/example-games.js";
15
+ import { getExampleGameBundle, installExampleGameAssets, listExampleGames } from "../../lib/example-games.js";
12
16
  import { workspaceFromRow } from "../../lib/workflow-workspace.js";
17
+ import { loadConfiguredProviders } from "../../configured-providers.js";
18
+ import { createRuntimeContext } from "../../session/model-interfaces.js";
13
19
  import { ApiErrorCode } from "../../error-codes.js";
14
20
  import { router } from "../index.js";
15
21
  import { protectedProcedure } from "../middleware.js";
16
22
  import { throwApiError } from "../error-formatter.js";
23
+ import { boundedGameDraftHistory, MAX_GAME_DRAFT_OPS } from "./gameDraftHistory.js";
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
+ });
17
32
  const idInput = z.object({ id: z.string() });
18
33
  const gameInfo = z.object({
19
34
  id: z.string(),
@@ -27,6 +42,35 @@ const gameInfo = z.object({
27
42
  updatedAt: z.string()
28
43
  });
29
44
  const gameWithDocument = z.object({ game: gameInfo, document: gameDocument });
45
+ const authoringPreview = z.object({
46
+ candidate: gameAuthoringCandidate,
47
+ document: gameDocument,
48
+ conflicts: z.array(gameAuthoringConflict),
49
+ affected_entities: z.array(z.string()),
50
+ changed_dependencies: z.array(z.string()),
51
+ restart_required: z.boolean()
52
+ });
53
+ const assetBrowserInfo = z.object({
54
+ dimension: z.enum(["2d", "3d"]),
55
+ draft_updated_at: z.string(),
56
+ candidate_workspace_id: z.string(),
57
+ candidates: z.array(z.object({
58
+ digest: z.string(), extension: z.string(), media_kind: z.enum(["image", "audio", "font", "model", "collider"]), path: z.string(),
59
+ size: z.number(), modified_at: z.string(), bound_slots: z.array(z.string()), slot: z.string().optional(), prompt: z.string().optional(),
60
+ source: z.string().optional(), recorded: z.boolean()
61
+ })),
62
+ slot_requests: z.record(z.string(), z.object({ kind: z.string(), prompt: z.string().optional(),
63
+ preparation: z.record(z.string(), z.unknown()).optional(), source: z.string() }))
64
+ });
65
+ const assetFrame = z.object({ x: z.number(), y: z.number(), width: z.number(), height: z.number() });
66
+ const generatedAssetResult = z.object({
67
+ error: z.string().optional(),
68
+ binding: anyGameAssetBinding.optional(),
69
+ frames: z.array(assetFrame).optional(),
70
+ tiles: z.array(z.object({ mask: z.number().int(), frame: assetFrame })).optional()
71
+ }).loose();
72
+ const stagedCandidateBinding = z.object({ error: z.string().optional(), binding: anyGameAssetBinding.optional() }).loose();
73
+ const generatedAsset = gameWithDocument.extend({ stagedDigest: z.string().nullable(), installed: z.boolean() });
30
74
  const gameRevisionInfo = z.object({ revision: z.string(), modifiedAt: z.number(), current: z.boolean(), message: z.string().nullable() });
31
75
  function info(game) {
32
76
  return {
@@ -44,6 +88,28 @@ function info(game) {
44
88
  function newRevision() {
45
89
  return randomUUID().replace(/-/g, "");
46
90
  }
91
+ function authoringRun(userId) {
92
+ const context = new ProcessingContext({ jobId: `game-authoring-${randomUUID()}`, userId });
93
+ context.set(PERMISSION_GATE_CONTEXT_KEY, headlessGate("game.authoring"));
94
+ return createCapabilityRun({ context, gate: gateFromContext(context, "game.authoring") });
95
+ }
96
+ /** A run that can reach the user's configured media providers, for editor-initiated asset generation. */
97
+ async function assetGenerationRun(userId, game, workspace) {
98
+ const context = createRuntimeContext({ jobId: `game-asset-${randomUUID()}`, userId, projectId: game.project_id, workspace });
99
+ context.set(PERMISSION_GATE_CONTEXT_KEY, headlessGate("game.assets"));
100
+ return createCapabilityRun({ context, gate: gateFromContext(context, "game.assets"), projectId: game.project_id,
101
+ availableSecrets: contextSecretAvailability(context), providers: await loadConfiguredProviders(userId) });
102
+ }
103
+ function checkAuthoringResult(result) {
104
+ if (result !== null && typeof result === "object" && "error" in result && typeof result.error === "string") {
105
+ if ("code" in result && result.code === "game_draft_source_unavailable") {
106
+ throwApiError(ApiErrorCode.SERVICE_UNAVAILABLE, result.error, "PRECONDITION_FAILED");
107
+ }
108
+ const code = /stale|concurrent|changed since/i.test(result.error)
109
+ ? ApiErrorCode.ALREADY_EXISTS : ApiErrorCode.INVALID_INPUT;
110
+ throwApiError(code, result.error);
111
+ }
112
+ }
47
113
  function sourcePath(game, revision) {
48
114
  return `${game.source_root}/revisions/${revision}/game.json`;
49
115
  }
@@ -74,6 +140,40 @@ async function gameWorkspace(userId, game) {
74
140
  }
75
141
  return workspace;
76
142
  }
143
+ const CONCURRENT_DRAFT_EDIT = "Game draft was modified concurrently";
144
+ async function persistGameAsset(userId, game, workspace, draftUpdatedAt, slot, binding, bytes, extension, contentType, siblingBindings) {
145
+ await workspace.write(`${game.source_root}/assets/${binding.digest}.${extension}`, bytes, contentType);
146
+ const installed = await Asset.create({
147
+ user_id: userId, project_id: game.project_id, parent_id: userId,
148
+ name: `${slot}.${extension}`, content_type: contentType, size: bytes.byteLength,
149
+ metadata: { game_source_asset_id: binding.mediaKind === "model" ? binding.sourceAssetId ?? binding.assetId : binding.assetId,
150
+ game_digest: binding.digest }
151
+ });
152
+ if (!(installed instanceof Asset)) {
153
+ throwApiError(ApiErrorCode.INTERNAL_ERROR, "Asset creation failed");
154
+ }
155
+ const storage = getAssetAdapter();
156
+ let uri = null;
157
+ try {
158
+ uri = await storage.store(getAssetStorageKey(userId, installed.id, contentType), bytes, contentType);
159
+ const op = gameDocumentOp.parse({ op: "bind_asset", slot, binding: { ...binding, assetId: installed.id } });
160
+ const parent = siblingBindings ? gameAssetBinding.safeParse({ ...binding, assetId: installed.id }) : null;
161
+ const siblings = siblingBindings && parent?.success
162
+ ? Object.entries(siblingBindings(parent.data)).map(([sibling, siblingBinding]) => gameDocumentOp.parse({ op: "bind_asset", slot: sibling, binding: siblingBinding })) : [];
163
+ const saved = await Game.updateDraft(userId, game.id, draftUpdatedAt, [op, ...siblings], workspace, { actor: "user" });
164
+ if (!saved) {
165
+ throwApiError(ApiErrorCode.ALREADY_EXISTS, CONCURRENT_DRAFT_EDIT);
166
+ }
167
+ return { game: info(saved.game), document: saved.document };
168
+ }
169
+ catch (error) {
170
+ if (uri) {
171
+ await storage.delete(uri);
172
+ }
173
+ await installed.delete();
174
+ throw error;
175
+ }
176
+ }
77
177
  async function projectWorkspace(userId, projectId) {
78
178
  const rows = await Workspace.listByProject(userId, projectId);
79
179
  const existing = rows.find((row) => row.isAccessible() && (process.env["NODETOOL_ENV"] !== "production" || row.isManaged()));
@@ -105,10 +205,14 @@ async function projectWorkspace(userId, projectId) {
105
205
  }));
106
206
  }
107
207
  function validatedDocument(value, id, revision) {
108
- const source = gameDocument.parse(value);
109
- const result = validateGame({ ...source, id, revision });
208
+ const parsed = parseGameDocument(value);
209
+ if (!parsed.ok) {
210
+ throwApiError(ApiErrorCode.INVALID_INPUT, parsed.diagnostics.map((issue) => `${issue.code} ${issue.path.join(".")}: ${issue.message}`).join("; "));
211
+ }
212
+ const source = parsed.document;
213
+ const result = validateAnyGame({ ...source, id, revision });
110
214
  if (!result.valid || !result.document) {
111
- throwApiError(ApiErrorCode.INVALID_INPUT, result.errors.join("; "));
215
+ throwApiError(ApiErrorCode.INVALID_INPUT, result.diagnostics.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
112
216
  }
113
217
  return result.document;
114
218
  }
@@ -129,7 +233,7 @@ async function readRevision(workspace, game, revision) {
129
233
  async function writeRevision(workspace, game, document) {
130
234
  await workspace.write(sourcePath(game, document.revision), JSON.stringify(document), "application/json");
131
235
  }
132
- async function publishDocument(userId, game, baseRevision, value, message) {
236
+ async function publishDocument(userId, game, baseRevision, value, message, restoreAuthoring = false, expectedDraftUpdatedAt, expectedDigest) {
133
237
  if (baseRevision !== game.current_revision) {
134
238
  throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game was modified concurrently");
135
239
  }
@@ -138,21 +242,52 @@ async function publishDocument(userId, game, baseRevision, value, message) {
138
242
  if (!draft) {
139
243
  throwApiError(ApiErrorCode.NOT_FOUND, "Game draft not found");
140
244
  }
245
+ if ((value !== undefined || restoreAuthoring) && !expectedDraftUpdatedAt) {
246
+ throwApiError(ApiErrorCode.INVALID_INPUT, "A draft token is required for publish and restore");
247
+ }
248
+ if (expectedDraftUpdatedAt !== undefined && expectedDraftUpdatedAt !== draft.game.draft_updated_at) {
249
+ throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
250
+ }
251
+ const candidate = validatedDocument(value ?? draft.document, game.id, draft.game.current_revision);
252
+ const digest = createHash("sha256").update(JSON.stringify(candidate)).digest("hex");
253
+ if (expectedDigest !== undefined && digest !== expectedDigest) {
254
+ throwApiError(ApiErrorCode.ALREADY_EXISTS, "The validated game document changed before publish");
255
+ }
141
256
  const revision = newRevision();
142
- const document = validatedDocument(value ?? draft?.document, game.id, revision);
257
+ let document = { ...candidate, revision };
258
+ if (!restoreAuthoring && value !== undefined && JSON.stringify(document.authoring) !== JSON.stringify(draft.document.authoring)) {
259
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Retained construction must be changed through preview and apply");
260
+ }
261
+ if (!restoreAuthoring && draft.document.authoring) {
262
+ document = trackGameAuthoringEdits(draft.document, document);
263
+ }
264
+ if ((document.schemaVersion === 3) !== (draft.document.schemaVersion === 3)) {
265
+ throwApiError(ApiErrorCode.INVALID_INPUT, "A game cannot change dimension");
266
+ }
143
267
  await writeRevision(workspace, game, document);
144
268
  const updated = await Game.publish(userId, game.id, baseRevision, revision, draft.game.draft_updated_at, workspace, message);
145
269
  if (!updated) {
146
- await workspace.delete(sourcePath(game, revision));
270
+ try {
271
+ await workspace.delete(sourcePath(game, revision));
272
+ }
273
+ catch (error) {
274
+ log.error("Losing game publication revision cleanup failed", { gameId: game.id, revision, error: String(error) });
275
+ }
147
276
  throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game was modified concurrently");
148
277
  }
149
- if (draft.game.draft_version_id) {
150
- await workspace.delete(`${game.source_root}/drafts/${draft.game.draft_version_id}.json`);
278
+ try {
279
+ if (draft.game.draft_version_id) {
280
+ await workspace.delete(`${game.source_root}/drafts/${draft.game.draft_version_id}.json`);
281
+ }
282
+ await workspace.write(`${game.source_root}/draft.json`, JSON.stringify(document), "application/json");
283
+ await Game.pruneRevisionFiles(userId, game.id, workspace);
284
+ }
285
+ catch (error) {
286
+ log.error("Game post-publish cleanup failed", { gameId: game.id, error: String(error) });
151
287
  }
152
- await workspace.write(`${game.source_root}/draft.json`, JSON.stringify(document), "application/json");
153
288
  return { game: info(updated), document };
154
289
  }
155
- async function createGame(userId, projectId, name, source) {
290
+ async function createGame(userId, projectId, name, source, dimension = "2d") {
156
291
  if (!(await Project.findOwned(userId, projectId))) {
157
292
  throwApiError(ApiErrorCode.NOT_FOUND, "Project not found");
158
293
  }
@@ -163,7 +298,10 @@ async function createGame(userId, projectId, name, source) {
163
298
  }
164
299
  const id = randomUUID().replace(/-/g, "");
165
300
  const revision = newRevision();
166
- const document = validatedDocument(source ?? createTopDownRoomGame(id), id, revision);
301
+ const document = validatedDocument(source ?? (dimension === "3d" ? createNative3DGame(id) : createTopDownRoomGame(id)), id, revision);
302
+ if (document.authoring) {
303
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Attach retained construction through preview and apply after creating the game");
304
+ }
167
305
  const candidate = new Game({
168
306
  id,
169
307
  user_id: userId,
@@ -185,8 +323,167 @@ async function createGame(userId, projectId, name, source) {
185
323
  throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game already exists");
186
324
  return { game: info(game), document };
187
325
  }
326
+ const installCandidateInput = idInput.extend({
327
+ baseRevision: z.string().optional(),
328
+ baseUpdatedAt: z.string().optional(),
329
+ slot: z.string().min(1),
330
+ candidateWorkspaceId: z.string().optional(),
331
+ binding: anyGameAssetBinding
332
+ });
333
+ function siblingBindingsFor(slot, assets, options) {
334
+ const { frames, tiles } = options;
335
+ if (frames?.length) {
336
+ return (installed) => Object.fromEntries(frames.map((frame, index) => [`${slot}.frame.${index}`, { ...installed, frame }]));
337
+ }
338
+ if (tiles?.length) {
339
+ return (installed) => Object.fromEntries(tiles.map((tile) => [`${slot}.tile.${tile.mask}`, { ...installed, frame: tile.frame }]));
340
+ }
341
+ if (!options.replaceInPlace) {
342
+ return undefined;
343
+ }
344
+ return (installed) => Object.fromEntries(gameAssetSiblingRebinds(assets, slot, installed).ops.map((op) => [op.slot, op.binding]));
345
+ }
346
+ /** Install a staged candidate's bytes into the draft, with the sibling bindings `options` names. */
347
+ async function installCandidateBinding(userId, input, options = {}) {
348
+ const ctx = { userId };
349
+ const game = await ownedGame(ctx.userId, input.id);
350
+ const workspace = await gameWorkspace(ctx.userId, game);
351
+ const candidateRow = input.candidateWorkspaceId
352
+ ? await Workspace.find(ctx.userId, input.candidateWorkspaceId)
353
+ : null;
354
+ if (input.candidateWorkspaceId && (!candidateRow || candidateRow.project_id !== game.project_id)) {
355
+ throwApiError(ApiErrorCode.NOT_FOUND, "Candidate workspace not found");
356
+ }
357
+ const candidateWorkspace = candidateRow ? workspaceFromRow(candidateRow) : workspace;
358
+ if (!candidateWorkspace) {
359
+ throwApiError(ApiErrorCode.SERVICE_UNAVAILABLE, "Candidate workspace storage is unavailable");
360
+ }
361
+ const digest = input.binding.digest;
362
+ if (!/^[a-f0-9]{64}$/.test(digest)) {
363
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Invalid candidate digest");
364
+ }
365
+ if (input.binding.mediaKind === "font" && !input.binding.fontFormat) {
366
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Font binding needs a TrueType or OpenType format");
367
+ }
368
+ if (input.binding.mediaKind === "hdri") {
369
+ throwApiError(ApiErrorCode.INVALID_INPUT, "HDRI candidates cannot be installed until HDRI preparation verifies their bytes and dimensions");
370
+ }
371
+ const formats = input.binding.mediaKind === "model"
372
+ ? [["glb", "model/gltf-binary"]]
373
+ : input.binding.mediaKind === "collider"
374
+ ? [["json", "application/json"]]
375
+ : input.binding.mediaKind === "audio"
376
+ ? [["wav", "audio/wav"], ["mp3", "audio/mpeg"], ["ogg", "audio/ogg"]]
377
+ : input.binding.mediaKind === "font"
378
+ ? [[input.binding.fontFormat, `font/${input.binding.fontFormat}`]]
379
+ : [["png", "image/png"], ["jpg", "image/jpeg"], ["webp", "image/webp"]];
380
+ let candidate = null;
381
+ for (const [extension, contentType] of formats) {
382
+ if (!extension || !contentType)
383
+ continue;
384
+ const path = `${game.source_root}/assets/${digest}.${extension}`;
385
+ const bytes = await candidateWorkspace.read(path);
386
+ if (bytes) {
387
+ candidate = { extension, contentType, bytes };
388
+ break;
389
+ }
390
+ }
391
+ if (!candidate || createHash("sha256").update(candidate.bytes).digest("hex") !== digest) {
392
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Candidate asset is missing or changed");
393
+ }
394
+ const { extension, contentType, bytes } = candidate;
395
+ let binding = input.binding;
396
+ if (binding.mediaKind === "model") {
397
+ const prepared = await prepareGameModelBinding3D(bytes, { assetId: binding.assetId, expectedDigest: digest });
398
+ if (!prepared.ok) {
399
+ throwApiError(ApiErrorCode.INVALID_INPUT, prepared.diagnostics.map((issue) => issue.message).join("; "));
400
+ }
401
+ const sourceAsset = binding.sourceAssetId ? await Asset.find(ctx.userId, binding.sourceAssetId) : null;
402
+ if (binding.sourceAssetId && !sourceAsset) {
403
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Model source asset is not owned");
404
+ }
405
+ const canonical = { ...prepared.binding, required: binding.required };
406
+ if (binding.provenance) {
407
+ canonical.provenance = binding.provenance;
408
+ }
409
+ if (binding.importSettings) {
410
+ canonical.importSettings = binding.importSettings;
411
+ }
412
+ if (binding.sourceDigest) {
413
+ canonical.sourceDigest = binding.sourceDigest;
414
+ }
415
+ if (sourceAsset) {
416
+ canonical.sourceAssetId = sourceAsset.id;
417
+ }
418
+ binding = canonical;
419
+ }
420
+ else if (binding.mediaKind === "collider") {
421
+ let geometry;
422
+ try {
423
+ geometry = JSON.parse(new TextDecoder().decode(bytes));
424
+ }
425
+ catch {
426
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Prepared collider is not valid JSON");
427
+ }
428
+ const parsed = gamePreparedCollider3D.safeParse(geometry);
429
+ if (!parsed.success || binding.shape === "triangleMesh" && !parsed.data.indices?.length) {
430
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Prepared collider has invalid vertices or triangle indices");
431
+ }
432
+ const { vertices, indices } = parsed.data;
433
+ const minimum = { x: Infinity, y: Infinity, z: Infinity };
434
+ const maximum = { x: -Infinity, y: -Infinity, z: -Infinity };
435
+ for (let index = 0; index < vertices.length; index += 3) {
436
+ minimum.x = Math.min(minimum.x, vertices[index]);
437
+ maximum.x = Math.max(maximum.x, vertices[index]);
438
+ minimum.y = Math.min(minimum.y, vertices[index + 1]);
439
+ maximum.y = Math.max(maximum.y, vertices[index + 1]);
440
+ minimum.z = Math.min(minimum.z, vertices[index + 2]);
441
+ maximum.z = Math.max(maximum.z, vertices[index + 2]);
442
+ }
443
+ binding = { ...binding, preparationVersion: "1", bounds: { min: minimum, max: maximum }, vertices: vertices.length / 3, triangles: (indices?.length ?? 0) / 3 };
444
+ try {
445
+ await decodePreparedGameCollider3D(bytes, gameAssetBinding3D.options[1].parse(binding));
446
+ }
447
+ catch (error) {
448
+ throwApiError(ApiErrorCode.INVALID_INPUT, error instanceof Error ? error.message : "Prepared collider is invalid");
449
+ }
450
+ }
451
+ if (input.baseRevision && game.current_revision !== input.baseRevision) {
452
+ throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game was modified concurrently");
453
+ }
454
+ const draft = await Game.readDraft(ctx.userId, game.id, workspace);
455
+ if (!draft)
456
+ throwApiError(ApiErrorCode.NOT_FOUND, "Game draft not found");
457
+ if (input.baseUpdatedAt && draft.game.draft_updated_at !== input.baseUpdatedAt) {
458
+ throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
459
+ }
460
+ const targetBinding = draft.document.schemaVersion === 3 ? gameAssetBinding3D.safeParse(binding) :
461
+ gameAssetBinding.safeParse(binding);
462
+ if (!targetBinding.success || draft.document.schemaVersion !== 3 && (binding.mediaKind === "model" || binding.mediaKind === "collider")) {
463
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Asset binding does not match the game dimension");
464
+ }
465
+ 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);
466
+ }
467
+ /**
468
+ * Install into whichever draft is current when the bytes are ready. An editor
469
+ * session saves while a generation runs, so a slot binding must not depend on
470
+ * the draft token the request started from. The write itself stays a
471
+ * compare-and-swap, retried when a save lands between the read and the write.
472
+ */
473
+ async function installOnLatestDraft(userId, input, options) {
474
+ for (let attempt = 1;; attempt++) {
475
+ try {
476
+ return await installCandidateBinding(userId, input, options);
477
+ }
478
+ catch (error) {
479
+ if (attempt >= 3 || !(error instanceof Error) || error.message !== CONCURRENT_DRAFT_EDIT) {
480
+ throw error;
481
+ }
482
+ }
483
+ }
484
+ }
188
485
  export const gamesRouter = router({
189
- list: protectedProcedure
486
+ list: gameProcedure
190
487
  .input(z.object({ projectId: z.string() }))
191
488
  .output(z.array(gameInfo))
192
489
  .query(async ({ ctx, input }) => {
@@ -195,14 +492,29 @@ export const gamesRouter = router({
195
492
  }
196
493
  return (await Game.listByProject(ctx.userId, input.projectId)).map(info);
197
494
  }),
198
- create: protectedProcedure
199
- .input(z.object({ projectId: z.string(), name: z.string().min(1).max(200), document: gameDocument.optional() }))
495
+ create: gameProcedure
496
+ .input(z.object({ projectId: z.string(), name: z.string().min(1).max(200), dimension: z.enum(["2d", "3d"]).optional(), document: gameDocument.optional() }))
200
497
  .output(gameWithDocument)
201
- .mutation(({ ctx, input }) => createGame(ctx.userId, input.projectId, input.name, input.document)),
202
- examples: protectedProcedure
498
+ .mutation(({ ctx, input }) => {
499
+ if (input.dimension && input.document && ((input.document.schemaVersion === 3) !== (input.dimension === "3d"))) {
500
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Requested dimension does not match the supplied game document");
501
+ }
502
+ return createGame(ctx.userId, input.projectId, input.name, input.document, input.dimension);
503
+ }),
504
+ examples: gameProcedure
203
505
  .output(z.array(exampleGameSummary))
204
506
  .query(({ ctx }) => listExampleGames(ctx.apiOptions)),
205
- installExample: protectedProcedure
507
+ example: gameProcedure
508
+ .input(z.object({ slug: z.string().min(1) }))
509
+ .output(z.object({ name: z.string(), document: gameDocument }))
510
+ .query(({ ctx, input }) => {
511
+ const bundle = getExampleGameBundle(ctx.apiOptions, input.slug);
512
+ if (!bundle) {
513
+ throwApiError(ApiErrorCode.NOT_FOUND, "Example game not found");
514
+ }
515
+ return { name: bundle.name, document: bundle.document };
516
+ }),
517
+ installExample: gameProcedure
206
518
  .input(installExampleGameInput)
207
519
  .output(gameWithDocument)
208
520
  .mutation(async ({ ctx, input }) => {
@@ -222,7 +534,7 @@ export const gamesRouter = router({
222
534
  throw error;
223
535
  }
224
536
  }),
225
- get: protectedProcedure
537
+ get: gameProcedure
226
538
  .input(idInput.extend({ revision: z.string().optional() }))
227
539
  .output(gameWithDocument)
228
540
  .query(async ({ ctx, input }) => {
@@ -230,7 +542,7 @@ export const gamesRouter = router({
230
542
  const workspace = await gameWorkspace(ctx.userId, game);
231
543
  return { game: info(game), document: await readRevision(workspace, game, input.revision ?? game.current_revision) };
232
544
  }),
233
- getDraft: protectedProcedure
545
+ getDraft: gameProcedure
234
546
  .input(idInput)
235
547
  .output(gameWithDocument)
236
548
  .query(async ({ ctx, input }) => {
@@ -240,8 +552,8 @@ export const gamesRouter = router({
240
552
  throwApiError(ApiErrorCode.NOT_FOUND, "Game draft not found");
241
553
  return { game: info(draft.game), document: draft.document };
242
554
  }),
243
- saveDraft: protectedProcedure
244
- .input(idInput.extend({ baseUpdatedAt: z.string(), ops: z.array(gameDocumentOp).min(1) }))
555
+ saveDraft: gameProcedure
556
+ .input(idInput.extend({ baseUpdatedAt: z.string(), ops: z.array(gameDocumentOp).min(1).max(MAX_GAME_DRAFT_OPS) }))
245
557
  .output(gameWithDocument)
246
558
  .mutation(async ({ ctx, input }) => {
247
559
  const game = await ownedGame(ctx.userId, input.id);
@@ -255,10 +567,59 @@ export const gamesRouter = router({
255
567
  if (error instanceof GameOpError) {
256
568
  throwApiError(ApiErrorCode.INVALID_INPUT, `Op ${error.opIndex}: ${error.path}: ${error.message}`);
257
569
  }
570
+ if (error instanceof InvalidGameDocumentError) {
571
+ throwApiError(ApiErrorCode.INVALID_INPUT, error.message);
572
+ }
573
+ throw error;
574
+ }
575
+ }),
576
+ saveDraftDocument: gameProcedure
577
+ .input(idInput.extend({ baseUpdatedAt: z.string(), document: gameDocument }))
578
+ .output(gameWithDocument)
579
+ .mutation(async ({ ctx, input }) => {
580
+ const game = await ownedGame(ctx.userId, input.id);
581
+ try {
582
+ const saved = await Game.saveDraftDocument(ctx.userId, game.id, input.baseUpdatedAt, input.document, await gameWorkspace(ctx.userId, game));
583
+ if (!saved)
584
+ throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
585
+ return { game: info(saved.game), document: saved.document };
586
+ }
587
+ catch (error) {
588
+ if (error instanceof InvalidGameDocumentError) {
589
+ throwApiError(ApiErrorCode.INVALID_INPUT, error.message);
590
+ }
258
591
  throw error;
259
592
  }
260
593
  }),
261
- draftChanges: protectedProcedure
594
+ previewAuthoring: gameProcedure
595
+ .input(idInput.extend({ program: gameAuthoringProgram.optional() }).strict())
596
+ .output(authoringPreview)
597
+ .mutation(async ({ ctx, input }) => {
598
+ const game = await ownedGame(ctx.userId, input.id);
599
+ const args = { game_id: game.id };
600
+ if (input.program) {
601
+ args.program = input.program;
602
+ }
603
+ const result = await authoringRun(ctx.userId).invoke("preview_native_game_authoring", args);
604
+ checkAuthoringResult(result);
605
+ return authoringPreview.parse(result);
606
+ }),
607
+ applyAuthoring: gameProcedure
608
+ .input(idInput.extend({ candidate: gameAuthoringCandidate }).strict())
609
+ .output(gameWithDocument)
610
+ .mutation(async ({ ctx, input }) => {
611
+ const game = await ownedGame(ctx.userId, input.id);
612
+ const result = await authoringRun(ctx.userId).invoke("apply_native_game_authoring", {
613
+ game_id: game.id, candidate: input.candidate
614
+ });
615
+ checkAuthoringResult(result);
616
+ const draft = await Game.readDraft(ctx.userId, game.id, await gameWorkspace(ctx.userId, game));
617
+ if (!draft) {
618
+ throwApiError(ApiErrorCode.NOT_FOUND, "Game draft not found");
619
+ }
620
+ return { game: info(draft.game), document: draft.document };
621
+ }),
622
+ draftChanges: gameProcedure
262
623
  .input(idInput)
263
624
  .output(z.array(z.object({
264
625
  id: z.string(), actor: z.enum(["agent", "user"]), threadId: z.string().nullable(),
@@ -268,9 +629,9 @@ export const gamesRouter = router({
268
629
  })))
269
630
  .query(async ({ ctx, input }) => {
270
631
  const game = await ownedGame(ctx.userId, input.id);
271
- return (await Game.listDraftChanges(ctx.userId, game.id)).map(({ gameId: _gameId, ...change }) => change);
632
+ return boundedGameDraftHistory((await Game.listDraftChanges(ctx.userId, game.id)).map(({ gameId: _gameId, ...change }) => change));
272
633
  }),
273
- draftBeforeChange: protectedProcedure
634
+ draftBeforeChange: gameProcedure
274
635
  .input(idInput.extend({ changeId: z.string() }))
275
636
  .output(gameDocument)
276
637
  .query(async ({ ctx, input }) => {
@@ -280,7 +641,7 @@ export const gamesRouter = router({
280
641
  throwApiError(ApiErrorCode.NOT_FOUND, "Game draft change not found");
281
642
  return document;
282
643
  }),
283
- revisions: protectedProcedure
644
+ revisions: gameProcedure
284
645
  .input(idInput)
285
646
  .output(z.array(gameRevisionInfo))
286
647
  .query(async ({ ctx, input }) => {
@@ -295,61 +656,102 @@ export const gamesRouter = router({
295
656
  });
296
657
  return revisions.sort((a, b) => b.modifiedAt - a.modifiedAt);
297
658
  }),
298
- publish: protectedProcedure
299
- .input(idInput.extend({ baseRevision: z.string(), document: gameDocument.optional(), message: z.string().trim().max(500).optional() }))
659
+ publish: gameProcedure
660
+ .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() }))
300
661
  .output(gameWithDocument)
301
- .mutation(async ({ ctx, input }) => publishDocument(ctx.userId, await ownedGame(ctx.userId, input.id), input.baseRevision, input.document, input.message)),
302
- restore: protectedProcedure
303
- .input(idInput.extend({ baseRevision: z.string(), revision: z.string() }))
662
+ .mutation(async ({ ctx, input }) => publishDocument(ctx.userId, await ownedGame(ctx.userId, input.id), input.baseRevision, input.document, input.message, false, input.baseUpdatedAt, input.expectedDigest)),
663
+ restore: gameProcedure
664
+ .input(idInput.extend({ baseRevision: z.string(), baseUpdatedAt: z.string(), revision: z.string() }))
304
665
  .output(gameWithDocument)
305
666
  .mutation(async ({ ctx, input }) => {
306
667
  const game = await ownedGame(ctx.userId, input.id);
307
668
  const workspace = await gameWorkspace(ctx.userId, game);
308
669
  const oldDocument = await readRevision(workspace, game, input.revision);
309
- return publishDocument(ctx.userId, game, input.baseRevision, oldDocument);
670
+ return publishDocument(ctx.userId, game, input.baseRevision, oldDocument, undefined, true, input.baseUpdatedAt);
310
671
  }),
311
- restoreDraft: protectedProcedure
672
+ restoreDraft: gameProcedure
312
673
  .input(idInput.extend({ baseUpdatedAt: z.string(), revision: z.string() }))
313
674
  .output(gameWithDocument)
314
675
  .mutation(async ({ ctx, input }) => {
315
676
  const game = await ownedGame(ctx.userId, input.id);
316
677
  const workspace = await gameWorkspace(ctx.userId, game);
317
- const document = await readRevision(workspace, game, input.revision);
318
- const restored = await Game.replaceDraft(ctx.userId, game.id, input.baseUpdatedAt, document, workspace);
319
- if (!restored)
320
- throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
321
- return { game: info(restored.game), document: restored.document };
678
+ try {
679
+ const document = await readRevision(workspace, game, input.revision);
680
+ const restored = await Game.replaceDraft(ctx.userId, game.id, input.baseUpdatedAt, document, workspace);
681
+ if (!restored)
682
+ throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
683
+ return { game: info(restored.game), document: restored.document };
684
+ }
685
+ catch (error) {
686
+ if (error instanceof InvalidGameDocumentError || error instanceof z.ZodError) {
687
+ throwApiError(ApiErrorCode.INVALID_INPUT, error.message);
688
+ }
689
+ throw error;
690
+ }
322
691
  }),
323
- installAsset: protectedProcedure
324
- .input(idInput.extend({ baseRevision: z.string().optional(), baseUpdatedAt: z.string().optional(), slot: z.string().min(1), assetId: z.string(), expectedDigest: z.string().optional() }))
692
+ installAsset: gameProcedure
693
+ .input(idInput.extend({ baseRevision: z.string().optional(), baseUpdatedAt: z.string().optional(), slot: z.string().min(1),
694
+ assetId: z.string(), expectedDigest: z.string().optional(), importSettings: gameModelImportSettings3D.partial().optional(),
695
+ dependencyAssetIds: z.record(z.string(), z.string()).refine((value) => Object.keys(value).length <= 320, "Too many model dependencies").optional() }))
325
696
  .output(gameWithDocument)
326
697
  .mutation(async ({ ctx, input }) => {
327
698
  const game = await ownedGame(ctx.userId, input.id);
699
+ const workspace = await gameWorkspace(ctx.userId, game);
700
+ const draft = await Game.readDraft(ctx.userId, game.id, workspace);
701
+ if (!draft) {
702
+ throwApiError(ApiErrorCode.NOT_FOUND, "Game draft not found");
703
+ }
704
+ if (input.baseRevision && game.current_revision !== input.baseRevision) {
705
+ throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game was modified concurrently");
706
+ }
707
+ if (input.baseUpdatedAt && draft.game.draft_updated_at !== input.baseUpdatedAt) {
708
+ throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
709
+ }
710
+ const is3D = draft.document.schemaVersion === 3;
328
711
  const asset = await Asset.find(ctx.userId, input.assetId);
329
- if (!asset || !asset.content_type.startsWith("image/")) {
330
- throwApiError(ApiErrorCode.NOT_FOUND, "Image asset not found");
712
+ if (!asset || (is3D ? !["model/gltf-binary", "model/gltf+json"].includes(asset.content_type) : !asset.content_type.startsWith("image/"))) {
713
+ throwApiError(ApiErrorCode.NOT_FOUND, is3D ? "Model asset not found" : "Image asset not found");
714
+ }
715
+ if (is3D && asset.size !== null && asset.size > 64 * 1024 * 1024) {
716
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Model source exceeds import byte budget");
331
717
  }
332
718
  const bytes = await retrieveAssetBytes(getAssetAdapter(), ctx.userId, asset.id, asset.content_type);
333
- if (!bytes)
334
- throwApiError(ApiErrorCode.NOT_FOUND, "Image bytes not found");
719
+ if (!bytes) {
720
+ throwApiError(ApiErrorCode.NOT_FOUND, is3D ? "Model bytes not found" : "Image bytes not found");
721
+ }
335
722
  const digest = createHash("sha256").update(bytes).digest("hex");
336
723
  if (input.expectedDigest && digest !== input.expectedDigest) {
337
- throwApiError(ApiErrorCode.INVALID_INPUT, "Image content digest changed");
724
+ throwApiError(ApiErrorCode.INVALID_INPUT, is3D ? "Model content digest changed" : "Image content digest changed");
725
+ }
726
+ if (draft.document.schemaVersion === 3) {
727
+ const normalized = await normalizeGameModel3D(bytes, {
728
+ assetId: asset.id, sourceAssetId: asset.id, importSettings: input.importSettings,
729
+ provenance: asset.workflow_id ?? undefined,
730
+ resolveDependency: async (uri) => {
731
+ const dependencyId = input.dependencyAssetIds && Object.hasOwn(input.dependencyAssetIds, uri)
732
+ ? input.dependencyAssetIds[uri] : undefined;
733
+ if (!dependencyId) {
734
+ return null;
735
+ }
736
+ const dependency = await Asset.find(ctx.userId, dependencyId);
737
+ if (!dependency) {
738
+ throwApiError(ApiErrorCode.NOT_FOUND, "Model dependency asset not found");
739
+ }
740
+ if (dependency.size !== null && dependency.size > 64 * 1024 * 1024) {
741
+ throwApiError(ApiErrorCode.INVALID_INPUT, "Model dependency exceeds import byte budget");
742
+ }
743
+ return retrieveAssetBytes(getAssetAdapter(), ctx.userId, dependency.id, dependency.content_type);
744
+ }
745
+ });
746
+ if (!normalized.ok) {
747
+ throwApiError(ApiErrorCode.INVALID_INPUT, normalized.diagnostics.map((issue) => issue.message).join("; "));
748
+ }
749
+ return persistGameAsset(ctx.userId, game, workspace, draft.game.draft_updated_at, input.slot, normalized.binding, normalized.bytes, "glb", "model/gltf-binary");
338
750
  }
339
751
  const metadata = await sharp(bytes).metadata();
340
752
  if (!metadata.width || !metadata.height) {
341
753
  throwApiError(ApiErrorCode.INVALID_INPUT, "Image dimensions are unavailable");
342
754
  }
343
- const workspace = await gameWorkspace(ctx.userId, game);
344
- const draft = await Game.readDraft(ctx.userId, game.id, workspace);
345
- if (!draft)
346
- throwApiError(ApiErrorCode.NOT_FOUND, "Game draft not found");
347
- if (input.baseRevision && game.current_revision !== input.baseRevision) {
348
- throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game was modified concurrently");
349
- }
350
- if (input.baseUpdatedAt && draft.game.draft_updated_at !== input.baseUpdatedAt) {
351
- throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
352
- }
353
755
  const saved = await Game.updateDraft(ctx.userId, game.id, draft.game.draft_updated_at, [{
354
756
  op: "bind_asset", slot: input.slot,
355
757
  binding: {
@@ -359,98 +761,91 @@ export const gamesRouter = router({
359
761
  provenance: asset.workflow_id ?? undefined
360
762
  }
361
763
  }], workspace, { actor: "user" });
362
- if (!saved)
764
+ if (!saved) {
363
765
  throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
766
+ }
364
767
  return { game: info(saved.game), document: saved.document };
365
768
  }),
366
- installCandidate: protectedProcedure
769
+ installCandidate: gameProcedure
770
+ .input(installCandidateInput)
771
+ .output(gameWithDocument)
772
+ .mutation(async ({ ctx, input }) => installCandidateBinding(ctx.userId, input)),
773
+ /** Staged candidates and per-slot generation requests for the asset browser. The catalog itself is computed client-side from the draft. */
774
+ assetBrowser: gameProcedure
775
+ .input(idInput)
776
+ .output(assetBrowserInfo)
777
+ .query(async ({ ctx, input }) => {
778
+ const game = await ownedGame(ctx.userId, input.id);
779
+ const result = await authoringRun(ctx.userId).invoke("browse_native_game_assets", { game_id: game.id });
780
+ checkAuthoringResult(result);
781
+ return assetBrowserInfo.parse(result);
782
+ }),
783
+ /**
784
+ * Generate an asset for one slot through `generate_game_asset`, record the
785
+ * prompt beside the staged bytes, and bind it in place on the draft that is
786
+ * current when the bytes are ready. The editor keeps saving while a
787
+ * generation runs, so the request carries no draft token: a sheet's frame
788
+ * bindings, or the slot's existing frame and tile bindings, are written in
789
+ * the same compare-and-swap as the slot, as the user who asked. A
790
+ * provider and model pin the generator; without them the capability picks one.
791
+ */
792
+ generateAsset: gameProcedure
367
793
  .input(idInput.extend({
368
- baseRevision: z.string().optional(),
369
- baseUpdatedAt: z.string().optional(),
370
794
  slot: z.string().min(1),
371
- candidateWorkspaceId: z.string().optional(),
372
- binding: gameAssetBinding
373
- }))
374
- .output(gameWithDocument)
795
+ kind: z.enum(["image", "audio", "music"]),
796
+ prompt: z.string().trim().min(1).max(4000),
797
+ preparation: z.record(z.string(), z.unknown()).optional(),
798
+ provider: z.string().min(1).optional(),
799
+ model: z.string().min(1).optional()
800
+ }).strict().refine((value) => (value.provider === undefined) === (value.model === undefined), "Pass provider and model together"))
801
+ .output(generatedAsset)
375
802
  .mutation(async ({ ctx, input }) => {
376
803
  const game = await ownedGame(ctx.userId, input.id);
377
804
  const workspace = await gameWorkspace(ctx.userId, game);
378
- const candidateRow = input.candidateWorkspaceId
379
- ? await Workspace.find(ctx.userId, input.candidateWorkspaceId)
380
- : null;
381
- if (input.candidateWorkspaceId && (!candidateRow || candidateRow.project_id !== game.project_id)) {
382
- throwApiError(ApiErrorCode.NOT_FOUND, "Candidate workspace not found");
383
- }
384
- const candidateWorkspace = candidateRow ? workspaceFromRow(candidateRow) : workspace;
385
- if (!candidateWorkspace) {
386
- throwApiError(ApiErrorCode.SERVICE_UNAVAILABLE, "Candidate workspace storage is unavailable");
387
- }
388
- const digest = input.binding.digest;
389
- if (!/^[a-f0-9]{64}$/.test(digest)) {
390
- throwApiError(ApiErrorCode.INVALID_INPUT, "Invalid candidate digest");
391
- }
392
- if (input.binding.mediaKind === "font" && !input.binding.fontFormat) {
393
- throwApiError(ApiErrorCode.INVALID_INPUT, "Font binding needs a TrueType or OpenType format");
394
- }
395
- const formats = input.binding.mediaKind === "audio"
396
- ? [["wav", "audio/wav"], ["mp3", "audio/mpeg"], ["ogg", "audio/ogg"]]
397
- : input.binding.mediaKind === "font"
398
- ? [[input.binding.fontFormat, `font/${input.binding.fontFormat}`]]
399
- : [["png", "image/png"], ["jpg", "image/jpeg"], ["webp", "image/webp"]];
400
- let candidate = null;
401
- for (const [extension, contentType] of formats) {
402
- if (!extension || !contentType)
403
- continue;
404
- const path = `${game.source_root}/assets/${digest}.${extension}`;
405
- const bytes = await candidateWorkspace.read(path);
406
- if (bytes) {
407
- candidate = { path, extension, contentType, bytes };
408
- break;
409
- }
805
+ const args = { game_id: game.id, slot: input.slot, kind: input.kind, prompt: input.prompt, install: false };
806
+ if (input.preparation) {
807
+ args["preparation"] = input.preparation;
410
808
  }
411
- if (!candidate || createHash("sha256").update(candidate.bytes).digest("hex") !== digest) {
412
- throwApiError(ApiErrorCode.INVALID_INPUT, "Candidate asset is missing or changed");
809
+ if (input.provider && input.model) {
810
+ args["provider"] = input.provider;
811
+ args["model"] = input.model;
413
812
  }
414
- const { path, extension, contentType, bytes } = candidate;
415
- if (input.baseRevision && game.current_revision !== input.baseRevision) {
416
- throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game was modified concurrently");
813
+ const run = await assetGenerationRun(ctx.userId, game, workspace);
814
+ const result = generatedAssetResult.safeParse(await run.invoke("generate_game_asset", args));
815
+ if (!result.success) {
816
+ throwApiError(ApiErrorCode.INTERNAL_ERROR, "Asset generation returned an invalid result");
417
817
  }
418
- const draft = await Game.readDraft(ctx.userId, game.id, workspace);
419
- if (!draft)
420
- throwApiError(ApiErrorCode.NOT_FOUND, "Game draft not found");
421
- if (input.baseUpdatedAt && draft.game.draft_updated_at !== input.baseUpdatedAt) {
422
- throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
818
+ checkAuthoringResult(result.data);
819
+ const { binding, frames, tiles } = result.data;
820
+ if (!binding) {
821
+ throwApiError(ApiErrorCode.INTERNAL_ERROR, "Asset generation did not stage an asset");
423
822
  }
424
- if (candidateRow && candidateRow.id !== game.workspace_id) {
425
- await workspace.write(path, bytes, contentType);
426
- }
427
- const installed = (await Asset.create({
428
- user_id: ctx.userId,
429
- project_id: game.project_id,
430
- parent_id: ctx.userId,
431
- name: `${input.slot}.${extension}`,
432
- content_type: contentType,
433
- size: bytes.byteLength,
434
- metadata: { game_source_asset_id: input.binding.assetId, game_digest: digest }
823
+ await workspace.write(stagedGameCandidateRecordPath(game.source_root, binding.digest), JSON.stringify(stagedGameCandidateRecord.parse({
824
+ version: 1, digest: binding.digest, slot: input.slot, prompt: input.prompt, source: "generate", stagedAt: new Date().toISOString(), binding
825
+ })), "application/json");
826
+ const installed = await installOnLatestDraft(ctx.userId, { id: game.id, slot: input.slot, binding }, { replaceInPlace: true, frames, tiles });
827
+ return { ...installed, stagedDigest: binding.digest, installed: true };
828
+ }),
829
+ /** Bind a staged candidate to a slot on the current draft. A 2D slot's frame and tile bindings move onto the new bytes. */
830
+ installStagedCandidate: gameProcedure
831
+ .input(idInput.extend({
832
+ slot: z.string().min(1),
833
+ digest: z.string().regex(/^[a-f0-9]{64}$/)
834
+ }).strict())
835
+ .output(gameWithDocument)
836
+ .mutation(async ({ ctx, input }) => {
837
+ const game = await ownedGame(ctx.userId, input.id);
838
+ const picked = stagedCandidateBinding.safeParse(await authoringRun(ctx.userId).invoke("browse_native_game_assets", {
839
+ game_id: game.id, digest: input.digest, slot: input.slot
435
840
  }));
436
- const storage = getAssetAdapter();
437
- const key = getAssetStorageKey(ctx.userId, installed.id, contentType);
438
- let uri = null;
439
- try {
440
- uri = await storage.store(key, bytes, contentType);
441
- const saved = await Game.updateDraft(ctx.userId, game.id, draft.game.draft_updated_at, [{
442
- op: "bind_asset", slot: input.slot, binding: { ...input.binding, assetId: installed.id }
443
- }], workspace, { actor: "user" });
444
- if (!saved)
445
- throwApiError(ApiErrorCode.ALREADY_EXISTS, "Game draft was modified concurrently");
446
- return { game: info(saved.game), document: saved.document };
841
+ if (!picked.success) {
842
+ throwApiError(ApiErrorCode.INTERNAL_ERROR, "Candidate lookup returned an invalid result");
447
843
  }
448
- catch (error) {
449
- if (uri)
450
- await storage.delete(uri);
451
- await installed.delete();
452
- throw error;
844
+ checkAuthoringResult(picked.data);
845
+ if (!picked.data.binding) {
846
+ throwApiError(ApiErrorCode.NOT_FOUND, "Staged candidate not found");
453
847
  }
848
+ return installOnLatestDraft(ctx.userId, { id: game.id, slot: input.slot, binding: picked.data.binding }, { replaceInPlace: true });
454
849
  })
455
850
  });
456
851
  //# sourceMappingURL=games.js.map