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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (665) hide show
  1. package/README.md +29 -0
  2. package/dist/.nodetool-build-stamp +1 -0
  3. package/dist/agent-memory.d.ts +118 -0
  4. package/dist/agent-memory.d.ts.map +1 -0
  5. package/dist/agent-memory.js +186 -0
  6. package/dist/agent-memory.js.map +1 -0
  7. package/dist/async-local-storage.d.ts +20 -0
  8. package/dist/async-local-storage.d.ts.map +1 -0
  9. package/dist/async-local-storage.js +28 -0
  10. package/dist/async-local-storage.js.map +1 -0
  11. package/dist/blender-executor.d.ts +93 -0
  12. package/dist/blender-executor.d.ts.map +1 -0
  13. package/dist/blender-executor.js +294 -0
  14. package/dist/blender-executor.js.map +1 -0
  15. package/dist/comfy-executor.d.ts +23 -1
  16. package/dist/comfy-executor.d.ts.map +1 -1
  17. package/dist/comfy-executor.js +234 -55
  18. package/dist/comfy-executor.js.map +1 -1
  19. package/dist/context-packer.d.ts +1 -1
  20. package/dist/context-packer.d.ts.map +1 -1
  21. package/dist/context-packer.js +23 -25
  22. package/dist/context-packer.js.map +1 -1
  23. package/dist/context.d.ts +954 -127
  24. package/dist/context.d.ts.map +1 -1
  25. package/dist/context.js +2424 -513
  26. package/dist/context.js.map +1 -1
  27. package/dist/cost-reconciler.d.ts +32 -0
  28. package/dist/cost-reconciler.d.ts.map +1 -0
  29. package/dist/cost-reconciler.js +21 -0
  30. package/dist/cost-reconciler.js.map +1 -0
  31. package/dist/entities.d.ts +33 -0
  32. package/dist/entities.d.ts.map +1 -0
  33. package/dist/entities.js +43 -0
  34. package/dist/entities.js.map +1 -0
  35. package/dist/external-media-fetch.d.ts +29 -0
  36. package/dist/external-media-fetch.d.ts.map +1 -0
  37. package/dist/external-media-fetch.js +38 -0
  38. package/dist/external-media-fetch.js.map +1 -0
  39. package/dist/generation-receipt.d.ts +86 -0
  40. package/dist/generation-receipt.d.ts.map +1 -0
  41. package/dist/generation-receipt.js +137 -0
  42. package/dist/generation-receipt.js.map +1 -0
  43. package/dist/generation-registry.d.ts +67 -0
  44. package/dist/generation-registry.d.ts.map +1 -0
  45. package/dist/generation-registry.js +126 -0
  46. package/dist/generation-registry.js.map +1 -0
  47. package/dist/google/client.d.ts +196 -0
  48. package/dist/google/client.d.ts.map +1 -0
  49. package/dist/google/client.js +396 -0
  50. package/dist/google/client.js.map +1 -0
  51. package/dist/google/index.d.ts +3 -0
  52. package/dist/google/index.d.ts.map +1 -0
  53. package/dist/google/index.js +3 -0
  54. package/dist/google/index.js.map +1 -0
  55. package/dist/google/token.d.ts +15 -0
  56. package/dist/google/token.d.ts.map +1 -0
  57. package/dist/google/token.js +24 -0
  58. package/dist/google/token.js.map +1 -0
  59. package/dist/host-binaries.d.ts +106 -0
  60. package/dist/host-binaries.d.ts.map +1 -0
  61. package/dist/host-binaries.js +344 -0
  62. package/dist/host-binaries.js.map +1 -0
  63. package/dist/image-codec.d.ts +74 -0
  64. package/dist/image-codec.d.ts.map +1 -0
  65. package/dist/image-codec.js +273 -0
  66. package/dist/image-codec.js.map +1 -0
  67. package/dist/index.d.ts +56 -6
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +43 -4
  70. package/dist/index.js.map +1 -1
  71. package/dist/invocation-account.d.ts +43 -0
  72. package/dist/invocation-account.d.ts.map +1 -0
  73. package/dist/invocation-account.js +60 -0
  74. package/dist/invocation-account.js.map +1 -0
  75. package/dist/media-ref-bytes.d.ts +19 -0
  76. package/dist/media-ref-bytes.d.ts.map +1 -0
  77. package/dist/media-ref-bytes.js +181 -0
  78. package/dist/media-ref-bytes.js.map +1 -0
  79. package/dist/node-executor.d.ts +113 -2
  80. package/dist/node-executor.d.ts.map +1 -1
  81. package/dist/permission-gate.d.ts +152 -0
  82. package/dist/permission-gate.d.ts.map +1 -0
  83. package/dist/permission-gate.js +87 -0
  84. package/dist/permission-gate.js.map +1 -0
  85. package/dist/prefixed-storage-adapter.d.ts +33 -0
  86. package/dist/prefixed-storage-adapter.d.ts.map +1 -0
  87. package/dist/prefixed-storage-adapter.js +70 -0
  88. package/dist/prefixed-storage-adapter.js.map +1 -0
  89. package/dist/prompt-asset-refs.d.ts +222 -0
  90. package/dist/prompt-asset-refs.d.ts.map +1 -0
  91. package/dist/prompt-asset-refs.js +751 -0
  92. package/dist/prompt-asset-refs.js.map +1 -0
  93. package/dist/provider-cache.d.ts +3 -0
  94. package/dist/provider-cache.d.ts.map +1 -0
  95. package/dist/provider-cache.js +8 -0
  96. package/dist/provider-cache.js.map +1 -0
  97. package/dist/providers/aki-provider.d.ts +33 -10
  98. package/dist/providers/aki-provider.d.ts.map +1 -1
  99. package/dist/providers/aki-provider.js +84 -77
  100. package/dist/providers/aki-provider.js.map +1 -1
  101. package/dist/providers/alibaba-provider.d.ts +39 -0
  102. package/dist/providers/alibaba-provider.d.ts.map +1 -0
  103. package/dist/providers/alibaba-provider.js +86 -0
  104. package/dist/providers/alibaba-provider.js.map +1 -0
  105. package/dist/providers/anthropic-provider.d.ts +52 -3
  106. package/dist/providers/anthropic-provider.d.ts.map +1 -1
  107. package/dist/providers/anthropic-provider.js +1020 -338
  108. package/dist/providers/anthropic-provider.js.map +1 -1
  109. package/dist/providers/atlascloud-provider.d.ts +101 -0
  110. package/dist/providers/atlascloud-provider.d.ts.map +1 -0
  111. package/dist/providers/atlascloud-provider.js +1009 -0
  112. package/dist/providers/atlascloud-provider.js.map +1 -0
  113. package/dist/providers/atlascloud-transport.d.ts +100 -0
  114. package/dist/providers/atlascloud-transport.d.ts.map +1 -0
  115. package/dist/providers/atlascloud-transport.js +364 -0
  116. package/dist/providers/atlascloud-transport.js.map +1 -0
  117. package/dist/providers/atlascloud-webhook-registry.d.ts +35 -0
  118. package/dist/providers/atlascloud-webhook-registry.d.ts.map +1 -0
  119. package/dist/providers/atlascloud-webhook-registry.js +104 -0
  120. package/dist/providers/atlascloud-webhook-registry.js.map +1 -0
  121. package/dist/providers/audio-mime.d.ts +11 -0
  122. package/dist/providers/audio-mime.d.ts.map +1 -0
  123. package/dist/providers/audio-mime.js +46 -0
  124. package/dist/providers/audio-mime.js.map +1 -0
  125. package/dist/providers/base-provider.d.ts +481 -24
  126. package/dist/providers/base-provider.d.ts.map +1 -1
  127. package/dist/providers/base-provider.js +1259 -42
  128. package/dist/providers/base-provider.js.map +1 -1
  129. package/dist/providers/cassette-provider.d.ts +217 -0
  130. package/dist/providers/cassette-provider.d.ts.map +1 -0
  131. package/dist/providers/cassette-provider.js +502 -0
  132. package/dist/providers/cassette-provider.js.map +1 -0
  133. package/dist/providers/cerebras-provider.d.ts +6 -12
  134. package/dist/providers/cerebras-provider.d.ts.map +1 -1
  135. package/dist/providers/cerebras-provider.js +8 -33
  136. package/dist/providers/cerebras-provider.js.map +1 -1
  137. package/dist/providers/claude-agent-provider.d.ts +131 -68
  138. package/dist/providers/claude-agent-provider.d.ts.map +1 -1
  139. package/dist/providers/claude-agent-provider.js +1095 -515
  140. package/dist/providers/claude-agent-provider.js.map +1 -1
  141. package/dist/providers/codex-provider.d.ts +82 -0
  142. package/dist/providers/codex-provider.d.ts.map +1 -0
  143. package/dist/providers/codex-provider.js +492 -0
  144. package/dist/providers/codex-provider.js.map +1 -0
  145. package/dist/providers/cohere-provider.d.ts +5 -2
  146. package/dist/providers/cohere-provider.d.ts.map +1 -1
  147. package/dist/providers/cohere-provider.js +9 -2
  148. package/dist/providers/cohere-provider.js.map +1 -1
  149. package/dist/providers/comfy-cloud-provider.d.ts +30 -0
  150. package/dist/providers/comfy-cloud-provider.d.ts.map +1 -0
  151. package/dist/providers/comfy-cloud-provider.js +42 -0
  152. package/dist/providers/comfy-cloud-provider.js.map +1 -0
  153. package/dist/providers/content-filter.d.ts +46 -0
  154. package/dist/providers/content-filter.d.ts.map +1 -0
  155. package/dist/providers/content-filter.js +92 -0
  156. package/dist/providers/content-filter.js.map +1 -0
  157. package/dist/providers/context-exceeded.d.ts +37 -0
  158. package/dist/providers/context-exceeded.d.ts.map +1 -0
  159. package/dist/providers/context-exceeded.js +114 -0
  160. package/dist/providers/context-exceeded.js.map +1 -0
  161. package/dist/providers/context-window.d.ts +44 -0
  162. package/dist/providers/context-window.d.ts.map +1 -0
  163. package/dist/providers/context-window.js +145 -0
  164. package/dist/providers/context-window.js.map +1 -0
  165. package/dist/providers/contract/index.d.ts +4 -0
  166. package/dist/providers/contract/index.d.ts.map +1 -0
  167. package/dist/providers/contract/index.js +4 -0
  168. package/dist/providers/contract/index.js.map +1 -0
  169. package/dist/providers/contract/probe-manifest.d.ts +75 -0
  170. package/dist/providers/contract/probe-manifest.d.ts.map +1 -0
  171. package/dist/providers/contract/probe-manifest.js +379 -0
  172. package/dist/providers/contract/probe-manifest.js.map +1 -0
  173. package/dist/providers/contract/probe-runner.d.ts +57 -0
  174. package/dist/providers/contract/probe-runner.d.ts.map +1 -0
  175. package/dist/providers/contract/probe-runner.js +180 -0
  176. package/dist/providers/contract/probe-runner.js.map +1 -0
  177. package/dist/providers/contract/redact.d.ts +29 -0
  178. package/dist/providers/contract/redact.d.ts.map +1 -0
  179. package/dist/providers/contract/redact.js +113 -0
  180. package/dist/providers/contract/redact.js.map +1 -0
  181. package/dist/providers/core-tools.d.ts +74 -0
  182. package/dist/providers/core-tools.d.ts.map +1 -0
  183. package/dist/providers/core-tools.js +127 -0
  184. package/dist/providers/core-tools.js.map +1 -0
  185. package/dist/providers/cost-calculator.d.ts +69 -50
  186. package/dist/providers/cost-calculator.d.ts.map +1 -1
  187. package/dist/providers/cost-calculator.js +207 -397
  188. package/dist/providers/cost-calculator.js.map +1 -1
  189. package/dist/providers/credential-check.d.ts +45 -0
  190. package/dist/providers/credential-check.d.ts.map +1 -0
  191. package/dist/providers/credential-check.js +159 -0
  192. package/dist/providers/credential-check.js.map +1 -0
  193. package/dist/providers/custom-model-kinds.d.ts +33 -0
  194. package/dist/providers/custom-model-kinds.d.ts.map +1 -0
  195. package/dist/providers/custom-model-kinds.js +96 -0
  196. package/dist/providers/custom-model-kinds.js.map +1 -0
  197. package/dist/providers/custom-openai-provider.d.ts +81 -0
  198. package/dist/providers/custom-openai-provider.d.ts.map +1 -0
  199. package/dist/providers/custom-openai-provider.js +182 -0
  200. package/dist/providers/custom-openai-provider.js.map +1 -0
  201. package/dist/providers/custom-provider-registry.d.ts +19 -0
  202. package/dist/providers/custom-provider-registry.d.ts.map +1 -0
  203. package/dist/providers/custom-provider-registry.js +52 -0
  204. package/dist/providers/custom-provider-registry.js.map +1 -0
  205. package/dist/providers/deepseek-provider.d.ts +8 -14
  206. package/dist/providers/deepseek-provider.d.ts.map +1 -1
  207. package/dist/providers/deepseek-provider.js +6 -35
  208. package/dist/providers/deepseek-provider.js.map +1 -1
  209. package/dist/providers/defaults.d.ts +12 -0
  210. package/dist/providers/defaults.d.ts.map +1 -0
  211. package/dist/providers/defaults.js +12 -0
  212. package/dist/providers/defaults.js.map +1 -0
  213. package/dist/providers/elevenlabs-provider.d.ts +49 -0
  214. package/dist/providers/elevenlabs-provider.d.ts.map +1 -0
  215. package/dist/providers/elevenlabs-provider.js +142 -0
  216. package/dist/providers/elevenlabs-provider.js.map +1 -0
  217. package/dist/providers/entity-references.d.ts +31 -0
  218. package/dist/providers/entity-references.d.ts.map +1 -0
  219. package/dist/providers/entity-references.js +113 -0
  220. package/dist/providers/entity-references.js.map +1 -0
  221. package/dist/providers/evolink-provider.d.ts +38 -0
  222. package/dist/providers/evolink-provider.d.ts.map +1 -0
  223. package/dist/providers/evolink-provider.js +304 -0
  224. package/dist/providers/evolink-provider.js.map +1 -0
  225. package/dist/providers/fake-provider.d.ts +72 -1
  226. package/dist/providers/fake-provider.d.ts.map +1 -1
  227. package/dist/providers/fake-provider.js +203 -1
  228. package/dist/providers/fake-provider.js.map +1 -1
  229. package/dist/providers/fal-generations.d.ts +106 -0
  230. package/dist/providers/fal-generations.d.ts.map +1 -0
  231. package/dist/providers/fal-generations.js +286 -0
  232. package/dist/providers/fal-generations.js.map +1 -0
  233. package/dist/providers/fal-provider.d.ts +171 -7
  234. package/dist/providers/fal-provider.d.ts.map +1 -1
  235. package/dist/providers/fal-provider.js +1369 -64
  236. package/dist/providers/fal-provider.js.map +1 -1
  237. package/dist/providers/fal-queue.d.ts +47 -0
  238. package/dist/providers/fal-queue.d.ts.map +1 -0
  239. package/dist/providers/fal-queue.js +231 -0
  240. package/dist/providers/fal-queue.js.map +1 -0
  241. package/dist/providers/gemini-provider.d.ts +148 -8
  242. package/dist/providers/gemini-provider.d.ts.map +1 -1
  243. package/dist/providers/gemini-provider.js +1500 -316
  244. package/dist/providers/gemini-provider.js.map +1 -1
  245. package/dist/providers/gmi-provider.d.ts +21 -0
  246. package/dist/providers/gmi-provider.d.ts.map +1 -0
  247. package/dist/providers/gmi-provider.js +34 -0
  248. package/dist/providers/gmi-provider.js.map +1 -0
  249. package/dist/providers/gpt-image-size.d.ts +3 -0
  250. package/dist/providers/gpt-image-size.d.ts.map +1 -0
  251. package/dist/providers/gpt-image-size.js +37 -0
  252. package/dist/providers/gpt-image-size.js.map +1 -0
  253. package/dist/providers/groq-provider.d.ts +9 -12
  254. package/dist/providers/groq-provider.d.ts.map +1 -1
  255. package/dist/providers/groq-provider.js +19 -33
  256. package/dist/providers/groq-provider.js.map +1 -1
  257. package/dist/providers/groq-request.d.ts +38 -0
  258. package/dist/providers/groq-request.d.ts.map +1 -0
  259. package/dist/providers/groq-request.js +138 -0
  260. package/dist/providers/groq-request.js.map +1 -0
  261. package/dist/providers/higgsfield-provider.d.ts +30 -0
  262. package/dist/providers/higgsfield-provider.d.ts.map +1 -0
  263. package/dist/providers/higgsfield-provider.js +131 -0
  264. package/dist/providers/higgsfield-provider.js.map +1 -0
  265. package/dist/providers/higgsfield-transport.d.ts +106 -0
  266. package/dist/providers/higgsfield-transport.d.ts.map +1 -0
  267. package/dist/providers/higgsfield-transport.js +182 -0
  268. package/dist/providers/higgsfield-transport.js.map +1 -0
  269. package/dist/providers/http-transport.d.ts +74 -0
  270. package/dist/providers/http-transport.d.ts.map +1 -0
  271. package/dist/providers/http-transport.js +152 -0
  272. package/dist/providers/http-transport.js.map +1 -0
  273. package/dist/providers/huggingface-provider.d.ts +71 -3
  274. package/dist/providers/huggingface-provider.d.ts.map +1 -1
  275. package/dist/providers/huggingface-provider.js +230 -34
  276. package/dist/providers/huggingface-provider.js.map +1 -1
  277. package/dist/providers/image-mime.d.ts +39 -0
  278. package/dist/providers/image-mime.d.ts.map +1 -0
  279. package/dist/providers/image-mime.js +111 -0
  280. package/dist/providers/image-mime.js.map +1 -0
  281. package/dist/providers/image-ref.d.ts +21 -0
  282. package/dist/providers/image-ref.d.ts.map +1 -0
  283. package/dist/providers/image-ref.js +44 -0
  284. package/dist/providers/image-ref.js.map +1 -0
  285. package/dist/providers/index.d.ts +84 -8
  286. package/dist/providers/index.d.ts.map +1 -1
  287. package/dist/providers/index.js +239 -56
  288. package/dist/providers/index.js.map +1 -1
  289. package/dist/providers/jina-provider.d.ts +10 -2
  290. package/dist/providers/jina-provider.d.ts.map +1 -1
  291. package/dist/providers/jina-provider.js +16 -7
  292. package/dist/providers/jina-provider.js.map +1 -1
  293. package/dist/providers/kie-provider.d.ts +170 -9
  294. package/dist/providers/kie-provider.d.ts.map +1 -1
  295. package/dist/providers/kie-provider.js +1176 -228
  296. package/dist/providers/kie-provider.js.map +1 -1
  297. package/dist/providers/kie-webhook-registry.d.ts +28 -0
  298. package/dist/providers/kie-webhook-registry.d.ts.map +1 -0
  299. package/dist/providers/kie-webhook-registry.js +91 -0
  300. package/dist/providers/kie-webhook-registry.js.map +1 -0
  301. package/dist/providers/llama-provider.d.ts +21 -37
  302. package/dist/providers/llama-provider.d.ts.map +1 -1
  303. package/dist/providers/llama-provider.js +43 -354
  304. package/dist/providers/llama-provider.js.map +1 -1
  305. package/dist/providers/lmstudio-provider.d.ts +4 -7
  306. package/dist/providers/lmstudio-provider.d.ts.map +1 -1
  307. package/dist/providers/lmstudio-provider.js +24 -15
  308. package/dist/providers/lmstudio-provider.js.map +1 -1
  309. package/dist/providers/manifest-models.d.ts +341 -1
  310. package/dist/providers/manifest-models.d.ts.map +1 -1
  311. package/dist/providers/manifest-models.js +1106 -22
  312. package/dist/providers/manifest-models.js.map +1 -1
  313. package/dist/providers/media-mime.d.ts +14 -0
  314. package/dist/providers/media-mime.d.ts.map +1 -0
  315. package/dist/providers/media-mime.js +35 -0
  316. package/dist/providers/media-mime.js.map +1 -0
  317. package/dist/providers/meshy-provider.d.ts +14 -1
  318. package/dist/providers/meshy-provider.d.ts.map +1 -1
  319. package/dist/providers/meshy-provider.js +76 -46
  320. package/dist/providers/meshy-provider.js.map +1 -1
  321. package/dist/providers/meta-provider.d.ts +22 -0
  322. package/dist/providers/meta-provider.d.ts.map +1 -0
  323. package/dist/providers/meta-provider.js +54 -0
  324. package/dist/providers/meta-provider.js.map +1 -0
  325. package/dist/providers/minimax-provider.d.ts +25 -18
  326. package/dist/providers/minimax-provider.d.ts.map +1 -1
  327. package/dist/providers/minimax-provider.js +211 -72
  328. package/dist/providers/minimax-provider.js.map +1 -1
  329. package/dist/providers/mistral-provider.d.ts +6 -12
  330. package/dist/providers/mistral-provider.d.ts.map +1 -1
  331. package/dist/providers/mistral-provider.js +10 -34
  332. package/dist/providers/mistral-provider.js.map +1 -1
  333. package/dist/providers/moonshot-provider.d.ts +11 -15
  334. package/dist/providers/moonshot-provider.d.ts.map +1 -1
  335. package/dist/providers/moonshot-provider.js +33 -23
  336. package/dist/providers/moonshot-provider.js.map +1 -1
  337. package/dist/providers/node-llama-cpp-provider.d.ts +98 -0
  338. package/dist/providers/node-llama-cpp-provider.d.ts.map +1 -0
  339. package/dist/providers/node-llama-cpp-provider.js +479 -0
  340. package/dist/providers/node-llama-cpp-provider.js.map +1 -0
  341. package/dist/providers/nodetool-provider.d.ts +49 -0
  342. package/dist/providers/nodetool-provider.d.ts.map +1 -0
  343. package/dist/providers/nodetool-provider.js +224 -0
  344. package/dist/providers/nodetool-provider.js.map +1 -0
  345. package/dist/providers/oauth/browser-launcher.d.ts +23 -0
  346. package/dist/providers/oauth/browser-launcher.d.ts.map +1 -0
  347. package/dist/providers/oauth/browser-launcher.js +45 -0
  348. package/dist/providers/oauth/browser-launcher.js.map +1 -0
  349. package/dist/providers/oauth/claude-code-credentials.d.ts +74 -0
  350. package/dist/providers/oauth/claude-code-credentials.d.ts.map +1 -0
  351. package/dist/providers/oauth/claude-code-credentials.js +133 -0
  352. package/dist/providers/oauth/claude-code-credentials.js.map +1 -0
  353. package/dist/providers/oauth/claude-code-login.d.ts +124 -0
  354. package/dist/providers/oauth/claude-code-login.d.ts.map +1 -0
  355. package/dist/providers/oauth/claude-code-login.js +230 -0
  356. package/dist/providers/oauth/claude-code-login.js.map +1 -0
  357. package/dist/providers/oauth/claude-code-oauth-client.d.ts +111 -0
  358. package/dist/providers/oauth/claude-code-oauth-client.d.ts.map +1 -0
  359. package/dist/providers/oauth/claude-code-oauth-client.js +207 -0
  360. package/dist/providers/oauth/claude-code-oauth-client.js.map +1 -0
  361. package/dist/providers/oauth/errors.d.ts +54 -0
  362. package/dist/providers/oauth/errors.d.ts.map +1 -0
  363. package/dist/providers/oauth/errors.js +79 -0
  364. package/dist/providers/oauth/errors.js.map +1 -0
  365. package/dist/providers/oauth/index.d.ts +105 -0
  366. package/dist/providers/oauth/index.d.ts.map +1 -0
  367. package/dist/providers/oauth/index.js +167 -0
  368. package/dist/providers/oauth/index.js.map +1 -0
  369. package/dist/providers/oauth/jwt-claims.d.ts +23 -0
  370. package/dist/providers/oauth/jwt-claims.d.ts.map +1 -0
  371. package/dist/providers/oauth/jwt-claims.js +67 -0
  372. package/dist/providers/oauth/jwt-claims.js.map +1 -0
  373. package/dist/providers/oauth/local-callback-server.d.ts +60 -0
  374. package/dist/providers/oauth/local-callback-server.d.ts.map +1 -0
  375. package/dist/providers/oauth/local-callback-server.js +148 -0
  376. package/dist/providers/oauth/local-callback-server.js.map +1 -0
  377. package/dist/providers/oauth/oauth-client.d.ts +55 -0
  378. package/dist/providers/oauth/oauth-client.d.ts.map +1 -0
  379. package/dist/providers/oauth/oauth-client.js +161 -0
  380. package/dist/providers/oauth/oauth-client.js.map +1 -0
  381. package/dist/providers/oauth/openai-oauth-provider.d.ts +129 -0
  382. package/dist/providers/oauth/openai-oauth-provider.d.ts.map +1 -0
  383. package/dist/providers/oauth/openai-oauth-provider.js +265 -0
  384. package/dist/providers/oauth/openai-oauth-provider.js.map +1 -0
  385. package/dist/providers/oauth/pkce-helper.d.ts +43 -0
  386. package/dist/providers/oauth/pkce-helper.d.ts.map +1 -0
  387. package/dist/providers/oauth/pkce-helper.js +57 -0
  388. package/dist/providers/oauth/pkce-helper.js.map +1 -0
  389. package/dist/providers/oauth/redaction.d.ts +22 -0
  390. package/dist/providers/oauth/redaction.d.ts.map +1 -0
  391. package/dist/providers/oauth/redaction.js +59 -0
  392. package/dist/providers/oauth/redaction.js.map +1 -0
  393. package/dist/providers/oauth/secure-credential-store.d.ts +67 -0
  394. package/dist/providers/oauth/secure-credential-store.d.ts.map +1 -0
  395. package/dist/providers/oauth/secure-credential-store.js +73 -0
  396. package/dist/providers/oauth/secure-credential-store.js.map +1 -0
  397. package/dist/providers/oauth/token-store.d.ts +53 -0
  398. package/dist/providers/oauth/token-store.d.ts.map +1 -0
  399. package/dist/providers/oauth/token-store.js +78 -0
  400. package/dist/providers/oauth/token-store.js.map +1 -0
  401. package/dist/providers/oauth/types.d.ts +64 -0
  402. package/dist/providers/oauth/types.d.ts.map +1 -0
  403. package/dist/providers/oauth/types.js +12 -0
  404. package/dist/providers/oauth/types.js.map +1 -0
  405. package/dist/providers/object-tracking.d.ts +47 -0
  406. package/dist/providers/object-tracking.d.ts.map +1 -0
  407. package/dist/providers/object-tracking.js +46 -0
  408. package/dist/providers/object-tracking.js.map +1 -0
  409. package/dist/providers/offline-model-index.d.ts +20 -0
  410. package/dist/providers/offline-model-index.d.ts.map +1 -0
  411. package/dist/providers/offline-model-index.js +112 -0
  412. package/dist/providers/offline-model-index.js.map +1 -0
  413. package/dist/providers/ollama-provider.d.ts +28 -1
  414. package/dist/providers/ollama-provider.d.ts.map +1 -1
  415. package/dist/providers/ollama-provider.js +193 -43
  416. package/dist/providers/ollama-provider.js.map +1 -1
  417. package/dist/providers/openai-compat/client.d.ts +57 -0
  418. package/dist/providers/openai-compat/client.d.ts.map +1 -0
  419. package/dist/providers/openai-compat/client.js +157 -0
  420. package/dist/providers/openai-compat/client.js.map +1 -0
  421. package/dist/providers/openai-compat/decode.d.ts +36 -0
  422. package/dist/providers/openai-compat/decode.d.ts.map +1 -0
  423. package/dist/providers/openai-compat/decode.js +55 -0
  424. package/dist/providers/openai-compat/decode.js.map +1 -0
  425. package/dist/providers/openai-compat/errors.d.ts +54 -0
  426. package/dist/providers/openai-compat/errors.d.ts.map +1 -0
  427. package/dist/providers/openai-compat/errors.js +144 -0
  428. package/dist/providers/openai-compat/errors.js.map +1 -0
  429. package/dist/providers/openai-compat/index.d.ts +8 -0
  430. package/dist/providers/openai-compat/index.d.ts.map +1 -0
  431. package/dist/providers/openai-compat/index.js +5 -0
  432. package/dist/providers/openai-compat/index.js.map +1 -0
  433. package/dist/providers/openai-compat/sse.d.ts +14 -0
  434. package/dist/providers/openai-compat/sse.d.ts.map +1 -0
  435. package/dist/providers/openai-compat/sse.js +69 -0
  436. package/dist/providers/openai-compat/sse.js.map +1 -0
  437. package/dist/providers/openai-compat/types.d.ts +75 -0
  438. package/dist/providers/openai-compat/types.d.ts.map +1 -0
  439. package/dist/providers/openai-compat/types.js +9 -0
  440. package/dist/providers/openai-compat/types.js.map +1 -0
  441. package/dist/providers/openai-compat-provider.d.ts +90 -0
  442. package/dist/providers/openai-compat-provider.d.ts.map +1 -0
  443. package/dist/providers/openai-compat-provider.js +282 -0
  444. package/dist/providers/openai-compat-provider.js.map +1 -0
  445. package/dist/providers/openai-provider.d.ts +89 -26
  446. package/dist/providers/openai-provider.d.ts.map +1 -1
  447. package/dist/providers/openai-provider.js +990 -105
  448. package/dist/providers/openai-provider.js.map +1 -1
  449. package/dist/providers/openrouter-provider.d.ts +6 -12
  450. package/dist/providers/openrouter-provider.d.ts.map +1 -1
  451. package/dist/providers/openrouter-provider.js +24 -53
  452. package/dist/providers/openrouter-provider.js.map +1 -1
  453. package/dist/providers/provider-call-failure.d.ts +39 -0
  454. package/dist/providers/provider-call-failure.d.ts.map +1 -0
  455. package/dist/providers/provider-call-failure.js +132 -0
  456. package/dist/providers/provider-call-failure.js.map +1 -0
  457. package/dist/providers/provider-error.d.ts +59 -0
  458. package/dist/providers/provider-error.d.ts.map +1 -0
  459. package/dist/providers/provider-error.js +165 -0
  460. package/dist/providers/provider-error.js.map +1 -0
  461. package/dist/providers/provider-generations.d.ts +99 -0
  462. package/dist/providers/provider-generations.d.ts.map +1 -0
  463. package/dist/providers/provider-generations.js +71 -0
  464. package/dist/providers/provider-generations.js.map +1 -0
  465. package/dist/providers/provider-queue.d.ts +62 -0
  466. package/dist/providers/provider-queue.d.ts.map +1 -0
  467. package/dist/providers/provider-queue.js +28 -0
  468. package/dist/providers/provider-queue.js.map +1 -0
  469. package/dist/providers/provider-registry.d.ts +33 -14
  470. package/dist/providers/provider-registry.d.ts.map +1 -1
  471. package/dist/providers/provider-registry.js +85 -68
  472. package/dist/providers/provider-registry.js.map +1 -1
  473. package/dist/providers/provider-request-log.d.ts +53 -0
  474. package/dist/providers/provider-request-log.d.ts.map +1 -0
  475. package/dist/providers/provider-request-log.js +157 -0
  476. package/dist/providers/provider-request-log.js.map +1 -0
  477. package/dist/providers/provider-session.d.ts +3 -0
  478. package/dist/providers/provider-session.d.ts.map +1 -0
  479. package/dist/providers/provider-session.js +10 -0
  480. package/dist/providers/provider-session.js.map +1 -0
  481. package/dist/providers/provider-transport.d.ts +11 -0
  482. package/dist/providers/provider-transport.d.ts.map +1 -0
  483. package/dist/providers/provider-transport.js +11 -0
  484. package/dist/providers/provider-transport.js.map +1 -0
  485. package/dist/providers/python-provider.d.ts +20 -12
  486. package/dist/providers/python-provider.d.ts.map +1 -1
  487. package/dist/providers/python-provider.js +185 -25
  488. package/dist/providers/python-provider.js.map +1 -1
  489. package/dist/providers/replicate-provider.d.ts +89 -7
  490. package/dist/providers/replicate-provider.d.ts.map +1 -1
  491. package/dist/providers/replicate-provider.js +801 -65
  492. package/dist/providers/replicate-provider.js.map +1 -1
  493. package/dist/providers/replicate-retry.d.ts +30 -0
  494. package/dist/providers/replicate-retry.d.ts.map +1 -0
  495. package/dist/providers/replicate-retry.js +97 -0
  496. package/dist/providers/replicate-retry.js.map +1 -0
  497. package/dist/providers/responses-api.d.ts +27 -0
  498. package/dist/providers/responses-api.d.ts.map +1 -0
  499. package/dist/providers/responses-api.js +297 -0
  500. package/dist/providers/responses-api.js.map +1 -0
  501. package/dist/providers/reve-provider.d.ts +45 -0
  502. package/dist/providers/reve-provider.d.ts.map +1 -0
  503. package/dist/providers/reve-provider.js +159 -0
  504. package/dist/providers/reve-provider.js.map +1 -0
  505. package/dist/providers/rodin-provider.d.ts +23 -1
  506. package/dist/providers/rodin-provider.d.ts.map +1 -1
  507. package/dist/providers/rodin-provider.js +89 -51
  508. package/dist/providers/rodin-provider.js.map +1 -1
  509. package/dist/providers/safe-url.d.ts +70 -0
  510. package/dist/providers/safe-url.d.ts.map +1 -0
  511. package/dist/providers/safe-url.js +277 -0
  512. package/dist/providers/safe-url.js.map +1 -0
  513. package/dist/providers/scripted-provider.d.ts +9 -0
  514. package/dist/providers/scripted-provider.d.ts.map +1 -1
  515. package/dist/providers/scripted-provider.js +86 -10
  516. package/dist/providers/scripted-provider.js.map +1 -1
  517. package/dist/providers/structured-output.d.ts +42 -0
  518. package/dist/providers/structured-output.d.ts.map +1 -0
  519. package/dist/providers/structured-output.js +83 -0
  520. package/dist/providers/structured-output.js.map +1 -0
  521. package/dist/providers/together-provider.d.ts +19 -16
  522. package/dist/providers/together-provider.d.ts.map +1 -1
  523. package/dist/providers/together-provider.js +103 -225
  524. package/dist/providers/together-provider.js.map +1 -1
  525. package/dist/providers/topaz-provider.d.ts +44 -0
  526. package/dist/providers/topaz-provider.d.ts.map +1 -0
  527. package/dist/providers/topaz-provider.js +239 -0
  528. package/dist/providers/topaz-provider.js.map +1 -0
  529. package/dist/providers/types.d.ts +603 -4
  530. package/dist/providers/types.d.ts.map +1 -1
  531. package/dist/providers/types.js +81 -1
  532. package/dist/providers/types.js.map +1 -1
  533. package/dist/providers/useapi-provider.d.ts +27 -0
  534. package/dist/providers/useapi-provider.d.ts.map +1 -0
  535. package/dist/providers/useapi-provider.js +266 -0
  536. package/dist/providers/useapi-provider.js.map +1 -0
  537. package/dist/providers/video-duration.d.ts +6 -0
  538. package/dist/providers/video-duration.d.ts.map +1 -0
  539. package/dist/providers/video-duration.js +63 -0
  540. package/dist/providers/video-duration.js.map +1 -0
  541. package/dist/providers/video-frame-fallback.d.ts +39 -0
  542. package/dist/providers/video-frame-fallback.d.ts.map +1 -0
  543. package/dist/providers/video-frame-fallback.js +155 -0
  544. package/dist/providers/video-frame-fallback.js.map +1 -0
  545. package/dist/providers/video-frames.d.ts +61 -0
  546. package/dist/providers/video-frames.d.ts.map +1 -0
  547. package/dist/providers/video-frames.js +208 -0
  548. package/dist/providers/video-frames.js.map +1 -0
  549. package/dist/providers/video-mime.d.ts +14 -0
  550. package/dist/providers/video-mime.d.ts.map +1 -0
  551. package/dist/providers/video-mime.js +44 -0
  552. package/dist/providers/video-mime.js.map +1 -0
  553. package/dist/providers/video-to-audio.d.ts +34 -0
  554. package/dist/providers/video-to-audio.d.ts.map +1 -0
  555. package/dist/providers/video-to-audio.js +62 -0
  556. package/dist/providers/video-to-audio.js.map +1 -0
  557. package/dist/providers/vllm-provider.d.ts +22 -8
  558. package/dist/providers/vllm-provider.d.ts.map +1 -1
  559. package/dist/providers/vllm-provider.js +48 -23
  560. package/dist/providers/vllm-provider.js.map +1 -1
  561. package/dist/providers/voyage-provider.d.ts +10 -2
  562. package/dist/providers/voyage-provider.d.ts.map +1 -1
  563. package/dist/providers/voyage-provider.js +16 -7
  564. package/dist/providers/voyage-provider.js.map +1 -1
  565. package/dist/providers/xai-provider.d.ts +54 -13
  566. package/dist/providers/xai-provider.d.ts.map +1 -1
  567. package/dist/providers/xai-provider.js +299 -22
  568. package/dist/providers/xai-provider.js.map +1 -1
  569. package/dist/python-bridge-base.d.ts +358 -0
  570. package/dist/python-bridge-base.d.ts.map +1 -0
  571. package/dist/python-bridge-base.js +1557 -0
  572. package/dist/python-bridge-base.js.map +1 -0
  573. package/dist/python-bridge-codec.d.ts +28 -0
  574. package/dist/python-bridge-codec.d.ts.map +1 -0
  575. package/dist/python-bridge-codec.js +39 -0
  576. package/dist/python-bridge-codec.js.map +1 -0
  577. package/dist/python-bridge-factory.d.ts +52 -0
  578. package/dist/python-bridge-factory.d.ts.map +1 -0
  579. package/dist/python-bridge-factory.js +69 -0
  580. package/dist/python-bridge-factory.js.map +1 -0
  581. package/dist/python-bridge-framing.d.ts +68 -0
  582. package/dist/python-bridge-framing.d.ts.map +1 -0
  583. package/dist/python-bridge-framing.js +91 -0
  584. package/dist/python-bridge-framing.js.map +1 -0
  585. package/dist/python-bridge-types.d.ts +545 -2
  586. package/dist/python-bridge-types.d.ts.map +1 -1
  587. package/dist/python-bridge-types.js +49 -1
  588. package/dist/python-bridge-types.js.map +1 -1
  589. package/dist/python-graph-resolver.d.ts +57 -0
  590. package/dist/python-graph-resolver.d.ts.map +1 -0
  591. package/dist/python-graph-resolver.js +79 -0
  592. package/dist/python-graph-resolver.js.map +1 -0
  593. package/dist/python-node-executor.d.ts +36 -4
  594. package/dist/python-node-executor.d.ts.map +1 -1
  595. package/dist/python-node-executor.js +177 -86
  596. package/dist/python-node-executor.js.map +1 -1
  597. package/dist/python-stdio-bridge.d.ts +18 -37
  598. package/dist/python-stdio-bridge.d.ts.map +1 -1
  599. package/dist/python-stdio-bridge.js +148 -475
  600. package/dist/python-stdio-bridge.js.map +1 -1
  601. package/dist/python-websocket-bridge.d.ts +140 -0
  602. package/dist/python-websocket-bridge.d.ts.map +1 -0
  603. package/dist/python-websocket-bridge.js +561 -0
  604. package/dist/python-websocket-bridge.js.map +1 -0
  605. package/dist/python-worker-stderr.d.ts +4 -0
  606. package/dist/python-worker-stderr.d.ts.map +1 -0
  607. package/dist/python-worker-stderr.js +24 -0
  608. package/dist/python-worker-stderr.js.map +1 -0
  609. package/dist/recommended-models.d.ts +2 -2
  610. package/dist/recommended-models.d.ts.map +1 -1
  611. package/dist/recommended-models.js +24 -4
  612. package/dist/recommended-models.js.map +1 -1
  613. package/dist/recoverable-node-error.d.ts +31 -0
  614. package/dist/recoverable-node-error.d.ts.map +1 -0
  615. package/dist/recoverable-node-error.js +37 -0
  616. package/dist/recoverable-node-error.js.map +1 -0
  617. package/dist/redact-params.d.ts +13 -0
  618. package/dist/redact-params.d.ts.map +1 -0
  619. package/dist/redact-params.js +57 -0
  620. package/dist/redact-params.js.map +1 -0
  621. package/dist/sandbox-module-catalog.d.ts +43 -0
  622. package/dist/sandbox-module-catalog.d.ts.map +1 -0
  623. package/dist/sandbox-module-catalog.js +25 -0
  624. package/dist/sandbox-module-catalog.js.map +1 -0
  625. package/dist/storage-workspace.d.ts +59 -0
  626. package/dist/storage-workspace.d.ts.map +1 -0
  627. package/dist/storage-workspace.js +426 -0
  628. package/dist/storage-workspace.js.map +1 -0
  629. package/dist/swappable-python-bridge.d.ts +83 -0
  630. package/dist/swappable-python-bridge.d.ts.map +1 -0
  631. package/dist/swappable-python-bridge.js +246 -0
  632. package/dist/swappable-python-bridge.js.map +1 -0
  633. package/dist/telemetry.d.ts +7 -0
  634. package/dist/telemetry.d.ts.map +1 -1
  635. package/dist/telemetry.js +23 -0
  636. package/dist/telemetry.js.map +1 -1
  637. package/dist/testing.d.ts +81 -0
  638. package/dist/testing.d.ts.map +1 -0
  639. package/dist/testing.js +130 -0
  640. package/dist/testing.js.map +1 -0
  641. package/dist/token-counter.d.ts +30 -0
  642. package/dist/token-counter.d.ts.map +1 -0
  643. package/dist/token-counter.js +156 -0
  644. package/dist/token-counter.js.map +1 -0
  645. package/dist/trace-exporters.d.ts +10 -0
  646. package/dist/trace-exporters.d.ts.map +1 -1
  647. package/dist/trace-exporters.js +59 -20
  648. package/dist/trace-exporters.js.map +1 -1
  649. package/dist/tracing-helpers.d.ts +45 -4
  650. package/dist/tracing-helpers.d.ts.map +1 -1
  651. package/dist/tracing-helpers.js +62 -19
  652. package/dist/tracing-helpers.js.map +1 -1
  653. package/dist/turn-budget.d.ts +278 -0
  654. package/dist/turn-budget.d.ts.map +1 -0
  655. package/dist/turn-budget.js +365 -0
  656. package/dist/turn-budget.js.map +1 -0
  657. package/dist/variable-channel.d.ts +48 -0
  658. package/dist/variable-channel.d.ts.map +1 -0
  659. package/dist/variable-channel.js +110 -0
  660. package/dist/variable-channel.js.map +1 -0
  661. package/dist/workspace.d.ts +119 -0
  662. package/dist/workspace.d.ts.map +1 -0
  663. package/dist/workspace.js +112 -0
  664. package/dist/workspace.js.map +1 -0
  665. package/package.json +107 -19
