@nodetool-ai/websocket 0.7.0-rc.22 → 0.8.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 (1020) hide show
  1. package/README.md +50 -0
  2. package/dist/.nodetool-build-stamp +1 -0
  3. package/dist/agent/run-node-tool.d.ts +39 -0
  4. package/dist/agent/run-node-tool.d.ts.map +1 -0
  5. package/dist/agent/run-node-tool.js +55 -0
  6. package/dist/agent/run-node-tool.js.map +1 -0
  7. package/dist/chat-prediction-forwarder.d.ts +13 -0
  8. package/dist/chat-prediction-forwarder.d.ts.map +1 -0
  9. package/dist/chat-prediction-forwarder.js +22 -0
  10. package/dist/chat-prediction-forwarder.js.map +1 -0
  11. package/dist/chat-tool-call-repair.d.ts +45 -0
  12. package/dist/chat-tool-call-repair.d.ts.map +1 -0
  13. package/dist/chat-tool-call-repair.js +100 -0
  14. package/dist/chat-tool-call-repair.js.map +1 -0
  15. package/dist/chat-turn-registry.d.ts +169 -0
  16. package/dist/chat-turn-registry.d.ts.map +1 -0
  17. package/dist/chat-turn-registry.js +326 -0
  18. package/dist/chat-turn-registry.js.map +1 -0
  19. package/dist/collection-api.d.ts +2 -2
  20. package/dist/collection-api.d.ts.map +1 -1
  21. package/dist/collection-api.js +44 -13
  22. package/dist/collection-api.js.map +1 -1
  23. package/dist/configured-providers.d.ts +52 -0
  24. package/dist/configured-providers.d.ts.map +1 -0
  25. package/dist/configured-providers.js +85 -0
  26. package/dist/configured-providers.js.map +1 -0
  27. package/dist/cors.d.ts +22 -0
  28. package/dist/cors.d.ts.map +1 -0
  29. package/dist/cors.js +91 -0
  30. package/dist/cors.js.map +1 -0
  31. package/dist/credit-gate.d.ts +36 -0
  32. package/dist/credit-gate.d.ts.map +1 -0
  33. package/dist/credit-gate.js +76 -0
  34. package/dist/credit-gate.js.map +1 -0
  35. package/dist/custom-providers.d.ts +40 -0
  36. package/dist/custom-providers.d.ts.map +1 -0
  37. package/dist/custom-providers.js +134 -0
  38. package/dist/custom-providers.js.map +1 -0
  39. package/dist/debug-sessions.d.ts +10 -0
  40. package/dist/debug-sessions.d.ts.map +1 -0
  41. package/dist/debug-sessions.js +9 -0
  42. package/dist/debug-sessions.js.map +1 -0
  43. package/dist/drain.d.ts +34 -0
  44. package/dist/drain.d.ts.map +1 -0
  45. package/dist/drain.js +68 -0
  46. package/dist/drain.js.map +1 -0
  47. package/dist/e2e-server.d.ts +30 -0
  48. package/dist/e2e-server.d.ts.map +1 -0
  49. package/dist/e2e-server.js +111 -0
  50. package/dist/e2e-server.js.map +1 -0
  51. package/dist/example-workflows.d.ts +20 -0
  52. package/dist/example-workflows.d.ts.map +1 -0
  53. package/dist/example-workflows.js +123 -0
  54. package/dist/example-workflows.js.map +1 -0
  55. package/dist/extension-cdp-bridge.d.ts +85 -0
  56. package/dist/extension-cdp-bridge.d.ts.map +1 -0
  57. package/dist/extension-cdp-bridge.js +163 -0
  58. package/dist/extension-cdp-bridge.js.map +1 -0
  59. package/dist/external-mcp.d.ts +58 -0
  60. package/dist/external-mcp.d.ts.map +1 -0
  61. package/dist/external-mcp.js +288 -0
  62. package/dist/external-mcp.js.map +1 -0
  63. package/dist/fake-runtime.d.ts +114 -0
  64. package/dist/fake-runtime.d.ts.map +1 -0
  65. package/dist/fake-runtime.js +270 -0
  66. package/dist/fake-runtime.js.map +1 -0
  67. package/dist/file-api.d.ts +4 -7
  68. package/dist/file-api.d.ts.map +1 -1
  69. package/dist/file-api.js +122 -39
  70. package/dist/file-api.js.map +1 -1
  71. package/dist/frontend-renderer-registry.d.ts +75 -0
  72. package/dist/frontend-renderer-registry.d.ts.map +1 -0
  73. package/dist/frontend-renderer-registry.js +111 -0
  74. package/dist/frontend-renderer-registry.js.map +1 -0
  75. package/dist/generation-recovery.d.ts +13 -0
  76. package/dist/generation-recovery.d.ts.map +1 -0
  77. package/dist/generation-recovery.js +197 -0
  78. package/dist/generation-recovery.js.map +1 -0
  79. package/dist/headless-job-runner.d.ts +71 -0
  80. package/dist/headless-job-runner.d.ts.map +1 -0
  81. package/dist/headless-job-runner.js +198 -0
  82. package/dist/headless-job-runner.js.map +1 -0
  83. package/dist/http-api.d.ts +157 -24
  84. package/dist/http-api.d.ts.map +1 -1
  85. package/dist/http-api.js +613 -646
  86. package/dist/http-api.js.map +1 -1
  87. package/dist/http-body-schemas.d.ts +109 -0
  88. package/dist/http-body-schemas.d.ts.map +1 -0
  89. package/dist/http-body-schemas.js +146 -0
  90. package/dist/http-body-schemas.js.map +1 -0
  91. package/dist/index.d.ts +11 -3
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +11 -3
  94. package/dist/index.js.map +1 -1
  95. package/dist/job-control.d.ts +57 -0
  96. package/dist/job-control.d.ts.map +1 -0
  97. package/dist/job-control.js +134 -0
  98. package/dist/job-control.js.map +1 -0
  99. package/dist/job-queue.d.ts +47 -0
  100. package/dist/job-queue.d.ts.map +1 -0
  101. package/dist/job-queue.js +52 -0
  102. package/dist/job-queue.js.map +1 -0
  103. package/dist/job-run-registry.d.ts +176 -0
  104. package/dist/job-run-registry.d.ts.map +1 -0
  105. package/dist/job-run-registry.js +312 -0
  106. package/dist/job-run-registry.js.map +1 -0
  107. package/dist/lib/app-build-service.d.ts +16 -0
  108. package/dist/lib/app-build-service.d.ts.map +1 -0
  109. package/dist/lib/app-build-service.js +39 -0
  110. package/dist/lib/app-build-service.js.map +1 -0
  111. package/dist/lib/app-debug-service.d.ts +16 -0
  112. package/dist/lib/app-debug-service.d.ts.map +1 -0
  113. package/dist/lib/app-debug-service.js +48 -0
  114. package/dist/lib/app-debug-service.js.map +1 -0
  115. package/dist/lib/app-deployment-service.d.ts +82 -0
  116. package/dist/lib/app-deployment-service.d.ts.map +1 -0
  117. package/dist/lib/app-deployment-service.js +282 -0
  118. package/dist/lib/app-deployment-service.js.map +1 -0
  119. package/dist/lib/app-run-server.d.ts +7 -0
  120. package/dist/lib/app-run-server.d.ts.map +1 -0
  121. package/dist/lib/app-run-server.js +6 -0
  122. package/dist/lib/app-run-server.js.map +1 -0
  123. package/dist/lib/app-session-scope.d.ts +91 -0
  124. package/dist/lib/app-session-scope.d.ts.map +1 -0
  125. package/dist/lib/app-session-scope.js +91 -0
  126. package/dist/lib/app-session-scope.js.map +1 -0
  127. package/dist/lib/applications-service.d.ts +173 -0
  128. package/dist/lib/applications-service.d.ts.map +1 -0
  129. package/dist/lib/applications-service.js +491 -0
  130. package/dist/lib/applications-service.js.map +1 -0
  131. package/dist/lib/asset-duration.d.ts +7 -0
  132. package/dist/lib/asset-duration.d.ts.map +1 -0
  133. package/dist/lib/asset-duration.js +24 -0
  134. package/dist/lib/asset-duration.js.map +1 -0
  135. package/dist/lib/asset-export.d.ts +34 -0
  136. package/dist/lib/asset-export.d.ts.map +1 -0
  137. package/dist/lib/asset-export.js +113 -0
  138. package/dist/lib/asset-export.js.map +1 -0
  139. package/dist/lib/asset-model-interface.d.ts +41 -0
  140. package/dist/lib/asset-model-interface.d.ts.map +1 -0
  141. package/dist/lib/asset-model-interface.js +74 -0
  142. package/dist/lib/asset-model-interface.js.map +1 -0
  143. package/dist/lib/asset-paths.d.ts +52 -0
  144. package/dist/lib/asset-paths.d.ts.map +1 -1
  145. package/dist/lib/asset-paths.js +145 -1
  146. package/dist/lib/asset-paths.js.map +1 -1
  147. package/dist/lib/asset-response.d.ts +12 -0
  148. package/dist/lib/asset-response.d.ts.map +1 -0
  149. package/dist/lib/asset-response.js +88 -0
  150. package/dist/lib/asset-response.js.map +1 -0
  151. package/dist/lib/audio-peaks.d.ts +23 -0
  152. package/dist/lib/audio-peaks.d.ts.map +1 -0
  153. package/dist/lib/audio-peaks.js +331 -0
  154. package/dist/lib/audio-peaks.js.map +1 -0
  155. package/dist/lib/bridge.d.ts +9 -1
  156. package/dist/lib/bridge.d.ts.map +1 -1
  157. package/dist/lib/bridge.js +27 -4
  158. package/dist/lib/bridge.js.map +1 -1
  159. package/dist/lib/compression.d.ts +3 -0
  160. package/dist/lib/compression.d.ts.map +1 -0
  161. package/dist/lib/compression.js +3 -0
  162. package/dist/lib/compression.js.map +1 -0
  163. package/dist/lib/document-model-interfaces.d.ts +30 -0
  164. package/dist/lib/document-model-interfaces.d.ts.map +1 -0
  165. package/dist/lib/document-model-interfaces.js +295 -0
  166. package/dist/lib/document-model-interfaces.js.map +1 -0
  167. package/dist/lib/env-config.d.ts +9 -0
  168. package/dist/lib/env-config.d.ts.map +1 -0
  169. package/dist/lib/env-config.js +18 -0
  170. package/dist/lib/env-config.js.map +1 -0
  171. package/dist/lib/example-apps.d.ts +43 -0
  172. package/dist/lib/example-apps.d.ts.map +1 -0
  173. package/dist/lib/example-apps.js +145 -0
  174. package/dist/lib/example-apps.js.map +1 -0
  175. package/dist/lib/example-games.d.ts +5 -0
  176. package/dist/lib/example-games.d.ts.map +1 -0
  177. package/dist/lib/example-games.js +13 -0
  178. package/dist/lib/example-games.js.map +1 -0
  179. package/dist/lib/example-recipes.d.ts +33 -0
  180. package/dist/lib/example-recipes.d.ts.map +1 -0
  181. package/dist/lib/example-recipes.js +213 -0
  182. package/dist/lib/example-recipes.js.map +1 -0
  183. package/dist/lib/example-storyboards.d.ts +40 -0
  184. package/dist/lib/example-storyboards.d.ts.map +1 -0
  185. package/dist/lib/example-storyboards.js +131 -0
  186. package/dist/lib/example-storyboards.js.map +1 -0
  187. package/dist/lib/example-thumbnail.d.ts +21 -0
  188. package/dist/lib/example-thumbnail.d.ts.map +1 -0
  189. package/dist/lib/example-thumbnail.js +50 -0
  190. package/dist/lib/example-thumbnail.js.map +1 -0
  191. package/dist/lib/example-timelines.d.ts +9 -0
  192. package/dist/lib/example-timelines.d.ts.map +1 -0
  193. package/dist/lib/example-timelines.js +25 -0
  194. package/dist/lib/example-timelines.js.map +1 -0
  195. package/dist/lib/extension-dist.d.ts +24 -0
  196. package/dist/lib/extension-dist.d.ts.map +1 -0
  197. package/dist/lib/extension-dist.js +67 -0
  198. package/dist/lib/extension-dist.js.map +1 -0
  199. package/dist/lib/external-asset-lookup.d.ts +30 -0
  200. package/dist/lib/external-asset-lookup.d.ts.map +1 -0
  201. package/dist/lib/external-asset-lookup.js +99 -0
  202. package/dist/lib/external-asset-lookup.js.map +1 -0
  203. package/dist/lib/external-assets.d.ts +13 -0
  204. package/dist/lib/external-assets.d.ts.map +1 -0
  205. package/dist/lib/external-assets.js +46 -0
  206. package/dist/lib/external-assets.js.map +1 -0
  207. package/dist/lib/fly-replay.d.ts +34 -0
  208. package/dist/lib/fly-replay.d.ts.map +1 -0
  209. package/dist/lib/fly-replay.js +111 -0
  210. package/dist/lib/fly-replay.js.map +1 -0
  211. package/dist/lib/http-rate-limit.d.ts +32 -0
  212. package/dist/lib/http-rate-limit.d.ts.map +1 -0
  213. package/dist/lib/http-rate-limit.js +47 -0
  214. package/dist/lib/http-rate-limit.js.map +1 -0
  215. package/dist/lib/instance-id.d.ts +25 -0
  216. package/dist/lib/instance-id.d.ts.map +1 -0
  217. package/dist/lib/instance-id.js +44 -0
  218. package/dist/lib/instance-id.js.map +1 -0
  219. package/dist/lib/link-codes.d.ts +82 -0
  220. package/dist/lib/link-codes.d.ts.map +1 -0
  221. package/dist/lib/link-codes.js +100 -0
  222. package/dist/lib/link-codes.js.map +1 -0
  223. package/dist/lib/local-asset-upload.d.ts +5 -0
  224. package/dist/lib/local-asset-upload.d.ts.map +1 -0
  225. package/dist/lib/local-asset-upload.js +69 -0
  226. package/dist/lib/local-asset-upload.js.map +1 -0
  227. package/dist/lib/local-file-access.d.ts +31 -0
  228. package/dist/lib/local-file-access.d.ts.map +1 -0
  229. package/dist/lib/local-file-access.js +169 -0
  230. package/dist/lib/local-file-access.js.map +1 -0
  231. package/dist/lib/localhost-trust.d.ts +59 -0
  232. package/dist/lib/localhost-trust.d.ts.map +1 -0
  233. package/dist/lib/localhost-trust.js +139 -0
  234. package/dist/lib/localhost-trust.js.map +1 -0
  235. package/dist/lib/mcp-mount.d.ts +25 -0
  236. package/dist/lib/mcp-mount.d.ts.map +1 -0
  237. package/dist/lib/mcp-mount.js +32 -0
  238. package/dist/lib/mcp-mount.js.map +1 -0
  239. package/dist/lib/media-size.d.ts +21 -0
  240. package/dist/lib/media-size.d.ts.map +1 -0
  241. package/dist/lib/media-size.js +48 -0
  242. package/dist/lib/media-size.js.map +1 -0
  243. package/dist/lib/media.d.ts +38 -0
  244. package/dist/lib/media.d.ts.map +1 -0
  245. package/dist/lib/media.js +146 -0
  246. package/dist/lib/media.js.map +1 -0
  247. package/dist/lib/package-asset-export.d.ts +83 -0
  248. package/dist/lib/package-asset-export.d.ts.map +1 -0
  249. package/dist/lib/package-asset-export.js +212 -0
  250. package/dist/lib/package-asset-export.js.map +1 -0
  251. package/dist/lib/package-assets.d.ts +41 -0
  252. package/dist/lib/package-assets.d.ts.map +1 -0
  253. package/dist/lib/package-assets.js +155 -0
  254. package/dist/lib/package-assets.js.map +1 -0
  255. package/dist/lib/project-delete.d.ts +11 -0
  256. package/dist/lib/project-delete.d.ts.map +1 -0
  257. package/dist/lib/project-delete.js +67 -0
  258. package/dist/lib/project-delete.js.map +1 -0
  259. package/dist/lib/project-document-copy.d.ts +31 -0
  260. package/dist/lib/project-document-copy.d.ts.map +1 -0
  261. package/dist/lib/project-document-copy.js +473 -0
  262. package/dist/lib/project-document-copy.js.map +1 -0
  263. package/dist/lib/public-app-assets.d.ts +8 -0
  264. package/dist/lib/public-app-assets.d.ts.map +1 -0
  265. package/dist/lib/public-app-assets.js +77 -0
  266. package/dist/lib/public-app-assets.js.map +1 -0
  267. package/dist/lib/public-routes.d.ts +95 -0
  268. package/dist/lib/public-routes.d.ts.map +1 -0
  269. package/dist/lib/public-routes.js +154 -0
  270. package/dist/lib/public-routes.js.map +1 -0
  271. package/dist/lib/runtime-detection.d.ts +17 -0
  272. package/dist/lib/runtime-detection.d.ts.map +1 -0
  273. package/dist/lib/runtime-detection.js +90 -0
  274. package/dist/lib/runtime-detection.js.map +1 -0
  275. package/dist/lib/storage-access.d.ts +21 -0
  276. package/dist/lib/storage-access.d.ts.map +1 -0
  277. package/dist/lib/storage-access.js +74 -0
  278. package/dist/lib/storage-access.js.map +1 -0
  279. package/dist/lib/storage.d.ts +9 -0
  280. package/dist/lib/storage.d.ts.map +1 -0
  281. package/dist/lib/storage.js +28 -0
  282. package/dist/lib/storage.js.map +1 -0
  283. package/dist/lib/storyboard-export.d.ts +64 -0
  284. package/dist/lib/storyboard-export.d.ts.map +1 -0
  285. package/dist/lib/storyboard-export.js +183 -0
  286. package/dist/lib/storyboard-export.js.map +1 -0
  287. package/dist/lib/style-presets.d.ts +34 -0
  288. package/dist/lib/style-presets.d.ts.map +1 -0
  289. package/dist/lib/style-presets.js +62 -0
  290. package/dist/lib/style-presets.js.map +1 -0
  291. package/dist/lib/temp-url-resolver.d.ts +35 -0
  292. package/dist/lib/temp-url-resolver.d.ts.map +1 -0
  293. package/dist/lib/temp-url-resolver.js +88 -0
  294. package/dist/lib/temp-url-resolver.js.map +1 -0
  295. package/dist/lib/thumbnail.d.ts +74 -0
  296. package/dist/lib/thumbnail.d.ts.map +1 -0
  297. package/dist/lib/thumbnail.js +302 -0
  298. package/dist/lib/thumbnail.js.map +1 -0
  299. package/dist/lib/timeline-bundle.d.ts +1689 -0
  300. package/dist/lib/timeline-bundle.d.ts.map +1 -0
  301. package/dist/lib/timeline-bundle.js +330 -0
  302. package/dist/lib/timeline-bundle.js.map +1 -0
  303. package/dist/lib/video-proxy.d.ts +114 -0
  304. package/dist/lib/video-proxy.d.ts.map +1 -0
  305. package/dist/lib/video-proxy.js +545 -0
  306. package/dist/lib/video-proxy.js.map +1 -0
  307. package/dist/lib/wire-values.d.ts +31 -0
  308. package/dist/lib/wire-values.d.ts.map +1 -0
  309. package/dist/lib/wire-values.js +49 -0
  310. package/dist/lib/wire-values.js.map +1 -0
  311. package/dist/lib/workflow-bundle.d.ts +102 -0
  312. package/dist/lib/workflow-bundle.d.ts.map +1 -0
  313. package/dist/lib/workflow-bundle.js +306 -0
  314. package/dist/lib/workflow-bundle.js.map +1 -0
  315. package/dist/lib/workflow-service.d.ts +82 -0
  316. package/dist/lib/workflow-service.d.ts.map +1 -0
  317. package/dist/lib/workflow-service.js +194 -0
  318. package/dist/lib/workflow-service.js.map +1 -0
  319. package/dist/lib/workflow-workspace.d.ts +20 -0
  320. package/dist/lib/workflow-workspace.d.ts.map +1 -0
  321. package/dist/lib/workflow-workspace.js +65 -0
  322. package/dist/lib/workflow-workspace.js.map +1 -0
  323. package/dist/lib/ws-connection-health.d.ts +10 -0
  324. package/dist/lib/ws-connection-health.d.ts.map +1 -0
  325. package/dist/lib/ws-connection-health.js +38 -0
  326. package/dist/lib/ws-connection-health.js.map +1 -0
  327. package/dist/lib/ws-rate-limit.d.ts +22 -0
  328. package/dist/lib/ws-rate-limit.d.ts.map +1 -0
  329. package/dist/lib/ws-rate-limit.js +45 -0
  330. package/dist/lib/ws-rate-limit.js.map +1 -0
  331. package/dist/lib/ws-upgrade.d.ts +40 -0
  332. package/dist/lib/ws-upgrade.d.ts.map +1 -0
  333. package/dist/lib/ws-upgrade.js +66 -0
  334. package/dist/lib/ws-upgrade.js.map +1 -0
  335. package/dist/lib/zip-stream.d.ts +46 -0
  336. package/dist/lib/zip-stream.d.ts.map +1 -0
  337. package/dist/lib/zip-stream.js +140 -0
  338. package/dist/lib/zip-stream.js.map +1 -0
  339. package/dist/mcp-agent-tools.d.ts +56 -0
  340. package/dist/mcp-agent-tools.d.ts.map +1 -0
  341. package/dist/mcp-agent-tools.js +1216 -0
  342. package/dist/mcp-agent-tools.js.map +1 -0
  343. package/dist/mcp-server.d.ts +56 -7
  344. package/dist/mcp-server.d.ts.map +1 -1
  345. package/dist/mcp-server.js +255 -762
  346. package/dist/mcp-server.js.map +1 -1
  347. package/dist/mcp-tool-deps.d.ts +20 -0
  348. package/dist/mcp-tool-deps.d.ts.map +1 -0
  349. package/dist/mcp-tool-deps.js +61 -0
  350. package/dist/mcp-tool-deps.js.map +1 -0
  351. package/dist/messagepack.d.ts +7 -0
  352. package/dist/messagepack.d.ts.map +1 -0
  353. package/dist/messagepack.js +19 -0
  354. package/dist/messagepack.js.map +1 -0
  355. package/dist/model-download-runtime.d.ts +9 -0
  356. package/dist/model-download-runtime.d.ts.map +1 -0
  357. package/dist/model-download-runtime.js +77 -0
  358. package/dist/model-download-runtime.js.map +1 -0
  359. package/dist/model-execution-availability.d.ts +14 -0
  360. package/dist/model-execution-availability.d.ts.map +1 -0
  361. package/dist/model-execution-availability.js +161 -0
  362. package/dist/model-execution-availability.js.map +1 -0
  363. package/dist/models-api.d.ts +51 -5
  364. package/dist/models-api.d.ts.map +1 -1
  365. package/dist/models-api.js +160 -690
  366. package/dist/models-api.js.map +1 -1
  367. package/dist/node-registry-setup.d.ts +84 -0
  368. package/dist/node-registry-setup.d.ts.map +1 -0
  369. package/dist/node-registry-setup.js +256 -0
  370. package/dist/node-registry-setup.js.map +1 -0
  371. package/dist/oauth/cimd.d.ts +28 -0
  372. package/dist/oauth/cimd.d.ts.map +1 -0
  373. package/dist/oauth/cimd.js +177 -0
  374. package/dist/oauth/cimd.js.map +1 -0
  375. package/dist/oauth/gate.d.ts +38 -0
  376. package/dist/oauth/gate.d.ts.map +1 -0
  377. package/dist/oauth/gate.js +77 -0
  378. package/dist/oauth/gate.js.map +1 -0
  379. package/dist/oauth/metadata.d.ts +35 -0
  380. package/dist/oauth/metadata.d.ts.map +1 -0
  381. package/dist/oauth/metadata.js +56 -0
  382. package/dist/oauth/metadata.js.map +1 -0
  383. package/dist/oauth/pending-store.d.ts +79 -0
  384. package/dist/oauth/pending-store.d.ts.map +1 -0
  385. package/dist/oauth/pending-store.js +133 -0
  386. package/dist/oauth/pending-store.js.map +1 -0
  387. package/dist/oauth/pkce.d.ts +16 -0
  388. package/dist/oauth/pkce.d.ts.map +1 -0
  389. package/dist/oauth/pkce.js +29 -0
  390. package/dist/oauth/pkce.js.map +1 -0
  391. package/dist/oauth/validate.d.ts +25 -0
  392. package/dist/oauth/validate.d.ts.map +1 -0
  393. package/dist/oauth/validate.js +62 -0
  394. package/dist/oauth/validate.js.map +1 -0
  395. package/dist/oauth/www-authenticate.d.ts +9 -0
  396. package/dist/oauth/www-authenticate.d.ts.map +1 -0
  397. package/dist/oauth/www-authenticate.js +33 -0
  398. package/dist/oauth/www-authenticate.js.map +1 -0
  399. package/dist/oauth-api.d.ts +19 -0
  400. package/dist/oauth-api.d.ts.map +1 -1
  401. package/dist/oauth-api.js +531 -12
  402. package/dist/oauth-api.js.map +1 -1
  403. package/dist/openai-api.d.ts.map +1 -1
  404. package/dist/openai-api.js +4 -9
  405. package/dist/openai-api.js.map +1 -1
  406. package/dist/pack-snapshot.d.ts +24 -0
  407. package/dist/pack-snapshot.d.ts.map +1 -0
  408. package/dist/pack-snapshot.js +35 -0
  409. package/dist/pack-snapshot.js.map +1 -0
  410. package/dist/plugins/websocket.d.ts +21 -4
  411. package/dist/plugins/websocket.d.ts.map +1 -1
  412. package/dist/plugins/websocket.js +136 -290
  413. package/dist/plugins/websocket.js.map +1 -1
  414. package/dist/resolve-media-urls.d.ts +17 -3
  415. package/dist/resolve-media-urls.d.ts.map +1 -1
  416. package/dist/resolve-media-urls.js +117 -31
  417. package/dist/resolve-media-urls.js.map +1 -1
  418. package/dist/resource-events.d.ts +28 -0
  419. package/dist/resource-events.d.ts.map +1 -0
  420. package/dist/resource-events.js +20 -0
  421. package/dist/resource-events.js.map +1 -0
  422. package/dist/routes/account.d.ts +100 -0
  423. package/dist/routes/account.d.ts.map +1 -0
  424. package/dist/routes/account.js +197 -0
  425. package/dist/routes/account.js.map +1 -0
  426. package/dist/routes/applications.d.ts +36 -0
  427. package/dist/routes/applications.d.ts.map +1 -0
  428. package/dist/routes/applications.js +183 -0
  429. package/dist/routes/applications.js.map +1 -0
  430. package/dist/routes/assets.d.ts +3 -3
  431. package/dist/routes/assets.d.ts.map +1 -1
  432. package/dist/routes/assets.js +78 -44
  433. package/dist/routes/assets.js.map +1 -1
  434. package/dist/routes/atlascloud-webhook.d.ts +93 -0
  435. package/dist/routes/atlascloud-webhook.d.ts.map +1 -0
  436. package/dist/routes/atlascloud-webhook.js +369 -0
  437. package/dist/routes/atlascloud-webhook.js.map +1 -0
  438. package/dist/routes/collections.d.ts.map +1 -1
  439. package/dist/routes/config.d.ts +26 -0
  440. package/dist/routes/config.d.ts.map +1 -0
  441. package/dist/routes/config.js +64 -0
  442. package/dist/routes/config.js.map +1 -0
  443. package/dist/routes/documents.d.ts +27 -0
  444. package/dist/routes/documents.d.ts.map +1 -0
  445. package/dist/routes/documents.js +155 -0
  446. package/dist/routes/documents.js.map +1 -0
  447. package/dist/routes/fal-credits.d.ts +4 -0
  448. package/dist/routes/fal-credits.d.ts.map +1 -0
  449. package/dist/routes/fal-credits.js +101 -0
  450. package/dist/routes/fal-credits.js.map +1 -0
  451. package/dist/routes/fal-pricing-estimate.d.ts +4 -0
  452. package/dist/routes/fal-pricing-estimate.d.ts.map +1 -0
  453. package/dist/routes/fal-pricing-estimate.js +114 -0
  454. package/dist/routes/fal-pricing-estimate.js.map +1 -0
  455. package/dist/routes/fal-pricing.d.ts +4 -0
  456. package/dist/routes/fal-pricing.d.ts.map +1 -0
  457. package/dist/routes/fal-pricing.js +93 -0
  458. package/dist/routes/fal-pricing.js.map +1 -0
  459. package/dist/routes/fal-webhook.d.ts +102 -0
  460. package/dist/routes/fal-webhook.d.ts.map +1 -0
  461. package/dist/routes/fal-webhook.js +379 -0
  462. package/dist/routes/fal-webhook.js.map +1 -0
  463. package/dist/routes/files.d.ts.map +1 -1
  464. package/dist/routes/files.js +4 -3
  465. package/dist/routes/files.js.map +1 -1
  466. package/dist/routes/health.d.ts +1 -0
  467. package/dist/routes/health.d.ts.map +1 -1
  468. package/dist/routes/health.js +41 -7
  469. package/dist/routes/health.js.map +1 -1
  470. package/dist/routes/integrations.d.ts +39 -0
  471. package/dist/routes/integrations.d.ts.map +1 -0
  472. package/dist/routes/integrations.js +211 -0
  473. package/dist/routes/integrations.js.map +1 -0
  474. package/dist/routes/js-scripts.d.ts +26 -0
  475. package/dist/routes/js-scripts.d.ts.map +1 -0
  476. package/dist/routes/js-scripts.js +116 -0
  477. package/dist/routes/js-scripts.js.map +1 -0
  478. package/dist/routes/kie-credits.d.ts +4 -0
  479. package/dist/routes/kie-credits.d.ts.map +1 -0
  480. package/dist/routes/kie-credits.js +93 -0
  481. package/dist/routes/kie-credits.js.map +1 -0
  482. package/dist/routes/kie-pricing.d.ts +4 -0
  483. package/dist/routes/kie-pricing.d.ts.map +1 -0
  484. package/dist/routes/kie-pricing.js +45 -0
  485. package/dist/routes/kie-pricing.js.map +1 -0
  486. package/dist/routes/kie-webhook.d.ts +4 -0
  487. package/dist/routes/kie-webhook.d.ts.map +1 -0
  488. package/dist/routes/kie-webhook.js +70 -0
  489. package/dist/routes/kie-webhook.js.map +1 -0
  490. package/dist/routes/nodes.d.ts.map +1 -1
  491. package/dist/routes/nodes.js +6 -59
  492. package/dist/routes/nodes.js.map +1 -1
  493. package/dist/routes/oauth-as.d.ts +30 -0
  494. package/dist/routes/oauth-as.d.ts.map +1 -0
  495. package/dist/routes/oauth-as.js +412 -0
  496. package/dist/routes/oauth-as.js.map +1 -0
  497. package/dist/routes/oauth.d.ts.map +1 -1
  498. package/dist/routes/openai.d.ts.map +1 -1
  499. package/dist/routes/public-apps.d.ts +27 -0
  500. package/dist/routes/public-apps.d.ts.map +1 -0
  501. package/dist/routes/public-apps.js +99 -0
  502. package/dist/routes/public-apps.js.map +1 -0
  503. package/dist/routes/sandbox-modules.d.ts +17 -0
  504. package/dist/routes/sandbox-modules.d.ts.map +1 -0
  505. package/dist/routes/sandbox-modules.js +85 -0
  506. package/dist/routes/sandbox-modules.js.map +1 -0
  507. package/dist/routes/sdk-v1.d.ts +24 -0
  508. package/dist/routes/sdk-v1.d.ts.map +1 -0
  509. package/dist/routes/sdk-v1.js +320 -0
  510. package/dist/routes/sdk-v1.js.map +1 -0
  511. package/dist/routes/storage.d.ts.map +1 -1
  512. package/dist/routes/storyboards.d.ts +16 -0
  513. package/dist/routes/storyboards.d.ts.map +1 -0
  514. package/dist/routes/storyboards.js +97 -0
  515. package/dist/routes/storyboards.js.map +1 -0
  516. package/dist/routes/timeline-animations.d.ts +23 -0
  517. package/dist/routes/timeline-animations.d.ts.map +1 -0
  518. package/dist/routes/timeline-animations.js +99 -0
  519. package/dist/routes/timeline-animations.js.map +1 -0
  520. package/dist/routes/timeline-audio-bake.d.ts +51 -0
  521. package/dist/routes/timeline-audio-bake.d.ts.map +1 -0
  522. package/dist/routes/timeline-audio-bake.js +110 -0
  523. package/dist/routes/timeline-audio-bake.js.map +1 -0
  524. package/dist/routes/timeline-fonts.d.ts +29 -0
  525. package/dist/routes/timeline-fonts.d.ts.map +1 -0
  526. package/dist/routes/timeline-fonts.js +64 -0
  527. package/dist/routes/timeline-fonts.js.map +1 -0
  528. package/dist/routes/timeline-isolate-subject.d.ts +55 -0
  529. package/dist/routes/timeline-isolate-subject.d.ts.map +1 -0
  530. package/dist/routes/timeline-isolate-subject.js +128 -0
  531. package/dist/routes/timeline-isolate-subject.js.map +1 -0
  532. package/dist/routes/timeline-track-object.d.ts +39 -0
  533. package/dist/routes/timeline-track-object.d.ts.map +1 -0
  534. package/dist/routes/timeline-track-object.js +97 -0
  535. package/dist/routes/timeline-track-object.js.map +1 -0
  536. package/dist/routes/timelines.d.ts +18 -0
  537. package/dist/routes/timelines.d.ts.map +1 -0
  538. package/dist/routes/timelines.js +191 -0
  539. package/dist/routes/timelines.js.map +1 -0
  540. package/dist/routes/workflows.d.ts +6 -9
  541. package/dist/routes/workflows.d.ts.map +1 -1
  542. package/dist/routes/workflows.js +100 -11
  543. package/dist/routes/workflows.js.map +1 -1
  544. package/dist/routes/workspace.d.ts.map +1 -1
  545. package/dist/run-supervisor.d.ts +30 -0
  546. package/dist/run-supervisor.d.ts.map +1 -0
  547. package/dist/run-supervisor.js +86 -0
  548. package/dist/run-supervisor.js.map +1 -0
  549. package/dist/sandbox-catalog.d.ts +28 -0
  550. package/dist/sandbox-catalog.d.ts.map +1 -0
  551. package/dist/sandbox-catalog.js +74 -0
  552. package/dist/sandbox-catalog.js.map +1 -0
  553. package/dist/screenshot-projects.d.ts +27 -0
  554. package/dist/screenshot-projects.d.ts.map +1 -0
  555. package/dist/screenshot-projects.js +880 -0
  556. package/dist/screenshot-projects.js.map +1 -0
  557. package/dist/screenshot-server.js +947 -43
  558. package/dist/screenshot-server.js.map +1 -1
  559. package/dist/sdk/sdk-cached-model-inventory.d.ts +21 -0
  560. package/dist/sdk/sdk-cached-model-inventory.d.ts.map +1 -0
  561. package/dist/sdk/sdk-cached-model-inventory.js +51 -0
  562. package/dist/sdk/sdk-cached-model-inventory.js.map +1 -0
  563. package/dist/sdk/sdk-capabilities-service.d.ts +32 -0
  564. package/dist/sdk/sdk-capabilities-service.d.ts.map +1 -0
  565. package/dist/sdk/sdk-capabilities-service.js +47 -0
  566. package/dist/sdk/sdk-capabilities-service.js.map +1 -0
  567. package/dist/sdk/sdk-execution-readiness-probe.d.ts +19 -0
  568. package/dist/sdk/sdk-execution-readiness-probe.d.ts.map +1 -0
  569. package/dist/sdk/sdk-execution-readiness-probe.js +90 -0
  570. package/dist/sdk/sdk-execution-readiness-probe.js.map +1 -0
  571. package/dist/sdk/sdk-execution-target-readiness.d.ts +15 -0
  572. package/dist/sdk/sdk-execution-target-readiness.d.ts.map +1 -0
  573. package/dist/sdk/sdk-execution-target-readiness.js +50 -0
  574. package/dist/sdk/sdk-execution-target-readiness.js.map +1 -0
  575. package/dist/sdk/sdk-feature-flags.d.ts +18 -0
  576. package/dist/sdk/sdk-feature-flags.d.ts.map +1 -0
  577. package/dist/sdk/sdk-feature-flags.js +30 -0
  578. package/dist/sdk/sdk-feature-flags.js.map +1 -0
  579. package/dist/sdk/sdk-huggingface-download-state.d.ts +15 -0
  580. package/dist/sdk/sdk-huggingface-download-state.d.ts.map +1 -0
  581. package/dist/sdk/sdk-huggingface-download-state.js +25 -0
  582. package/dist/sdk/sdk-huggingface-download-state.js.map +1 -0
  583. package/dist/sdk/sdk-live-runner-registry.d.ts +21 -0
  584. package/dist/sdk/sdk-live-runner-registry.d.ts.map +1 -0
  585. package/dist/sdk/sdk-live-runner-registry.js +26 -0
  586. package/dist/sdk/sdk-live-runner-registry.js.map +1 -0
  587. package/dist/sdk/sdk-model-catalog-service.d.ts +20 -0
  588. package/dist/sdk/sdk-model-catalog-service.d.ts.map +1 -0
  589. package/dist/sdk/sdk-model-catalog-service.js +219 -0
  590. package/dist/sdk/sdk-model-catalog-service.js.map +1 -0
  591. package/dist/sdk/sdk-model-download-service.d.ts +39 -0
  592. package/dist/sdk/sdk-model-download-service.d.ts.map +1 -0
  593. package/dist/sdk/sdk-model-download-service.js +274 -0
  594. package/dist/sdk/sdk-model-download-service.js.map +1 -0
  595. package/dist/sdk/sdk-model-requirement-probe.d.ts +30 -0
  596. package/dist/sdk/sdk-model-requirement-probe.d.ts.map +1 -0
  597. package/dist/sdk/sdk-model-requirement-probe.js +120 -0
  598. package/dist/sdk/sdk-model-requirement-probe.js.map +1 -0
  599. package/dist/sdk/sdk-node-package-requirement-probe.d.ts +20 -0
  600. package/dist/sdk/sdk-node-package-requirement-probe.d.ts.map +1 -0
  601. package/dist/sdk/sdk-node-package-requirement-probe.js +44 -0
  602. package/dist/sdk/sdk-node-package-requirement-probe.js.map +1 -0
  603. package/dist/sdk/sdk-node-type-inventory-service.d.ts +11 -0
  604. package/dist/sdk/sdk-node-type-inventory-service.d.ts.map +1 -0
  605. package/dist/sdk/sdk-node-type-inventory-service.js +36 -0
  606. package/dist/sdk/sdk-node-type-inventory-service.js.map +1 -0
  607. package/dist/sdk/sdk-preflight-orchestrator.d.ts +54 -0
  608. package/dist/sdk/sdk-preflight-orchestrator.d.ts.map +1 -0
  609. package/dist/sdk/sdk-preflight-orchestrator.js +63 -0
  610. package/dist/sdk/sdk-preflight-orchestrator.js.map +1 -0
  611. package/dist/sdk/sdk-preflight-requirement-resolver.d.ts +35 -0
  612. package/dist/sdk/sdk-preflight-requirement-resolver.d.ts.map +1 -0
  613. package/dist/sdk/sdk-preflight-requirement-resolver.js +67 -0
  614. package/dist/sdk/sdk-preflight-requirement-resolver.js.map +1 -0
  615. package/dist/sdk/sdk-preflight-service.d.ts +61 -0
  616. package/dist/sdk/sdk-preflight-service.d.ts.map +1 -0
  617. package/dist/sdk/sdk-preflight-service.js +104 -0
  618. package/dist/sdk/sdk-preflight-service.js.map +1 -0
  619. package/dist/sdk/sdk-preflight-workflow-source.d.ts +18 -0
  620. package/dist/sdk/sdk-preflight-workflow-source.d.ts.map +1 -0
  621. package/dist/sdk/sdk-preflight-workflow-source.js +42 -0
  622. package/dist/sdk/sdk-preflight-workflow-source.js.map +1 -0
  623. package/dist/sdk/sdk-route-policy.d.ts +3 -0
  624. package/dist/sdk/sdk-route-policy.d.ts.map +1 -0
  625. package/dist/sdk/sdk-route-policy.js +18 -0
  626. package/dist/sdk/sdk-route-policy.js.map +1 -0
  627. package/dist/sdk/sdk-runtime-capabilities-service.d.ts +60 -0
  628. package/dist/sdk/sdk-runtime-capabilities-service.d.ts.map +1 -0
  629. package/dist/sdk/sdk-runtime-capabilities-service.js +30 -0
  630. package/dist/sdk/sdk-runtime-capabilities-service.js.map +1 -0
  631. package/dist/sdk/sdk-runtime-requirement-probe.d.ts +13 -0
  632. package/dist/sdk/sdk-runtime-requirement-probe.d.ts.map +1 -0
  633. package/dist/sdk/sdk-runtime-requirement-probe.js +55 -0
  634. package/dist/sdk/sdk-runtime-requirement-probe.js.map +1 -0
  635. package/dist/sdk/sdk-static-preflight-service.d.ts +66 -0
  636. package/dist/sdk/sdk-static-preflight-service.d.ts.map +1 -0
  637. package/dist/sdk/sdk-static-preflight-service.js +492 -0
  638. package/dist/sdk/sdk-static-preflight-service.js.map +1 -0
  639. package/dist/sdk/sdk-temporary-asset-service.d.ts +18 -0
  640. package/dist/sdk/sdk-temporary-asset-service.d.ts.map +1 -0
  641. package/dist/sdk/sdk-temporary-asset-service.js +33 -0
  642. package/dist/sdk/sdk-temporary-asset-service.js.map +1 -0
  643. package/dist/sdk/sdk-temporary-asset-upload-http-handler.d.ts +9 -0
  644. package/dist/sdk/sdk-temporary-asset-upload-http-handler.d.ts.map +1 -0
  645. package/dist/sdk/sdk-temporary-asset-upload-http-handler.js +65 -0
  646. package/dist/sdk/sdk-temporary-asset-upload-http-handler.js.map +1 -0
  647. package/dist/sdk/sdk-v1-handler-map.d.ts +44 -0
  648. package/dist/sdk/sdk-v1-handler-map.d.ts.map +1 -0
  649. package/dist/sdk/sdk-v1-handler-map.js +37 -0
  650. package/dist/sdk/sdk-v1-handler-map.js.map +1 -0
  651. package/dist/sdk/sdk-v1-service-error.d.ts +21 -0
  652. package/dist/sdk/sdk-v1-service-error.d.ts.map +1 -0
  653. package/dist/sdk/sdk-v1-service-error.js +62 -0
  654. package/dist/sdk/sdk-v1-service-error.js.map +1 -0
  655. package/dist/sdk/sdk-v1-service.d.ts +74 -0
  656. package/dist/sdk/sdk-v1-service.d.ts.map +1 -0
  657. package/dist/sdk/sdk-v1-service.js +219 -0
  658. package/dist/sdk/sdk-v1-service.js.map +1 -0
  659. package/dist/sdk/sdk-worker-readiness-adapter.d.ts +22 -0
  660. package/dist/sdk/sdk-worker-readiness-adapter.d.ts.map +1 -0
  661. package/dist/sdk/sdk-worker-readiness-adapter.js +29 -0
  662. package/dist/sdk/sdk-worker-readiness-adapter.js.map +1 -0
  663. package/dist/server.js +1019 -201
  664. package/dist/server.js.map +1 -1
  665. package/dist/session/asset-autosave.d.ts +80 -0
  666. package/dist/session/asset-autosave.d.ts.map +1 -0
  667. package/dist/session/asset-autosave.js +456 -0
  668. package/dist/session/asset-autosave.js.map +1 -0
  669. package/dist/session/chat-compaction.d.ts +107 -0
  670. package/dist/session/chat-compaction.d.ts.map +1 -0
  671. package/dist/session/chat-compaction.js +191 -0
  672. package/dist/session/chat-compaction.js.map +1 -0
  673. package/dist/session/chat-history.d.ts +75 -0
  674. package/dist/session/chat-history.d.ts.map +1 -0
  675. package/dist/session/chat-history.js +194 -0
  676. package/dist/session/chat-history.js.map +1 -0
  677. package/dist/session/chat-prompt.d.ts +45 -0
  678. package/dist/session/chat-prompt.d.ts.map +1 -0
  679. package/dist/session/chat-prompt.js +480 -0
  680. package/dist/session/chat-prompt.js.map +1 -0
  681. package/dist/session/chat-turn.d.ts +336 -0
  682. package/dist/session/chat-turn.d.ts.map +1 -0
  683. package/dist/session/chat-turn.js +3516 -0
  684. package/dist/session/chat-turn.js.map +1 -0
  685. package/dist/session/client-session.d.ts +59 -0
  686. package/dist/session/client-session.d.ts.map +1 -0
  687. package/dist/session/client-session.js +2 -0
  688. package/dist/session/client-session.js.map +1 -0
  689. package/dist/session/commands.d.ts +133 -0
  690. package/dist/session/commands.d.ts.map +1 -0
  691. package/dist/session/commands.js +778 -0
  692. package/dist/session/commands.js.map +1 -0
  693. package/dist/session/inference.d.ts +213 -0
  694. package/dist/session/inference.d.ts.map +1 -0
  695. package/dist/session/inference.js +1072 -0
  696. package/dist/session/inference.js.map +1 -0
  697. package/dist/session/job-execution.d.ts +477 -0
  698. package/dist/session/job-execution.d.ts.map +1 -0
  699. package/dist/session/job-execution.js +2089 -0
  700. package/dist/session/job-execution.js.map +1 -0
  701. package/dist/session/media-generation.d.ts +94 -0
  702. package/dist/session/media-generation.d.ts.map +1 -0
  703. package/dist/session/media-generation.js +234 -0
  704. package/dist/session/media-generation.js.map +1 -0
  705. package/dist/session/model-interfaces.d.ts +27 -0
  706. package/dist/session/model-interfaces.d.ts.map +1 -0
  707. package/dist/session/model-interfaces.js +132 -0
  708. package/dist/session/model-interfaces.js.map +1 -0
  709. package/dist/session/sanitize.d.ts +7 -0
  710. package/dist/session/sanitize.d.ts.map +1 -0
  711. package/dist/session/sanitize.js +64 -0
  712. package/dist/session/sanitize.js.map +1 -0
  713. package/dist/settings-registry.d.ts +6 -15
  714. package/dist/settings-registry.d.ts.map +1 -1
  715. package/dist/settings-registry.js +6 -103
  716. package/dist/settings-registry.js.map +1 -1
  717. package/dist/storage-api.d.ts +20 -0
  718. package/dist/storage-api.d.ts.map +1 -1
  719. package/dist/storage-api.js +260 -77
  720. package/dist/storage-api.js.map +1 -1
  721. package/dist/storage-retention.d.ts +9 -0
  722. package/dist/storage-retention.d.ts.map +1 -0
  723. package/dist/storage-retention.js +74 -0
  724. package/dist/storage-retention.js.map +1 -0
  725. package/dist/system-stats.d.ts +52 -0
  726. package/dist/system-stats.d.ts.map +1 -0
  727. package/dist/system-stats.js +134 -0
  728. package/dist/system-stats.js.map +1 -0
  729. package/dist/test-ui-server.d.ts +46 -0
  730. package/dist/test-ui-server.d.ts.map +1 -1
  731. package/dist/test-ui-server.js +188 -41
  732. package/dist/test-ui-server.js.map +1 -1
  733. package/dist/triggers/boot.d.ts +73 -0
  734. package/dist/triggers/boot.d.ts.map +1 -0
  735. package/dist/triggers/boot.js +149 -0
  736. package/dist/triggers/boot.js.map +1 -0
  737. package/dist/triggers/dispatcher.d.ts +234 -0
  738. package/dist/triggers/dispatcher.d.ts.map +1 -0
  739. package/dist/triggers/dispatcher.js +525 -0
  740. package/dist/triggers/dispatcher.js.map +1 -0
  741. package/dist/triggers/file-watch.d.ts +90 -0
  742. package/dist/triggers/file-watch.d.ts.map +1 -0
  743. package/dist/triggers/file-watch.js +405 -0
  744. package/dist/triggers/file-watch.js.map +1 -0
  745. package/dist/triggers/registration-sync.d.ts +34 -0
  746. package/dist/triggers/registration-sync.d.ts.map +1 -0
  747. package/dist/triggers/registration-sync.js +162 -0
  748. package/dist/triggers/registration-sync.js.map +1 -0
  749. package/dist/triggers/schedule-timing.d.ts +52 -0
  750. package/dist/triggers/schedule-timing.d.ts.map +1 -0
  751. package/dist/triggers/schedule-timing.js +90 -0
  752. package/dist/triggers/schedule-timing.js.map +1 -0
  753. package/dist/triggers/scheduler.d.ts +55 -0
  754. package/dist/triggers/scheduler.d.ts.map +1 -0
  755. package/dist/triggers/scheduler.js +122 -0
  756. package/dist/triggers/scheduler.js.map +1 -0
  757. package/dist/triggers/settle.d.ts +66 -0
  758. package/dist/triggers/settle.d.ts.map +1 -0
  759. package/dist/triggers/settle.js +99 -0
  760. package/dist/triggers/settle.js.map +1 -0
  761. package/dist/triggers/stores.d.ts +38 -0
  762. package/dist/triggers/stores.d.ts.map +1 -0
  763. package/dist/triggers/stores.js +168 -0
  764. package/dist/triggers/stores.js.map +1 -0
  765. package/dist/triggers/webhook-route.d.ts +49 -0
  766. package/dist/triggers/webhook-route.d.ts.map +1 -0
  767. package/dist/triggers/webhook-route.js +148 -0
  768. package/dist/triggers/webhook-route.js.map +1 -0
  769. package/dist/trpc/context.d.ts +52 -5
  770. package/dist/trpc/context.d.ts.map +1 -1
  771. package/dist/trpc/context.js +4 -1
  772. package/dist/trpc/context.js.map +1 -1
  773. package/dist/trpc/error-formatter.d.ts.map +1 -1
  774. package/dist/trpc/error-formatter.js +23 -2
  775. package/dist/trpc/error-formatter.js.map +1 -1
  776. package/dist/trpc/error-logging.d.ts +14 -0
  777. package/dist/trpc/error-logging.d.ts.map +1 -0
  778. package/dist/trpc/error-logging.js +27 -0
  779. package/dist/trpc/error-logging.js.map +1 -0
  780. package/dist/trpc/index.d.ts +2 -2
  781. package/dist/trpc/index.d.ts.map +1 -1
  782. package/dist/trpc/index.js +0 -2
  783. package/dist/trpc/index.js.map +1 -1
  784. package/dist/trpc/middleware.d.ts +6 -3
  785. package/dist/trpc/middleware.d.ts.map +1 -1
  786. package/dist/trpc/router.d.ts +29791 -1303
  787. package/dist/trpc/router.d.ts.map +1 -1
  788. package/dist/trpc/router.js +46 -3
  789. package/dist/trpc/router.js.map +1 -1
  790. package/dist/trpc/routers/agent-access.d.ts +149 -0
  791. package/dist/trpc/routers/agent-access.d.ts.map +1 -0
  792. package/dist/trpc/routers/agent-access.js +224 -0
  793. package/dist/trpc/routers/agent-access.js.map +1 -0
  794. package/dist/trpc/routers/applications.d.ts +1117 -0
  795. package/dist/trpc/routers/applications.d.ts.map +1 -0
  796. package/dist/trpc/routers/applications.js +218 -0
  797. package/dist/trpc/routers/applications.js.map +1 -0
  798. package/dist/trpc/routers/assets.d.ts +184 -42
  799. package/dist/trpc/routers/assets.d.ts.map +1 -1
  800. package/dist/trpc/routers/assets.js +499 -105
  801. package/dist/trpc/routers/assets.js.map +1 -1
  802. package/dist/trpc/routers/code-gen.d.ts +99 -0
  803. package/dist/trpc/routers/code-gen.d.ts.map +1 -0
  804. package/dist/trpc/routers/code-gen.js +164 -0
  805. package/dist/trpc/routers/code-gen.js.map +1 -0
  806. package/dist/trpc/routers/collections.d.ts +1 -26
  807. package/dist/trpc/routers/collections.d.ts.map +1 -1
  808. package/dist/trpc/routers/collections.js +145 -78
  809. package/dist/trpc/routers/collections.js.map +1 -1
  810. package/dist/trpc/routers/costs.d.ts +40 -96
  811. package/dist/trpc/routers/costs.d.ts.map +1 -1
  812. package/dist/trpc/routers/costs.js +14 -89
  813. package/dist/trpc/routers/costs.js.map +1 -1
  814. package/dist/trpc/routers/credits.d.ts +110 -0
  815. package/dist/trpc/routers/credits.d.ts.map +1 -0
  816. package/dist/trpc/routers/credits.js +69 -0
  817. package/dist/trpc/routers/credits.js.map +1 -0
  818. package/dist/trpc/routers/custom-providers.d.ts +88 -0
  819. package/dist/trpc/routers/custom-providers.d.ts.map +1 -0
  820. package/dist/trpc/routers/custom-providers.js +140 -0
  821. package/dist/trpc/routers/custom-providers.js.map +1 -0
  822. package/dist/trpc/routers/documents.d.ts +61 -0
  823. package/dist/trpc/routers/documents.d.ts.map +1 -0
  824. package/dist/trpc/routers/documents.js +23 -0
  825. package/dist/trpc/routers/documents.js.map +1 -0
  826. package/dist/trpc/routers/extension.d.ts +24 -0
  827. package/dist/trpc/routers/extension.d.ts.map +1 -0
  828. package/dist/trpc/routers/extension.js +28 -0
  829. package/dist/trpc/routers/extension.js.map +1 -0
  830. package/dist/trpc/routers/external-mcp.d.ts +108 -0
  831. package/dist/trpc/routers/external-mcp.d.ts.map +1 -0
  832. package/dist/trpc/routers/external-mcp.js +70 -0
  833. package/dist/trpc/routers/external-mcp.js.map +1 -0
  834. package/dist/trpc/routers/files.d.ts +9 -2
  835. package/dist/trpc/routers/files.d.ts.map +1 -1
  836. package/dist/trpc/routers/files.js +67 -37
  837. package/dist/trpc/routers/files.js.map +1 -1
  838. package/dist/trpc/routers/fonts.d.ts +30 -0
  839. package/dist/trpc/routers/fonts.d.ts.map +1 -0
  840. package/dist/trpc/routers/fonts.js +112 -0
  841. package/dist/trpc/routers/fonts.js.map +1 -0
  842. package/dist/trpc/routers/games.d.ts +7610 -0
  843. package/dist/trpc/routers/games.d.ts.map +1 -0
  844. package/dist/trpc/routers/games.js +456 -0
  845. package/dist/trpc/routers/games.js.map +1 -0
  846. package/dist/trpc/routers/integrations.d.ts +86 -0
  847. package/dist/trpc/routers/integrations.d.ts.map +1 -0
  848. package/dist/trpc/routers/integrations.js +129 -0
  849. package/dist/trpc/routers/integrations.js.map +1 -0
  850. package/dist/trpc/routers/jobs.d.ts +88 -27
  851. package/dist/trpc/routers/jobs.d.ts.map +1 -1
  852. package/dist/trpc/routers/jobs.js +113 -35
  853. package/dist/trpc/routers/jobs.js.map +1 -1
  854. package/dist/trpc/routers/js-scripts.d.ts +410 -0
  855. package/dist/trpc/routers/js-scripts.d.ts.map +1 -0
  856. package/dist/trpc/routers/js-scripts.js +303 -0
  857. package/dist/trpc/routers/js-scripts.js.map +1 -0
  858. package/dist/trpc/routers/mcp-config.d.ts +1 -1
  859. package/dist/trpc/routers/mcp-config.js +5 -3
  860. package/dist/trpc/routers/mcp-config.js.map +1 -1
  861. package/dist/trpc/routers/memories.d.ts +79 -0
  862. package/dist/trpc/routers/memories.d.ts.map +1 -0
  863. package/dist/trpc/routers/memories.js +66 -0
  864. package/dist/trpc/routers/memories.js.map +1 -0
  865. package/dist/trpc/routers/messages.d.ts +4 -77
  866. package/dist/trpc/routers/messages.d.ts.map +1 -1
  867. package/dist/trpc/routers/messages.js +9 -58
  868. package/dist/trpc/routers/messages.js.map +1 -1
  869. package/dist/trpc/routers/models.d.ts +1403 -238
  870. package/dist/trpc/routers/models.d.ts.map +1 -1
  871. package/dist/trpc/routers/models.js +532 -294
  872. package/dist/trpc/routers/models.js.map +1 -1
  873. package/dist/trpc/routers/nodes.d.ts +32 -16
  874. package/dist/trpc/routers/nodes.d.ts.map +1 -1
  875. package/dist/trpc/routers/nodes.js +70 -31
  876. package/dist/trpc/routers/nodes.js.map +1 -1
  877. package/dist/trpc/routers/packs.d.ts +184 -0
  878. package/dist/trpc/routers/packs.d.ts.map +1 -0
  879. package/dist/trpc/routers/packs.js +259 -0
  880. package/dist/trpc/routers/packs.js.map +1 -0
  881. package/dist/trpc/routers/projects.d.ts +422 -0
  882. package/dist/trpc/routers/projects.d.ts.map +1 -0
  883. package/dist/trpc/routers/projects.js +373 -0
  884. package/dist/trpc/routers/projects.js.map +1 -0
  885. package/dist/trpc/routers/resources.d.ts +120 -0
  886. package/dist/trpc/routers/resources.d.ts.map +1 -0
  887. package/dist/trpc/routers/resources.js +212 -0
  888. package/dist/trpc/routers/resources.js.map +1 -0
  889. package/dist/trpc/routers/scripts.d.ts +1116 -0
  890. package/dist/trpc/routers/scripts.d.ts.map +1 -0
  891. package/dist/trpc/routers/scripts.js +131 -0
  892. package/dist/trpc/routers/scripts.js.map +1 -0
  893. package/dist/trpc/routers/segmentation.d.ts +62 -0
  894. package/dist/trpc/routers/segmentation.d.ts.map +1 -0
  895. package/dist/trpc/routers/segmentation.js +146 -0
  896. package/dist/trpc/routers/segmentation.js.map +1 -0
  897. package/dist/trpc/routers/settings.d.ts +177 -10
  898. package/dist/trpc/routers/settings.d.ts.map +1 -1
  899. package/dist/trpc/routers/settings.js +123 -33
  900. package/dist/trpc/routers/settings.js.map +1 -1
  901. package/dist/trpc/routers/sketch.d.ts +854 -0
  902. package/dist/trpc/routers/sketch.d.ts.map +1 -0
  903. package/dist/trpc/routers/sketch.js +617 -0
  904. package/dist/trpc/routers/sketch.js.map +1 -0
  905. package/dist/trpc/routers/skills.d.ts +76 -22
  906. package/dist/trpc/routers/skills.d.ts.map +1 -1
  907. package/dist/trpc/routers/skills.js +195 -170
  908. package/dist/trpc/routers/skills.js.map +1 -1
  909. package/dist/trpc/routers/storage.d.ts +3 -30
  910. package/dist/trpc/routers/storage.d.ts.map +1 -1
  911. package/dist/trpc/routers/storage.js +60 -117
  912. package/dist/trpc/routers/storage.js.map +1 -1
  913. package/dist/trpc/routers/storyboards.d.ts +5907 -0
  914. package/dist/trpc/routers/storyboards.d.ts.map +1 -0
  915. package/dist/trpc/routers/storyboards.js +156 -0
  916. package/dist/trpc/routers/storyboards.js.map +1 -0
  917. package/dist/trpc/routers/threads.d.ts +33 -21
  918. package/dist/trpc/routers/threads.d.ts.map +1 -1
  919. package/dist/trpc/routers/threads.js +36 -30
  920. package/dist/trpc/routers/threads.js.map +1 -1
  921. package/dist/trpc/routers/timeline.d.ts +8516 -0
  922. package/dist/trpc/routers/timeline.d.ts.map +1 -0
  923. package/dist/trpc/routers/timeline.js +493 -0
  924. package/dist/trpc/routers/timeline.js.map +1 -0
  925. package/dist/trpc/routers/triggers.d.ts +46 -0
  926. package/dist/trpc/routers/triggers.d.ts.map +1 -0
  927. package/dist/trpc/routers/triggers.js +117 -0
  928. package/dist/trpc/routers/triggers.js.map +1 -0
  929. package/dist/trpc/routers/users.d.ts +1 -14
  930. package/dist/trpc/routers/users.d.ts.map +1 -1
  931. package/dist/trpc/routers/users.js +1 -12
  932. package/dist/trpc/routers/users.js.map +1 -1
  933. package/dist/trpc/routers/worker.d.ts +242 -0
  934. package/dist/trpc/routers/worker.d.ts.map +1 -0
  935. package/dist/trpc/routers/worker.js +220 -0
  936. package/dist/trpc/routers/worker.js.map +1 -0
  937. package/dist/trpc/routers/workflows.d.ts +385 -197
  938. package/dist/trpc/routers/workflows.d.ts.map +1 -1
  939. package/dist/trpc/routers/workflows.js +326 -345
  940. package/dist/trpc/routers/workflows.js.map +1 -1
  941. package/dist/trpc/routers/workspace.d.ts +59 -33
  942. package/dist/trpc/routers/workspace.d.ts.map +1 -1
  943. package/dist/trpc/routers/workspace.js +230 -93
  944. package/dist/trpc/routers/workspace.js.map +1 -1
  945. package/dist/trpc/sandbox-coverage.d.ts +64 -0
  946. package/dist/trpc/sandbox-coverage.d.ts.map +1 -0
  947. package/dist/trpc/sandbox-coverage.js +1049 -0
  948. package/dist/trpc/sandbox-coverage.js.map +1 -0
  949. package/dist/websocket-client-session.d.ts +320 -0
  950. package/dist/websocket-client-session.d.ts.map +1 -0
  951. package/dist/websocket-client-session.js +1253 -0
  952. package/dist/websocket-client-session.js.map +1 -0
  953. package/dist/workflow-interface-service.d.ts +87 -0
  954. package/dist/workflow-interface-service.d.ts.map +1 -0
  955. package/dist/workflow-interface-service.js +131 -0
  956. package/dist/workflow-interface-service.js.map +1 -0
  957. package/dist/workspace-api.d.ts.map +1 -1
  958. package/dist/workspace-api.js +33 -23
  959. package/dist/workspace-api.js.map +1 -1
  960. package/dist/ws-adapter.d.ts +46 -3
  961. package/dist/ws-adapter.d.ts.map +1 -1
  962. package/dist/ws-adapter.js +164 -26
  963. package/dist/ws-adapter.js.map +1 -1
  964. package/package.json +81 -35
  965. package/dist/agent/agent-runtime.d.ts +0 -47
  966. package/dist/agent/agent-runtime.d.ts.map +0 -1
  967. package/dist/agent/agent-runtime.js +0 -579
  968. package/dist/agent/agent-runtime.js.map +0 -1
  969. package/dist/agent/codex-agent.d.ts +0 -41
  970. package/dist/agent/codex-agent.d.ts.map +0 -1
  971. package/dist/agent/codex-agent.js +0 -334
  972. package/dist/agent/codex-agent.js.map +0 -1
  973. package/dist/agent/index.d.ts +0 -6
  974. package/dist/agent/index.d.ts.map +0 -1
  975. package/dist/agent/index.js +0 -4
  976. package/dist/agent/index.js.map +0 -1
  977. package/dist/agent/llm-agent.d.ts +0 -51
  978. package/dist/agent/llm-agent.d.ts.map +0 -1
  979. package/dist/agent/llm-agent.js +0 -526
  980. package/dist/agent/llm-agent.js.map +0 -1
  981. package/dist/agent/mcp-tool-server.d.ts +0 -33
  982. package/dist/agent/mcp-tool-server.d.ts.map +0 -1
  983. package/dist/agent/mcp-tool-server.js +0 -292
  984. package/dist/agent/mcp-tool-server.js.map +0 -1
  985. package/dist/agent/opencode-agent.d.ts +0 -32
  986. package/dist/agent/opencode-agent.d.ts.map +0 -1
  987. package/dist/agent/opencode-agent.js +0 -332
  988. package/dist/agent/opencode-agent.js.map +0 -1
  989. package/dist/agent/pi-agent.d.ts +0 -42
  990. package/dist/agent/pi-agent.d.ts.map +0 -1
  991. package/dist/agent/pi-agent.js +0 -429
  992. package/dist/agent/pi-agent.js.map +0 -1
  993. package/dist/agent/sdk-provider.d.ts +0 -46
  994. package/dist/agent/sdk-provider.d.ts.map +0 -1
  995. package/dist/agent/sdk-provider.js +0 -50
  996. package/dist/agent/sdk-provider.js.map +0 -1
  997. package/dist/agent/socket-route.d.ts +0 -11
  998. package/dist/agent/socket-route.d.ts.map +0 -1
  999. package/dist/agent/socket-route.js +0 -262
  1000. package/dist/agent/socket-route.js.map +0 -1
  1001. package/dist/agent/transport.d.ts +0 -39
  1002. package/dist/agent/transport.d.ts.map +0 -1
  1003. package/dist/agent/transport.js +0 -17
  1004. package/dist/agent/transport.js.map +0 -1
  1005. package/dist/agent/types.d.ts +0 -11
  1006. package/dist/agent/types.d.ts.map +0 -1
  1007. package/dist/agent/types.js +0 -10
  1008. package/dist/agent/types.js.map +0 -1
  1009. package/dist/routes/jobs.d.ts +0 -16
  1010. package/dist/routes/jobs.d.ts.map +0 -1
  1011. package/dist/routes/jobs.js +0 -25
  1012. package/dist/routes/jobs.js.map +0 -1
  1013. package/dist/trpc/routers/sandboxes.d.ts +0 -64
  1014. package/dist/trpc/routers/sandboxes.d.ts.map +0 -1
  1015. package/dist/trpc/routers/sandboxes.js +0 -327
  1016. package/dist/trpc/routers/sandboxes.js.map +0 -1
  1017. package/dist/unified-websocket-runner.d.ts +0 -280
  1018. package/dist/unified-websocket-runner.d.ts.map +0 -1
  1019. package/dist/unified-websocket-runner.js +0 -3840
  1020. package/dist/unified-websocket-runner.js.map +0 -1
