@nodetool-ai/runtime 0.7.0-rc.23 → 0.8.2

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 (713) 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 +982 -127
  24. package/dist/context.d.ts.map +1 -1
  25. package/dist/context.js +2649 -530
  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 +75 -0
  44. package/dist/generation-registry.d.ts.map +1 -0
  45. package/dist/generation-registry.js +144 -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 +352 -0
  62. package/dist/host-binaries.js.map +1 -0
  63. package/dist/image-codec.d.ts +76 -0
  64. package/dist/image-codec.d.ts.map +1 -0
  65. package/dist/image-codec.js +293 -0
  66. package/dist/image-codec.js.map +1 -0
  67. package/dist/image-model-tasks.d.ts +15 -0
  68. package/dist/image-model-tasks.d.ts.map +1 -0
  69. package/dist/image-model-tasks.js +24 -0
  70. package/dist/image-model-tasks.js.map +1 -0
  71. package/dist/index.d.ts +67 -6
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +51 -4
  74. package/dist/index.js.map +1 -1
  75. package/dist/invocation-account.d.ts +43 -0
  76. package/dist/invocation-account.d.ts.map +1 -0
  77. package/dist/invocation-account.js +60 -0
  78. package/dist/invocation-account.js.map +1 -0
  79. package/dist/media-ref-bytes.d.ts +35 -0
  80. package/dist/media-ref-bytes.d.ts.map +1 -0
  81. package/dist/media-ref-bytes.js +191 -0
  82. package/dist/media-ref-bytes.js.map +1 -0
  83. package/dist/node-executor.d.ts +119 -2
  84. package/dist/node-executor.d.ts.map +1 -1
  85. package/dist/permission-gate.d.ts +152 -0
  86. package/dist/permission-gate.d.ts.map +1 -0
  87. package/dist/permission-gate.js +87 -0
  88. package/dist/permission-gate.js.map +1 -0
  89. package/dist/prefixed-storage-adapter.d.ts +33 -0
  90. package/dist/prefixed-storage-adapter.d.ts.map +1 -0
  91. package/dist/prefixed-storage-adapter.js +70 -0
  92. package/dist/prefixed-storage-adapter.js.map +1 -0
  93. package/dist/prompt-asset-refs.d.ts +222 -0
  94. package/dist/prompt-asset-refs.d.ts.map +1 -0
  95. package/dist/prompt-asset-refs.js +751 -0
  96. package/dist/prompt-asset-refs.js.map +1 -0
  97. package/dist/provider-cache.d.ts +3 -0
  98. package/dist/provider-cache.d.ts.map +1 -0
  99. package/dist/provider-cache.js +8 -0
  100. package/dist/provider-cache.js.map +1 -0
  101. package/dist/providers/aki-provider.d.ts +35 -10
  102. package/dist/providers/aki-provider.d.ts.map +1 -1
  103. package/dist/providers/aki-provider.js +90 -77
  104. package/dist/providers/aki-provider.js.map +1 -1
  105. package/dist/providers/alibaba-provider.d.ts +39 -0
  106. package/dist/providers/alibaba-provider.d.ts.map +1 -0
  107. package/dist/providers/alibaba-provider.js +86 -0
  108. package/dist/providers/alibaba-provider.js.map +1 -0
  109. package/dist/providers/anthropic-provider.d.ts +52 -3
  110. package/dist/providers/anthropic-provider.d.ts.map +1 -1
  111. package/dist/providers/anthropic-provider.js +1020 -338
  112. package/dist/providers/anthropic-provider.js.map +1 -1
  113. package/dist/providers/atlascloud-provider.d.ts +103 -0
  114. package/dist/providers/atlascloud-provider.d.ts.map +1 -0
  115. package/dist/providers/atlascloud-provider.js +1031 -0
  116. package/dist/providers/atlascloud-provider.js.map +1 -0
  117. package/dist/providers/atlascloud-transport.d.ts +100 -0
  118. package/dist/providers/atlascloud-transport.d.ts.map +1 -0
  119. package/dist/providers/atlascloud-transport.js +364 -0
  120. package/dist/providers/atlascloud-transport.js.map +1 -0
  121. package/dist/providers/atlascloud-webhook-registry.d.ts +35 -0
  122. package/dist/providers/atlascloud-webhook-registry.d.ts.map +1 -0
  123. package/dist/providers/atlascloud-webhook-registry.js +104 -0
  124. package/dist/providers/atlascloud-webhook-registry.js.map +1 -0
  125. package/dist/providers/audio-mime.d.ts +11 -0
  126. package/dist/providers/audio-mime.d.ts.map +1 -0
  127. package/dist/providers/audio-mime.js +46 -0
  128. package/dist/providers/audio-mime.js.map +1 -0
  129. package/dist/providers/base-provider.d.ts +501 -32
  130. package/dist/providers/base-provider.d.ts.map +1 -1
  131. package/dist/providers/base-provider.js +1280 -87
  132. package/dist/providers/base-provider.js.map +1 -1
  133. package/dist/providers/cassette-provider.d.ts +217 -0
  134. package/dist/providers/cassette-provider.d.ts.map +1 -0
  135. package/dist/providers/cassette-provider.js +502 -0
  136. package/dist/providers/cassette-provider.js.map +1 -0
  137. package/dist/providers/cerebras-provider.d.ts +6 -12
  138. package/dist/providers/cerebras-provider.d.ts.map +1 -1
  139. package/dist/providers/cerebras-provider.js +8 -33
  140. package/dist/providers/cerebras-provider.js.map +1 -1
  141. package/dist/providers/claude-agent-provider.d.ts +107 -74
  142. package/dist/providers/claude-agent-provider.d.ts.map +1 -1
  143. package/dist/providers/claude-agent-provider.js +1279 -517
  144. package/dist/providers/claude-agent-provider.js.map +1 -1
  145. package/dist/providers/codex-provider.d.ts +84 -0
  146. package/dist/providers/codex-provider.d.ts.map +1 -0
  147. package/dist/providers/codex-provider.js +505 -0
  148. package/dist/providers/codex-provider.js.map +1 -0
  149. package/dist/providers/cohere-provider.d.ts +7 -3
  150. package/dist/providers/cohere-provider.d.ts.map +1 -1
  151. package/dist/providers/cohere-provider.js +14 -2
  152. package/dist/providers/cohere-provider.js.map +1 -1
  153. package/dist/providers/comfy-cloud-provider.d.ts +30 -0
  154. package/dist/providers/comfy-cloud-provider.d.ts.map +1 -0
  155. package/dist/providers/comfy-cloud-provider.js +42 -0
  156. package/dist/providers/comfy-cloud-provider.js.map +1 -0
  157. package/dist/providers/content-filter.d.ts +46 -0
  158. package/dist/providers/content-filter.d.ts.map +1 -0
  159. package/dist/providers/content-filter.js +92 -0
  160. package/dist/providers/content-filter.js.map +1 -0
  161. package/dist/providers/context-exceeded.d.ts +37 -0
  162. package/dist/providers/context-exceeded.d.ts.map +1 -0
  163. package/dist/providers/context-exceeded.js +114 -0
  164. package/dist/providers/context-exceeded.js.map +1 -0
  165. package/dist/providers/context-window.d.ts +44 -0
  166. package/dist/providers/context-window.d.ts.map +1 -0
  167. package/dist/providers/context-window.js +145 -0
  168. package/dist/providers/context-window.js.map +1 -0
  169. package/dist/providers/contract/index.d.ts +4 -0
  170. package/dist/providers/contract/index.d.ts.map +1 -0
  171. package/dist/providers/contract/index.js +4 -0
  172. package/dist/providers/contract/index.js.map +1 -0
  173. package/dist/providers/contract/probe-manifest.d.ts +75 -0
  174. package/dist/providers/contract/probe-manifest.d.ts.map +1 -0
  175. package/dist/providers/contract/probe-manifest.js +379 -0
  176. package/dist/providers/contract/probe-manifest.js.map +1 -0
  177. package/dist/providers/contract/probe-runner.d.ts +57 -0
  178. package/dist/providers/contract/probe-runner.d.ts.map +1 -0
  179. package/dist/providers/contract/probe-runner.js +180 -0
  180. package/dist/providers/contract/probe-runner.js.map +1 -0
  181. package/dist/providers/contract/redact.d.ts +29 -0
  182. package/dist/providers/contract/redact.d.ts.map +1 -0
  183. package/dist/providers/contract/redact.js +113 -0
  184. package/dist/providers/contract/redact.js.map +1 -0
  185. package/dist/providers/core-tools.d.ts +75 -0
  186. package/dist/providers/core-tools.d.ts.map +1 -0
  187. package/dist/providers/core-tools.js +128 -0
  188. package/dist/providers/core-tools.js.map +1 -0
  189. package/dist/providers/cost-calculator.d.ts +69 -50
  190. package/dist/providers/cost-calculator.d.ts.map +1 -1
  191. package/dist/providers/cost-calculator.js +207 -397
  192. package/dist/providers/cost-calculator.js.map +1 -1
  193. package/dist/providers/credential-check.d.ts +45 -0
  194. package/dist/providers/credential-check.d.ts.map +1 -0
  195. package/dist/providers/credential-check.js +164 -0
  196. package/dist/providers/credential-check.js.map +1 -0
  197. package/dist/providers/custom-model-kinds.d.ts +33 -0
  198. package/dist/providers/custom-model-kinds.d.ts.map +1 -0
  199. package/dist/providers/custom-model-kinds.js +96 -0
  200. package/dist/providers/custom-model-kinds.js.map +1 -0
  201. package/dist/providers/custom-openai-provider.d.ts +83 -0
  202. package/dist/providers/custom-openai-provider.d.ts.map +1 -0
  203. package/dist/providers/custom-openai-provider.js +190 -0
  204. package/dist/providers/custom-openai-provider.js.map +1 -0
  205. package/dist/providers/custom-provider-registry.d.ts +19 -0
  206. package/dist/providers/custom-provider-registry.d.ts.map +1 -0
  207. package/dist/providers/custom-provider-registry.js +52 -0
  208. package/dist/providers/custom-provider-registry.js.map +1 -0
  209. package/dist/providers/deepseek-provider.d.ts +8 -14
  210. package/dist/providers/deepseek-provider.d.ts.map +1 -1
  211. package/dist/providers/deepseek-provider.js +6 -35
  212. package/dist/providers/deepseek-provider.js.map +1 -1
  213. package/dist/providers/defaults.d.ts +12 -0
  214. package/dist/providers/defaults.d.ts.map +1 -0
  215. package/dist/providers/defaults.js +12 -0
  216. package/dist/providers/defaults.js.map +1 -0
  217. package/dist/providers/dreamina-models.d.ts +5 -0
  218. package/dist/providers/dreamina-models.d.ts.map +1 -0
  219. package/dist/providers/dreamina-models.js +57 -0
  220. package/dist/providers/dreamina-models.js.map +1 -0
  221. package/dist/providers/dreamina-music.d.ts +52 -0
  222. package/dist/providers/dreamina-music.d.ts.map +1 -0
  223. package/dist/providers/dreamina-music.js +115 -0
  224. package/dist/providers/dreamina-music.js.map +1 -0
  225. package/dist/providers/dreamina-provider.d.ts +62 -0
  226. package/dist/providers/dreamina-provider.d.ts.map +1 -0
  227. package/dist/providers/dreamina-provider.js +790 -0
  228. package/dist/providers/dreamina-provider.js.map +1 -0
  229. package/dist/providers/elevenlabs-provider.d.ts +50 -0
  230. package/dist/providers/elevenlabs-provider.d.ts.map +1 -0
  231. package/dist/providers/elevenlabs-provider.js +147 -0
  232. package/dist/providers/elevenlabs-provider.js.map +1 -0
  233. package/dist/providers/entity-references.d.ts +31 -0
  234. package/dist/providers/entity-references.d.ts.map +1 -0
  235. package/dist/providers/entity-references.js +113 -0
  236. package/dist/providers/entity-references.js.map +1 -0
  237. package/dist/providers/evolink-provider.d.ts +40 -0
  238. package/dist/providers/evolink-provider.d.ts.map +1 -0
  239. package/dist/providers/evolink-provider.js +312 -0
  240. package/dist/providers/evolink-provider.js.map +1 -0
  241. package/dist/providers/fake-provider.d.ts +74 -2
  242. package/dist/providers/fake-provider.d.ts.map +1 -1
  243. package/dist/providers/fake-provider.js +213 -1
  244. package/dist/providers/fake-provider.js.map +1 -1
  245. package/dist/providers/fal-generations.d.ts +106 -0
  246. package/dist/providers/fal-generations.d.ts.map +1 -0
  247. package/dist/providers/fal-generations.js +286 -0
  248. package/dist/providers/fal-generations.js.map +1 -0
  249. package/dist/providers/fal-provider.d.ts +173 -8
  250. package/dist/providers/fal-provider.d.ts.map +1 -1
  251. package/dist/providers/fal-provider.js +1396 -64
  252. package/dist/providers/fal-provider.js.map +1 -1
  253. package/dist/providers/fal-queue.d.ts +47 -0
  254. package/dist/providers/fal-queue.d.ts.map +1 -0
  255. package/dist/providers/fal-queue.js +231 -0
  256. package/dist/providers/fal-queue.js.map +1 -0
  257. package/dist/providers/gemini-provider.d.ts +150 -9
  258. package/dist/providers/gemini-provider.d.ts.map +1 -1
  259. package/dist/providers/gemini-provider.js +1515 -316
  260. package/dist/providers/gemini-provider.js.map +1 -1
  261. package/dist/providers/gmi-provider.d.ts +21 -0
  262. package/dist/providers/gmi-provider.d.ts.map +1 -0
  263. package/dist/providers/gmi-provider.js +34 -0
  264. package/dist/providers/gmi-provider.js.map +1 -0
  265. package/dist/providers/gpt-image-size.d.ts +3 -0
  266. package/dist/providers/gpt-image-size.d.ts.map +1 -0
  267. package/dist/providers/gpt-image-size.js +37 -0
  268. package/dist/providers/gpt-image-size.js.map +1 -0
  269. package/dist/providers/groq-provider.d.ts +9 -12
  270. package/dist/providers/groq-provider.d.ts.map +1 -1
  271. package/dist/providers/groq-provider.js +19 -33
  272. package/dist/providers/groq-provider.js.map +1 -1
  273. package/dist/providers/groq-request.d.ts +38 -0
  274. package/dist/providers/groq-request.d.ts.map +1 -0
  275. package/dist/providers/groq-request.js +138 -0
  276. package/dist/providers/groq-request.js.map +1 -0
  277. package/dist/providers/higgsfield-provider.d.ts +31 -0
  278. package/dist/providers/higgsfield-provider.d.ts.map +1 -0
  279. package/dist/providers/higgsfield-provider.js +143 -0
  280. package/dist/providers/higgsfield-provider.js.map +1 -0
  281. package/dist/providers/higgsfield-transport.d.ts +106 -0
  282. package/dist/providers/higgsfield-transport.d.ts.map +1 -0
  283. package/dist/providers/higgsfield-transport.js +182 -0
  284. package/dist/providers/higgsfield-transport.js.map +1 -0
  285. package/dist/providers/http-transport.d.ts +74 -0
  286. package/dist/providers/http-transport.d.ts.map +1 -0
  287. package/dist/providers/http-transport.js +152 -0
  288. package/dist/providers/http-transport.js.map +1 -0
  289. package/dist/providers/huggingface-provider.d.ts +73 -4
  290. package/dist/providers/huggingface-provider.d.ts.map +1 -1
  291. package/dist/providers/huggingface-provider.js +240 -34
  292. package/dist/providers/huggingface-provider.js.map +1 -1
  293. package/dist/providers/image-mime.d.ts +39 -0
  294. package/dist/providers/image-mime.d.ts.map +1 -0
  295. package/dist/providers/image-mime.js +111 -0
  296. package/dist/providers/image-mime.js.map +1 -0
  297. package/dist/providers/image-ref.d.ts +21 -0
  298. package/dist/providers/image-ref.d.ts.map +1 -0
  299. package/dist/providers/image-ref.js +44 -0
  300. package/dist/providers/image-ref.js.map +1 -0
  301. package/dist/providers/index.d.ts +87 -8
  302. package/dist/providers/index.d.ts.map +1 -1
  303. package/dist/providers/index.js +250 -56
  304. package/dist/providers/index.js.map +1 -1
  305. package/dist/providers/jina-provider.d.ts +12 -3
  306. package/dist/providers/jina-provider.d.ts.map +1 -1
  307. package/dist/providers/jina-provider.js +21 -7
  308. package/dist/providers/jina-provider.js.map +1 -1
  309. package/dist/providers/kie-provider.d.ts +172 -10
  310. package/dist/providers/kie-provider.d.ts.map +1 -1
  311. package/dist/providers/kie-provider.js +1190 -228
  312. package/dist/providers/kie-provider.js.map +1 -1
  313. package/dist/providers/kie-webhook-registry.d.ts +28 -0
  314. package/dist/providers/kie-webhook-registry.d.ts.map +1 -0
  315. package/dist/providers/kie-webhook-registry.js +91 -0
  316. package/dist/providers/kie-webhook-registry.js.map +1 -0
  317. package/dist/providers/llama-provider.d.ts +23 -36
  318. package/dist/providers/llama-provider.d.ts.map +1 -1
  319. package/dist/providers/llama-provider.js +50 -355
  320. package/dist/providers/llama-provider.js.map +1 -1
  321. package/dist/providers/lmstudio-provider.d.ts +4 -7
  322. package/dist/providers/lmstudio-provider.d.ts.map +1 -1
  323. package/dist/providers/lmstudio-provider.js +24 -15
  324. package/dist/providers/lmstudio-provider.js.map +1 -1
  325. package/dist/providers/loop-tracing.d.ts +17 -0
  326. package/dist/providers/loop-tracing.d.ts.map +1 -0
  327. package/dist/providers/loop-tracing.js +132 -0
  328. package/dist/providers/loop-tracing.js.map +1 -0
  329. package/dist/providers/manifest-models.d.ts +341 -1
  330. package/dist/providers/manifest-models.d.ts.map +1 -1
  331. package/dist/providers/manifest-models.js +1106 -22
  332. package/dist/providers/manifest-models.js.map +1 -1
  333. package/dist/providers/media-mime.d.ts +14 -0
  334. package/dist/providers/media-mime.d.ts.map +1 -0
  335. package/dist/providers/media-mime.js +35 -0
  336. package/dist/providers/media-mime.js.map +1 -0
  337. package/dist/providers/meshy-provider.d.ts +16 -2
  338. package/dist/providers/meshy-provider.d.ts.map +1 -1
  339. package/dist/providers/meshy-provider.js +82 -46
  340. package/dist/providers/meshy-provider.js.map +1 -1
  341. package/dist/providers/meta-provider.d.ts +22 -0
  342. package/dist/providers/meta-provider.d.ts.map +1 -0
  343. package/dist/providers/meta-provider.js +54 -0
  344. package/dist/providers/meta-provider.js.map +1 -0
  345. package/dist/providers/minimax-provider.d.ts +27 -18
  346. package/dist/providers/minimax-provider.d.ts.map +1 -1
  347. package/dist/providers/minimax-provider.js +222 -72
  348. package/dist/providers/minimax-provider.js.map +1 -1
  349. package/dist/providers/mistral-provider.d.ts +8 -12
  350. package/dist/providers/mistral-provider.d.ts.map +1 -1
  351. package/dist/providers/mistral-provider.js +15 -34
  352. package/dist/providers/mistral-provider.js.map +1 -1
  353. package/dist/providers/moonshot-provider.d.ts +11 -15
  354. package/dist/providers/moonshot-provider.d.ts.map +1 -1
  355. package/dist/providers/moonshot-provider.js +33 -23
  356. package/dist/providers/moonshot-provider.js.map +1 -1
  357. package/dist/providers/node-llama-cpp-provider.d.ts +99 -0
  358. package/dist/providers/node-llama-cpp-provider.d.ts.map +1 -0
  359. package/dist/providers/node-llama-cpp-provider.js +484 -0
  360. package/dist/providers/node-llama-cpp-provider.js.map +1 -0
  361. package/dist/providers/nodetool-provider.d.ts +49 -0
  362. package/dist/providers/nodetool-provider.d.ts.map +1 -0
  363. package/dist/providers/nodetool-provider.js +222 -0
  364. package/dist/providers/nodetool-provider.js.map +1 -0
  365. package/dist/providers/oauth/browser-launcher.d.ts +23 -0
  366. package/dist/providers/oauth/browser-launcher.d.ts.map +1 -0
  367. package/dist/providers/oauth/browser-launcher.js +45 -0
  368. package/dist/providers/oauth/browser-launcher.js.map +1 -0
  369. package/dist/providers/oauth/claude-code-credentials.d.ts +74 -0
  370. package/dist/providers/oauth/claude-code-credentials.d.ts.map +1 -0
  371. package/dist/providers/oauth/claude-code-credentials.js +133 -0
  372. package/dist/providers/oauth/claude-code-credentials.js.map +1 -0
  373. package/dist/providers/oauth/claude-code-login.d.ts +124 -0
  374. package/dist/providers/oauth/claude-code-login.d.ts.map +1 -0
  375. package/dist/providers/oauth/claude-code-login.js +230 -0
  376. package/dist/providers/oauth/claude-code-login.js.map +1 -0
  377. package/dist/providers/oauth/claude-code-oauth-client.d.ts +111 -0
  378. package/dist/providers/oauth/claude-code-oauth-client.d.ts.map +1 -0
  379. package/dist/providers/oauth/claude-code-oauth-client.js +207 -0
  380. package/dist/providers/oauth/claude-code-oauth-client.js.map +1 -0
  381. package/dist/providers/oauth/errors.d.ts +54 -0
  382. package/dist/providers/oauth/errors.d.ts.map +1 -0
  383. package/dist/providers/oauth/errors.js +79 -0
  384. package/dist/providers/oauth/errors.js.map +1 -0
  385. package/dist/providers/oauth/index.d.ts +105 -0
  386. package/dist/providers/oauth/index.d.ts.map +1 -0
  387. package/dist/providers/oauth/index.js +167 -0
  388. package/dist/providers/oauth/index.js.map +1 -0
  389. package/dist/providers/oauth/jwt-claims.d.ts +23 -0
  390. package/dist/providers/oauth/jwt-claims.d.ts.map +1 -0
  391. package/dist/providers/oauth/jwt-claims.js +67 -0
  392. package/dist/providers/oauth/jwt-claims.js.map +1 -0
  393. package/dist/providers/oauth/local-callback-server.d.ts +60 -0
  394. package/dist/providers/oauth/local-callback-server.d.ts.map +1 -0
  395. package/dist/providers/oauth/local-callback-server.js +148 -0
  396. package/dist/providers/oauth/local-callback-server.js.map +1 -0
  397. package/dist/providers/oauth/oauth-client.d.ts +55 -0
  398. package/dist/providers/oauth/oauth-client.d.ts.map +1 -0
  399. package/dist/providers/oauth/oauth-client.js +161 -0
  400. package/dist/providers/oauth/oauth-client.js.map +1 -0
  401. package/dist/providers/oauth/openai-oauth-provider.d.ts +129 -0
  402. package/dist/providers/oauth/openai-oauth-provider.d.ts.map +1 -0
  403. package/dist/providers/oauth/openai-oauth-provider.js +265 -0
  404. package/dist/providers/oauth/openai-oauth-provider.js.map +1 -0
  405. package/dist/providers/oauth/pkce-helper.d.ts +43 -0
  406. package/dist/providers/oauth/pkce-helper.d.ts.map +1 -0
  407. package/dist/providers/oauth/pkce-helper.js +57 -0
  408. package/dist/providers/oauth/pkce-helper.js.map +1 -0
  409. package/dist/providers/oauth/redaction.d.ts +22 -0
  410. package/dist/providers/oauth/redaction.d.ts.map +1 -0
  411. package/dist/providers/oauth/redaction.js +59 -0
  412. package/dist/providers/oauth/redaction.js.map +1 -0
  413. package/dist/providers/oauth/secure-credential-store.d.ts +67 -0
  414. package/dist/providers/oauth/secure-credential-store.d.ts.map +1 -0
  415. package/dist/providers/oauth/secure-credential-store.js +73 -0
  416. package/dist/providers/oauth/secure-credential-store.js.map +1 -0
  417. package/dist/providers/oauth/token-store.d.ts +53 -0
  418. package/dist/providers/oauth/token-store.d.ts.map +1 -0
  419. package/dist/providers/oauth/token-store.js +78 -0
  420. package/dist/providers/oauth/token-store.js.map +1 -0
  421. package/dist/providers/oauth/types.d.ts +64 -0
  422. package/dist/providers/oauth/types.d.ts.map +1 -0
  423. package/dist/providers/oauth/types.js +12 -0
  424. package/dist/providers/oauth/types.js.map +1 -0
  425. package/dist/providers/object-tracking.d.ts +47 -0
  426. package/dist/providers/object-tracking.d.ts.map +1 -0
  427. package/dist/providers/object-tracking.js +46 -0
  428. package/dist/providers/object-tracking.js.map +1 -0
  429. package/dist/providers/offline-model-index.d.ts +20 -0
  430. package/dist/providers/offline-model-index.d.ts.map +1 -0
  431. package/dist/providers/offline-model-index.js +112 -0
  432. package/dist/providers/offline-model-index.js.map +1 -0
  433. package/dist/providers/ollama-provider.d.ts +30 -2
  434. package/dist/providers/ollama-provider.d.ts.map +1 -1
  435. package/dist/providers/ollama-provider.js +198 -43
  436. package/dist/providers/ollama-provider.js.map +1 -1
  437. package/dist/providers/openai-compat/client.d.ts +57 -0
  438. package/dist/providers/openai-compat/client.d.ts.map +1 -0
  439. package/dist/providers/openai-compat/client.js +157 -0
  440. package/dist/providers/openai-compat/client.js.map +1 -0
  441. package/dist/providers/openai-compat/decode.d.ts +36 -0
  442. package/dist/providers/openai-compat/decode.d.ts.map +1 -0
  443. package/dist/providers/openai-compat/decode.js +55 -0
  444. package/dist/providers/openai-compat/decode.js.map +1 -0
  445. package/dist/providers/openai-compat/errors.d.ts +54 -0
  446. package/dist/providers/openai-compat/errors.d.ts.map +1 -0
  447. package/dist/providers/openai-compat/errors.js +144 -0
  448. package/dist/providers/openai-compat/errors.js.map +1 -0
  449. package/dist/providers/openai-compat/index.d.ts +8 -0
  450. package/dist/providers/openai-compat/index.d.ts.map +1 -0
  451. package/dist/providers/openai-compat/index.js +5 -0
  452. package/dist/providers/openai-compat/index.js.map +1 -0
  453. package/dist/providers/openai-compat/sse.d.ts +14 -0
  454. package/dist/providers/openai-compat/sse.d.ts.map +1 -0
  455. package/dist/providers/openai-compat/sse.js +69 -0
  456. package/dist/providers/openai-compat/sse.js.map +1 -0
  457. package/dist/providers/openai-compat/types.d.ts +75 -0
  458. package/dist/providers/openai-compat/types.d.ts.map +1 -0
  459. package/dist/providers/openai-compat/types.js +9 -0
  460. package/dist/providers/openai-compat/types.js.map +1 -0
  461. package/dist/providers/openai-compat-provider.d.ts +90 -0
  462. package/dist/providers/openai-compat-provider.d.ts.map +1 -0
  463. package/dist/providers/openai-compat-provider.js +282 -0
  464. package/dist/providers/openai-compat-provider.js.map +1 -0
  465. package/dist/providers/openai-provider.d.ts +98 -27
  466. package/dist/providers/openai-provider.d.ts.map +1 -1
  467. package/dist/providers/openai-provider.js +1037 -105
  468. package/dist/providers/openai-provider.js.map +1 -1
  469. package/dist/providers/openrouter-provider.d.ts +58 -13
  470. package/dist/providers/openrouter-provider.d.ts.map +1 -1
  471. package/dist/providers/openrouter-provider.js +383 -51
  472. package/dist/providers/openrouter-provider.js.map +1 -1
  473. package/dist/providers/provider-call-failure.d.ts +39 -0
  474. package/dist/providers/provider-call-failure.d.ts.map +1 -0
  475. package/dist/providers/provider-call-failure.js +132 -0
  476. package/dist/providers/provider-call-failure.js.map +1 -0
  477. package/dist/providers/provider-error.d.ts +59 -0
  478. package/dist/providers/provider-error.d.ts.map +1 -0
  479. package/dist/providers/provider-error.js +171 -0
  480. package/dist/providers/provider-error.js.map +1 -0
  481. package/dist/providers/provider-generations.d.ts +99 -0
  482. package/dist/providers/provider-generations.d.ts.map +1 -0
  483. package/dist/providers/provider-generations.js +71 -0
  484. package/dist/providers/provider-generations.js.map +1 -0
  485. package/dist/providers/provider-queue.d.ts +62 -0
  486. package/dist/providers/provider-queue.d.ts.map +1 -0
  487. package/dist/providers/provider-queue.js +28 -0
  488. package/dist/providers/provider-queue.js.map +1 -0
  489. package/dist/providers/provider-registry.d.ts +33 -14
  490. package/dist/providers/provider-registry.d.ts.map +1 -1
  491. package/dist/providers/provider-registry.js +85 -68
  492. package/dist/providers/provider-registry.js.map +1 -1
  493. package/dist/providers/provider-request-log.d.ts +53 -0
  494. package/dist/providers/provider-request-log.d.ts.map +1 -0
  495. package/dist/providers/provider-request-log.js +168 -0
  496. package/dist/providers/provider-request-log.js.map +1 -0
  497. package/dist/providers/provider-session.d.ts +3 -0
  498. package/dist/providers/provider-session.d.ts.map +1 -0
  499. package/dist/providers/provider-session.js +10 -0
  500. package/dist/providers/provider-session.js.map +1 -0
  501. package/dist/providers/provider-transport.d.ts +11 -0
  502. package/dist/providers/provider-transport.d.ts.map +1 -0
  503. package/dist/providers/provider-transport.js +11 -0
  504. package/dist/providers/provider-transport.js.map +1 -0
  505. package/dist/providers/python-provider.d.ts +26 -13
  506. package/dist/providers/python-provider.d.ts.map +1 -1
  507. package/dist/providers/python-provider.js +204 -25
  508. package/dist/providers/python-provider.js.map +1 -1
  509. package/dist/providers/replicate-provider.d.ts +91 -8
  510. package/dist/providers/replicate-provider.d.ts.map +1 -1
  511. package/dist/providers/replicate-provider.js +823 -65
  512. package/dist/providers/replicate-provider.js.map +1 -1
  513. package/dist/providers/replicate-retry.d.ts +30 -0
  514. package/dist/providers/replicate-retry.d.ts.map +1 -0
  515. package/dist/providers/replicate-retry.js +97 -0
  516. package/dist/providers/replicate-retry.js.map +1 -0
  517. package/dist/providers/requesty-provider.d.ts +30 -0
  518. package/dist/providers/requesty-provider.d.ts.map +1 -0
  519. package/dist/providers/requesty-provider.js +87 -0
  520. package/dist/providers/requesty-provider.js.map +1 -0
  521. package/dist/providers/responses-api.d.ts +27 -0
  522. package/dist/providers/responses-api.d.ts.map +1 -0
  523. package/dist/providers/responses-api.js +297 -0
  524. package/dist/providers/responses-api.js.map +1 -0
  525. package/dist/providers/reve-provider.d.ts +46 -0
  526. package/dist/providers/reve-provider.d.ts.map +1 -0
  527. package/dist/providers/reve-provider.js +165 -0
  528. package/dist/providers/reve-provider.js.map +1 -0
  529. package/dist/providers/rodin-provider.d.ts +25 -2
  530. package/dist/providers/rodin-provider.d.ts.map +1 -1
  531. package/dist/providers/rodin-provider.js +95 -51
  532. package/dist/providers/rodin-provider.js.map +1 -1
  533. package/dist/providers/safe-url.d.ts +70 -0
  534. package/dist/providers/safe-url.d.ts.map +1 -0
  535. package/dist/providers/safe-url.js +277 -0
  536. package/dist/providers/safe-url.js.map +1 -0
  537. package/dist/providers/scripted-provider.d.ts +9 -0
  538. package/dist/providers/scripted-provider.d.ts.map +1 -1
  539. package/dist/providers/scripted-provider.js +86 -10
  540. package/dist/providers/scripted-provider.js.map +1 -1
  541. package/dist/providers/structured-output.d.ts +42 -0
  542. package/dist/providers/structured-output.d.ts.map +1 -0
  543. package/dist/providers/structured-output.js +83 -0
  544. package/dist/providers/structured-output.js.map +1 -0
  545. package/dist/providers/together-operations.d.ts +27 -0
  546. package/dist/providers/together-operations.d.ts.map +1 -0
  547. package/dist/providers/together-operations.js +95 -0
  548. package/dist/providers/together-operations.js.map +1 -0
  549. package/dist/providers/together-provider.d.ts +18 -18
  550. package/dist/providers/together-provider.d.ts.map +1 -1
  551. package/dist/providers/together-provider.js +140 -367
  552. package/dist/providers/together-provider.js.map +1 -1
  553. package/dist/providers/topaz-provider.d.ts +45 -0
  554. package/dist/providers/topaz-provider.d.ts.map +1 -0
  555. package/dist/providers/topaz-provider.js +244 -0
  556. package/dist/providers/topaz-provider.js.map +1 -0
  557. package/dist/providers/types.d.ts +641 -4
  558. package/dist/providers/types.d.ts.map +1 -1
  559. package/dist/providers/types.js +81 -1
  560. package/dist/providers/types.js.map +1 -1
  561. package/dist/providers/useapi-provider.d.ts +28 -0
  562. package/dist/providers/useapi-provider.d.ts.map +1 -0
  563. package/dist/providers/useapi-provider.js +274 -0
  564. package/dist/providers/useapi-provider.js.map +1 -0
  565. package/dist/providers/video-duration.d.ts +6 -0
  566. package/dist/providers/video-duration.d.ts.map +1 -0
  567. package/dist/providers/video-duration.js +63 -0
  568. package/dist/providers/video-duration.js.map +1 -0
  569. package/dist/providers/video-frame-fallback.d.ts +39 -0
  570. package/dist/providers/video-frame-fallback.d.ts.map +1 -0
  571. package/dist/providers/video-frame-fallback.js +155 -0
  572. package/dist/providers/video-frame-fallback.js.map +1 -0
  573. package/dist/providers/video-frames.d.ts +61 -0
  574. package/dist/providers/video-frames.d.ts.map +1 -0
  575. package/dist/providers/video-frames.js +208 -0
  576. package/dist/providers/video-frames.js.map +1 -0
  577. package/dist/providers/video-mime.d.ts +14 -0
  578. package/dist/providers/video-mime.d.ts.map +1 -0
  579. package/dist/providers/video-mime.js +44 -0
  580. package/dist/providers/video-mime.js.map +1 -0
  581. package/dist/providers/video-to-audio.d.ts +34 -0
  582. package/dist/providers/video-to-audio.d.ts.map +1 -0
  583. package/dist/providers/video-to-audio.js +62 -0
  584. package/dist/providers/video-to-audio.js.map +1 -0
  585. package/dist/providers/vllm-provider.d.ts +20 -8
  586. package/dist/providers/vllm-provider.d.ts.map +1 -1
  587. package/dist/providers/vllm-provider.js +44 -23
  588. package/dist/providers/vllm-provider.js.map +1 -1
  589. package/dist/providers/voyage-provider.d.ts +12 -3
  590. package/dist/providers/voyage-provider.d.ts.map +1 -1
  591. package/dist/providers/voyage-provider.js +21 -7
  592. package/dist/providers/voyage-provider.js.map +1 -1
  593. package/dist/providers/xai-provider.d.ts +56 -13
  594. package/dist/providers/xai-provider.d.ts.map +1 -1
  595. package/dist/providers/xai-provider.js +307 -22
  596. package/dist/providers/xai-provider.js.map +1 -1
  597. package/dist/python-bridge-base.d.ts +358 -0
  598. package/dist/python-bridge-base.d.ts.map +1 -0
  599. package/dist/python-bridge-base.js +1557 -0
  600. package/dist/python-bridge-base.js.map +1 -0
  601. package/dist/python-bridge-codec.d.ts +28 -0
  602. package/dist/python-bridge-codec.d.ts.map +1 -0
  603. package/dist/python-bridge-codec.js +39 -0
  604. package/dist/python-bridge-codec.js.map +1 -0
  605. package/dist/python-bridge-factory.d.ts +52 -0
  606. package/dist/python-bridge-factory.d.ts.map +1 -0
  607. package/dist/python-bridge-factory.js +69 -0
  608. package/dist/python-bridge-factory.js.map +1 -0
  609. package/dist/python-bridge-framing.d.ts +68 -0
  610. package/dist/python-bridge-framing.d.ts.map +1 -0
  611. package/dist/python-bridge-framing.js +91 -0
  612. package/dist/python-bridge-framing.js.map +1 -0
  613. package/dist/python-bridge-types.d.ts +545 -2
  614. package/dist/python-bridge-types.d.ts.map +1 -1
  615. package/dist/python-bridge-types.js +49 -1
  616. package/dist/python-bridge-types.js.map +1 -1
  617. package/dist/python-graph-resolver.d.ts +57 -0
  618. package/dist/python-graph-resolver.d.ts.map +1 -0
  619. package/dist/python-graph-resolver.js +79 -0
  620. package/dist/python-graph-resolver.js.map +1 -0
  621. package/dist/python-node-executor.d.ts +36 -4
  622. package/dist/python-node-executor.d.ts.map +1 -1
  623. package/dist/python-node-executor.js +177 -86
  624. package/dist/python-node-executor.js.map +1 -1
  625. package/dist/python-stdio-bridge.d.ts +18 -37
  626. package/dist/python-stdio-bridge.d.ts.map +1 -1
  627. package/dist/python-stdio-bridge.js +148 -475
  628. package/dist/python-stdio-bridge.js.map +1 -1
  629. package/dist/python-websocket-bridge.d.ts +140 -0
  630. package/dist/python-websocket-bridge.d.ts.map +1 -0
  631. package/dist/python-websocket-bridge.js +561 -0
  632. package/dist/python-websocket-bridge.js.map +1 -0
  633. package/dist/python-worker-stderr.d.ts +4 -0
  634. package/dist/python-worker-stderr.d.ts.map +1 -0
  635. package/dist/python-worker-stderr.js +24 -0
  636. package/dist/python-worker-stderr.js.map +1 -0
  637. package/dist/recommended-models.d.ts +2 -2
  638. package/dist/recommended-models.d.ts.map +1 -1
  639. package/dist/recommended-models.js +44 -4
  640. package/dist/recommended-models.js.map +1 -1
  641. package/dist/recoverable-node-error.d.ts +31 -0
  642. package/dist/recoverable-node-error.d.ts.map +1 -0
  643. package/dist/recoverable-node-error.js +37 -0
  644. package/dist/recoverable-node-error.js.map +1 -0
  645. package/dist/redact-params.d.ts +13 -0
  646. package/dist/redact-params.d.ts.map +1 -0
  647. package/dist/redact-params.js +57 -0
  648. package/dist/redact-params.js.map +1 -0
  649. package/dist/run-context.d.ts +20 -0
  650. package/dist/run-context.d.ts.map +1 -0
  651. package/dist/run-context.js +18 -0
  652. package/dist/run-context.js.map +1 -0
  653. package/dist/run-trace-context.d.ts +30 -0
  654. package/dist/run-trace-context.d.ts.map +1 -0
  655. package/dist/run-trace-context.js +80 -0
  656. package/dist/run-trace-context.js.map +1 -0
  657. package/dist/run-trace-processor.d.ts +65 -0
  658. package/dist/run-trace-processor.d.ts.map +1 -0
  659. package/dist/run-trace-processor.js +417 -0
  660. package/dist/run-trace-processor.js.map +1 -0
  661. package/dist/run-trace-serialization.d.ts +5 -0
  662. package/dist/run-trace-serialization.d.ts.map +1 -0
  663. package/dist/run-trace-serialization.js +113 -0
  664. package/dist/run-trace-serialization.js.map +1 -0
  665. package/dist/sandbox-module-catalog.d.ts +43 -0
  666. package/dist/sandbox-module-catalog.d.ts.map +1 -0
  667. package/dist/sandbox-module-catalog.js +25 -0
  668. package/dist/sandbox-module-catalog.js.map +1 -0
  669. package/dist/storage-workspace.d.ts +62 -0
  670. package/dist/storage-workspace.d.ts.map +1 -0
  671. package/dist/storage-workspace.js +441 -0
  672. package/dist/storage-workspace.js.map +1 -0
  673. package/dist/swappable-python-bridge.d.ts +83 -0
  674. package/dist/swappable-python-bridge.d.ts.map +1 -0
  675. package/dist/swappable-python-bridge.js +246 -0
  676. package/dist/swappable-python-bridge.js.map +1 -0
  677. package/dist/telemetry.d.ts +16 -3
  678. package/dist/telemetry.d.ts.map +1 -1
  679. package/dist/telemetry.js +76 -20
  680. package/dist/telemetry.js.map +1 -1
  681. package/dist/testing.d.ts +81 -0
  682. package/dist/testing.d.ts.map +1 -0
  683. package/dist/testing.js +130 -0
  684. package/dist/testing.js.map +1 -0
  685. package/dist/token-counter.d.ts +30 -0
  686. package/dist/token-counter.d.ts.map +1 -0
  687. package/dist/token-counter.js +156 -0
  688. package/dist/token-counter.js.map +1 -0
  689. package/dist/trace-exporters.d.ts +11 -28
  690. package/dist/trace-exporters.d.ts.map +1 -1
  691. package/dist/trace-exporters.js +69 -22
  692. package/dist/trace-exporters.js.map +1 -1
  693. package/dist/tracing-helpers.d.ts +60 -26
  694. package/dist/tracing-helpers.d.ts.map +1 -1
  695. package/dist/tracing-helpers.js +100 -25
  696. package/dist/tracing-helpers.js.map +1 -1
  697. package/dist/trim-video-window.d.ts +8 -0
  698. package/dist/trim-video-window.d.ts.map +1 -0
  699. package/dist/trim-video-window.js +50 -0
  700. package/dist/trim-video-window.js.map +1 -0
  701. package/dist/turn-budget.d.ts +278 -0
  702. package/dist/turn-budget.d.ts.map +1 -0
  703. package/dist/turn-budget.js +365 -0
  704. package/dist/turn-budget.js.map +1 -0
  705. package/dist/variable-channel.d.ts +48 -0
  706. package/dist/variable-channel.d.ts.map +1 -0
  707. package/dist/variable-channel.js +110 -0
  708. package/dist/variable-channel.js.map +1 -0
  709. package/dist/workspace.d.ts +119 -0
  710. package/dist/workspace.d.ts.map +1 -0
  711. package/dist/workspace.js +112 -0
  712. package/dist/workspace.js.map +1 -0
  713. package/package.json +121 -19
