@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.
Files changed (389) hide show
  1. package/README.md +5 -0
  2. package/dist/.nodetool-build-stamp +1 -1
  3. package/dist/agent/run-node-tool.d.ts.map +1 -1
  4. package/dist/agent/run-node-tool.js +11 -1
  5. package/dist/agent/run-node-tool.js.map +1 -1
  6. package/dist/chat-prediction-forwarder.d.ts +5 -3
  7. package/dist/chat-prediction-forwarder.d.ts.map +1 -1
  8. package/dist/chat-prediction-forwarder.js +6 -4
  9. package/dist/chat-prediction-forwarder.js.map +1 -1
  10. package/dist/chat-turn-registry.d.ts +2 -1
  11. package/dist/chat-turn-registry.d.ts.map +1 -1
  12. package/dist/chat-turn-registry.js +8 -1
  13. package/dist/chat-turn-registry.js.map +1 -1
  14. package/dist/collection-api.d.ts.map +1 -1
  15. package/dist/collection-api.js +3 -5
  16. package/dist/collection-api.js.map +1 -1
  17. package/dist/custom-providers.d.ts.map +1 -1
  18. package/dist/custom-providers.js +6 -1
  19. package/dist/custom-providers.js.map +1 -1
  20. package/dist/e2e-server.js +1 -1
  21. package/dist/e2e-server.js.map +1 -1
  22. package/dist/example-workflows.d.ts.map +1 -1
  23. package/dist/example-workflows.js +7 -1
  24. package/dist/example-workflows.js.map +1 -1
  25. package/dist/fake-runtime.d.ts +54 -8
  26. package/dist/fake-runtime.d.ts.map +1 -1
  27. package/dist/fake-runtime.js +228 -15
  28. package/dist/fake-runtime.js.map +1 -1
  29. package/dist/file-api.d.ts.map +1 -1
  30. package/dist/file-api.js +6 -8
  31. package/dist/file-api.js.map +1 -1
  32. package/dist/generation-recovery.d.ts.map +1 -1
  33. package/dist/generation-recovery.js +7 -4
  34. package/dist/generation-recovery.js.map +1 -1
  35. package/dist/headless-job-runner.d.ts.map +1 -1
  36. package/dist/headless-job-runner.js +8 -2
  37. package/dist/headless-job-runner.js.map +1 -1
  38. package/dist/http-api.d.ts +4 -2
  39. package/dist/http-api.d.ts.map +1 -1
  40. package/dist/http-api.js +67 -16
  41. package/dist/http-api.js.map +1 -1
  42. package/dist/http-body-schemas.d.ts +2 -0
  43. package/dist/http-body-schemas.d.ts.map +1 -1
  44. package/dist/http-body-schemas.js +8 -1
  45. package/dist/http-body-schemas.js.map +1 -1
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +3 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/job-control.d.ts +8 -0
  51. package/dist/job-control.d.ts.map +1 -1
  52. package/dist/job-control.js +12 -0
  53. package/dist/job-control.js.map +1 -1
  54. package/dist/lib/admin.d.ts +7 -0
  55. package/dist/lib/admin.d.ts.map +1 -0
  56. package/dist/lib/admin.js +19 -0
  57. package/dist/lib/admin.js.map +1 -0
  58. package/dist/lib/app-instances-service.d.ts +35 -0
  59. package/dist/lib/app-instances-service.d.ts.map +1 -1
  60. package/dist/lib/app-instances-service.js +3 -4
  61. package/dist/lib/app-instances-service.js.map +1 -1
  62. package/dist/lib/app-project-export.d.ts +12 -0
  63. package/dist/lib/app-project-export.d.ts.map +1 -0
  64. package/dist/lib/app-project-export.js +42 -0
  65. package/dist/lib/app-project-export.js.map +1 -0
  66. package/dist/lib/applications-service.d.ts.map +1 -1
  67. package/dist/lib/applications-service.js +7 -2
  68. package/dist/lib/applications-service.js.map +1 -1
  69. package/dist/lib/asset-export.d.ts +7 -3
  70. package/dist/lib/asset-export.d.ts.map +1 -1
  71. package/dist/lib/asset-export.js +16 -8
  72. package/dist/lib/asset-export.js.map +1 -1
  73. package/dist/lib/asset-paths.d.ts +1 -1
  74. package/dist/lib/asset-paths.js +1 -1
  75. package/dist/lib/asset-response.d.ts.map +1 -1
  76. package/dist/lib/asset-response.js +13 -6
  77. package/dist/lib/asset-response.js.map +1 -1
  78. package/dist/lib/cloud-profile.d.ts +10 -0
  79. package/dist/lib/cloud-profile.d.ts.map +1 -0
  80. package/dist/lib/cloud-profile.js +13 -0
  81. package/dist/lib/cloud-profile.js.map +1 -0
  82. package/dist/lib/example-apps.d.ts +3 -0
  83. package/dist/lib/example-apps.d.ts.map +1 -1
  84. package/dist/lib/example-apps.js +3 -2
  85. package/dist/lib/example-apps.js.map +1 -1
  86. package/dist/lib/http-user-id.d.ts +2 -0
  87. package/dist/lib/http-user-id.d.ts.map +1 -0
  88. package/dist/lib/http-user-id.js +4 -0
  89. package/dist/lib/http-user-id.js.map +1 -0
  90. package/dist/lib/json-body.d.ts +11 -0
  91. package/dist/lib/json-body.d.ts.map +1 -0
  92. package/dist/lib/json-body.js +13 -0
  93. package/dist/lib/json-body.js.map +1 -0
  94. package/dist/lib/project-archive-host.d.ts +14 -0
  95. package/dist/lib/project-archive-host.d.ts.map +1 -0
  96. package/dist/lib/project-archive-host.js +24 -0
  97. package/dist/lib/project-archive-host.js.map +1 -0
  98. package/dist/lib/project-archive.d.ts +103 -0
  99. package/dist/lib/project-archive.d.ts.map +1 -0
  100. package/dist/lib/project-archive.js +610 -0
  101. package/dist/lib/project-archive.js.map +1 -0
  102. package/dist/lib/public-routes.d.ts +15 -0
  103. package/dist/lib/public-routes.d.ts.map +1 -1
  104. package/dist/lib/public-routes.js +27 -0
  105. package/dist/lib/public-routes.js.map +1 -1
  106. package/dist/lib/public-workflow-share.d.ts +12 -1
  107. package/dist/lib/public-workflow-share.d.ts.map +1 -1
  108. package/dist/lib/public-workflow-share.js +45 -2
  109. package/dist/lib/public-workflow-share.js.map +1 -1
  110. package/dist/lib/python-metadata-roots.d.ts +27 -0
  111. package/dist/lib/python-metadata-roots.d.ts.map +1 -0
  112. package/dist/lib/python-metadata-roots.js +92 -0
  113. package/dist/lib/python-metadata-roots.js.map +1 -0
  114. package/dist/lib/runtime-detection.d.ts +1 -1
  115. package/dist/lib/runtime-detection.d.ts.map +1 -1
  116. package/dist/lib/runtime-detection.js +2 -1
  117. package/dist/lib/runtime-detection.js.map +1 -1
  118. package/dist/lib/server-auth-token.d.ts +18 -0
  119. package/dist/lib/server-auth-token.d.ts.map +1 -0
  120. package/dist/lib/server-auth-token.js +28 -0
  121. package/dist/lib/server-auth-token.js.map +1 -0
  122. package/dist/lib/thumbnail.d.ts.map +1 -1
  123. package/dist/lib/thumbnail.js +3 -1
  124. package/dist/lib/thumbnail.js.map +1 -1
  125. package/dist/lib/timeline-bundle.d.ts +70 -0
  126. package/dist/lib/timeline-bundle.d.ts.map +1 -1
  127. package/dist/lib/timeline-bundle.js +28 -10
  128. package/dist/lib/timeline-bundle.js.map +1 -1
  129. package/dist/lib/timeline-media-export.d.ts +18 -0
  130. package/dist/lib/timeline-media-export.d.ts.map +1 -0
  131. package/dist/lib/timeline-media-export.js +66 -0
  132. package/dist/lib/timeline-media-export.js.map +1 -0
  133. package/dist/lib/timeline-premiere-import.d.ts +24 -0
  134. package/dist/lib/timeline-premiere-import.d.ts.map +1 -0
  135. package/dist/lib/timeline-premiere-import.js +112 -0
  136. package/dist/lib/timeline-premiere-import.js.map +1 -0
  137. package/dist/lib/timeline-premiere.d.ts +38 -0
  138. package/dist/lib/timeline-premiere.d.ts.map +1 -0
  139. package/dist/lib/timeline-premiere.js +87 -0
  140. package/dist/lib/timeline-premiere.js.map +1 -0
  141. package/dist/lib/zip-stream.d.ts +3 -0
  142. package/dist/lib/zip-stream.d.ts.map +1 -1
  143. package/dist/lib/zip-stream.js +6 -0
  144. package/dist/lib/zip-stream.js.map +1 -1
  145. package/dist/mcp-agent-tools.d.ts +48 -0
  146. package/dist/mcp-agent-tools.d.ts.map +1 -1
  147. package/dist/mcp-agent-tools.js +15 -2
  148. package/dist/mcp-agent-tools.js.map +1 -1
  149. package/dist/mcp-server.d.ts +7 -0
  150. package/dist/mcp-server.d.ts.map +1 -1
  151. package/dist/mcp-server.js +163 -16
  152. package/dist/mcp-server.js.map +1 -1
  153. package/dist/mcp-skill-prompts.d.ts +23 -0
  154. package/dist/mcp-skill-prompts.d.ts.map +1 -0
  155. package/dist/mcp-skill-prompts.js +91 -0
  156. package/dist/mcp-skill-prompts.js.map +1 -0
  157. package/dist/mcp-tool-deps.d.ts.map +1 -1
  158. package/dist/mcp-tool-deps.js +8 -1
  159. package/dist/mcp-tool-deps.js.map +1 -1
  160. package/dist/model-download-runtime.d.ts.map +1 -1
  161. package/dist/model-download-runtime.js +2 -1
  162. package/dist/model-download-runtime.js.map +1 -1
  163. package/dist/models-api.d.ts.map +1 -1
  164. package/dist/models-api.js +6 -2
  165. package/dist/models-api.js.map +1 -1
  166. package/dist/node-registry-setup.d.ts +8 -5
  167. package/dist/node-registry-setup.d.ts.map +1 -1
  168. package/dist/node-registry-setup.js +22 -10
  169. package/dist/node-registry-setup.js.map +1 -1
  170. package/dist/oauth-api.d.ts.map +1 -1
  171. package/dist/oauth-api.js +45 -23
  172. package/dist/oauth-api.js.map +1 -1
  173. package/dist/openai-api.d.ts.map +1 -1
  174. package/dist/openai-api.js +10 -2
  175. package/dist/openai-api.js.map +1 -1
  176. package/dist/pack-source.d.ts +15 -0
  177. package/dist/pack-source.d.ts.map +1 -0
  178. package/dist/pack-source.js +85 -0
  179. package/dist/pack-source.js.map +1 -0
  180. package/dist/plugins/websocket.d.ts.map +1 -1
  181. package/dist/plugins/websocket.js +31 -5
  182. package/dist/plugins/websocket.js.map +1 -1
  183. package/dist/routes/applications.d.ts +1 -0
  184. package/dist/routes/applications.d.ts.map +1 -1
  185. package/dist/routes/applications.js +24 -0
  186. package/dist/routes/applications.js.map +1 -1
  187. package/dist/routes/assets.d.ts.map +1 -1
  188. package/dist/routes/assets.js +31 -41
  189. package/dist/routes/assets.js.map +1 -1
  190. package/dist/routes/config.d.ts.map +1 -1
  191. package/dist/routes/config.js +6 -0
  192. package/dist/routes/config.js.map +1 -1
  193. package/dist/routes/documents.d.ts.map +1 -1
  194. package/dist/routes/documents.js +32 -1
  195. package/dist/routes/documents.js.map +1 -1
  196. package/dist/routes/fal-pricing-estimate.d.ts.map +1 -1
  197. package/dist/routes/fal-pricing-estimate.js +2 -0
  198. package/dist/routes/fal-pricing-estimate.js.map +1 -1
  199. package/dist/routes/files.d.ts.map +1 -1
  200. package/dist/routes/files.js +2 -0
  201. package/dist/routes/files.js.map +1 -1
  202. package/dist/routes/games.d.ts +38 -0
  203. package/dist/routes/games.d.ts.map +1 -0
  204. package/dist/routes/games.js +159 -0
  205. package/dist/routes/games.js.map +1 -0
  206. package/dist/routes/integrations.d.ts.map +1 -1
  207. package/dist/routes/integrations.js +22 -26
  208. package/dist/routes/integrations.js.map +1 -1
  209. package/dist/routes/js-scripts.d.ts +1 -1
  210. package/dist/routes/js-scripts.d.ts.map +1 -1
  211. package/dist/routes/js-scripts.js +1 -1
  212. package/dist/routes/js-scripts.js.map +1 -1
  213. package/dist/routes/kie-pricing.js +1 -1
  214. package/dist/routes/kie-pricing.js.map +1 -1
  215. package/dist/routes/nodes.d.ts.map +1 -1
  216. package/dist/routes/nodes.js +13 -1
  217. package/dist/routes/nodes.js.map +1 -1
  218. package/dist/routes/project-archive.d.ts +18 -0
  219. package/dist/routes/project-archive.d.ts.map +1 -0
  220. package/dist/routes/project-archive.js +98 -0
  221. package/dist/routes/project-archive.js.map +1 -0
  222. package/dist/routes/storage.d.ts.map +1 -1
  223. package/dist/routes/storage.js +2 -0
  224. package/dist/routes/storage.js.map +1 -1
  225. package/dist/routes/storyboards.d.ts.map +1 -1
  226. package/dist/routes/storyboards.js +6 -3
  227. package/dist/routes/storyboards.js.map +1 -1
  228. package/dist/routes/timelines.d.ts +8 -4
  229. package/dist/routes/timelines.d.ts.map +1 -1
  230. package/dist/routes/timelines.js +209 -16
  231. package/dist/routes/timelines.js.map +1 -1
  232. package/dist/screenshot-docs-seed.d.ts +24 -0
  233. package/dist/screenshot-docs-seed.d.ts.map +1 -0
  234. package/dist/screenshot-docs-seed.js +421 -0
  235. package/dist/screenshot-docs-seed.js.map +1 -0
  236. package/dist/screenshot-projects.d.ts.map +1 -1
  237. package/dist/screenshot-projects.js +9 -9
  238. package/dist/screenshot-projects.js.map +1 -1
  239. package/dist/screenshot-server.js +274 -17
  240. package/dist/screenshot-server.js.map +1 -1
  241. package/dist/sdk/sdk-node-type-inventory-service.d.ts +1 -1
  242. package/dist/sdk/sdk-node-type-inventory-service.d.ts.map +1 -1
  243. package/dist/sdk/sdk-node-type-inventory-service.js +4 -4
  244. package/dist/sdk/sdk-node-type-inventory-service.js.map +1 -1
  245. package/dist/sdk/sdk-v1-service.js +1 -1
  246. package/dist/sdk/sdk-v1-service.js.map +1 -1
  247. package/dist/server.d.ts +7 -1
  248. package/dist/server.d.ts.map +1 -1
  249. package/dist/server.js +1482 -1487
  250. package/dist/server.js.map +1 -1
  251. package/dist/session/chat-prompt.d.ts.map +1 -1
  252. package/dist/session/chat-prompt.js +6 -0
  253. package/dist/session/chat-prompt.js.map +1 -1
  254. package/dist/session/chat-turn.d.ts +10 -0
  255. package/dist/session/chat-turn.d.ts.map +1 -1
  256. package/dist/session/chat-turn.js +103 -20
  257. package/dist/session/chat-turn.js.map +1 -1
  258. package/dist/session/client-tool-gate.d.ts +23 -0
  259. package/dist/session/client-tool-gate.d.ts.map +1 -0
  260. package/dist/session/client-tool-gate.js +59 -0
  261. package/dist/session/client-tool-gate.js.map +1 -0
  262. package/dist/session/commands.d.ts +14 -0
  263. package/dist/session/commands.d.ts.map +1 -1
  264. package/dist/session/commands.js +113 -14
  265. package/dist/session/commands.js.map +1 -1
  266. package/dist/session/inference.d.ts +25 -2
  267. package/dist/session/inference.d.ts.map +1 -1
  268. package/dist/session/inference.js +105 -21
  269. package/dist/session/inference.js.map +1 -1
  270. package/dist/session/job-execution.d.ts +0 -1
  271. package/dist/session/job-execution.d.ts.map +1 -1
  272. package/dist/session/job-execution.js +43 -39
  273. package/dist/session/job-execution.js.map +1 -1
  274. package/dist/storage-api.d.ts +0 -4
  275. package/dist/storage-api.d.ts.map +1 -1
  276. package/dist/storage-api.js +3 -26
  277. package/dist/storage-api.js.map +1 -1
  278. package/dist/test-ui-server.d.ts +15 -3
  279. package/dist/test-ui-server.d.ts.map +1 -1
  280. package/dist/test-ui-server.js +74 -12
  281. package/dist/test-ui-server.js.map +1 -1
  282. package/dist/triggers/dispatcher.d.ts.map +1 -1
  283. package/dist/triggers/dispatcher.js +24 -4
  284. package/dist/triggers/dispatcher.js.map +1 -1
  285. package/dist/triggers/file-watch.d.ts +2 -2
  286. package/dist/triggers/file-watch.d.ts.map +1 -1
  287. package/dist/triggers/file-watch.js +44 -21
  288. package/dist/triggers/file-watch.js.map +1 -1
  289. package/dist/triggers/scheduler.d.ts.map +1 -1
  290. package/dist/triggers/scheduler.js +7 -5
  291. package/dist/triggers/scheduler.js.map +1 -1
  292. package/dist/trpc/error-formatter.d.ts.map +1 -1
  293. package/dist/trpc/error-formatter.js +2 -1
  294. package/dist/trpc/error-formatter.js.map +1 -1
  295. package/dist/trpc/routers/app-instances.d.ts +63 -0
  296. package/dist/trpc/routers/app-instances.d.ts.map +1 -1
  297. package/dist/trpc/routers/app-runs.d.ts +28 -0
  298. package/dist/trpc/routers/app-runs.d.ts.map +1 -1
  299. package/dist/trpc/routers/applications.d.ts +7 -0
  300. package/dist/trpc/routers/applications.d.ts.map +1 -1
  301. package/dist/trpc/routers/assets.d.ts +8 -0
  302. package/dist/trpc/routers/assets.d.ts.map +1 -1
  303. package/dist/trpc/routers/assets.js +16 -5
  304. package/dist/trpc/routers/assets.js.map +1 -1
  305. package/dist/trpc/routers/collections.d.ts.map +1 -1
  306. package/dist/trpc/routers/collections.js +16 -10
  307. package/dist/trpc/routers/collections.js.map +1 -1
  308. package/dist/trpc/routers/credits.d.ts.map +1 -1
  309. package/dist/trpc/routers/credits.js +11 -1
  310. package/dist/trpc/routers/credits.js.map +1 -1
  311. package/dist/trpc/routers/extension.d.ts +17 -3
  312. package/dist/trpc/routers/extension.d.ts.map +1 -1
  313. package/dist/trpc/routers/extension.js +40 -7
  314. package/dist/trpc/routers/extension.js.map +1 -1
  315. package/dist/trpc/routers/gameDraftHistory.d.ts +1 -2
  316. package/dist/trpc/routers/gameDraftHistory.d.ts.map +1 -1
  317. package/dist/trpc/routers/gameDraftHistory.js +1 -1
  318. package/dist/trpc/routers/gameDraftHistory.js.map +1 -1
  319. package/dist/trpc/routers/games.d.ts +20478 -24667
  320. package/dist/trpc/routers/games.d.ts.map +1 -1
  321. package/dist/trpc/routers/games.js +325 -143
  322. package/dist/trpc/routers/games.js.map +1 -1
  323. package/dist/trpc/routers/jobs.d.ts +7 -0
  324. package/dist/trpc/routers/jobs.d.ts.map +1 -1
  325. package/dist/trpc/routers/jobs.js +10 -3
  326. package/dist/trpc/routers/jobs.js.map +1 -1
  327. package/dist/trpc/routers/js-scripts.d.ts.map +1 -1
  328. package/dist/trpc/routers/js-scripts.js +18 -2
  329. package/dist/trpc/routers/js-scripts.js.map +1 -1
  330. package/dist/trpc/routers/messages.d.ts +10 -0
  331. package/dist/trpc/routers/messages.d.ts.map +1 -1
  332. package/dist/trpc/routers/messages.js +16 -2
  333. package/dist/trpc/routers/messages.js.map +1 -1
  334. package/dist/trpc/routers/models.d.ts.map +1 -1
  335. package/dist/trpc/routers/models.js +58 -37
  336. package/dist/trpc/routers/models.js.map +1 -1
  337. package/dist/trpc/routers/packs.d.ts.map +1 -1
  338. package/dist/trpc/routers/packs.js +19 -5
  339. package/dist/trpc/routers/packs.js.map +1 -1
  340. package/dist/trpc/routers/projects.d.ts +66 -2
  341. package/dist/trpc/routers/projects.d.ts.map +1 -1
  342. package/dist/trpc/routers/projects.js +19 -2
  343. package/dist/trpc/routers/projects.js.map +1 -1
  344. package/dist/trpc/routers/settings.d.ts.map +1 -1
  345. package/dist/trpc/routers/settings.js +30 -5
  346. package/dist/trpc/routers/settings.js.map +1 -1
  347. package/dist/trpc/routers/sketch.d.ts +30 -0
  348. package/dist/trpc/routers/sketch.d.ts.map +1 -1
  349. package/dist/trpc/routers/sketch.js +2 -2
  350. package/dist/trpc/routers/sketch.js.map +1 -1
  351. package/dist/trpc/routers/storyboards.d.ts +171 -1
  352. package/dist/trpc/routers/storyboards.d.ts.map +1 -1
  353. package/dist/trpc/routers/storyboards.js +8 -5
  354. package/dist/trpc/routers/storyboards.js.map +1 -1
  355. package/dist/trpc/routers/threads.d.ts.map +1 -1
  356. package/dist/trpc/routers/threads.js +11 -2
  357. package/dist/trpc/routers/threads.js.map +1 -1
  358. package/dist/trpc/routers/timeline.d.ts +379 -0
  359. package/dist/trpc/routers/timeline.d.ts.map +1 -1
  360. package/dist/trpc/routers/timeline.js +15 -2
  361. package/dist/trpc/routers/timeline.js.map +1 -1
  362. package/dist/trpc/routers/triggers.d.ts.map +1 -1
  363. package/dist/trpc/routers/triggers.js +18 -3
  364. package/dist/trpc/routers/triggers.js.map +1 -1
  365. package/dist/trpc/routers/users.d.ts +3 -2
  366. package/dist/trpc/routers/users.d.ts.map +1 -1
  367. package/dist/trpc/routers/users.js +25 -27
  368. package/dist/trpc/routers/users.js.map +1 -1
  369. package/dist/trpc/routers/worker.d.ts.map +1 -1
  370. package/dist/trpc/routers/worker.js +37 -14
  371. package/dist/trpc/routers/worker.js.map +1 -1
  372. package/dist/trpc/routers/workflows.d.ts +190 -0
  373. package/dist/trpc/routers/workflows.d.ts.map +1 -1
  374. package/dist/trpc/routers/workflows.js +56 -15
  375. package/dist/trpc/routers/workflows.js.map +1 -1
  376. package/dist/trpc/sandbox-coverage.d.ts.map +1 -1
  377. package/dist/trpc/sandbox-coverage.js +34 -0
  378. package/dist/trpc/sandbox-coverage.js.map +1 -1
  379. package/dist/websocket-client-session.d.ts +2 -2
  380. package/dist/websocket-client-session.d.ts.map +1 -1
  381. package/dist/websocket-client-session.js +9 -3
  382. package/dist/websocket-client-session.js.map +1 -1
  383. package/dist/worker-settings-env.d.ts +23 -0
  384. package/dist/worker-settings-env.d.ts.map +1 -0
  385. package/dist/worker-settings-env.js +47 -0
  386. package/dist/worker-settings-env.js.map +1 -0
  387. package/dist/workflow-interface-service.d.ts +7 -0
  388. package/dist/workflow-interface-service.d.ts.map +1 -1
  389. 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 sharp from "sharp";
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 as gameDocument, installExampleGameInput } 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 { 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
- async function persistGameAsset(userId, game, workspace, draftUpdatedAt, slot, binding, bytes, extension, contentType) {
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 saved = await Game.updateDraft(userId, game.id, draftUpdatedAt, [op], workspace, { actor: "user" });
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, "Game draft was modified concurrently");
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: protectedProcedure
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: protectedProcedure
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: protectedProcedure
507
+ examples: gameProcedure
302
508
  .output(z.array(exampleGameSummary))
303
509
  .query(({ ctx }) => listExampleGames(ctx.apiOptions)),
304
- example: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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
- previewAuthoring: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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: protectedProcedure
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
- candidateWorkspaceId: z.string().optional(),
554
- binding: anyGameAssetBinding
555
- }))
556
- .output(gameWithDocument)
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 candidateRow = input.candidateWorkspaceId
561
- ? await Workspace.find(ctx.userId, input.candidateWorkspaceId)
562
- : null;
563
- if (input.candidateWorkspaceId && (!candidateRow || candidateRow.project_id !== game.project_id)) {
564
- throwApiError(ApiErrorCode.NOT_FOUND, "Candidate workspace not found");
565
- }
566
- const candidateWorkspace = candidateRow ? workspaceFromRow(candidateRow) : workspace;
567
- if (!candidateWorkspace) {
568
- throwApiError(ApiErrorCode.SERVICE_UNAVAILABLE, "Candidate workspace storage is unavailable");
569
- }
570
- const digest = input.binding.digest;
571
- if (!/^[a-f0-9]{64}$/.test(digest)) {
572
- throwApiError(ApiErrorCode.INVALID_INPUT, "Invalid candidate digest");
573
- }
574
- if (input.binding.mediaKind === "font" && !input.binding.fontFormat) {
575
- throwApiError(ApiErrorCode.INVALID_INPUT, "Font binding needs a TrueType or OpenType format");
576
- }
577
- const formats = input.binding.mediaKind === "model"
578
- ? [["glb", "model/gltf-binary"]]
579
- : input.binding.mediaKind === "collider"
580
- ? [["json", "application/json"]]
581
- : input.binding.mediaKind === "audio"
582
- ? [["wav", "audio/wav"], ["mp3", "audio/mpeg"], ["ogg", "audio/ogg"]]
583
- : input.binding.mediaKind === "font"
584
- ? [[input.binding.fontFormat, `font/${input.binding.fontFormat}`]]
585
- : [["png", "image/png"], ["jpg", "image/jpeg"], ["webp", "image/webp"]];
586
- let candidate = null;
587
- for (const [extension, contentType] of formats) {
588
- if (!extension || !contentType)
589
- continue;
590
- const path = `${game.source_root}/assets/${digest}.${extension}`;
591
- const bytes = await candidateWorkspace.read(path);
592
- if (bytes) {
593
- candidate = { extension, contentType, bytes };
594
- break;
595
- }
596
- }
597
- if (!candidate || createHash("sha256").update(candidate.bytes).digest("hex") !== digest) {
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
- const targetBinding = draft.document.schemaVersion === 3 ? gameAssetBinding3D.safeParse(binding) :
667
- gameAssetBinding.safeParse(binding);
668
- if (!targetBinding.success || draft.document.schemaVersion !== 3 && (binding.mediaKind === "model" || binding.mediaKind === "collider")) {
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 persistGameAsset(ctx.userId, game, workspace, draft.game.draft_updated_at, input.slot, targetBinding.data, bytes, extension, contentType);
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