@nodetool-ai/runtime 0.7.0-rc.23 → 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 (665) hide show
  1. package/README.md +29 -0
  2. package/dist/.nodetool-build-stamp +1 -0
  3. package/dist/agent-memory.d.ts +118 -0
  4. package/dist/agent-memory.d.ts.map +1 -0
  5. package/dist/agent-memory.js +186 -0
  6. package/dist/agent-memory.js.map +1 -0
  7. package/dist/async-local-storage.d.ts +20 -0
  8. package/dist/async-local-storage.d.ts.map +1 -0
  9. package/dist/async-local-storage.js +28 -0
  10. package/dist/async-local-storage.js.map +1 -0
  11. package/dist/blender-executor.d.ts +93 -0
  12. package/dist/blender-executor.d.ts.map +1 -0
  13. package/dist/blender-executor.js +294 -0
  14. package/dist/blender-executor.js.map +1 -0
  15. package/dist/comfy-executor.d.ts +23 -1
  16. package/dist/comfy-executor.d.ts.map +1 -1
  17. package/dist/comfy-executor.js +234 -55
  18. package/dist/comfy-executor.js.map +1 -1
  19. package/dist/context-packer.d.ts +1 -1
  20. package/dist/context-packer.d.ts.map +1 -1
  21. package/dist/context-packer.js +23 -25
  22. package/dist/context-packer.js.map +1 -1
  23. package/dist/context.d.ts +954 -127
  24. package/dist/context.d.ts.map +1 -1
  25. package/dist/context.js +2424 -513
  26. package/dist/context.js.map +1 -1
  27. package/dist/cost-reconciler.d.ts +32 -0
  28. package/dist/cost-reconciler.d.ts.map +1 -0
  29. package/dist/cost-reconciler.js +21 -0
  30. package/dist/cost-reconciler.js.map +1 -0
  31. package/dist/entities.d.ts +33 -0
  32. package/dist/entities.d.ts.map +1 -0
  33. package/dist/entities.js +43 -0
  34. package/dist/entities.js.map +1 -0
  35. package/dist/external-media-fetch.d.ts +29 -0
  36. package/dist/external-media-fetch.d.ts.map +1 -0
  37. package/dist/external-media-fetch.js +38 -0
  38. package/dist/external-media-fetch.js.map +1 -0
  39. package/dist/generation-receipt.d.ts +86 -0
  40. package/dist/generation-receipt.d.ts.map +1 -0
  41. package/dist/generation-receipt.js +137 -0
  42. package/dist/generation-receipt.js.map +1 -0
  43. package/dist/generation-registry.d.ts +67 -0
  44. package/dist/generation-registry.d.ts.map +1 -0
  45. package/dist/generation-registry.js +126 -0
  46. package/dist/generation-registry.js.map +1 -0
  47. package/dist/google/client.d.ts +196 -0
  48. package/dist/google/client.d.ts.map +1 -0
  49. package/dist/google/client.js +396 -0
  50. package/dist/google/client.js.map +1 -0
  51. package/dist/google/index.d.ts +3 -0
  52. package/dist/google/index.d.ts.map +1 -0
  53. package/dist/google/index.js +3 -0
  54. package/dist/google/index.js.map +1 -0
  55. package/dist/google/token.d.ts +15 -0
  56. package/dist/google/token.d.ts.map +1 -0
  57. package/dist/google/token.js +24 -0
  58. package/dist/google/token.js.map +1 -0
  59. package/dist/host-binaries.d.ts +106 -0
  60. package/dist/host-binaries.d.ts.map +1 -0
  61. package/dist/host-binaries.js +344 -0
  62. package/dist/host-binaries.js.map +1 -0
  63. package/dist/image-codec.d.ts +74 -0
  64. package/dist/image-codec.d.ts.map +1 -0
  65. package/dist/image-codec.js +273 -0
  66. package/dist/image-codec.js.map +1 -0
  67. package/dist/index.d.ts +56 -6
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +43 -4
  70. package/dist/index.js.map +1 -1
  71. package/dist/invocation-account.d.ts +43 -0
  72. package/dist/invocation-account.d.ts.map +1 -0
  73. package/dist/invocation-account.js +60 -0
  74. package/dist/invocation-account.js.map +1 -0
  75. package/dist/media-ref-bytes.d.ts +19 -0
  76. package/dist/media-ref-bytes.d.ts.map +1 -0
  77. package/dist/media-ref-bytes.js +181 -0
  78. package/dist/media-ref-bytes.js.map +1 -0
  79. package/dist/node-executor.d.ts +113 -2
  80. package/dist/node-executor.d.ts.map +1 -1
  81. package/dist/permission-gate.d.ts +152 -0
  82. package/dist/permission-gate.d.ts.map +1 -0
  83. package/dist/permission-gate.js +87 -0
  84. package/dist/permission-gate.js.map +1 -0
  85. package/dist/prefixed-storage-adapter.d.ts +33 -0
  86. package/dist/prefixed-storage-adapter.d.ts.map +1 -0
  87. package/dist/prefixed-storage-adapter.js +70 -0
  88. package/dist/prefixed-storage-adapter.js.map +1 -0
  89. package/dist/prompt-asset-refs.d.ts +222 -0
  90. package/dist/prompt-asset-refs.d.ts.map +1 -0
  91. package/dist/prompt-asset-refs.js +751 -0
  92. package/dist/prompt-asset-refs.js.map +1 -0
  93. package/dist/provider-cache.d.ts +3 -0
  94. package/dist/provider-cache.d.ts.map +1 -0
  95. package/dist/provider-cache.js +8 -0
  96. package/dist/provider-cache.js.map +1 -0
  97. package/dist/providers/aki-provider.d.ts +33 -10
  98. package/dist/providers/aki-provider.d.ts.map +1 -1
  99. package/dist/providers/aki-provider.js +84 -77
  100. package/dist/providers/aki-provider.js.map +1 -1
  101. package/dist/providers/alibaba-provider.d.ts +39 -0
  102. package/dist/providers/alibaba-provider.d.ts.map +1 -0
  103. package/dist/providers/alibaba-provider.js +86 -0
  104. package/dist/providers/alibaba-provider.js.map +1 -0
  105. package/dist/providers/anthropic-provider.d.ts +52 -3
  106. package/dist/providers/anthropic-provider.d.ts.map +1 -1
  107. package/dist/providers/anthropic-provider.js +1020 -338
  108. package/dist/providers/anthropic-provider.js.map +1 -1
  109. package/dist/providers/atlascloud-provider.d.ts +101 -0
  110. package/dist/providers/atlascloud-provider.d.ts.map +1 -0
  111. package/dist/providers/atlascloud-provider.js +1009 -0
  112. package/dist/providers/atlascloud-provider.js.map +1 -0
  113. package/dist/providers/atlascloud-transport.d.ts +100 -0
  114. package/dist/providers/atlascloud-transport.d.ts.map +1 -0
  115. package/dist/providers/atlascloud-transport.js +364 -0
  116. package/dist/providers/atlascloud-transport.js.map +1 -0
  117. package/dist/providers/atlascloud-webhook-registry.d.ts +35 -0
  118. package/dist/providers/atlascloud-webhook-registry.d.ts.map +1 -0
  119. package/dist/providers/atlascloud-webhook-registry.js +104 -0
  120. package/dist/providers/atlascloud-webhook-registry.js.map +1 -0
  121. package/dist/providers/audio-mime.d.ts +11 -0
  122. package/dist/providers/audio-mime.d.ts.map +1 -0
  123. package/dist/providers/audio-mime.js +46 -0
  124. package/dist/providers/audio-mime.js.map +1 -0
  125. package/dist/providers/base-provider.d.ts +481 -24
  126. package/dist/providers/base-provider.d.ts.map +1 -1
  127. package/dist/providers/base-provider.js +1259 -42
  128. package/dist/providers/base-provider.js.map +1 -1
  129. package/dist/providers/cassette-provider.d.ts +217 -0
  130. package/dist/providers/cassette-provider.d.ts.map +1 -0
  131. package/dist/providers/cassette-provider.js +502 -0
  132. package/dist/providers/cassette-provider.js.map +1 -0
  133. package/dist/providers/cerebras-provider.d.ts +6 -12
  134. package/dist/providers/cerebras-provider.d.ts.map +1 -1
  135. package/dist/providers/cerebras-provider.js +8 -33
  136. package/dist/providers/cerebras-provider.js.map +1 -1
  137. package/dist/providers/claude-agent-provider.d.ts +131 -68
  138. package/dist/providers/claude-agent-provider.d.ts.map +1 -1
  139. package/dist/providers/claude-agent-provider.js +1095 -515
  140. package/dist/providers/claude-agent-provider.js.map +1 -1
  141. package/dist/providers/codex-provider.d.ts +82 -0
  142. package/dist/providers/codex-provider.d.ts.map +1 -0
  143. package/dist/providers/codex-provider.js +492 -0
  144. package/dist/providers/codex-provider.js.map +1 -0
  145. package/dist/providers/cohere-provider.d.ts +5 -2
  146. package/dist/providers/cohere-provider.d.ts.map +1 -1
  147. package/dist/providers/cohere-provider.js +9 -2
  148. package/dist/providers/cohere-provider.js.map +1 -1
  149. package/dist/providers/comfy-cloud-provider.d.ts +30 -0
  150. package/dist/providers/comfy-cloud-provider.d.ts.map +1 -0
  151. package/dist/providers/comfy-cloud-provider.js +42 -0
  152. package/dist/providers/comfy-cloud-provider.js.map +1 -0
  153. package/dist/providers/content-filter.d.ts +46 -0
  154. package/dist/providers/content-filter.d.ts.map +1 -0
  155. package/dist/providers/content-filter.js +92 -0
  156. package/dist/providers/content-filter.js.map +1 -0
  157. package/dist/providers/context-exceeded.d.ts +37 -0
  158. package/dist/providers/context-exceeded.d.ts.map +1 -0
  159. package/dist/providers/context-exceeded.js +114 -0
  160. package/dist/providers/context-exceeded.js.map +1 -0
  161. package/dist/providers/context-window.d.ts +44 -0
  162. package/dist/providers/context-window.d.ts.map +1 -0
  163. package/dist/providers/context-window.js +145 -0
  164. package/dist/providers/context-window.js.map +1 -0
  165. package/dist/providers/contract/index.d.ts +4 -0
  166. package/dist/providers/contract/index.d.ts.map +1 -0
  167. package/dist/providers/contract/index.js +4 -0
  168. package/dist/providers/contract/index.js.map +1 -0
  169. package/dist/providers/contract/probe-manifest.d.ts +75 -0
  170. package/dist/providers/contract/probe-manifest.d.ts.map +1 -0
  171. package/dist/providers/contract/probe-manifest.js +379 -0
  172. package/dist/providers/contract/probe-manifest.js.map +1 -0
  173. package/dist/providers/contract/probe-runner.d.ts +57 -0
  174. package/dist/providers/contract/probe-runner.d.ts.map +1 -0
  175. package/dist/providers/contract/probe-runner.js +180 -0
  176. package/dist/providers/contract/probe-runner.js.map +1 -0
  177. package/dist/providers/contract/redact.d.ts +29 -0
  178. package/dist/providers/contract/redact.d.ts.map +1 -0
  179. package/dist/providers/contract/redact.js +113 -0
  180. package/dist/providers/contract/redact.js.map +1 -0
  181. package/dist/providers/core-tools.d.ts +74 -0
  182. package/dist/providers/core-tools.d.ts.map +1 -0
  183. package/dist/providers/core-tools.js +127 -0
  184. package/dist/providers/core-tools.js.map +1 -0
  185. package/dist/providers/cost-calculator.d.ts +69 -50
  186. package/dist/providers/cost-calculator.d.ts.map +1 -1
  187. package/dist/providers/cost-calculator.js +207 -397
  188. package/dist/providers/cost-calculator.js.map +1 -1
  189. package/dist/providers/credential-check.d.ts +45 -0
  190. package/dist/providers/credential-check.d.ts.map +1 -0
  191. package/dist/providers/credential-check.js +159 -0
  192. package/dist/providers/credential-check.js.map +1 -0
  193. package/dist/providers/custom-model-kinds.d.ts +33 -0
  194. package/dist/providers/custom-model-kinds.d.ts.map +1 -0
  195. package/dist/providers/custom-model-kinds.js +96 -0
  196. package/dist/providers/custom-model-kinds.js.map +1 -0
  197. package/dist/providers/custom-openai-provider.d.ts +81 -0
  198. package/dist/providers/custom-openai-provider.d.ts.map +1 -0
  199. package/dist/providers/custom-openai-provider.js +182 -0
  200. package/dist/providers/custom-openai-provider.js.map +1 -0
  201. package/dist/providers/custom-provider-registry.d.ts +19 -0
  202. package/dist/providers/custom-provider-registry.d.ts.map +1 -0
  203. package/dist/providers/custom-provider-registry.js +52 -0
  204. package/dist/providers/custom-provider-registry.js.map +1 -0
  205. package/dist/providers/deepseek-provider.d.ts +8 -14
  206. package/dist/providers/deepseek-provider.d.ts.map +1 -1
  207. package/dist/providers/deepseek-provider.js +6 -35
  208. package/dist/providers/deepseek-provider.js.map +1 -1
  209. package/dist/providers/defaults.d.ts +12 -0
  210. package/dist/providers/defaults.d.ts.map +1 -0
  211. package/dist/providers/defaults.js +12 -0
  212. package/dist/providers/defaults.js.map +1 -0
  213. package/dist/providers/elevenlabs-provider.d.ts +49 -0
  214. package/dist/providers/elevenlabs-provider.d.ts.map +1 -0
  215. package/dist/providers/elevenlabs-provider.js +142 -0
  216. package/dist/providers/elevenlabs-provider.js.map +1 -0
  217. package/dist/providers/entity-references.d.ts +31 -0
  218. package/dist/providers/entity-references.d.ts.map +1 -0
  219. package/dist/providers/entity-references.js +113 -0
  220. package/dist/providers/entity-references.js.map +1 -0
  221. package/dist/providers/evolink-provider.d.ts +38 -0
  222. package/dist/providers/evolink-provider.d.ts.map +1 -0
  223. package/dist/providers/evolink-provider.js +304 -0
  224. package/dist/providers/evolink-provider.js.map +1 -0
  225. package/dist/providers/fake-provider.d.ts +72 -1
  226. package/dist/providers/fake-provider.d.ts.map +1 -1
  227. package/dist/providers/fake-provider.js +203 -1
  228. package/dist/providers/fake-provider.js.map +1 -1
  229. package/dist/providers/fal-generations.d.ts +106 -0
  230. package/dist/providers/fal-generations.d.ts.map +1 -0
  231. package/dist/providers/fal-generations.js +286 -0
  232. package/dist/providers/fal-generations.js.map +1 -0
  233. package/dist/providers/fal-provider.d.ts +171 -7
  234. package/dist/providers/fal-provider.d.ts.map +1 -1
  235. package/dist/providers/fal-provider.js +1369 -64
  236. package/dist/providers/fal-provider.js.map +1 -1
  237. package/dist/providers/fal-queue.d.ts +47 -0
  238. package/dist/providers/fal-queue.d.ts.map +1 -0
  239. package/dist/providers/fal-queue.js +231 -0
  240. package/dist/providers/fal-queue.js.map +1 -0
  241. package/dist/providers/gemini-provider.d.ts +148 -8
  242. package/dist/providers/gemini-provider.d.ts.map +1 -1
  243. package/dist/providers/gemini-provider.js +1500 -316
  244. package/dist/providers/gemini-provider.js.map +1 -1
  245. package/dist/providers/gmi-provider.d.ts +21 -0
  246. package/dist/providers/gmi-provider.d.ts.map +1 -0
  247. package/dist/providers/gmi-provider.js +34 -0
  248. package/dist/providers/gmi-provider.js.map +1 -0
  249. package/dist/providers/gpt-image-size.d.ts +3 -0
  250. package/dist/providers/gpt-image-size.d.ts.map +1 -0
  251. package/dist/providers/gpt-image-size.js +37 -0
  252. package/dist/providers/gpt-image-size.js.map +1 -0
  253. package/dist/providers/groq-provider.d.ts +9 -12
  254. package/dist/providers/groq-provider.d.ts.map +1 -1
  255. package/dist/providers/groq-provider.js +19 -33
  256. package/dist/providers/groq-provider.js.map +1 -1
  257. package/dist/providers/groq-request.d.ts +38 -0
  258. package/dist/providers/groq-request.d.ts.map +1 -0
  259. package/dist/providers/groq-request.js +138 -0
  260. package/dist/providers/groq-request.js.map +1 -0
  261. package/dist/providers/higgsfield-provider.d.ts +30 -0
  262. package/dist/providers/higgsfield-provider.d.ts.map +1 -0
  263. package/dist/providers/higgsfield-provider.js +131 -0
  264. package/dist/providers/higgsfield-provider.js.map +1 -0
  265. package/dist/providers/higgsfield-transport.d.ts +106 -0
  266. package/dist/providers/higgsfield-transport.d.ts.map +1 -0
  267. package/dist/providers/higgsfield-transport.js +182 -0
  268. package/dist/providers/higgsfield-transport.js.map +1 -0
  269. package/dist/providers/http-transport.d.ts +74 -0
  270. package/dist/providers/http-transport.d.ts.map +1 -0
  271. package/dist/providers/http-transport.js +152 -0
  272. package/dist/providers/http-transport.js.map +1 -0
  273. package/dist/providers/huggingface-provider.d.ts +71 -3
  274. package/dist/providers/huggingface-provider.d.ts.map +1 -1
  275. package/dist/providers/huggingface-provider.js +230 -34
  276. package/dist/providers/huggingface-provider.js.map +1 -1
  277. package/dist/providers/image-mime.d.ts +39 -0
  278. package/dist/providers/image-mime.d.ts.map +1 -0
  279. package/dist/providers/image-mime.js +111 -0
  280. package/dist/providers/image-mime.js.map +1 -0
  281. package/dist/providers/image-ref.d.ts +21 -0
  282. package/dist/providers/image-ref.d.ts.map +1 -0
  283. package/dist/providers/image-ref.js +44 -0
  284. package/dist/providers/image-ref.js.map +1 -0
  285. package/dist/providers/index.d.ts +84 -8
  286. package/dist/providers/index.d.ts.map +1 -1
  287. package/dist/providers/index.js +239 -56
  288. package/dist/providers/index.js.map +1 -1
  289. package/dist/providers/jina-provider.d.ts +10 -2
  290. package/dist/providers/jina-provider.d.ts.map +1 -1
  291. package/dist/providers/jina-provider.js +16 -7
  292. package/dist/providers/jina-provider.js.map +1 -1
  293. package/dist/providers/kie-provider.d.ts +170 -9
  294. package/dist/providers/kie-provider.d.ts.map +1 -1
  295. package/dist/providers/kie-provider.js +1176 -228
  296. package/dist/providers/kie-provider.js.map +1 -1
  297. package/dist/providers/kie-webhook-registry.d.ts +28 -0
  298. package/dist/providers/kie-webhook-registry.d.ts.map +1 -0
  299. package/dist/providers/kie-webhook-registry.js +91 -0
  300. package/dist/providers/kie-webhook-registry.js.map +1 -0
  301. package/dist/providers/llama-provider.d.ts +21 -37
  302. package/dist/providers/llama-provider.d.ts.map +1 -1
  303. package/dist/providers/llama-provider.js +43 -354
  304. package/dist/providers/llama-provider.js.map +1 -1
  305. package/dist/providers/lmstudio-provider.d.ts +4 -7
  306. package/dist/providers/lmstudio-provider.d.ts.map +1 -1
  307. package/dist/providers/lmstudio-provider.js +24 -15
  308. package/dist/providers/lmstudio-provider.js.map +1 -1
  309. package/dist/providers/manifest-models.d.ts +341 -1
  310. package/dist/providers/manifest-models.d.ts.map +1 -1
  311. package/dist/providers/manifest-models.js +1106 -22
  312. package/dist/providers/manifest-models.js.map +1 -1
  313. package/dist/providers/media-mime.d.ts +14 -0
  314. package/dist/providers/media-mime.d.ts.map +1 -0
  315. package/dist/providers/media-mime.js +35 -0
  316. package/dist/providers/media-mime.js.map +1 -0
  317. package/dist/providers/meshy-provider.d.ts +14 -1
  318. package/dist/providers/meshy-provider.d.ts.map +1 -1
  319. package/dist/providers/meshy-provider.js +76 -46
  320. package/dist/providers/meshy-provider.js.map +1 -1
  321. package/dist/providers/meta-provider.d.ts +22 -0
  322. package/dist/providers/meta-provider.d.ts.map +1 -0
  323. package/dist/providers/meta-provider.js +54 -0
  324. package/dist/providers/meta-provider.js.map +1 -0
  325. package/dist/providers/minimax-provider.d.ts +25 -18
  326. package/dist/providers/minimax-provider.d.ts.map +1 -1
  327. package/dist/providers/minimax-provider.js +211 -72
  328. package/dist/providers/minimax-provider.js.map +1 -1
  329. package/dist/providers/mistral-provider.d.ts +6 -12
  330. package/dist/providers/mistral-provider.d.ts.map +1 -1
  331. package/dist/providers/mistral-provider.js +10 -34
  332. package/dist/providers/mistral-provider.js.map +1 -1
  333. package/dist/providers/moonshot-provider.d.ts +11 -15
  334. package/dist/providers/moonshot-provider.d.ts.map +1 -1
  335. package/dist/providers/moonshot-provider.js +33 -23
  336. package/dist/providers/moonshot-provider.js.map +1 -1
  337. package/dist/providers/node-llama-cpp-provider.d.ts +98 -0
  338. package/dist/providers/node-llama-cpp-provider.d.ts.map +1 -0
  339. package/dist/providers/node-llama-cpp-provider.js +479 -0
  340. package/dist/providers/node-llama-cpp-provider.js.map +1 -0
  341. package/dist/providers/nodetool-provider.d.ts +49 -0
  342. package/dist/providers/nodetool-provider.d.ts.map +1 -0
  343. package/dist/providers/nodetool-provider.js +224 -0
  344. package/dist/providers/nodetool-provider.js.map +1 -0
  345. package/dist/providers/oauth/browser-launcher.d.ts +23 -0
  346. package/dist/providers/oauth/browser-launcher.d.ts.map +1 -0
  347. package/dist/providers/oauth/browser-launcher.js +45 -0
  348. package/dist/providers/oauth/browser-launcher.js.map +1 -0
  349. package/dist/providers/oauth/claude-code-credentials.d.ts +74 -0
  350. package/dist/providers/oauth/claude-code-credentials.d.ts.map +1 -0
  351. package/dist/providers/oauth/claude-code-credentials.js +133 -0
  352. package/dist/providers/oauth/claude-code-credentials.js.map +1 -0
  353. package/dist/providers/oauth/claude-code-login.d.ts +124 -0
  354. package/dist/providers/oauth/claude-code-login.d.ts.map +1 -0
  355. package/dist/providers/oauth/claude-code-login.js +230 -0
  356. package/dist/providers/oauth/claude-code-login.js.map +1 -0
  357. package/dist/providers/oauth/claude-code-oauth-client.d.ts +111 -0
  358. package/dist/providers/oauth/claude-code-oauth-client.d.ts.map +1 -0
  359. package/dist/providers/oauth/claude-code-oauth-client.js +207 -0
  360. package/dist/providers/oauth/claude-code-oauth-client.js.map +1 -0
  361. package/dist/providers/oauth/errors.d.ts +54 -0
  362. package/dist/providers/oauth/errors.d.ts.map +1 -0
  363. package/dist/providers/oauth/errors.js +79 -0
  364. package/dist/providers/oauth/errors.js.map +1 -0
  365. package/dist/providers/oauth/index.d.ts +105 -0
  366. package/dist/providers/oauth/index.d.ts.map +1 -0
  367. package/dist/providers/oauth/index.js +167 -0
  368. package/dist/providers/oauth/index.js.map +1 -0
  369. package/dist/providers/oauth/jwt-claims.d.ts +23 -0
  370. package/dist/providers/oauth/jwt-claims.d.ts.map +1 -0
  371. package/dist/providers/oauth/jwt-claims.js +67 -0
  372. package/dist/providers/oauth/jwt-claims.js.map +1 -0
  373. package/dist/providers/oauth/local-callback-server.d.ts +60 -0
  374. package/dist/providers/oauth/local-callback-server.d.ts.map +1 -0
  375. package/dist/providers/oauth/local-callback-server.js +148 -0
  376. package/dist/providers/oauth/local-callback-server.js.map +1 -0
  377. package/dist/providers/oauth/oauth-client.d.ts +55 -0
  378. package/dist/providers/oauth/oauth-client.d.ts.map +1 -0
  379. package/dist/providers/oauth/oauth-client.js +161 -0
  380. package/dist/providers/oauth/oauth-client.js.map +1 -0
  381. package/dist/providers/oauth/openai-oauth-provider.d.ts +129 -0
  382. package/dist/providers/oauth/openai-oauth-provider.d.ts.map +1 -0
  383. package/dist/providers/oauth/openai-oauth-provider.js +265 -0
  384. package/dist/providers/oauth/openai-oauth-provider.js.map +1 -0
  385. package/dist/providers/oauth/pkce-helper.d.ts +43 -0
  386. package/dist/providers/oauth/pkce-helper.d.ts.map +1 -0
  387. package/dist/providers/oauth/pkce-helper.js +57 -0
  388. package/dist/providers/oauth/pkce-helper.js.map +1 -0
  389. package/dist/providers/oauth/redaction.d.ts +22 -0
  390. package/dist/providers/oauth/redaction.d.ts.map +1 -0
  391. package/dist/providers/oauth/redaction.js +59 -0
  392. package/dist/providers/oauth/redaction.js.map +1 -0
  393. package/dist/providers/oauth/secure-credential-store.d.ts +67 -0
  394. package/dist/providers/oauth/secure-credential-store.d.ts.map +1 -0
  395. package/dist/providers/oauth/secure-credential-store.js +73 -0
  396. package/dist/providers/oauth/secure-credential-store.js.map +1 -0
  397. package/dist/providers/oauth/token-store.d.ts +53 -0
  398. package/dist/providers/oauth/token-store.d.ts.map +1 -0
  399. package/dist/providers/oauth/token-store.js +78 -0
  400. package/dist/providers/oauth/token-store.js.map +1 -0
  401. package/dist/providers/oauth/types.d.ts +64 -0
  402. package/dist/providers/oauth/types.d.ts.map +1 -0
  403. package/dist/providers/oauth/types.js +12 -0
  404. package/dist/providers/oauth/types.js.map +1 -0
  405. package/dist/providers/object-tracking.d.ts +47 -0
  406. package/dist/providers/object-tracking.d.ts.map +1 -0
  407. package/dist/providers/object-tracking.js +46 -0
  408. package/dist/providers/object-tracking.js.map +1 -0
  409. package/dist/providers/offline-model-index.d.ts +20 -0
  410. package/dist/providers/offline-model-index.d.ts.map +1 -0
  411. package/dist/providers/offline-model-index.js +112 -0
  412. package/dist/providers/offline-model-index.js.map +1 -0
  413. package/dist/providers/ollama-provider.d.ts +28 -1
  414. package/dist/providers/ollama-provider.d.ts.map +1 -1
  415. package/dist/providers/ollama-provider.js +193 -43
  416. package/dist/providers/ollama-provider.js.map +1 -1
  417. package/dist/providers/openai-compat/client.d.ts +57 -0
  418. package/dist/providers/openai-compat/client.d.ts.map +1 -0
  419. package/dist/providers/openai-compat/client.js +157 -0
  420. package/dist/providers/openai-compat/client.js.map +1 -0
  421. package/dist/providers/openai-compat/decode.d.ts +36 -0
  422. package/dist/providers/openai-compat/decode.d.ts.map +1 -0
  423. package/dist/providers/openai-compat/decode.js +55 -0
  424. package/dist/providers/openai-compat/decode.js.map +1 -0
  425. package/dist/providers/openai-compat/errors.d.ts +54 -0
  426. package/dist/providers/openai-compat/errors.d.ts.map +1 -0
  427. package/dist/providers/openai-compat/errors.js +144 -0
  428. package/dist/providers/openai-compat/errors.js.map +1 -0
  429. package/dist/providers/openai-compat/index.d.ts +8 -0
  430. package/dist/providers/openai-compat/index.d.ts.map +1 -0
  431. package/dist/providers/openai-compat/index.js +5 -0
  432. package/dist/providers/openai-compat/index.js.map +1 -0
  433. package/dist/providers/openai-compat/sse.d.ts +14 -0
  434. package/dist/providers/openai-compat/sse.d.ts.map +1 -0
  435. package/dist/providers/openai-compat/sse.js +69 -0
  436. package/dist/providers/openai-compat/sse.js.map +1 -0
  437. package/dist/providers/openai-compat/types.d.ts +75 -0
  438. package/dist/providers/openai-compat/types.d.ts.map +1 -0
  439. package/dist/providers/openai-compat/types.js +9 -0
  440. package/dist/providers/openai-compat/types.js.map +1 -0
  441. package/dist/providers/openai-compat-provider.d.ts +90 -0
  442. package/dist/providers/openai-compat-provider.d.ts.map +1 -0
  443. package/dist/providers/openai-compat-provider.js +282 -0
  444. package/dist/providers/openai-compat-provider.js.map +1 -0
  445. package/dist/providers/openai-provider.d.ts +89 -26
  446. package/dist/providers/openai-provider.d.ts.map +1 -1
  447. package/dist/providers/openai-provider.js +990 -105
  448. package/dist/providers/openai-provider.js.map +1 -1
  449. package/dist/providers/openrouter-provider.d.ts +6 -12
  450. package/dist/providers/openrouter-provider.d.ts.map +1 -1
  451. package/dist/providers/openrouter-provider.js +24 -53
  452. package/dist/providers/openrouter-provider.js.map +1 -1
  453. package/dist/providers/provider-call-failure.d.ts +39 -0
  454. package/dist/providers/provider-call-failure.d.ts.map +1 -0
  455. package/dist/providers/provider-call-failure.js +132 -0
  456. package/dist/providers/provider-call-failure.js.map +1 -0
  457. package/dist/providers/provider-error.d.ts +59 -0
  458. package/dist/providers/provider-error.d.ts.map +1 -0
  459. package/dist/providers/provider-error.js +165 -0
  460. package/dist/providers/provider-error.js.map +1 -0
  461. package/dist/providers/provider-generations.d.ts +99 -0
  462. package/dist/providers/provider-generations.d.ts.map +1 -0
  463. package/dist/providers/provider-generations.js +71 -0
  464. package/dist/providers/provider-generations.js.map +1 -0
  465. package/dist/providers/provider-queue.d.ts +62 -0
  466. package/dist/providers/provider-queue.d.ts.map +1 -0
  467. package/dist/providers/provider-queue.js +28 -0
  468. package/dist/providers/provider-queue.js.map +1 -0
  469. package/dist/providers/provider-registry.d.ts +33 -14
  470. package/dist/providers/provider-registry.d.ts.map +1 -1
  471. package/dist/providers/provider-registry.js +85 -68
  472. package/dist/providers/provider-registry.js.map +1 -1
  473. package/dist/providers/provider-request-log.d.ts +53 -0
  474. package/dist/providers/provider-request-log.d.ts.map +1 -0
  475. package/dist/providers/provider-request-log.js +157 -0
  476. package/dist/providers/provider-request-log.js.map +1 -0
  477. package/dist/providers/provider-session.d.ts +3 -0
  478. package/dist/providers/provider-session.d.ts.map +1 -0
  479. package/dist/providers/provider-session.js +10 -0
  480. package/dist/providers/provider-session.js.map +1 -0
  481. package/dist/providers/provider-transport.d.ts +11 -0
  482. package/dist/providers/provider-transport.d.ts.map +1 -0
  483. package/dist/providers/provider-transport.js +11 -0
  484. package/dist/providers/provider-transport.js.map +1 -0
  485. package/dist/providers/python-provider.d.ts +20 -12
  486. package/dist/providers/python-provider.d.ts.map +1 -1
  487. package/dist/providers/python-provider.js +185 -25
  488. package/dist/providers/python-provider.js.map +1 -1
  489. package/dist/providers/replicate-provider.d.ts +89 -7
  490. package/dist/providers/replicate-provider.d.ts.map +1 -1
  491. package/dist/providers/replicate-provider.js +801 -65
  492. package/dist/providers/replicate-provider.js.map +1 -1
  493. package/dist/providers/replicate-retry.d.ts +30 -0
  494. package/dist/providers/replicate-retry.d.ts.map +1 -0
  495. package/dist/providers/replicate-retry.js +97 -0
  496. package/dist/providers/replicate-retry.js.map +1 -0
  497. package/dist/providers/responses-api.d.ts +27 -0
  498. package/dist/providers/responses-api.d.ts.map +1 -0
  499. package/dist/providers/responses-api.js +297 -0
  500. package/dist/providers/responses-api.js.map +1 -0
  501. package/dist/providers/reve-provider.d.ts +45 -0
  502. package/dist/providers/reve-provider.d.ts.map +1 -0
  503. package/dist/providers/reve-provider.js +159 -0
  504. package/dist/providers/reve-provider.js.map +1 -0
  505. package/dist/providers/rodin-provider.d.ts +23 -1
  506. package/dist/providers/rodin-provider.d.ts.map +1 -1
  507. package/dist/providers/rodin-provider.js +89 -51
  508. package/dist/providers/rodin-provider.js.map +1 -1
  509. package/dist/providers/safe-url.d.ts +70 -0
  510. package/dist/providers/safe-url.d.ts.map +1 -0
  511. package/dist/providers/safe-url.js +277 -0
  512. package/dist/providers/safe-url.js.map +1 -0
  513. package/dist/providers/scripted-provider.d.ts +9 -0
  514. package/dist/providers/scripted-provider.d.ts.map +1 -1
  515. package/dist/providers/scripted-provider.js +86 -10
  516. package/dist/providers/scripted-provider.js.map +1 -1
  517. package/dist/providers/structured-output.d.ts +42 -0
  518. package/dist/providers/structured-output.d.ts.map +1 -0
  519. package/dist/providers/structured-output.js +83 -0
  520. package/dist/providers/structured-output.js.map +1 -0
  521. package/dist/providers/together-provider.d.ts +19 -16
  522. package/dist/providers/together-provider.d.ts.map +1 -1
  523. package/dist/providers/together-provider.js +103 -225
  524. package/dist/providers/together-provider.js.map +1 -1
  525. package/dist/providers/topaz-provider.d.ts +44 -0
  526. package/dist/providers/topaz-provider.d.ts.map +1 -0
  527. package/dist/providers/topaz-provider.js +239 -0
  528. package/dist/providers/topaz-provider.js.map +1 -0
  529. package/dist/providers/types.d.ts +603 -4
  530. package/dist/providers/types.d.ts.map +1 -1
  531. package/dist/providers/types.js +81 -1
  532. package/dist/providers/types.js.map +1 -1
  533. package/dist/providers/useapi-provider.d.ts +27 -0
  534. package/dist/providers/useapi-provider.d.ts.map +1 -0
  535. package/dist/providers/useapi-provider.js +266 -0
  536. package/dist/providers/useapi-provider.js.map +1 -0
  537. package/dist/providers/video-duration.d.ts +6 -0
  538. package/dist/providers/video-duration.d.ts.map +1 -0
  539. package/dist/providers/video-duration.js +63 -0
  540. package/dist/providers/video-duration.js.map +1 -0
  541. package/dist/providers/video-frame-fallback.d.ts +39 -0
  542. package/dist/providers/video-frame-fallback.d.ts.map +1 -0
  543. package/dist/providers/video-frame-fallback.js +155 -0
  544. package/dist/providers/video-frame-fallback.js.map +1 -0
  545. package/dist/providers/video-frames.d.ts +61 -0
  546. package/dist/providers/video-frames.d.ts.map +1 -0
  547. package/dist/providers/video-frames.js +208 -0
  548. package/dist/providers/video-frames.js.map +1 -0
  549. package/dist/providers/video-mime.d.ts +14 -0
  550. package/dist/providers/video-mime.d.ts.map +1 -0
  551. package/dist/providers/video-mime.js +44 -0
  552. package/dist/providers/video-mime.js.map +1 -0
  553. package/dist/providers/video-to-audio.d.ts +34 -0
  554. package/dist/providers/video-to-audio.d.ts.map +1 -0
  555. package/dist/providers/video-to-audio.js +62 -0
  556. package/dist/providers/video-to-audio.js.map +1 -0
  557. package/dist/providers/vllm-provider.d.ts +22 -8
  558. package/dist/providers/vllm-provider.d.ts.map +1 -1
  559. package/dist/providers/vllm-provider.js +48 -23
  560. package/dist/providers/vllm-provider.js.map +1 -1
  561. package/dist/providers/voyage-provider.d.ts +10 -2
  562. package/dist/providers/voyage-provider.d.ts.map +1 -1
  563. package/dist/providers/voyage-provider.js +16 -7
  564. package/dist/providers/voyage-provider.js.map +1 -1
  565. package/dist/providers/xai-provider.d.ts +54 -13
  566. package/dist/providers/xai-provider.d.ts.map +1 -1
  567. package/dist/providers/xai-provider.js +299 -22
  568. package/dist/providers/xai-provider.js.map +1 -1
  569. package/dist/python-bridge-base.d.ts +358 -0
  570. package/dist/python-bridge-base.d.ts.map +1 -0
  571. package/dist/python-bridge-base.js +1557 -0
  572. package/dist/python-bridge-base.js.map +1 -0
  573. package/dist/python-bridge-codec.d.ts +28 -0
  574. package/dist/python-bridge-codec.d.ts.map +1 -0
  575. package/dist/python-bridge-codec.js +39 -0
  576. package/dist/python-bridge-codec.js.map +1 -0
  577. package/dist/python-bridge-factory.d.ts +52 -0
  578. package/dist/python-bridge-factory.d.ts.map +1 -0
  579. package/dist/python-bridge-factory.js +69 -0
  580. package/dist/python-bridge-factory.js.map +1 -0
  581. package/dist/python-bridge-framing.d.ts +68 -0
  582. package/dist/python-bridge-framing.d.ts.map +1 -0
  583. package/dist/python-bridge-framing.js +91 -0
  584. package/dist/python-bridge-framing.js.map +1 -0
  585. package/dist/python-bridge-types.d.ts +545 -2
  586. package/dist/python-bridge-types.d.ts.map +1 -1
  587. package/dist/python-bridge-types.js +49 -1
  588. package/dist/python-bridge-types.js.map +1 -1
  589. package/dist/python-graph-resolver.d.ts +57 -0
  590. package/dist/python-graph-resolver.d.ts.map +1 -0
  591. package/dist/python-graph-resolver.js +79 -0
  592. package/dist/python-graph-resolver.js.map +1 -0
  593. package/dist/python-node-executor.d.ts +36 -4
  594. package/dist/python-node-executor.d.ts.map +1 -1
  595. package/dist/python-node-executor.js +177 -86
  596. package/dist/python-node-executor.js.map +1 -1
  597. package/dist/python-stdio-bridge.d.ts +18 -37
  598. package/dist/python-stdio-bridge.d.ts.map +1 -1
  599. package/dist/python-stdio-bridge.js +148 -475
  600. package/dist/python-stdio-bridge.js.map +1 -1
  601. package/dist/python-websocket-bridge.d.ts +140 -0
  602. package/dist/python-websocket-bridge.d.ts.map +1 -0
  603. package/dist/python-websocket-bridge.js +561 -0
  604. package/dist/python-websocket-bridge.js.map +1 -0
  605. package/dist/python-worker-stderr.d.ts +4 -0
  606. package/dist/python-worker-stderr.d.ts.map +1 -0
  607. package/dist/python-worker-stderr.js +24 -0
  608. package/dist/python-worker-stderr.js.map +1 -0
  609. package/dist/recommended-models.d.ts +2 -2
  610. package/dist/recommended-models.d.ts.map +1 -1
  611. package/dist/recommended-models.js +24 -4
  612. package/dist/recommended-models.js.map +1 -1
  613. package/dist/recoverable-node-error.d.ts +31 -0
  614. package/dist/recoverable-node-error.d.ts.map +1 -0
  615. package/dist/recoverable-node-error.js +37 -0
  616. package/dist/recoverable-node-error.js.map +1 -0
  617. package/dist/redact-params.d.ts +13 -0
  618. package/dist/redact-params.d.ts.map +1 -0
  619. package/dist/redact-params.js +57 -0
  620. package/dist/redact-params.js.map +1 -0
  621. package/dist/sandbox-module-catalog.d.ts +43 -0
  622. package/dist/sandbox-module-catalog.d.ts.map +1 -0
  623. package/dist/sandbox-module-catalog.js +25 -0
  624. package/dist/sandbox-module-catalog.js.map +1 -0
  625. package/dist/storage-workspace.d.ts +59 -0
  626. package/dist/storage-workspace.d.ts.map +1 -0
  627. package/dist/storage-workspace.js +426 -0
  628. package/dist/storage-workspace.js.map +1 -0
  629. package/dist/swappable-python-bridge.d.ts +83 -0
  630. package/dist/swappable-python-bridge.d.ts.map +1 -0
  631. package/dist/swappable-python-bridge.js +246 -0
  632. package/dist/swappable-python-bridge.js.map +1 -0
  633. package/dist/telemetry.d.ts +7 -0
  634. package/dist/telemetry.d.ts.map +1 -1
  635. package/dist/telemetry.js +23 -0
  636. package/dist/telemetry.js.map +1 -1
  637. package/dist/testing.d.ts +81 -0
  638. package/dist/testing.d.ts.map +1 -0
  639. package/dist/testing.js +130 -0
  640. package/dist/testing.js.map +1 -0
  641. package/dist/token-counter.d.ts +30 -0
  642. package/dist/token-counter.d.ts.map +1 -0
  643. package/dist/token-counter.js +156 -0
  644. package/dist/token-counter.js.map +1 -0
  645. package/dist/trace-exporters.d.ts +10 -0
  646. package/dist/trace-exporters.d.ts.map +1 -1
  647. package/dist/trace-exporters.js +59 -20
  648. package/dist/trace-exporters.js.map +1 -1
  649. package/dist/tracing-helpers.d.ts +45 -4
  650. package/dist/tracing-helpers.d.ts.map +1 -1
  651. package/dist/tracing-helpers.js +62 -19
  652. package/dist/tracing-helpers.js.map +1 -1
  653. package/dist/turn-budget.d.ts +278 -0
  654. package/dist/turn-budget.d.ts.map +1 -0
  655. package/dist/turn-budget.js +365 -0
  656. package/dist/turn-budget.js.map +1 -0
  657. package/dist/variable-channel.d.ts +48 -0
  658. package/dist/variable-channel.d.ts.map +1 -0
  659. package/dist/variable-channel.js +110 -0
  660. package/dist/variable-channel.js.map +1 -0
  661. package/dist/workspace.d.ts +119 -0
  662. package/dist/workspace.d.ts.map +1 -0
  663. package/dist/workspace.js +112 -0
  664. package/dist/workspace.js.map +1 -0
  665. package/package.json +107 -19