@@ -7,22 +7,140 @@
7
7
  * endpoints and reuse the existing OpenAI/Anthropic provider implementations
8
8
  * where their APIs are wire-compatible.
9
9
  */
10
+ import { recordGenerationReceipt } from "../generation-receipt.js";
10
11
  import Anthropic from "@anthropic-ai/sdk";
11
12
  import OpenAI from "openai";
12
13
  import { BaseProvider } from "./base-provider.js";
14
+ import { safeFetch } from "./safe-url.js";
15
+ import { isString } from "@nodetool-ai/protocol";
13
16
  import { createLogger } from "@nodetool-ai/config";
14
- import { loadVideoModels, loadImageModels } from "./manifest-models.js";
17
+ import { loadVideoModels, loadImageModels, loadMusicModels, loadTTSModels, getModelImageInputs, getModelMediaInputs, getModelReferenceInputs, getModelInputFields, getManifestNodeMeta, selectPrimaryImageInput, selectMaskImageInput } from "./manifest-models.js";
18
+ import { validateReferenceInputs } from "./manifest-models.js";
19
+ import { registerWebhookWait } from "./kie-webhook-registry.js";
20
+ import { sniffAudioMime } from "./audio-mime.js";
21
+ import { detectImageMime, extForImageMime } from "./image-mime.js";
22
+ import { sniffMediaMime } from "./media-mime.js";
15
23
  import { OpenAIProvider } from "./openai-provider.js";