@@ -6,48 +6,342 @@
6
6
  * deduplicates the image and video entries so providers don't need to
7
7
  * maintain hardcoded model lists.
8
8
  */
9
- import { createRequire } from "node:module";
10
- import { createLogger } from "@nodetool-ai/config";
9
+ import { createLogger, loadPackageAssetJson } from "@nodetool-ai/config";
10
+ import { isNumber } from "@nodetool-ai/protocol";
11
+ // Stryker disable next-line StringLiteral: logger name is diagnostic, not asserted.
11
12
  const log = createLogger("nodetool.runtime.providers.manifest-models");
12
- function nodeId(n) {
13
+ export function explicitTasks(n) {
14
+ return Array.isArray(n.supportedTasks) && n.supportedTasks.length > 0
15
+ ? n.supportedTasks
16
+ : undefined;
17
+ }
18
+ /**
19
+ * Enum values declared for a manifest input field, by canonical API name.
20
+ * Reads both the FAL/Replicate `inputFields` (`apiParamName ?? name` →
21
+ * `enumValues`) and the Kie `fields` (`name` → `values`) conventions.
22
+ *
23
+ * Values are stringified rather than returned as read: the manifests are JSON
24
+ * and the declared `string[]` is a claim about them, not a guarantee. FAL's
25
+ * two LoRA trainers declare `resolution` as an enum of numbers (768/1024 and
26
+ * 512/1024), which reached `unifiedModel.resolutions` — `z.array(z.string())`
27
+ * — and failed output validation for the whole `models.imageByProvider`
28
+ * query. `videoConstraints` reads durations back with `Number()`, so a
29
+ * numeric enum still works there.
30
+ */
31
+ export function enumValuesFor(n, apiName) {
32
+ // Stryker disable next-line ArrayDeclaration: the fallback's contents are irrelevant — a non-array node yields no matching field either way.
33
+ const inputField = (n.inputFields ?? []).find((f) => (f.apiParamName ?? f.name) === apiName);
34
+ const fromInput = inputField?.enumValues;
35
+ if (fromInput && fromInput.length > 0)
36
+ return fromInput.map(String);
37
+ // Stryker disable next-line ArrayDeclaration: same rationale — a non-array node yields no matching field either way.
38
+ const field = (n.fields ?? []).find((f) => f.name === apiName);
39
+ const fromField = field?.values;
40
+ return fromField && fromField.length > 0 ? fromField.map(String) : undefined;
41
+ }
42
+ /**
43
+ * Seconds from one declared duration option.
44
+ *
45
+ * The manifests spell a clip length three ways, because the providers do:
46
+ * a bare number (`8`), a numeric string (`"8"`), and — across the FAL Veo and
47
+ * Marey families — a unit-suffixed string (`"8s"`). A bare `Number("8s")` is
48
+ * `NaN`, so the suffixed spelling used to drop the whole enum and leave those
49
+ * endpoints looking unconstrained: 22 FAL video endpoints, the Veo 3.1
50
+ * image-to-video and first-last-frame families among them, offered the app's
51
+ * full duration ladder when FAL sells them only 4, 6 and 8 seconds.
52
+ *
53
+ * Only a plain count of seconds is read. `"auto"` (FAL's Seedance rows) and
54
+ * `-1` (AtlasCloud's) name no length, and neither does a range or a frame
55
+ * count, so they stay out rather than becoming a number nothing published.
56
+ */
57
+ function durationSeconds(value) {
58
+ const trimmed = value.trim();
59
+ if (trimmed === "")
60
+ return undefined;
61
+ const digits = /^(\d+(?:\.\d+)?)\s*s(?:ec(?:onds?)?)?$/i.exec(trimmed)?.[1] ?? trimmed;
62
+ const parsed = Number(digits);
63
+ return Number.isFinite(parsed) ? parsed : undefined;
64
+ }
65
+ /** Option constraints (duration/resolution/aspect) for a video endpoint. */
66
+ export function videoConstraints(n) {
67
+ const durationEnum = enumValuesFor(n, "duration");
68
+ const durations = durationEnum
69
+ ?.map(durationSeconds)
70
+ .filter((v) => v !== undefined);
71
+ return {
72
+ durations: durations && durations.length > 0 ? durations : undefined,
73
+ resolutions: enumValuesFor(n, "resolution"),
74
+ aspectRatios: enumValuesFor(n, "aspect_ratio")
75
+ };
76
+ }
77
+ // FAL image endpoints usually declare an `image_size` enum instead of an
78
+ // `aspect_ratio`. This maps its named / explicit sizes onto the aspect-ratio
79
+ // vocabulary the picker uses; `auto*` sizes have no fixed ratio and are dropped.
80
+ const SIZE_ENUM_TO_ASPECT = {
81
+ square: "1:1",
82
+ square_hd: "1:1",
83
+ square_uhd: "1:1",
84
+ "1024x1024": "1:1",
85
+ landscape_4_3: "4:3",
86
+ portrait_4_3: "3:4",
87
+ landscape_16_9: "16:9",
88
+ landscape_hd: "16:9",
89
+ portrait_16_9: "9:16",
90
+ portrait_hd: "9:16",
91
+ landscape_3_2: "3:2",
92
+ "1536x1024": "3:2",
93
+ portrait_3_2: "2:3",
94
+ "1024x1536": "2:3"
95
+ };
96
+ /** Map a FAL `image_size` enum value onto an aspect ratio, or undefined. */
97
+ export function sizeEnumToAspect(value) {
98
+ return SIZE_ENUM_TO_ASPECT[value];
99
+ }
100
+ /** Option constraints (aspect/resolution) for an image endpoint. */
101
+ export function imageConstraints(n) {
102
+ // FAL splits size across `aspect_ratio` and `image_size`; union both into one
103
+ // aspect-ratio list, preserving first-seen order and dropping duplicates.
104
+ const fromAspect = enumValuesFor(n, "aspect_ratio") ?? [];
105
+ const fromSize = (enumValuesFor(n, "image_size") ?? [])
106
+ .map(sizeEnumToAspect)
107
+ .filter((v) => Boolean(v));
108
+ const aspectRatios = [...new Set([...fromAspect, ...fromSize])];
109
+ return {
110
+ aspectRatios: aspectRatios.length > 0 ? aspectRatios : undefined,
111
+ // Resolution is its own enum — never derived from image_size.
112
+ resolutions: enumValuesFor(n, "resolution")
113
+ };
114
+ }
115
+ export function nodeId(n) {
13
116
  return n.modelId ?? n.endpointId ?? "";
14
117
  }
15
- function nodeName(n) {
118
+ export function nodeName(n) {
16
119
  // className often looks like "FluxSchnellRedux" — split on caps
17
120
  const raw = n.title ?? n.className ?? nodeId(n);
18
121
  // If it's PascalCase with no spaces, add spaces before capitals
122
+ // Stryker disable next-line Regex: the test is an optimization gate; the replaces below are a no-op without a [a-z][A-Z] boundary, so widening it is behaviour-preserving.
19
123
  if (raw && !raw.includes(" ") && /[a-z][A-Z]/.test(raw)) {
20
- return raw.replace(/([a-z])([A-Z])/g, "$1 $2")
21
- .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
124
+ const spaced = raw.replace(/([a-z])([A-Z])/g, "$1 $2");
125
+ // Stryker disable next-line Regex: collapsing the acronym group [A-Z]+ -> [A-Z] leaves the unmatched leading uppercase chars in place, producing identical output.
126
+ return spaced.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
22
127
  }
23
128
  return raw;
24
129
  }
25
- function inferVideoTasks(name) {
26
- const tl = name.toLowerCase();
130
+ /** Match a list of keyword fragments against id + name (both lowercased). */
131
+ export function matchesAny(haystack, ...needles) {
132
+ return needles.some((n) => haystack.includes(n));
133
+ }
134
+ /**
135
+ * Endpoints that take an existing clip and hand back another one. Their ids
136
+ * never spell out "video-to-video", so without this list they fall through to
137
+ * the generator branch and turn up in the text/image-to-video pickers with no
138
+ * video to work on.
139
+ */
140
+ const VIDEO_SOURCE_KEYWORDS = [
141
+ "extend-video",
142
+ "extend video",
143
+ "reframe",
144
+ "retake",
145
+ "edit-video",
146
+ "video-edit",
147
+ "video edit",
148
+ "restyle",
149
+ "inpaint",
150
+ "background-removal",
151
+ "background removal",
152
+ "dubbing",
153
+ "eraser"
154
+ ];
155
+ /** Resolution / detail enhancers that take a clip and hand back a bigger one. */
156
+ const VIDEO_UPSCALE_KEYWORDS = [
157
+ "upscal",
158
+ "super-resolution",
159
+ "super resolution",
160
+ "superres",
161
+ "seedvr",
162
+ "vsr",
163
+ "enhancer"
164
+ ];
165
+ /** Frame-rate / slow-motion models that synthesize intermediate frames. */
166
+ const VIDEO_INTERPOLATE_KEYWORDS = [
167
+ "interpolation",
168
+ "interpolate",
169
+ "frame-interp",
170
+ "frame interp",
171
+ "rife",
172
+ "film-net"
173
+ ];
174
+ /** Models that extend a clip past its frame. */
175
+ const VIDEO_OUTPAINT_KEYWORDS = ["outpaint", "uncrop", "expand-video"];
176
+ /** The specialized transform an id names, or null for everything else. */
177
+ function specializedVideoTask(hay) {
178
+ if (matchesAny(hay, ...VIDEO_UPSCALE_KEYWORDS))
179
+ return "upscale_video";
180
+ if (matchesAny(hay, ...VIDEO_INTERPOLATE_KEYWORDS))
181
+ return "interpolate_video";
182
+ if (matchesAny(hay, ...VIDEO_OUTPAINT_KEYWORDS))
183
+ return "outpaint_video";
184
+ return null;
185
+ }
186
+ /**
187
+ * A LoRA trainer, not a generation endpoint. `fal-ai/ltx23-trainer-v2/outpaint`
188
+ * trains an outpainting adapter from a dataset URL — it takes no clip and
189
+ * returns no video, so every task keyword in its id is about what the trained
190
+ * weights will do, not what the endpoint does.
191
+ */
192
+ function isTrainerEndpoint(hay) {
193
+ return matchesAny(hay, "trainer", "-train", "/train", "training");
194
+ }
195
+ /** A bare `/extend` path segment (`.../ltx-video-13b-dev/extend`). */
196
+ const EXTEND_SEGMENT = /(^|[/\- ])extend([/\- ]|$)/;
197
+ function isVideoExtensionEndpoint(name, id) {
198
+ const hay = `${id} ${name}`.toLowerCase();
199
+ return (!isTrainerEndpoint(hay) &&
200
+ (EXTEND_SEGMENT.test(id.toLowerCase()) ||
201
+ matchesAny(hay, "extend-video", "video-extend", "video-extension", "video extension")));
202
+ }
203
+ /**
204
+ * Endpoints that need a still (or several) to start from, named in ways the
205
+ * plain "image-to-video" test misses. They are image-conditioned only: a text
206
+ * prompt alone is not a call these accept.
207
+ */
208
+ const IMAGE_CONDITIONED_KEYWORDS = [
209
+ "first-last-frame-to-video",
210
+ "first last frame to video",
211
+ "start-end-to-video",
212
+ "keyframes-to-video",
213
+ "frames-to-video",
214
+ ];
215
+ const REFERENCE_VIDEO_KEYWORDS = [
216
+ "reference-to-video",
217
+ "reference to video",
218
+ "referencetovideo",
219
+ "ref-to-video",
220
+ "r2v"
221
+ ];
222
+ export function inferVideoTasks(name, id) {
223
+ const hay = `${id} ${name}`.toLowerCase();
27
224
  const tasks = [];
28
- if (tl.includes("text-to-video") || tl.includes("text to video") || tl.includes("texttovideo")) {
225
+ if (isVideoExtensionEndpoint(name, id)) {
226
+ return ["extend_video"];
227
+ }
228
+ // Specialized video transforms — kept out of the text/image generation lists.
229
+ if (matchesAny(hay, "lipsync", "lip-sync", "lip sync")) {
230
+ return ["lip_sync"];
231
+ }
232
+ // Audio-conditioned generators (LTX's audio-to-video, talking-avatar
233
+ // endpoints) need a recording, so neither generation picker can call one.
234
+ // `image-audio-to-video` matches here too, and belongs here: without the
235
+ // audio the call fails whatever image it gets.
236
+ if (matchesAny(hay, "audio-to-video", "audio to video", "speech-to-video")) {
237
+ return ["audio_to_video"];
238
+ }
239
+ if (matchesAny(hay, "video-to-video", "video to video", "videotovideo", "v2v")) {
240
+ return ["video_to_video"];
241
+ }
242
+ // Specialized video transforms. Each takes a source clip and answers a
243
+ // question `video_to_video` cannot: how big, how many frames, how wide.
244
+ // Their ids never spell out "video-to-video", so without these branches they
245
+ // fall through to the generator branch and show up in the text/image-to-video
246
+ // pickers with no video input.
247
+ const specialized = specializedVideoTask(hay);
248
+ if (specialized) {
249
+ // A trainer carries the same keywords and performs none of them, so it
250
+ // stays a plain source-consuming endpoint rather than claiming the task.
251
+ return [isTrainerEndpoint(hay) ? "video_to_video" : specialized];
252
+ }
253
+ if (matchesAny(hay, ...VIDEO_SOURCE_KEYWORDS) ||
254
+ EXTEND_SEGMENT.test(id.toLowerCase())) {
255
+ return ["video_to_video"];
256
+ }
257
+ if (matchesAny(hay, ...REFERENCE_VIDEO_KEYWORDS)) {
258
+ return ["reference_to_video"];
259
+ }
260
+ if (matchesAny(hay, ...IMAGE_CONDITIONED_KEYWORDS)) {
261
+ return ["image_to_video"];
262
+ }
263
+ if (matchesAny(hay, "text-to-video", "text to video", "texttovideo")) {
29
264
  tasks.push("text_to_video");
30
265
  }
31
- if (tl.includes("image-to-video") || tl.includes("image to video") || tl.includes("imagetovideo")) {
266
+ if (matchesAny(hay, "image-to-video", "image to video", "imagetovideo")) {
32
267
  tasks.push("image_to_video");
33
268
  }
34
- return tasks.length > 0 ? tasks : ["text_to_video"];
269
+ // Ambiguous generator — tag both so generation filtering stays strict.
270
+ return tasks.length > 0 ? tasks : ["text_to_video", "image_to_video"];
271
+ }
272
+ /**
273
+ * Infer the task(s) an image model serves from its id/name.
274
+ *
275
+ * Specialized transforms (upscale, background removal, relighting,
276
+ * vectorization) are distinctive and mutually exclusive, so they get a single
277
+ * specific task and stay out of the generation pickers. Everything else is a
278
+ * general generator: t2i vs i2i can't be told apart from FAL/Replicate naming
279
+ * (e.g. `virtual-tryon`, `multiple-angles`, `canny` are image-conditioned but
280
+ * don't say so), so generators are tagged with BOTH generation tasks. Tagging
281
+ * every entry means generation filtering is strict for these providers — no
282
+ * reliance on the untagged-passes-everything fallback.
283
+ */
284
+ export function inferImageTasks(name, id) {
285
+ const hay = `${id} ${name}`.toLowerCase();
286
+ // Outpainting is tested before inpainting: `bria/genfill` aside, the two
287
+ // families share vocabulary ("fill", "expand"), and an endpoint whose id says
288
+ // outpaint never also accepts a mask-confined edit.
289
+ if (matchesAny(hay, "outpaint", "uncrop", "expand-image", "expand image")) {
290
+ return ["outpaint"];
291
+ }
292
+ if (matchesAny(hay, "upscal", "super-resolution", "super resolution", "superres", "esrgan", "seedvr", "clarity")) {
293
+ return ["upscale"];
294
+ }
295
+ if (matchesAny(hay, "background/remove", "remove-background", "remove background", "removebackground", "rembg", "bg-remove", "/remove-bg", "background-removal", "bria/background")) {
296
+ return ["remove_background"];
297
+ }
298
+ // IC-Light (fal-ai/iclight-v2, zsxkib/ic-light-background, …) is a relighting
299
+ // model but never spells out "relight" in its id/name, so match it explicitly.
300
+ if (matchesAny(hay, "relight", "relighting", "re-light", "iclight", "ic-light", "ic_light")) {
301
+ return ["relight"];
302
+ }
303
+ // Segmentation models return masks, not a picture — keep them out of every
304
+ // generation picker. "sam" is matched with its separators so it cannot hit
305
+ // a name that merely contains those three letters, and fal's `sam-audio`
306
+ // stem separators are excluded outright: they take a recording, not an image.
307
+ if (
308
+ // An RLE endpoint answers with run-length text and no mask image, which
309
+ // nothing downstream here can render — leave it out of the task entirely.
310
+ !matchesAny(hay, "sam-audio", "image-rle", "video-rle") &&
311
+ matchesAny(hay, "segment", "segmentation", "/sam", "sam-2", "sam2", "sam-3", "sam3", "evf-sam", "birefnet")) {
312
+ return ["segment"];
313
+ }
314
+ if (matchesAny(hay, "vectorize", "vectorization", "/vectorize", "to-svg", "image-to-svg")) {
315
+ return ["vectorize"];
316
+ }
317
+ // Mask-guided editors that don't declare their mask field. `buildImageModels`
318
+ // tags `inpainting` from a declared mask input, which is the reliable signal;
319
+ // this catches the endpoints whose manifest entry carries no mask field but
320
+ // whose id says what they are (`fal-ai/lora/inpaint`, `bria/genfill`). The
321
+ // tag is added, not substituted: these take a prompt and an image like any
322
+ // other editor, so they stay answers to an image_to_image request too.
323
+ const tasks = ["text_to_image", "image_to_image"];
324
+ if (matchesAny(hay, "inpaint", "genfill", "eraser")) {
325
+ tasks.push("inpainting");
326
+ }
327
+ return tasks;
35
328
  }
36
329
  // ---------------------------------------------------------------------------
37
330
  // Manifest cache
38
331
  // ---------------------------------------------------------------------------
39
332
  const _cache = new Map();
40
- function loadManifest(packageName, exportPath) {
333
+ export function loadManifest(packageName, exportPath) {
41
334
  const key = `${packageName}/${exportPath}`;
335
+ // Stryker disable next-line ConditionalExpression: this cache short-circuit is a pure memoization; skipping it just re-resolves the same manifest — behaviour-preserving.
42
336
  if (_cache.has(key))
43
337
  return _cache.get(key);
44
338
  try {
45
- const req = createRequire(import.meta.url);
46
- const data = req(`${packageName}/${exportPath}`);
339
+ const data = loadPackageAssetJson({ pkg: packageName, path: exportPath }, import.meta.url);
47
340
  _cache.set(key, data);
48
341
  return data;
49
342
  }
50
343
  catch (err) {
344
+ // Stryker disable next-line StringLiteral: diagnostic log, not asserted.
51
345
  log.warn(`Could not load ${key}: ${err}`);
52
346
  _cache.set(key, []);
53
347
  return [];
@@ -57,7 +351,83 @@ function loadManifest(packageName, exportPath) {
57
351
  // Public API
58
352
  // ---------------------------------------------------------------------------
59
353
  export function loadVideoModels(packageName, exportPath, provider) {
60
- const manifest = loadManifest(packageName, exportPath);
354
+ return buildVideoModels(loadManifest(packageName, exportPath), provider);
355
+ }
356
+ /**
357
+ * Does this entry refuse the call without an input of `kind`?
358
+ *
359
+ * The declaration, not the name: a manifest field marked `required` is the
360
+ * endpoint's own statement that it cannot run without that media. Read across
361
+ * both manifest conventions — FAL/Replicate `inputFields` (`propType`) and
362
+ * AtlasCloud/Kie `fields` (`type`). Kie's `uploads` descriptors carry no
363
+ * requiredness, so they answer false and the name-based inference stands.
364
+ */
365
+ export function requiresMediaInput(n, kind) {
366
+ const required = (t, fRequired) => fRequired === true && (t === kind || t === `list[${kind}]`);
367
+ // Stryker disable next-line ArrayDeclaration: an entry with no inputFields declares nothing required either way.
368
+ if (n.inputFields?.some((f) => required(f.propType.toLowerCase(), f.required))) {
369
+ return true;
370
+ }
371
+ // Stryker disable next-line ArrayDeclaration: same rationale for the fields convention.
372
+ return n.fields?.some((f) => required(f.type.toLowerCase(), f.required)) ?? false;
373
+ }
374
+ /**
375
+ * Drop the generation tasks an entry's own declared inputs rule out, and fall
376
+ * back to the transform task when that leaves nothing.
377
+ *
378
+ * Inference reads names, and a name is often silent about direction — which is
379
+ * why an ambiguous generator is tagged with both. A *required* image input is
380
+ * not silent: `alibaba/qwen-image-3/edit` cannot generate from a prompt alone,
381
+ * and it was the top-ranked `text_to_image` answer a live session got, because
382
+ * nothing here read the field that says so.
383
+ *
384
+ * Only ever subtracts. Explicit `supportedTasks` never reach this.
385
+ */
386
+ export function narrowTasksByRequiredInputs(tasks, n, kind) {
387
+ const generationTask = kind === "image" ? "text_to_image" : "text_to_video";
388
+ const transformTask = kind === "image" ? "image_to_image" : "video_to_video";
389
+ const drop = new Set();
390
+ const hasRequiredReference = (entry) => manifestEntryMediaInputs(entry).some((field) => /reference|refers|subject/i.test(`${field.name} ${field.apiName}`) &&
391
+ ((entry.inputFields ?? []).some((f) => (f.apiParamName ?? f.name) === field.apiName && f.required === true) ||
392
+ (entry.fields ?? []).some((f) => f.name === field.apiName && f.required === true)));
393
+ if (kind === "video" && !tasks.includes("video_to_video") &&
394
+ !hasRequiredSourceMedia(n) && hasRequiredReference(n)) {
395
+ drop.add("text_to_video");
396
+ drop.add("image_to_video");
397
+ const referenceTasks = tasks.filter((task) => task === "reference_to_video");
398
+ if (referenceTasks.length > 0)
399
+ return referenceTasks;
400
+ return ["reference_to_video"];
401
+ }
402
+ if (requiresMediaInput(n, "image"))
403
+ drop.add(generationTask);
404
+ if (kind === "video" && requiresMediaInput(n, "video")) {
405
+ drop.add("text_to_video");
406
+ drop.add("image_to_video");
407
+ }
408
+ if (drop.size === 0)
409
+ return tasks;
410
+ const kept = tasks.filter((t) => !drop.has(t));
411
+ return kept.length > 0 ? kept : [transformTask];
412
+ }
413
+ /**
414
+ * Tasks whose whole call is "here is a clip, hand me back another one". A
415
+ * model that declares no video input cannot serve one however its id reads:
416
+ * `grok-imagine/upscale` upscales a previous task by id, and fal's
417
+ * `amt-interpolation/frame-interpolation` takes a frame list. Offered under
418
+ * these tasks they fail at call time with nothing to work on.
419
+ */
420
+ const VIDEO_SOURCE_TASKS = new Set([
421
+ "extend_video",
422
+ "upscale_video",
423
+ "interpolate_video",
424
+ "outpaint_video"
425
+ ]);
426
+ function declaresVideoInput(entry) {
427
+ return manifestEntryMediaInputs(entry).some((f) => f.kind === "video");
428
+ }
429
+ /** Pure transform: manifest nodes → deduplicated, task-tagged video models. */
430
+ export function buildVideoModels(manifest, provider) {
61
431
  const seen = new Map();
62
432
  for (const n of manifest) {
63
433
  if (n.outputType !== "video")
@@ -66,30 +436,744 @@ export function loadVideoModels(packageName, exportPath, provider) {
66
436
  if (!id)
67
437
  continue;
68
438
  const name = nodeName(n);
69
- const tasks = inferVideoTasks(name);
439
+ const declared = explicitTasks(n);
440
+ const inferred = inferVideoTasks(name, id);
441
+ const referenceInputs = manifestEntryReferenceInputs(n);
442
+ let tasks = declared ?? narrowTasksByRequiredInputs(inferred, n, "video");
443
+ if (!declared && referenceInputs.length > 0 &&
444
+ !hasRequiredSourceMedia(n) && !tasks.includes("video_to_video")) {
445
+ const dedicatedReference = inferred.includes("reference_to_video");
446
+ const requiredReference = referenceInputs.some((field) => field.required === true);
447
+ if (dedicatedReference || requiredReference) {
448
+ tasks = ["reference_to_video"];
449
+ }
450
+ else if (!tasks.includes("reference_to_video")) {
451
+ tasks = [...tasks, "reference_to_video"];
452
+ }
453
+ }
454
+ tasks = tasks.filter((task) => task !== "reference_to_video" || referenceInputs.length > 0);
455
+ tasks = tasks.filter((task) => !VIDEO_SOURCE_TASKS.has(task) || declaresVideoInput(n));
456
+ const supportsEditReferences = tasks.includes("video_to_video") &&
457
+ declaresVideoInput(n) &&
458
+ referenceInputs.filter((field) => field.kind === "image").length === 1 &&
459
+ !referenceInputs.some((field) => field.kind === "video" && field.required);
460
+ tasks = tasks.filter((task) => task !== "video_to_video_reference");
461
+ if (supportsEditReferences) {
462
+ tasks.push("video_to_video_reference");
463
+ }
464
+ // The direct extension contract needs a source video and added duration.
465
+ // A missing mode field means the endpoint continues from the last frame;
466
+ // explicit mode fields contribute only the directions they declare.
467
+ const extensionModeValues = (n.inputFields ?? []).find((field) => (field.apiParamName ?? field.name) === "mode")?.enumValues ??
468
+ (n.fields ?? []).find((field) => field.name === "mode")?.values;
469
+ const hasExtensionDuration = (n.inputFields ?? []).some((field) => (field.apiParamName ?? field.name) === "duration") || (n.fields ?? []).some((field) => field.name === "duration");
470
+ tasks = tasks.filter((task) => task !== "extend_video" ||
471
+ (hasExtensionDuration &&
472
+ (!extensionModeValues ||
473
+ extensionModeValues.includes("start") ||
474
+ extensionModeValues.includes("end"))));
475
+ if (tasks.includes("extend_video")) {
476
+ if (!extensionModeValues || extensionModeValues.includes("end")) {
477
+ tasks.push("extend_video_end");
478
+ }
479
+ if (extensionModeValues?.includes("start")) {
480
+ tasks.push("extend_video_start");
481
+ }
482
+ }
483
+ if (tasks.length === 0)
484
+ continue;
70
485
  const existing = seen.get(id);
71
486
  if (existing) {
487
+ // Stryker disable next-line ArrayDeclaration: existing entries always carry a non-empty supportedTasks (set when first created), so the `?? []` fallback is dead.
72
488
  const merged = new Set([...(existing.supportedTasks ?? []), ...tasks]);
73
489
  existing.supportedTasks = [...merged];
490
+ // Prefer the first entry that actually declares constraints (image- and
491
+ // text-to-video variants of one model can share an id).
492
+ const c = videoConstraints(n);
493
+ existing.durations ??= c.durations;
494
+ existing.resolutions ??= c.resolutions;
495
+ existing.aspectRatios ??= c.aspectRatios;
74
496
  }
75
497
  else {
76
- seen.set(id, { id, name, provider, supportedTasks: tasks });
498
+ seen.set(id, {
499
+ id,
500
+ name,
501
+ provider,
502
+ supportedTasks: tasks,
503
+ ...videoConstraints(n)
504
+ });
77
505
  }
78
506
  }
79
507
  return [...seen.values()];
80
508
  }
509
+ export function manifestEntryMediaInputs(entry) {
510
+ const fromUploads = (entry.uploads ?? [])
511
+ .filter((u) => u.kind === "image" || u.kind === "video" || u.kind === "audio")
512
+ .map((u) => {
513
+ const field = (entry.fields ?? []).find((f) => f.name === u.field);
514
+ const input = (entry.inputFields ?? []).find((f) => f.name === u.field ||
515
+ (f.apiParamName ?? f.name) === (u.paramName ?? u.field));
516
+ return {
517
+ apiName: u.paramName ?? `${u.field}_url${u.isList ? "s" : ""}`,
518
+ isList: Boolean(u.isList),
519
+ name: u.field,
520
+ kind: u.kind,
521
+ required: field?.required ?? input?.required,
522
+ min: field?.min ?? input?.min,
523
+ max: field?.max ?? input?.max,
524
+ wrapInto: field?.wrapInto
525
+ };
526
+ });
527
+ if (fromUploads.length > 0)
528
+ return fromUploads;
529
+ const fromInput = (entry.inputFields ?? [])
530
+ .filter((f) => {
531
+ const type = f.propType.toLowerCase();
532
+ return [
533
+ "image",
534
+ "list[image]",
535
+ "video",
536
+ "list[video]",
537
+ "audio",
538
+ "list[audio]"
539
+ ].includes(type);
540
+ })
541
+ .map((f) => {
542
+ const type = f.propType.toLowerCase();
543
+ return {
544
+ apiName: f.apiParamName ?? f.name,
545
+ isList: type.startsWith("list["),
546
+ name: f.name,
547
+ kind: (type.includes("video")
548
+ ? "video"
549
+ : type.includes("audio")
550
+ ? "audio"
551
+ : "image"),
552
+ required: f.required,
553
+ min: f.min,
554
+ max: f.max
555
+ };
556
+ });
557
+ if (fromInput.length > 0)
558
+ return fromInput;
559
+ return (entry.fields ?? [])
560
+ .filter((f) => /^(image|video|audio|list\[(image|video|audio)\])$/i.test(f.type))
561
+ .map((f) => ({
562
+ apiName: f.name,
563
+ isList: f.type.toLowerCase().startsWith("list["),
564
+ name: f.name,
565
+ kind: f.type.toLowerCase().includes("video")
566
+ ? "video"
567
+ : f.type.toLowerCase().includes("audio")
568
+ ? "audio"
569
+ : "image",
570
+ required: f.required,
571
+ min: f.min,
572
+ max: f.max,
573
+ wrapInto: f.wrapInto
574
+ }));
575
+ }
576
+ export function getModelReferenceInputs(packageName, exportPath, modelId) {
577
+ const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
578
+ if (!entry)
579
+ return [];
580
+ return manifestEntryReferenceInputs(entry);
581
+ }
582
+ function hasRequiredSourceMedia(entry) {
583
+ const references = manifestEntryReferenceInputs(entry);
584
+ return manifestEntryMediaInputs(entry).some((field) => field.required &&
585
+ !references.some((reference) => reference.apiName === field.apiName));
586
+ }
587
+ function manifestEntryReferenceInputs(entry) {
588
+ const explicitReference = entry.supportedTasks?.includes("reference_to_video") === true ||
589
+ inferVideoTasks(nodeName(entry), nodeId(entry)).includes("reference_to_video");
590
+ return manifestEntryMediaInputs(entry).filter((field) => {
591
+ const names = [field.name, field.apiName];
592
+ // Sora calls its optional first-frame input `input_reference`. It is the
593
+ // single start image used by image_to_video, not a role-specific reference
594
+ // array and must not opt the model into reference_to_video.
595
+ if (names.some((name) => /^input_reference$/i.test(name)))
596
+ return false;
597
+ if (field.kind === "image" && /video/i.test(field.apiName))
598
+ return false;
599
+ if (field.kind === "video" && /image/i.test(field.apiName))
600
+ return false;
601
+ if (names.some((name) => /reference|refers|subject/i.test(name)))
602
+ return true;
603
+ return explicitReference && names.some((name) => /^(?:input_)?(?:images?|videos?)(?:_urls?)?$/i.test(name));
604
+ });
605
+ }
606
+ export function validateReferenceInputs(provider, modelId, inputs, fields) {
607
+ const images = inputs.images.filter((b) => b.length > 0);
608
+ const videos = inputs.videos.filter((b) => b.length > 0);
609
+ const audios = inputs.audios?.filter((b) => b.length > 0) ?? [];
610
+ if (images.length === 0 && videos.length === 0 && audios.length === 0) {
611
+ throw new Error("reference_to_video requires at least one reference image, video, or audio");
612
+ }
613
+ for (const [kind, count] of [
614
+ ["image", images.length],
615
+ ["video", videos.length],
616
+ ["audio", audios.length]
617
+ ]) {
618
+ if (count === 0)
619
+ continue;
620
+ const matching = fields.filter((field) => field.kind === kind);
621
+ if (matching.length > 1)
622
+ throw new Error(`${provider} model ${modelId} has ambiguous reference ${kind} inputs`);
623
+ const field = matching[0];
624
+ if (!field)
625
+ throw new Error(`${provider} model ${modelId} does not support reference ${kind}s`);
626
+ if (!field.isList && count > 1)
627
+ throw new Error(`${provider} model ${modelId} accepts only one reference ${kind}`);
628
+ if (!field.wrapInto && field.min !== undefined && count < field.min) {
629
+ throw new Error(`${provider} model ${modelId} requires at least ${field.min} reference ${kind}(s)`);
630
+ }
631
+ if (!field.wrapInto && field.max !== undefined && count > field.max) {
632
+ throw new Error(`${provider} model ${modelId} accepts at most ${field.max} reference ${kind}(s)`);
633
+ }
634
+ }
635
+ const requiredGroups = new Set();
636
+ for (const field of fields) {
637
+ if (!field.required)
638
+ continue;
639
+ requiredGroups.add(field.wrapInto ?? `${field.kind}:${field.apiName}`);
640
+ }
641
+ for (const group of requiredGroups) {
642
+ const groupFields = fields.filter((field) => (field.wrapInto ?? `${field.kind}:${field.apiName}`) === group);
643
+ const count = groupFields.reduce((total, field) => total +
644
+ (field.kind === "image"
645
+ ? images.length
646
+ : field.kind === "video"
647
+ ? videos.length
648
+ : audios.length), 0);
649
+ if (count === 0)
650
+ throw new Error(`${provider} model ${modelId} requires at least one reference image or video`);
651
+ }
652
+ }
653
+ /**
654
+ * Image inputs a model declares, resolved from its manifest entry. KIE-style
655
+ * `uploads` (which carry the real API param name) take precedence; otherwise
656
+ * the FAL/Replicate `inputFields` image/list[image] entries are used.
657
+ */
658
+ /**
659
+ * Input parameter names a model declares, from the generated manifest.
660
+ *
661
+ * Providers assemble a generic input bag from `*Params` (prompt,
662
+ * negative_prompt, strength, seed…), but each endpoint accepts only a subset.
663
+ * Replicate rejects the whole prediction with a validation error when it sees
664
+ * an undeclared field rather than ignoring it, so sending the superset fails
665
+ * outright — `decart/lucy-edit-2` takes neither `negative_prompt` nor
666
+ * `strength`.
667
+ *
668
+ * Returns an empty set when the model is missing from the manifest or declares
669
+ * no fields. Callers must read that as "unknown, send everything" rather than
670
+ * "accepts nothing", so a model absent from the manifest keeps working.
671
+ */
672
+ export function getModelInputNames(packageName, exportPath, modelId) {
673
+ const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
674
+ return new Set((entry?.inputFields ?? [])
675
+ .map((f) => f.name)
676
+ .filter((n) => typeof n === "string" && n.length > 0));
677
+ }
678
+ /**
679
+ * Every media input a model declares, image and video alike, normalized across
680
+ * the manifest conventions. {@link getModelImageInputs} answers the image-only
681
+ * question; a video transform needs the field its clip attaches to.
682
+ */
683
+ export function getModelMediaInputs(packageName, exportPath, modelId) {
684
+ const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
685
+ if (!entry)
686
+ return [];
687
+ return manifestEntryMediaInputs(entry);
688
+ }
689
+ export function getModelImageInputs(packageName, exportPath, modelId) {
690
+ const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
691
+ if (!entry)
692
+ return [];
693
+ return manifestEntryImageInputs(entry);
694
+ }
695
+ /**
696
+ * Pure: resolve the image inputs declared by a single manifest entry. KIE-style
697
+ * `uploads` (which carry the real API param name) take precedence; otherwise the
698
+ * FAL/Replicate `inputFields` image / list[image] entries are used.
699
+ */
700
+ export function manifestEntryImageInputs(entry) {
701
+ if (entry.uploads?.length) {
702
+ return entry.uploads
703
+ .filter((u) => u.kind === "image")
704
+ .map((u) => ({
705
+ apiName: u.paramName ?? `${u.field}_url${u.isList ? "s" : ""}`,
706
+ isList: Boolean(u.isList),
707
+ name: u.field
708
+ }));
709
+ }
710
+ return (entry.inputFields ?? [])
711
+ .filter((f) => {
712
+ const t = f.propType.toLowerCase();
713
+ return t === "image" || t === "list[image]";
714
+ })
715
+ .map((f) => ({
716
+ apiName: f.apiParamName ?? f.name,
717
+ isList: f.propType.toLowerCase().startsWith("list["),
718
+ name: f.name
719
+ }));
720
+ }
721
+ /**
722
+ * The input fields a model declares, normalized across manifest conventions.
723
+ * Kie `fields` (where `name` is the API param and options live in `values`) take
724
+ * precedence; otherwise the FAL/Replicate `inputFields` are normalized
725
+ * (`apiParamName ?? name`, `propType` lowercased, `enumValues`).
726
+ */
727
+ export function getModelInputFields(packageName, exportPath, modelId) {
728
+ const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
729
+ if (!entry)
730
+ return [];
731
+ if (entry.fields?.length) {
732
+ return entry.fields.map((f) => {
733
+ const field = {
734
+ name: f.name,
735
+ type: f.type.toLowerCase()
736
+ };
737
+ if (f.values && f.values.length > 0) {
738
+ field.enumValues = f.values;
739
+ }
740
+ if (f.required !== undefined) {
741
+ field.required = f.required;
742
+ }
743
+ if (f.default !== undefined) {
744
+ field.default = f.default;
745
+ }
746
+ if (isNumber(f.min)) {
747
+ field.min = f.min;
748
+ }
749
+ if (isNumber(f.max)) {
750
+ field.max = f.max;
751
+ }
752
+ if (f.wrapInto !== undefined) {
753
+ field.wrapInto = f.wrapInto;
754
+ }
755
+ return field;
756
+ });
757
+ }
758
+ return (entry.inputFields ?? []).map((f) => {
759
+ const field = {
760
+ name: f.apiParamName ?? f.name,
761
+ type: f.propType.toLowerCase()
762
+ };
763
+ if (f.enumValues && f.enumValues.length > 0) {
764
+ field.enumValues = f.enumValues;
765
+ }
766
+ return field;
767
+ });
768
+ }
769
+ function requiredTextInputsOf(entry) {
770
+ const names = new Set();
771
+ const textFields = new Set();
772
+ // Kie: `fields`, where `name` is the API param.
773
+ for (const field of entry.fields ?? []) {
774
+ if (field.type.toLowerCase() !== "str")
775
+ continue;
776
+ textFields.add(field.name);
777
+ if (field.required === true)
778
+ names.add(field.name);
779
+ }
780
+ // Kie also states it a second way, as the rules the node factory enforces
781
+ // before spending a call. Most entries say both; some say only this one.
782
+ for (const rule of entry.validation ?? []) {
783
+ if (rule.rule === "not_empty" && textFields.has(rule.field)) {
784
+ names.add(rule.field);
785
+ }
786
+ }
787
+ // FAL / Replicate: `inputFields`. The declared `name` is the property name;
788
+ // `apiParamName` is the wire name, which is not what a node property is called.
789
+ for (const field of entry.inputFields ?? []) {
790
+ if (field.required === true && field.propType.toLowerCase() === "str") {
791
+ names.add(field.name);
792
+ }
793
+ }
794
+ return [...names];
795
+ }
796
+ /**
797
+ * Text fields `modelId` refuses to run without, by the name the manifest gives
798
+ * them — which is also the name the generic media nodes give the matching
799
+ * property, so a caller can match one against the other.
800
+ *
801
+ * Restricted to text on purpose. A required *media* field is fed through the
802
+ * node's own image/video wiring under a different API name (kie's `images`
803
+ * arrives as `image_urls`), and a required number or enum carries a default the
804
+ * provider fills in. What is left is the case that actually fails: a text field
805
+ * the endpoint rejects when blank. `kling-2.6/image-to-video` declares `prompt`
806
+ * required in both conventions and answers an empty one with
807
+ * `500 This field is required`, naming neither the field nor the model.
808
+ *
809
+ * Undefined means the model is not in this manifest — "cannot tell", never
810
+ * "requires nothing". An entry that declares none returns an empty list.
811
+ */
812
+ export function getRequiredTextInputNames(packageName, exportPath, modelId) {
813
+ const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
814
+ if (!entry)
815
+ return undefined;
816
+ return requiredTextInputsOf(entry);
817
+ }
818
+ /**
819
+ * Kie execution metadata for a model, or undefined when the model id is unknown.
820
+ */
821
+ export function getManifestNodeMeta(packageName, exportPath, modelId) {
822
+ const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
823
+ if (!entry)
824
+ return undefined;
825
+ const meta = { useSuno: Boolean(entry.useSuno) };
826
+ if (entry.sunoEndpoint !== undefined) {
827
+ meta.sunoEndpoint = entry.sunoEndpoint;
828
+ }
829
+ if (entry.pollInterval !== undefined) {
830
+ meta.pollInterval = entry.pollInterval;
831
+ }
832
+ if (entry.maxAttempts !== undefined) {
833
+ meta.maxAttempts = entry.maxAttempts;
834
+ }
835
+ return meta;
836
+ }
837
+ // Auxiliary image inputs (mask / control / reference / style / end-frame, …)
838
+ // must never receive the primary source image in a generic request.
839
+ const AUXILIARY_IMAGE_RE =
840
+ // Stryker disable next-line Regex: heuristic alternation; auxiliary-vs-primary behaviour is pinned by tests, but the optional `[_-]?` separator variants are functionally interchangeable for real field names.
841
+ /mask|control|reference|style|depth|canny|pose|ip[_-]?adapter|redux|face|last_frame|end_image|end_frame/i;
842
+ // Primary source-image field names, best first. Covers i2i (`image`,
843
+ // `input_image`, `image_input`) and i2v first-frame (`first_frame_image`,
844
+ // `start_image`).
845
+ const PRIMARY_IMAGE_PRIORITY = [
846
+ "image",
847
+ "input_image",
848
+ "image_input",
849
+ "images",
850
+ "input_images",
851
+ "img",
852
+ "first_frame_image",
853
+ "start_image",
854
+ "source_image",
855
+ "image_path",
856
+ "subject_image",
857
+ "file"
858
+ ];
859
+ /**
860
+ * Choose the mask image input declared by a model's manifest entry. Returns the
861
+ * first field whose name or API param name contains "mask", or undefined when
862
+ * the model doesn't declare one (callers fall back to a conventional name like
863
+ * `mask_url`).
864
+ */
865
+ export function selectMaskImageInput(inputs) {
866
+ return inputs.find((i) => /mask/i.test(i.name) || /mask/i.test(i.apiName));
867
+ }
868
+ /**
869
+ * Choose the primary image input for a generic image-to-image / image-to-video
870
+ * request. Auxiliary inputs (mask, control, reference/style/end-frame images)
871
+ * are skipped. When more than one source image is supplied, a list-typed field
872
+ * is strongly preferred so every image is forwarded.
873
+ */
874
+ export function selectPrimaryImageInput(inputs, imageCount) {
875
+ const primary = inputs.filter((i) => !AUXILIARY_IMAGE_RE.test(i.name));
876
+ const pool = primary.length > 0 ? primary : inputs;
877
+ // Stryker disable next-line ConditionalExpression: equivalent — pool is empty only when inputs is empty, and the sort+[0] below also yields undefined for an empty pool.
878
+ if (pool.length === 0)
879
+ return undefined;
880
+ const multiple = imageCount > 1;
881
+ const score = (i) => {
882
+ let idx = PRIMARY_IMAGE_PRIORITY.indexOf(i.name.toLowerCase());
883
+ if (idx < 0)
884
+ idx = PRIMARY_IMAGE_PRIORITY.length;
885
+ if (multiple)
886
+ idx += i.isList ? -100 : 10;
887
+ return idx;
888
+ };
889
+ return [...pool].sort((a, b) => score(a) - score(b))[0];
890
+ }
81
891
  export function loadImageModels(packageName, exportPath, provider) {
82
- const manifest = loadManifest(packageName, exportPath);
892
+ return buildImageModels(loadManifest(packageName, exportPath), provider);
893
+ }
894
+ /** Pure transform: manifest nodes → deduplicated, task-tagged image models. */
895
+ export function buildImageModels(manifest, provider) {
896
+ const seen = new Map();
897
+ for (const n of manifest) {
898
+ const id = nodeId(n);
899
+ if (!id || seen.has(id))
900
+ continue;
901
+ const name = nodeName(n);
902
+ // Copy so the mask-input augmentation below never mutates the manifest
903
+ // node's own `supportedTasks` array (which `loadManifest` memoizes and
904
+ // shares across every caller).
905
+ const tasks = [
906
+ ...(explicitTasks(n) ??
907
+ narrowTasksByRequiredInputs(inferImageTasks(name, id), n, "image"))
908
+ ];
909
+ // Endpoints that declare a mask image input support inpainting (mask-guided
910
+ // editing). Tag them so the inpaint capability/picker can find them.
911
+ if (!tasks.includes("inpainting") &&
912
+ selectMaskImageInput(manifestEntryImageInputs(n))) {
913
+ tasks.push("inpainting");
914
+ }
915
+ // Image-typed entries always qualify. `dict`-typed entries (FAL endpoints
916
+ // whose response schema is an object, e.g. clarity-upscaler) are salvaged
917
+ // only when they're recognizable image transforms, so the picker can offer
918
+ // them under upscale/remove-background/relight/vectorize.
919
+ const qualifies = n.outputType === "image" ||
920
+ // Stryker disable next-line ConditionalExpression,EqualityOperator: tasks is always non-empty (infer* returns >=1, explicit is non-empty), so `tasks.length > 0` is invariantly true here.
921
+ (n.outputType === "dict" && tasks.length > 0);
922
+ if (!qualifies)
923
+ continue;
924
+ seen.set(id, {
925
+ id,
926
+ name,
927
+ provider,
928
+ supportedTasks: tasks,
929
+ ...imageConstraints(n)
930
+ });
931
+ }
932
+ return [...seen.values()];
933
+ }
934
+ /**
935
+ * Everything an audio entry says about itself: id, name, and the capability
936
+ * tags. FAL files every text→audio endpoint — speech, music, and sound
937
+ * effects alike — under `moduleName: "text_to_audio"`, so the module name
938
+ * cannot tell the three apart; the tags can.
939
+ */
940
+ function audioHaystack(n) {
941
+ return `${nodeId(n)} ${nodeName(n)} ${(n.tags ?? []).join(" ")}`.toLowerCase();
942
+ }
943
+ /** Speech that self-declares in its id or name, rather than in a tag. */
944
+ function saysTextToSpeech(n) {
945
+ const hay = `${nodeId(n)} ${nodeName(n)}`.toLowerCase();
946
+ return matchesAny(hay, "text-to-speech", "text to speech", "texttospeech", "tts");
947
+ }
948
+ // Endpoints that take existing audio and hand back more of it. They are not
949
+ // generators: offered as a text_to_music or text_to_speech model they fail at
950
+ // call time with nothing to work on.
951
+ const AUDIO_TRANSFORM_KEYWORDS = [
952
+ "audio-to-audio",
953
+ "audio_to_audio",
954
+ "speech-to-speech",
955
+ "video-to-audio",
956
+ "inpaint",
957
+ "outpaint",
958
+ "isolation",
959
+ "voice-changer",
960
+ "extend-audio",
961
+ "audio-extend",
962
+ "super-resolution",
963
+ "loudnorm",
964
+ "compressor",
965
+ "impulse-response"
966
+ ];
967
+ // Sound-effect generators. They emit audio from a prompt like a music model
968
+ // does, and are neither music nor speech.
969
+ const SOUND_EFFECT_KEYWORDS = [
970
+ "sound-effect",
971
+ "sound_effect",
972
+ "sound effects",
973
+ "soundeffect",
974
+ "sfx",
975
+ "foley"
976
+ ];
977
+ // Speech engines whose names carry no "tts"/"speech" substring. Without them
978
+ // FAL's Kokoro language variants — tagged only `[audio, generation,
979
+ // text-to-audio, sound]` — are in no list at all.
980
+ const TTS_KEYWORDS = [
981
+ "speech",
982
+ "voiceover",
983
+ "narration",
984
+ "dialogue",
985
+ "kokoro",
986
+ "zonos",
987
+ "chatterbox"
988
+ ];
989
+ /** Takes existing audio rather than generating it from a prompt. */
990
+ export function isAudioTransformNode(n) {
991
+ return matchesAny(audioHaystack(n), ...AUDIO_TRANSFORM_KEYWORDS);
992
+ }
993
+ /** Generates sound effects — neither music nor speech. */
994
+ export function isSoundEffectNode(n) {
995
+ return matchesAny(audioHaystack(n), ...SOUND_EFFECT_KEYWORDS);
996
+ }
997
+ /**
998
+ * Is this manifest entry a text-to-speech model? An explicit task or FAL's
999
+ * `moduleName: "text_to_speech"` settles it, as does a "text-to-speech"/"tts"
1000
+ * hint in the id or name — those are the endpoint naming itself, and outrank
1001
+ * the exclusions below, so a voice-clone TTS endpoint that also takes
1002
+ * reference audio stays a TTS model. Everything else is decided on the tags:
1003
+ * a music model, an audio transform and a sound-effect generator are none of
1004
+ * them speech, and what is left qualifies on a speech keyword.
1005
+ */
1006
+ export function isTTSNode(n) {
1007
+ if (n.outputType !== "audio")
1008
+ return false;
1009
+ if (explicitTasks(n)?.includes("text_to_speech"))
1010
+ return true;
1011
+ if (n.moduleName === "text_to_speech")
1012
+ return true;
1013
+ if (saysTextToSpeech(n))
1014
+ return true;
1015
+ if (isMusicNode(n) || isAudioTransformNode(n) || isSoundEffectNode(n)) {
1016
+ return false;
1017
+ }
1018
+ return matchesAny(audioHaystack(n), ...TTS_KEYWORDS);
1019
+ }
1020
+ export function loadTTSModels(packageName, exportPath, provider) {
1021
+ return buildTTSModels(loadManifest(packageName, exportPath), provider);
1022
+ }
1023
+ /** Pure transform: manifest nodes → deduplicated TTS models with voice lists. */
1024
+ export function buildTTSModels(manifest, provider) {
83
1025
  const seen = new Map();
84
1026
  for (const n of manifest) {
85
- if (n.outputType !== "image")
1027
+ if (!isTTSNode(n))
86
1028
  continue;
87
1029
  const id = nodeId(n);
88
- if (!id)
1030
+ if (!id || seen.has(id))
1031
+ continue;
1032
+ // Preset voices come from an enumerated `voice` (or `speaker`) field.
1033
+ // Models that take a free-form voice id / reference audio have none.
1034
+ const voices = enumValuesFor(n, "voice") ?? enumValuesFor(n, "speaker");
1035
+ const model = {
1036
+ id,
1037
+ name: nodeName(n),
1038
+ provider: provider
1039
+ };
1040
+ if (voices) {
1041
+ model.voices = voices;
1042
+ }
1043
+ seen.set(id, model);
1044
+ }
1045
+ return [...seen.values()];
1046
+ }
1047
+ // Music-generation engine names and signal words. Used to tell music endpoints
1048
+ // apart from speech / sound-effect audio nodes (the `outputType === "audio"`
1049
+ // guard plus the `!isTTSNode` check already exclude TTS).
1050
+ const MUSIC_KEYWORDS = [
1051
+ "music",
1052
+ "song",
1053
+ "lyric",
1054
+ "vocal",
1055
+ "instrumental",
1056
+ "soundtrack",
1057
+ "melody",
1058
+ "remix",
1059
+ "cover",
1060
+ "musicgen",
1061
+ "audiogen",
1062
+ "stable-audio",
1063
+ "stable_audio",
1064
+ "suno",
1065
+ "ace-step",
1066
+ "acestep",
1067
+ "diffrhythm",
1068
+ "lyria",
1069
+ "sonauto",
1070
+ "cassette",
1071
+ "beatoven",
1072
+ "jukebox",
1073
+ "flux-music",
1074
+ "yue"
1075
+ ];
1076
+ // Kie Suno ships many audio endpoints, but only a few generate music from a
1077
+ // text prompt — the rest transform existing audio or tasks (referenced by
1078
+ // upload / task / audio / music ids) or output text (lyrics). A required field
1079
+ // whose name references such an existing asset marks a transform, not a
1080
+ // generator.
1081
+ const AUDIO_ASSET_REF_RE =
1082
+ // Stryker disable next-line Regex: heuristic alternation over Suno field names; the exact-boundary variants are interchangeable for the real manifest names, and the predicate is pinned by a test against the real manifest.
1083
+ /upload|task_?id|audio_?id|music_?id|verify|voice_?url/i;
1084
+ /**
1085
+ * Is this Kie-style entry a text-to-music generator? True only when it declares
1086
+ * a free-form `prompt`, selects a generation `model`, and requires no field
1087
+ * that references an existing audio asset or task. On the current Kie manifest
1088
+ * this yields exactly {generate-music, generate-sounds}; every Suno utility
1089
+ * (covers, extends, mashups, vocal splits, lyric/voice tools) is rejected.
1090
+ */
1091
+ export function isKieMusicNode(n) {
1092
+ const fields = n.fields ?? [];
1093
+ const hasPrompt = fields.some((f) => f.name === "prompt");
1094
+ const hasModel = fields.some((f) => f.name === "model");
1095
+ const requiresAsset = fields.some((f) => f.required === true && AUDIO_ASSET_REF_RE.test(f.name));
1096
+ return hasPrompt && hasModel && !requiresAsset;
1097
+ }
1098
+ /**
1099
+ * Is this manifest entry a music-generation model? Music endpoints emit audio
1100
+ * from a prompt but are neither speech nor sound effects. Kie-style entries
1101
+ * (those that carry a `fields` schema or route through Suno) are judged
1102
+ * structurally by `isKieMusicNode` — keyword matching over-tags Suno's utility
1103
+ * endpoints. Everything else needs positive music evidence: an explicit
1104
+ * `text_to_music` task, or a music keyword in the id, name or tags.
1105
+ *
1106
+ * FAL's `moduleName: "text_to_audio"` used to be taken as that evidence, and
1107
+ * it is not one: FAL files every text→audio endpoint under it, so the music
1108
+ * catalog carried the Kokoro voices, `elevenlabs/text-to-dialogue` and every
1109
+ * sound-effect generator — and a session asking for a music model was handed a
1110
+ * dialogue model that a `music_model` property then refused.
1111
+ */
1112
+ export function isMusicNode(n) {
1113
+ if (n.outputType !== "audio")
1114
+ return false;
1115
+ if (Array.isArray(n.fields) || n.useSuno === true) {
1116
+ return isKieMusicNode(n);
1117
+ }
1118
+ if (explicitTasks(n)?.includes("text_to_music"))
1119
+ return true;
1120
+ if (isAudioTransformNode(n) || isSoundEffectNode(n))
1121
+ return false;
1122
+ return matchesAny(audioHaystack(n), ...MUSIC_KEYWORDS);
1123
+ }
1124
+ export function loadMusicModels(packageName, exportPath, provider) {
1125
+ return buildMusicModels(loadManifest(packageName, exportPath), provider);
1126
+ }
1127
+ export function loadAudioToAudioModels(packageName, exportPath, provider) {
1128
+ return buildAudioToAudioModels(loadManifest(packageName, exportPath), provider);
1129
+ }
1130
+ /**
1131
+ * Pure transform: manifest nodes → deduplicated audio-transform models. These
1132
+ * are exactly the entries {@link isAudioTransformNode} keeps out of the TTS and
1133
+ * music lists, which is the point: without this list they are in no list at all.
1134
+ *
1135
+ * The TTS and music tests run first, and win. A speech endpoint that names
1136
+ * itself one — `fal-ai/dia-tts/voice-clone`, `fal-ai/tada/*\/text-to-speech` —
1137
+ * matches a transform keyword through its tags while still needing text to say;
1138
+ * offered here it would be called with a recording and no text. The same
1139
+ * precedence {@link isTTSNode} already applies, applied from this side.
1140
+ */
1141
+ export function buildAudioToAudioModels(manifest, provider) {
1142
+ const seen = new Map();
1143
+ for (const n of manifest) {
1144
+ if (n.outputType !== "audio")
89
1145
  continue;
90
- if (seen.has(id))
1146
+ if (!isAudioTransformNode(n))
1147
+ continue;
1148
+ if (isTTSNode(n) || isMusicNode(n))
1149
+ continue;
1150
+ const id = nodeId(n);
1151
+ if (!id || seen.has(id))
1152
+ continue;
1153
+ seen.set(id, {
1154
+ id,
1155
+ name: nodeName(n),
1156
+ provider,
1157
+ supportedTasks: ["audio_to_audio"]
1158
+ });
1159
+ }
1160
+ return [...seen.values()];
1161
+ }
1162
+ /** Pure transform: manifest nodes → deduplicated text-to-music models. */
1163
+ export function buildMusicModels(manifest, provider) {
1164
+ const seen = new Map();
1165
+ for (const n of manifest) {
1166
+ if (!isMusicNode(n))
1167
+ continue;
1168
+ const id = nodeId(n);
1169
+ if (!id || seen.has(id))
91
1170
  continue;
92
- seen.set(id, { id, name: nodeName(n), provider });
1171
+ seen.set(id, {
1172
+ id,
1173
+ name: nodeName(n),
1174
+ provider: provider,
1175
+ supportedTasks: ["text_to_music"]
1176
+ });
93
1177
  }
94
1178
  return [...seen.values()];
95
1179
  }