@@ -0,0 +1,1557 @@
1
+ /**
2
+ * Transport-agnostic Python worker bridge base class.
3
+ *
4
+ * Holds all the protocol logic that is independent of how bytes move
5
+ * between the JS runtime and the Python worker — pending-request bookkeeping,
6
+ * message dispatch, discover/execute/cancel, provider RPC, and the shared
7
+ * connection lifecycle. Concrete transports (stdio, WebSocket) subclass this
8
+ * and implement the small set of transport hooks below.
9
+ *
10
+ * The wire protocol itself (msgpack-encoded `{type, request_id, data}` frames)
11
+ * is shared; only framing/transport differs per subclass.
12
+ */
13
+ import { getNodeBuiltinSync, safeProcessEnv } from "@nodetool-ai/config";
14
+ // The base only needs crypto (request IDs) and events (EventEmitter).
15
+ // Lazy-load so the module *graph* loads off-Node; instantiating a concrete
16
+ // bridge there throws at construction. Notably the base does NOT require
17
+ // child_process — that belongs to the stdio subclass only.
18
+ const nodeCrypto = getNodeBuiltinSync("node:crypto");
19
+ const nodeEvents = getNodeBuiltinSync("node:events");
20
+ function notOnNode(api) {
21
+ throw new Error(`${api} requires Node — PythonBridgeBase is Node-only`);
22
+ }
23
+ const randomUUID = nodeCrypto?.randomUUID ?? (() => notOnNode("node:crypto.randomUUID"));
24
+ // Re-export the EventEmitter type/class — falls back to a no-op so the
25
+ // module evaluates off-Node; consumers that instantiate the bridge will
26
+ // fail at construction time, not at module load.
27
+ class FallbackEmitter {
28
+ on(_, __) {
29
+ notOnNode("node:events.EventEmitter");
30
+ }
31
+ emit(_, ...__) {
32
+ notOnNode("node:events.EventEmitter");
33
+ }
34
+ removeAllListeners() {
35
+ notOnNode("node:events.EventEmitter");
36
+ }
37
+ }
38
+ // SAFETY: off-Node the fallback stands in for `EventEmitter` and throws from
39
+ // every member. It cannot implement the full class (`once`, `off`,
40
+ // `listenerCount`, the static helpers), which `PythonBridge extends
41
+ // EventEmitter` puts in the package's public contract.
42
+ const EventEmitter = (nodeEvents?.EventEmitter ??
43
+ FallbackEmitter);
44
+ import { createLogger } from "@nodetool-ai/config";
45
+ import { BRIDGE_PROTOCOL_VERSION, MIN_BRIDGE_PROTOCOL_VERSION, MIN_NODETOOL_CORE_VERSION } from "@nodetool-ai/protocol/bridge-protocol";
46
+ import { validateBridgeFrame } from "@nodetool-ai/protocol";
47
+ import { isNumber } from "@nodetool-ai/protocol";
48
+ const log = createLogger("nodetool.runtime.python-bridge-base");
49
+ /**
50
+ * Inbound bridge-frame validation gate (task B3). Every frame
51
+ * `_handleMessage` dispatches is safe-parsed against its
52
+ * `@nodetool-ai/protocol` schema first when this returns true; a frame that
53
+ * fails gets a structured, non-fatal rejection (see
54
+ * {@link PythonBridgeBase._handleInvalidFrame}) instead of silently
55
+ * dispatching malformed data.
56
+ *
57
+ * Mirrors `shouldValidateOutboundWs` in
58
+ * `packages/websocket/src/websocket-client-session.ts`: set
59
+ * `NODETOOL_VALIDATE_BRIDGE_FRAMES=1`/`=0` to force on/off; unset, it
60
+ * defaults to on under `NODE_ENV=test`/Vitest and off otherwise, so a worker
61
+ * bug that emits a malformed frame fails the test that exercised it rather
62
+ * than risking a perf hit validating every frame in production before the
63
+ * mechanism has burned in.
64
+ */
65
+ function shouldValidateBridgeFrames() {
66
+ const override = safeProcessEnv()["NODETOOL_VALIDATE_BRIDGE_FRAMES"]?.trim();
67
+ if (override === "1" || override === "true")
68
+ return true;
69
+ if (override === "0" || override === "false")
70
+ return false;
71
+ const env = safeProcessEnv();
72
+ return env["NODE_ENV"] === "test" || Boolean(env["VITEST"]);
73
+ }
74
+ import { comfyStatusInfoSchema, workerStatusSchema } from "./python-bridge-types.js";
75
+ const DEFAULT_EXECUTE_TIMEOUT_MS = Number(safeProcessEnv()["NODETOOL_PYTHON_EXECUTE_TIMEOUT_MS"] ?? 12 * 60 * 1000);
76
+ const DEFAULT_STATUS_TIMEOUT_MS = Number(safeProcessEnv()["NODETOOL_PYTHON_STATUS_TIMEOUT_MS"] ?? 30000);
77
+ const DEFAULT_DOWNLOAD_IDLE_TIMEOUT_MS = Number(safeProcessEnv()["NODETOOL_PYTHON_DOWNLOAD_IDLE_TIMEOUT_MS"] ?? 5 * 60 * 1000);
78
+ const MAX_RESULT_BLOB_BYTES = Number(safeProcessEnv()["NODETOOL_PYTHON_MAX_RESULT_BLOB_BYTES"] ??
79
+ 2 * 1024 * 1024 * 1024);
80
+ /**
81
+ * Transport-agnostic Python bridge. Subclasses provide the transport via
82
+ * {@link _openTransport} and {@link _send}, plus {@link close}. The optional
83
+ * {@link _assertCanConnect} hook lets a transport refuse to connect (e.g. in
84
+ * production).
85
+ */
86
+ export class PythonBridgeBase extends EventEmitter {
87
+ _nodeMetadata = [];
88
+ _loadErrors = [];
89
+ _workerStatus = null;
90
+ _pending = new Map();
91
+ _pendingStream = new Map();
92
+ /**
93
+ * `comfy.execute` event callbacks, keyed by request id. Separate from the
94
+ * pending maps because `comfy.event` frames are neither `progress` (wrong
95
+ * shape) nor terminal — they stream the ComfyUI lifecycle while the same
96
+ * request's terminal `result`/`error` settles via {@link _pendingStream}.
97
+ */
98
+ _pendingComfyEvents = new Map();
99
+ /**
100
+ * `blender.execute` event callbacks, keyed by request id. Same shape as
101
+ * {@link _pendingComfyEvents}: `blender.event` frames stream progress while
102
+ * the same request's terminal `result`/`error` settles via
103
+ * {@link _pendingStream}.
104
+ */
105
+ _pendingBlenderEvents = new Map();
106
+ _options;
107
+ _connected = false;
108
+ _connectPromise = null;
109
+ constructor(options = {}) {
110
+ super();
111
+ this._options = options;
112
+ }
113
+ /**
114
+ * Requests still awaiting a worker reply (plain, streaming, and Comfy event
115
+ * subscriptions). Exposed for leak accounting — a run that ended with
116
+ * pending requests left a promise nothing will ever settle.
117
+ */
118
+ get pendingRequestCount() {
119
+ return (this._pending.size +
120
+ this._pendingStream.size +
121
+ this._pendingComfyEvents.size +
122
+ this._pendingBlenderEvents.size);
123
+ }
124
+ /**
125
+ * Optional guard invoked at the start of connect(). Throw to refuse.
126
+ * Default is a no-op.
127
+ */
128
+ _assertCanConnect() { }
129
+ // ── Connection lifecycle ───────────────────────────────────────────
130
+ async connect() {
131
+ this._assertCanConnect();
132
+ await this._openTransport();
133
+ await this._discover();
134
+ try {
135
+ await this._getWorkerStatusWithTimeout();
136
+ }
137
+ catch (err) {
138
+ log.warn("Failed to fetch initial Python worker status; load_errors will be unavailable until next status fetch", err);
139
+ }
140
+ }
141
+ ensureConnected() {
142
+ if (this._connected)
143
+ return Promise.resolve();
144
+ if (!this._connectPromise) {
145
+ this._connectPromise = this.connect().then(() => {
146
+ this._connectPromise = null;
147
+ }, (err) => {
148
+ this._connectPromise = null;
149
+ throw err;
150
+ });
151
+ }
152
+ return this._connectPromise;
153
+ }
154
+ // ── Message dispatch ────────────────────────────────────────────────
155
+ _handleMessage(msg) {
156
+ const type = msg.type;
157
+ const requestId = msg.request_id;
158
+ if (shouldValidateBridgeFrames()) {
159
+ const validation = validateBridgeFrame(msg);
160
+ if (!validation.success) {
161
+ this._handleInvalidFrame(type, requestId, validation.error);
162
+ return;
163
+ }
164
+ }
165
+ if (type === "discover" && requestId) {
166
+ const pending = this._pending.get(requestId);
167
+ if (pending) {
168
+ const data = msg.data;
169
+ // Reject the discover promise only if the worker's protocol is below
170
+ // the HARD FLOOR (a real wire break). Workers at or above the floor
171
+ // but below BRIDGE_PROTOCOL_VERSION still connect — newer, additive
172
+ // features are gated per-capability (e.g. supportsModelManagement),
173
+ // so an older worker keeps running everything it understands. Workers
174
+ // that pre-date the protocol_version field are treated as version 1
175
+ // (the initial release) — same wire format, they just don't announce.
176
+ const workerVersion = isNumber(data.protocol_version)
177
+ ? data.protocol_version
178
+ : 1;
179
+ if (workerVersion < MIN_BRIDGE_PROTOCOL_VERSION) {
180
+ this._pending.delete(requestId);
181
+ pending.reject(new Error(`The installed nodetool-core speaks bridge protocol v${workerVersion}, ` +
182
+ `but this Nodetool build requires at least v${MIN_BRIDGE_PROTOCOL_VERSION}. ` +
183
+ `Please reinstall the Python environment from Settings → Packages ` +
184
+ `(Reinstall environment) — it will fetch nodetool-core>=${MIN_NODETOOL_CORE_VERSION}.`));
185
+ return;
186
+ }
187
+ if (workerVersion > BRIDGE_PROTOCOL_VERSION) {
188
+ // Forward-compat: a newer worker is expected to keep speaking
189
+ // older protocols, so we proceed with a warning.
190
+ this.emit("stderr", `[python-bridge] Worker protocol v${workerVersion} is newer than ` +
191
+ `JS runtime v${BRIDGE_PROTOCOL_VERSION}; assuming backward compatibility.\n`);
192
+ }
193
+ this._nodeMetadata = data.nodes;
194
+ this._loadErrors = data.load_errors ?? [];
195
+ pending.resolve({ outputs: {}, blobs: {} });
196
+ }
197
+ }
198
+ else if (type === "result" && requestId) {
199
+ const streamReq = this._pendingStream.get(requestId);
200
+ if (streamReq) {
201
+ this._pendingStream.delete(requestId);
202
+ streamReq.resolve(msg.data);
203
+ return;
204
+ }
205
+ const pending = this._pending.get(requestId);
206
+ if (pending) {
207
+ this._pending.delete(requestId);
208
+ if (pending.blobTransfers?.size) {
209
+ pending.reject(new Error("Python worker ended the result before all blob transfers completed"));
210
+ return;
211
+ }
212
+ const data = msg.data;
213
+ pending.resolve({
214
+ outputs: data.outputs,
215
+ blobs: { ...(data.blobs ?? {}), ...(pending.completedBlobs ?? {}) }
216
+ });
217
+ }
218
+ }
219
+ else if (type === "error" && requestId) {
220
+ const streamReq = this._pendingStream.get(requestId);
221
+ if (streamReq) {
222
+ this._pendingStream.delete(requestId);
223
+ const data = msg.data;
224
+ const err = new Error(data.error);
225
+ Reflect.set(err, "traceback", data.traceback);
226
+ streamReq.reject(err);
227
+ return;
228
+ }
229
+ const pending = this._pending.get(requestId);
230
+ if (pending) {
231
+ this._pending.delete(requestId);
232
+ const data = msg.data;
233
+ const err = new Error(data.error);
234
+ Reflect.set(err, "traceback", data.traceback);
235
+ pending.reject(err);
236
+ }
237
+ }
238
+ else if (type === "chunk" && requestId) {
239
+ const streamReq = this._pendingStream.get(requestId);
240
+ if (streamReq) {
241
+ streamReq.onChunk(msg.data);
242
+ }
243
+ }
244
+ else if (type === "blob.start" && requestId) {
245
+ this._startBlobTransfer(requestId, msg.data);
246
+ }
247
+ else if (type === "blob.chunk" && requestId) {
248
+ this._appendBlobChunk(requestId, msg.data);
249
+ }
250
+ else if (type === "blob.end" && requestId) {
251
+ this._finishBlobTransfer(requestId, msg.data);
252
+ }
253
+ else if (type === "progress" && requestId) {
254
+ const pending = this._pending.get(requestId);
255
+ if (pending?.onProgress) {
256
+ const data = msg.data;
257
+ pending.onProgress({ request_id: requestId, ...data });
258
+ }
259
+ this.emit("progress", msg.data);
260
+ }
261
+ else if (type === "comfy.event" && requestId) {
262
+ // Dedicated `comfy.execute` lifecycle frame. Distinct from `progress`
263
+ // because ComfyUI's events don't fit `{progress,total,message}`. Without
264
+ // this explicit case the frames would fall through and vanish silently.
265
+ const onEvent = this._pendingComfyEvents.get(requestId);
266
+ if (onEvent) {
267
+ onEvent(msg.data);
268
+ }
269
+ }
270
+ else if (type === "blender.event" && requestId) {
271
+ // Dedicated `blender.execute` progress frame, mirroring `comfy.event`:
272
+ // frame progress does not fit `{progress,total,message}` either, and
273
+ // without this case the frames fall through and vanish silently — which
274
+ // is also what an older build without this case does with them.
275
+ const onEvent = this._pendingBlenderEvents.get(requestId);
276
+ if (onEvent) {
277
+ onEvent(msg.data);
278
+ }
279
+ }
280
+ }
281
+ _rejectBlobTransfer(requestId, message) {
282
+ const pending = this._pending.get(requestId);
283
+ if (!pending)
284
+ return;
285
+ this._pending.delete(requestId);
286
+ pending.reject(new Error(message));
287
+ try {
288
+ this.cancel(requestId);
289
+ }
290
+ catch {
291
+ // The worker may already have completed; cancellation is best-effort.
292
+ }
293
+ }
294
+ _startBlobTransfer(requestId, data) {
295
+ const pending = this._pending.get(requestId);
296
+ const name = data["name"];
297
+ const size = data["size"];
298
+ if (!pending || typeof name !== "string" || typeof size !== "number")
299
+ return;
300
+ if (!Number.isSafeInteger(size) ||
301
+ size < 0 ||
302
+ size > MAX_RESULT_BLOB_BYTES) {
303
+ this._rejectBlobTransfer(requestId, `Python worker declared invalid blob size ${String(size)} for "${name}"`);
304
+ return;
305
+ }
306
+ pending.blobTransfers ??= new Map();
307
+ pending.completedBlobs ??= Object.create(null);
308
+ if (pending.blobTransfers.has(name) ||
309
+ Object.hasOwn(pending.completedBlobs, name)) {
310
+ this._rejectBlobTransfer(requestId, `Python worker started duplicate blob "${name}"`);
311
+ return;
312
+ }
313
+ pending.blobTransfers.set(name, { size, received: 0, chunks: [] });
314
+ }
315
+ _appendBlobChunk(requestId, data) {
316
+ const pending = this._pending.get(requestId);
317
+ const name = data["name"];
318
+ const offset = data["offset"];
319
+ const bytes = data["bytes"];
320
+ if (!pending ||
321
+ typeof name !== "string" ||
322
+ typeof offset !== "number" ||
323
+ !(bytes instanceof Uint8Array))
324
+ return;
325
+ const transfer = pending.blobTransfers?.get(name);
326
+ if (!transfer ||
327
+ offset !== transfer.received ||
328
+ offset + bytes.length > transfer.size) {
329
+ this._rejectBlobTransfer(requestId, `Python worker sent an out-of-order or oversized chunk for blob "${name}"`);
330
+ return;
331
+ }
332
+ transfer.chunks.push(bytes);
333
+ transfer.received += bytes.length;
334
+ }
335
+ _finishBlobTransfer(requestId, data) {
336
+ const pending = this._pending.get(requestId);
337
+ const name = data["name"];
338
+ const size = data["size"];
339
+ const expectedDigest = data["sha256"];
340
+ if (!pending ||
341
+ typeof name !== "string" ||
342
+ typeof size !== "number" ||
343
+ typeof expectedDigest !== "string")
344
+ return;
345
+ const transfer = pending.blobTransfers?.get(name);
346
+ if (!transfer ||
347
+ size !== transfer.size ||
348
+ transfer.received !== transfer.size) {
349
+ this._rejectBlobTransfer(requestId, `Python worker truncated blob "${name}"`);
350
+ return;
351
+ }
352
+ const blob = new Uint8Array(transfer.size);
353
+ let offset = 0;
354
+ for (const chunk of transfer.chunks) {
355
+ blob.set(chunk, offset);
356
+ offset += chunk.length;
357
+ }
358
+ const digest = nodeCrypto?.createHash("sha256").update(blob).digest("hex");
359
+ if (!digest || digest !== expectedDigest) {
360
+ this._rejectBlobTransfer(requestId, `Python worker blob "${name}" failed SHA-256 verification`);
361
+ return;
362
+ }
363
+ pending.blobTransfers?.delete(name);
364
+ pending.completedBlobs ??= Object.create(null);
365
+ pending.completedBlobs[name] = blob;
366
+ }
367
+ /**
368
+ * A frame decoded fine off the wire (valid msgpack) but failed its
369
+ * `@nodetool-ai/protocol` schema — a worker-side protocol bug, not a
370
+ * transport desync. Unlike an undecodable frame (bad length prefix,
371
+ * corrupt msgpack — see each transport's `_failProtocol`), this does NOT
372
+ * tear down the connection: only the request the malformed frame carries
373
+ * a `request_id` for is failed, so a concurrent request already in flight
374
+ * still settles normally.
375
+ *
376
+ * A frame with no (or non-string) `request_id` can't be attributed to any
377
+ * pending request — logged and dropped, matching the dispatcher's
378
+ * existing silent-ignore behavior for frame types/ids it doesn't
379
+ * recognize.
380
+ */
381
+ _handleInvalidFrame(type, requestId, reason) {
382
+ log.warn(`Rejected malformed Python bridge frame (type=${type ?? "<unknown>"}, request_id=${requestId ?? "<none>"}): ${reason ?? "failed schema validation"}`);
383
+ if (!requestId)
384
+ return;
385
+ const err = new Error(`Received malformed '${type ?? "<unknown>"}' frame from Python worker: ${reason ?? "failed schema validation"}`);
386
+ const streamReq = this._pendingStream.get(requestId);
387
+ if (streamReq) {
388
+ this._pendingStream.delete(requestId);
389
+ this._pendingComfyEvents.delete(requestId);
390
+ this._pendingBlenderEvents.delete(requestId);
391
+ streamReq.reject(err);
392
+ return;
393
+ }
394
+ const pending = this._pending.get(requestId);
395
+ if (pending) {
396
+ this._pending.delete(requestId);
397
+ pending.reject(err);
398
+ }
399
+ }
400
+ _rejectAllPending(error) {
401
+ for (const [, req] of this._pending) {
402
+ req.reject(error);
403
+ }
404
+ this._pending.clear();
405
+ for (const [, req] of this._pendingStream) {
406
+ req.reject(error);
407
+ }
408
+ this._pendingStream.clear();
409
+ this._pendingComfyEvents.clear();
410
+ this._pendingBlenderEvents.clear();
411
+ }
412
+ // ── Discover ───────────────────────────────────────────────────────
413
+ async _discover() {
414
+ const requestId = randomUUID();
415
+ // Bound the discover RPC: _openTransport resolves as soon as the worker
416
+ // signals readiness, so a worker that becomes READY but never answers
417
+ // discover (wedged/hung post-ready init) would leave connect() pending
418
+ // forever. Mirror _getWorkerStatusWithTimeout's timeout handling.
419
+ const timeoutMs = this._options.statusTimeoutMs ?? DEFAULT_STATUS_TIMEOUT_MS;
420
+ return new Promise((resolve, reject) => {
421
+ let timer;
422
+ this._pending.set(requestId, {
423
+ resolve: () => {
424
+ if (timer)
425
+ clearTimeout(timer);
426
+ this._pending.delete(requestId);
427
+ resolve();
428
+ },
429
+ reject: (err) => {
430
+ if (timer)
431
+ clearTimeout(timer);
432
+ this._pending.delete(requestId);
433
+ reject(err);
434
+ }
435
+ });
436
+ if (timeoutMs > 0) {
437
+ timer = setTimeout(() => {
438
+ this._pending.delete(requestId);
439
+ reject(new Error(`Python worker discover timed out after ${timeoutMs}ms.`));
440
+ }, timeoutMs);
441
+ }
442
+ try {
443
+ this._send({ type: "discover", request_id: requestId, data: {} });
444
+ }
445
+ catch (err) {
446
+ if (timer)
447
+ clearTimeout(timer);
448
+ this._pending.delete(requestId);
449
+ reject(err instanceof Error ? err : new Error(String(err)));
450
+ }
451
+ });
452
+ }
453
+ // ── Node execution ─────────────────────────────────────────────────
454
+ /**
455
+ * Snake-case the run identity onto the `execute` / `execute.stream` payload,
456
+ * dropping fields the caller could not name so the worker sees an absent key
457
+ * rather than a null it has to special-case.
458
+ *
459
+ * Sent unconditionally, not gated on {@link supportsJobLifecycle}: these are
460
+ * extra dict entries, and a pre-v4 worker that reads `data["node_type"]`,
461
+ * `["fields"]`, `["secrets"]` and `["blobs"]` never looks at them. Gating
462
+ * them would only mean a worker that DOES understand them gets nothing
463
+ * whenever its `worker.status` hasn't landed yet.
464
+ */
465
+ _identityPayload(identity) {
466
+ if (!identity)
467
+ return {};
468
+ const payload = {};
469
+ if (identity.nodeId)
470
+ payload.node_id = identity.nodeId;
471
+ if (identity.jobId)
472
+ payload.job_id = identity.jobId;
473
+ if (identity.workflowId)
474
+ payload.workflow_id = identity.workflowId;
475
+ if (identity.userId)
476
+ payload.user_id = identity.userId;
477
+ if (identity.requiresVramGb != null) {
478
+ payload.requires_vram_gb = identity.requiresVramGb;
479
+ }
480
+ return payload;
481
+ }
482
+ async execute(nodeType, fields, secrets, blobs, onProgress, identity) {
483
+ const requestId = randomUUID();
484
+ const timeoutMs = this._options.executeTimeoutMs ?? DEFAULT_EXECUTE_TIMEOUT_MS;
485
+ log.debug("Python bridge execute dispatched", { nodeType, requestId });
486
+ const executeData = {
487
+ node_type: nodeType,
488
+ fields,
489
+ secrets,
490
+ blobs,
491
+ ...this._identityPayload(identity)
492
+ };
493
+ if (this._workerStatus?.protocol_version != null &&
494
+ this._workerStatus.protocol_version >= 5) {
495
+ executeData["blob_transfer"] = "chunked-v1";
496
+ }
497
+ const executePromise = new Promise((resolve, reject) => {
498
+ this._pending.set(requestId, { resolve, reject, onProgress });
499
+ try {
500
+ this._send({
501
+ type: "execute",
502
+ request_id: requestId,
503
+ data: executeData
504
+ });
505
+ }
506
+ catch (err) {
507
+ this._pending.delete(requestId);
508
+ reject(err instanceof Error ? err : new Error(String(err)));
509
+ }
510
+ });
511
+ if (timeoutMs <= 0) {
512
+ return executePromise;
513
+ }
514
+ let timer;
515
+ const timeoutPromise = new Promise((_, reject) => {
516
+ timer = setTimeout(() => {
517
+ if (!this._pending.has(requestId)) {
518
+ return;
519
+ }
520
+ this._pending.delete(requestId);
521
+ try {
522
+ this.cancel(requestId);
523
+ }
524
+ catch {
525
+ // Worker may already be gone; cancel is best-effort.
526
+ }
527
+ const stderrHint = this.getRecentStderrSummary(4);
528
+ reject(new Error(`Python node "${nodeType}" timed out after ${timeoutMs}ms waiting for the worker.` +
529
+ (stderrHint ? ` Recent stderr: ${stderrHint}` : "")));
530
+ }, timeoutMs);
531
+ });
532
+ try {
533
+ return await Promise.race([executePromise, timeoutPromise]);
534
+ }
535
+ finally {
536
+ if (timer) {
537
+ clearTimeout(timer);
538
+ }
539
+ }
540
+ }
541
+ async *executeStream(nodeType, fields, secrets, blobs, onProgress, identity) {
542
+ const requestId = randomUUID();
543
+ const chunks = [];
544
+ let done = false;
545
+ let error = null;
546
+ let finalResult = null;
547
+ let emittedCount = 0;
548
+ let resolveWait = null;
549
+ if (onProgress) {
550
+ this._pending.set(requestId, {
551
+ resolve: () => undefined,
552
+ reject: () => undefined,
553
+ onProgress
554
+ });
555
+ }
556
+ const onChunk = (chunk) => {
557
+ chunks.push({
558
+ outputs: chunk.outputs ?? {},
559
+ blobs: chunk.blobs ?? {}
560
+ });
561
+ if (resolveWait) {
562
+ resolveWait();
563
+ resolveWait = null;
564
+ }
565
+ };
566
+ const streamPromise = new Promise((resolve, reject) => {
567
+ this._pendingStream.set(requestId, { resolve, reject, onChunk });
568
+ });
569
+ streamPromise
570
+ .then((result) => {
571
+ finalResult = {
572
+ outputs: result.outputs ?? {},
573
+ blobs: result.blobs ?? {}
574
+ };
575
+ done = true;
576
+ this._pending.delete(requestId);
577
+ if (resolveWait) {
578
+ resolveWait();
579
+ resolveWait = null;
580
+ }
581
+ })
582
+ .catch((err) => {
583
+ error = err;
584
+ done = true;
585
+ this._pending.delete(requestId);
586
+ if (resolveWait) {
587
+ resolveWait();
588
+ resolveWait = null;
589
+ }
590
+ });
591
+ try {
592
+ this._send({
593
+ type: "execute.stream",
594
+ request_id: requestId,
595
+ data: {
596
+ node_type: nodeType,
597
+ fields,
598
+ secrets,
599
+ blobs,
600
+ ...this._identityPayload(identity)
601
+ }
602
+ });
603
+ while (true) {
604
+ while (chunks.length > 0) {
605
+ emittedCount += 1;
606
+ yield chunks.shift();
607
+ }
608
+ if (done)
609
+ break;
610
+ if (error)
611
+ throw error;
612
+ await new Promise((resolve) => {
613
+ resolveWait = resolve;
614
+ });
615
+ }
616
+ if (error)
617
+ throw error;
618
+ if (emittedCount === 0 && finalResult) {
619
+ yield finalResult;
620
+ }
621
+ }
622
+ finally {
623
+ // If the stream never reached its terminal frame (consumer abandoned the
624
+ // generator via break/return, or the initial _send threw), the worker is
625
+ // still producing output nobody reads. Cancel it and release pending
626
+ // state so chunks[] stops growing and the entries don't leak.
627
+ if (!done) {
628
+ this._pending.delete(requestId);
629
+ this._pendingStream.delete(requestId);
630
+ try {
631
+ this.cancel(requestId);
632
+ }
633
+ catch {
634
+ // Worker may already be gone; cancel is best-effort.
635
+ }
636
+ }
637
+ }
638
+ }
639
+ cancel(requestId) {
640
+ this._send({ type: "cancel", request_id: requestId, data: {} });
641
+ }
642
+ getNodeMetadata() {
643
+ return this._nodeMetadata;
644
+ }
645
+ getLoadErrors() {
646
+ return this._loadErrors;
647
+ }
648
+ async getWorkerStatus() {
649
+ const requestId = randomUUID();
650
+ const result = await new Promise((resolve, reject) => {
651
+ this._pendingStream.set(requestId, {
652
+ resolve,
653
+ reject,
654
+ onChunk: () => { }
655
+ });
656
+ try {
657
+ this._send({
658
+ type: "worker.status",
659
+ request_id: requestId,
660
+ data: {}
661
+ });
662
+ }
663
+ catch (err) {
664
+ this._pendingStream.delete(requestId);
665
+ reject(err instanceof Error ? err : new Error(String(err)));
666
+ }
667
+ });
668
+ this._workerStatus = workerStatusSchema.parse(result);
669
+ // A status reply that carries no `load_errors` leaves the ones `discover`
670
+ // reported in place; only a reply that names the field replaces them.
671
+ this._loadErrors =
672
+ result["load_errors"] == null
673
+ ? this._loadErrors
674
+ : this._workerStatus.load_errors;
675
+ return this._workerStatus;
676
+ }
677
+ /**
678
+ * getWorkerStatus() guarded by a timeout so a silent worker cannot hang
679
+ * connect() forever. On timeout we reject this single call and clean up its
680
+ * pending entry + timer; connect()'s catch then logs and proceeds.
681
+ *
682
+ * Protected so transports that override connect() (e.g. the WebSocket
683
+ * bridge, which wraps the whole RPC phase in its own timeout) can still
684
+ * honor statusTimeoutMs for the status sub-call.
685
+ */
686
+ async _getWorkerStatusWithTimeout() {
687
+ const timeoutMs = this._options.statusTimeoutMs ?? DEFAULT_STATUS_TIMEOUT_MS;
688
+ if (timeoutMs <= 0) {
689
+ return this.getWorkerStatus();
690
+ }
691
+ const requestId = randomUUID();
692
+ let timer;
693
+ const statusPromise = new Promise((resolve, reject) => {
694
+ this._pendingStream.set(requestId, {
695
+ resolve,
696
+ reject,
697
+ onChunk: () => { }
698
+ });
699
+ try {
700
+ this._send({
701
+ type: "worker.status",
702
+ request_id: requestId,
703
+ data: {}
704
+ });
705
+ }
706
+ catch (err) {
707
+ this._pendingStream.delete(requestId);
708
+ reject(err instanceof Error ? err : new Error(String(err)));
709
+ }
710
+ });
711
+ const timeoutPromise = new Promise((_, reject) => {
712
+ timer = setTimeout(() => {
713
+ // Reject only this single call and drop its pending entry so a late
714
+ // response is ignored rather than resolving a dead promise.
715
+ this._pendingStream.delete(requestId);
716
+ reject(new Error(`Python worker status timed out after ${timeoutMs}ms.`));
717
+ }, timeoutMs);
718
+ });
719
+ try {
720
+ const result = await Promise.race([statusPromise, timeoutPromise]);
721
+ this._workerStatus = workerStatusSchema.parse(result);
722
+ // See getWorkerStatus: absence keeps the discover-reported errors.
723
+ this._loadErrors =
724
+ result["load_errors"] == null
725
+ ? this._loadErrors
726
+ : this._workerStatus.load_errors;
727
+ return this._workerStatus;
728
+ }
729
+ finally {
730
+ if (timer) {
731
+ clearTimeout(timer);
732
+ }
733
+ }
734
+ }
735
+ hasNodeType(nodeType) {
736
+ return this._nodeMetadata.some((n) => n.node_type === nodeType);
737
+ }
738
+ get isConnected() {
739
+ return this._connected;
740
+ }
741
+ /**
742
+ * Whether this bridge has a worker it can attempt to connect to. Gates
743
+ * boot-time auto-connect — the server only eagerly calls ensureConnected()
744
+ * when this returns true. The base default is true (a WebSocket bridge always
745
+ * has a configured URL); the stdio subclass overrides it to report whether a
746
+ * local Python interpreter was found.
747
+ */
748
+ isAvailable() {
749
+ return true;
750
+ }
751
+ // ── Provider bridge methods ────────────────────────────────────────
752
+ async listProviders() {
753
+ const result = await this._providerCall("provider.list", {});
754
+ return result.providers;
755
+ }
756
+ async getProviderModels(providerId, modelType, secrets) {
757
+ const result = await this._providerCall("provider.models", {
758
+ provider: providerId,
759
+ model_type: modelType,
760
+ secrets: secrets ?? {}
761
+ });
762
+ return result.models;
763
+ }
764
+ async providerGenerate(providerId, messages, model, options) {
765
+ const result = await this._providerCall("provider.generate", {
766
+ provider: providerId,
767
+ messages,
768
+ model,
769
+ ...options
770
+ });
771
+ return result.message;
772
+ }
773
+ async *providerStream(providerId, messages, model, options) {
774
+ const requestId = randomUUID();
775
+ const chunks = [];
776
+ let done = false;
777
+ let error = null;
778
+ let resolveWait = null;
779
+ const onChunk = (chunk) => {
780
+ chunks.push(chunk);
781
+ if (resolveWait) {
782
+ resolveWait();
783
+ resolveWait = null;
784
+ }
785
+ };
786
+ const streamPromise = new Promise((resolve, reject) => {
787
+ this._pendingStream.set(requestId, { resolve, reject, onChunk });
788
+ });
789
+ streamPromise
790
+ .then(() => {
791
+ done = true;
792
+ if (resolveWait) {
793
+ resolveWait();
794
+ resolveWait = null;
795
+ }
796
+ })
797
+ .catch((err) => {
798
+ error = err;
799
+ done = true;
800
+ if (resolveWait) {
801
+ resolveWait();
802
+ resolveWait = null;
803
+ }
804
+ });
805
+ try {
806
+ this._send({
807
+ type: "provider.stream",
808
+ request_id: requestId,
809
+ data: { provider: providerId, messages, model, ...options }
810
+ });
811
+ while (true) {
812
+ while (chunks.length > 0)
813
+ yield chunks.shift();
814
+ if (done)
815
+ break;
816
+ if (error)
817
+ throw error;
818
+ await new Promise((resolve) => {
819
+ resolveWait = resolve;
820
+ });
821
+ }
822
+ if (error)
823
+ throw error;
824
+ }
825
+ finally {
826
+ // Cancel the worker-side stream and drop pending state if the generator
827
+ // is torn down before its terminal frame (consumer break/return, or a
828
+ // throwing initial _send).
829
+ if (!done) {
830
+ this._pendingStream.delete(requestId);
831
+ try {
832
+ this.cancel(requestId);
833
+ }
834
+ catch {
835
+ // Worker may already be gone; cancel is best-effort.
836
+ }
837
+ }
838
+ }
839
+ }
840
+ async providerTextToImage(providerId, params, secrets, signal) {
841
+ const result = await this._providerBlobCall("provider.text_to_image", {
842
+ provider: providerId,
843
+ params,
844
+ secrets: secrets ?? {}
845
+ }, signal);
846
+ return result.blobs.image;
847
+ }
848
+ async providerImageToImage(providerId, image, params, secrets, signal) {
849
+ const result = await this._providerBlobCall("provider.image_to_image", {
850
+ provider: providerId,
851
+ image,
852
+ params,
853
+ secrets: secrets ?? {}
854
+ }, signal);
855
+ return result.blobs.image;
856
+ }
857
+ async providerTextToVideo(providerId, params, secrets, signal) {
858
+ const result = await this._providerBlobCall("provider.text_to_video", {
859
+ provider: providerId,
860
+ params,
861
+ secrets: secrets ?? {}
862
+ }, signal);
863
+ return result.blobs.video;
864
+ }
865
+ async providerImageToVideo(providerId, image, params, secrets, signal) {
866
+ const result = await this._providerBlobCall("provider.image_to_video", {
867
+ provider: providerId,
868
+ image,
869
+ params,
870
+ secrets: secrets ?? {}
871
+ }, signal);
872
+ return result.blobs.video;
873
+ }
874
+ async providerReferenceToVideo(providerId, inputs, params, secrets, signal) {
875
+ const { images, videos } = inputs;
876
+ if (!Array.isArray(images) ||
877
+ !Array.isArray(videos) ||
878
+ (images.length === 0 && videos.length === 0)) {
879
+ throw new Error("reference_to_video requires at least one reference image or video");
880
+ }
881
+ if ([...images, ...videos].some((bytes) => !(bytes instanceof Uint8Array))) {
882
+ throw new Error("reference_to_video reference media must be binary buffers");
883
+ }
884
+ if ([...images, ...videos].some((bytes) => bytes.byteLength === 0)) {
885
+ throw new Error("reference_to_video reference media must be non-empty");
886
+ }
887
+ const totalBytes = [...images, ...videos].reduce((total, bytes) => total + bytes.byteLength, 0);
888
+ if (totalBytes > 192 * 1024 * 1024) {
889
+ throw new Error(`reference_to_video reference media is ${totalBytes} bytes, exceeding the 201326592 byte limit`);
890
+ }
891
+ const result = await this._providerBlobCall("provider.reference_to_video", {
892
+ provider: providerId,
893
+ reference_images: images,
894
+ reference_videos: videos,
895
+ params,
896
+ secrets: secrets ?? {}
897
+ }, signal);
898
+ return result.blobs.video;
899
+ }
900
+ async providerTextToAudio(providerId, params, secrets) {
901
+ const result = await this._providerBlobCall("provider.text_to_audio", {
902
+ provider: providerId,
903
+ params,
904
+ secrets: secrets ?? {}
905
+ });
906
+ return result.blobs.audio;
907
+ }
908
+ async providerTTSEncoded(providerId, params, secrets) {
909
+ const result = await this._providerBlobCall("provider.tts_encoded", {
910
+ provider: providerId,
911
+ params,
912
+ secrets: secrets ?? {}
913
+ });
914
+ return result.blobs.audio;
915
+ }
916
+ async *providerTTS(providerId, text, model, options) {
917
+ const requestId = randomUUID();
918
+ const chunks = [];
919
+ let done = false;
920
+ let error = null;
921
+ let resolveWait = null;
922
+ const onChunk = (chunk) => {
923
+ const blobs = chunk.blobs;
924
+ if (blobs?.audio)
925
+ chunks.push(blobs.audio);
926
+ if (resolveWait) {
927
+ resolveWait();
928
+ resolveWait = null;
929
+ }
930
+ };
931
+ const streamPromise = new Promise((resolve, reject) => {
932
+ this._pendingStream.set(requestId, { resolve, reject, onChunk });
933
+ });
934
+ streamPromise
935
+ .then(() => {
936
+ done = true;
937
+ if (resolveWait) {
938
+ resolveWait();
939
+ resolveWait = null;
940
+ }
941
+ })
942
+ .catch((err) => {
943
+ error = err;
944
+ done = true;
945
+ if (resolveWait) {
946
+ resolveWait();
947
+ resolveWait = null;
948
+ }
949
+ });
950
+ try {
951
+ this._send({
952
+ type: "provider.tts",
953
+ request_id: requestId,
954
+ data: { provider: providerId, text, model, ...options }
955
+ });
956
+ while (true) {
957
+ while (chunks.length > 0)
958
+ yield chunks.shift();
959
+ if (done)
960
+ break;
961
+ if (error)
962
+ throw error;
963
+ await new Promise((resolve) => {
964
+ resolveWait = resolve;
965
+ });
966
+ }
967
+ if (error)
968
+ throw error;
969
+ }
970
+ finally {
971
+ // Cancel the worker-side stream and drop pending state if the generator
972
+ // is torn down before its terminal frame (consumer break/return, or a
973
+ // throwing initial _send).
974
+ if (!done) {
975
+ this._pendingStream.delete(requestId);
976
+ try {
977
+ this.cancel(requestId);
978
+ }
979
+ catch {
980
+ // Worker may already be gone; cancel is best-effort.
981
+ }
982
+ }
983
+ }
984
+ }
985
+ async providerASR(providerId, audio, model, options) {
986
+ const result = await this._providerCall("provider.asr", {
987
+ provider: providerId,
988
+ audio,
989
+ model,
990
+ ...options
991
+ });
992
+ const r = result;
993
+ return {
994
+ text: r.text,
995
+ chunks: r.chunks
996
+ };
997
+ }
998
+ async providerEmbedding(providerId, text, model, dimensions) {
999
+ const result = await this._providerCall("provider.embedding", {
1000
+ provider: providerId,
1001
+ text,
1002
+ model,
1003
+ dimensions
1004
+ });
1005
+ return result.embeddings;
1006
+ }
1007
+ // ── Worker model management (HuggingFace cache) ───────────────────────
1008
+ /**
1009
+ * List the models cached on the worker's HF_HOME. Cache-only (no network):
1010
+ * each entry is a UnifiedModel JSON with `downloaded` forced true. Requires a
1011
+ * worker that speaks bridge protocol v2 ({@link supportsModelManagement}).
1012
+ */
1013
+ async listCachedModels() {
1014
+ const result = await this._providerCall("models.list_cached", {});
1015
+ return result.models;
1016
+ }
1017
+ /**
1018
+ * Download a model onto the worker's persistent cache, streaming progress.
1019
+ *
1020
+ * The worker emits ordered `progress` frames (start → 0+ progress →
1021
+ * completed) followed by a terminal `result`. Each `progress` frame's `data`
1022
+ * is forwarded to {@link onProgress} verbatim; the promise resolves on the
1023
+ * terminal `result` and rejects on an `error` frame.
1024
+ *
1025
+ * The request is registered in BOTH pending maps: `_pending` (with
1026
+ * onProgress) so {@link _handleMessage} routes `progress` frames to the
1027
+ * callback, and `_pendingStream` for the terminal `result`/`error`. Both are
1028
+ * cleaned up on settle.
1029
+ *
1030
+ * Pass a stable `requestId` to make the download cancellable by a known key:
1031
+ * {@link cancelModelDownload}(requestId) then reaches this exact download.
1032
+ * Defaults to a random id when the caller has no need to cancel.
1033
+ *
1034
+ * Unlike a plain provider RPC, this download is settled defensively: an
1035
+ * inactivity timer (reset on every progress frame) rejects the promise if the
1036
+ * worker hangs mid-download, and {@link cancelModelDownload} rejects it
1037
+ * immediately. Either path clears BOTH pending maps so nothing leaks even when
1038
+ * no terminal `result`/`error` ever arrives.
1039
+ */
1040
+ async downloadModel(req, onProgress, requestId = randomUUID()) {
1041
+ // A blank token is not a credential: `Authorization: Bearer ` fails
1042
+ // differently than sending no header at all, and worse for a public repo.
1043
+ // Drop it here so every caller gets the rule, matching the worker's own
1044
+ // handling (nodetool-core#1008).
1045
+ const { token, ...rest } = req;
1046
+ const payload = typeof token === "string" && token.trim()
1047
+ ? { ...rest, token: token.trim() }
1048
+ : rest;
1049
+ return this._streamingDownload("models.download", payload,
1050
+ // SAFETY: the worker's `models.download` progress frame carries exactly
1051
+ // these fields; unlike the comfy update shapes, `ModelDownloadUpdate`
1052
+ // declares no index signature, so the frame record does not overlap it.
1053
+ (u) => onProgress(u), requestId);
1054
+ }
1055
+ /** Prepare an image-owned model through an advertised worker backend. */
1056
+ async prepareModel(req, onProgress, requestId = randomUUID()) {
1057
+ const { token, ...rest } = req;
1058
+ const payload = typeof token === "string" && token.trim()
1059
+ ? { ...rest, token: token.trim() }
1060
+ : rest;
1061
+ return this._streamingDownload("models.prepare", payload, (update) => onProgress(update), requestId);
1062
+ }
1063
+ /**
1064
+ * Shared engine for the streaming-download RPCs (`models.download`,
1065
+ * `comfy.models.download`): a request that emits ordered `progress` frames
1066
+ * then a terminal `result`/`error`. Registers in BOTH pending maps and settles
1067
+ * defensively — an inactivity watchdog (reset on every progress frame) and
1068
+ * {@link cancel} both clear both maps so nothing leaks even if no terminal
1069
+ * frame ever arrives. See {@link downloadModel} for the full rationale.
1070
+ */
1071
+ _streamingDownload(type, data, onProgress, requestId) {
1072
+ const idleTimeoutMs = this._options.downloadIdleTimeoutMs ?? DEFAULT_DOWNLOAD_IDLE_TIMEOUT_MS;
1073
+ return new Promise((resolve, reject) => {
1074
+ let settled = false;
1075
+ let timer;
1076
+ const settle = (fn) => {
1077
+ if (settled)
1078
+ return;
1079
+ settled = true;
1080
+ if (timer)
1081
+ clearTimeout(timer);
1082
+ this._pending.delete(requestId);
1083
+ this._pendingStream.delete(requestId);
1084
+ fn();
1085
+ };
1086
+ // Inactivity watchdog: a download making steady progress keeps resetting
1087
+ // the clock, but a worker that goes silent mid-download is cancelled and
1088
+ // rejected instead of leaking its pending entries forever.
1089
+ const armIdleTimer = () => {
1090
+ if (idleTimeoutMs <= 0)
1091
+ return;
1092
+ if (timer)
1093
+ clearTimeout(timer);
1094
+ timer = setTimeout(() => {
1095
+ try {
1096
+ this.cancel(requestId);
1097
+ }
1098
+ catch {
1099
+ // Worker may already be gone; cancel is best-effort.
1100
+ }
1101
+ const stderrHint = this.getRecentStderrSummary(4);
1102
+ settle(() => reject(new Error(`Download "${requestId}" stalled: no progress for ${idleTimeoutMs}ms.` +
1103
+ (stderrHint ? ` Recent stderr: ${stderrHint}` : ""))));
1104
+ }, idleTimeoutMs);
1105
+ };
1106
+ this._pending.set(requestId, {
1107
+ resolve: () => undefined,
1108
+ reject: () => undefined,
1109
+ onProgress: (event) => {
1110
+ armIdleTimer();
1111
+ onProgress({ ...event });
1112
+ }
1113
+ });
1114
+ this._pendingStream.set(requestId, {
1115
+ resolve: () => settle(resolve),
1116
+ reject: (err) => settle(() => reject(err)),
1117
+ onChunk: () => { }
1118
+ });
1119
+ armIdleTimer();
1120
+ try {
1121
+ this._send({ type, request_id: requestId, data });
1122
+ }
1123
+ catch (err) {
1124
+ settle(() => reject(err instanceof Error ? err : new Error(String(err))));
1125
+ }
1126
+ });
1127
+ }
1128
+ /**
1129
+ * Cancel an in-flight {@link downloadModel} by its request id. Sends the
1130
+ * cancel frame AND settles the local promise immediately by rejecting it —
1131
+ * the worker may never emit a terminal frame after a cancel (or may be hung),
1132
+ * so we cannot rely on `result`/`error` to clean up. Rejecting through the
1133
+ * pending-stream entry clears both pending maps; a no-op if the id is unknown.
1134
+ */
1135
+ cancelModelDownload(requestId) {
1136
+ // Best-effort per the JSDoc: cancel() -> _send() throws when the transport
1137
+ // is down, but this must stay a no-op so a disconnected caller isn't hit
1138
+ // with 'Not connected' from a documented safe cancel.
1139
+ try {
1140
+ this.cancel(requestId);
1141
+ }
1142
+ catch {
1143
+ // Worker may already be gone; cancel is best-effort.
1144
+ }
1145
+ const streamReq = this._pendingStream.get(requestId);
1146
+ if (streamReq) {
1147
+ streamReq.reject(new Error(`Model download "${requestId}" was cancelled.`));
1148
+ }
1149
+ }
1150
+ /** Delete a cached model from the worker's HF_HOME. Returns whether it existed. */
1151
+ async deleteCachedModel(repoId) {
1152
+ const result = await this._providerCall("models.delete", {
1153
+ repo_id: repoId
1154
+ });
1155
+ return Boolean(result.deleted);
1156
+ }
1157
+ /**
1158
+ * Whether the attached worker speaks bridge protocol v2+ and therefore
1159
+ * supports the models.* RPC. This is the per-capability gate that lets an
1160
+ * older v1 worker connect normally (it sits above the hard floor) and simply
1161
+ * not expose worker model management — the Worker scope is hidden/disabled
1162
+ * rather than erroring. `models.*` was introduced in protocol v2, so the
1163
+ * floor here is a fixed 2, independent of BRIDGE_PROTOCOL_VERSION.
1164
+ */
1165
+ supportsModelManagement() {
1166
+ return (this._workerStatus?.protocol_version ?? 0) >= 2;
1167
+ }
1168
+ /** Whether the attached image advertises a specific preparation backend. */
1169
+ supportsModelPreparation(backend) {
1170
+ return ((this._workerStatus?.protocol_version ?? 0) >= 5 &&
1171
+ (this._workerStatus?.model_prepare_backends ?? []).includes(backend));
1172
+ }
1173
+ /**
1174
+ * Drop loaded model weights on the worker. The worker reclaims reactively on
1175
+ * its own thresholds; this is the path for what only the JS side knows — the
1176
+ * user switched workflows, another process wants the GPU, the worker is
1177
+ * idle. Introduced in protocol v4, so the floor here is a fixed 4.
1178
+ *
1179
+ * A pre-v4 worker would answer `Unknown message type`, so this resolves to an
1180
+ * empty eviction instead of sending: eviction is an optimization, and a host
1181
+ * asking to free memory on a worker that cannot should not have to branch.
1182
+ */
1183
+ async evictModels(req = {}) {
1184
+ if (!this.supportsJobLifecycle()) {
1185
+ return { evicted: [] };
1186
+ }
1187
+ const result = await this._providerCall("models.evict", { ...req });
1188
+ const evictResult = {
1189
+ // SAFETY: the worker answers `models.evict` with a list of model ids; a
1190
+ // reply that is not an array is treated as evicting nothing.
1191
+ evicted: Array.isArray(result.evicted) ? result.evicted : []
1192
+ };
1193
+ if (isNumber(result.freed_vram_gb)) {
1194
+ evictResult.freed_vram_gb = result.freed_vram_gb;
1195
+ }
1196
+ return evictResult;
1197
+ }
1198
+ // ── Run boundary (bridge protocol v4+) ─────────────────────────────────
1199
+ /**
1200
+ * Whether the attached worker speaks bridge protocol v4+ and therefore
1201
+ * understands `job.start` / `job.end` / `models.evict`. Per-capability soft
1202
+ * gate, like {@link supportsModelManagement}.
1203
+ *
1204
+ * This gates only the new message *types*. The identity fields v4 adds to
1205
+ * `execute` ride along regardless — see {@link _identityPayload}.
1206
+ */
1207
+ supportsJobLifecycle() {
1208
+ return (this._workerStatus?.protocol_version ?? 0) >= 4;
1209
+ }
1210
+ /**
1211
+ * Open a run boundary. Optional: the worker needs no `job.start` to
1212
+ * attribute an execution (every `execute` carries its own `job_id`), so this
1213
+ * exists as the one place to do a single reclaim pass per run instead of one
1214
+ * per node.
1215
+ */
1216
+ async jobStart(job) {
1217
+ await this._jobBoundary("job.start", job);
1218
+ }
1219
+ /**
1220
+ * Close a run boundary: the worker's nodes for this job are retired and
1221
+ * their models become eligible for release. This is the caller
1222
+ * `release_nodes()` never had — without it the worker's model cache grows
1223
+ * across runs and is only ever trimmed reactively under memory pressure.
1224
+ *
1225
+ * Must fire on abnormal termination too (cancelled, client disconnected, run
1226
+ * abandoned), which is why `ExecutionSession` calls it from the same
1227
+ * `finally` that closes the bridge rather than off the success path.
1228
+ */
1229
+ async jobEnd(job) {
1230
+ await this._jobBoundary("job.end", job);
1231
+ }
1232
+ /**
1233
+ * Send one boundary frame. Never rejects: a boundary is bookkeeping, and a
1234
+ * `job.end` that fails on a worker already tearing down must not turn a
1235
+ * finished run into a failed one. Failures are logged and swallowed.
1236
+ */
1237
+ async _jobBoundary(type, job) {
1238
+ if (!this.supportsJobLifecycle())
1239
+ return;
1240
+ const data = { job_id: job.jobId };
1241
+ if (job.workflowId)
1242
+ data.workflow_id = job.workflowId;
1243
+ if (job.userId)
1244
+ data.user_id = job.userId;
1245
+ if (job.reason)
1246
+ data.reason = job.reason;
1247
+ try {
1248
+ await this._providerCall(type, data);
1249
+ }
1250
+ catch (err) {
1251
+ log.warn(`Python bridge ${type} failed`, {
1252
+ jobId: job.jobId,
1253
+ error: err instanceof Error ? err.message : String(err)
1254
+ });
1255
+ }
1256
+ }
1257
+ // ── ComfyUI proxy (bridge protocol v3+) ────────────────────────────────
1258
+ /**
1259
+ * Whether the attached worker fronts a ComfyUI server and speaks `comfy.*`.
1260
+ * Requires protocol v3+ (the `comfy.*` family) AND a `worker.status.comfy`
1261
+ * block reporting `enabled: true` — not every v3 worker has a ComfyUI, so
1262
+ * route ComfyUI jobs only where this is true. Per-capability soft gate, like
1263
+ * {@link supportsModelManagement}.
1264
+ */
1265
+ supportsComfy() {
1266
+ const status = this._workerStatus;
1267
+ return ((status?.protocol_version ?? 0) >= 3 && status?.comfy?.enabled === true);
1268
+ }
1269
+ /** The last-known `comfy` block from `worker.status`, or null. */
1270
+ getComfyStatus() {
1271
+ return this._workerStatus?.comfy ?? null;
1272
+ }
1273
+ /**
1274
+ * Submit a ComfyUI workflow and drain its `comfy.event` lifecycle.
1275
+ *
1276
+ * `comfy.execute` streams `queued → queue → started/cached → executing →
1277
+ * progress → node_output → preview → completed/cancelled` as dedicated
1278
+ * `comfy.event` frames (routed to {@link onEvent}), then settles with a
1279
+ * terminal `result` (resolve) or `error` (reject) — `result` is always last.
1280
+ * The event callback is registered in {@link _pendingComfyEvents} and the
1281
+ * terminal frame in {@link _pendingStream}; both are cleared on settle.
1282
+ *
1283
+ * Cancellable via {@link cancelComfyExecute}(requestId): pass a stable
1284
+ * `requestId` (or reuse the returned default) to reach this exact run. Prefer
1285
+ * that over the bare {@link cancel} — a plain cancel frame does not settle the
1286
+ * local promise, so if the worker never emits a terminal frame the promise
1287
+ * would hang and the pending maps would leak.
1288
+ */
1289
+ comfyExecute(workflow, options = {}, onEvent, requestId = randomUUID()) {
1290
+ // The worker's field is `workflow`, not `prompt` — see the comfy.execute
1291
+ // request schema in nodetool-core's docs/comfy-proxy.md.
1292
+ const data = { workflow };
1293
+ if (options.blobs)
1294
+ data.blobs = options.blobs;
1295
+ if (options.previews)
1296
+ data.previews = true;
1297
+ if (options.includeTemp)
1298
+ data.include_temp = true;
1299
+ if (options.timeout != null)
1300
+ data.timeout = options.timeout;
1301
+ return new Promise((resolve, reject) => {
1302
+ let settled = false;
1303
+ const settle = (fn) => {
1304
+ if (settled)
1305
+ return;
1306
+ settled = true;
1307
+ this._pendingStream.delete(requestId);
1308
+ this._pendingComfyEvents.delete(requestId);
1309
+ fn();
1310
+ };
1311
+ if (onEvent)
1312
+ this._pendingComfyEvents.set(requestId, onEvent);
1313
+ this._pendingStream.set(requestId, {
1314
+ resolve: (result) => settle(() => resolve(result)),
1315
+ reject: (err) => settle(() => reject(err)),
1316
+ onChunk: () => { }
1317
+ });
1318
+ try {
1319
+ this._send({ type: "comfy.execute", request_id: requestId, data });
1320
+ }
1321
+ catch (err) {
1322
+ settle(() => reject(err instanceof Error ? err : new Error(String(err))));
1323
+ }
1324
+ });
1325
+ }
1326
+ /**
1327
+ * Cancel an in-flight {@link comfyExecute} by request id. Sends the cancel
1328
+ * frame AND settles the local promise by rejecting it — the worker may never
1329
+ * emit a terminal `result`/`error` after a cancel (or may be hung), so we
1330
+ * cannot rely on one to clean up. Rejecting through the pending-stream entry
1331
+ * runs comfyExecute's settle(), clearing BOTH `_pendingStream` and
1332
+ * `_pendingComfyEvents`. A no-op if the id is unknown. Mirrors
1333
+ * {@link cancelModelDownload}.
1334
+ */
1335
+ cancelComfyExecute(requestId) {
1336
+ // Best-effort (see cancelModelDownload): swallow a 'Not connected' from
1337
+ // _send so a disconnected cancel stays the documented no-op.
1338
+ try {
1339
+ this.cancel(requestId);
1340
+ }
1341
+ catch {
1342
+ // Worker may already be gone; cancel is best-effort.
1343
+ }
1344
+ const streamReq = this._pendingStream.get(requestId);
1345
+ if (streamReq) {
1346
+ streamReq.reject(new Error(`ComfyUI execution "${requestId}" was cancelled.`));
1347
+ }
1348
+ }
1349
+ // ── Blender worker ────────────────────────────────────────────────────
1350
+ /**
1351
+ * Whether the attached worker can run Blender and speaks `blender.*`.
1352
+ * Requires a `worker.status.blender` block reporting `enabled: true` — a
1353
+ * worker that says nothing about Blender (every worker that predates the
1354
+ * family) is treated as having none. Per-capability soft gate, like
1355
+ * {@link supportsComfy}. Deliberately no protocol-version floor: the
1356
+ * version bump rides with the worker image, which ships outside this
1357
+ * repository, so the flag alone decides.
1358
+ */
1359
+ supportsBlender() {
1360
+ return this._workerStatus?.blender?.enabled === true;
1361
+ }
1362
+ /** The last-known `blender` block from `worker.status`, or null. */
1363
+ getBlenderStatus() {
1364
+ return this._workerStatus?.blender ?? null;
1365
+ }
1366
+ /**
1367
+ * Run a Blender job and drain its `blender.event` progress stream.
1368
+ *
1369
+ * `blender.execute` streams `progress` events as dedicated `blender.event`
1370
+ * frames (routed to {@link onEvent}), then settles with a terminal
1371
+ * `result` (resolve) or `error` (reject) — `result` is always last. The
1372
+ * event callback is registered in {@link _pendingBlenderEvents} and the
1373
+ * terminal frame in {@link _pendingStream}; both are cleared on settle.
1374
+ *
1375
+ * Cancellable via {@link cancelBlenderExecute}(requestId): pass a stable
1376
+ * `requestId` (or reuse the returned default) to reach this exact run.
1377
+ * Prefer that over the bare {@link cancel} — a plain cancel frame does not
1378
+ * settle the local promise, so if the worker never emits a terminal frame
1379
+ * the promise would hang and the pending maps would leak.
1380
+ */
1381
+ blenderExecute(job, inputs, options = {}, onEvent, requestId = randomUUID()) {
1382
+ const data = { job, inputs };
1383
+ if (options.blobs)
1384
+ data.blobs = options.blobs;
1385
+ if (options.timeout != null)
1386
+ data.timeout = options.timeout;
1387
+ return new Promise((resolve, reject) => {
1388
+ let settled = false;
1389
+ const settle = (fn) => {
1390
+ if (settled)
1391
+ return;
1392
+ settled = true;
1393
+ this._pendingStream.delete(requestId);
1394
+ this._pendingBlenderEvents.delete(requestId);
1395
+ fn();
1396
+ };
1397
+ if (onEvent)
1398
+ this._pendingBlenderEvents.set(requestId, onEvent);
1399
+ this._pendingStream.set(requestId, {
1400
+ resolve: (result) => settle(() => resolve(result)),
1401
+ reject: (err) => settle(() => reject(err)),
1402
+ onChunk: () => { }
1403
+ });
1404
+ try {
1405
+ this._send({ type: "blender.execute", request_id: requestId, data });
1406
+ }
1407
+ catch (err) {
1408
+ settle(() => reject(err instanceof Error ? err : new Error(String(err))));
1409
+ }
1410
+ });
1411
+ }
1412
+ /**
1413
+ * Cancel an in-flight {@link blenderExecute} by request id. Sends the
1414
+ * cancel frame AND settles the local promise by rejecting it — the worker
1415
+ * may never emit a terminal `result`/`error` after a cancel (or may be
1416
+ * hung), so we cannot rely on one to clean up. Rejecting through the
1417
+ * pending-stream entry runs blenderExecute's settle(), clearing BOTH
1418
+ * `_pendingStream` and `_pendingBlenderEvents`. A no-op if the id is
1419
+ * unknown. Mirrors {@link cancelComfyExecute}.
1420
+ */
1421
+ cancelBlenderExecute(requestId) {
1422
+ // Best-effort (see cancelModelDownload): swallow a 'Not connected' from
1423
+ // _send so a disconnected cancel stays the documented no-op.
1424
+ try {
1425
+ this.cancel(requestId);
1426
+ }
1427
+ catch {
1428
+ // Worker may already be gone; cancel is best-effort.
1429
+ }
1430
+ const streamReq = this._pendingStream.get(requestId);
1431
+ if (streamReq) {
1432
+ streamReq.reject(new Error(`Blender execution "${requestId}" was cancelled.`));
1433
+ }
1434
+ }
1435
+ async comfyQueue() {
1436
+ return this._providerCall("comfy.queue", {});
1437
+ }
1438
+ async comfyInterrupt() {
1439
+ await this._providerCall("comfy.interrupt", {});
1440
+ }
1441
+ async comfyCancelPrompt(promptId) {
1442
+ await this._providerCall("comfy.cancel", { prompt_id: promptId });
1443
+ }
1444
+ async comfyUpload(filename, bytes, options = {}) {
1445
+ // The worker reads the payload from `data`, not `blob`.
1446
+ return this._providerCall("comfy.upload", {
1447
+ filename,
1448
+ data: bytes,
1449
+ ...options
1450
+ });
1451
+ }
1452
+ async comfyView(filename, options = {}) {
1453
+ return this._providerCall("comfy.view", { filename, ...options });
1454
+ }
1455
+ async comfyObjectInfo() {
1456
+ // The worker wraps the catalog as `{object_info: {...}}`; unwrap so callers
1457
+ // get the catalog itself, as this method's contract promises.
1458
+ const result = await this._providerCall("comfy.object_info", {});
1459
+ return (result.object_info ?? result);
1460
+ }
1461
+ async comfySystemStats() {
1462
+ return this._providerCall("comfy.system_stats", {});
1463
+ }
1464
+ async comfyStatus() {
1465
+ return comfyStatusInfoSchema.parse(await this._providerCall("comfy.status", {}));
1466
+ }
1467
+ async comfyFree(options = {}) {
1468
+ await this._providerCall("comfy.free", options);
1469
+ }
1470
+ async comfyModelsList(folder) {
1471
+ const result = await this._providerCall("comfy.models.list", {});
1472
+ const models = result.models ?? [];
1473
+ // The worker always returns the whole volume — there is no `folder` filter
1474
+ // in the comfy.models.list request schema — so narrow it here.
1475
+ return folder ? models.filter((m) => m.folder === folder) : models;
1476
+ }
1477
+ async comfyModelsDownload(req, onProgress, requestId = randomUUID()) {
1478
+ return this._streamingDownload("comfy.models.download", { ...req }, (u) => onProgress(u), requestId);
1479
+ }
1480
+ async comfyModelsDelete(folder, filename) {
1481
+ const result = await this._providerCall("comfy.models.delete", {
1482
+ folder,
1483
+ filename
1484
+ });
1485
+ return Boolean(result.deleted);
1486
+ }
1487
+ async _providerCall(type, data) {
1488
+ const requestId = randomUUID();
1489
+ return new Promise((resolve, reject) => {
1490
+ this._pendingStream.set(requestId, {
1491
+ resolve,
1492
+ reject,
1493
+ onChunk: () => { }
1494
+ });
1495
+ try {
1496
+ this._send({ type, request_id: requestId, data });
1497
+ }
1498
+ catch (err) {
1499
+ this._pendingStream.delete(requestId);
1500
+ reject(err instanceof Error ? err : new Error(String(err)));
1501
+ }
1502
+ });
1503
+ }
1504
+ /** Provider RPC variant that opts into protocol-v5 chunked result blobs. */
1505
+ async _providerBlobCall(type, data, signal) {
1506
+ const requestId = randomUUID();
1507
+ if (signal?.aborted) {
1508
+ throw new Error(`Provider request "${requestId}" was cancelled.`);
1509
+ }
1510
+ return new Promise((resolve, reject) => {
1511
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
1512
+ const onAbort = () => {
1513
+ this._pending.delete(requestId);
1514
+ cleanup();
1515
+ try {
1516
+ this.cancel(requestId);
1517
+ }
1518
+ catch {
1519
+ // Worker may already be gone; cancellation is best-effort.
1520
+ }
1521
+ reject(new Error(`Provider request "${requestId}" was cancelled.`));
1522
+ };
1523
+ this._pending.set(requestId, {
1524
+ resolve: (value) => {
1525
+ cleanup();
1526
+ resolve(value);
1527
+ },
1528
+ reject: (error) => {
1529
+ cleanup();
1530
+ reject(error);
1531
+ }
1532
+ });
1533
+ signal?.addEventListener("abort", onAbort, { once: true });
1534
+ try {
1535
+ this._send({
1536
+ type,
1537
+ request_id: requestId,
1538
+ data: { ...data, blob_transfer: "chunked-v1" }
1539
+ });
1540
+ }
1541
+ catch (err) {
1542
+ this._pending.delete(requestId);
1543
+ cleanup();
1544
+ reject(err instanceof Error ? err : new Error(String(err)));
1545
+ }
1546
+ });
1547
+ }
1548
+ // ── Diagnostics ────────────────────────────────────────────────────
1549
+ /**
1550
+ * Recent worker stderr summary used to enrich timeout errors. Transports
1551
+ * that capture stderr (stdio) override this; the base returns null.
1552
+ */
1553
+ getRecentStderrSummary(_limit = 12) {
1554
+ return null;
1555
+ }
1556
+ }
1557
+ //# sourceMappingURL=python-bridge-base.js.map