16
24
  import { AnthropicProvider } from "./anthropic-provider.js";
25
+ import { extractResponsesText, extractResponsesToolCalls, messagesToResponsesInput, responseToolChoice, responseTools, responseUsage, streamResponsesEvents } from "./responses-api.js";
17
26
  const log = createLogger("nodetool.runtime.providers.kie");
27
+ function combineSignals(signal, timeout) {
28
+ if (!signal)
29
+ return timeout;
30
+ if (!timeout)
31
+ return signal;
32
+ return AbortSignal.any([signal, timeout]);
33
+ }
18
34
  const KIE_API_BASE = "https://api.kie.ai";
35
+ const KIE_UPLOAD_URL = "https://kieai.redpandaai.co/api/file-stream-upload";
36
+ // The Suno API always requires a callBackUrl. When KIE_WEBHOOK_URL is set we
37
+ // use it; otherwise a placeholder satisfies the requirement (we poll instead).
38
+ const KIE_SUNO_CALLBACK = "https://nodetool.ai/kie-callback";
39
+ /**
40
+ * When set, KIE tasks are submitted with a real callBackUrl and the provider
41
+ * waits for the webhook instead of polling. Set to the public base URL of this
42
+ * server (e.g. `https://myserver.example.com`).
43
+ */
44
+ function getWebhookBaseUrl() {
45
+ const url = process.env.KIE_WEBHOOK_URL;
46
+ return url && url.trim() ? url.replace(/\/+$/, "") : undefined;
47
+ }
48
+ // Mirrors the "Chat Models" section of docs.kie.ai/llms.txt. Each entry's
49
+ // basePath is the model's documented endpoint prefix: the Claude models share
50
+ // /claude/v1/messages, the GPT, Grok, Kimi and DeepSeek models post to a
51
+ // /responses endpoint, and every Gemini (plus GPT 5.2) endpoint is OpenAI
52
+ // chat-completions with the model pinned by the path.
19
53
  const KIE_CHAT_MODELS = [
54
+ {
55
+ id: "gpt-6-sol",
56
+ name: "GPT 6 Sol",
57
+ api: "responses",
58
+ basePath: "/codex/v1"
59
+ },
60
+ {
61
+ id: "gpt-6-astra",
62
+ name: "GPT 6 Astra",
63
+ api: "responses",
64
+ basePath: "/codex/v1"
65
+ },
66
+ {
67
+ id: "gpt-6-luna",
68
+ name: "GPT 6 Luna",
69
+ api: "responses",
70
+ basePath: "/codex/v1"
71
+ },
72
+ {
73
+ id: "gpt-5-6-sol",
74
+ name: "GPT 5.6 Sol",
75
+ api: "responses",
76
+ basePath: "/codex/v1"
77
+ },
78
+ {
79
+ id: "gpt-5-6-terra",
80
+ name: "GPT 5.6 Terra",
81
+ api: "responses",
82
+ basePath: "/codex/v1"
83
+ },
84
+ {
85
+ id: "gpt-5-6-luna",
86
+ name: "GPT 5.6 Luna",
87
+ api: "responses",
88
+ basePath: "/codex/v1"
89
+ },
20
90
  {
21
91
  id: "gpt-5-5",
22
92
  name: "GPT 5.5",
23
93
  api: "responses",
24
94
  basePath: "/codex/v1"
25
95
  },
96
+ {
97
+ id: "gpt-5-4",
98
+ name: "GPT 5.4",
99
+ api: "responses",
100
+ basePath: "/codex/v1"
101
+ },
102
+ {
103
+ id: "gpt-5.1-codex",
104
+ name: "GPT Codex",
105
+ api: "responses",
106
+ basePath: "/api/v1"
107
+ },
108
+ {
109
+ id: "gpt-5-2",
110
+ name: "GPT 5.2",
111
+ api: "openai",
112
+ basePath: "/gpt-5-2/v1"
113
+ },
114
+ {
115
+ id: "claude-opus-5",
116
+ name: "Claude Opus 5",
117
+ api: "anthropic",
118
+ basePath: "/claude"
119
+ },
120
+ {
121
+ id: "claude-sonnet-5",
122
+ name: "Claude Sonnet 5",
123
+ api: "anthropic",
124
+ basePath: "/claude"
125
+ },
126
+ {
127
+ id: "claude-fable-5",
128
+ name: "Claude Fable 5",
129
+ api: "anthropic",
130
+ basePath: "/claude"
131
+ },
132
+ {
133
+ id: "claude-opus-4-8",
134
+ name: "Claude Opus 4.8",
135
+ api: "anthropic",
136
+ basePath: "/claude"
137
+ },
138
+ {
139
+ id: "claude-opus-4-7",
140
+ name: "Claude Opus 4.7",
141
+ api: "anthropic",
142
+ basePath: "/claude"
143
+ },
26
144
  {
27
145
  id: "claude-opus-4-6",
28
146
  name: "Claude Opus 4.6",
@@ -35,23 +153,113 @@ const KIE_CHAT_MODELS = [
35
153
  api: "anthropic",
36
154
  basePath: "/claude"
37
155
  },
156
+ {
157
+ id: "claude-opus-4-5",
158
+ name: "Claude Opus 4.5",
159
+ api: "anthropic",
160
+ basePath: "/claude"
161
+ },
162
+ {
163
+ id: "claude-sonnet-4-5",
164
+ name: "Claude Sonnet 4.5",
165
+ api: "anthropic",
166
+ basePath: "/claude"
167
+ },
38
168
  {
39
169
  id: "claude-haiku-4-5",
40
170
  name: "Claude Haiku 4.5",
41
171
  api: "anthropic",
42
172
  basePath: "/claude"
43
173
  },
174
+ {
175
+ id: "grok-4-7",
176
+ name: "Grok 4.7",
177
+ api: "responses",
178
+ basePath: "/grok/v1"
179
+ },
180
+ {
181
+ id: "grok-4-6",
182
+ name: "Grok 4.6",
183
+ api: "responses",
184
+ basePath: "/grok/v1"
185
+ },
186
+ {
187
+ id: "grok-4-5",
188
+ name: "Grok 4.5",
189
+ api: "responses",
190
+ basePath: "/grok/v1"
191
+ },
192
+ {
193
+ id: "grok-4-3",
194
+ name: "Grok 4.3",
195
+ api: "responses",
196
+ basePath: "/grok/v1"
197
+ },
198
+ {
199
+ id: "kimi-k3",
200
+ name: "Kimi K3",
201
+ api: "responses",
202
+ basePath: "/openai/v1"
203
+ },
204
+ {
205
+ id: "deepseek-v4-1-flash",
206
+ name: "DeepSeek V4.1 Flash",
207
+ api: "responses",
208
+ basePath: "/openai/v1"
209
+ },
44
210
  {
45
211
  id: "gemini-3.1-pro",
46
212
  name: "Gemini 3.1 Pro",
47
213
  api: "openai",
48
214
  basePath: "/gemini-3.1-pro/v1"
49
215
  },
216
+ {
217
+ id: "gemini-3-pro",
218
+ name: "Gemini 3 Pro",
219
+ api: "openai",
220
+ basePath: "/gemini-3-pro/v1"
221
+ },
222
+ {
223
+ id: "gemini-3-8-flash",
224
+ name: "Gemini 3.8 Flash",
225
+ api: "openai",
226
+ basePath: "/gemini-3-8-flash-openai/v1"
227
+ },
228
+ {
229
+ id: "gemini-3-7-flash",
230
+ name: "Gemini 3.7 Flash",
231
+ api: "openai",
232
+ basePath: "/gemini-3-7-flash-openai/v1"
233
+ },
234
+ {
235
+ id: "gemini-3-6-flash",
236
+ name: "Gemini 3.6 Flash",
237
+ api: "openai",
238
+ basePath: "/gemini-3-6-flash-openai/v1"
239
+ },
240
+ {
241
+ id: "gemini-3-5-flash",
242
+ name: "Gemini 3.5 Flash",
243
+ api: "openai",
244
+ basePath: "/gemini-3-5-flash-openai/v1"
245
+ },
50
246
  {
51
247
  id: "gemini-3-flash",
52
248
  name: "Gemini 3 Flash",
53
249
  api: "openai",
54
250
  basePath: "/gemini-3-flash/v1"
251
+ },
252
+ {
253
+ id: "gemini-2.5-pro",
254
+ name: "Gemini 2.5 Pro",
255
+ api: "openai",
256
+ basePath: "/gemini-2.5-pro/v1"
257
+ },
258
+ {
259
+ id: "gemini-2.5-flash",
260
+ name: "Gemini 2.5 Flash",
261
+ api: "openai",
262
+ basePath: "/gemini-2.5-flash/v1"
55
263
  }
56
264
  ];
57
265
  function chatModel(model) {
@@ -63,73 +271,377 @@ function headers(apiKey) {
63
271
  "Content-Type": "application/json"
64
272
  };
65
273
  }
66
- async function submitTask(apiKey, model, input) {
274
+ /** Resolve the abort reason as an Error (mirrors the gemini provider). */
275
+ function abortError(signal) {
276
+ return signal?.reason instanceof Error ? signal.reason : new Error("Aborted");
277
+ }
278
+ /** Sleep that rejects promptly when `signal` aborts instead of running full. */
279
+ function sleep(ms, signal) {
280
+ return new Promise((resolve, reject) => {
281
+ if (signal?.aborted) {
282
+ reject(abortError(signal));
283
+ return;
284
+ }
285
+ const onAbort = () => {
286
+ clearTimeout(timer);
287
+ reject(abortError(signal));
288
+ };
289
+ const timer = setTimeout(() => {
290
+ signal?.removeEventListener("abort", onAbort);
291
+ resolve();
292
+ }, ms);
293
+ signal?.addEventListener("abort", onAbort, { once: true });
294
+ });
295
+ }
296
+ /**
297
+ * KIE reports API errors inside HTTP-200 bodies as `{code, msg}`. Map the known
298
+ * error codes to a message and throw; code 200 (and any unmapped code) passes
299
+ * through. Mirrors kie-base's `checkStatus` — without this a failed job returns
300
+ * a 200 that the poll loop reads as "not done yet" and runs to full timeout.
301
+ */
302
+ /** The `code` values KIE returns inside an HTTP-200 error envelope. */
303
+ const KIE_ERROR_CODES = {
304
+ 401: "Unauthorized",
305
+ 402: "Insufficient Credits",
306
+ 404: "Not Found",
307
+ 422: "Validation Error",
308
+ 429: "Rate Limited",
309
+ 455: "Service Unavailable",
310
+ 500: "Server Error",
311
+ 501: "Generation Failed",
312
+ 505: "Feature Disabled"
313
+ };
314
+ /**
315
+ * Classify a KIE response envelope. Returns the error KIE announced inside an
316
+ * otherwise-successful HTTP response, or null when the envelope is not an
317
+ * error. The live contract probe reads this to check that the error envelope
318
+ * still looks the way the provider expects.
319
+ */
320
+ export function kieEnvelopeError(data) {
321
+ const code = Number(data.code);
322
+ const label = KIE_ERROR_CODES[code];
323
+ if (!label)
324
+ return null;
325
+ const message = isString(data.msg) ? data.msg : "";
326
+ return { code, label, message };
327
+ }
328
+ function checkStatus(data) {
329
+ const error = kieEnvelopeError(data);
330
+ if (error)
331
+ throw new Error(`${error.label}: ${JSON.stringify(data)}`);
332
+ }
333
+ /** Decode a `createTask` body into the task id it announces. */
334
+ export function decodeKieTaskSubmission(data) {
335
+ checkStatus(data);
336
+ const taskId = data.data?.taskId;
337
+ if (!isString(taskId) || taskId.length === 0) {
338
+ throw new Error(`No taskId in Kie response: ${JSON.stringify(data)}`);
339
+ }
340
+ return taskId;
341
+ }
342
+ /** Decode a `recordInfo` body into the job state the poller branches on. */
343
+ export function decodeKieRecordInfo(data) {
344
+ checkStatus(data);
345
+ const payload = data.data ?? {};
346
+ const state = payload.state;
347
+ if (!isString(state) || state.length === 0) {
348
+ throw new Error(`No state in Kie recordInfo: ${JSON.stringify(data)}`);
349
+ }
350
+ return { state, failMsg: isString(payload.failMsg) ? payload.failMsg : "" };
351
+ }
352
+ /**
353
+ * Decode the result URLs out of a finished `recordInfo` body. KIE nests them
354
+ * as a JSON *string* under `data.resultJson`, so this is two decodes.
355
+ */
356
+ export function decodeKieResultUrls(data) {
357
+ checkStatus(data);
358
+ const resultJsonStr = data.data?.resultJson;
359
+ if (!isString(resultJsonStr)) {
360
+ throw new Error("No resultJson in Kie response");
361
+ }
362
+ let resultData;
363
+ try {
364
+ resultData = JSON.parse(resultJsonStr);
365
+ }
366
+ catch {
367
+ throw new Error("Kie resultJson is not JSON");
368
+ }
369
+ const resultUrls = resultData.resultUrls;
370
+ if (!Array.isArray(resultUrls) || resultUrls.length === 0) {
371
+ throw new Error("No resultUrls in Kie resultJson");
372
+ }
373
+ return resultUrls.filter(isString);
374
+ }
375
+ /**
376
+ * Parse a KIE JSON response defensively: a gateway 502 returns an HTML page, not
377
+ * JSON, so read the text and surface a "Kie <label> failed: <status> …" error
378
+ * with a body snippet rather than a bare SyntaxError. Applies the HTTP-200 error
379
+ * envelope check ({@link checkStatus}) on every parsed body.
380
+ */
381
+ async function parseKieJson(res, label) {
382
+ const text = await res.text();
383
+ let data;
384
+ try {
385
+ data = JSON.parse(text);
386
+ }
387
+ catch {
388
+ throw new Error(`Kie ${label} failed: ${res.status} ${text.slice(0, 200)}`);
389
+ }
390
+ if (data.code !== undefined)
391
+ checkStatus(data);
392
+ return data;
393
+ }
394
+ /** Parse an "a:b" aspect ratio string into its numeric value, or undefined. */
395
+ function aspectToNumber(ratio) {
396
+ const m = /^(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)$/.exec(ratio.trim());
397
+ if (!m)
398
+ return undefined;
399
+ const w = Number(m[1]);
400
+ const h = Number(m[2]);
401
+ if (!Number.isFinite(w) || !Number.isFinite(h) || h === 0)
402
+ return undefined;
403
+ return w / h;
404
+ }
405
+ /** Choose the declared enum aspect ratio closest to a target numeric ratio. */
406
+ function nearestAspect(enumValues, target) {
407
+ let best;
408
+ for (const value of enumValues) {
409
+ const n = aspectToNumber(value);
410
+ if (n === undefined)
411
+ continue;
412
+ const diff = Math.abs(n - target);
413
+ if (!best || diff < best.diff)
414
+ best = { value, diff };
415
+ }
416
+ return best?.value ?? enumValues[0];
417
+ }
418
+ /**
419
+ * Coerce a requested duration (seconds) to the model's declared `duration`
420
+ * field type: snap to the nearest allowed enum value (sent as the enum's own
421
+ * value type — the manifest enums are strings), round to an int, or pass a
422
+ * float through. Returns undefined when the model declares no duration field.
423
+ */
424
+ function coerceDuration(field, seconds) {
425
+ if (!field)
426
+ return undefined;
427
+ if (field.type === "enum" && field.enumValues && field.enumValues.length > 0) {
428
+ let best;
429
+ for (const value of field.enumValues) {
430
+ const n = Number(value);
431
+ if (!Number.isFinite(n))
432
+ continue;
433
+ const diff = Math.abs(n - seconds);
434
+ if (!best || diff < best.diff)
435
+ best = { value, diff };
436
+ }
437
+ return best?.value ?? field.enumValues[0];
438
+ }
439
+ if (field.type === "float")
440
+ return seconds;
441
+ // int and any other numeric type: send a rounded integer.
442
+ return Math.round(seconds);
443
+ }
444
+ /**
445
+ * A field's default usable as an input value: the declared default when it's
446
+ * meaningful (not an empty string), otherwise the first enum option. KIE's
447
+ * required `model` field ships with an empty default, so the first declared
448
+ * version is used.
449
+ */
450
+ /**
451
+ * HOLDOUT (anti-slop/no-unknown-returns): the value comes straight from the
452
+ * manifest's `default`, which `ModelInputField` declares `unknown`; naming the
453
+ * result means typing the manifest decoder in `manifest-models.ts`.
454
+ */
455
+ function defaultForField(field) {
456
+ const d = field.default;
457
+ if (d !== undefined && !(isString(d) && d === ""))
458
+ return d;
459
+ if (field.enumValues && field.enumValues.length > 0)
460
+ return field.enumValues[0];
461
+ return undefined;
462
+ }
463
+ /**
464
+ * Upload raw image bytes to KIE's file store and return the hosted URL. Sniffs
465
+ * the container from the magic bytes so the upload carries the right mime and
466
+ * extension (KIE rejects a JPEG announced as PNG).
467
+ */
468
+ async function uploadImageBytes(apiKey, bytes, signal) {
469
+ const mime = detectImageMime(bytes);
470
+ const ext = extForImageMime(mime) ?? "png";
471
+ const fileName = `upload-${Date.now()}.${ext}`;
472
+ const form = new FormData();
473
+ form.append("file", new Blob([new Uint8Array(bytes)], { type: mime }), fileName);
474
+ form.append("uploadPath", "images/user-uploads");
475
+ form.append("fileName", fileName);
476
+ const res = await fetch(KIE_UPLOAD_URL, {
477
+ method: "POST",
478
+ headers: { Authorization: `Bearer ${apiKey}` },
479
+ body: form,
480
+ signal
481
+ });
482
+ const data = await parseKieJson(res, "upload");
483
+ if (!res.ok || !data.success) {
484
+ throw new Error(`Kie upload failed: ${res.status} ${JSON.stringify(data)}`);
485
+ }
486
+ const downloadUrl = data.data
487
+ ?.downloadUrl;
488
+ if (!downloadUrl) {
489
+ throw new Error(`No downloadUrl in Kie upload response`);
490
+ }
491
+ return downloadUrl;
492
+ }
493
+ async function uploadMediaBytes(apiKey, bytes, signal, kind) {
494
+ const mime = sniffMediaMime(bytes, kind === "image" ? "image/png" : "video/mp4");
495
+ const ext = kind === "image" ? (extForImageMime(mime) ?? "png") : "mp4";
496
+ const fileName = `upload-${Date.now()}.${ext}`;
497
+ const form = new FormData();
498
+ form.append("file", new Blob([new Uint8Array(bytes)], { type: mime }), fileName);
499
+ form.append("uploadPath", kind === "image" ? "images/user-uploads" : "videos/user-uploads");
500
+ form.append("fileName", fileName);
501
+ const res = await fetch(KIE_UPLOAD_URL, { method: "POST", headers: { Authorization: `Bearer ${apiKey}` }, body: form, signal });
502
+ const data = await parseKieJson(res, "upload");
503
+ if (!res.ok || !data.success)
504
+ throw new Error(`Kie upload failed: ${res.status} ${JSON.stringify(data)}`);
505
+ const downloadUrl = data.data?.downloadUrl;
506
+ if (typeof downloadUrl !== "string" || !downloadUrl)
507
+ throw new Error("No downloadUrl in Kie upload response");
508
+ return downloadUrl;
509
+ }
510
+ async function submitTask(apiKey, model, input, signal) {
67
511
  const res = await fetch(`${KIE_API_BASE}/api/v1/jobs/createTask`, {
68
512
  method: "POST",
69
513
  headers: headers(apiKey),
70
- body: JSON.stringify({ model, input })
514
+ body: JSON.stringify({ model, input }),
515
+ signal
71
516
  });
72
- const data = (await res.json());
517
+ const data = await parseKieJson(res, "submit");
73
518
  if (!res.ok) {
74
519
  throw new Error(`Kie submit failed: ${res.status} ${JSON.stringify(data)}`);
75
520
  }
76
- const taskId = data.data?.taskId;
77
- if (!taskId) {
78
- throw new Error(`No taskId in Kie response: ${JSON.stringify(data)}`);
79
- }
521
+ const taskId = decodeKieTaskSubmission(data);
522
+ // KIE bills at submit, so the task id is the receipt even when the poll
523
+ // later fails or times out: the reconciler looks the charge up by it.
524
+ recordGenerationReceipt({ provider_request_id: taskId });
80
525
  return taskId;
81
526
  }
82
- async function pollUntilDone(apiKey, taskId, pollInterval = 4000, maxAttempts = 300) {
527
+ /**
528
+ * Submit a task with an optional webhook callback URL. When webhook mode is
529
+ * enabled, the callBackUrl is injected into the input so KIE calls us back
530
+ * when the task completes. The generic `/api/kie/webhook` endpoint extracts
531
+ * the taskId from the callback body.
532
+ */
533
+ async function submitTaskWithWebhook(apiKey, model, input, signal) {
534
+ const webhookBase = getWebhookBaseUrl();
535
+ const finalInput = webhookBase
536
+ ? { ...input, callBackUrl: `${webhookBase}/api/kie/webhook` }
537
+ : input;
538
+ return submitTask(apiKey, model, finalInput, signal);
539
+ }
540
+ async function pollUntilDone(apiKey, taskId, pollInterval = 4000, maxAttempts = 300, signal) {
83
541
  const url = `${KIE_API_BASE}/api/v1/jobs/recordInfo?taskId=${taskId}`;
542
+ // KIE reports failures two ways that both look like "still running" to a naive
543
+ // loop: a persistent non-OK poll (429/5xx/revoked key) after createTask
544
+ // succeeded, and an HTTP-200 body carrying an error `code` (checkStatus). Both
545
+ // used to run the full maxAttempts (~20 min) before blaming a slow job. Fail
546
+ // fast: on a bounded run of consecutive HTTP errors, and immediately on a code
547
+ // envelope (via parseKieJson).
548
+ const MAX_CONSECUTIVE_ERRORS = 5;
549
+ let consecutiveErrors = 0;
84
550
  for (let i = 0; i < maxAttempts; i++) {
85
- const res = await fetch(url, { headers: headers(apiKey) });
86
- const data = (await res.json());
87
- const state = data.data?.state;
551
+ const res = await fetch(url, { headers: headers(apiKey), signal });
552
+ if (!res.ok) {
553
+ consecutiveErrors += 1;
554
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
555
+ const body = await res.text().catch(() => "");
556
+ throw new Error(`Kie recordInfo failed ${consecutiveErrors}× (HTTP ${res.status}) for taskId ${taskId}: ${body.slice(0, 200)}`);
557
+ }
558
+ await sleep(pollInterval, signal);
559
+ continue;
560
+ }
561
+ consecutiveErrors = 0;
562
+ const { state, failMsg } = decodeKieRecordInfo(await parseKieJson(res, "recordInfo"));
88
563
  if (state === "success")
89
564
  return;
90
565
  if (state === "failed" || state === "fail") {
91
- const msg = data.data?.failMsg || "Unknown error";
92
- throw new Error(`Kie task failed: ${msg}`);
566
+ throw new Error(`Kie task failed: ${failMsg || "Unknown error"} (taskId: ${taskId})`);
93
567
  }
94
- await new Promise((r) => setTimeout(r, pollInterval));
568
+ await sleep(pollInterval, signal);
95
569
  }
96
- throw new Error(`Kie task timed out after ${maxAttempts * pollInterval}ms`);
570
+ const timeoutSeconds = (maxAttempts * pollInterval) / 1000;
571
+ throw new Error(`Kie task timed out after ${timeoutSeconds}s (taskId: ${taskId}). ` +
572
+ "The job may still complete on KIE — check recordInfo or the KIE dashboard.");
97
573
  }
98
- async function downloadResultBytes(apiKey, taskId) {
574
+ /**
575
+ * Wait for a KIE task to complete: use webhook if KIE_WEBHOOK_URL is set,
576
+ * otherwise fall back to polling.
577
+ */
578
+ async function waitForCompletion(apiKey, taskId, pollInterval, maxAttempts, signal) {
579
+ if (getWebhookBaseUrl()) {
580
+ const timeoutMs = pollInterval * maxAttempts;
581
+ log.info("Waiting for KIE webhook callback", { taskId, timeoutMs });
582
+ await registerWebhookWait(taskId, timeoutMs, signal);
583
+ return;
584
+ }
585
+ await pollUntilDone(apiKey, taskId, pollInterval, maxAttempts, signal);
586
+ }
587
+ async function downloadResultBytes(apiKey, taskId, signal) {
99
588
  const url = `${KIE_API_BASE}/api/v1/jobs/recordInfo?taskId=${taskId}`;
100
- const res = await fetch(url, { headers: headers(apiKey) });
589
+ const res = await fetch(url, { headers: headers(apiKey), signal });
101
590
  if (!res.ok)
102
591
  throw new Error(`Failed to get Kie result: ${res.status}`);
103
- const data = (await res.json());
104
- const resultJsonStr = data.data
105
- ?.resultJson;
106
- if (!resultJsonStr)
107
- throw new Error("No resultJson in Kie response");
108
- const resultData = JSON.parse(resultJsonStr);
109
- const resultUrls = resultData.resultUrls;
110
- if (!resultUrls?.length)
111
- throw new Error("No resultUrls in Kie resultJson");
112
- const dlRes = await fetch(resultUrls[0]);
592
+ const resultUrls = decodeKieResultUrls(await parseKieJson(res, "recordInfo"));
593
+ const dlRes = await safeFetch(resultUrls[0], { signal });
113
594
  if (!dlRes.ok) {
114
595
  throw new Error(`Failed to download from ${resultUrls[0]}`);
115
596
  }
116
597
  return new Uint8Array(await dlRes.arrayBuffer());
117
598
  }
118
- function isRecord(value) {
119
- return value !== null && typeof value === "object" && !Array.isArray(value);
120
- }
121
- function stringifyContent(value) {
122
- if (typeof value === "string")
123
- return value;
124
- if (value == null)
125
- return "";
126
- return JSON.stringify(value);
599
+ /**
600
+ * Submit a Suno music task to `<sunoEndpoint>` and return the task id. The Suno
601
+ * API is separate from the generic jobs API and always requires `callBackUrl`.
602
+ */
603
+ async function submitSuno(apiKey, endpoint, input, signal) {
604
+ const callBackUrl = getWebhookBaseUrl()
605
+ ? `${getWebhookBaseUrl()}/api/kie/webhook`
606
+ : KIE_SUNO_CALLBACK;
607
+ const body = { ...input, callBackUrl };
608
+ const res = await fetch(`${KIE_API_BASE}${endpoint}`, {
609
+ method: "POST",
610
+ headers: headers(apiKey),
611
+ body: JSON.stringify(body),
612
+ signal
613
+ });
614
+ const data = await parseKieJson(res, "submit");
615
+ if (!res.ok) {
616
+ throw new Error(`Kie submit failed: ${res.status} ${JSON.stringify(data)}`);
617
+ }
618
+ const taskId = decodeKieTaskSubmission(data);
619
+ recordGenerationReceipt({ provider_request_id: taskId });
620
+ return taskId;
127
621
  }
128
- function dataUri(mimeType, data) {
129
- if (typeof data === "string" && data.startsWith("data:"))
130
- return data;
131
- const base64 = typeof data === "string" ? data : Buffer.from(data).toString("base64");
132
- return `data:${mimeType};base64,${base64}`;
622
+ /** Poll the Suno record-info endpoint until the task reaches a terminal state. */
623
+ async function pollSuno(apiKey, taskId, pollInterval, maxAttempts, signal) {
624
+ const url = `${KIE_API_BASE}/api/v1/generate/record-info?taskId=${taskId}`;
625
+ const failed = new Set([
626
+ "CREATE_TASK_FAILED",
627
+ "GENERATE_AUDIO_FAILED",
628
+ "CALLBACK_EXCEPTION",
629
+ "SENSITIVE_WORD_ERROR"
630
+ ]);
631
+ for (let i = 0; i < maxAttempts; i++) {
632
+ const res = await fetch(url, { headers: headers(apiKey), signal });
633
+ const data = await parseKieJson(res, "record-info");
634
+ const status = data.data?.status;
635
+ if (status === "SUCCESS")
636
+ return data;
637
+ if (failed.has(status)) {
638
+ throw new Error(`Kie Suno task failed: ${status} (taskId: ${taskId})`);
639
+ }
640
+ await sleep(pollInterval, signal);
641
+ }
642
+ const timeoutSeconds = (maxAttempts * pollInterval) / 1000;
643
+ throw new Error(`Kie Suno task timed out after ${timeoutSeconds}s (taskId: ${taskId}). ` +
644
+ "The job may still complete on KIE — check record-info or the KIE dashboard.");
133
645
  }
134
646
  const KIE_MANIFEST_PKG = "@nodetool-ai/kie-nodes";
135
647
  const KIE_MANIFEST_PATH = "kie-manifest.json";
@@ -152,24 +664,22 @@ export class KieProvider extends BaseProvider {
152
664
  return this.apiKey;
153
665
  }
154
666
  makeOpenAIProvider(basePath) {
155
- const provider = new OpenAIProvider({ OPENAI_API_KEY: this.requireApiKey() }, {
667
+ return new OpenAIProvider({ OPENAI_API_KEY: this.requireApiKey() }, {
668
+ providerId: "kie",
156
669
  clientFactory: (apiKey) => new OpenAI({
157
670
  apiKey,
158
671
  baseURL: `${KIE_API_BASE}${basePath}`
159
672
  })
160
673
  });
161
- provider.provider = "kie";
162
- return provider;
163
674
  }
164
675
  makeAnthropicProvider(basePath) {
165
- const provider = new AnthropicProvider({ ANTHROPIC_API_KEY: this.requireApiKey() }, {
676
+ return new AnthropicProvider({ ANTHROPIC_API_KEY: this.requireApiKey() }, {
677
+ providerId: "kie",
166
678
  clientFactory: (apiKey) => new Anthropic({
167
679
  authToken: apiKey,
168
680
  baseURL: `${KIE_API_BASE}${basePath}`
169
681
  })
170
682
  });
171
- provider.provider = "kie";
172
- return provider;
173
683
  }
174
684
  makeResponsesClient(basePath) {
175
685
  return new OpenAI({
@@ -215,93 +725,11 @@ export class KieProvider extends BaseProvider {
215
725
  }
216
726
  yield* this.generateResponseMessages(args, model.basePath);
217
727
  }
218
- async responseInputContent(content) {
219
- if (content.type === "text") {
220
- return { type: "input_text", text: content.text };
221
- }
222
- if (content.type === "image_url") {
223
- const image = content.image;
224
- if (image.uri) {
225
- const resolved = await this.resolveUri(image.uri);
226
- return { type: "input_image", image_url: resolved };
227
- }
228
- if (image.data) {
229
- return {
230
- type: "input_image",
231
- image_url: dataUri(image.mimeType ?? "image/jpeg", image.data)
232
- };
233
- }
234
- }
235
- return null;
236
- }
237
- async messagesToResponsesInput(messages) {
238
- const input = [];
239
- for (const message of messages) {
240
- if (message.role === "tool") {
241
- input.push({
242
- type: "function_call_output",
243
- call_id: message.toolCallId ?? "",
244
- output: stringifyContent(message.content)
245
- });
246
- continue;
247
- }
248
- if (message.role === "assistant") {
249
- const text = stringifyContent(message.content);
250
- if (text) {
251
- input.push({
252
- type: "message",
253
- role: "assistant",
254
- content: [{ type: "output_text", text }]
255
- });
256
- }
257
- for (const toolCall of message.toolCalls ?? []) {
258
- input.push({
259
- type: "function_call",
260
- id: toolCall.id,
261
- call_id: toolCall.id,
262
- name: toolCall.name,
263
- arguments: JSON.stringify(toolCall.args ?? {})
264
- });
265
- }
266
- continue;
267
- }
268
- const content = [];
269
- if (typeof message.content === "string") {
270
- content.push({ type: "input_text", text: message.content });
271
- }
272
- else if (Array.isArray(message.content)) {
273
- for (const part of message.content) {
274
- const converted = await this.responseInputContent(part);
275
- if (converted)
276
- content.push(converted);
277
- }
278
- }
279
- if (content.length === 0)
280
- continue;
281
- input.push({ role: message.role, content });
282
- }
283
- return input;
284
- }
285
- responseTools(tools = []) {
286
- return tools.map((tool) => ({
287
- type: "function",
288
- name: tool.name,
289
- description: tool.description ?? "",
290
- parameters: tool.inputSchema ?? { type: "object", properties: {} }
291
- }));
292
- }
293
- responseToolChoice(toolChoice) {
294
- if (!toolChoice)
295
- return undefined;
296
- if (toolChoice === "any")
297
- return "auto";
298
- return { type: "function", name: toolChoice };
299
- }
300
728
  async generateResponseMessage(args, basePath) {
301
729
  const client = this.makeResponsesClient(basePath);
302
730
  const request = {
303
731
  model: args.model,
304
- input: await this.messagesToResponsesInput(args.messages),
732
+ input: await messagesToResponsesInput(args.messages, (uri) => this.resolveUri(uri)),
305
733
  stream: false
306
734
  };
307
735
  if (args.maxTokens != null)
@@ -310,19 +738,22 @@ export class KieProvider extends BaseProvider {
310
738
  request.temperature = args.temperature;
311
739
  if (args.topP != null)
312
740
  request.top_p = args.topP;
313
- const tools = this.responseTools(args.tools);
741
+ const tools = responseTools(args.tools);
314
742
  if (tools.length > 0) {
315
743
  request.tools = tools;
316
- request.tool_choice = this.responseToolChoice(args.toolChoice) ?? "auto";
744
+ request.tool_choice = responseToolChoice(args.toolChoice) ?? "auto";
317
745
  }
746
+ this.recordRequestPayload(request);
747
+ // SAFETY: the SDK's `create` is overloaded over a closed param interface,
748
+ // while this request is assembled field by field.
318
749
  const response = (await client.responses.create.call(client.responses, request, {
319
750
  signal: args.signal
320
751
  }));
321
- this.trackResponseUsage(args.model, response);
322
- const outputText = typeof response.output_text === "string"
752
+ this.trackUsage(args.model, responseUsage(response));
753
+ const outputText = isString(response.output_text)
323
754
  ? response.output_text
324
- : this.extractResponsesText(response.output);
325
- const toolCalls = this.extractResponsesToolCalls(response.output);
755
+ : extractResponsesText(response.output);
756
+ const toolCalls = extractResponsesToolCalls(response.output, this.buildToolCall.bind(this));
326
757
  return {
327
758
  role: "assistant",
328
759
  content: outputText || null,
@@ -333,7 +764,7 @@ export class KieProvider extends BaseProvider {
333
764
  const client = this.makeResponsesClient(basePath);
334
765
  const request = {
335
766
  model: args.model,
336
- input: await this.messagesToResponsesInput(args.messages),
767
+ input: await messagesToResponsesInput(args.messages, (uri) => this.resolveUri(uri)),
337
768
  stream: true
338
769
  };
339
770
  if (args.maxTokens != null)
@@ -342,122 +773,639 @@ export class KieProvider extends BaseProvider {
342
773
  request.temperature = args.temperature;
343
774
  if (args.topP != null)
344
775
  request.top_p = args.topP;
345
- const tools = this.responseTools(args.tools);
776
+ const tools = responseTools(args.tools);
346
777
  if (tools.length > 0) {
347
778
  request.tools = tools;
348
- request.tool_choice = this.responseToolChoice(args.toolChoice) ?? "auto";
779
+ request.tool_choice = responseToolChoice(args.toolChoice) ?? "auto";
349
780
  }
781
+ this.recordRequestPayload(request);
782
+ // SAFETY: as above, for the streaming overload.
350
783
  const stream = (await client.responses.create.call(client.responses, request, { signal: args.signal }));
351
- const pendingArgs = new Map();
352
- for await (const event of stream) {
353
- const type = event.type;
354
- if (type === "response.output_text.delta") {
355
- const chunk = {
356
- type: "chunk",
357
- content: String(event.delta ?? ""),
358
- done: false
359
- };
360
- yield chunk;
361
- continue;
784
+ yield* streamResponsesEvents(stream, {
785
+ model: args.model,
786
+ buildToolCall: this.buildToolCall.bind(this),
787
+ onUsage: (model, usage) => this.trackUsage(model, usage)
788
+ });
789
+ }
790
+ async getAvailableVideoModels() {
791
+ return loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
792
+ }
793
+ async getAvailableImageModels() {
794
+ return loadImageModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
795
+ }
796
+ async getAvailableTTSModels() {
797
+ return loadTTSModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
798
+ }
799
+ async getAvailableMusicModels() {
800
+ return loadMusicModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
801
+ }
802
+ /** Resolve poll interval and attempt count for a model's async task. */
803
+ pollConfig(modelId, timeoutSeconds) {
804
+ const meta = getManifestNodeMeta(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
805
+ const pollInterval = meta?.pollInterval ?? 4000;
806
+ // A caller-supplied timeout wins: translate it into a bounded poll window.
807
+ if (timeoutSeconds != null && timeoutSeconds > 0) {
808
+ const maxAttempts = Math.max(1, Math.ceil((timeoutSeconds * 1000) / pollInterval));
809
+ return { pollInterval, maxAttempts };
810
+ }
811
+ return { pollInterval, maxAttempts: meta?.maxAttempts ?? 300 };
812
+ }
813
+ declaresField(fields, name) {
814
+ return fields.some((f) => f.name === name);
815
+ }
816
+ /**
817
+ * Per-call timeout signal (mirrors the gemini provider): threaded into the
818
+ * poll-loop fetches and sleeps so an expired budget aborts the in-flight
819
+ * request promptly instead of leaking it until the loop exhausts.
820
+ */
821
+ timeoutSignal(timeoutSeconds) {
822
+ return timeoutSeconds && timeoutSeconds > 0
823
+ ? AbortSignal.timeout(timeoutSeconds * 1000)
824
+ : undefined;
825
+ }
826
+ /**
827
+ * Generate music as an encoded audio file. The advertised KIE music models
828
+ * (generate-music, generate-sounds) run through the Suno API — a separate
829
+ * submit endpoint and record-info poll, not the generic jobs task API. A
830
+ * music model without Suno metadata falls back to the generic jobs path.
831
+ */
832
+ async textToMusic(params) {
833
+ if (!params.prompt)
834
+ throw new Error("Prompt is required");
835
+ const apiKey = this.requireApiKey();
836
+ const modelId = params.model.id;
837
+ log.debug("Kie textToMusic", { model: modelId });
838
+ const signal = this.timeoutSignal(params.timeoutSeconds);
839
+ const meta = getManifestNodeMeta(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
840
+ if (meta?.useSuno && meta.sunoEndpoint) {
841
+ const input = this.buildSunoInput(modelId, params);
842
+ const taskId = await submitSuno(apiKey, meta.sunoEndpoint, input, signal);
843
+ const { pollInterval, maxAttempts } = this.sunoPollConfig(meta.pollInterval, meta.maxAttempts, params.timeoutSeconds);
844
+ if (getWebhookBaseUrl()) {
845
+ const timeoutMs = pollInterval * maxAttempts;
846
+ log.info("Waiting for KIE Suno webhook callback", { taskId, timeoutMs });
847
+ await registerWebhookWait(taskId, timeoutMs, signal);
848
+ // After webhook fires, fetch the record to download audio
849
+ const url = `${KIE_API_BASE}/api/v1/generate/record-info?taskId=${taskId}`;
850
+ const res = await fetch(url, { headers: headers(apiKey), signal });
851
+ const record = await parseKieJson(res, "record-info");
852
+ const bytes = await this.downloadSunoAudio(record, signal);
853
+ return { data: bytes, mimeType: sniffAudioMime(bytes) };
362
854
  }
363
- if (type === "response.function_call_arguments.delta") {
364
- const itemId = String(event.item_id ?? "");
365
- pendingArgs.set(itemId, (pendingArgs.get(itemId) ?? "") + String(event.delta ?? ""));
366
- continue;
855
+ const record = await pollSuno(apiKey, taskId, pollInterval, maxAttempts, signal);
856
+ const bytes = await this.downloadSunoAudio(record, signal);
857
+ return { data: bytes, mimeType: sniffAudioMime(bytes) };
858
+ }
859
+ // Fallback: a music model that doesn't route through Suno.
860
+ const input = { prompt: params.prompt };
861
+ if (params.lyrics)
862
+ input.lyrics = params.lyrics;
863
+ if (params.durationSeconds != null) {
864
+ input.duration = Math.round(params.durationSeconds);
865
+ }
866
+ const { pollInterval, maxAttempts } = this.pollConfig(modelId, params.timeoutSeconds);
867
+ const taskId = await submitTaskWithWebhook(apiKey, modelId, input, signal);
868
+ await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts, signal);
869
+ const bytes = await downloadResultBytes(apiKey, taskId, signal);
870
+ return { data: bytes, mimeType: sniffAudioMime(bytes) };
871
+ }
872
+ /** Poll interval/attempts for a Suno task, honoring a per-call timeout. */
873
+ sunoPollConfig(metaPollInterval, metaMaxAttempts, timeoutSeconds) {
874
+ const pollInterval = metaPollInterval ?? 4000;
875
+ if (timeoutSeconds != null && timeoutSeconds > 0) {
876
+ return {
877
+ pollInterval,
878
+ maxAttempts: Math.max(1, Math.ceil((timeoutSeconds * 1000) / pollInterval))
879
+ };
880
+ }
881
+ return { pollInterval, maxAttempts: metaMaxAttempts ?? 120 };
882
+ }
883
+ /**
884
+ * Build the Suno submit body from the model's manifest fields. `generate-music`
885
+ * distinguishes custom mode (lyrics supplied → prompt = lyrics, style = the
886
+ * description) from non-custom mode (KIE auto-generates lyrics from the
887
+ * prompt). Required fields with a usable default (e.g. `model`) are filled.
888
+ */
889
+ buildSunoInput(modelId, params) {
890
+ const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
891
+ const has = (name) => this.declaresField(fields, name);
892
+ const input = {};
893
+ if (params.lyrics && has("customMode") && has("style")) {
894
+ input.customMode = true;
895
+ input.prompt = params.lyrics;
896
+ input.style = params.prompt;
897
+ if (has("instrumental"))
898
+ input.instrumental = false;
899
+ }
900
+ else {
901
+ if (has("customMode"))
902
+ input.customMode = false;
903
+ input.prompt = params.prompt;
904
+ }
905
+ for (const f of fields) {
906
+ if (f.required && input[f.name] === undefined) {
907
+ const d = defaultForField(f);
908
+ if (d !== undefined)
909
+ input[f.name] = d;
367
910
  }
368
- if (type === "response.function_call_arguments.done") {
369
- const itemId = String(event.item_id ?? "");
370
- const argsText = String(event.arguments ?? pendingArgs.get(itemId) ?? "{}");
371
- pendingArgs.delete(itemId);
372
- yield this.buildToolCall(itemId, String(event.name ?? ""), argsText);
911
+ }
912
+ return input;
913
+ }
914
+ /** Download the first audio track from a Suno record-info response. */
915
+ async downloadSunoAudio(record, signal) {
916
+ const response = record.data?.response;
917
+ const sunoData = response?.sunoData;
918
+ const audioUrl = sunoData?.[0]?.audioUrl;
919
+ if (!audioUrl)
920
+ throw new Error("No audioUrl in Kie Suno response");
921
+ const dlRes = await safeFetch(audioUrl, { signal });
922
+ if (!dlRes.ok) {
923
+ throw new Error(`Failed to download from ${audioUrl}`);
924
+ }
925
+ return new Uint8Array(await dlRes.arrayBuffer());
926
+ }
927
+ /**
928
+ * Generate speech as an encoded audio file. KIE runs TTS as an async job and
929
+ * returns a result URL (mp3); the unified TTS node consumes this encoded path
930
+ * rather than streaming PCM samples.
931
+ */
932
+ async textToSpeechEncoded(args) {
933
+ if (!args.text)
934
+ throw new Error("text must not be empty");
935
+ const input = { text: args.text };
936
+ if (args.voice)
937
+ input.voice = args.voice;
938
+ if (args.speed != null)
939
+ input.speed = args.speed;
940
+ log.debug("Kie textToSpeech", { model: args.model });
941
+ const apiKey = this.requireApiKey();
942
+ const { pollInterval, maxAttempts } = this.pollConfig(args.model);
943
+ const taskId = await submitTaskWithWebhook(apiKey, args.model, input);
944
+ await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts);
945
+ const bytes = await downloadResultBytes(apiKey, taskId);
946
+ return { data: bytes, mimeType: sniffAudioMime(bytes) };
947
+ }
948
+ async textToVideo(params) {
949
+ if (!params.prompt)
950
+ throw new Error("Prompt is required");
951
+ const modelId = params.model.id;
952
+ const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
953
+ const input = { prompt: params.prompt };
954
+ if (params.aspectRatio)
955
+ input.aspect_ratio = params.aspectRatio;
956
+ this.applyVideoDuration(input, fields, params);
957
+ this.applyRequiredDefaults(input, fields);
958
+ this.clampToDeclaredMax(input, fields);
959
+ log.debug("Kie textToVideo", { model: modelId });
960
+ const apiKey = this.requireApiKey();
961
+ const signal = this.timeoutSignal(params.timeoutSeconds);
962
+ const { pollInterval, maxAttempts } = this.pollConfig(modelId, params.timeoutSeconds);
963
+ const taskId = await submitTaskWithWebhook(apiKey, modelId, input, signal);
964
+ await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts, signal);
965
+ return downloadResultBytes(apiKey, taskId, signal);
966
+ }
967
+ /**
968
+ * Set the request `duration` from the caller's requested seconds (preferring
969
+ * `durationSeconds`, falling back to `numFrames`/24), coerced to the model's
970
+ * declared field type. A declared schema with no `duration` field sends
971
+ * nothing; an unknown model keeps the historical numeric behavior.
972
+ */
973
+ /**
974
+ * Fill required fields the model declares but nothing else set, using the
975
+ * manifest's declared default.
976
+ *
977
+ * Kie rejects a task with `500 "This field is required"` when a declared
978
+ * required field is absent — it does not fall back to the documented default
979
+ * itself. `kling-2.6/image-to-video` requires `sound` (default false), which
980
+ * no param maps to, so an image-to-video task failed outright.
981
+ *
982
+ * Only fields the manifest marks required AND gives a default are filled, so
983
+ * this cannot invent a value the model never described.
984
+ */
985
+ /**
986
+ * Trim string inputs to the length the manifest declares for them.
987
+ *
988
+ * Kie rejects an over-long field with `500 {"msg":"The text length cannot
989
+ * exceed the maximum limit"}` — no field name, no limit, no actual length.
990
+ * The manifest already carries the cap (Kling 2.6's `prompt` is capped at
991
+ * 1000), so the ceiling is known here and there is no reason to spend a
992
+ * round trip discovering it.
993
+ *
994
+ * Composed prompts are what hit this: `composeShotPrompt` concatenates a
995
+ * shot's action, camera, motion and the screenplay's style bible, and a
996
+ * verbose director clears 1000 characters without trying. Truncation is on a
997
+ * word boundary and always logged with both lengths — silently reshaping
998
+ * someone's prompt is worse than the 500 it replaces.
999
+ */
1000
+ clampToDeclaredMax(input, fields) {
1001
+ for (const field of fields) {
1002
+ const max = field.max;
1003
+ if (max === undefined || max <= 0)
373
1004
  continue;
374
- }
375
- if (type === "response.completed") {
376
- const response = event.response;
377
- if (isRecord(response))
378
- this.trackResponseUsage(args.model, response);
379
- const chunk = { type: "chunk", content: "", done: true };
380
- yield chunk;
1005
+ const value = input[field.name];
1006
+ if (!isString(value) || value.length <= max)
381
1007
  continue;
382
- }
383
- if (type === "response.failed" || type === "response.error") {
384
- throw new Error(`Kie responses stream failed: ${JSON.stringify(event)}`);
385
- }
1008
+ const cut = value.slice(0, max);
1009
+ const lastSpace = cut.lastIndexOf(" ");
1010
+ input[field.name] = (lastSpace > max * 0.6 ? cut.slice(0, lastSpace) : cut).trimEnd();
1011
+ log.warn("Kie input truncated to the manifest's declared maximum", {
1012
+ field: field.name,
1013
+ max,
1014
+ originalLength: value.length
1015
+ });
386
1016
  }
387
1017
  }
388
- extractResponsesText(output) {
389
- if (!Array.isArray(output))
390
- return "";
391
- const parts = [];
392
- for (const item of output) {
393
- if (!isRecord(item) || item.type !== "message" || !Array.isArray(item.content)) {
1018
+ applyRequiredDefaults(input, fields) {
1019
+ for (const field of fields) {
1020
+ if (!field.required)
394
1021
  continue;
395
- }
396
- for (const content of item.content) {
397
- if (isRecord(content) && content.type === "output_text") {
398
- parts.push(String(content.text ?? ""));
399
- }
400
- }
1022
+ if (field.default === undefined)
1023
+ continue;
1024
+ if (input[field.name] !== undefined)
1025
+ continue;
1026
+ input[field.name] = field.default;
401
1027
  }
402
- return parts.join("");
403
1028
  }
404
- extractResponsesToolCalls(output) {
405
- if (!Array.isArray(output))
406
- return [];
407
- const toolCalls = [];
408
- for (const item of output) {
409
- if (!isRecord(item) || item.type !== "function_call")
1029
+ /**
1030
+ * Refuse a call whose required text field is blank, naming the field and the
1031
+ * model.
1032
+ *
1033
+ * Kie answers one with `500 {"msg":"This field is required"}` — no field, no
1034
+ * model, and by then the rest of the graph has been paid for. Reaching it is
1035
+ * easy from a generic node: `nodetool.video.ImageToVideo` treats `prompt` as
1036
+ * optional because Veo 3.1 does, so pointing the same node at
1037
+ * `kling-2.6/image-to-video` sends a blank one. `applyRequiredDefaults` does
1038
+ * not save it — the manifest's declared default for such a field is `""`,
1039
+ * which is the value the endpoint rejects.
1040
+ *
1041
+ * Text only. A required media field arrives under its upload descriptor's API
1042
+ * name (`images` is sent as `image_urls`), so reading it off the input bag
1043
+ * under the manifest's name would refuse a call that is correctly formed.
1044
+ */
1045
+ assertRequiredTextFields(input, fields, modelId) {
1046
+ for (const field of fields) {
1047
+ if (!field.required || field.type !== "str")
1048
+ continue;
1049
+ const value = input[field.name];
1050
+ if (isString(value) && value.trim().length > 0)
410
1051
  continue;
411
- toolCalls.push(this.buildToolCall(String(item.call_id ?? item.id ?? ""), String(item.name ?? ""), item.arguments));
1052
+ throw new Error(`Kie model "${modelId}" requires "${field.name}", which is empty. ` +
1053
+ `Set the node's "${field.name}" property (or wire it), or choose a ` +
1054
+ "model that does not require it.");
412
1055
  }
413
- return toolCalls;
414
1056
  }
415
- trackResponseUsage(model, response) {
416
- const usage = response.usage;
417
- if (!isRecord(usage))
1057
+ applyVideoDuration(input, fields, params) {
1058
+ const seconds = params.durationSeconds != null
1059
+ ? params.durationSeconds
1060
+ : params.numFrames != null
1061
+ ? params.numFrames / 24
1062
+ : undefined;
1063
+ if (seconds == null)
418
1064
  return;
419
- const inputTokens = Number(usage.input_tokens ?? 0);
420
- const outputTokens = Number(usage.output_tokens ?? 0);
421
- const details = usage.input_tokens_details;
422
- const cachedTokens = isRecord(details) ? Number(details.cached_tokens ?? 0) : 0;
423
- this.trackUsage(model, {
424
- inputTokens: Number.isFinite(inputTokens) ? inputTokens : 0,
425
- outputTokens: Number.isFinite(outputTokens) ? outputTokens : 0,
426
- cachedTokens: Number.isFinite(cachedTokens) ? cachedTokens : 0
427
- });
428
- }
429
- async getAvailableVideoModels() {
430
- return loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
431
- }
432
- async getAvailableImageModels() {
433
- return loadImageModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
1065
+ if (fields.length === 0) {
1066
+ // Unknown model: derive a plain integer as before (ceil for a frame count,
1067
+ // round for an explicit second count).
1068
+ input.duration =
1069
+ params.durationSeconds != null ? Math.round(seconds) : Math.ceil(seconds);
1070
+ return;
1071
+ }
1072
+ const durationField = fields.find((f) => f.name === "duration");
1073
+ if (!durationField)
1074
+ return;
1075
+ input.duration = coerceDuration(durationField, seconds);
434
1076
  }
435
- async textToVideo(params) {
1077
+ async textToImage(params) {
436
1078
  if (!params.prompt)
437
1079
  throw new Error("Prompt is required");
1080
+ const modelId = params.model.id;
1081
+ const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
438
1082
  const input = { prompt: params.prompt };
439
- if (params.aspectRatio)
1083
+ // Schema-driven: only send fields the model declares. No KIE model declares
1084
+ // width/height, so those are never sent — an aspect ratio is derived instead.
1085
+ if (params.negativePrompt && this.declaresField(fields, "negative_prompt")) {
1086
+ input.negative_prompt = params.negativePrompt;
1087
+ }
1088
+ if (params.seed != null && this.declaresField(fields, "seed")) {
1089
+ input.seed = params.seed;
1090
+ }
1091
+ this.applyTextToImageAspect(input, fields, params);
1092
+ log.debug("Kie textToImage", { model: modelId });
1093
+ const apiKey = this.requireApiKey();
1094
+ const { pollInterval, maxAttempts } = this.pollConfig(modelId);
1095
+ this.applyRequiredDefaults(input, fields);
1096
+ this.clampToDeclaredMax(input, fields);
1097
+ const taskId = await submitTaskWithWebhook(apiKey, modelId, input);
1098
+ await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts);
1099
+ return downloadResultBytes(apiKey, taskId);
1100
+ }
1101
+ /**
1102
+ * Set `aspect_ratio` when the model declares it: pass a caller-supplied ratio
1103
+ * through if it's a declared enum value (else snap to the nearest), or derive
1104
+ * the nearest declared ratio from width/height when no ratio was given.
1105
+ */
1106
+ applyTextToImageAspect(input, fields, params) {
1107
+ const field = fields.find((f) => f.name === "aspect_ratio");
1108
+ if (!field)
1109
+ return;
1110
+ const enumValues = field.enumValues;
1111
+ if (params.aspectRatio) {
1112
+ if (enumValues && enumValues.length > 0) {
1113
+ input.aspect_ratio = enumValues.includes(params.aspectRatio)
1114
+ ? params.aspectRatio
1115
+ : nearestAspect(enumValues, aspectToNumber(params.aspectRatio) ?? 1);
1116
+ }
1117
+ else {
1118
+ input.aspect_ratio = params.aspectRatio;
1119
+ }
1120
+ return;
1121
+ }
1122
+ if (params.width && params.height && enumValues && enumValues.length > 0) {
1123
+ input.aspect_ratio = nearestAspect(enumValues, params.width / params.height);
1124
+ }
1125
+ }
1126
+ /**
1127
+ * Set the request's `negative_prompt` / `aspect_ratio` / `seed` from the
1128
+ * caller only when the model declares them. Unknown models (empty schema)
1129
+ * keep the historical behavior: send whatever the caller supplied under the
1130
+ * conventional names.
1131
+ */
1132
+ applyEditOptions(input, fields, params) {
1133
+ const declaredOrUnknown = (name) => fields.length === 0 || this.declaresField(fields, name);
1134
+ if (params.negativePrompt && declaredOrUnknown("negative_prompt")) {
1135
+ input.negative_prompt = params.negativePrompt;
1136
+ }
1137
+ if (params.aspectRatio && declaredOrUnknown("aspect_ratio")) {
440
1138
  input.aspect_ratio = params.aspectRatio;
441
- if (params.numFrames) {
442
- input.duration = Math.ceil(params.numFrames / 24);
1139
+ }
1140
+ if (params.seed != null && declaredOrUnknown("seed")) {
1141
+ input.seed = params.seed;
1142
+ }
1143
+ }
1144
+ /**
1145
+ * Edit/transform one or more source images. Uses the model's schema to route
1146
+ * the uploaded image(s) to the correct input field — single-image models
1147
+ * (`image_url`) and multi-image edit/composition models (`image_urls` /
1148
+ * `image_input`) are both supported.
1149
+ */
1150
+ async imageToImage(images, params) {
1151
+ const apiKey = this.requireApiKey();
1152
+ const imageUrls = await this.uploadImages(apiKey, images);
1153
+ if (imageUrls.length === 0) {
1154
+ throw new Error("The input image is empty.");
443
1155
  }
444
1156
  const modelId = params.model.id;
445
- log.debug("Kie textToVideo", { model: modelId });
446
- const taskId = await submitTask(this.requireApiKey(), modelId, input);
447
- await pollUntilDone(this.requireApiKey(), taskId);
448
- return downloadResultBytes(this.requireApiKey(), taskId);
1157
+ const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
1158
+ const input = {
1159
+ prompt: params.prompt,
1160
+ ...this.imageInput(modelId, imageUrls)
1161
+ };
1162
+ this.applyEditOptions(input, fields, params);
1163
+ log.debug("Kie imageToImage", { model: modelId, images: imageUrls.length });
1164
+ const { pollInterval, maxAttempts } = this.pollConfig(modelId);
1165
+ this.applyRequiredDefaults(input, fields);
1166
+ this.clampToDeclaredMax(input, fields);
1167
+ const taskId = await submitTaskWithWebhook(apiKey, modelId, input);
1168
+ await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts);
1169
+ return downloadResultBytes(apiKey, taskId);
449
1170
  }
450
- async textToImage(params) {
451
- const input = { prompt: params.prompt };
452
- if (params.width)
453
- input.width = params.width;
454
- if (params.height)
455
- input.height = params.height;
1171
+ /**
1172
+ * Inpaint: regenerate the masked region of one or more source images. The
1173
+ * source image(s) route to the model's primary image field; the mask routes
1174
+ * to whatever mask field the model's schema declares (falling back to
1175
+ * `mask_url`).
1176
+ */
1177
+ async inpaint(images, params) {
1178
+ const apiKey = this.requireApiKey();
1179
+ const imageUrls = await this.uploadImages(apiKey, images);
1180
+ if (imageUrls.length === 0) {
1181
+ throw new Error("The input image is empty.");
1182
+ }
456
1183
  const modelId = params.model.id;
457
- log.debug("Kie textToImage", { model: modelId });
458
- const taskId = await submitTask(this.requireApiKey(), modelId, input);
459
- await pollUntilDone(this.requireApiKey(), taskId);
460
- return downloadResultBytes(this.requireApiKey(), taskId);
1184
+ const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
1185
+ const input = {
1186
+ prompt: params.prompt,
1187
+ ...this.imageInput(modelId, imageUrls)
1188
+ };
1189
+ this.applyEditOptions(input, fields, params);
1190
+ if (params.mask && params.mask.length > 0) {
1191
+ const [maskUrl] = await this.uploadImages(apiKey, [params.mask]);
1192
+ if (maskUrl) {
1193
+ const maskField = selectMaskImageInput(getModelImageInputs(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId));
1194
+ const fieldName = maskField?.apiName ?? "mask_url";
1195
+ input[fieldName] = maskField?.isList ? [maskUrl] : maskUrl;
1196
+ }
1197
+ }
1198
+ log.debug("Kie inpaint", { model: modelId, images: imageUrls.length });
1199
+ const { pollInterval, maxAttempts } = this.pollConfig(modelId);
1200
+ this.applyRequiredDefaults(input, fields);
1201
+ this.clampToDeclaredMax(input, fields);
1202
+ const taskId = await submitTaskWithWebhook(apiKey, modelId, input);
1203
+ await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts);
1204
+ return downloadResultBytes(apiKey, taskId);
1205
+ }
1206
+ /**
1207
+ * Animate one or more source images into a video. The model schema decides
1208
+ * whether the frame goes to a single field (`image_url`) or a list.
1209
+ */
1210
+ async imageToVideo(image, params) {
1211
+ const selected = loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie")
1212
+ .find((model) => model.id === params.model.id);
1213
+ if (selected && !selected.supportedTasks?.includes("image_to_video")) {
1214
+ throw new Error(`Kie model ${params.model.id} does not support image_to_video`);
1215
+ }
1216
+ const apiKey = this.requireApiKey();
1217
+ const signal = this.timeoutSignal(params.timeoutSeconds);
1218
+ const imageUrls = await this.uploadImages(apiKey, [image], signal);
1219
+ if (imageUrls.length === 0) {
1220
+ throw new Error("The input image is empty.");
1221
+ }
1222
+ const modelId = params.model.id;
1223
+ const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
1224
+ const input = this.imageInput(modelId, imageUrls);
1225
+ if (params.prompt)
1226
+ input.prompt = params.prompt;
1227
+ this.applyEditOptions(input, fields, params);
1228
+ this.applyVideoDuration(input, fields, params);
1229
+ this.applyRequiredDefaults(input, fields);
1230
+ this.clampToDeclaredMax(input, fields);
1231
+ this.assertRequiredTextFields(input, fields, modelId);
1232
+ log.debug("Kie imageToVideo", { model: modelId, images: imageUrls.length });
1233
+ const { pollInterval, maxAttempts } = this.pollConfig(modelId, params.timeoutSeconds);
1234
+ const taskId = await submitTaskWithWebhook(apiKey, modelId, input, signal);
1235
+ await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts, signal);
1236
+ return downloadResultBytes(apiKey, taskId, signal);
1237
+ }
1238
+ async extendVideo(video, params) {
1239
+ if (video.length === 0) {
1240
+ throw new Error("The input video is empty.");
1241
+ }
1242
+ const modelId = params.model.id;
1243
+ const selected = loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie").find((model) => model.id === modelId);
1244
+ if (!selected?.supportedTasks?.includes("extend_video")) {
1245
+ throw new Error(`Kie model ${modelId} does not support extend_video`);
1246
+ }
1247
+ if (!selected.supportedTasks.includes(`extend_video_${params.mode}`)) {
1248
+ throw new Error(`Kie model ${modelId} does not support ${params.mode} extension`);
1249
+ }
1250
+ if (!Number.isFinite(params.durationSeconds) ||
1251
+ params.durationSeconds <= 0 ||
1252
+ (selected.durations?.length &&
1253
+ !selected.durations.includes(params.durationSeconds))) {
1254
+ throw new Error("Choose a supported extension duration.");
1255
+ }
1256
+ const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
1257
+ const durationField = fields.find((field) => field.name === "duration");
1258
+ if ((durationField?.min !== undefined &&
1259
+ params.durationSeconds < durationField.min) ||
1260
+ (durationField?.max !== undefined &&
1261
+ params.durationSeconds > durationField.max)) {
1262
+ throw new Error("Choose an extension duration within the model's supported range.");
1263
+ }
1264
+ const promptField = fields.find((field) => field.name === "prompt");
1265
+ if (promptField?.required && !params.prompt.trim()) {
1266
+ throw new Error(`Kie model ${modelId} requires a prompt`);
1267
+ }
1268
+ const videoField = getModelMediaInputs(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId).find((field) => field.kind === "video");
1269
+ if (!videoField) {
1270
+ throw new Error(`Kie model ${modelId} does not accept a source video`);
1271
+ }
1272
+ const qualityField = fields.find((field) => field.name === "quality");
1273
+ const quality = qualityField?.enumValues?.includes("720p")
1274
+ ? "720p"
1275
+ : qualityField?.enumValues?.[0];
1276
+ if (qualityField?.required && !quality) {
1277
+ throw new Error(`Kie model ${modelId} does not declare a usable quality`);
1278
+ }
1279
+ const apiKey = this.requireApiKey();
1280
+ const videoUrl = await uploadMediaBytes(apiKey, video, undefined, "video");
1281
+ const input = {
1282
+ [videoField.apiName]: videoField.isList ? [videoUrl] : videoUrl,
1283
+ prompt: params.prompt
1284
+ };
1285
+ this.applyVideoDuration(input, fields, params);
1286
+ if (quality) {
1287
+ input.quality = quality;
1288
+ }
1289
+ this.clampToDeclaredMax(input, fields);
1290
+ log.debug("Kie extendVideo", { model: modelId, mode: params.mode });
1291
+ const { pollInterval, maxAttempts } = this.pollConfig(modelId);
1292
+ const taskId = await submitTaskWithWebhook(apiKey, modelId, input);
1293
+ await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts);
1294
+ return downloadResultBytes(apiKey, taskId);
1295
+ }
1296
+ /**
1297
+ * Upscale a video. The clip uploads to the model's declared video field and
1298
+ * the requested size is written to whichever of `upscale_factor` /
1299
+ * `resolution` the schema declares — Kie's two upscalers take one each.
1300
+ */
1301
+ async upscaleVideo(video, params) {
1302
+ if (video.length === 0) {
1303
+ throw new Error("The input video is empty.");
1304
+ }
1305
+ const modelId = params.model.id;
1306
+ const supported = loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie")
1307
+ .find((model) => model.id === modelId)
1308
+ ?.supportedTasks;
1309
+ if (supported && !supported.includes("upscale_video")) {
1310
+ throw new Error(`Kie model ${modelId} does not support upscale_video`);
1311
+ }
1312
+ const apiKey = this.requireApiKey();
1313
+ const signal = this.timeoutSignal(params.timeoutSeconds);
1314
+ const videoUrl = await uploadMediaBytes(apiKey, video, signal, "video");
1315
+ const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
1316
+ const videoField = getModelMediaInputs(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId)
1317
+ .find((field) => field.kind === "video");
1318
+ const input = {
1319
+ [videoField?.apiName ?? "video_url"]: videoField?.isList
1320
+ ? [videoUrl]
1321
+ : videoUrl
1322
+ };
1323
+ if (params.scale != null && this.declaresField(fields, "upscale_factor")) {
1324
+ input.upscale_factor = params.scale;
1325
+ }
1326
+ if (params.targetResolution &&
1327
+ this.declaresField(fields, "resolution")) {
1328
+ input.resolution = params.targetResolution;
1329
+ }
1330
+ this.applyEditOptions(input, fields, params);
1331
+ this.applyRequiredDefaults(input, fields);
1332
+ this.clampToDeclaredMax(input, fields);
1333
+ log.debug("Kie upscaleVideo", { model: modelId });
1334
+ const { pollInterval, maxAttempts } = this.pollConfig(modelId, params.timeoutSeconds);
1335
+ const taskId = await submitTaskWithWebhook(apiKey, modelId, input, signal);
1336
+ await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts, signal);
1337
+ return downloadResultBytes(apiKey, taskId, signal);
1338
+ }
1339
+ async referenceToVideo(inputs, params) {
1340
+ const images = inputs.images.filter((b) => b.length > 0);
1341
+ const videos = inputs.videos.filter((b) => b.length > 0);
1342
+ if (images.length === 0 && videos.length === 0)
1343
+ throw new Error("reference_to_video requires at least one reference image or video");
1344
+ const apiKey = this.requireApiKey();
1345
+ const signal = combineSignals(params.signal, this.timeoutSignal(params.timeoutSeconds));
1346
+ if (params.useReferenceVideoAudio === true && videos.length === 0)
1347
+ throw new Error(`Kie model ${params.model.id} requires a reference video when audio is enabled`);
1348
+ if (!(loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie").find((m) => m.id === params.model.id)?.supportedTasks?.includes("reference_to_video") ?? false))
1349
+ throw new Error(`Kie model ${params.model.id} does not support reference_to_video`);
1350
+ const referenceFields = getModelReferenceInputs(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, params.model.id);
1351
+ // H3's generated minimum describes clip duration (2 seconds), not video count.
1352
+ if (params.model.id === "minimax-h3/reference-to-video") {
1353
+ for (const field of referenceFields) {
1354
+ if (field.kind === "video")
1355
+ delete field.min;
1356
+ }
1357
+ }
1358
+ validateReferenceInputs("Kie", params.model.id, inputs, referenceFields);
1359
+ const imageField = referenceFields.find((f) => f.kind === "image");
1360
+ const videoField = referenceFields.find((f) => f.kind === "video");
1361
+ const fieldsForModel = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, params.model.id);
1362
+ if (params.useReferenceVideoAudio === true && !fieldsForModel.some((field) => field.name === "use_reference_video_audio")) {
1363
+ throw new Error(`Kie model ${params.model.id} does not support reference video audio`);
1364
+ }
1365
+ signal?.throwIfAborted();
1366
+ const [imageUrls, videoUrls] = await Promise.all([
1367
+ Promise.all(images.map((b) => uploadMediaBytes(apiKey, b, signal, "image"))),
1368
+ Promise.all(videos.map((b) => uploadMediaBytes(apiKey, b, signal, "video")))
1369
+ ]);
1370
+ const input = {};
1371
+ if (imageField && imageUrls.length > 0)
1372
+ input[imageField.apiName] = imageField.isList ? imageUrls : imageUrls[0];
1373
+ if (videoField && videoUrls.length > 0)
1374
+ input[videoField.apiName] = videoField.isList ? videoUrls : videoUrls[0];
1375
+ if (params.prompt)
1376
+ input.prompt = params.prompt;
1377
+ if (params.negativePrompt && fieldsForModel.some((f) => f.name === "negative_prompt"))
1378
+ input.negative_prompt = params.negativePrompt;
1379
+ this.applyEditOptions(input, fieldsForModel, params);
1380
+ if (params.useReferenceVideoAudio === true) {
1381
+ input.use_reference_video_audio = true;
1382
+ }
1383
+ this.applyVideoDuration(input, fieldsForModel, params);
1384
+ this.applyRequiredDefaults(input, fieldsForModel);
1385
+ this.assertRequiredTextFields(input, fieldsForModel, params.model.id);
1386
+ const taskId = await submitTaskWithWebhook(apiKey, params.model.id, input, signal);
1387
+ const polling = this.pollConfig(params.model.id, params.timeoutSeconds);
1388
+ await waitForCompletion(apiKey, taskId, polling.pollInterval, polling.maxAttempts, signal);
1389
+ return downloadResultBytes(apiKey, taskId, signal);
1390
+ }
1391
+ /**
1392
+ * Map uploaded image URLs to the model's declared input field. Single-image
1393
+ * models get `image_url` (string); multi-image models get `image_urls` /
1394
+ * `image_input` (array). Falls back to KIE's conventions for unknown models.
1395
+ */
1396
+ imageInput(modelId, urls) {
1397
+ const field = selectPrimaryImageInput(getModelImageInputs(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId), urls.length);
1398
+ if (field) {
1399
+ return { [field.apiName]: field.isList ? urls : urls[0] };
1400
+ }
1401
+ return urls.length === 1
1402
+ ? { image_url: urls[0] }
1403
+ : { image_urls: urls };
1404
+ }
1405
+ /** Upload every non-empty image to KIE's file store, returning hosted URLs. */
1406
+ async uploadImages(apiKey, images, signal) {
1407
+ const valid = images.filter((b) => b && b.length > 0);
1408
+ return Promise.all(valid.map((b) => uploadImageBytes(apiKey, b, signal)));
461
1409
  }
462
1410
  }
463
1411
  //# sourceMappingURL=kie-provider.js.map