@@ -1,3840 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { tmpdir } from "node:os";
3
- import { pathToFileURL } from "node:url";
4
- import { getSecret } from "@nodetool-ai/models";
5
- import { getSetting } from "./settings-registry.js";
6
- import { pack, unpack } from "msgpackr";
7
- import { createLogger, getDefaultAssetsPath, buildAssetUrl } from "@nodetool-ai/config";
8
- import { resolveContentUrls, resolveContentForProvider } from "./resolve-media-urls.js";
9
- import { Graph, WorkflowRunner } from "@nodetool-ai/kernel";
10
- import { Asset, Job, Message, ModelObserver, Prediction, Thread, Workflow } from "@nodetool-ai/models";
11
- import { FileStorageAdapter, ProcessingContext as RuntimeProcessingContext, executeComfy } from "@nodetool-ai/runtime";
12
- import { Tool } from "@nodetool-ai/agents";
13
- const log = createLogger("nodetool.websocket.runner");
14
- const DATA_URI_PATTERN = /data:([^;,]+)?;base64,[A-Za-z0-9+/=\r\n]+/gi;
15
- const MAX_ERROR_TEXT_LENGTH = 4000;
16
- /**
17
- * Return `true` when the given http(s) URL appears to point at a public
18
- * destination (not a loopback, link-local, or RFC1918 private address).
19
- *
20
- * Used before `fetch`ing URLs supplied by chat clients to resolve source
21
- * images — without this gate, an authenticated user could coerce the server
22
- * into reading internal services via `http://169.254.169.254/...`,
23
- * `http://localhost:6379/...`, etc. The check is conservative: unparseable
24
- * URLs and literal IP addresses in private ranges are refused. DNS-based
25
- * bypass is still possible, so this is a defense-in-depth measure and not a
26
- * full SSRF mitigation; intended for complementing network-level egress
27
- * filtering.
28
- */
29
- function isSafeExternalUrl(url) {
30
- let parsed;
31
- try {
32
- parsed = new URL(url);
33
- }
34
- catch {
35
- return false;
36
- }
37
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
38
- return false;
39
- }
40
- // Normalise IPv6 hostnames: WHATWG URL may return them with or without
41
- // surrounding brackets depending on the runtime.
42
- const host = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
43
- if (!host)
44
- return false;
45
- if (host === "localhost" ||
46
- host === "ip6-localhost" ||
47
- host === "ip6-loopback" ||
48
- host.endsWith(".localhost") ||
49
- host.endsWith(".local") ||
50
- host.endsWith(".internal")) {
51
- return false;
52
- }
53
- // IPv4 literal check
54
- const ipv4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
55
- if (ipv4) {
56
- const [a, b] = ipv4.slice(1).map((n) => parseInt(n, 10));
57
- // 0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16,
58
- // 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 (CGNAT)
59
- if (a === 0 || a === 10 || a === 127)
60
- return false;
61
- if (a === 169 && b === 254)
62
- return false;
63
- if (a === 172 && b >= 16 && b <= 31)
64
- return false;
65
- if (a === 192 && b === 168)
66
- return false;
67
- if (a === 100 && b >= 64 && b <= 127)
68
- return false;
69
- }
70
- // IPv6 literal — refuse loopback / ULA / link-local / unspecified ranges.
71
- if (host.includes(":")) {
72
- if (host === "::" || host === "::1")
73
- return false;
74
- if (host.startsWith("fc") || host.startsWith("fd"))
75
- return false; // ULA fc00::/7
76
- if (host.startsWith("fe80:"))
77
- return false; // link-local
78
- }
79
- return true;
80
- }
81
- function sanitizeLargeText(text, maxLength = MAX_ERROR_TEXT_LENGTH) {
82
- const sanitized = text.replace(DATA_URI_PATTERN, (match, mimeType) => {
83
- const mime = typeof mimeType === "string" && mimeType !== "" ? mimeType : "data";
84
- return `[${mime} base64 omitted, ${match.length} chars]`;
85
- });
86
- if (sanitized.length <= maxLength) {
87
- return sanitized;
88
- }
89
- const truncatedChars = sanitized.length - maxLength;
90
- return `${sanitized.slice(0, maxLength)}... (truncated ${truncatedChars} chars)`;
91
- }
92
- function sanitizeErrorValue(value, seen = new WeakSet()) {
93
- if (typeof value === "string") {
94
- return sanitizeLargeText(value);
95
- }
96
- if (value instanceof Error) {
97
- return sanitizeLargeText(value.message);
98
- }
99
- if (Array.isArray(value)) {
100
- return value.map((item) => sanitizeErrorValue(item, seen));
101
- }
102
- if (value && typeof value === "object") {
103
- if (seen.has(value)) {
104
- return "[circular]";
105
- }
106
- seen.add(value);
107
- const result = {};
108
- for (const [key, nested] of Object.entries(value)) {
109
- result[key] = sanitizeErrorValue(nested, seen);
110
- }
111
- return result;
112
- }
113
- return value;
114
- }
115
- function formatSanitizedError(error) {
116
- if (typeof error === "string") {
117
- return sanitizeLargeText(error);
118
- }
119
- if (error instanceof Error) {
120
- return sanitizeLargeText(error.message);
121
- }
122
- const sanitized = sanitizeErrorValue(error);
123
- if (typeof sanitized === "string") {
124
- return sanitized;
125
- }
126
- try {
127
- return sanitizeLargeText(JSON.stringify(sanitized));
128
- }
129
- catch {
130
- return sanitizeLargeText(String(error));
131
- }
132
- }
133
- function getAssetStoragePath() {
134
- return getDefaultAssetsPath();
135
- }
136
- // ---------------------------------------------------------------------------
137
- // Auto-save assets — persists generated media as Asset records
138
- // ---------------------------------------------------------------------------
139
- const ASSET_MEDIA_TYPES = new Set(["image", "audio", "video"]);
140
- const ASSET_TYPE_MIME = {
141
- image: "image/png",
142
- audio: "audio/wav",
143
- video: "video/mp4"
144
- };
145
- const ASSET_TYPE_EXT = {
146
- image: "png",
147
- audio: "wav",
148
- video: "mp4"
149
- };
150
- function isAssetLikeValue(value) {
151
- if (!value || typeof value !== "object" || Array.isArray(value))
152
- return false;
153
- const v = value;
154
- return (typeof v.type === "string" &&
155
- ASSET_MEDIA_TYPES.has(v.type) &&
156
- ("data" in v || "uri" in v));
157
- }
158
- function decodeAssetBytes(data) {
159
- if (data === null || data === undefined)
160
- return null;
161
- if (data instanceof Uint8Array)
162
- return data;
163
- if (Buffer.isBuffer(data))
164
- return new Uint8Array(data);
165
- if (Array.isArray(data) && data.every((v) => Number.isInteger(v))) {
166
- return new Uint8Array(data);
167
- }
168
- if (typeof data === "string") {
169
- return Uint8Array.from(Buffer.from(data, "base64"));
170
- }
171
- return null;
172
- }
173
- async function readBytesFromUri(uri) {
174
- if (!uri)
175
- return null;
176
- try {
177
- if (uri.startsWith("file://")) {
178
- const { readFile } = await import("node:fs/promises");
179
- const { fileURLToPath } = await import("node:url");
180
- return new Uint8Array(await readFile(fileURLToPath(uri)));
181
- }
182
- if (uri.startsWith("data:")) {
183
- const commaIdx = uri.indexOf(",");
184
- if (commaIdx < 0)
185
- return null;
186
- return Uint8Array.from(Buffer.from(uri.slice(commaIdx + 1), "base64"));
187
- }
188
- if (uri.startsWith("http://") || uri.startsWith("https://")) {
189
- const resp = await fetch(uri);
190
- if (!resp.ok)
191
- return null;
192
- return new Uint8Array(await resp.arrayBuffer());
193
- }
194
- }
195
- catch {
196
- // Failed to read bytes — non-fatal
197
- }
198
- return null;
199
- }
200
- /**
201
- * Recursively find asset-like values in a result object and persist them as
202
- * Asset records in the database + on disk.
203
- *
204
- * Mutates the result in-place: sets `asset_id` and updates `uri` to
205
- * `asset://{id}.{ext}`.
206
- */
207
- async function autoSaveAssets(result, opts) {
208
- const { join } = await import("node:path");
209
- const { writeFile, mkdir } = await import("node:fs/promises");
210
- const queue = [];
211
- // Collect all asset-like values from the result (may be nested)
212
- function collect(value) {
213
- if (value === null || value === undefined)
214
- return;
215
- if (Array.isArray(value)) {
216
- for (const item of value)
217
- collect(item);
218
- return;
219
- }
220
- if (isAssetLikeValue(value)) {
221
- queue.push(value);
222
- return;
223
- }
224
- if (typeof value === "object") {
225
- for (const v of Object.values(value)) {
226
- collect(v);
227
- }
228
- }
229
- }
230
- collect(result);
231
- for (const assetValue of queue) {
232
- // Skip if already saved
233
- if (assetValue.asset_id)
234
- continue;
235
- const assetType = String(assetValue.type);
236
- // Get bytes from inline data or URI
237
- let bytes = decodeAssetBytes(assetValue.data);
238
- if (!bytes && typeof assetValue.uri === "string") {
239
- bytes = await readBytesFromUri(assetValue.uri);
240
- }
241
- if (!bytes)
242
- continue;
243
- // Determine mime/ext, preferring explicit content_type
244
- const explicitMime = assetValue.mime_type ?? assetValue.content_type;
245
- const contentType = typeof explicitMime === "string" && explicitMime
246
- ? explicitMime
247
- : (ASSET_TYPE_MIME[assetType] ?? "application/octet-stream");
248
- const ext = ASSET_TYPE_EXT[assetType] ?? "bin";
249
- // Create Asset record
250
- const asset = new Asset({
251
- user_id: opts.userId,
252
- workflow_id: opts.workflowId ?? null,
253
- node_id: opts.nodeId,
254
- job_id: opts.jobId,
255
- name: `${assetType}_${opts.nodeId.slice(0, 8)}`,
256
- content_type: contentType,
257
- parent_id: null
258
- });
259
- const fileName = `${asset.id}.${ext}`;
260
- try {
261
- await mkdir(opts.storagePath, { recursive: true });
262
- await writeFile(join(opts.storagePath, fileName), bytes);
263
- asset.size = bytes.length;
264
- await asset.save();
265
- // Mutate the result value in-place
266
- assetValue.asset_id = asset.id;
267
- assetValue.uri = `asset://${fileName}`;
268
- }
269
- catch (err) {
270
- log.warn("Auto-save asset failed", {
271
- nodeId: opts.nodeId,
272
- error: String(err)
273
- });
274
- }
275
- }
276
- }
277
- /**
278
- * Returns true if every node in the graph has a type starting with "comfy.".
279
- */
280
- function isComfyGraph(graph) {
281
- return (graph.nodes.length > 0 &&
282
- graph.nodes.every((n) => typeof n.type === "string" && n.type.startsWith("comfy.")));
283
- }
284
- /**
285
- * Converts a NodeTool graph (with comfy.* nodes) into a ComfyUI API prompt dict.
286
- */
287
- function graphToComfyPrompt(graph) {
288
- const prompt = {};
289
- for (const node of graph.nodes) {
290
- const id = String(node.id);
291
- const classType = node.type.replace(/^comfy\./, "");
292
- const props = node.properties ??
293
- node.data ??
294
- {};
295
- const inputs = {};
296
- for (const [key, value] of Object.entries(props)) {
297
- if (key.startsWith("_") || key === "workflow_id")
298
- continue;
299
- // MsgPack may decode large integers as BigInt; ComfyUI expects plain numbers
300
- inputs[key] = typeof value === "bigint" ? Number(value) : value;
301
- }
302
- prompt[id] = { class_type: classType, inputs };
303
- }
304
- // Wire edges: set inputs to [sourceId, slotIndex]
305
- for (const edge of graph.edges) {
306
- const sourceId = String(edge.source);
307
- const targetId = String(edge.target);
308
- const sourceHandle = edge.sourceHandle;
309
- const targetHandle = edge.targetHandle;
310
- if (!prompt[targetId])
311
- continue;
312
- // Determine output slot index from source handle name.
313
- // Handles may be "output_0", "output_1", etc. (generic indexed format)
314
- // or named like "MODEL", "CLIP", "VAE" (resolved via _comfy_metadata).
315
- let slotIndex = 0;
316
- const outputMatch = /^output_(\d+)$/.exec(sourceHandle);
317
- if (outputMatch) {
318
- slotIndex = parseInt(outputMatch[1], 10);
319
- }
320
- else {
321
- const sourceNode = graph.nodes.find((n) => String(n.id) === sourceId);
322
- if (sourceNode) {
323
- const nodeProps = sourceNode.properties ??
324
- sourceNode.data ??
325
- {};
326
- const meta = (nodeProps._comfy_metadata ??
327
- sourceNode._comfy_metadata);
328
- if (meta?.outputs) {
329
- const idx = meta.outputs.indexOf(sourceHandle);
330
- if (idx >= 0)
331
- slotIndex = idx;
332
- }
333
- }
334
- }
335
- prompt[targetId].inputs[targetHandle] = [sourceId, slotIndex];
336
- }
337
- return prompt;
338
- }
339
- function createRuntimeContext(opts) {
340
- const storagePath = getAssetStoragePath();
341
- const storage = new FileStorageAdapter(storagePath);
342
- const ctx = new RuntimeProcessingContext({
343
- ...opts,
344
- secretResolver: getSecret,
345
- storage,
346
- tempUrlResolver: (fileUri) => {
347
- // Convert file:///path/to/storage/temp/uuid.png → public asset URL.
348
- // When ASSET_DOMAIN / TEMP_DOMAIN are configured the result uses those
349
- // domains; otherwise it falls back to /api/storage/temp/uuid.png.
350
- const prefix = pathToFileURL(storagePath).toString();
351
- if (fileUri.startsWith(prefix)) {
352
- return buildAssetUrl(fileUri.slice(prefix.length + 1));
353
- }
354
- return fileUri;
355
- }
356
- });
357
- const MIME_TO_EXT = {
358
- "image/jpeg": "jpg", "image/png": "png", "image/gif": "gif",
359
- "image/webp": "webp", "image/bmp": "bmp", "image/svg+xml": "svg",
360
- "audio/mpeg": "mp3", "audio/mp3": "mp3", "audio/wav": "wav",
361
- "audio/ogg": "ogg", "video/mp4": "mp4", "video/webm": "webm",
362
- "application/pdf": "pdf", "text/plain": "txt", "text/html": "html",
363
- "model/gltf-binary": "glb"
364
- };
365
- ctx.setModelInterfaces({
366
- createAsset: async (args) => {
367
- const { join } = await import("node:path");
368
- const { writeFile, mkdir } = await import("node:fs/promises");
369
- const asset = new Asset({
370
- user_id: args.userId,
371
- workflow_id: args.workflowId ?? null,
372
- node_id: args.nodeId ?? null,
373
- job_id: args.jobId ?? null,
374
- name: args.name,
375
- content_type: args.contentType,
376
- parent_id: args.parentId ?? null
377
- });
378
- if (args.content) {
379
- const ext = MIME_TO_EXT[args.contentType] ?? "bin";
380
- const fileName = `${asset.id}.${ext}`;
381
- await mkdir(storagePath, { recursive: true });
382
- await writeFile(join(storagePath, fileName), args.content);
383
- asset.size = args.content.length;
384
- }
385
- await asset.save();
386
- return asset;
387
- }
388
- });
389
- return ctx;
390
- }
391
- /**
392
- * Default system prompt for regular chat — matches Python's REGULAR_SYSTEM_PROMPT.
393
- */
394
- const REGULAR_SYSTEM_PROMPT = `You are a helpful assistant.
395
-
396
- # IMAGE TOOLS
397
- When using image tools, you will get an image url as result.
398
- ALWAYS EMBED THE IMAGE AS MARKDOWN IMAGE TAG.
399
-
400
- # File types
401
- References to documents, images, videos or audio files are objects with following structure:
402
- - type: either document, image, video, audio
403
- - uri: either local "file:///path/to/file" or "http://"
404
- `;
405
- class ToolBridge {
406
- waiters = new Map();
407
- createWaiter(toolCallId, timeoutMs = 300_000) {
408
- return new Promise((resolve, reject) => {
409
- let timer = null;
410
- const cleanup = () => {
411
- if (timer)
412
- clearTimeout(timer);
413
- this.waiters.delete(toolCallId);
414
- };
415
- const wrappedResolve = (value) => {
416
- cleanup();
417
- resolve(value);
418
- };
419
- const wrappedReject = (reason) => {
420
- cleanup();
421
- reject(reason);
422
- };
423
- this.waiters.set(toolCallId, {
424
- resolve: wrappedResolve,
425
- reject: wrappedReject
426
- });
427
- if (timeoutMs > 0) {
428
- timer = setTimeout(() => {
429
- if (this.waiters.has(toolCallId)) {
430
- wrappedReject(new Error(`Tool call ${toolCallId} timed out after ${timeoutMs}ms`));
431
- }
432
- }, timeoutMs);
433
- }
434
- });
435
- }
436
- resolveResult(toolCallId, payload) {
437
- const waiter = this.waiters.get(toolCallId);
438
- if (!waiter)
439
- return;
440
- waiter.resolve(payload);
441
- }
442
- cancelAll() {
443
- const error = new Error("All pending tool calls cancelled");
444
- for (const waiter of this.waiters.values()) {
445
- waiter.reject(error);
446
- }
447
- this.waiters.clear();
448
- }
449
- }
450
- /**
451
- * Proxy tool that forwards execution to the frontend via ToolBridge.
452
- * Mirrors Python's UIToolProxy from messaging/ui_tool_proxy.py.
453
- */
454
- class UIToolProxy extends Tool {
455
- name;
456
- description;
457
- inputSchema;
458
- bridge;
459
- sendMsg;
460
- constructor(manifest, bridge, sendMsg) {
461
- super();
462
- this.name = typeof manifest.name === "string" ? manifest.name : "";
463
- this.description =
464
- typeof manifest.description === "string"
465
- ? manifest.description
466
- : "UI tool";
467
- this.inputSchema =
468
- typeof manifest.parameters === "object" && manifest.parameters !== null
469
- ? manifest.parameters
470
- : {};
471
- this.bridge = bridge;
472
- this.sendMsg = sendMsg;
473
- }
474
- async process(_context, params) {
475
- const toolCallId = randomUUID();
476
- await this.sendMsg({
477
- type: "tool_call",
478
- tool_call_id: toolCallId,
479
- name: this.name,
480
- args: params
481
- });
482
- try {
483
- const payload = await this.bridge.createWaiter(toolCallId, 60_000);
484
- if (payload.ok) {
485
- return payload.result ?? {};
486
- }
487
- return {
488
- error: `Frontend tool execution failed: ${payload.error ?? "Unknown error"}`
489
- };
490
- }
491
- catch (err) {
492
- return { error: err instanceof Error ? err.message : String(err) };
493
- }
494
- }
495
- userMessage(_params) {
496
- return `Executing frontend tool: ${this.name}`;
497
- }
498
- }
499
- export class UnifiedWebSocketRunner {
500
- websocket = null;
501
- mode = "binary";
502
- userId;
503
- authToken;
504
- defaultModel;
505
- defaultProvider;
506
- resolveExecutor;
507
- resolveNodeType;
508
- resolveProvider;
509
- resolveTools;
510
- getSystemStats;
511
- workspaceResolver;
512
- beforeRunJob;
513
- getNodeMetadata;
514
- validateNode;
515
- nodeRegistry;
516
- configuredProvidersCache = new Map();
517
- sendLock = Promise.resolve();
518
- activeJobs = new Map();
519
- currentTask = null;
520
- heartbeatTimer = null;
521
- statsTimer = null;
522
- chatRequestSeq = 0;
523
- clientToolsManifest = {};
524
- toolBridge = new ToolBridge();
525
- observerRegistered = false;
526
- logError(context, error) {
527
- log.error(context, formatSanitizedError(error));
528
- }
529
- /**
530
- * Extract text from message content that may be a string or array of content items.
531
- * Mirrors Python's _extract_query_text / _extract_objective / _extract_text_content.
532
- */
533
- extractTextContent(content, fallback = "") {
534
- if (typeof content === "string")
535
- return content;
536
- if (Array.isArray(content)) {
537
- const texts = content
538
- .filter((c) => c.type === "text" && typeof c.text === "string")
539
- .map((c) => c.text);
540
- return texts.length > 0 ? texts.join(" ") : fallback;
541
- }
542
- return fallback;
543
- }
544
- /**
545
- * Run one final LLM call to present structured agent results as readable
546
- * markdown. Falls back to formatted JSON if the call fails.
547
- */
548
- async presentStructuredResults(provider, model, objective, resultsJson, threadId) {
549
- const messages = [
550
- {
551
- role: "user",
552
- content: `You completed the following task:\n"${objective}"\n\nHere are the structured results:\n\`\`\`json\n${resultsJson}\n\`\`\`\n\nPresent these results to the user in clear, well-formatted markdown. Be concise — do not repeat the raw JSON. Focus on readability.`
553
- }
554
- ];
555
- try {
556
- let content = "";
557
- for await (const item of provider.generateMessagesTraced({
558
- messages,
559
- model
560
- })) {
561
- if ("type" in item && item.type === "chunk") {
562
- const chunk = item;
563
- content += chunk.content ?? "";
564
- // Stream chunks to client so the user sees the response forming
565
- await this.sendMessage({
566
- type: "chunk",
567
- content: chunk.content ?? "",
568
- done: false,
569
- thread_id: threadId
570
- });
571
- }
572
- }
573
- return content || resultsJson;
574
- }
575
- catch (err) {
576
- log.warn("Failed to present structured results via LLM, using JSON fallback", {
577
- error: err instanceof Error ? err.message : String(err)
578
- });
579
- return resultsJson;
580
- }
581
- }
582
- inferOutputType(value) {
583
- if (value === null || value === undefined)
584
- return "any";
585
- if (typeof value === "string")
586
- return "str";
587
- if (typeof value === "number")
588
- return Number.isInteger(value) ? "int" : "float";
589
- if (typeof value === "boolean")
590
- return "bool";
591
- if (Array.isArray(value))
592
- return "list";
593
- if (value && typeof value === "object")
594
- return "dict";
595
- return "any";
596
- }
597
- resolveOutputNodeForKey(active, outputKey) {
598
- let fallback = null;
599
- for (const raw of active.graph.nodes) {
600
- const node = raw;
601
- const id = typeof node.id === "string" ? node.id : null;
602
- if (!id)
603
- continue;
604
- const name = typeof node.name === "string" ? node.name : id;
605
- const type = typeof node.type === "string" ? node.type : "";
606
- if (name === outputKey || id === outputKey)
607
- return { id, name };
608
- if (type === "nodetool.output.Output" && !fallback)
609
- fallback = { id, name };
610
- }
611
- return fallback;
612
- }
613
- async sendOutputUpdates(active, outputs) {
614
- for (const [outputKey, values] of Object.entries(outputs)) {
615
- const nodeRef = this.resolveOutputNodeForKey(active, outputKey) ?? {
616
- id: outputKey,
617
- name: outputKey
618
- };
619
- const seq = Array.isArray(values) ? values : [];
620
- for (const rawValue of seq) {
621
- const value = await active.context.normalizeOutputValue(rawValue);
622
- await this.sendMessage({
623
- type: "output_update",
624
- node_id: nodeRef.id,
625
- node_name: nodeRef.name,
626
- output_name: "output",
627
- value,
628
- output_type: this.inferOutputType(value),
629
- metadata: {},
630
- workflow_id: active.workflowId,
631
- job_id: active.jobId
632
- });
633
- }
634
- }
635
- }
636
- constructor(options) {
637
- this.userId = options.userId ?? null;
638
- this.authToken = options.authToken ?? null;
639
- this.defaultModel = options.defaultModel ?? "gpt-oss:20b";
640
- this.defaultProvider = options.defaultProvider ?? "ollama";
641
- this.resolveExecutor = options.resolveExecutor;
642
- this.resolveNodeType = options.resolveNodeType;
643
- this.resolveProvider = options.resolveProvider;
644
- this.resolveTools = options.resolveTools;
645
- this.workspaceResolver = options.workspaceResolver;
646
- this.beforeRunJob = options.beforeRunJob;
647
- this.getNodeMetadata = options.getNodeMetadata;
648
- this.validateNode = options.validateNode;
649
- this.nodeRegistry = options.nodeRegistry;
650
- this.getSystemStats =
651
- options.getSystemStats ??
652
- (() => ({
653
- timestamp: Date.now(),
654
- process_uptime_sec: process.uptime(),
655
- memory: process.memoryUsage()
656
- }));
657
- }
658
- async connect(websocket, userId, authToken) {
659
- if (userId)
660
- this.userId = userId;
661
- if (authToken)
662
- this.authToken = authToken;
663
- this.userId = this.userId ?? "1";
664
- await websocket.accept();
665
- this.websocket = websocket;
666
- log.info("Client connected", { userId: this.userId });
667
- this.startHeartbeat();
668
- // Only broadcast system stats in development — unnecessary overhead in production
669
- if (process.env.NODE_ENV !== "production") {
670
- this.startStatsBroadcast();
671
- }
672
- this.registerObserver();
673
- }
674
- async disconnect() {
675
- log.info("Client disconnected");
676
- this.stopHeartbeat();
677
- this.stopStatsBroadcast();
678
- this.unregisterObserver();
679
- this.toolBridge.cancelAll();
680
- this.currentTask = null;
681
- for (const [jobId, job] of this.activeJobs) {
682
- if (job.comfyHandle) {
683
- job.comfyHandle.cancel();
684
- }
685
- else if (job.runner) {
686
- job.runner.cancel();
687
- }
688
- this.activeJobs.delete(jobId);
689
- }
690
- if (this.websocket) {
691
- try {
692
- await this.websocket.close();
693
- }
694
- catch (error) {
695
- this.logError("disconnect websocket.close failed", error);
696
- }
697
- }
698
- this.websocket = null;
699
- }
700
- serializeForJson(value) {
701
- if (value instanceof Uint8Array)
702
- return Array.from(value);
703
- if (value instanceof Date)
704
- return value.toISOString();
705
- if (Array.isArray(value))
706
- return value.map((v) => this.serializeForJson(v));
707
- if (value && typeof value === "object") {
708
- const out = {};
709
- for (const [k, v] of Object.entries(value)) {
710
- out[k] = this.serializeForJson(v);
711
- }
712
- return out;
713
- }
714
- return value;
715
- }
716
- async sendMessage(message) {
717
- if (!this.websocket)
718
- return;
719
- if (this.websocket.clientState === "disconnected" ||
720
- this.websocket.applicationState === "disconnected") {
721
- return;
722
- }
723
- // Resolve storage keys in content to browser-accessible URLs before
724
- // sending over the wire. This keeps DB storage URL-agnostic while
725
- // delivering ready-to-use URLs to the client.
726
- if (Array.isArray(message.content)) {
727
- message = {
728
- ...message,
729
- content: resolveContentUrls(message.content)
730
- };
731
- }
732
- const payload = this.mode === "text"
733
- ? this.serializeForJson(message)
734
- : message;
735
- const prev = this.sendLock;
736
- let release;
737
- this.sendLock = new Promise((resolve) => {
738
- release = resolve;
739
- });
740
- await prev;
741
- try {
742
- if (this.mode === "binary") {
743
- await this.websocket.sendBytes(pack(payload));
744
- }
745
- else {
746
- await this.websocket.sendText(JSON.stringify(payload));
747
- }
748
- }
749
- finally {
750
- release();
751
- }
752
- }
753
- async receiveMessage() {
754
- if (!this.websocket) {
755
- throw new Error("WebSocket is not connected");
756
- }
757
- const message = await this.websocket.receive();
758
- if (message.type === "websocket.disconnect")
759
- return null;
760
- if (message.bytes) {
761
- return unpack(message.bytes);
762
- }
763
- if (message.text) {
764
- return JSON.parse(message.text);
765
- }
766
- return null;
767
- }
768
- /**
769
- * Normalize a raw graph so that the kernel's NodeDescriptor contract is met.
770
- * The web-UI / Python serialisation stores node properties under `data`;
771
- * the kernel expects them under `properties`.
772
- */
773
- normalizeGraph(graph) {
774
- const nodes = graph.nodes.map((n) => {
775
- if (n.properties === undefined && n.data !== undefined) {
776
- const { data, ...rest } = n;
777
- return { ...rest, properties: data };
778
- }
779
- return n;
780
- });
781
- const edges = graph.edges.map((edge) => {
782
- const rawEdgeType = edge.edge_type ?? edge.type;
783
- const edge_type = rawEdgeType === "control" ? "control" : "data";
784
- const { type, ...rest } = edge;
785
- return { ...rest, edge_type };
786
- });
787
- return { nodes, edges };
788
- }
789
- async hydrateGraph(graph) {
790
- const normalized = this.normalizeGraph(graph);
791
- if (!this.resolveNodeType) {
792
- return normalized;
793
- }
794
- const hydrated = await Graph.loadFromDict(normalized, {
795
- resolver: this.resolveNodeType
796
- });
797
- return {
798
- nodes: [...hydrated.nodes],
799
- edges: [...hydrated.edges]
800
- };
801
- }
802
- getRawGraph(req) {
803
- if (req.graph) {
804
- return this.normalizeGraph(req.graph);
805
- }
806
- if (req.workflow_id && this.userId) {
807
- const userId = this.userId;
808
- const workflowId = req.workflow_id;
809
- return (async () => {
810
- const workflow = await Workflow.find(userId, workflowId);
811
- if (!workflow)
812
- throw new Error(`Workflow not found: ${workflowId}`);
813
- return this.normalizeGraph(workflow.graph);
814
- })();
815
- }
816
- throw new Error("workflow_id or graph is required");
817
- }
818
- /**
819
- * Surface a clean terminal job_update when pre-run setup fails (typically
820
- * because the Python bridge could not start). Without this the error would
821
- * bubble up to handleCommand and be sent as a generic `invalid_command`
822
- * envelope, which the UI does not associate with the job — the workflow
823
- * appears to spin forever instead of failing.
824
- */
825
- async emitBeforeRunFailure(jobId, workflowId, err) {
826
- const errorMessage = err instanceof Error ? err.message : String(err);
827
- this.logError("beforeRunJob failed", err);
828
- await this.sendMessage({
829
- type: "job_update",
830
- status: "failed",
831
- job_id: jobId,
832
- workflow_id: workflowId,
833
- error: errorMessage
834
- });
835
- try {
836
- const job = (await Job.get(jobId));
837
- if (job) {
838
- job.markFailed(errorMessage);
839
- await job.save();
840
- }
841
- }
842
- catch (persistErr) {
843
- this.logError("beforeRunJob failure persistence failed", persistErr);
844
- }
845
- }
846
- async runJob(req) {
847
- const userId = req.user_id ?? this.userId ?? "1";
848
- const workflowId = req.workflow_id ?? null;
849
- const jobId = req.job_id ?? randomUUID();
850
- // Get the normalized (but not hydrated) graph first so we can check
851
- // for comfy nodes before hydration strips unregistered node types.
852
- const rawGraph = await this.getRawGraph(req);
853
- // Route ComfyUI workflows to the dedicated comfy executor
854
- if (isComfyGraph(rawGraph)) {
855
- if (this.beforeRunJob) {
856
- try {
857
- await this.beforeRunJob(rawGraph);
858
- }
859
- catch (err) {
860
- await this.emitBeforeRunFailure(jobId, workflowId, err);
861
- return;
862
- }
863
- }
864
- await this.runComfyJob(jobId, workflowId, userId, rawGraph, req.settings ?? {});
865
- return;
866
- }
867
- // Hydrate non-comfy graphs (resolves node types from the registry)
868
- const graph = await this.hydrateGraph(rawGraph);
869
- if (this.beforeRunJob) {
870
- try {
871
- await this.beforeRunJob(graph);
872
- }
873
- catch (err) {
874
- await this.emitBeforeRunFailure(jobId, workflowId, err);
875
- return;
876
- }
877
- }
878
- const workspaceDir = workflowId && this.workspaceResolver
879
- ? await this.workspaceResolver(workflowId, userId)
880
- : null;
881
- const context = createRuntimeContext({
882
- jobId,
883
- workflowId,
884
- userId,
885
- workspaceDir,
886
- assetOutputMode: this.mode === "text" ? "data_uri" : "temp_url"
887
- });
888
- // Expose executor/node-type resolution on the context so that
889
- // sub-workflow nodes (WorkflowNode) can create child runners.
890
- context.setResolveExecutor((node) => this.resolveExecutor(node));
891
- if (this.resolveNodeType) {
892
- const resolverObj = typeof this.resolveNodeType === "function"
893
- ? { resolveNodeType: this.resolveNodeType }
894
- : this.resolveNodeType;
895
- context.setResolveNodeType((nodeType) => resolverObj.resolveNodeType(nodeType));
896
- }
897
- const runner = new WorkflowRunner(jobId, {
898
- resolveExecutor: (node) => this.resolveExecutor(node),
899
- executionContext: context,
900
- validateNode: this.validateNode
901
- });
902
- const active = {
903
- jobId,
904
- workflowId,
905
- context,
906
- runner,
907
- graph,
908
- finished: false,
909
- status: "running"
910
- };
911
- this.activeJobs.set(jobId, active);
912
- log.info("Job started", { jobId, workflowId });
913
- try {
914
- const existing = await Job.get(jobId);
915
- if (!existing) {
916
- await Job.create({
917
- id: jobId,
918
- workflow_id: workflowId ?? "",
919
- user_id: userId,
920
- status: "running",
921
- params: req.params ?? {},
922
- graph
923
- });
924
- }
925
- }
926
- catch (error) {
927
- this.logError("runJob persistence failed", error);
928
- // Persistence is best-effort in TS runtime mode.
929
- }
930
- const executePromise = runner.run({
931
- job_id: jobId,
932
- workflow_id: workflowId ?? undefined,
933
- params: req.params ?? {}
934
- }, graph);
935
- active.streamTask = this.streamJobMessages(active, executePromise);
936
- }
937
- /**
938
- * Execute a ComfyUI workflow via the comfy executor (local or RunPod).
939
- */
940
- async runComfyJob(jobId, workflowId, _userId, graph, _settings) {
941
- const active = {
942
- jobId,
943
- workflowId,
944
- context: null,
945
- runner: null,
946
- graph,
947
- finished: false,
948
- status: "running"
949
- };
950
- this.activeJobs.set(jobId, active);
951
- // Build node metadata lookup for status messages
952
- const nodeLookup = new Map();
953
- for (const node of graph.nodes) {
954
- const id = String(node.id);
955
- const type = node.type ?? "comfy.unknown";
956
- const props = (node.properties ?? node.data ?? {});
957
- const name = props.title ?? type;
958
- nodeLookup.set(id, { name, type });
959
- }
960
- const getNode = (id) => nodeLookup.get(id) ?? { name: `Node ${id}`, type: "comfy.unknown" };
961
- try {
962
- await this.sendMessage({
963
- type: "job_update",
964
- status: "running",
965
- job_id: jobId,
966
- workflow_id: workflowId
967
- });
968
- const prompt = graphToComfyPrompt(graph);
969
- const host = await getSetting("COMFYUI_ADDR");
970
- if (!host) {
971
- throw new Error("COMFYUI_ADDR is not configured. Set it in Settings.");
972
- }
973
- // Track active node so we can mark it completed when the next one starts
974
- let activeNodeId = null;
975
- const completeNode = (nodeId) => {
976
- const n = getNode(nodeId);
977
- void this.sendMessage({
978
- type: "node_update",
979
- node_id: nodeId,
980
- node_name: n.name,
981
- node_type: n.type,
982
- status: "completed",
983
- workflow_id: workflowId
984
- });
985
- };
986
- const onProgress = (event) => {
987
- switch (event.type) {
988
- case "executing": {
989
- // When a new node starts, the previous one is implicitly done
990
- if (activeNodeId && activeNodeId !== event.node) {
991
- completeNode(activeNodeId);
992
- activeNodeId = null;
993
- }
994
- if (event.node) {
995
- activeNodeId = event.node;
996
- const n = getNode(event.node);
997
- void this.sendMessage({
998
- type: "node_update",
999
- node_id: event.node,
1000
- node_name: n.name,
1001
- node_type: n.type,
1002
- status: "running",
1003
- workflow_id: workflowId
1004
- });
1005
- }
1006
- else {
1007
- // null node = execution finished, complete any lingering active node
1008
- if (activeNodeId) {
1009
- completeNode(activeNodeId);
1010
- activeNodeId = null;
1011
- }
1012
- }
1013
- break;
1014
- }
1015
- case "progress":
1016
- if (event.node) {
1017
- void this.sendMessage({
1018
- type: "node_progress",
1019
- node_id: event.node,
1020
- progress: event.progress ?? 0,
1021
- total: event.total ?? 1,
1022
- workflow_id: workflowId
1023
- });
1024
- }
1025
- break;
1026
- case "executed":
1027
- if (event.node) {
1028
- // Explicit completion with output — mark done and clear active
1029
- if (activeNodeId === event.node)
1030
- activeNodeId = null;
1031
- const n = getNode(event.node);
1032
- void this.sendMessage({
1033
- type: "node_update",
1034
- node_id: event.node,
1035
- node_name: n.name,
1036
- node_type: n.type,
1037
- status: "completed",
1038
- result: event.output ?? null,
1039
- workflow_id: workflowId
1040
- });
1041
- }
1042
- break;
1043
- case "execution_cached":
1044
- if (event.cached_nodes) {
1045
- for (const nodeId of event.cached_nodes) {
1046
- completeNode(nodeId);
1047
- }
1048
- }
1049
- break;
1050
- case "execution_error":
1051
- if (event.node) {
1052
- activeNodeId = null;
1053
- const n = getNode(event.node);
1054
- void this.sendMessage({
1055
- type: "node_update",
1056
- node_id: event.node,
1057
- node_name: n.name,
1058
- node_type: n.type,
1059
- status: "error",
1060
- error: event.error ?? "Execution error",
1061
- workflow_id: workflowId
1062
- });
1063
- }
1064
- break;
1065
- }
1066
- };
1067
- const handle = executeComfy(prompt, host, onProgress);
1068
- active.comfyHandle = handle;
1069
- const result = await handle.result;
1070
- if (result.status === "completed") {
1071
- if (result.images && result.images.length > 0) {
1072
- await this.sendMessage({
1073
- type: "output_update",
1074
- job_id: jobId,
1075
- workflow_id: workflowId,
1076
- result: { images: result.images }
1077
- });
1078
- }
1079
- active.status = "completed";
1080
- active.finished = true;
1081
- await this.sendMessage({
1082
- type: "job_update",
1083
- status: "completed",
1084
- job_id: jobId,
1085
- workflow_id: workflowId
1086
- });
1087
- }
1088
- else {
1089
- throw new Error(result.error ?? "ComfyUI execution failed");
1090
- }
1091
- }
1092
- catch (err) {
1093
- active.status = "failed";
1094
- active.finished = true;
1095
- active.error = err instanceof Error ? err.message : String(err);
1096
- log.error("ComfyUI job failed", { jobId, error: active.error });
1097
- await this.sendMessage({
1098
- type: "job_update",
1099
- status: "failed",
1100
- job_id: jobId,
1101
- workflow_id: workflowId,
1102
- error: active.error
1103
- });
1104
- }
1105
- finally {
1106
- this.activeJobs.delete(jobId);
1107
- }
1108
- }
1109
- async streamJobMessages(active, executePromise) {
1110
- let terminalSeen = false;
1111
- let terminalWithResultSeen = false;
1112
- let outputUpdateSeen = false;
1113
- let finalOutputs = {};
1114
- await this.sendMessage({
1115
- type: "job_update",
1116
- status: "running",
1117
- job_id: active.jobId,
1118
- workflow_id: active.workflowId
1119
- });
1120
- void executePromise
1121
- .then((result) => {
1122
- active.status = result.status;
1123
- active.error = result.error;
1124
- finalOutputs = result.outputs ?? {};
1125
- })
1126
- .catch((err) => {
1127
- this.logError("job execution failed", err);
1128
- active.status = "failed";
1129
- active.error = formatSanitizedError(err);
1130
- })
1131
- .finally(() => {
1132
- active.finished = true;
1133
- });
1134
- while (!active.finished || active.context.hasMessages()) {
1135
- while (active.context.hasMessages()) {
1136
- const msg = active.context.popMessage();
1137
- if (!msg)
1138
- break;
1139
- const outbound = {
1140
- ...msg,
1141
- job_id: msg.job_id ?? active.jobId,
1142
- workflow_id: msg.workflow_id ??
1143
- active.workflowId
1144
- };
1145
- if (outbound.error !== undefined) {
1146
- outbound.error = formatSanitizedError(outbound.error);
1147
- }
1148
- if (outbound.type === "notification" &&
1149
- typeof outbound.content === "string") {
1150
- outbound.content = sanitizeLargeText(outbound.content);
1151
- }
1152
- if (outbound.type === "node_update" && outbound.status === "error") {
1153
- log.error("Node error", {
1154
- jobId: active.jobId,
1155
- nodeId: outbound.node_id,
1156
- error: outbound.error
1157
- });
1158
- }
1159
- else if (outbound.type === "job_update" &&
1160
- outbound.status === "failed") {
1161
- log.error("Job failed", {
1162
- jobId: active.jobId,
1163
- error: outbound.error
1164
- });
1165
- }
1166
- // Skip messages for constant/input nodes — they produce trivial
1167
- // outputs that don't need to be relayed to the frontend.
1168
- if (outbound.type === "output_update" ||
1169
- outbound.type === "node_update") {
1170
- const nodeId = String(outbound.node_id ?? "");
1171
- const graphNodes = active.graph.nodes ?? [];
1172
- const node = graphNodes.find((n) => n.id === nodeId);
1173
- const nodeType = typeof node?.type === "string" ? node.type : "";
1174
- // Skip constant and input nodes entirely
1175
- if (nodeType.startsWith("nodetool.constant.") ||
1176
- nodeType.startsWith("nodetool.input.")) {
1177
- continue;
1178
- }
1179
- // Only relay output_update for Output-type nodes
1180
- if (outbound.type === "output_update") {
1181
- if (!nodeType.includes("Output"))
1182
- continue;
1183
- outputUpdateSeen = true;
1184
- }
1185
- // Auto-save generated assets before normalization strips inline data
1186
- if (outbound.type === "node_update" &&
1187
- outbound.status === "completed" &&
1188
- outbound.result != null &&
1189
- this.getNodeMetadata) {
1190
- const meta = this.getNodeMetadata(nodeType);
1191
- if (meta?.auto_save_asset) {
1192
- try {
1193
- await autoSaveAssets(outbound.result, {
1194
- userId: this.userId ?? "1",
1195
- workflowId: active.workflowId,
1196
- jobId: active.jobId,
1197
- nodeId: String(outbound.node_id ?? ""),
1198
- storagePath: getAssetStoragePath()
1199
- });
1200
- }
1201
- catch (err) {
1202
- log.warn("autoSaveAssets error", { error: String(err) });
1203
- }
1204
- }
1205
- }
1206
- // Materialize binary assets to temp URLs before sending over WebSocket
1207
- if (outbound.type === "node_update" && outbound.result != null) {
1208
- outbound.result = await active.context.normalizeOutputValue(outbound.result);
1209
- }
1210
- if (outbound.type === "output_update" && outbound.value != null) {
1211
- outbound.value = await active.context.normalizeOutputValue(outbound.value);
1212
- }
1213
- }
1214
- await this.sendMessage(outbound);
1215
- if (outbound.type === "job_update") {
1216
- const status = String(outbound.status ?? "");
1217
- if (["completed", "failed", "cancelled", "error", "suspended"].includes(status)) {
1218
- terminalSeen = true;
1219
- if (outbound.result !== undefined) {
1220
- terminalWithResultSeen = true;
1221
- }
1222
- }
1223
- }
1224
- }
1225
- if (!active.finished) {
1226
- await new Promise((resolve) => setTimeout(resolve, 10));
1227
- }
1228
- }
1229
- if (!outputUpdateSeen && Object.keys(finalOutputs).length > 0) {
1230
- await this.sendOutputUpdates(active, finalOutputs);
1231
- }
1232
- log.info("Job completed", { jobId: active.jobId, status: active.status });
1233
- if (!terminalSeen ||
1234
- (!terminalWithResultSeen && Object.keys(finalOutputs).length > 0)) {
1235
- await this.sendMessage({
1236
- type: "job_update",
1237
- status: active.status,
1238
- job_id: active.jobId,
1239
- workflow_id: active.workflowId,
1240
- error: active.error,
1241
- result: { outputs: finalOutputs }
1242
- });
1243
- }
1244
- // Persist final job status
1245
- try {
1246
- const job = (await Job.get(active.jobId));
1247
- if (job) {
1248
- if (active.status === "completed") {
1249
- job.markCompleted();
1250
- }
1251
- else if (active.status === "failed") {
1252
- job.markFailed(active.error ?? "Unknown error");
1253
- }
1254
- else if (active.status === "cancelled") {
1255
- job.markCancelled();
1256
- }
1257
- await job.save();
1258
- }
1259
- }
1260
- catch (error) {
1261
- this.logError("job persistence (final status) failed", error);
1262
- }
1263
- this.activeJobs.delete(active.jobId);
1264
- }
1265
- async reconnectJob(jobId, workflowId) {
1266
- const active = this.activeJobs.get(jobId);
1267
- if (!active) {
1268
- throw new Error(`Job ${jobId} not found`);
1269
- }
1270
- await this.sendMessage({
1271
- type: "job_update",
1272
- status: active.status,
1273
- job_id: jobId,
1274
- workflow_id: workflowId ?? active.workflowId
1275
- });
1276
- for (const status of Object.values(active.context.getNodeStatuses())) {
1277
- await this.sendMessage({
1278
- ...status,
1279
- job_id: jobId,
1280
- workflow_id: workflowId ?? active.workflowId
1281
- });
1282
- }
1283
- for (const status of Object.values(active.context.getEdgeStatuses())) {
1284
- await this.sendMessage({
1285
- ...status,
1286
- job_id: jobId,
1287
- workflow_id: workflowId ?? active.workflowId
1288
- });
1289
- }
1290
- }
1291
- async resumeJob(jobId, workflowId) {
1292
- await this.reconnectJob(jobId, workflowId);
1293
- }
1294
- async cancelJob(jobId, workflowId) {
1295
- if (!jobId) {
1296
- return { error: "No job_id provided" };
1297
- }
1298
- const active = this.activeJobs.get(jobId);
1299
- if (!active) {
1300
- return {
1301
- error: "Job not found or already completed",
1302
- job_id: jobId,
1303
- workflow_id: workflowId ?? ""
1304
- };
1305
- }
1306
- if (active.comfyHandle) {
1307
- active.comfyHandle.cancel();
1308
- }
1309
- else if (active.runner) {
1310
- active.runner.cancel();
1311
- }
1312
- active.status = "cancelled";
1313
- // Do NOT set active.finished = true here. Let the runner's cancellation
1314
- // propagate through executePromise's .finally() callback so that
1315
- // streamJobMessages can drain remaining messages and persist job state.
1316
- return {
1317
- message: "Job cancellation requested",
1318
- job_id: jobId,
1319
- workflow_id: workflowId ?? active.workflowId ?? ""
1320
- };
1321
- }
1322
- getStatus(jobId) {
1323
- if (jobId) {
1324
- const active = this.activeJobs.get(jobId);
1325
- if (!active) {
1326
- return { status: "not_found", job_id: jobId };
1327
- }
1328
- return {
1329
- status: active.status,
1330
- job_id: active.jobId,
1331
- workflow_id: active.workflowId
1332
- };
1333
- }
1334
- return {
1335
- active_jobs: Array.from(this.activeJobs.values()).map((job) => ({
1336
- job_id: job.jobId,
1337
- workflow_id: job.workflowId,
1338
- status: job.status
1339
- }))
1340
- };
1341
- }
1342
- async clearModels() {
1343
- return {
1344
- message: "Model clearing is managed by provider implementations in TS runtime"
1345
- };
1346
- }
1347
- async ensureThreadExists(threadId) {
1348
- const userId = this.userId ?? "1";
1349
- if (!threadId) {
1350
- const thread = await Thread.create({ user_id: userId, title: "" });
1351
- return thread.id;
1352
- }
1353
- const existing = await Thread.find(userId, threadId);
1354
- if (existing)
1355
- return existing.id;
1356
- const thread = await Thread.create({
1357
- id: threadId,
1358
- user_id: userId,
1359
- title: ""
1360
- });
1361
- return thread.id;
1362
- }
1363
- dbMessageToProviderMessage(m) {
1364
- const role = m.role;
1365
- // Filter out non-standard roles (e.g. "agent_execution") that providers can't handle
1366
- if (!role || !["user", "assistant", "system", "tool"].includes(role)) {
1367
- return null;
1368
- }
1369
- const rawContent = Array.isArray(m.content)
1370
- ? resolveContentForProvider(m.content)
1371
- : m.content;
1372
- return {
1373
- role,
1374
- content: rawContent ?? "",
1375
- toolCallId: typeof m.tool_call_id === "string" ? m.tool_call_id : null,
1376
- toolCalls: Array.isArray(m.tool_calls)
1377
- ? m.tool_calls
1378
- : null,
1379
- threadId: m.thread_id
1380
- };
1381
- }
1382
- /**
1383
- * Save a message dict to the database.
1384
- * Mirrors Python's _save_message_to_db_async: pops id, type, user_id before create.
1385
- */
1386
- async saveMessageToDb(messageData) {
1387
- const data = { ...messageData };
1388
- delete data.id;
1389
- delete data.type;
1390
- const threadId = typeof data.thread_id === "string" ? data.thread_id : "";
1391
- delete data.thread_id;
1392
- const userId = this.userId ?? "1";
1393
- delete data.user_id;
1394
- await Message.create({
1395
- thread_id: threadId,
1396
- user_id: userId,
1397
- ...data
1398
- });
1399
- }
1400
- /**
1401
- * Recursively process tool results, handling asset-like objects.
1402
- * Mirrors Python's RegularChatProcessor._process_tool_result().
1403
- *
1404
- * - Asset-like objects (have type + uri/data): materialized via storage
1405
- * - Date/datetime: converted to ISO string
1406
- * - Arrays/objects: recursed into
1407
- * - Primitives: returned as-is
1408
- */
1409
- async processToolResult(obj, ctx) {
1410
- if (obj === null || obj === undefined)
1411
- return obj;
1412
- // Asset-like objects: { type: "image"|"audio"|"video"|..., uri?: string, data?: ... }
1413
- if (typeof obj === "object" && !Array.isArray(obj)) {
1414
- const record = obj;
1415
- // Check if it's an asset-like object (has type + uri or data)
1416
- if ("type" in record &&
1417
- ("uri" in record || "data" in record || "asset_id" in record)) {
1418
- // Use ProcessingContext's normalizeOutputValue to handle asset materialization
1419
- return ctx.normalizeOutputValue(record, "storage_url");
1420
- }
1421
- // Date objects
1422
- if (obj instanceof Date) {
1423
- return obj.toISOString();
1424
- }
1425
- // Regular objects — recurse into values
1426
- const result = {};
1427
- for (const [key, value] of Object.entries(record)) {
1428
- result[key] = await this.processToolResult(value, ctx);
1429
- }
1430
- return result;
1431
- }
1432
- // Arrays — recurse into items
1433
- if (Array.isArray(obj)) {
1434
- return Promise.all(obj.map((item) => this.processToolResult(item, ctx)));
1435
- }
1436
- // Uint8Array/Buffer — store as asset
1437
- if (obj instanceof Uint8Array) {
1438
- if (!ctx.storage)
1439
- return obj;
1440
- const key = `assets/${randomUUID()}.bin`;
1441
- const uri = await ctx.storage.store(key, obj);
1442
- return { type: "asset", uri };
1443
- }
1444
- // Primitives
1445
- return obj;
1446
- }
1447
- /**
1448
- * Query vector store collections and return concatenated context string.
1449
- * Mirrors Python's RegularChatProcessor._query_collections().
1450
- */
1451
- async queryCollections(collections, queryText, nResults = 5) {
1452
- if (!collections.length || !queryText)
1453
- return "";
1454
- try {
1455
- const { getVecStore } = await import("@nodetool-ai/vectorstore");
1456
- const store = await getVecStore();
1457
- const allResults = [];
1458
- for (const collectionName of collections) {
1459
- try {
1460
- const collection = await store.getCollection({
1461
- name: collectionName
1462
- });
1463
- const results = await collection.query({
1464
- queryTexts: [queryText],
1465
- nResults,
1466
- include: ["documents", "metadatas"]
1467
- });
1468
- if (results.documents?.[0]?.length) {
1469
- let collectionResults = `\n\n### Results from ${collectionName}:\n`;
1470
- for (const doc of results.documents[0]) {
1471
- if (!doc)
1472
- continue;
1473
- const preview = doc.length > 200 ? `${doc.slice(0, 200)}...` : doc;
1474
- collectionResults += `\n- ${preview}`;
1475
- }
1476
- allResults.push(collectionResults);
1477
- }
1478
- }
1479
- catch (err) {
1480
- log.warn("Collection query failed", {
1481
- collection: collectionName,
1482
- error: err instanceof Error ? err.message : String(err)
1483
- });
1484
- }
1485
- }
1486
- return allResults.join("\n");
1487
- }
1488
- catch (err) {
1489
- log.warn("Vector store init failed", {
1490
- error: err instanceof Error ? err.message : String(err)
1491
- });
1492
- return "";
1493
- }
1494
- }
1495
- /**
1496
- * Add collection context as a system message before the last user message.
1497
- * Mirrors Python's RegularChatProcessor._add_collection_context().
1498
- */
1499
- addCollectionContext(messages, collectionContext) {
1500
- // Find the last user message index
1501
- let lastUserIndex = -1;
1502
- for (let i = messages.length - 1; i >= 0; i--) {
1503
- if (messages[i].role === "user") {
1504
- lastUserIndex = i;
1505
- break;
1506
- }
1507
- }
1508
- if (lastUserIndex >= 0) {
1509
- const contextMessage = {
1510
- role: "system",
1511
- content: `Context from knowledge base:\n${collectionContext}`,
1512
- toolCallId: null,
1513
- toolCalls: null,
1514
- threadId: null
1515
- };
1516
- return [
1517
- ...messages.slice(0, lastUserIndex),
1518
- contextMessage,
1519
- ...messages.slice(lastUserIndex)
1520
- ];
1521
- }
1522
- return messages;
1523
- }
1524
- /**
1525
- * Handle an incoming chat message.
1526
- *
1527
- * Mirrors Python's full 3-layer flow:
1528
- * handle_chat_message → handle_message_impl → process_messages
1529
- * → _run_processor + RegularChatProcessor.process()
1530
- *
1531
- * The processor sends messages to a queue. _run_processor reads them:
1532
- * - type === "message" → persist to DB AND forward to client
1533
- * - anything else → forward to client only
1534
- *
1535
- * RegularChatProcessor.process():
1536
- * 1. Prepend system prompt if first message isn't system role
1537
- * 2. while True: messages_to_send = chat_history + unprocessed_messages
1538
- * 3. Stream chunks (type: "chunk") — forwarded to client (not persisted)
1539
- * 4. On tool call: build assistant Message + tool result Message (type: "message")
1540
- * → persisted to DB AND forwarded to client
1541
- * 5. If unprocessed_messages empty, break
1542
- * 6. Send done chunk + final assistant Message
1543
- */
1544
- async handleChatMessage(data, requestSeq) {
1545
- const threadId = await this.ensureThreadExists(typeof data.thread_id === "string" ? data.thread_id : undefined);
1546
- data.thread_id = threadId;
1547
- // Apply defaults — matches Python's handle_chat_message
1548
- if (!data.model)
1549
- data.model = this.defaultModel;
1550
- if (!data.provider)
1551
- data.provider = this.defaultProvider;
1552
- const providerId = data.provider;
1553
- const model = data.model;
1554
- const workflowId = typeof data.workflow_id === "string" ? data.workflow_id : null;
1555
- const userId = this.userId ?? "1";
1556
- log.debug("Chat message", { threadId, model, provider: providerId });
1557
- // Save user message to DB — matches Python's _save_message_to_db_async(data)
1558
- await this.saveMessageToDb(data);
1559
- if (requestSeq !== undefined && requestSeq !== this.chatRequestSeq)
1560
- return;
1561
- if (!this.resolveProvider) {
1562
- await this.sendMessage({
1563
- type: "error",
1564
- message: "No provider resolver configured",
1565
- thread_id: threadId
1566
- });
1567
- return;
1568
- }
1569
- // Route to workflow processor when workflow_target or workflow_id is set — matches Python's handle_message_impl
1570
- // This check comes BEFORE agent_mode, matching Python's routing priority.
1571
- const workflowTarget = typeof data.workflow_target === "string" ? data.workflow_target : null;
1572
- if (workflowTarget === "workflow" || workflowId) {
1573
- await this.handleWorkflowMessage(data, requestSeq);
1574
- return;
1575
- }
1576
- // Route to media generation when the client requests a text-to-image or
1577
- // text-to-video turn. The composer attaches a `media_generation` field
1578
- // with mode + params; when mode is a media mode we invoke the provider's
1579
- // textToImage / textToVideo instead of a regular LLM round and return an
1580
- // assistant message containing MessageImageContent / MessageVideoContent.
1581
- const mediaGeneration = data.media_generation && typeof data.media_generation === "object"
1582
- ? data.media_generation
1583
- : null;
1584
- if (mediaGeneration &&
1585
- typeof mediaGeneration.mode === "string" &&
1586
- mediaGeneration.mode !== "chat") {
1587
- await this.handleMediaGenerationMessage(data, mediaGeneration, requestSeq);
1588
- return;
1589
- }
1590
- // Route to agent mode if requested — matches Python's handle_message_impl
1591
- const agentMode = data.agent_mode === true || data.agent_mode === "true";
1592
- if (agentMode) {
1593
- await this.handleAgentMessage(data, requestSeq);
1594
- return;
1595
- }
1596
- // Load history from DB, filter out agent_execution — matches Python's get_chat_history_from_db
1597
- const [dbMessages] = await Message.paginate(threadId, { limit: 1000 });
1598
- const chatHistory = [];
1599
- for (const m of dbMessages) {
1600
- const pm = this.dbMessageToProviderMessage(m);
1601
- if (pm)
1602
- chatHistory.push(pm);
1603
- }
1604
- const provider = await this.resolveProvider(providerId, userId);
1605
- // Extract tool names from raw tool data — supports both string[] and object[]
1606
- const rawTools = Array.isArray(data.tools) ? data.tools : [];
1607
- const toolNames = rawTools
1608
- .map((t) => {
1609
- if (typeof t === "string")
1610
- return t;
1611
- const tool = t;
1612
- return typeof tool.name === "string" ? tool.name : "";
1613
- })
1614
- .filter((n) => n.length > 0);
1615
- log.info("Chat tools from client", {
1616
- rawToolCount: rawTools.length,
1617
- rawToolTypes: rawTools.map((t) => typeof t),
1618
- toolNames
1619
- });
1620
- // Resolve server-side Tool instances for execution
1621
- let serverTools = [];
1622
- if (toolNames.length > 0 && this.resolveTools) {
1623
- serverTools = await this.resolveTools(toolNames, userId);
1624
- }
1625
- const serverToolMap = new Map(serverTools.map((t) => [t.name, t]));
1626
- log.info("Resolved server tools", {
1627
- requested: toolNames,
1628
- resolved: serverTools.map((t) => t.name),
1629
- hasResolveTools: !!this.resolveTools
1630
- });
1631
- // Build provider-format tool schemas from resolved Tool instances + client tools
1632
- const providerToolSchemas = serverTools.map((t) => t.toProviderTool());
1633
- // Only include client tools (ui_*) when a workflow is active
1634
- const clientToolNames = workflowId
1635
- ? Object.keys(this.clientToolsManifest)
1636
- : [];
1637
- if (workflowId) {
1638
- for (const [name, manifest] of Object.entries(this.clientToolsManifest)) {
1639
- providerToolSchemas.push({
1640
- name,
1641
- description: typeof manifest.description === "string"
1642
- ? manifest.description
1643
- : undefined,
1644
- inputSchema: typeof manifest.inputSchema === "object"
1645
- ? manifest.inputSchema
1646
- : undefined
1647
- });
1648
- }
1649
- }
1650
- log.info("Provider tool schemas", {
1651
- serverToolCount: serverTools.length,
1652
- clientToolCount: clientToolNames.length,
1653
- clientTools: clientToolNames,
1654
- totalSchemas: providerToolSchemas.length,
1655
- schemaNames: providerToolSchemas.map((t) => t.name)
1656
- });
1657
- // Create a processing context for tool execution
1658
- const chatWorkspaceDir = workflowId && this.workspaceResolver
1659
- ? await this.workspaceResolver(workflowId, userId)
1660
- : tmpdir();
1661
- const ctx = createRuntimeContext({
1662
- jobId: randomUUID(),
1663
- userId,
1664
- workspaceDir: chatWorkspaceDir
1665
- });
1666
- // Prepend system prompt if first message isn't system role — matches Python
1667
- if (chatHistory.length === 0 || chatHistory[0].role !== "system") {
1668
- chatHistory.unshift({
1669
- role: "system",
1670
- content: REGULAR_SYSTEM_PROMPT,
1671
- toolCallId: null,
1672
- toolCalls: null,
1673
- threadId: null
1674
- });
1675
- }
1676
- // Query collections for RAG context — matches Python's _query_collections()
1677
- const collections = Array.isArray(data.collections)
1678
- ? data.collections.filter((c) => typeof c === "string")
1679
- : [];
1680
- const userContent = this.extractTextContent(data.content);
1681
- let collectionContext = "";
1682
- if (collections.length > 0 && userContent) {
1683
- collectionContext = await this.queryCollections(collections, userContent);
1684
- if (collectionContext) {
1685
- log.debug("Retrieved collection context", {
1686
- chars: collectionContext.length
1687
- });
1688
- }
1689
- }
1690
- let content = "";
1691
- let unprocessedMessages = [];
1692
- // Tool execution loop — mirrors Python's RegularChatProcessor.process()
1693
- const MAX_TOOL_ROUNDS = 10;
1694
- let toolRound = 0;
1695
- const shouldIncludeTools = true;
1696
- try {
1697
- while (true) {
1698
- if (requestSeq !== undefined && requestSeq !== this.chatRequestSeq)
1699
- return;
1700
- let messagesToSend = [...chatHistory, ...unprocessedMessages];
1701
- unprocessedMessages = [];
1702
- // Add collection context on first iteration — matches Python
1703
- if (collectionContext) {
1704
- messagesToSend = this.addCollectionContext(messagesToSend, collectionContext);
1705
- collectionContext = ""; // Clear after first use
1706
- }
1707
- const stream = provider.generateMessagesTraced({
1708
- messages: messagesToSend,
1709
- model,
1710
- tools: shouldIncludeTools && providerToolSchemas.length > 0
1711
- ? providerToolSchemas
1712
- : undefined,
1713
- threadId,
1714
- onToolCall: shouldIncludeTools && providerToolSchemas.length > 0
1715
- ? async (name, args) => {
1716
- let toolResult;
1717
- const serverTool = serverToolMap.get(name);
1718
- if (serverTool) {
1719
- try {
1720
- toolResult = await serverTool.process(ctx, args);
1721
- }
1722
- catch (err) {
1723
- const errMsg = err instanceof Error ? err.message : String(err);
1724
- toolResult = { error: errMsg };
1725
- }
1726
- }
1727
- else if (this.clientToolsManifest[name]) {
1728
- // Client-side tool — send to UI via ToolBridge
1729
- const callId = `call_${Date.now()}`;
1730
- await this.sendMessage({
1731
- type: "tool_call",
1732
- thread_id: threadId,
1733
- tool_call_id: callId,
1734
- name,
1735
- args
1736
- });
1737
- const clientResult = await this.toolBridge.createWaiter(callId, 300_000);
1738
- toolResult =
1739
- clientResult.result ?? clientResult.content ?? clientResult;
1740
- }
1741
- else {
1742
- toolResult = { error: `Tool "${name}" not available` };
1743
- }
1744
- const processed = await this.processToolResult(toolResult, ctx);
1745
- return typeof processed === "string"
1746
- ? processed
1747
- : JSON.stringify(processed);
1748
- }
1749
- : undefined
1750
- });
1751
- const pendingToolCalls = [];
1752
- for await (const item of stream) {
1753
- if (requestSeq !== undefined && requestSeq !== this.chatRequestSeq)
1754
- return;
1755
- if ("type" in item && item.type === "chunk") {
1756
- // --- Text chunk --- forward to client (not persisted)
1757
- const chunk = item;
1758
- const text = chunk.content ?? "";
1759
- content += text;
1760
- // Set thread_id if not already set — matches Python
1761
- if (!chunk.thread_id)
1762
- chunk.thread_id = threadId;
1763
- await this.sendMessage({ ...chunk });
1764
- }
1765
- else if ("name" in item && "id" in item) {
1766
- // --- Tool call from provider (collect, don't execute yet) ---
1767
- const tc = item;
1768
- log.info("Tool call", { tool: tc.name, args: tc.args });
1769
- pendingToolCalls.push({ tc });
1770
- }
1771
- }
1772
- // Build ONE assistant message with ALL tool calls (OpenAI requires this)
1773
- if (pendingToolCalls.length > 0) {
1774
- const allToolCalls = pendingToolCalls.map(({ tc }) => ({
1775
- id: tc.id,
1776
- name: tc.name,
1777
- args: tc.args,
1778
- result: null
1779
- }));
1780
- const assistantMsgData = {
1781
- type: "message",
1782
- role: "assistant",
1783
- content: content || null,
1784
- tool_calls: allToolCalls,
1785
- thread_id: threadId,
1786
- workflow_id: workflowId,
1787
- provider: providerId,
1788
- model
1789
- };
1790
- await this.saveMessageToDb(assistantMsgData);
1791
- await this.sendMessage(assistantMsgData);
1792
- unprocessedMessages.push({
1793
- role: "assistant",
1794
- content: content || null,
1795
- toolCalls: pendingToolCalls.map(({ tc }) => ({
1796
- id: tc.id,
1797
- name: tc.name,
1798
- args: tc.args
1799
- })),
1800
- toolCallId: null,
1801
- threadId
1802
- });
1803
- }
1804
- // Phase 2: Execute all collected tool calls in parallel
1805
- if (pendingToolCalls.length > 0) {
1806
- const executeOne = async ({ tc }) => {
1807
- try {
1808
- let toolResult;
1809
- const serverTool = serverToolMap.get(tc.name);
1810
- if (serverTool) {
1811
- try {
1812
- toolResult = await serverTool.process(ctx, tc.args);
1813
- }
1814
- catch (err) {
1815
- const errMsg = err instanceof Error ? err.message : String(err);
1816
- log.error("Tool execution failed", {
1817
- tool: tc.name,
1818
- error: errMsg
1819
- });
1820
- toolResult = { error: errMsg };
1821
- }
1822
- }
1823
- else if (this.clientToolsManifest[tc.name]) {
1824
- // Client-side tool via ToolBridge
1825
- await this.sendMessage({
1826
- type: "tool_call",
1827
- thread_id: threadId,
1828
- tool_call_id: tc.id,
1829
- name: tc.name,
1830
- args: tc.args
1831
- });
1832
- const clientResult = await this.toolBridge.createWaiter(tc.id, 300_000);
1833
- toolResult =
1834
- clientResult.result ?? clientResult.content ?? clientResult;
1835
- }
1836
- else {
1837
- toolResult = { error: `Tool "${tc.name}" not available` };
1838
- }
1839
- // Process tool result — handle asset-like objects, dates, etc.
1840
- const processedResult = await this.processToolResult(toolResult, ctx);
1841
- const toolResultJson = JSON.stringify(processedResult);
1842
- return { tc, toolResultJson };
1843
- }
1844
- catch (err) {
1845
- const errMsg = err instanceof Error ? err.message : String(err);
1846
- log.error("Tool executeOne failed", {
1847
- tool: tc.name,
1848
- error: errMsg
1849
- });
1850
- return { tc, toolResultJson: JSON.stringify({ error: errMsg }) };
1851
- }
1852
- };
1853
- const results = await Promise.all(pendingToolCalls.map(executeOne));
1854
- for (const { tc, toolResultJson } of results) {
1855
- // Build tool result Message
1856
- const toolMsgData = {
1857
- type: "message",
1858
- role: "tool",
1859
- tool_call_id: tc.id,
1860
- name: tc.name,
1861
- content: toolResultJson,
1862
- thread_id: threadId,
1863
- workflow_id: workflowId,
1864
- provider: providerId,
1865
- model
1866
- };
1867
- await this.saveMessageToDb(toolMsgData);
1868
- await this.sendMessage(toolMsgData);
1869
- // Add tool result to unprocessed for next provider round
1870
- unprocessedMessages.push({
1871
- role: "tool",
1872
- content: toolResultJson,
1873
- toolCallId: tc.id,
1874
- toolCalls: null,
1875
- threadId
1876
- });
1877
- }
1878
- }
1879
- // If no unprocessed messages, generation is complete — matches Python's break condition
1880
- if (unprocessedMessages.length === 0) {
1881
- break;
1882
- }
1883
- // Reset content for next tool round so the final message only contains
1884
- // text from the last generation pass (prior rounds were already streamed).
1885
- content = "";
1886
- toolRound++;
1887
- if (toolRound >= MAX_TOOL_ROUNDS) {
1888
- log.warn("Max tool rounds reached, stopping tool loop", {
1889
- rounds: toolRound
1890
- });
1891
- break;
1892
- }
1893
- log.debug("Unprocessed messages", {
1894
- count: unprocessedMessages.length,
1895
- round: toolRound
1896
- });
1897
- }
1898
- // Log provider call for cost tracking — matches Python's _log_provider_call()
1899
- await this._logProviderCall(userId, provider, providerId, model, workflowId);
1900
- // Signal completion — matches Python's done chunk + final assistant Message
1901
- await this.sendMessage({
1902
- type: "chunk",
1903
- content: "",
1904
- done: true,
1905
- thread_id: threadId
1906
- });
1907
- // Final assistant message — persisted and forwarded (type: "message")
1908
- const finalMsgData = {
1909
- type: "message",
1910
- role: "assistant",
1911
- content: content || null,
1912
- thread_id: threadId,
1913
- workflow_id: workflowId,
1914
- provider: providerId,
1915
- model
1916
- };
1917
- await this.saveMessageToDb(finalMsgData);
1918
- await this.sendMessage(finalMsgData);
1919
- log.debug("Chat complete", { threadId, chars: content.length });
1920
- }
1921
- catch (err) {
1922
- const errMsg = err instanceof Error ? err.message : String(err);
1923
- log.error("Chat processing error", { threadId, error: errMsg });
1924
- // Detect error type — matches Python's separate ConnectError / HTTPStatusError handlers
1925
- let errorType = "error";
1926
- let statusCode;
1927
- let formattedMsg = errMsg;
1928
- // Connection errors (ECONNREFUSED, ENOTFOUND, etc.)
1929
- if (errMsg.includes("ECONNREFUSED") ||
1930
- errMsg.includes("ENOTFOUND") ||
1931
- errMsg.includes("fetch failed") ||
1932
- errMsg.includes("nodename nor servname")) {
1933
- errorType = "connection_error";
1934
- if (errMsg.includes("ENOTFOUND") ||
1935
- errMsg.includes("nodename nor servname")) {
1936
- formattedMsg =
1937
- "Connection error: Unable to resolve hostname. Please check your network connection and API endpoint configuration.";
1938
- }
1939
- else {
1940
- formattedMsg = `Connection error: ${errMsg}`;
1941
- }
1942
- }
1943
- // HTTP status errors — check for status code in error
1944
- else if (err && typeof err === "object" && "status" in err) {
1945
- const status = err.status;
1946
- errorType = "http_status_error";
1947
- statusCode = status;
1948
- // Try to extract error message from response body
1949
- let bodyMsg = null;
1950
- try {
1951
- if ("body" in err || "response" in err) {
1952
- const body = err.body ?? err.response;
1953
- if (body && typeof body === "object" && "error" in body) {
1954
- const errorDetail = body.error;
1955
- if (typeof errorDetail === "object" &&
1956
- errorDetail &&
1957
- "message" in errorDetail) {
1958
- bodyMsg = String(errorDetail.message);
1959
- }
1960
- }
1961
- }
1962
- }
1963
- catch {
1964
- // Intentional: best-effort extraction of error message from response body
1965
- }
1966
- if (bodyMsg) {
1967
- formattedMsg = bodyMsg;
1968
- }
1969
- else if (status === 400) {
1970
- formattedMsg = `Bad request: ${errMsg}`;
1971
- }
1972
- else if (status === 401) {
1973
- formattedMsg = "Authentication failed: Invalid API key or token";
1974
- }
1975
- else if (status === 403) {
1976
- formattedMsg =
1977
- "Access forbidden: You don't have permission for this resource";
1978
- }
1979
- else if (status === 404) {
1980
- formattedMsg = "Not found: The requested resource was not found";
1981
- }
1982
- else if (status === 429) {
1983
- formattedMsg = "Rate limited: Too many requests, please slow down";
1984
- }
1985
- else if (status >= 500) {
1986
- formattedMsg = `Server error (${status}): The service is temporarily unavailable`;
1987
- }
1988
- else {
1989
- formattedMsg = `HTTP error (${status}): ${errMsg}`;
1990
- }
1991
- }
1992
- await this.sendMessage({
1993
- type: "error",
1994
- message: formattedMsg,
1995
- error_type: errorType,
1996
- ...(statusCode !== undefined ? { status_code: statusCode } : {}),
1997
- thread_id: threadId,
1998
- workflow_id: workflowId
1999
- });
2000
- // Signal completion even on error — matches Python
2001
- await this.sendMessage({
2002
- type: "chunk",
2003
- content: "",
2004
- done: true,
2005
- thread_id: threadId
2006
- });
2007
- const errorMsgData = {
2008
- type: "message",
2009
- role: "assistant",
2010
- content: errorType === "connection_error"
2011
- ? `I encountered a connection error: ${formattedMsg}. Please check your network connection and try again.`
2012
- : errorType === "http_status_error"
2013
- ? `I encountered an API error (HTTP ${statusCode}): ${formattedMsg}`
2014
- : `I encountered an error: ${formattedMsg}`,
2015
- thread_id: threadId,
2016
- workflow_id: workflowId,
2017
- provider: providerId,
2018
- model
2019
- };
2020
- await this.saveMessageToDb(errorMsgData);
2021
- await this.sendMessage(errorMsgData);
2022
- }
2023
- }
2024
- /**
2025
- * Log a provider call for cost tracking — mirrors Python's _log_provider_call().
2026
- * Best-effort: never throws, logs warnings on failure.
2027
- */
2028
- async _logProviderCall(userId, provider, providerId, model, workflowId) {
2029
- if (!providerId || !model) {
2030
- log.warn("Cannot log provider call: missing provider or model");
2031
- return;
2032
- }
2033
- try {
2034
- const cost = provider.cost;
2035
- await Prediction.create({
2036
- user_id: userId,
2037
- provider: providerId,
2038
- model,
2039
- cost,
2040
- workflow_id: workflowId,
2041
- status: "completed",
2042
- node_id: ""
2043
- });
2044
- log.debug("Logged provider call", { provider: providerId, model, cost });
2045
- }
2046
- catch (err) {
2047
- if (err instanceof TypeError || err instanceof ReferenceError) {
2048
- log.warn("Failed to log provider call due to invalid data", {
2049
- error: err instanceof Error ? err.message : String(err)
2050
- });
2051
- }
2052
- else {
2053
- log.error("Unexpected error logging provider call", {
2054
- error: err instanceof Error ? err.message : String(err)
2055
- });
2056
- }
2057
- }
2058
- }
2059
- /**
2060
- * Detect message input node names from a workflow graph.
2061
- * Mirrors Python's WorkflowMessageProcessor._detect_message_input_names().
2062
- *
2063
- * Scans graph nodes for types ending in .MessageInput / .MessageListInput
2064
- * and returns their data.name values.
2065
- */
2066
- detectMessageInputNames(graph) {
2067
- let messageName = null;
2068
- let messagesName = null;
2069
- for (const node of graph.nodes) {
2070
- const nodeType = typeof node.type === "string" ? node.type : "";
2071
- const data = typeof node.data === "object" && node.data !== null
2072
- ? node.data
2073
- : {};
2074
- const nodeName = typeof data.name === "string" ? data.name.trim() : "";
2075
- if (!nodeName)
2076
- continue;
2077
- if (messageName === null &&
2078
- (nodeType === "nodetool.input.MessageInput" ||
2079
- nodeType.endsWith(".MessageInput"))) {
2080
- messageName = nodeName;
2081
- }
2082
- if (messagesName === null &&
2083
- (nodeType === "nodetool.input.MessageListInput" ||
2084
- nodeType.endsWith(".MessageListInput"))) {
2085
- messagesName = nodeName;
2086
- }
2087
- }
2088
- return { messageName, messagesName };
2089
- }
2090
- /**
2091
- * Convert workflow result dict into a response message with typed content.
2092
- * Mirrors Python's WorkflowMessageProcessor._create_response_message().
2093
- *
2094
- * Converts outputs to MessageContent items:
2095
- * - string → { type: "text", text }
2096
- * - list → { type: "text", text: joined }
2097
- * - dict with type "image"/"video"/"audio" → media content
2098
- * - other → { type: "text", text: stringified }
2099
- */
2100
- createWorkflowResponseContent(result) {
2101
- const content = [];
2102
- for (const [, value] of Object.entries(result)) {
2103
- if (value === null || value === undefined)
2104
- continue;
2105
- if (typeof value === "string") {
2106
- content.push({ type: "text", text: value });
2107
- }
2108
- else if (Array.isArray(value)) {
2109
- content.push({ type: "text", text: value.map(String).join(" ") });
2110
- }
2111
- else if (typeof value === "object") {
2112
- const obj = value;
2113
- const assetType = typeof obj.type === "string" ? obj.type : "";
2114
- if (assetType === "image") {
2115
- content.push({
2116
- type: "image",
2117
- image: { uri: obj.uri, asset_id: obj.asset_id, data: obj.data }
2118
- });
2119
- }
2120
- else if (assetType === "video") {
2121
- content.push({
2122
- type: "video",
2123
- video: { uri: obj.uri, asset_id: obj.asset_id, data: obj.data }
2124
- });
2125
- }
2126
- else if (assetType === "audio") {
2127
- content.push({
2128
- type: "audio",
2129
- audio: { uri: obj.uri, asset_id: obj.asset_id, data: obj.data }
2130
- });
2131
- }
2132
- else {
2133
- content.push({ type: "text", text: JSON.stringify(obj) });
2134
- }
2135
- }
2136
- else {
2137
- content.push({ type: "text", text: String(value) });
2138
- }
2139
- }
2140
- if (content.length === 0) {
2141
- content.push({ type: "text", text: "Workflow completed successfully." });
2142
- }
2143
- return content;
2144
- }
2145
- /**
2146
- * Handle a chat message that targets a workflow.
2147
- *
2148
- * Mirrors Python's process_messages_for_workflow → WorkflowMessageProcessor/
2149
- * ChatWorkflowMessageProcessor flow:
2150
- * 1. Load workflow from DB
2151
- * 2. Detect message input node names from graph
2152
- * 3. Prepare params (serialized message + history)
2153
- * 4. Run workflow via WorkflowRunner
2154
- * 5. Stream events (job_update, node_update, output_update)
2155
- * 6. Collect output_update results
2156
- * 7. Send done chunk + response message with typed content
2157
- */
2158
- /**
2159
- * Handle a chat_message with a `media_generation` payload by invoking the
2160
- * selected provider's textToImage / textToVideo API, storing the resulting
2161
- * asset(s), and returning them to the client as an assistant `Message`
2162
- * whose `content` is an array of `MessageImageContent` / `MessageVideoContent`
2163
- * blocks.
2164
- *
2165
- * The generated bytes are persisted via `ctx.storage.store()` so each
2166
- * output receives a stable URI the client can resolve as a server asset.
2167
- * The `media_generation` echo on the assistant message lets the UI render
2168
- * the generation header (model, variation count, resolution, etc.) in the
2169
- * conversation stream.
2170
- */
2171
- async handleMediaGenerationMessage(data, mediaGeneration, requestSeq) {
2172
- const threadId = typeof data.thread_id === "string" ? data.thread_id : "";
2173
- const workflowId = typeof data.workflow_id === "string" ? data.workflow_id : null;
2174
- const userId = this.userId ?? "1";
2175
- const mode = String(mediaGeneration.mode ?? "");
2176
- const providerId = String(mediaGeneration.provider ?? data.provider ?? this.defaultProvider);
2177
- const modelId = String(mediaGeneration.model ?? data.model ?? this.defaultModel);
2178
- const prompt = this.extractTextContent(data.content);
2179
- log.info("Media generation", {
2180
- threadId,
2181
- mode,
2182
- provider: providerId,
2183
- model: modelId,
2184
- promptLen: prompt.length
2185
- });
2186
- if (!this.resolveProvider) {
2187
- await this.sendMessage({
2188
- type: "error",
2189
- message: "No provider resolver configured",
2190
- thread_id: threadId
2191
- });
2192
- return;
2193
- }
2194
- if (!modelId || modelId === "undefined") {
2195
- await this.sendMessage({
2196
- type: "error",
2197
- message: `Please select a ${mode} model before generating`,
2198
- thread_id: threadId
2199
- });
2200
- return;
2201
- }
2202
- if (!prompt) {
2203
- await this.sendMessage({
2204
- type: "error",
2205
- message: "Please enter a prompt",
2206
- thread_id: threadId
2207
- });
2208
- return;
2209
- }
2210
- if (requestSeq !== undefined && requestSeq !== this.chatRequestSeq)
2211
- return;
2212
- const provider = await this.resolveProvider(providerId, userId);
2213
- // Wire up progress forwarding so provider.emitMessage() reaches the client.
2214
- provider.setMessageEmitter((msg) => void this.sendMessage(msg));
2215
- // Store generated media as a proper Asset record and return the
2216
- // asset ID. The DB message stores only `asset_id` — URLs are
2217
- // resolved at serve time by resolveContentUrls / sendMessage.
2218
- const storagePath = getAssetStoragePath();
2219
- const storeMediaAsset = async (bytes, contentType, ext) => {
2220
- const { join } = await import("node:path");
2221
- const { writeFile: fsWrite, mkdir: fsMkdir } = await import("node:fs/promises");
2222
- const asset = new Asset({
2223
- user_id: userId,
2224
- workflow_id: workflowId ?? null,
2225
- name: `${mode}_${Date.now()}`,
2226
- content_type: contentType,
2227
- parent_id: null
2228
- });
2229
- const fileName = `${asset.id}.${ext}`;
2230
- await fsMkdir(storagePath, { recursive: true });
2231
- await fsWrite(join(storagePath, fileName), bytes);
2232
- asset.size = bytes.length;
2233
- await asset.save();
2234
- return asset.id;
2235
- };
2236
- try {
2237
- if (mode === "image") {
2238
- const variations = Math.max(1, Math.min(Number(mediaGeneration.variations ?? 1), 8));
2239
- const width = typeof mediaGeneration.width === "number"
2240
- ? mediaGeneration.width
2241
- : undefined;
2242
- const height = typeof mediaGeneration.height === "number"
2243
- ? mediaGeneration.height
2244
- : undefined;
2245
- const imageModel = {
2246
- id: modelId,
2247
- name: modelId,
2248
- provider: providerId
2249
- };
2250
- const params = {
2251
- model: imageModel,
2252
- prompt,
2253
- width,
2254
- height
2255
- };
2256
- // Surface a progress chunk so the UI can show the request flight
2257
- await this.sendMessage({
2258
- type: "chunk",
2259
- thread_id: threadId,
2260
- content: "",
2261
- content_type: "text",
2262
- content_metadata: { media_generation: mediaGeneration },
2263
- done: false
2264
- });
2265
- if (requestSeq !== undefined && requestSeq !== this.chatRequestSeq)
2266
- return;
2267
- const imageBytesList = await provider.textToImages(params, variations);
2268
- const imageContents = [];
2269
- for (const bytes of imageBytesList) {
2270
- const assetId = await storeMediaAsset(bytes, "image/png", "png");
2271
- imageContents.push({
2272
- type: "image_url",
2273
- image: { type: "image", asset_id: assetId, mimeType: "image/png" }
2274
- });
2275
- }
2276
- await this.sendMessage({
2277
- type: "chunk",
2278
- thread_id: threadId,
2279
- content: "",
2280
- done: true
2281
- });
2282
- const assistantMsgData = {
2283
- type: "message",
2284
- role: "assistant",
2285
- content: imageContents,
2286
- thread_id: threadId,
2287
- workflow_id: workflowId,
2288
- provider: providerId,
2289
- model: modelId,
2290
- media_generation: mediaGeneration
2291
- };
2292
- await this.saveMessageToDb(assistantMsgData);
2293
- await this.sendMessage(assistantMsgData);
2294
- return;
2295
- }
2296
- if (mode === "video") {
2297
- const aspectRatio = typeof mediaGeneration.aspect_ratio === "string"
2298
- ? mediaGeneration.aspect_ratio
2299
- : null;
2300
- const resolution = typeof mediaGeneration.resolution === "string"
2301
- ? mediaGeneration.resolution
2302
- : null;
2303
- const duration = typeof mediaGeneration.duration === "number"
2304
- ? mediaGeneration.duration
2305
- : null;
2306
- const videoModel = {
2307
- id: modelId,
2308
- name: modelId,
2309
- provider: providerId
2310
- };
2311
- const params = {
2312
- model: videoModel,
2313
- prompt,
2314
- aspectRatio,
2315
- resolution,
2316
- durationSeconds: duration
2317
- };
2318
- await this.sendMessage({
2319
- type: "chunk",
2320
- thread_id: threadId,
2321
- content: "",
2322
- content_type: "text",
2323
- content_metadata: { media_generation: mediaGeneration },
2324
- done: false
2325
- });
2326
- const bytes = await provider.textToVideo(params);
2327
- const assetId = await storeMediaAsset(bytes, "video/mp4", "mp4");
2328
- await this.sendMessage({
2329
- type: "chunk",
2330
- thread_id: threadId,
2331
- content: "",
2332
- done: true
2333
- });
2334
- const assistantMsgData = {
2335
- type: "message",
2336
- role: "assistant",
2337
- content: [
2338
- {
2339
- type: "video",
2340
- video: {
2341
- type: "video",
2342
- asset_id: assetId,
2343
- format: "mp4",
2344
- duration: duration
2345
- }
2346
- }
2347
- ],
2348
- thread_id: threadId,
2349
- workflow_id: workflowId,
2350
- provider: providerId,
2351
- model: modelId,
2352
- media_generation: mediaGeneration
2353
- };
2354
- await this.saveMessageToDb(assistantMsgData);
2355
- await this.sendMessage(assistantMsgData);
2356
- return;
2357
- }
2358
- if (mode === "audio") {
2359
- const voice = typeof mediaGeneration.voice === "string"
2360
- ? mediaGeneration.voice
2361
- : undefined;
2362
- const speed = typeof mediaGeneration.speed === "number"
2363
- ? mediaGeneration.speed
2364
- : 1.0;
2365
- const requestedFormatRaw = typeof mediaGeneration.audio_format === "string"
2366
- ? mediaGeneration.audio_format.toLowerCase()
2367
- : null;
2368
- const supportedFormats = new Set([
2369
- "mp3",
2370
- "wav",
2371
- "pcm",
2372
- "opus",
2373
- "flac",
2374
- "aac"
2375
- ]);
2376
- const requestedFormat = requestedFormatRaw && supportedFormats.has(requestedFormatRaw)
2377
- ? requestedFormatRaw
2378
- : null;
2379
- await this.sendMessage({
2380
- type: "chunk",
2381
- thread_id: threadId,
2382
- content: "",
2383
- content_type: "text",
2384
- content_metadata: { media_generation: mediaGeneration },
2385
- done: false
2386
- });
2387
- let assetId;
2388
- let audioMimeType;
2389
- // Some providers (e.g. HuggingFace, OpenAI) can return fully-encoded
2390
- // audio. Prefer that path when available and honor the requested
2391
- // container when the provider supports it.
2392
- const encoded = await provider.textToSpeechEncoded({
2393
- text: prompt,
2394
- model: modelId,
2395
- voice,
2396
- speed,
2397
- audioFormat: requestedFormat ?? undefined
2398
- });
2399
- if (encoded) {
2400
- const mimeToExt = {
2401
- "audio/mpeg": "mp3",
2402
- "audio/wav": "wav",
2403
- "audio/ogg": "ogg",
2404
- "audio/flac": "flac",
2405
- "audio/aac": "aac"
2406
- };
2407
- const ext = mimeToExt[encoded.mimeType] ?? "flac";
2408
- if (requestedFormat &&
2409
- requestedFormat !== ext &&
2410
- requestedFormat !== "pcm") {
2411
- log.warn("Requested audio_format not supported by provider; returning native format", { providerId, modelId, requestedFormat, returnedMime: encoded.mimeType });
2412
- }
2413
- assetId = await storeMediaAsset(encoded.data, encoded.mimeType, ext);
2414
- audioMimeType = encoded.mimeType;
2415
- }
2416
- else {
2417
- // Streaming PCM path (OpenAI, Gemini, etc.)
2418
- const pcmChunks = [];
2419
- let totalBytes = 0;
2420
- let chunkSampleRate = 24000;
2421
- for await (const chunk of provider.textToSpeech({
2422
- text: prompt,
2423
- model: modelId,
2424
- voice,
2425
- speed,
2426
- audioFormat: requestedFormat ?? undefined
2427
- })) {
2428
- if (requestSeq !== undefined &&
2429
- requestSeq !== this.chatRequestSeq)
2430
- return;
2431
- if (chunk?.samples) {
2432
- if (chunk.sampleRate)
2433
- chunkSampleRate = chunk.sampleRate;
2434
- const view = new Uint8Array(chunk.samples.buffer, chunk.samples.byteOffset, chunk.samples.byteLength);
2435
- const copy = new Uint8Array(view);
2436
- pcmChunks.push(copy);
2437
- totalBytes += copy.byteLength;
2438
- }
2439
- }
2440
- const merged = new Uint8Array(totalBytes);
2441
- let off = 0;
2442
- for (const c of pcmChunks) {
2443
- merged.set(c, off);
2444
- off += c.byteLength;
2445
- }
2446
- if (requestedFormat === "pcm") {
2447
- // Return raw PCM Int16 bytes (no container).
2448
- assetId = await storeMediaAsset(merged, "audio/pcm", "pcm");
2449
- audioMimeType = "audio/pcm";
2450
- }
2451
- else {
2452
- if (requestedFormat &&
2453
- requestedFormat !== "wav" &&
2454
- requestedFormat !== "pcm") {
2455
- log.warn("Requested audio_format cannot be produced from streaming PCM; falling back to WAV", { providerId, modelId, requestedFormat });
2456
- }
2457
- // Wrap raw PCM Int16 in a WAV container so browsers can play it.
2458
- const sampleRate = chunkSampleRate;
2459
- const numChannels = 1;
2460
- const bitsPerSample = 16;
2461
- const byteRate = sampleRate * numChannels * (bitsPerSample / 8);
2462
- const blockAlign = numChannels * (bitsPerSample / 8);
2463
- const wavHeader = new ArrayBuffer(44);
2464
- const dv = new DataView(wavHeader);
2465
- const writeStr = (pos, str) => {
2466
- for (let i = 0; i < str.length; i++)
2467
- dv.setUint8(pos + i, str.charCodeAt(i));
2468
- };
2469
- writeStr(0, "RIFF");
2470
- dv.setUint32(4, 36 + merged.byteLength, true);
2471
- writeStr(8, "WAVE");
2472
- writeStr(12, "fmt ");
2473
- dv.setUint32(16, 16, true);
2474
- dv.setUint16(20, 1, true);
2475
- dv.setUint16(22, numChannels, true);
2476
- dv.setUint32(24, sampleRate, true);
2477
- dv.setUint32(28, byteRate, true);
2478
- dv.setUint16(32, blockAlign, true);
2479
- dv.setUint16(34, bitsPerSample, true);
2480
- writeStr(36, "data");
2481
- dv.setUint32(40, merged.byteLength, true);
2482
- const wav = new Uint8Array(44 + merged.byteLength);
2483
- wav.set(new Uint8Array(wavHeader), 0);
2484
- wav.set(merged, 44);
2485
- assetId = await storeMediaAsset(wav, "audio/wav", "wav");
2486
- audioMimeType = "audio/wav";
2487
- }
2488
- }
2489
- await this.sendMessage({
2490
- type: "chunk",
2491
- thread_id: threadId,
2492
- content: "",
2493
- done: true
2494
- });
2495
- const assistantMsgData = {
2496
- type: "message",
2497
- role: "assistant",
2498
- content: [
2499
- {
2500
- type: "audio",
2501
- audio: {
2502
- type: "audio",
2503
- asset_id: assetId,
2504
- mimeType: audioMimeType
2505
- }
2506
- }
2507
- ],
2508
- thread_id: threadId,
2509
- workflow_id: workflowId,
2510
- provider: providerId,
2511
- model: modelId,
2512
- media_generation: mediaGeneration
2513
- };
2514
- await this.saveMessageToDb(assistantMsgData);
2515
- await this.sendMessage(assistantMsgData);
2516
- return;
2517
- }
2518
- if (mode === "image_edit" || mode === "image_to_video") {
2519
- // Resolve the source image from either the message content (most
2520
- // common path: user dropped an image into the composer) or from the
2521
- // explicit `source_asset_id` echo on the media_generation payload.
2522
- const sourceBytes = await this.resolveSourceImageBytes(data, mediaGeneration, userId);
2523
- if (!sourceBytes) {
2524
- await this.sendMessage({
2525
- type: "error",
2526
- message: "A source image is required — drop or attach an image first",
2527
- thread_id: threadId
2528
- });
2529
- return;
2530
- }
2531
- await this.sendMessage({
2532
- type: "chunk",
2533
- thread_id: threadId,
2534
- content: "",
2535
- content_type: "text",
2536
- content_metadata: { media_generation: mediaGeneration },
2537
- done: false
2538
- });
2539
- if (mode === "image_edit") {
2540
- const variations = Math.max(1, Math.min(Number(mediaGeneration.variations ?? 1), 8));
2541
- const targetWidth = typeof mediaGeneration.width === "number"
2542
- ? mediaGeneration.width
2543
- : undefined;
2544
- const targetHeight = typeof mediaGeneration.height === "number"
2545
- ? mediaGeneration.height
2546
- : undefined;
2547
- const strength = typeof mediaGeneration.strength === "number"
2548
- ? mediaGeneration.strength
2549
- : undefined;
2550
- const numInferenceSteps = typeof mediaGeneration.num_inference_steps === "number"
2551
- ? mediaGeneration.num_inference_steps
2552
- : undefined;
2553
- const editModel = {
2554
- id: modelId,
2555
- name: modelId,
2556
- provider: providerId
2557
- };
2558
- const params = {
2559
- model: editModel,
2560
- prompt,
2561
- targetWidth: targetWidth ?? null,
2562
- targetHeight: targetHeight ?? null,
2563
- strength: strength ?? null,
2564
- numInferenceSteps: numInferenceSteps ?? null
2565
- };
2566
- if (requestSeq !== undefined && requestSeq !== this.chatRequestSeq)
2567
- return;
2568
- const imageBytesList = await provider.imageToImages(sourceBytes, params, variations);
2569
- const imageContents = [];
2570
- for (const bytes of imageBytesList) {
2571
- const assetId = await storeMediaAsset(bytes, "image/png", "png");
2572
- imageContents.push({
2573
- type: "image_url",
2574
- image: {
2575
- type: "image",
2576
- asset_id: assetId,
2577
- mimeType: "image/png"
2578
- }
2579
- });
2580
- }
2581
- await this.sendMessage({
2582
- type: "chunk",
2583
- thread_id: threadId,
2584
- content: "",
2585
- done: true
2586
- });
2587
- const assistantMsgData = {
2588
- type: "message",
2589
- role: "assistant",
2590
- content: imageContents,
2591
- thread_id: threadId,
2592
- workflow_id: workflowId,
2593
- provider: providerId,
2594
- model: modelId,
2595
- media_generation: mediaGeneration
2596
- };
2597
- await this.saveMessageToDb(assistantMsgData);
2598
- await this.sendMessage(assistantMsgData);
2599
- return;
2600
- }
2601
- // image_to_video
2602
- const aspectRatio = typeof mediaGeneration.aspect_ratio === "string"
2603
- ? mediaGeneration.aspect_ratio
2604
- : null;
2605
- const resolution = typeof mediaGeneration.resolution === "string"
2606
- ? mediaGeneration.resolution
2607
- : null;
2608
- const duration = typeof mediaGeneration.duration === "number"
2609
- ? mediaGeneration.duration
2610
- : null;
2611
- const numInferenceSteps = typeof mediaGeneration.num_inference_steps === "number"
2612
- ? mediaGeneration.num_inference_steps
2613
- : null;
2614
- const i2vModel = {
2615
- id: modelId,
2616
- name: modelId,
2617
- provider: providerId
2618
- };
2619
- const params = {
2620
- model: i2vModel,
2621
- prompt,
2622
- aspectRatio,
2623
- resolution,
2624
- durationSeconds: duration,
2625
- numInferenceSteps
2626
- };
2627
- const bytes = await provider.imageToVideo(sourceBytes, params);
2628
- const assetId = await storeMediaAsset(bytes, "video/mp4", "mp4");
2629
- await this.sendMessage({
2630
- type: "chunk",
2631
- thread_id: threadId,
2632
- content: "",
2633
- done: true
2634
- });
2635
- const assistantMsgData = {
2636
- type: "message",
2637
- role: "assistant",
2638
- content: [
2639
- {
2640
- type: "video",
2641
- video: {
2642
- type: "video",
2643
- asset_id: assetId,
2644
- format: "mp4",
2645
- duration
2646
- }
2647
- }
2648
- ],
2649
- thread_id: threadId,
2650
- workflow_id: workflowId,
2651
- provider: providerId,
2652
- model: modelId,
2653
- media_generation: mediaGeneration
2654
- };
2655
- await this.saveMessageToDb(assistantMsgData);
2656
- await this.sendMessage(assistantMsgData);
2657
- return;
2658
- }
2659
- // Modes not yet implemented on the backend — fall back to an informative
2660
- // error so the client can render the unsupported state cleanly.
2661
- await this.sendMessage({
2662
- type: "error",
2663
- message: `Media generation mode "${mode}" is not yet supported`,
2664
- thread_id: threadId
2665
- });
2666
- }
2667
- catch (err) {
2668
- const errMsg = err instanceof Error ? err.message : String(err);
2669
- log.error("Media generation error", { threadId, mode, error: errMsg });
2670
- await this.sendMessage({
2671
- type: "error",
2672
- message: `Generation failed: ${errMsg}`,
2673
- thread_id: threadId
2674
- });
2675
- }
2676
- }
2677
- /**
2678
- * Resolve the source image bytes for image-edit / image-to-video calls.
2679
- * Searches in priority order:
2680
- * 1. `media_generation.source_asset_id` → load from Asset storage
2681
- * 2. The first `image_url` content block on the user message
2682
- * (supports asset_id, http(s) uri, and inline data:base64 payloads)
2683
- * Returns `null` when no usable source image can be found.
2684
- */
2685
- async resolveSourceImageBytes(data, mediaGeneration, userId) {
2686
- const tryLoadAsset = async (assetId) => {
2687
- if (!assetId)
2688
- return null;
2689
- try {
2690
- const asset = await Asset.find(userId, assetId);
2691
- if (!asset)
2692
- return null;
2693
- const ext = (asset.content_type ?? "image/png").split("/")[1] ?? "png";
2694
- const { join } = await import("node:path");
2695
- const { readFile } = await import("node:fs/promises");
2696
- return new Uint8Array(await readFile(join(getAssetStoragePath(), `${assetId}.${ext}`)));
2697
- }
2698
- catch (err) {
2699
- log.warn("resolveSourceImageBytes: asset load failed", {
2700
- assetId,
2701
- error: err instanceof Error ? err.message : String(err)
2702
- });
2703
- return null;
2704
- }
2705
- };
2706
- const explicitId = typeof mediaGeneration.source_asset_id === "string"
2707
- ? mediaGeneration.source_asset_id
2708
- : null;
2709
- if (explicitId) {
2710
- const fromAsset = await tryLoadAsset(explicitId);
2711
- if (fromAsset)
2712
- return fromAsset;
2713
- }
2714
- const content = data.content;
2715
- if (Array.isArray(content)) {
2716
- for (const c of content) {
2717
- if (!c || typeof c !== "object")
2718
- continue;
2719
- const block = c;
2720
- if (block.type !== "image_url")
2721
- continue;
2722
- const image = (block.image ?? {});
2723
- const assetId = typeof image.asset_id === "string"
2724
- ? image.asset_id
2725
- : null;
2726
- if (assetId) {
2727
- const bytes = await tryLoadAsset(assetId);
2728
- if (bytes)
2729
- return bytes;
2730
- }
2731
- const uri = typeof image.uri === "string" ? image.uri : null;
2732
- if (uri) {
2733
- if (uri.startsWith("data:")) {
2734
- const commaIdx = uri.indexOf(",");
2735
- if (commaIdx > -1) {
2736
- const b64 = uri.slice(commaIdx + 1);
2737
- try {
2738
- return new Uint8Array(Buffer.from(b64, "base64"));
2739
- }
2740
- catch {
2741
- /* fall through */
2742
- }
2743
- }
2744
- }
2745
- else if (uri.startsWith("http://") || uri.startsWith("https://")) {
2746
- if (!isSafeExternalUrl(uri)) {
2747
- log.warn("resolveSourceImageBytes: refusing to fetch non-public URL", { uri });
2748
- }
2749
- else {
2750
- try {
2751
- const resp = await fetch(uri);
2752
- if (resp.ok) {
2753
- return new Uint8Array(await resp.arrayBuffer());
2754
- }
2755
- }
2756
- catch (err) {
2757
- log.warn("resolveSourceImageBytes: fetch failed", {
2758
- uri,
2759
- error: err instanceof Error ? err.message : String(err)
2760
- });
2761
- }
2762
- }
2763
- }
2764
- }
2765
- const data64 = typeof image.data === "string" ? image.data : null;
2766
- if (data64) {
2767
- try {
2768
- return new Uint8Array(Buffer.from(data64, "base64"));
2769
- }
2770
- catch {
2771
- /* ignore */
2772
- }
2773
- }
2774
- }
2775
- }
2776
- return null;
2777
- }
2778
- async handleWorkflowMessage(data, _requestSeq) {
2779
- const threadId = typeof data.thread_id === "string" ? data.thread_id : "";
2780
- const workflowId = typeof data.workflow_id === "string" ? data.workflow_id : null;
2781
- const providerId = typeof data.provider === "string" ? data.provider : this.defaultProvider;
2782
- const model = typeof data.model === "string" ? data.model : this.defaultModel;
2783
- const userId = this.userId ?? "1";
2784
- const jobId = randomUUID();
2785
- log.info("Workflow message", { threadId, workflowId, jobId });
2786
- try {
2787
- if (!workflowId) {
2788
- throw new Error("workflow_id is required for workflow processing");
2789
- }
2790
- // Load workflow from DB
2791
- const workflow = await Workflow.find(userId, workflowId);
2792
- if (!workflow) {
2793
- throw new Error(`Workflow ${workflowId} not found`);
2794
- }
2795
- const rawGraph = workflow.graph;
2796
- if (this.beforeRunJob) {
2797
- await this.beforeRunJob(rawGraph);
2798
- }
2799
- // Detect message input names from raw graph (reads node.data) — matches Python
2800
- const { messageName, messagesName } = this.detectMessageInputNames(rawGraph);
2801
- const graph = await this.hydrateGraph(rawGraph);
2802
- const messageInputName = (typeof data.workflow_message_input_name === "string"
2803
- ? data.workflow_message_input_name
2804
- : null) ??
2805
- messageName ??
2806
- "message";
2807
- const messagesInputName = (typeof data.workflow_messages_input_name === "string"
2808
- ? data.workflow_messages_input_name
2809
- : null) ??
2810
- messagesName ??
2811
- "messages";
2812
- // Build chat history for params — matches Python
2813
- const [dbMessages] = await Message.paginate(threadId, { limit: 1000 });
2814
- const chatHistorySerialized = dbMessages.map((m) => ({
2815
- role: m.role,
2816
- content: m.content,
2817
- created_at: m.created_at,
2818
- thread_id: m.thread_id
2819
- }));
2820
- // Serialize current message
2821
- const currentMessage = {
2822
- role: typeof data.role === "string" ? data.role : "user",
2823
- content: data.content,
2824
- thread_id: threadId,
2825
- workflow_id: workflowId,
2826
- provider: providerId,
2827
- model
2828
- };
2829
- // Prepare params — matches Python's WorkflowMessageProcessor
2830
- const params = {
2831
- [messageInputName]: currentMessage,
2832
- [messagesInputName]: [...chatHistorySerialized, currentMessage],
2833
- ...(typeof data.params === "object" && data.params !== null
2834
- ? data.params
2835
- : {})
2836
- };
2837
- // If chat workflow, add legacy params — matches Python's ChatWorkflowMessageProcessor
2838
- if (workflow.run_mode === "chat") {
2839
- const legacyChatInput = chatHistorySerialized.map((m) => ({
2840
- role: m.role,
2841
- content: this.extractTextContent(m.content),
2842
- created_at: m.created_at
2843
- }));
2844
- params["chat_input"] = legacyChatInput;
2845
- if (messagesInputName !== "messages") {
2846
- params["messages"] = legacyChatInput;
2847
- }
2848
- }
2849
- // Create processing context
2850
- const workspaceDir = this.workspaceResolver
2851
- ? await this.workspaceResolver(workflowId, userId)
2852
- : null;
2853
- const context = createRuntimeContext({
2854
- jobId,
2855
- workflowId,
2856
- userId,
2857
- workspaceDir,
2858
- assetOutputMode: this.mode === "text" ? "data_uri" : "temp_url"
2859
- });
2860
- // Expose executor/node-type resolution for sub-workflow nodes
2861
- context.setResolveExecutor((node) => this.resolveExecutor(node));
2862
- if (this.resolveNodeType) {
2863
- const resolverObj = typeof this.resolveNodeType === "function"
2864
- ? { resolveNodeType: this.resolveNodeType }
2865
- : this.resolveNodeType;
2866
- context.setResolveNodeType((nodeType) => resolverObj.resolveNodeType(nodeType));
2867
- }
2868
- // Create and run workflow
2869
- const runner = new WorkflowRunner(jobId, {
2870
- resolveExecutor: (node) => this.resolveExecutor(node),
2871
- executionContext: context,
2872
- validateNode: this.validateNode
2873
- });
2874
- const active = {
2875
- jobId,
2876
- workflowId,
2877
- context,
2878
- runner,
2879
- graph,
2880
- finished: false,
2881
- status: "running"
2882
- };
2883
- this.activeJobs.set(jobId, active);
2884
- // Persist job to DB (best-effort)
2885
- try {
2886
- await Job.create({
2887
- id: jobId,
2888
- workflow_id: workflowId,
2889
- user_id: userId,
2890
- status: "running",
2891
- params,
2892
- graph
2893
- });
2894
- }
2895
- catch (error) {
2896
- this.logError("workflow job persistence failed", error);
2897
- }
2898
- // Execute workflow and stream messages
2899
- const executePromise = runner.run({ job_id: jobId, workflow_id: workflowId, params }, graph);
2900
- // Stream events, collect output_update results
2901
- const result = {};
2902
- await this.sendMessage({
2903
- type: "job_update",
2904
- status: "running",
2905
- job_id: jobId,
2906
- workflow_id: workflowId
2907
- });
2908
- let finalOutputs = {};
2909
- void executePromise
2910
- .then((r) => {
2911
- active.status = r.status;
2912
- active.error = r.error;
2913
- finalOutputs = r.outputs ?? {};
2914
- })
2915
- .catch((err) => {
2916
- active.status = "failed";
2917
- active.error = err instanceof Error ? err.message : String(err);
2918
- })
2919
- .finally(() => {
2920
- active.finished = true;
2921
- });
2922
- while (!active.finished || active.context.hasMessages()) {
2923
- while (active.context.hasMessages()) {
2924
- const msg = active.context.popMessage();
2925
- if (!msg)
2926
- break;
2927
- const outbound = {
2928
- ...msg,
2929
- job_id: msg.job_id ?? jobId,
2930
- workflow_id: msg.workflow_id ??
2931
- workflowId
2932
- };
2933
- // Capture output_update values for the response message
2934
- if (outbound.type === "output_update") {
2935
- const nodeId = String(outbound.node_id ?? "");
2936
- const graphNodes = graph.nodes ?? [];
2937
- const node = graphNodes.find((n) => n.id === nodeId);
2938
- const nodeType = typeof node?.type === "string" ? node.type : "";
2939
- if (nodeType.includes("Output")) {
2940
- const nodeName = typeof outbound.node_name === "string"
2941
- ? outbound.node_name
2942
- : nodeType;
2943
- result[nodeName] = outbound.value;
2944
- }
2945
- else {
2946
- continue; // Skip non-output node output_updates
2947
- }
2948
- }
2949
- await this.sendMessage(outbound);
2950
- }
2951
- if (!active.finished) {
2952
- await new Promise((resolve) => setTimeout(resolve, 10));
2953
- }
2954
- }
2955
- // Collect any outputs from the runner result — only Output-type nodes
2956
- // The kernel considers all leaf nodes as "output nodes", but for the
2957
- // response message we only want nodes whose type includes "Output"
2958
- // (matching Python's WorkflowMessageProcessor behavior).
2959
- for (const [nodeType, values] of Object.entries(finalOutputs)) {
2960
- if (!nodeType.includes("Output"))
2961
- continue;
2962
- if (!result[nodeType] && Array.isArray(values) && values.length > 0) {
2963
- result[nodeType] = values.length === 1 ? values[0] : values;
2964
- }
2965
- }
2966
- // Send terminal job_update if not already sent
2967
- await this.sendMessage({
2968
- type: "job_update",
2969
- status: active.status,
2970
- job_id: jobId,
2971
- workflow_id: workflowId,
2972
- error: active.error,
2973
- result: { outputs: finalOutputs }
2974
- });
2975
- // Persist final job status
2976
- try {
2977
- const job = (await Job.get(jobId));
2978
- if (job) {
2979
- if (active.status === "completed")
2980
- job.markCompleted();
2981
- else if (active.status === "failed")
2982
- job.markFailed(active.error ?? "Unknown error");
2983
- else if (active.status === "cancelled")
2984
- job.markCancelled();
2985
- await job.save();
2986
- }
2987
- }
2988
- catch (error) {
2989
- this.logError("workflow job persistence (final) failed", error);
2990
- }
2991
- this.activeJobs.delete(jobId);
2992
- // Signal completion — done chunk with job_id + workflow_id
2993
- await this.sendMessage({
2994
- type: "chunk",
2995
- content: "",
2996
- done: true,
2997
- job_id: jobId,
2998
- workflow_id: workflowId,
2999
- thread_id: threadId
3000
- });
3001
- // Create response message from workflow outputs — matches Python's _create_response_message
3002
- const responseContent = this.createWorkflowResponseContent(result);
3003
- const responseMsg = {
3004
- type: "message",
3005
- role: "assistant",
3006
- content: responseContent,
3007
- thread_id: threadId,
3008
- workflow_id: workflowId,
3009
- provider: providerId,
3010
- model,
3011
- job_id: jobId
3012
- };
3013
- await this.saveMessageToDb(responseMsg);
3014
- await this.sendMessage(responseMsg);
3015
- log.debug("Workflow message complete", { threadId, workflowId, jobId });
3016
- }
3017
- catch (err) {
3018
- const errMsg = err instanceof Error ? err.message : String(err);
3019
- log.error("Workflow message error", {
3020
- threadId,
3021
- workflowId,
3022
- error: errMsg
3023
- });
3024
- await this.sendMessage({
3025
- type: "error",
3026
- message: `Error processing workflow: ${errMsg}`,
3027
- job_id: jobId,
3028
- workflow_id: workflowId,
3029
- thread_id: threadId
3030
- });
3031
- // Send done chunk even on error — matches Python
3032
- await this.sendMessage({
3033
- type: "chunk",
3034
- content: "",
3035
- done: true,
3036
- job_id: jobId,
3037
- workflow_id: workflowId,
3038
- thread_id: threadId
3039
- });
3040
- }
3041
- }
3042
- /**
3043
- * Build the map of configured BaseProvider instances for the given user.
3044
- * Cached per user — invalidate by clearing `configuredProvidersCache`.
3045
- * Used to expose `find_model` to the GraphPlanner agent so it can pick a
3046
- * real model+provider for generic AI nodes.
3047
- */
3048
- async getConfiguredProviders(userId) {
3049
- const cached = this.configuredProvidersCache.get(userId);
3050
- if (cached)
3051
- return cached;
3052
- const providersMod = await import("@nodetool-ai/runtime");
3053
- const ids = providersMod.listRegisteredProviderIds();
3054
- const result = {};
3055
- await Promise.all(ids.map(async (id) => {
3056
- try {
3057
- if (await providersMod.isProviderConfigured(id, userId)) {
3058
- result[id] = await providersMod.getProvider(id, userId);
3059
- }
3060
- }
3061
- catch (err) {
3062
- log.debug("Skipping provider for find_model", {
3063
- provider: id,
3064
- error: err instanceof Error ? err.message : String(err)
3065
- });
3066
- }
3067
- }));
3068
- this.configuredProvidersCache.set(userId, result);
3069
- return result;
3070
- }
3071
- /**
3072
- * Handle agent mode messages.
3073
- * Mirrors Python's AgentMessageProcessor.process().
3074
- *
3075
- * Creates an Agent with the user's objective and streams execution events
3076
- * (Chunk, ToolCallUpdate, TaskUpdate, PlanningUpdate, LogUpdate, StepResult)
3077
- * to the client. Messages with type "message" are persisted to DB.
3078
- */
3079
- async handleAgentMessage(data, requestSeq) {
3080
- const threadId = typeof data.thread_id === "string" ? data.thread_id : "";
3081
- const providerId = data.provider;
3082
- const model = data.model;
3083
- const workflowId = typeof data.workflow_id === "string" ? data.workflow_id : null;
3084
- const userId = this.userId ?? "1";
3085
- const provider = await this.resolveProvider(providerId, userId);
3086
- // Extract objective from content
3087
- const objective = this.extractTextContent(data.content, "Complete the requested task");
3088
- // Generate unique execution ID — matches Python
3089
- const agentExecutionId = randomUUID();
3090
- // Resolve tools — matches Python's tool resolution
3091
- const { MultiModeAgent, ReadFileTool, WriteFileTool, BrowserTool, GoogleSearchTool, getAllMcpTools, resolveTool } = await import("@nodetool-ai/agents");
3092
- let selectedTools = [];
3093
- const rawToolNames = Array.isArray(data.tools)
3094
- ? data.tools.filter((t) => typeof t === "string")
3095
- : [];
3096
- // Build configured providers up-front so both `getAllMcpTools` and the
3097
- // MultiModeAgent below share the same map. Cached per-user in
3098
- // `getConfiguredProviders`. Independent of the NodeRegistry — the
3099
- // multi-task planner needs `find_model` and the media-generation tools
3100
- // even when no registry is wired (i.e. graph mode is unavailable).
3101
- const agentProviders = await this.getConfiguredProviders(userId);
3102
- if (rawToolNames.length > 0) {
3103
- // User explicitly specified tools — resolve by name
3104
- for (const name of rawToolNames) {
3105
- const tool = resolveTool(name);
3106
- if (tool)
3107
- selectedTools.push(tool);
3108
- }
3109
- log.debug("Selected tools for agent", {
3110
- tools: selectedTools.map((t) => t.name)
3111
- });
3112
- }
3113
- else {
3114
- // No tools specified — use the current built-in defaults plus NodeTool's
3115
- // MCP-style backend tools. When a NodeRegistry is wired up, the MCP
3116
- // helper swaps the REST search/list/get tools for the in-process biased
3117
- // versions and adds `find_model`, so any agent loop (not just the
3118
- // GraphPlanner) gets the same node-selection bias.
3119
- selectedTools = [
3120
- new ReadFileTool(),
3121
- new WriteFileTool(),
3122
- new BrowserTool(),
3123
- new GoogleSearchTool(),
3124
- ...getAllMcpTools({
3125
- registry: this.nodeRegistry,
3126
- providers: agentProviders
3127
- })
3128
- ];
3129
- log.debug("Using default + MCP tools for agent", {
3130
- count: selectedTools.length
3131
- });
3132
- }
3133
- // Server-side tools from resolveTools option
3134
- if (rawToolNames.length > 0 && this.resolveTools) {
3135
- const serverTools = await this.resolveTools(rawToolNames, userId);
3136
- for (const st of serverTools) {
3137
- if (!selectedTools.find((t) => t.name === st.name)) {
3138
- selectedTools.push(st);
3139
- }
3140
- }
3141
- }
3142
- // Include UI proxy tools if client provided a manifest via tool bridge — matches Python
3143
- if (Object.keys(this.clientToolsManifest).length > 0) {
3144
- const sendMsg = this.sendMessage.bind(this);
3145
- for (const [, manifest] of Object.entries(this.clientToolsManifest)) {
3146
- try {
3147
- selectedTools.push(new UIToolProxy(manifest, this.toolBridge, sendMsg));
3148
- }
3149
- catch (err) {
3150
- log.warn("Failed to register UI tool proxy", {
3151
- error: err instanceof Error ? err.message : String(err)
3152
- });
3153
- }
3154
- }
3155
- log.debug("Added UI tool proxies to agent", {
3156
- count: Object.keys(this.clientToolsManifest).length
3157
- });
3158
- }
3159
- // Create ProcessingContext for agent execution
3160
- const agentWorkspaceDir = workflowId && this.workspaceResolver
3161
- ? await this.workspaceResolver(workflowId, userId)
3162
- : tmpdir();
3163
- const ctx = createRuntimeContext({
3164
- jobId: randomUUID(),
3165
- userId,
3166
- workspaceDir: agentWorkspaceDir
3167
- });
3168
- const maxStepIterations = typeof data.max_step_iterations === "number" &&
3169
- data.max_step_iterations > 0
3170
- ? Math.floor(data.max_step_iterations)
3171
- : 20;
3172
- const maxSteps = typeof data.max_steps === "number" && data.max_steps > 0
3173
- ? Math.floor(data.max_steps)
3174
- : undefined;
3175
- // Pick the planner. Explicit `agent_planner` from the client wins; if
3176
- // omitted, default to "graph" when a NodeRegistry is wired (the
3177
- // workflow-builder path) and "multi" otherwise (TaskPlanner →
3178
- // ParallelTaskExecutor). Either way the request only reaches the
3179
- // graph path when a registry is actually available.
3180
- const requestedPlanner = data.agent_planner === "graph" || data.agent_planner === "multi"
3181
- ? data.agent_planner
3182
- : null;
3183
- const defaultPlanner = this.nodeRegistry
3184
- ? "graph"
3185
- : "multi";
3186
- const plannerType = requestedPlanner ?? defaultPlanner;
3187
- const useGraphPlanner = plannerType === "graph" && !!this.nodeRegistry;
3188
- if (requestedPlanner === "graph" && !this.nodeRegistry) {
3189
- log.warn("Client requested graph planner but no NodeRegistry is wired — falling back to multi-task planner.");
3190
- }
3191
- const markdownOutputSchema = {
3192
- type: "object",
3193
- properties: {
3194
- markdown: {
3195
- type: "string",
3196
- description: "The markdown content of the response"
3197
- }
3198
- },
3199
- required: ["markdown"]
3200
- };
3201
- try {
3202
- // Both planner types route through MultiModeAgent in plan mode; the
3203
- // `useGraphPlanner` flag picks GraphPlanner vs TaskPlanner under the
3204
- // hood. The legacy plain `Agent` is no longer used for agent_mode.
3205
- const agent = new MultiModeAgent({
3206
- name: "Assistant",
3207
- objective,
3208
- provider,
3209
- model,
3210
- mode: "plan",
3211
- tools: selectedTools,
3212
- useGraphPlanner,
3213
- registry: useGraphPlanner ? this.nodeRegistry : undefined,
3214
- providers: useGraphPlanner ? agentProviders : undefined,
3215
- maxStepIterations,
3216
- ...(maxSteps !== undefined ? { maxSteps } : {}),
3217
- outputSchema: markdownOutputSchema
3218
- });
3219
- for await (const item of agent.execute(ctx)) {
3220
- if (requestSeq !== undefined && requestSeq !== this.chatRequestSeq)
3221
- return;
3222
- const msgType = item.type;
3223
- if (msgType === "chunk") {
3224
- // Stream text chunks to client
3225
- const chunk = item;
3226
- await this.sendMessage({
3227
- type: "chunk",
3228
- content: chunk.content ?? "",
3229
- done: chunk.done ?? false,
3230
- thinking: chunk.thinking ?? false,
3231
- thread_id: chunk.thread_id ?? threadId
3232
- });
3233
- }
3234
- else if (msgType === "tool_call_update") {
3235
- // Forward tool call updates live AND persist them as an
3236
- // agent_execution message so that reopening a thread preserves the
3237
- // full per-step debug trace (which tools were called, with what args).
3238
- const tc = item;
3239
- await this.sendMessage({
3240
- type: "tool_call_update",
3241
- thread_id: threadId,
3242
- workflow_id: workflowId,
3243
- tool_call_id: tc.tool_call_id ?? null,
3244
- name: tc.name,
3245
- message: tc.message ?? `Calling ${tc.name}...`,
3246
- args: tc.args,
3247
- step_id: tc.step_id ?? null,
3248
- node_id: tc.node_id ?? null,
3249
- agent_execution_id: agentExecutionId
3250
- });
3251
- const persisted = {
3252
- type: "message",
3253
- role: "agent_execution",
3254
- execution_event_type: "tool_call_update",
3255
- agent_execution_id: agentExecutionId,
3256
- content: {
3257
- type: "tool_call_update",
3258
- tool_call_id: tc.tool_call_id ?? null,
3259
- name: tc.name,
3260
- message: tc.message ?? `Calling ${tc.name}...`,
3261
- args: tc.args ?? {},
3262
- step_id: tc.step_id ?? null,
3263
- node_id: tc.node_id ?? null
3264
- },
3265
- thread_id: threadId,
3266
- workflow_id: workflowId,
3267
- provider: providerId,
3268
- model,
3269
- agent_mode: true
3270
- };
3271
- await this.saveMessageToDb(persisted);
3272
- }
3273
- else if (msgType === "task_update") {
3274
- // Send task update as agent_execution message — persisted by _run_processor pattern
3275
- const tu = item;
3276
- const contentDict = {
3277
- type: "task_update",
3278
- event: tu.event,
3279
- task: tu.task ?? null,
3280
- step: tu.step ?? null
3281
- };
3282
- const msg = {
3283
- type: "message",
3284
- role: "agent_execution",
3285
- execution_event_type: "task_update",
3286
- agent_execution_id: agentExecutionId,
3287
- content: contentDict,
3288
- thread_id: threadId,
3289
- workflow_id: workflowId,
3290
- provider: providerId,
3291
- model,
3292
- agent_mode: true
3293
- };
3294
- await this.saveMessageToDb(msg);
3295
- await this.sendMessage(msg);
3296
- }
3297
- else if (msgType === "planning_update") {
3298
- // Send planning update as agent_execution message
3299
- const pu = item;
3300
- const contentDict = {
3301
- type: "planning_update",
3302
- phase: pu.phase,
3303
- status: pu.status,
3304
- content: pu.content,
3305
- node_id: pu.node_id
3306
- };
3307
- const msg = {
3308
- type: "message",
3309
- role: "agent_execution",
3310
- execution_event_type: "planning_update",
3311
- agent_execution_id: agentExecutionId,
3312
- content: contentDict,
3313
- thread_id: threadId,
3314
- workflow_id: workflowId,
3315
- provider: providerId,
3316
- model,
3317
- agent_mode: true
3318
- };
3319
- await this.saveMessageToDb(msg);
3320
- await this.sendMessage(msg);
3321
- // Also send persistent LogUpdate for completed phases — matches Python
3322
- if (pu.status === "Success" || pu.status === "Failed") {
3323
- const logMsg = {
3324
- type: "message",
3325
- role: "agent_execution",
3326
- execution_event_type: "log_update",
3327
- agent_execution_id: agentExecutionId,
3328
- content: {
3329
- type: "log_update",
3330
- node_id: pu.node_id ?? "agent",
3331
- node_name: "Agent",
3332
- content: `${pu.phase}: ${pu.content ?? ""}`,
3333
- severity: pu.status === "Failed" ? "error" : "info"
3334
- },
3335
- thread_id: threadId,
3336
- workflow_id: workflowId,
3337
- provider: providerId,
3338
- model,
3339
- agent_mode: true
3340
- };
3341
- await this.saveMessageToDb(logMsg);
3342
- await this.sendMessage(logMsg);
3343
- }
3344
- }
3345
- else if (msgType === "log_update") {
3346
- // Forward log updates as agent_execution messages
3347
- const lu = item;
3348
- const msg = {
3349
- type: "message",
3350
- role: "agent_execution",
3351
- execution_event_type: "log_update",
3352
- agent_execution_id: agentExecutionId,
3353
- content: {
3354
- type: "log_update",
3355
- node_id: lu.node_id,
3356
- node_name: lu.node_name,
3357
- content: lu.content,
3358
- severity: lu.severity
3359
- },
3360
- thread_id: threadId,
3361
- workflow_id: workflowId,
3362
- provider: providerId,
3363
- model,
3364
- agent_mode: true
3365
- };
3366
- await this.saveMessageToDb(msg);
3367
- await this.sendMessage(msg);
3368
- }
3369
- else if (msgType === "step_result") {
3370
- const sr = item;
3371
- // Only forward non-task step results — task result handled via agent.results
3372
- if (!sr.is_task_result) {
3373
- const contentDict = {
3374
- type: "step_result",
3375
- result: sr.result,
3376
- step: sr.step ?? null,
3377
- error: sr.error,
3378
- is_task_result: sr.is_task_result
3379
- };
3380
- const msg = {
3381
- type: "message",
3382
- role: "agent_execution",
3383
- execution_event_type: "step_result",
3384
- agent_execution_id: agentExecutionId,
3385
- content: contentDict,
3386
- thread_id: threadId,
3387
- workflow_id: workflowId,
3388
- provider: providerId,
3389
- model,
3390
- agent_mode: true
3391
- };
3392
- await this.saveMessageToDb(msg);
3393
- await this.sendMessage(msg);
3394
- }
3395
- }
3396
- }
3397
- // Normalize final agent output — matches Python
3398
- const results = agent.getResults();
3399
- let content;
3400
- if (typeof results === "string") {
3401
- content = results;
3402
- }
3403
- else if (results &&
3404
- typeof results === "object" &&
3405
- "markdown" in results) {
3406
- const md = results.markdown;
3407
- content = typeof md === "string" ? md : String(results);
3408
- }
3409
- else if (results != null && typeof results === "object") {
3410
- // Structured result — run one more LLM call to present it
3411
- const resultsJson = JSON.stringify(results, null, 2);
3412
- content = await this.presentStructuredResults(provider, model, objective, resultsJson, threadId);
3413
- }
3414
- else {
3415
- content = results != null ? String(results) : "";
3416
- }
3417
- // Send final assistant message — persisted
3418
- const finalMsg = {
3419
- type: "message",
3420
- role: "assistant",
3421
- content,
3422
- thread_id: threadId,
3423
- workflow_id: workflowId,
3424
- provider: providerId,
3425
- model,
3426
- agent_mode: true
3427
- };
3428
- await this.saveMessageToDb(finalMsg);
3429
- await this.sendMessage(finalMsg);
3430
- // Signal completion
3431
- await this.sendMessage({
3432
- type: "chunk",
3433
- content: "",
3434
- done: true,
3435
- thread_id: threadId,
3436
- workflow_id: workflowId
3437
- });
3438
- log.debug("Agent execution complete", { threadId });
3439
- }
3440
- catch (err) {
3441
- const errMsg = err instanceof Error ? err.message : String(err);
3442
- log.error("Agent execution error", { threadId, error: errMsg });
3443
- await this.sendMessage({
3444
- type: "error",
3445
- message: `Agent execution error: ${errMsg}`,
3446
- error_type: "agent_error",
3447
- thread_id: threadId,
3448
- workflow_id: workflowId
3449
- });
3450
- // Signal completion even on error
3451
- await this.sendMessage({
3452
- type: "chunk",
3453
- content: "",
3454
- done: true,
3455
- thread_id: threadId,
3456
- workflow_id: workflowId
3457
- });
3458
- // Return error assistant message
3459
- const errorFinalMsg = {
3460
- type: "message",
3461
- role: "assistant",
3462
- content: `Agent execution error: ${errMsg}`,
3463
- thread_id: threadId,
3464
- workflow_id: workflowId,
3465
- provider: providerId,
3466
- model,
3467
- agent_mode: true
3468
- };
3469
- await this.saveMessageToDb(errorFinalMsg);
3470
- await this.sendMessage(errorFinalMsg);
3471
- }
3472
- }
3473
- async handleInference(data, requestSeq) {
3474
- const providerId = typeof data.provider === "string" ? data.provider : this.defaultProvider;
3475
- const model = typeof data.model === "string" ? data.model : this.defaultModel;
3476
- const rawMessages = Array.isArray(data.messages) ? data.messages : [];
3477
- log.debug("Inference request", {
3478
- model,
3479
- provider: providerId,
3480
- messages: rawMessages.length
3481
- });
3482
- const messages = rawMessages.map((m) => {
3483
- const msg = m;
3484
- return {
3485
- role: (typeof msg.role === "string"
3486
- ? msg.role
3487
- : "user"),
3488
- content: typeof msg.content === "string"
3489
- ? msg.content
3490
- : Array.isArray(msg.content)
3491
- ? msg.content
3492
- : "",
3493
- toolCallId: typeof msg.toolCallId === "string" ? msg.toolCallId : null,
3494
- toolCalls: Array.isArray(msg.toolCalls)
3495
- ? msg.toolCalls
3496
- : null,
3497
- threadId: null
3498
- };
3499
- });
3500
- if (!this.resolveProvider) {
3501
- await this.sendMessage({
3502
- type: "error",
3503
- message: "No provider resolver configured"
3504
- });
3505
- return;
3506
- }
3507
- const rawTools = Array.isArray(data.tools) ? data.tools : [];
3508
- const tools = rawTools
3509
- .map((t) => {
3510
- const tool = t;
3511
- return {
3512
- name: typeof tool.name === "string" ? tool.name : "",
3513
- description: typeof tool.description === "string" ? tool.description : undefined,
3514
- inputSchema: typeof tool.inputSchema === "object"
3515
- ? tool.inputSchema
3516
- : undefined
3517
- };
3518
- })
3519
- .filter((t) => t.name.length > 0);
3520
- const provider = await this.resolveProvider(providerId, this.userId ?? "1");
3521
- for await (const item of provider.generateMessagesTraced({
3522
- messages,
3523
- model,
3524
- tools: tools.length > 0 ? tools : undefined
3525
- })) {
3526
- if (requestSeq !== this.chatRequestSeq)
3527
- break; // cancelled
3528
- if ("type" in item && item.type === "chunk") {
3529
- await this.sendMessage({
3530
- ...item,
3531
- seq: requestSeq
3532
- });
3533
- }
3534
- else if ("name" in item) {
3535
- const toolItem = item;
3536
- log.info("Tool call", { tool: toolItem.name, args: toolItem.args });
3537
- await this.sendMessage({
3538
- type: "tool_call",
3539
- id: toolItem.id,
3540
- name: toolItem.name,
3541
- args: toolItem.args,
3542
- seq: requestSeq
3543
- });
3544
- }
3545
- }
3546
- if (requestSeq === this.chatRequestSeq) {
3547
- log.debug("Inference complete");
3548
- await this.sendMessage({ type: "inference_done", seq: requestSeq });
3549
- }
3550
- }
3551
- async handleCommand(command) {
3552
- const data = command.data ?? {};
3553
- const jobId = typeof data.job_id === "string" ? data.job_id : undefined;
3554
- const workflowId = typeof data.workflow_id === "string" ? data.workflow_id : undefined;
3555
- log.debug("Command", { command: command.command });
3556
- switch (command.command) {
3557
- case "clear_models":
3558
- return this.clearModels();
3559
- case "run_job":
3560
- await this.runJob(data);
3561
- return { message: "Job started", workflow_id: workflowId ?? null };
3562
- case "reconnect_job":
3563
- if (!jobId)
3564
- return { error: "job_id is required" };
3565
- void this.reconnectJob(jobId, workflowId);
3566
- return {
3567
- message: `Reconnecting to job ${jobId}`,
3568
- job_id: jobId,
3569
- workflow_id: workflowId ?? null
3570
- };
3571
- case "resume_job":
3572
- if (!jobId)
3573
- return { error: "job_id is required" };
3574
- void this.resumeJob(jobId, workflowId);
3575
- return {
3576
- message: `Resumption initiated for job ${jobId}`,
3577
- job_id: jobId,
3578
- workflow_id: workflowId ?? null
3579
- };
3580
- case "stream_input":
3581
- if (!jobId)
3582
- return { error: "job_id is required" };
3583
- {
3584
- const active = this.activeJobs.get(jobId);
3585
- log.info("stream_input command", {
3586
- jobId,
3587
- hasActive: !!active,
3588
- inputName: data.input,
3589
- handle: data.handle,
3590
- hasValue: data.value !== undefined,
3591
- activeJobIds: [...this.activeJobs.keys()]
3592
- });
3593
- if (!active)
3594
- return { error: "No active job/context" };
3595
- const inputName = typeof data.input === "string" ? data.input : "";
3596
- if (!inputName.trim())
3597
- return { error: "Invalid input name" };
3598
- const value = data.value;
3599
- const handle = typeof data.handle === "string" ? data.handle : undefined;
3600
- try {
3601
- await active.runner.pushInputValue(inputName, value, handle);
3602
- return {
3603
- message: "Input item streamed",
3604
- job_id: jobId,
3605
- workflow_id: workflowId ?? active.workflowId
3606
- };
3607
- }
3608
- catch (err) {
3609
- log.error("stream_input failed", {
3610
- error: err instanceof Error ? err.message : String(err)
3611
- });
3612
- return {
3613
- error: err instanceof Error ? err.message : String(err),
3614
- job_id: jobId,
3615
- workflow_id: workflowId ?? active.workflowId
3616
- };
3617
- }
3618
- }
3619
- case "end_input_stream":
3620
- if (!jobId)
3621
- return { error: "job_id is required" };
3622
- {
3623
- const active = this.activeJobs.get(jobId);
3624
- if (!active)
3625
- return { error: "No active job/context" };
3626
- const inputName = typeof data.input === "string" ? data.input : "";
3627
- if (!inputName.trim())
3628
- return { error: "Invalid input name" };
3629
- const handle = typeof data.handle === "string" ? data.handle : undefined;
3630
- try {
3631
- active.runner.finishInputStream(inputName, handle);
3632
- return {
3633
- message: "Input stream ended",
3634
- job_id: jobId,
3635
- workflow_id: workflowId ?? active.workflowId
3636
- };
3637
- }
3638
- catch (err) {
3639
- return {
3640
- error: err instanceof Error ? err.message : String(err),
3641
- job_id: jobId,
3642
- workflow_id: workflowId ?? active.workflowId
3643
- };
3644
- }
3645
- }
3646
- case "cancel_job":
3647
- if (!jobId)
3648
- return { error: "job_id is required" };
3649
- return this.cancelJob(jobId, workflowId);
3650
- case "get_status":
3651
- return this.getStatus(jobId);
3652
- case "set_mode": {
3653
- const mode = data.mode;
3654
- if (mode !== "binary" && mode !== "text") {
3655
- return { error: "mode must be binary or text" };
3656
- }
3657
- this.mode = mode;
3658
- return { message: `Mode set to ${mode}` };
3659
- }
3660
- case "chat_message": {
3661
- const threadId = data.thread_id;
3662
- if (typeof threadId !== "string" || threadId.length === 0) {
3663
- return { error: "thread_id is required for chat_message command" };
3664
- }
3665
- this.chatRequestSeq += 1;
3666
- const seq = this.chatRequestSeq;
3667
- this.currentTask = this.handleChatMessage(data, seq);
3668
- void this.currentTask.catch(async (err) => {
3669
- this.logError("chat_message processing failed", err);
3670
- await this.sendMessage({
3671
- type: "error",
3672
- message: err instanceof Error ? err.message : String(err)
3673
- });
3674
- });
3675
- return {
3676
- message: "Chat message processing started",
3677
- thread_id: threadId
3678
- };
3679
- }
3680
- case "inference": {
3681
- this.chatRequestSeq += 1;
3682
- const seq = this.chatRequestSeq;
3683
- this.currentTask = this.handleInference(data, seq);
3684
- void this.currentTask.catch(async (err) => {
3685
- this.logError("inference processing failed", err);
3686
- await this.sendMessage({
3687
- type: "error",
3688
- message: err instanceof Error ? err.message : String(err)
3689
- });
3690
- });
3691
- return { message: "Inference started" };
3692
- }
3693
- case "stop": {
3694
- const threadId = typeof data.thread_id === "string" ? data.thread_id : undefined;
3695
- // Always increment seq to cancel any in-progress chat or inference
3696
- this.chatRequestSeq += 1;
3697
- this.currentTask = null;
3698
- if (jobId) {
3699
- const active = this.activeJobs.get(jobId);
3700
- if (active) {
3701
- active.runner.cancel();
3702
- active.finished = true;
3703
- active.status = "cancelled";
3704
- }
3705
- }
3706
- this.toolBridge.cancelAll();
3707
- await this.sendMessage({
3708
- type: "generation_stopped",
3709
- message: "Generation stopped by user",
3710
- job_id: jobId ?? null,
3711
- thread_id: threadId ?? null
3712
- });
3713
- return {
3714
- message: "Stop command processed",
3715
- job_id: jobId ?? null,
3716
- thread_id: threadId ?? null
3717
- };
3718
- }
3719
- default:
3720
- return { error: "Unknown command" };
3721
- }
3722
- }
3723
- startHeartbeat() {
3724
- this.stopHeartbeat();
3725
- this.heartbeatTimer = setInterval(() => {
3726
- this.sendMessage({ type: "ping", ts: Date.now() / 1000 }).catch((err) => {
3727
- log.warn("Failed to send heartbeat ping", { error: String(err) });
3728
- });
3729
- }, 25_000);
3730
- }
3731
- stopHeartbeat() {
3732
- if (this.heartbeatTimer) {
3733
- clearInterval(this.heartbeatTimer);
3734
- this.heartbeatTimer = null;
3735
- }
3736
- }
3737
- startStatsBroadcast() {
3738
- this.stopStatsBroadcast();
3739
- this.statsTimer = setInterval(() => {
3740
- this.sendMessage({
3741
- type: "system_stats",
3742
- stats: this.getSystemStats()
3743
- }).catch((err) => {
3744
- log.warn("Failed to send system stats", { error: String(err) });
3745
- });
3746
- }, 30_000);
3747
- }
3748
- stopStatsBroadcast() {
3749
- if (this.statsTimer) {
3750
- clearInterval(this.statsTimer);
3751
- this.statsTimer = null;
3752
- }
3753
- }
3754
- registerObserver() {
3755
- if (this.observerRegistered)
3756
- return;
3757
- ModelObserver.subscribe(this.onModelChange);
3758
- this.observerRegistered = true;
3759
- }
3760
- unregisterObserver() {
3761
- if (!this.observerRegistered)
3762
- return;
3763
- ModelObserver.unsubscribe(this.onModelChange);
3764
- this.observerRegistered = false;
3765
- }
3766
- onModelChange = (instance, event) => {
3767
- if (!this.websocket)
3768
- return;
3769
- void this.sendMessage({
3770
- type: "resource_change",
3771
- event,
3772
- resource_type: instance.constructor.name.toLowerCase(),
3773
- resource: {
3774
- id: instance.partitionValue(),
3775
- etag: instance.getEtag()
3776
- }
3777
- });
3778
- };
3779
- async run(websocket) {
3780
- await this.connect(websocket, this.userId ?? undefined, this.authToken ?? undefined);
3781
- try {
3782
- await this.receiveMessages();
3783
- }
3784
- finally {
3785
- await this.disconnect();
3786
- }
3787
- }
3788
- async receiveMessages() {
3789
- while (true) {
3790
- const data = await this.receiveMessage();
3791
- if (data === null)
3792
- break;
3793
- const msgType = typeof data.type === "string" ? data.type : null;
3794
- if (msgType === "client_tools_manifest") {
3795
- const tools = Array.isArray(data.tools) ? data.tools : [];
3796
- this.clientToolsManifest = {};
3797
- for (const tool of tools) {
3798
- if (tool &&
3799
- typeof tool === "object" &&
3800
- typeof tool.name === "string") {
3801
- const name = tool.name;
3802
- this.clientToolsManifest[name] = tool;
3803
- }
3804
- }
3805
- continue;
3806
- }
3807
- if (msgType === "tool_result") {
3808
- const toolCallId = typeof data.tool_call_id === "string" ? data.tool_call_id : null;
3809
- if (toolCallId) {
3810
- this.toolBridge.resolveResult(toolCallId, data);
3811
- }
3812
- continue;
3813
- }
3814
- if (msgType === "ping") {
3815
- await this.sendMessage({ type: "pong", ts: Date.now() / 1000 });
3816
- continue;
3817
- }
3818
- if (typeof data.command === "string") {
3819
- try {
3820
- const command = data;
3821
- const response = await this.handleCommand(command);
3822
- await this.sendMessage(response);
3823
- }
3824
- catch (err) {
3825
- this.logError("invalid_command handling failed", err);
3826
- await this.sendMessage({
3827
- error: "invalid_command",
3828
- details: err instanceof Error ? err.message : String(err)
3829
- });
3830
- }
3831
- continue;
3832
- }
3833
- await this.sendMessage({
3834
- error: "invalid_message",
3835
- message: "All messages must include a 'command' field. Use 'chat_message' command for chat."
3836
- });
3837
- }
3838
- }
3839
- }
3840
- //# sourceMappingURL=unified-websocket-runner.js.map