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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (713) hide show
  1. package/README.md +29 -0
  2. package/dist/.nodetool-build-stamp +1 -0
  3. package/dist/agent-memory.d.ts +118 -0
  4. package/dist/agent-memory.d.ts.map +1 -0
  5. package/dist/agent-memory.js +186 -0
  6. package/dist/agent-memory.js.map +1 -0
  7. package/dist/async-local-storage.d.ts +20 -0
  8. package/dist/async-local-storage.d.ts.map +1 -0
  9. package/dist/async-local-storage.js +28 -0
  10. package/dist/async-local-storage.js.map +1 -0
  11. package/dist/blender-executor.d.ts +93 -0
  12. package/dist/blender-executor.d.ts.map +1 -0
  13. package/dist/blender-executor.js +294 -0
  14. package/dist/blender-executor.js.map +1 -0
  15. package/dist/comfy-executor.d.ts +23 -1
  16. package/dist/comfy-executor.d.ts.map +1 -1
  17. package/dist/comfy-executor.js +234 -55
  18. package/dist/comfy-executor.js.map +1 -1
  19. package/dist/context-packer.d.ts +1 -1
  20. package/dist/context-packer.d.ts.map +1 -1
  21. package/dist/context-packer.js +23 -25
  22. package/dist/context-packer.js.map +1 -1
  23. package/dist/context.d.ts +982 -127
  24. package/dist/context.d.ts.map +1 -1
  25. package/dist/context.js +2649 -530
  26. package/dist/context.js.map +1 -1
  27. package/dist/cost-reconciler.d.ts +32 -0
  28. package/dist/cost-reconciler.d.ts.map +1 -0
  29. package/dist/cost-reconciler.js +21 -0
  30. package/dist/cost-reconciler.js.map +1 -0
  31. package/dist/entities.d.ts +33 -0
  32. package/dist/entities.d.ts.map +1 -0
  33. package/dist/entities.js +43 -0
  34. package/dist/entities.js.map +1 -0
  35. package/dist/external-media-fetch.d.ts +29 -0
  36. package/dist/external-media-fetch.d.ts.map +1 -0
  37. package/dist/external-media-fetch.js +38 -0
  38. package/dist/external-media-fetch.js.map +1 -0
  39. package/dist/generation-receipt.d.ts +86 -0
  40. package/dist/generation-receipt.d.ts.map +1 -0
  41. package/dist/generation-receipt.js +137 -0
  42. package/dist/generation-receipt.js.map +1 -0
  43. package/dist/generation-registry.d.ts +75 -0
  44. package/dist/generation-registry.d.ts.map +1 -0
  45. package/dist/generation-registry.js +144 -0
  46. package/dist/generation-registry.js.map +1 -0
  47. package/dist/google/client.d.ts +196 -0
  48. package/dist/google/client.d.ts.map +1 -0
  49. package/dist/google/client.js +396 -0
  50. package/dist/google/client.js.map +1 -0
  51. package/dist/google/index.d.ts +3 -0
  52. package/dist/google/index.d.ts.map +1 -0
  53. package/dist/google/index.js +3 -0
  54. package/dist/google/index.js.map +1 -0
  55. package/dist/google/token.d.ts +15 -0
  56. package/dist/google/token.d.ts.map +1 -0
  57. package/dist/google/token.js +24 -0
  58. package/dist/google/token.js.map +1 -0
  59. package/dist/host-binaries.d.ts +106 -0
  60. package/dist/host-binaries.d.ts.map +1 -0
  61. package/dist/host-binaries.js +352 -0
  62. package/dist/host-binaries.js.map +1 -0
  63. package/dist/image-codec.d.ts +76 -0
  64. package/dist/image-codec.d.ts.map +1 -0
  65. package/dist/image-codec.js +293 -0
  66. package/dist/image-codec.js.map +1 -0
  67. package/dist/image-model-tasks.d.ts +15 -0
  68. package/dist/image-model-tasks.d.ts.map +1 -0
  69. package/dist/image-model-tasks.js +24 -0
  70. package/dist/image-model-tasks.js.map +1 -0
  71. package/dist/index.d.ts +67 -6
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +51 -4
  74. package/dist/index.js.map +1 -1
  75. package/dist/invocation-account.d.ts +43 -0
  76. package/dist/invocation-account.d.ts.map +1 -0
  77. package/dist/invocation-account.js +60 -0
  78. package/dist/invocation-account.js.map +1 -0
  79. package/dist/media-ref-bytes.d.ts +35 -0
  80. package/dist/media-ref-bytes.d.ts.map +1 -0
  81. package/dist/media-ref-bytes.js +191 -0
  82. package/dist/media-ref-bytes.js.map +1 -0
  83. package/dist/node-executor.d.ts +119 -2
  84. package/dist/node-executor.d.ts.map +1 -1
  85. package/dist/permission-gate.d.ts +152 -0
  86. package/dist/permission-gate.d.ts.map +1 -0
  87. package/dist/permission-gate.js +87 -0
  88. package/dist/permission-gate.js.map +1 -0
  89. package/dist/prefixed-storage-adapter.d.ts +33 -0
  90. package/dist/prefixed-storage-adapter.d.ts.map +1 -0
  91. package/dist/prefixed-storage-adapter.js +70 -0
  92. package/dist/prefixed-storage-adapter.js.map +1 -0
  93. package/dist/prompt-asset-refs.d.ts +222 -0
  94. package/dist/prompt-asset-refs.d.ts.map +1 -0
  95. package/dist/prompt-asset-refs.js +751 -0
  96. package/dist/prompt-asset-refs.js.map +1 -0
  97. package/dist/provider-cache.d.ts +3 -0
  98. package/dist/provider-cache.d.ts.map +1 -0
  99. package/dist/provider-cache.js +8 -0
  100. package/dist/provider-cache.js.map +1 -0
  101. package/dist/providers/aki-provider.d.ts +35 -10
  102. package/dist/providers/aki-provider.d.ts.map +1 -1
  103. package/dist/providers/aki-provider.js +90 -77
  104. package/dist/providers/aki-provider.js.map +1 -1
  105. package/dist/providers/alibaba-provider.d.ts +39 -0
  106. package/dist/providers/alibaba-provider.d.ts.map +1 -0
  107. package/dist/providers/alibaba-provider.js +86 -0
  108. package/dist/providers/alibaba-provider.js.map +1 -0
  109. package/dist/providers/anthropic-provider.d.ts +52 -3
  110. package/dist/providers/anthropic-provider.d.ts.map +1 -1
  111. package/dist/providers/anthropic-provider.js +1020 -338
  112. package/dist/providers/anthropic-provider.js.map +1 -1
  113. package/dist/providers/atlascloud-provider.d.ts +103 -0
  114. package/dist/providers/atlascloud-provider.d.ts.map +1 -0
  115. package/dist/providers/atlascloud-provider.js +1031 -0
  116. package/dist/providers/atlascloud-provider.js.map +1 -0
  117. package/dist/providers/atlascloud-transport.d.ts +100 -0
  118. package/dist/providers/atlascloud-transport.d.ts.map +1 -0
  119. package/dist/providers/atlascloud-transport.js +364 -0
  120. package/dist/providers/atlascloud-transport.js.map +1 -0
  121. package/dist/providers/atlascloud-webhook-registry.d.ts +35 -0
  122. package/dist/providers/atlascloud-webhook-registry.d.ts.map +1 -0
  123. package/dist/providers/atlascloud-webhook-registry.js +104 -0
  124. package/dist/providers/atlascloud-webhook-registry.js.map +1 -0
  125. package/dist/providers/audio-mime.d.ts +11 -0
  126. package/dist/providers/audio-mime.d.ts.map +1 -0
  127. package/dist/providers/audio-mime.js +46 -0
  128. package/dist/providers/audio-mime.js.map +1 -0
  129. package/dist/providers/base-provider.d.ts +501 -32
  130. package/dist/providers/base-provider.d.ts.map +1 -1
  131. package/dist/providers/base-provider.js +1280 -87
  132. package/dist/providers/base-provider.js.map +1 -1
  133. package/dist/providers/cassette-provider.d.ts +217 -0
  134. package/dist/providers/cassette-provider.d.ts.map +1 -0
  135. package/dist/providers/cassette-provider.js +502 -0
  136. package/dist/providers/cassette-provider.js.map +1 -0
  137. package/dist/providers/cerebras-provider.d.ts +6 -12
  138. package/dist/providers/cerebras-provider.d.ts.map +1 -1
  139. package/dist/providers/cerebras-provider.js +8 -33
  140. package/dist/providers/cerebras-provider.js.map +1 -1
  141. package/dist/providers/claude-agent-provider.d.ts +107 -74
  142. package/dist/providers/claude-agent-provider.d.ts.map +1 -1
  143. package/dist/providers/claude-agent-provider.js +1279 -517
  144. package/dist/providers/claude-agent-provider.js.map +1 -1
  145. package/dist/providers/codex-provider.d.ts +84 -0
  146. package/dist/providers/codex-provider.d.ts.map +1 -0
  147. package/dist/providers/codex-provider.js +505 -0
  148. package/dist/providers/codex-provider.js.map +1 -0
  149. package/dist/providers/cohere-provider.d.ts +7 -3
  150. package/dist/providers/cohere-provider.d.ts.map +1 -1
  151. package/dist/providers/cohere-provider.js +14 -2
  152. package/dist/providers/cohere-provider.js.map +1 -1
  153. package/dist/providers/comfy-cloud-provider.d.ts +30 -0
  154. package/dist/providers/comfy-cloud-provider.d.ts.map +1 -0
  155. package/dist/providers/comfy-cloud-provider.js +42 -0
  156. package/dist/providers/comfy-cloud-provider.js.map +1 -0
  157. package/dist/providers/content-filter.d.ts +46 -0
  158. package/dist/providers/content-filter.d.ts.map +1 -0
  159. package/dist/providers/content-filter.js +92 -0
  160. package/dist/providers/content-filter.js.map +1 -0
  161. package/dist/providers/context-exceeded.d.ts +37 -0
  162. package/dist/providers/context-exceeded.d.ts.map +1 -0
  163. package/dist/providers/context-exceeded.js +114 -0
  164. package/dist/providers/context-exceeded.js.map +1 -0
  165. package/dist/providers/context-window.d.ts +44 -0
  166. package/dist/providers/context-window.d.ts.map +1 -0
  167. package/dist/providers/context-window.js +145 -0
  168. package/dist/providers/context-window.js.map +1 -0
  169. package/dist/providers/contract/index.d.ts +4 -0
  170. package/dist/providers/contract/index.d.ts.map +1 -0
  171. package/dist/providers/contract/index.js +4 -0
  172. package/dist/providers/contract/index.js.map +1 -0
  173. package/dist/providers/contract/probe-manifest.d.ts +75 -0
  174. package/dist/providers/contract/probe-manifest.d.ts.map +1 -0
  175. package/dist/providers/contract/probe-manifest.js +379 -0
  176. package/dist/providers/contract/probe-manifest.js.map +1 -0
  177. package/dist/providers/contract/probe-runner.d.ts +57 -0
  178. package/dist/providers/contract/probe-runner.d.ts.map +1 -0
  179. package/dist/providers/contract/probe-runner.js +180 -0
  180. package/dist/providers/contract/probe-runner.js.map +1 -0
  181. package/dist/providers/contract/redact.d.ts +29 -0
  182. package/dist/providers/contract/redact.d.ts.map +1 -0
  183. package/dist/providers/contract/redact.js +113 -0
  184. package/dist/providers/contract/redact.js.map +1 -0
  185. package/dist/providers/core-tools.d.ts +75 -0
  186. package/dist/providers/core-tools.d.ts.map +1 -0
  187. package/dist/providers/core-tools.js +128 -0
  188. package/dist/providers/core-tools.js.map +1 -0
  189. package/dist/providers/cost-calculator.d.ts +69 -50
  190. package/dist/providers/cost-calculator.d.ts.map +1 -1
  191. package/dist/providers/cost-calculator.js +207 -397
  192. package/dist/providers/cost-calculator.js.map +1 -1
  193. package/dist/providers/credential-check.d.ts +45 -0
  194. package/dist/providers/credential-check.d.ts.map +1 -0
  195. package/dist/providers/credential-check.js +164 -0
  196. package/dist/providers/credential-check.js.map +1 -0
  197. package/dist/providers/custom-model-kinds.d.ts +33 -0
  198. package/dist/providers/custom-model-kinds.d.ts.map +1 -0
  199. package/dist/providers/custom-model-kinds.js +96 -0
  200. package/dist/providers/custom-model-kinds.js.map +1 -0
  201. package/dist/providers/custom-openai-provider.d.ts +83 -0
  202. package/dist/providers/custom-openai-provider.d.ts.map +1 -0
  203. package/dist/providers/custom-openai-provider.js +190 -0
  204. package/dist/providers/custom-openai-provider.js.map +1 -0
  205. package/dist/providers/custom-provider-registry.d.ts +19 -0
  206. package/dist/providers/custom-provider-registry.d.ts.map +1 -0
  207. package/dist/providers/custom-provider-registry.js +52 -0
  208. package/dist/providers/custom-provider-registry.js.map +1 -0
  209. package/dist/providers/deepseek-provider.d.ts +8 -14
  210. package/dist/providers/deepseek-provider.d.ts.map +1 -1
  211. package/dist/providers/deepseek-provider.js +6 -35
  212. package/dist/providers/deepseek-provider.js.map +1 -1
  213. package/dist/providers/defaults.d.ts +12 -0
  214. package/dist/providers/defaults.d.ts.map +1 -0
  215. package/dist/providers/defaults.js +12 -0
  216. package/dist/providers/defaults.js.map +1 -0
  217. package/dist/providers/dreamina-models.d.ts +5 -0
  218. package/dist/providers/dreamina-models.d.ts.map +1 -0
  219. package/dist/providers/dreamina-models.js +57 -0
  220. package/dist/providers/dreamina-models.js.map +1 -0
  221. package/dist/providers/dreamina-music.d.ts +52 -0
  222. package/dist/providers/dreamina-music.d.ts.map +1 -0
  223. package/dist/providers/dreamina-music.js +115 -0
  224. package/dist/providers/dreamina-music.js.map +1 -0
  225. package/dist/providers/dreamina-provider.d.ts +62 -0
  226. package/dist/providers/dreamina-provider.d.ts.map +1 -0
  227. package/dist/providers/dreamina-provider.js +790 -0
  228. package/dist/providers/dreamina-provider.js.map +1 -0
  229. package/dist/providers/elevenlabs-provider.d.ts +50 -0
  230. package/dist/providers/elevenlabs-provider.d.ts.map +1 -0
  231. package/dist/providers/elevenlabs-provider.js +147 -0
  232. package/dist/providers/elevenlabs-provider.js.map +1 -0
  233. package/dist/providers/entity-references.d.ts +31 -0
  234. package/dist/providers/entity-references.d.ts.map +1 -0
  235. package/dist/providers/entity-references.js +113 -0
  236. package/dist/providers/entity-references.js.map +1 -0
  237. package/dist/providers/evolink-provider.d.ts +40 -0
  238. package/dist/providers/evolink-provider.d.ts.map +1 -0
  239. package/dist/providers/evolink-provider.js +312 -0
  240. package/dist/providers/evolink-provider.js.map +1 -0
  241. package/dist/providers/fake-provider.d.ts +74 -2
  242. package/dist/providers/fake-provider.d.ts.map +1 -1
  243. package/dist/providers/fake-provider.js +213 -1
  244. package/dist/providers/fake-provider.js.map +1 -1
  245. package/dist/providers/fal-generations.d.ts +106 -0
  246. package/dist/providers/fal-generations.d.ts.map +1 -0
  247. package/dist/providers/fal-generations.js +286 -0
  248. package/dist/providers/fal-generations.js.map +1 -0
  249. package/dist/providers/fal-provider.d.ts +173 -8
  250. package/dist/providers/fal-provider.d.ts.map +1 -1
  251. package/dist/providers/fal-provider.js +1396 -64
  252. package/dist/providers/fal-provider.js.map +1 -1
  253. package/dist/providers/fal-queue.d.ts +47 -0
  254. package/dist/providers/fal-queue.d.ts.map +1 -0
  255. package/dist/providers/fal-queue.js +231 -0
  256. package/dist/providers/fal-queue.js.map +1 -0
  257. package/dist/providers/gemini-provider.d.ts +150 -9
  258. package/dist/providers/gemini-provider.d.ts.map +1 -1
  259. package/dist/providers/gemini-provider.js +1515 -316
  260. package/dist/providers/gemini-provider.js.map +1 -1
  261. package/dist/providers/gmi-provider.d.ts +21 -0
  262. package/dist/providers/gmi-provider.d.ts.map +1 -0
  263. package/dist/providers/gmi-provider.js +34 -0
  264. package/dist/providers/gmi-provider.js.map +1 -0
  265. package/dist/providers/gpt-image-size.d.ts +3 -0
  266. package/dist/providers/gpt-image-size.d.ts.map +1 -0
  267. package/dist/providers/gpt-image-size.js +37 -0
  268. package/dist/providers/gpt-image-size.js.map +1 -0
  269. package/dist/providers/groq-provider.d.ts +9 -12
  270. package/dist/providers/groq-provider.d.ts.map +1 -1
  271. package/dist/providers/groq-provider.js +19 -33
  272. package/dist/providers/groq-provider.js.map +1 -1
  273. package/dist/providers/groq-request.d.ts +38 -0
  274. package/dist/providers/groq-request.d.ts.map +1 -0
  275. package/dist/providers/groq-request.js +138 -0
  276. package/dist/providers/groq-request.js.map +1 -0
  277. package/dist/providers/higgsfield-provider.d.ts +31 -0
  278. package/dist/providers/higgsfield-provider.d.ts.map +1 -0
  279. package/dist/providers/higgsfield-provider.js +143 -0
  280. package/dist/providers/higgsfield-provider.js.map +1 -0
  281. package/dist/providers/higgsfield-transport.d.ts +106 -0
  282. package/dist/providers/higgsfield-transport.d.ts.map +1 -0
  283. package/dist/providers/higgsfield-transport.js +182 -0
  284. package/dist/providers/higgsfield-transport.js.map +1 -0
  285. package/dist/providers/http-transport.d.ts +74 -0
  286. package/dist/providers/http-transport.d.ts.map +1 -0
  287. package/dist/providers/http-transport.js +152 -0
  288. package/dist/providers/http-transport.js.map +1 -0
  289. package/dist/providers/huggingface-provider.d.ts +73 -4
  290. package/dist/providers/huggingface-provider.d.ts.map +1 -1
  291. package/dist/providers/huggingface-provider.js +240 -34
  292. package/dist/providers/huggingface-provider.js.map +1 -1
  293. package/dist/providers/image-mime.d.ts +39 -0
  294. package/dist/providers/image-mime.d.ts.map +1 -0
  295. package/dist/providers/image-mime.js +111 -0
  296. package/dist/providers/image-mime.js.map +1 -0
  297. package/dist/providers/image-ref.d.ts +21 -0
  298. package/dist/providers/image-ref.d.ts.map +1 -0
  299. package/dist/providers/image-ref.js +44 -0
  300. package/dist/providers/image-ref.js.map +1 -0
  301. package/dist/providers/index.d.ts +87 -8
  302. package/dist/providers/index.d.ts.map +1 -1
  303. package/dist/providers/index.js +250 -56
  304. package/dist/providers/index.js.map +1 -1
  305. package/dist/providers/jina-provider.d.ts +12 -3
  306. package/dist/providers/jina-provider.d.ts.map +1 -1
  307. package/dist/providers/jina-provider.js +21 -7
  308. package/dist/providers/jina-provider.js.map +1 -1
  309. package/dist/providers/kie-provider.d.ts +172 -10
  310. package/dist/providers/kie-provider.d.ts.map +1 -1
  311. package/dist/providers/kie-provider.js +1190 -228
  312. package/dist/providers/kie-provider.js.map +1 -1
  313. package/dist/providers/kie-webhook-registry.d.ts +28 -0
  314. package/dist/providers/kie-webhook-registry.d.ts.map +1 -0
  315. package/dist/providers/kie-webhook-registry.js +91 -0
  316. package/dist/providers/kie-webhook-registry.js.map +1 -0
  317. package/dist/providers/llama-provider.d.ts +23 -36
  318. package/dist/providers/llama-provider.d.ts.map +1 -1
  319. package/dist/providers/llama-provider.js +50 -355
  320. package/dist/providers/llama-provider.js.map +1 -1
  321. package/dist/providers/lmstudio-provider.d.ts +4 -7
  322. package/dist/providers/lmstudio-provider.d.ts.map +1 -1
  323. package/dist/providers/lmstudio-provider.js +24 -15
  324. package/dist/providers/lmstudio-provider.js.map +1 -1
  325. package/dist/providers/loop-tracing.d.ts +17 -0
  326. package/dist/providers/loop-tracing.d.ts.map +1 -0
  327. package/dist/providers/loop-tracing.js +132 -0
  328. package/dist/providers/loop-tracing.js.map +1 -0
  329. package/dist/providers/manifest-models.d.ts +341 -1
  330. package/dist/providers/manifest-models.d.ts.map +1 -1
  331. package/dist/providers/manifest-models.js +1106 -22
  332. package/dist/providers/manifest-models.js.map +1 -1
  333. package/dist/providers/media-mime.d.ts +14 -0
  334. package/dist/providers/media-mime.d.ts.map +1 -0
  335. package/dist/providers/media-mime.js +35 -0
  336. package/dist/providers/media-mime.js.map +1 -0
  337. package/dist/providers/meshy-provider.d.ts +16 -2
  338. package/dist/providers/meshy-provider.d.ts.map +1 -1
  339. package/dist/providers/meshy-provider.js +82 -46
  340. package/dist/providers/meshy-provider.js.map +1 -1
  341. package/dist/providers/meta-provider.d.ts +22 -0
  342. package/dist/providers/meta-provider.d.ts.map +1 -0
  343. package/dist/providers/meta-provider.js +54 -0
  344. package/dist/providers/meta-provider.js.map +1 -0
  345. package/dist/providers/minimax-provider.d.ts +27 -18
  346. package/dist/providers/minimax-provider.d.ts.map +1 -1
  347. package/dist/providers/minimax-provider.js +222 -72
  348. package/dist/providers/minimax-provider.js.map +1 -1
  349. package/dist/providers/mistral-provider.d.ts +8 -12
  350. package/dist/providers/mistral-provider.d.ts.map +1 -1
  351. package/dist/providers/mistral-provider.js +15 -34
  352. package/dist/providers/mistral-provider.js.map +1 -1
  353. package/dist/providers/moonshot-provider.d.ts +11 -15
  354. package/dist/providers/moonshot-provider.d.ts.map +1 -1
  355. package/dist/providers/moonshot-provider.js +33 -23
  356. package/dist/providers/moonshot-provider.js.map +1 -1
  357. package/dist/providers/node-llama-cpp-provider.d.ts +99 -0
  358. package/dist/providers/node-llama-cpp-provider.d.ts.map +1 -0
  359. package/dist/providers/node-llama-cpp-provider.js +484 -0
  360. package/dist/providers/node-llama-cpp-provider.js.map +1 -0
  361. package/dist/providers/nodetool-provider.d.ts +49 -0
  362. package/dist/providers/nodetool-provider.d.ts.map +1 -0
  363. package/dist/providers/nodetool-provider.js +222 -0
  364. package/dist/providers/nodetool-provider.js.map +1 -0
  365. package/dist/providers/oauth/browser-launcher.d.ts +23 -0
  366. package/dist/providers/oauth/browser-launcher.d.ts.map +1 -0
  367. package/dist/providers/oauth/browser-launcher.js +45 -0
  368. package/dist/providers/oauth/browser-launcher.js.map +1 -0
  369. package/dist/providers/oauth/claude-code-credentials.d.ts +74 -0
  370. package/dist/providers/oauth/claude-code-credentials.d.ts.map +1 -0
  371. package/dist/providers/oauth/claude-code-credentials.js +133 -0
  372. package/dist/providers/oauth/claude-code-credentials.js.map +1 -0
  373. package/dist/providers/oauth/claude-code-login.d.ts +124 -0
  374. package/dist/providers/oauth/claude-code-login.d.ts.map +1 -0
  375. package/dist/providers/oauth/claude-code-login.js +230 -0
  376. package/dist/providers/oauth/claude-code-login.js.map +1 -0
  377. package/dist/providers/oauth/claude-code-oauth-client.d.ts +111 -0
  378. package/dist/providers/oauth/claude-code-oauth-client.d.ts.map +1 -0
  379. package/dist/providers/oauth/claude-code-oauth-client.js +207 -0
  380. package/dist/providers/oauth/claude-code-oauth-client.js.map +1 -0
  381. package/dist/providers/oauth/errors.d.ts +54 -0
  382. package/dist/providers/oauth/errors.d.ts.map +1 -0
  383. package/dist/providers/oauth/errors.js +79 -0
  384. package/dist/providers/oauth/errors.js.map +1 -0
  385. package/dist/providers/oauth/index.d.ts +105 -0
  386. package/dist/providers/oauth/index.d.ts.map +1 -0
  387. package/dist/providers/oauth/index.js +167 -0
  388. package/dist/providers/oauth/index.js.map +1 -0
  389. package/dist/providers/oauth/jwt-claims.d.ts +23 -0
  390. package/dist/providers/oauth/jwt-claims.d.ts.map +1 -0
  391. package/dist/providers/oauth/jwt-claims.js +67 -0
  392. package/dist/providers/oauth/jwt-claims.js.map +1 -0
  393. package/dist/providers/oauth/local-callback-server.d.ts +60 -0
  394. package/dist/providers/oauth/local-callback-server.d.ts.map +1 -0
  395. package/dist/providers/oauth/local-callback-server.js +148 -0
  396. package/dist/providers/oauth/local-callback-server.js.map +1 -0
  397. package/dist/providers/oauth/oauth-client.d.ts +55 -0
  398. package/dist/providers/oauth/oauth-client.d.ts.map +1 -0
  399. package/dist/providers/oauth/oauth-client.js +161 -0
  400. package/dist/providers/oauth/oauth-client.js.map +1 -0
  401. package/dist/providers/oauth/openai-oauth-provider.d.ts +129 -0
  402. package/dist/providers/oauth/openai-oauth-provider.d.ts.map +1 -0
  403. package/dist/providers/oauth/openai-oauth-provider.js +265 -0
  404. package/dist/providers/oauth/openai-oauth-provider.js.map +1 -0
  405. package/dist/providers/oauth/pkce-helper.d.ts +43 -0
  406. package/dist/providers/oauth/pkce-helper.d.ts.map +1 -0
  407. package/dist/providers/oauth/pkce-helper.js +57 -0
  408. package/dist/providers/oauth/pkce-helper.js.map +1 -0
  409. package/dist/providers/oauth/redaction.d.ts +22 -0
  410. package/dist/providers/oauth/redaction.d.ts.map +1 -0
  411. package/dist/providers/oauth/redaction.js +59 -0
  412. package/dist/providers/oauth/redaction.js.map +1 -0
  413. package/dist/providers/oauth/secure-credential-store.d.ts +67 -0
  414. package/dist/providers/oauth/secure-credential-store.d.ts.map +1 -0
  415. package/dist/providers/oauth/secure-credential-store.js +73 -0
  416. package/dist/providers/oauth/secure-credential-store.js.map +1 -0
  417. package/dist/providers/oauth/token-store.d.ts +53 -0
  418. package/dist/providers/oauth/token-store.d.ts.map +1 -0
  419. package/dist/providers/oauth/token-store.js +78 -0
  420. package/dist/providers/oauth/token-store.js.map +1 -0
  421. package/dist/providers/oauth/types.d.ts +64 -0
  422. package/dist/providers/oauth/types.d.ts.map +1 -0
  423. package/dist/providers/oauth/types.js +12 -0
  424. package/dist/providers/oauth/types.js.map +1 -0
  425. package/dist/providers/object-tracking.d.ts +47 -0
  426. package/dist/providers/object-tracking.d.ts.map +1 -0
  427. package/dist/providers/object-tracking.js +46 -0
  428. package/dist/providers/object-tracking.js.map +1 -0
  429. package/dist/providers/offline-model-index.d.ts +20 -0
  430. package/dist/providers/offline-model-index.d.ts.map +1 -0
  431. package/dist/providers/offline-model-index.js +112 -0
  432. package/dist/providers/offline-model-index.js.map +1 -0
  433. package/dist/providers/ollama-provider.d.ts +30 -2
  434. package/dist/providers/ollama-provider.d.ts.map +1 -1
  435. package/dist/providers/ollama-provider.js +198 -43
  436. package/dist/providers/ollama-provider.js.map +1 -1
  437. package/dist/providers/openai-compat/client.d.ts +57 -0
  438. package/dist/providers/openai-compat/client.d.ts.map +1 -0
  439. package/dist/providers/openai-compat/client.js +157 -0
  440. package/dist/providers/openai-compat/client.js.map +1 -0
  441. package/dist/providers/openai-compat/decode.d.ts +36 -0
  442. package/dist/providers/openai-compat/decode.d.ts.map +1 -0
  443. package/dist/providers/openai-compat/decode.js +55 -0
  444. package/dist/providers/openai-compat/decode.js.map +1 -0
  445. package/dist/providers/openai-compat/errors.d.ts +54 -0
  446. package/dist/providers/openai-compat/errors.d.ts.map +1 -0
  447. package/dist/providers/openai-compat/errors.js +144 -0
  448. package/dist/providers/openai-compat/errors.js.map +1 -0
  449. package/dist/providers/openai-compat/index.d.ts +8 -0
  450. package/dist/providers/openai-compat/index.d.ts.map +1 -0
  451. package/dist/providers/openai-compat/index.js +5 -0
  452. package/dist/providers/openai-compat/index.js.map +1 -0
  453. package/dist/providers/openai-compat/sse.d.ts +14 -0
  454. package/dist/providers/openai-compat/sse.d.ts.map +1 -0
  455. package/dist/providers/openai-compat/sse.js +69 -0
  456. package/dist/providers/openai-compat/sse.js.map +1 -0
  457. package/dist/providers/openai-compat/types.d.ts +75 -0
  458. package/dist/providers/openai-compat/types.d.ts.map +1 -0
  459. package/dist/providers/openai-compat/types.js +9 -0
  460. package/dist/providers/openai-compat/types.js.map +1 -0
  461. package/dist/providers/openai-compat-provider.d.ts +90 -0
  462. package/dist/providers/openai-compat-provider.d.ts.map +1 -0
  463. package/dist/providers/openai-compat-provider.js +282 -0
  464. package/dist/providers/openai-compat-provider.js.map +1 -0
  465. package/dist/providers/openai-provider.d.ts +98 -27
  466. package/dist/providers/openai-provider.d.ts.map +1 -1
  467. package/dist/providers/openai-provider.js +1037 -105
  468. package/dist/providers/openai-provider.js.map +1 -1
  469. package/dist/providers/openrouter-provider.d.ts +58 -13
  470. package/dist/providers/openrouter-provider.d.ts.map +1 -1
  471. package/dist/providers/openrouter-provider.js +383 -51
  472. package/dist/providers/openrouter-provider.js.map +1 -1
  473. package/dist/providers/provider-call-failure.d.ts +39 -0
  474. package/dist/providers/provider-call-failure.d.ts.map +1 -0
  475. package/dist/providers/provider-call-failure.js +132 -0
  476. package/dist/providers/provider-call-failure.js.map +1 -0
  477. package/dist/providers/provider-error.d.ts +59 -0
  478. package/dist/providers/provider-error.d.ts.map +1 -0
  479. package/dist/providers/provider-error.js +171 -0
  480. package/dist/providers/provider-error.js.map +1 -0
  481. package/dist/providers/provider-generations.d.ts +99 -0
  482. package/dist/providers/provider-generations.d.ts.map +1 -0
  483. package/dist/providers/provider-generations.js +71 -0
  484. package/dist/providers/provider-generations.js.map +1 -0
  485. package/dist/providers/provider-queue.d.ts +62 -0
  486. package/dist/providers/provider-queue.d.ts.map +1 -0
  487. package/dist/providers/provider-queue.js +28 -0
  488. package/dist/providers/provider-queue.js.map +1 -0
  489. package/dist/providers/provider-registry.d.ts +33 -14
  490. package/dist/providers/provider-registry.d.ts.map +1 -1
  491. package/dist/providers/provider-registry.js +85 -68
  492. package/dist/providers/provider-registry.js.map +1 -1
  493. package/dist/providers/provider-request-log.d.ts +53 -0
  494. package/dist/providers/provider-request-log.d.ts.map +1 -0
  495. package/dist/providers/provider-request-log.js +168 -0
  496. package/dist/providers/provider-request-log.js.map +1 -0
  497. package/dist/providers/provider-session.d.ts +3 -0
  498. package/dist/providers/provider-session.d.ts.map +1 -0
  499. package/dist/providers/provider-session.js +10 -0
  500. package/dist/providers/provider-session.js.map +1 -0
  501. package/dist/providers/provider-transport.d.ts +11 -0
  502. package/dist/providers/provider-transport.d.ts.map +1 -0
  503. package/dist/providers/provider-transport.js +11 -0
  504. package/dist/providers/provider-transport.js.map +1 -0
  505. package/dist/providers/python-provider.d.ts +26 -13
  506. package/dist/providers/python-provider.d.ts.map +1 -1
  507. package/dist/providers/python-provider.js +204 -25
  508. package/dist/providers/python-provider.js.map +1 -1
  509. package/dist/providers/replicate-provider.d.ts +91 -8
  510. package/dist/providers/replicate-provider.d.ts.map +1 -1
  511. package/dist/providers/replicate-provider.js +823 -65
  512. package/dist/providers/replicate-provider.js.map +1 -1
  513. package/dist/providers/replicate-retry.d.ts +30 -0
  514. package/dist/providers/replicate-retry.d.ts.map +1 -0
  515. package/dist/providers/replicate-retry.js +97 -0
  516. package/dist/providers/replicate-retry.js.map +1 -0
  517. package/dist/providers/requesty-provider.d.ts +30 -0
  518. package/dist/providers/requesty-provider.d.ts.map +1 -0
  519. package/dist/providers/requesty-provider.js +87 -0
  520. package/dist/providers/requesty-provider.js.map +1 -0
  521. package/dist/providers/responses-api.d.ts +27 -0
  522. package/dist/providers/responses-api.d.ts.map +1 -0
  523. package/dist/providers/responses-api.js +297 -0
  524. package/dist/providers/responses-api.js.map +1 -0
  525. package/dist/providers/reve-provider.d.ts +46 -0
  526. package/dist/providers/reve-provider.d.ts.map +1 -0
  527. package/dist/providers/reve-provider.js +165 -0
  528. package/dist/providers/reve-provider.js.map +1 -0
  529. package/dist/providers/rodin-provider.d.ts +25 -2
  530. package/dist/providers/rodin-provider.d.ts.map +1 -1
  531. package/dist/providers/rodin-provider.js +95 -51
  532. package/dist/providers/rodin-provider.js.map +1 -1
  533. package/dist/providers/safe-url.d.ts +70 -0
  534. package/dist/providers/safe-url.d.ts.map +1 -0
  535. package/dist/providers/safe-url.js +277 -0
  536. package/dist/providers/safe-url.js.map +1 -0
  537. package/dist/providers/scripted-provider.d.ts +9 -0
  538. package/dist/providers/scripted-provider.d.ts.map +1 -1
  539. package/dist/providers/scripted-provider.js +86 -10
  540. package/dist/providers/scripted-provider.js.map +1 -1
  541. package/dist/providers/structured-output.d.ts +42 -0
  542. package/dist/providers/structured-output.d.ts.map +1 -0
  543. package/dist/providers/structured-output.js +83 -0
  544. package/dist/providers/structured-output.js.map +1 -0
  545. package/dist/providers/together-operations.d.ts +27 -0
  546. package/dist/providers/together-operations.d.ts.map +1 -0
  547. package/dist/providers/together-operations.js +95 -0
  548. package/dist/providers/together-operations.js.map +1 -0
  549. package/dist/providers/together-provider.d.ts +18 -18
  550. package/dist/providers/together-provider.d.ts.map +1 -1
  551. package/dist/providers/together-provider.js +140 -367
  552. package/dist/providers/together-provider.js.map +1 -1
  553. package/dist/providers/topaz-provider.d.ts +45 -0
  554. package/dist/providers/topaz-provider.d.ts.map +1 -0
  555. package/dist/providers/topaz-provider.js +244 -0
  556. package/dist/providers/topaz-provider.js.map +1 -0
  557. package/dist/providers/types.d.ts +641 -4
  558. package/dist/providers/types.d.ts.map +1 -1
  559. package/dist/providers/types.js +81 -1
  560. package/dist/providers/types.js.map +1 -1
  561. package/dist/providers/useapi-provider.d.ts +28 -0
  562. package/dist/providers/useapi-provider.d.ts.map +1 -0
  563. package/dist/providers/useapi-provider.js +274 -0
  564. package/dist/providers/useapi-provider.js.map +1 -0
  565. package/dist/providers/video-duration.d.ts +6 -0
  566. package/dist/providers/video-duration.d.ts.map +1 -0
  567. package/dist/providers/video-duration.js +63 -0
  568. package/dist/providers/video-duration.js.map +1 -0
  569. package/dist/providers/video-frame-fallback.d.ts +39 -0
  570. package/dist/providers/video-frame-fallback.d.ts.map +1 -0
  571. package/dist/providers/video-frame-fallback.js +155 -0
  572. package/dist/providers/video-frame-fallback.js.map +1 -0
  573. package/dist/providers/video-frames.d.ts +61 -0
  574. package/dist/providers/video-frames.d.ts.map +1 -0
  575. package/dist/providers/video-frames.js +208 -0
  576. package/dist/providers/video-frames.js.map +1 -0
  577. package/dist/providers/video-mime.d.ts +14 -0
  578. package/dist/providers/video-mime.d.ts.map +1 -0
  579. package/dist/providers/video-mime.js +44 -0
  580. package/dist/providers/video-mime.js.map +1 -0
  581. package/dist/providers/video-to-audio.d.ts +34 -0
  582. package/dist/providers/video-to-audio.d.ts.map +1 -0
  583. package/dist/providers/video-to-audio.js +62 -0
  584. package/dist/providers/video-to-audio.js.map +1 -0
  585. package/dist/providers/vllm-provider.d.ts +20 -8
  586. package/dist/providers/vllm-provider.d.ts.map +1 -1
  587. package/dist/providers/vllm-provider.js +44 -23
  588. package/dist/providers/vllm-provider.js.map +1 -1
  589. package/dist/providers/voyage-provider.d.ts +12 -3
  590. package/dist/providers/voyage-provider.d.ts.map +1 -1
  591. package/dist/providers/voyage-provider.js +21 -7
  592. package/dist/providers/voyage-provider.js.map +1 -1
  593. package/dist/providers/xai-provider.d.ts +56 -13
  594. package/dist/providers/xai-provider.d.ts.map +1 -1
  595. package/dist/providers/xai-provider.js +307 -22
  596. package/dist/providers/xai-provider.js.map +1 -1
  597. package/dist/python-bridge-base.d.ts +358 -0
  598. package/dist/python-bridge-base.d.ts.map +1 -0
  599. package/dist/python-bridge-base.js +1557 -0
  600. package/dist/python-bridge-base.js.map +1 -0
  601. package/dist/python-bridge-codec.d.ts +28 -0
  602. package/dist/python-bridge-codec.d.ts.map +1 -0
  603. package/dist/python-bridge-codec.js +39 -0
  604. package/dist/python-bridge-codec.js.map +1 -0
  605. package/dist/python-bridge-factory.d.ts +52 -0
  606. package/dist/python-bridge-factory.d.ts.map +1 -0
  607. package/dist/python-bridge-factory.js +69 -0
  608. package/dist/python-bridge-factory.js.map +1 -0
  609. package/dist/python-bridge-framing.d.ts +68 -0
  610. package/dist/python-bridge-framing.d.ts.map +1 -0
  611. package/dist/python-bridge-framing.js +91 -0
  612. package/dist/python-bridge-framing.js.map +1 -0
  613. package/dist/python-bridge-types.d.ts +545 -2
  614. package/dist/python-bridge-types.d.ts.map +1 -1
  615. package/dist/python-bridge-types.js +49 -1
  616. package/dist/python-bridge-types.js.map +1 -1
  617. package/dist/python-graph-resolver.d.ts +57 -0
  618. package/dist/python-graph-resolver.d.ts.map +1 -0
  619. package/dist/python-graph-resolver.js +79 -0
  620. package/dist/python-graph-resolver.js.map +1 -0
  621. package/dist/python-node-executor.d.ts +36 -4
  622. package/dist/python-node-executor.d.ts.map +1 -1
  623. package/dist/python-node-executor.js +177 -86
  624. package/dist/python-node-executor.js.map +1 -1
  625. package/dist/python-stdio-bridge.d.ts +18 -37
  626. package/dist/python-stdio-bridge.d.ts.map +1 -1
  627. package/dist/python-stdio-bridge.js +148 -475
  628. package/dist/python-stdio-bridge.js.map +1 -1
  629. package/dist/python-websocket-bridge.d.ts +140 -0
  630. package/dist/python-websocket-bridge.d.ts.map +1 -0
  631. package/dist/python-websocket-bridge.js +561 -0
  632. package/dist/python-websocket-bridge.js.map +1 -0
  633. package/dist/python-worker-stderr.d.ts +4 -0
  634. package/dist/python-worker-stderr.d.ts.map +1 -0
  635. package/dist/python-worker-stderr.js +24 -0
  636. package/dist/python-worker-stderr.js.map +1 -0
  637. package/dist/recommended-models.d.ts +2 -2
  638. package/dist/recommended-models.d.ts.map +1 -1
  639. package/dist/recommended-models.js +44 -4
  640. package/dist/recommended-models.js.map +1 -1
  641. package/dist/recoverable-node-error.d.ts +31 -0
  642. package/dist/recoverable-node-error.d.ts.map +1 -0
  643. package/dist/recoverable-node-error.js +37 -0
  644. package/dist/recoverable-node-error.js.map +1 -0
  645. package/dist/redact-params.d.ts +13 -0
  646. package/dist/redact-params.d.ts.map +1 -0
  647. package/dist/redact-params.js +57 -0
  648. package/dist/redact-params.js.map +1 -0
  649. package/dist/run-context.d.ts +20 -0
  650. package/dist/run-context.d.ts.map +1 -0
  651. package/dist/run-context.js +18 -0
  652. package/dist/run-context.js.map +1 -0
  653. package/dist/run-trace-context.d.ts +30 -0
  654. package/dist/run-trace-context.d.ts.map +1 -0
  655. package/dist/run-trace-context.js +80 -0
  656. package/dist/run-trace-context.js.map +1 -0
  657. package/dist/run-trace-processor.d.ts +65 -0
  658. package/dist/run-trace-processor.d.ts.map +1 -0
  659. package/dist/run-trace-processor.js +417 -0
  660. package/dist/run-trace-processor.js.map +1 -0
  661. package/dist/run-trace-serialization.d.ts +5 -0
  662. package/dist/run-trace-serialization.d.ts.map +1 -0
  663. package/dist/run-trace-serialization.js +113 -0
  664. package/dist/run-trace-serialization.js.map +1 -0
  665. package/dist/sandbox-module-catalog.d.ts +43 -0
  666. package/dist/sandbox-module-catalog.d.ts.map +1 -0
  667. package/dist/sandbox-module-catalog.js +25 -0
  668. package/dist/sandbox-module-catalog.js.map +1 -0
  669. package/dist/storage-workspace.d.ts +62 -0
  670. package/dist/storage-workspace.d.ts.map +1 -0
  671. package/dist/storage-workspace.js +441 -0
  672. package/dist/storage-workspace.js.map +1 -0
  673. package/dist/swappable-python-bridge.d.ts +83 -0
  674. package/dist/swappable-python-bridge.d.ts.map +1 -0
  675. package/dist/swappable-python-bridge.js +246 -0
  676. package/dist/swappable-python-bridge.js.map +1 -0
  677. package/dist/telemetry.d.ts +16 -3
  678. package/dist/telemetry.d.ts.map +1 -1
  679. package/dist/telemetry.js +76 -20
  680. package/dist/telemetry.js.map +1 -1
  681. package/dist/testing.d.ts +81 -0
  682. package/dist/testing.d.ts.map +1 -0
  683. package/dist/testing.js +130 -0
  684. package/dist/testing.js.map +1 -0
  685. package/dist/token-counter.d.ts +30 -0
  686. package/dist/token-counter.d.ts.map +1 -0
  687. package/dist/token-counter.js +156 -0
  688. package/dist/token-counter.js.map +1 -0
  689. package/dist/trace-exporters.d.ts +11 -28
  690. package/dist/trace-exporters.d.ts.map +1 -1
  691. package/dist/trace-exporters.js +69 -22
  692. package/dist/trace-exporters.js.map +1 -1
  693. package/dist/tracing-helpers.d.ts +60 -26
  694. package/dist/tracing-helpers.d.ts.map +1 -1
  695. package/dist/tracing-helpers.js +100 -25
  696. package/dist/tracing-helpers.js.map +1 -1
  697. package/dist/trim-video-window.d.ts +8 -0
  698. package/dist/trim-video-window.d.ts.map +1 -0
  699. package/dist/trim-video-window.js +50 -0
  700. package/dist/trim-video-window.js.map +1 -0
  701. package/dist/turn-budget.d.ts +278 -0
  702. package/dist/turn-budget.d.ts.map +1 -0
  703. package/dist/turn-budget.js +365 -0
  704. package/dist/turn-budget.js.map +1 -0
  705. package/dist/variable-channel.d.ts +48 -0
  706. package/dist/variable-channel.d.ts.map +1 -0
  707. package/dist/variable-channel.js +110 -0
  708. package/dist/variable-channel.js.map +1 -0
  709. package/dist/workspace.d.ts +119 -0
  710. package/dist/workspace.d.ts.map +1 -0
  711. package/dist/workspace.js +112 -0
  712. package/dist/workspace.js.map +1 -0
  713. package/package.json +121 -19
@@ -1,7 +1,40 @@
1
+ import { context, trace } from "@opentelemetry/api";
2
+ import { tracedProviderLoop, tracedToolCall, beginProviderRound, providerRoundContext, tracedProviderLlm } from "./loop-tracing.js";
1
3
  import OpenAI, { toFile } from "openai";
2
- import { createLogger } from "@nodetool-ai/config";
3
- import { BaseProvider } from "./base-provider.js";
4
+ import { createLogger, importHidden } from "@nodetool-ai/config";
5
+ import { BaseProvider, budgetStopItem, resolveTurnBudget, splitToolResultImages } from "./base-provider.js";
6
+ import { openAIContextExceeded } from "./context-exceeded.js";
7
+ import { hashSystemPrompt } from "./provider-session.js";
8
+ import { sniffAudioMime } from "./audio-mime.js";
9
+ /** Extension for each mime `sniffAudioMime` can return; anything else is mp3. */
10
+ const AUDIO_MIME_EXT = {
11
+ "audio/wav": "wav",
12
+ "audio/ogg": "ogg",
13
+ "audio/flac": "flac",
14
+ "audio/mpeg": "mp3"
15
+ };
16
+ import { extractResponsesImages, extractResponsesText, extractResponsesToolCalls, messagesToResponsesInput, responseToolChoice, responseTools, responseUsage, streamResponsesEvents } from "./responses-api.js";
17
+ import { PROVIDER_IDS, isCallable, isNumber, isRecord, isString } from "@nodetool-ai/protocol";
18
+ import { isProviderMessageEvent, isProviderSessionUpdate, IMAGE_GENERATION_TOOL_NAME, PROVIDER_STOP_ABORTED, WEB_SEARCH_TOOL_NAME } from "./types.js";
19
+ async function loadSharp() {
20
+ const mod = await importHidden("sharp");
21
+ if (!mod)
22
+ throw new Error("sharp requires Node");
23
+ return "default" in mod ? mod.default : mod;
24
+ }
4
25
  const log = createLogger("nodetool.runtime.providers.openai");
26
+ import { snapToGptImage2Size } from "./gpt-image-size.js";
27
+ function openAIImageUsage(response, quality) {
28
+ return {
29
+ imageCount: response.data?.length ?? 1,
30
+ imageQuality: quality ?? undefined,
31
+ inputTokens: response.usage?.input_tokens,
32
+ outputTokens: response.usage?.output_tokens,
33
+ inputTextTokens: response.usage?.input_tokens_details?.text_tokens,
34
+ inputImageTokens: response.usage?.input_tokens_details?.image_tokens,
35
+ outputImageTokens: response.usage?.output_tokens_details?.image_tokens
36
+ };
37
+ }
5
38
  function asUint8Array(data) {
6
39
  if (data instanceof Uint8Array) {
7
40
  return data;
@@ -9,7 +42,7 @@ function asUint8Array(data) {
9
42
  if (Array.isArray(data) && data.every((v) => Number.isInteger(v))) {
10
43
  return new Uint8Array(data);
11
44
  }
12
- if (typeof data === "string") {
45
+ if (isString(data)) {
13
46
  return Uint8Array.from(Buffer.from(data, "base64"));
14
47
  }
15
48
  if (data instanceof ArrayBuffer) {
@@ -18,8 +51,8 @@ function asUint8Array(data) {
18
51
  throw new Error("Unsupported binary payload format");
19
52
  }
20
53
  function toInt16Samples(bytes) {
21
- const aligned = bytes.length % 2 === 0 ? bytes : bytes.slice(0, bytes.length - 1);
22
- return new Int16Array(aligned.buffer, aligned.byteOffset, aligned.byteLength / 2);
54
+ const copy = Uint8Array.from(bytes);
55
+ return new Int16Array(copy.buffer);
23
56
  }
24
57
  function parseDataUri(uri) {
25
58
  const idx = uri.indexOf(",");
@@ -43,9 +76,15 @@ function makeDataUri(mime, data) {
43
76
  return `data:${mime};base64,${b64}`;
44
77
  }
45
78
  /**
46
- * Custom JSON replacer that handles objects with toJSON() methods
47
- * and other non-serializable types. Mirrors Python's _default_serializer.
79
+ * OpenAI's Chat Completions `input_audio` block accepts only `"wav"` or `"mp3"`
80
+ * as its `format` — a bare token, not a mime type. Resolve it from the declared
81
+ * mime when present, otherwise sniff the leading magic bytes. Anything that
82
+ * isn't WAV is sent as `"mp3"` (the only other format OpenAI decodes).
48
83
  */
84
+ function openAiAudioFormat(mime, bytes) {
85
+ const resolved = mime && mime !== "application/octet-stream" ? mime : sniffAudioMime(bytes);
86
+ return /wave?$/i.test(resolved) ? "wav" : "mp3";
87
+ }
49
88
  function defaultSerializer(_key, value) {
50
89
  if (value === null || value === undefined)
51
90
  return value;
@@ -59,11 +98,118 @@ function defaultSerializer(_key, value) {
59
98
  return [...value];
60
99
  if (typeof value === "object" &&
61
100
  "toJSON" in value &&
62
- typeof value.toJSON === "function") {
101
+ isCallable(value.toJSON)) {
102
+ // SAFETY: `toJSON` is the JSON.stringify protocol hook — it exists on the
103
+ // value and, by that contract, produces a JSON-representable result.
63
104
  return value.toJSON();
64
105
  }
65
106
  return value;
66
107
  }
108
+ const RESPONSE_WEB_SEARCH_TOOL = {
109
+ type: "web_search"
110
+ };
111
+ const RESPONSE_IMAGE_GENERATION_TOOL = {
112
+ type: "image_generation"
113
+ };
114
+ const RESPONSE_HOSTED_TOOL_CHOICES = {
115
+ [WEB_SEARCH_TOOL_NAME]: "web_search",
116
+ [IMAGE_GENERATION_TOOL_NAME]: "image_generation"
117
+ };
118
+ const SORA_VIDEO_DURATIONS = [4, 8, 12, 16, 20];
119
+ export const OPENAI_FALLBACK_MODELS = [
120
+ "gpt-6-astra",
121
+ "gpt-5.6",
122
+ "gpt-5.6-sol",
123
+ "gpt-5.6-terra",
124
+ "gpt-5.6-luna",
125
+ "gpt-5.5",
126
+ "gpt-5.5-pro",
127
+ "gpt-5.4",
128
+ "gpt-5.4-pro",
129
+ "gpt-5.4-mini",
130
+ "gpt-5.4-nano",
131
+ "gpt-5",
132
+ "gpt-5-mini",
133
+ "gpt-5-nano"
134
+ ].map((id) => ({
135
+ id,
136
+ name: id,
137
+ provider: PROVIDER_IDS.OPENAI
138
+ }));
139
+ /**
140
+ * OpenAI language models we support: the gpt-5 family, served through the
141
+ * Responses API. Everything before gpt-5 (gpt-4o, gpt-4.1, the o-series, …) is
142
+ * retired. OpenAI-compatible subclasses set their own provider id and never
143
+ * reach this predicate, so they keep their own model support on Chat Completions.
144
+ */
145
+ function isOpenAIResponsesModel(model) {
146
+ const id = model.toLowerCase();
147
+ // Audio / realtime / transcription variants stay on Chat Completions — the
148
+ // Responses API doesn't serve the `modalities: ["text","audio"]` path.
149
+ if (/audio|realtime|transcribe/.test(id))
150
+ return false;
151
+ return /^gpt-5(?:[.-]|$)/.test(id);
152
+ }
153
+ /**
154
+ * Decode a `GET /v1/models` body into the language models it announces.
155
+ *
156
+ * Split out of {@link OpenAIProvider.getAvailableLanguageModels} so the live
157
+ * contract probe and its raw-response fixtures parse a real production
158
+ * response the same way the provider does. Strict on the envelope: a body
159
+ * without a `data` array throws, and the caller decides what to fall back to.
160
+ */
161
+ export function decodeOpenAIModelList(payload, options) {
162
+ if (!isRecord(payload)) {
163
+ throw new Error("OpenAI model list is not an object");
164
+ }
165
+ const rows = payload.data;
166
+ if (!Array.isArray(rows)) {
167
+ throw new Error("OpenAI model list has no `data` array");
168
+ }
169
+ return rows
170
+ .filter(isRecord)
171
+ .filter((row) => isString(row.id) && row.id.length > 0)
172
+ .filter((row) => !options.onlyResponsesModels || isOpenAIResponsesModel(row.id))
173
+ .map((row) => ({
174
+ id: row.id,
175
+ name: row.id,
176
+ provider: options.provider
177
+ }));
178
+ }
179
+ function responsesSystemPrompt(messages) {
180
+ return messages
181
+ .filter((m) => m.role === "system")
182
+ .map((m) => (isString(m.content) ? m.content : ""))
183
+ .filter(Boolean)
184
+ .join("\n\n");
185
+ }
186
+ /**
187
+ * A native image_generation result surfaced by the stream parser as an image
188
+ * chunk. Returns the content block to attach to the assistant message, or
189
+ * null for any other stream item. The multi-megabyte base64 blob must be
190
+ * absorbed here, never yielded onward as a chunk.
191
+ */
192
+ function imageContentFromChunk(item) {
193
+ if (!isRecord(item) ||
194
+ item.type !== "chunk" ||
195
+ item.content_type !== "image" ||
196
+ !isString(item.content)) {
197
+ return null;
198
+ }
199
+ const metadata = isRecord(item.content_metadata) ? item.content_metadata : {};
200
+ const mimeType = isString(metadata.mimeType)
201
+ ? metadata.mimeType
202
+ : "image/png";
203
+ return { type: "image_url", image: { data: item.content, mimeType } };
204
+ }
205
+ /** Text block (when non-empty) followed by generated-image blocks. */
206
+ function assistantContentWithImages(text, images) {
207
+ const blocks = [];
208
+ if (text)
209
+ blocks.push({ type: "text", text });
210
+ blocks.push(...images);
211
+ return blocks;
212
+ }
67
213
  export class OpenAIProvider extends BaseProvider {
68
214
  static requiredSecrets() {
69
215
  return ["OPENAI_API_KEY"];
@@ -73,7 +219,7 @@ export class OpenAIProvider extends BaseProvider {
73
219
  _clientFactory;
74
220
  _fetch;
75
221
  constructor(secrets, options = {}) {
76
- super("openai");
222
+ super(options.providerId ?? "openai");
77
223
  const apiKey = secrets.OPENAI_API_KEY;
78
224
  if (!apiKey) {
79
225
  throw new Error("OPENAI_API_KEY is required");
@@ -93,46 +239,155 @@ export class OpenAIProvider extends BaseProvider {
93
239
  }
94
240
  return this._client;
95
241
  }
242
+ /**
243
+ * True when this instance should serve `model` through the Responses API.
244
+ * Gated on the real `openai` provider so OpenAI-compatible subclasses (which
245
+ * pass their own provider id) stay on Chat Completions.
246
+ */
247
+ usesResponsesApi(model) {
248
+ return (this.provider === PROVIDER_IDS.OPENAI && isOpenAIResponsesModel(model));
249
+ }
250
+ get supportsNativeWebSearch() {
251
+ return this.provider === PROVIDER_IDS.OPENAI;
252
+ }
253
+ get supportsNativeImageGeneration() {
254
+ return this.provider === PROVIDER_IDS.OPENAI;
255
+ }
96
256
  async hasToolSupport(model) {
257
+ if (this.usesResponsesApi(model))
258
+ return true;
97
259
  return !(model.startsWith("o1") || model.startsWith("o3"));
98
260
  }
261
+ /**
262
+ * Whether this instance actually serves OpenAI's own model catalog.
263
+ *
264
+ * The media and embedding lists below are hardcoded OpenAI models tagged
265
+ * `provider: "openai"`. Subclasses inherit OpenAI's *chat dialect*, not its
266
+ * lineup — a Groq or vLLM instance offering `gpt-image-2` or `whisper-1` is
267
+ * always wrong, and picking one sends the request to a host that has no such
268
+ * endpoint. Subclasses that do serve media (Together, MiniMax, GMI, xAI, …)
269
+ * override these methods with their own catalog; everyone else correctly
270
+ * reports none.
271
+ */
272
+ servesOpenAICatalog() {
273
+ return this.provider === PROVIDER_IDS.OPENAI;
274
+ }
275
+ /**
276
+ * OpenAI's own media and embedding operations. A subclass serves them only
277
+ * through {@link servesOpenAICatalog}; one with its own catalog declares its
278
+ * own list.
279
+ */
280
+ declaredCapabilities() {
281
+ if (!this.servesOpenAICatalog())
282
+ return [];
283
+ return [
284
+ "text_to_image",
285
+ "image_to_image",
286
+ "inpainting",
287
+ "text_to_video",
288
+ "image_to_video",
289
+ "text_to_speech",
290
+ "automatic_speech_recognition",
291
+ "generate_embedding"
292
+ ];
293
+ }
99
294
  async getAvailableLanguageModels() {
295
+ // The `openai` provider only serves the supported (gpt-5+) models; retire
296
+ // everything older. Subclasses (their own provider id) keep their full list.
297
+ const isOpenAI = this.provider === PROVIDER_IDS.OPENAI;
100
298
  const response = await this._fetch("https://api.openai.com/v1/models", {
101
299
  headers: {
102
300
  Authorization: `Bearer ${this.apiKey}`
103
301
  }
104
302
  });
105
303
  if (!response.ok) {
106
- return [];
304
+ return isOpenAI ? OPENAI_FALLBACK_MODELS : [];
107
305
  }
108
- const payload = (await response.json());
109
- const rows = payload.data ?? [];
110
- return rows
111
- .filter((row) => typeof row.id === "string" && row.id.length > 0)
112
- .map((row) => ({
113
- id: row.id,
114
- name: row.id,
115
- provider: "openai"
116
- }));
306
+ let models;
307
+ try {
308
+ models = decodeOpenAIModelList(await response.json(), {
309
+ provider: this.provider,
310
+ onlyResponsesModels: isOpenAI
311
+ });
312
+ }
313
+ catch {
314
+ // A body OpenAI's own decoder rejects is the same situation as an
315
+ // unreachable listing: fall back rather than fail model discovery.
316
+ return isOpenAI ? OPENAI_FALLBACK_MODELS : [];
317
+ }
318
+ if (models.length === 0 && isOpenAI) {
319
+ return OPENAI_FALLBACK_MODELS;
320
+ }
321
+ return models;
117
322
  }
118
323
  async getAvailableTTSModels() {
324
+ if (!this.servesOpenAICatalog())
325
+ return [];
326
+ const legacyVoices = [
327
+ "alloy",
328
+ "ash",
329
+ "coral",
330
+ "echo",
331
+ "fable",
332
+ "onyx",
333
+ "nova",
334
+ "sage",
335
+ "shimmer"
336
+ ];
119
337
  return [
338
+ {
339
+ id: "gpt-4o-mini-tts",
340
+ name: "GPT-4o Mini TTS (Deprecated)",
341
+ provider: "openai",
342
+ voices: [
343
+ "alloy",
344
+ "ash",
345
+ "ballad",
346
+ "coral",
347
+ "echo",
348
+ "fable",
349
+ "nova",
350
+ "onyx",
351
+ "sage",
352
+ "shimmer",
353
+ "verse",
354
+ "marin",
355
+ "cedar"
356
+ ]
357
+ },
120
358
  {
121
359
  id: "tts-1",
122
360
  name: "TTS 1",
123
361
  provider: "openai",
124
- voices: ["alloy", "echo", "fable", "onyx", "nova", "shimmer"]
362
+ voices: legacyVoices
125
363
  },
126
364
  {
127
365
  id: "tts-1-hd",
128
366
  name: "TTS 1 HD",
129
367
  provider: "openai",
130
- voices: ["alloy", "echo", "fable", "onyx", "nova", "shimmer"]
368
+ voices: legacyVoices
131
369
  }
132
370
  ];
133
371
  }
134
372
  async getAvailableASRModels() {
373
+ if (!this.servesOpenAICatalog())
374
+ return [];
135
375
  return [
376
+ {
377
+ id: "gpt-4o-transcribe",
378
+ name: "GPT-4o Transcribe",
379
+ provider: "openai"
380
+ },
381
+ {
382
+ id: "gpt-4o-mini-transcribe",
383
+ name: "GPT-4o Mini Transcribe",
384
+ provider: "openai"
385
+ },
386
+ {
387
+ id: "gpt-4o-transcribe-diarize",
388
+ name: "GPT-4o Transcribe Diarize",
389
+ provider: "openai"
390
+ },
136
391
  {
137
392
  id: "whisper-1",
138
393
  name: "Whisper",
@@ -141,23 +396,45 @@ export class OpenAIProvider extends BaseProvider {
141
396
  ];
142
397
  }
143
398
  async getAvailableVideoModels() {
399
+ if (!this.servesOpenAICatalog())
400
+ return [];
144
401
  return [
145
402
  {
146
403
  id: "sora-2",
147
404
  name: "Sora 2",
148
405
  provider: "openai",
149
- supportedTasks: ["text_to_video"]
406
+ supportedTasks: ["text_to_video", "image_to_video"],
407
+ durations: [...SORA_VIDEO_DURATIONS],
408
+ resolutions: ["720p"],
409
+ aspectRatios: ["16:9", "9:16"]
150
410
  },
151
411
  {
152
412
  id: "sora-2-pro",
153
413
  name: "Sora 2 Pro",
154
414
  provider: "openai",
155
- supportedTasks: ["text_to_video"]
415
+ supportedTasks: ["text_to_video", "image_to_video"],
416
+ durations: [...SORA_VIDEO_DURATIONS],
417
+ resolutions: ["720p", "1024p", "1080p"],
418
+ aspectRatios: ["16:9", "9:16"]
156
419
  }
157
420
  ];
158
421
  }
159
422
  async getAvailableImageModels() {
423
+ if (!this.servesOpenAICatalog())
424
+ return [];
160
425
  return [
426
+ {
427
+ id: "gpt-image-2.5-flare",
428
+ name: "GPT Image 2.5 Flare",
429
+ provider: "openai",
430
+ supportedTasks: ["text_to_image", "image_to_image"]
431
+ },
432
+ {
433
+ id: "gpt-image-2.5-sunburst",
434
+ name: "GPT Image 2.5 Sunburst",
435
+ provider: "openai",
436
+ supportedTasks: ["text_to_image", "image_to_image"]
437
+ },
161
438
  {
162
439
  id: "gpt-image-2",
163
440
  name: "GPT Image 2",
@@ -185,6 +462,8 @@ export class OpenAIProvider extends BaseProvider {
185
462
  ];
186
463
  }
187
464
  async getAvailableEmbeddingModels() {
465
+ if (!this.servesOpenAICatalog())
466
+ return [];
188
467
  return [
189
468
  {
190
469
  id: "text-embedding-3-small",
@@ -206,10 +485,27 @@ export class OpenAIProvider extends BaseProvider {
206
485
  }
207
486
  ];
208
487
  }
209
- resolveImageSize(width, height) {
488
+ /**
489
+ * gpt-image-2 accepts arbitrary sizes within limits: both edges a multiple of
490
+ * 16, long edge <= 3840, aspect ratio <= 3:1, and total area between 0.64MP
491
+ * and 8.3MP. Requested sizes rarely land on that grid (16:9 at 1K is
492
+ * 1820x1024), so snap to the nearest valid size preserving the aspect ratio
493
+ * rather than dropping `size` and letting the API default to a square.
494
+ */
495
+ static snapToGptImage2Size(width, height) {
496
+ const size = snapToGptImage2Size(width, height);
497
+ return size ? `${size[0]}x${size[1]}` : null;
498
+ }
499
+ resolveImageSize(width, height, model) {
210
500
  if (!width || !height) {
211
501
  return null;
212
502
  }
503
+ const usesFlexibleDimensions = model === "gpt-image-2" ||
504
+ model?.startsWith("gpt-image-2-") === true ||
505
+ model?.startsWith("gpt-image-2.") === true;
506
+ if (usesFlexibleDimensions) {
507
+ return OpenAIProvider.snapToGptImage2Size(width, height);
508
+ }
213
509
  const supported = [
214
510
  [1024, 1024],
215
511
  [1024, 1536],
@@ -229,16 +525,24 @@ export class OpenAIProvider extends BaseProvider {
229
525
  : bestSoFar);
230
526
  return `${best[0]}x${best[1]}`;
231
527
  }
232
- static resolveVideoSize(aspectRatio, resolution) {
528
+ static resolveVideoSize(aspectRatio, resolution, model) {
233
529
  if (!resolution) {
234
530
  return null;
235
531
  }
236
- const supported = [
237
- [1280, 720],
238
- [1792, 1024],
239
- [720, 1280],
240
- [1024, 1792]
241
- ];
532
+ const isStandardSora2 = model?.startsWith("sora-2") === true && !model.includes("pro");
533
+ const supported = isStandardSora2
534
+ ? [
535
+ [1280, 720],
536
+ [720, 1280]
537
+ ]
538
+ : [
539
+ [1280, 720],
540
+ [1792, 1024],
541
+ [1920, 1080],
542
+ [720, 1280],
543
+ [1024, 1792],
544
+ [1080, 1920]
545
+ ];
242
546
  const aspect = (aspectRatio ?? "16:9").replaceAll(" ", "");
243
547
  const resolutionKey = resolution.toLowerCase();
244
548
  const presets = {
@@ -284,16 +588,18 @@ export class OpenAIProvider extends BaseProvider {
284
588
  return closest(Math.max(2, Math.round(targetW)), Math.max(2, Math.round(targetH)));
285
589
  }
286
590
  static secondsFromParams(params) {
591
+ const durationSeconds = params.durationSeconds;
592
+ if (durationSeconds != null &&
593
+ Number.isFinite(durationSeconds) &&
594
+ durationSeconds > 0) {
595
+ return SORA_VIDEO_DURATIONS.reduce((duration, candidate) => candidate <= durationSeconds ? candidate : duration, SORA_VIDEO_DURATIONS[0]);
596
+ }
287
597
  const numFrames = params.numFrames;
288
- if (!numFrames || numFrames <= 0) {
598
+ if (!numFrames || !Number.isFinite(numFrames) || numFrames <= 0) {
289
599
  return null;
290
600
  }
291
601
  const estimated = Math.ceil(numFrames / 24);
292
- if (estimated < 8)
293
- return 4;
294
- if (estimated < 12)
295
- return 8;
296
- return 12;
602
+ return SORA_VIDEO_DURATIONS.reduce((duration, candidate) => (candidate <= estimated ? candidate : duration), SORA_VIDEO_DURATIONS[0]);
297
603
  }
298
604
  static snapToValidVideoDimensions(width, height) {
299
605
  const supported = [
@@ -369,17 +675,38 @@ export class OpenAIProvider extends BaseProvider {
369
675
  }
370
676
  if (content.type === "audio") {
371
677
  const c = content;
372
- const base64 = c.audio.uri
373
- ? (await this.uriToBase64(c.audio.uri)).split(",", 2)[1]
374
- : Buffer.from(asUint8Array(c.audio.data ?? new Uint8Array())).toString("base64");
678
+ let bytes;
679
+ let mime = c.audio.mimeType;
680
+ if (c.audio.uri) {
681
+ const parsed = parseDataUri(await this.uriToBase64(c.audio.uri));
682
+ bytes = parsed.data;
683
+ mime = mime ?? parsed.mime;
684
+ }
685
+ else {
686
+ bytes = asUint8Array(c.audio.data ?? new Uint8Array());
687
+ }
375
688
  return {
376
689
  type: "input_audio",
377
690
  input_audio: {
378
- format: "mp3",
379
- data: base64
691
+ format: openAiAudioFormat(mime, bytes),
692
+ data: Buffer.from(bytes).toString("base64")
380
693
  }
381
694
  };
382
695
  }
696
+ if (content.type === "video") {
697
+ // Reached only with the fallback switched off: BaseProvider otherwise
698
+ // samples the clip into frames before conversion whenever
699
+ // `supportsVideoInput` is false, and a missing ffmpeg fails there with
700
+ // its own message. Named for the configured provider, not the wire
701
+ // format: every
702
+ // OpenAI-compatible re-host lands here, and reporting "openai" sent a
703
+ // diagnosis after the wrong provider.
704
+ throw new Error(`video input is not supported by ${this.provider} — its chat API is ` +
705
+ `OpenAI-compatible, which has no video content part. Frame sampling ` +
706
+ `is off (NODETOOL_VIDEO_FRAME_FALLBACK=0); unset it to read the clip ` +
707
+ `as stills, or use a provider whose chat models read video natively ` +
708
+ `(gemini).`);
709
+ }
383
710
  const c = content;
384
711
  const imageUrl = c.image.uri
385
712
  ? await this.uriToBase64(c.image.uri)
@@ -395,7 +722,7 @@ export class OpenAIProvider extends BaseProvider {
395
722
  throw new Error("Tool message requires toolCallId");
396
723
  }
397
724
  let content = "";
398
- if (typeof message.content === "string") {
725
+ if (isString(message.content)) {
399
726
  content = message.content;
400
727
  }
401
728
  else if (message.content != null) {
@@ -410,7 +737,7 @@ export class OpenAIProvider extends BaseProvider {
410
737
  if (message.role === "system") {
411
738
  return {
412
739
  role: "system",
413
- content: typeof message.content === "string" ? message.content : ""
740
+ content: isString(message.content) ? message.content : ""
414
741
  };
415
742
  }
416
743
  if (message.role === "assistant") {
@@ -422,24 +749,30 @@ export class OpenAIProvider extends BaseProvider {
422
749
  arguments: JSON.stringify(tc.args, defaultSerializer)
423
750
  }
424
751
  }));
425
- if (typeof message.content === "string" || message.content == null) {
426
- return {
752
+ if (isString(message.content) || message.content == null) {
753
+ const assistant = {
427
754
  role: "assistant",
428
- content: message.content,
429
- ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {})
755
+ content: message.content
430
756
  };
757
+ if (toolCalls.length > 0) {
758
+ assistant.tool_calls = toolCalls;
759
+ }
760
+ return assistant;
431
761
  }
432
762
  const parts = await Promise.all(message.content.map((part) => this.messageContentToOpenAIContentPart(part)));
433
- return {
763
+ const assistant = {
434
764
  role: "assistant",
435
- content: parts,
436
- ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {})
765
+ content: parts
437
766
  };
767
+ if (toolCalls.length > 0) {
768
+ assistant.tool_calls = toolCalls;
769
+ }
770
+ return assistant;
438
771
  }
439
772
  if (message.role !== "user") {
440
773
  throw new Error(`Unsupported role: ${message.role}`);
441
774
  }
442
- if (typeof message.content === "string") {
775
+ if (isString(message.content)) {
443
776
  return {
444
777
  role: "user",
445
778
  content: message.content
@@ -468,19 +801,31 @@ export class OpenAIProvider extends BaseProvider {
468
801
  });
469
802
  }
470
803
  convertSystemToUserForOModels(messages, model) {
471
- if (!model.startsWith("o")) {
804
+ // Match ONLY genuine OpenAI reasoning models (o1/o3/o4), not any id that
805
+ // merely starts with "o". Compat providers use namespaced ids like
806
+ // "openai/gpt-4o" or "openai/gpt-oss-120b" that begin with "o" but fully
807
+ // support the system role — mangling their system prompt corrupts every
808
+ // request. Strip a leading "vendor/" namespace before matching.
809
+ const bareModel = model.includes("/")
810
+ ? model.slice(model.lastIndexOf("/") + 1)
811
+ : model;
812
+ if (!/^o[134](-|$)/.test(bareModel)) {
472
813
  return messages;
473
814
  }
474
815
  return messages.map((msg) => msg.role === "system"
475
816
  ? {
476
817
  ...msg,
477
818
  role: "user",
478
- content: `Instructions: ${typeof msg.content === "string" ? msg.content : ""}`
819
+ content: `Instructions: ${isString(msg.content) ? msg.content : ""}`
479
820
  }
480
821
  : msg);
481
822
  }
482
823
  async *generateMessages(args) {
483
- const { model, tools = [], maxTokens = 16384, temperature, topP, presencePenalty, frequencyPenalty, audio } = args;
824
+ if (this.usesResponsesApi(args.model)) {
825
+ yield* this.generateMessagesResponses(args);
826
+ return;
827
+ }
828
+ const { model, tools = [], maxTokens = 16384, temperature, topP, presencePenalty, frequencyPenalty, audio, toolChoice } = args;
484
829
  const messages = this.convertSystemToUserForOModels(args.messages, model);
485
830
  const openaiMessages = await Promise.all(messages.map((m) => this.convertMessage(m)));
486
831
  const request = {
@@ -505,9 +850,19 @@ export class OpenAIProvider extends BaseProvider {
505
850
  }
506
851
  if (hasTools) {
507
852
  request.tools = this.formatTools(tools);
853
+ if (toolChoice) {
854
+ request.tool_choice =
855
+ toolChoice === "any"
856
+ ? "required"
857
+ : { type: "function", function: { name: toolChoice } };
858
+ }
508
859
  }
509
860
  log.debug("OpenAI request", { model });
510
- const stream = (await this.getClient().chat.completions.create(request));
861
+ this.recordRequestPayload(request);
862
+ // SAFETY: the request is assembled field by field as a dictionary, which
863
+ // the SDK's closed param interface does not overlap; `stream: true` on it
864
+ // selects the streaming overload.
865
+ const stream = (await this.getClient().chat.completions.create(request, { signal: args.signal }));
511
866
  const deltaToolCalls = new Map();
512
867
  try {
513
868
  for await (const chunk of stream) {
@@ -555,22 +910,34 @@ export class OpenAIProvider extends BaseProvider {
555
910
  };
556
911
  yield item;
557
912
  }
558
- if (choice.finish_reason === "tool_calls") {
913
+ if (choice.finish_reason && deltaToolCalls.size > 0) {
559
914
  for (const call of deltaToolCalls.values()) {
560
915
  const toolCall = this.buildToolCall(call.id, call.name, call.arguments);
561
916
  yield toolCall;
562
917
  }
563
918
  deltaToolCalls.clear();
564
919
  }
920
+ // Always emit a terminal `done: true` chunk when the completion
921
+ // finishes, regardless of reason. "stop" already emits one above; for
922
+ // every other terminal reason (tool_calls, length, content_filter)
923
+ // emit one here so consumers get a consistent end-of-stream marker
924
+ // (matching the Anthropic and Gemini providers).
925
+ if (choice.finish_reason && choice.finish_reason !== "stop") {
926
+ const doneChunk = { type: "chunk", content: "", done: true };
927
+ yield doneChunk;
928
+ }
565
929
  }
566
930
  }
567
931
  finally {
568
- if (typeof stream.close === "function") {
932
+ if (isCallable(stream.close)) {
569
933
  await stream.close();
570
934
  }
571
935
  }
572
936
  }
573
937
  async generateMessage(args) {
938
+ if (this.usesResponsesApi(args.model)) {
939
+ return this.generateMessageResponses(args);
940
+ }
574
941
  const { model, tools = [], toolChoice, maxTokens = 16384, temperature, topP, presencePenalty, frequencyPenalty } = args;
575
942
  const messages = this.convertSystemToUserForOModels(args.messages, model);
576
943
  const openaiMessages = await Promise.all(messages.map((m) => this.convertMessage(m)));
@@ -599,7 +966,10 @@ export class OpenAIProvider extends BaseProvider {
599
966
  }
600
967
  }
601
968
  log.debug("OpenAI request", { model });
602
- const completion = await this.getClient().chat.completions.create(request);
969
+ this.recordRequestPayload(request);
970
+ // SAFETY: as above — a dictionary request against the SDK's closed
971
+ // non-streaming param interface.
972
+ const completion = await this.getClient().chat.completions.create(request, { signal: args.signal });
603
973
  const choice = completion.choices?.[0];
604
974
  if (!choice) {
605
975
  throw new Error("OpenAI returned no choices");
@@ -624,6 +994,427 @@ export class OpenAIProvider extends BaseProvider {
624
994
  toolCalls
625
995
  };
626
996
  }
997
+ // ---------------------------------------------------------------------------
998
+ // Responses API path (used for OpenAI models matched by isOpenAIResponsesModel)
999
+ // ---------------------------------------------------------------------------
1000
+ /** Responses uses native equivalents by default, caller dispatch when restricted. */
1001
+ formatResponsesTools(tools, providedToolsOnly = false) {
1002
+ const formatted = [];
1003
+ for (const tool of tools) {
1004
+ if (!providedToolsOnly && tool.name === WEB_SEARCH_TOOL_NAME) {
1005
+ formatted.push(RESPONSE_WEB_SEARCH_TOOL);
1006
+ continue;
1007
+ }
1008
+ if (!providedToolsOnly && tool.name === IMAGE_GENERATION_TOOL_NAME) {
1009
+ formatted.push(RESPONSE_IMAGE_GENERATION_TOOL);
1010
+ continue;
1011
+ }
1012
+ const [functionTool] = responseTools([tool]);
1013
+ if (functionTool)
1014
+ formatted.push(functionTool);
1015
+ }
1016
+ return formatted;
1017
+ }
1018
+ async generateMessageResponses(args) {
1019
+ const request = await this.buildResponsesRequest(args, {
1020
+ input: await messagesToResponsesInput(args.messages, (uri) => this.resolveUri(uri)),
1021
+ stream: false,
1022
+ store: false
1023
+ });
1024
+ const client = this.getClient();
1025
+ this.recordRequestPayload(request);
1026
+ // SAFETY: the SDK's `create` is overloaded over a closed param interface,
1027
+ // while this request is assembled field by field; `stream: false` is set
1028
+ // on it, so the SDK resolves a response object, not an async iterable.
1029
+ const response = (await client.responses.create.call(client.responses, request, {
1030
+ signal: args.signal
1031
+ }));
1032
+ this.trackUsage(args.model, responseUsage(response));
1033
+ const outputText = isString(response.output_text)
1034
+ ? response.output_text
1035
+ : extractResponsesText(response.output);
1036
+ const toolCalls = extractResponsesToolCalls(response.output, this.buildToolCall.bind(this));
1037
+ const images = extractResponsesImages(response.output);
1038
+ const content = images.length > 0
1039
+ ? assistantContentWithImages(outputText, images)
1040
+ : outputText || null;
1041
+ return {
1042
+ role: "assistant",
1043
+ content,
1044
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
1045
+ };
1046
+ }
1047
+ async *generateMessagesResponses(args) {
1048
+ const input = await messagesToResponsesInput(args.messages, (uri) => this.resolveUri(uri));
1049
+ const request = await this.buildResponsesRequest(args, {
1050
+ input,
1051
+ stream: true,
1052
+ store: false
1053
+ });
1054
+ // Absorb native image results here too — this non-loop path must uphold
1055
+ // the same invariant as collectModelTurn: the base64 blob never leaks
1056
+ // outward as a chunk. The image rides a trailing assistant message event.
1057
+ const images = [];
1058
+ let assistantText = "";
1059
+ for await (const item of this.streamResponsesRequest(args, request)) {
1060
+ const image = imageContentFromChunk(item);
1061
+ if (image) {
1062
+ images.push(image);
1063
+ continue;
1064
+ }
1065
+ if (isRecord(item) &&
1066
+ item.type === "chunk" &&
1067
+ isString(item.content) &&
1068
+ !item.thinking) {
1069
+ assistantText += item.content;
1070
+ }
1071
+ yield item;
1072
+ }
1073
+ if (images.length > 0) {
1074
+ yield {
1075
+ type: "message",
1076
+ message: {
1077
+ role: "assistant",
1078
+ content: assistantContentWithImages(assistantText, images)
1079
+ }
1080
+ };
1081
+ }
1082
+ }
1083
+ async *generateLoop(args) {
1084
+ if (!this.usesResponsesApi(args.model)) {
1085
+ return yield* super.generateLoop(args);
1086
+ }
1087
+ return yield* tracedProviderLoop(this.provider, args.model, () => this.generateResponsesLoop(args));
1088
+ }
1089
+ async *generateResponsesLoop(args) {
1090
+ const maxIterations = args.maxIterations ?? 25;
1091
+ const { executeTool, maxIterations: _omitMaxIterations, sequentialTools, providedToolsOnly, turnBudget: budgetArg, resolveMedia, ...turnArgs } = args;
1092
+ const { runBudget, turnBudget } = resolveTurnBudget(budgetArg);
1093
+ const systemHash = hashSystemPrompt(responsesSystemPrompt(args.messages));
1094
+ const prior = args.providerSession ?? null;
1095
+ const canResume = prior != null &&
1096
+ prior.providerId === this.provider &&
1097
+ prior.model === args.model &&
1098
+ (prior.systemHash == null || prior.systemHash === systemHash) &&
1099
+ args.messages.length > prior.checkpoint;
1100
+ if (canResume && prior) {
1101
+ const firstMessages = args.messages.slice(prior.checkpoint);
1102
+ const firstTurnState = this.createResponsesTurnState(prior.token);
1103
+ try {
1104
+ yield* this.runResponsesLoop({
1105
+ args: turnArgs,
1106
+ resolveMedia,
1107
+ executeTool,
1108
+ providedToolsOnly,
1109
+ sequentialTools: sequentialTools === true,
1110
+ maxIterations,
1111
+ firstMessages,
1112
+ firstPreviousResponseId: prior.token,
1113
+ systemHash,
1114
+ firstTurnState,
1115
+ turnBudget,
1116
+ runBudget
1117
+ });
1118
+ return;
1119
+ }
1120
+ catch (err) {
1121
+ if (firstTurnState.emittedContent) {
1122
+ throw err;
1123
+ }
1124
+ log.warn("OpenAI Responses resume failed; starting fresh", {
1125
+ error: err instanceof Error ? err.message : String(err)
1126
+ });
1127
+ }
1128
+ }
1129
+ const freshMessages = args.loadFullHistory
1130
+ ? await args.loadFullHistory()
1131
+ : args.messages;
1132
+ yield* this.runResponsesLoop({
1133
+ args: turnArgs,
1134
+ resolveMedia,
1135
+ executeTool,
1136
+ providedToolsOnly,
1137
+ sequentialTools: sequentialTools === true,
1138
+ maxIterations,
1139
+ firstMessages: freshMessages,
1140
+ firstPreviousResponseId: null,
1141
+ systemHash,
1142
+ firstTurnState: this.createResponsesTurnState(null),
1143
+ turnBudget,
1144
+ runBudget
1145
+ });
1146
+ }
1147
+ async buildResponsesRequest(args, config) {
1148
+ const request = {
1149
+ model: args.model,
1150
+ input: config.input,
1151
+ stream: config.stream,
1152
+ store: config.store
1153
+ };
1154
+ if (config.previousResponseId) {
1155
+ request.previous_response_id = config.previousResponseId;
1156
+ }
1157
+ if (args.maxTokens != null)
1158
+ request.max_output_tokens = args.maxTokens;
1159
+ if (args.temperature != null)
1160
+ request.temperature = args.temperature;
1161
+ if (args.topP != null)
1162
+ request.top_p = args.topP;
1163
+ const tools = this.formatResponsesTools(args.tools ?? [], config.providedToolsOnly);
1164
+ if (tools.length > 0) {
1165
+ request.tools = tools;
1166
+ const hostedToolChoice = !config.providedToolsOnly && args.toolChoice
1167
+ ? RESPONSE_HOSTED_TOOL_CHOICES[args.toolChoice]
1168
+ : undefined;
1169
+ request.tool_choice = hostedToolChoice
1170
+ ? { type: hostedToolChoice }
1171
+ : (responseToolChoice(args.toolChoice) ?? "auto");
1172
+ }
1173
+ return request;
1174
+ }
1175
+ async *streamResponsesRequest(args, request, onResponseId) {
1176
+ const client = this.getClient();
1177
+ this.recordRequestPayload(request);
1178
+ // SAFETY: as in `generateMessageResponses` — an overloaded SDK method over
1179
+ // a closed param interface, given a request assembled field by field. The
1180
+ // caller set `stream: true`, so the SDK resolves an async iterable.
1181
+ const stream = (await client.responses.create.call(client.responses, request, {
1182
+ signal: args.signal
1183
+ }));
1184
+ yield* streamResponsesEvents(stream, {
1185
+ model: args.model,
1186
+ buildToolCall: this.buildToolCall.bind(this),
1187
+ onUsage: (model, usage) => {
1188
+ const cost = this.trackUsage(model, usage);
1189
+ const span = trace.getActiveSpan();
1190
+ span?.setAttributes({
1191
+ "gen_ai.usage.input_tokens": usage.inputTokens ?? 0,
1192
+ "gen_ai.usage.output_tokens": usage.outputTokens ?? 0,
1193
+ "gen_ai.usage.total_tokens": (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0),
1194
+ });
1195
+ if (usage.cachedTokens !== undefined) {
1196
+ span?.setAttribute("gen_ai.usage.cached_input_tokens", usage.cachedTokens);
1197
+ }
1198
+ if (this.unpricedReason === null) {
1199
+ span?.setAttribute("gen_ai.usage.cost_usd", cost);
1200
+ }
1201
+ return cost;
1202
+ },
1203
+ onResponseId
1204
+ });
1205
+ }
1206
+ createResponsesTurnState(responseId) {
1207
+ return {
1208
+ assistantText: "",
1209
+ images: [],
1210
+ pending: [],
1211
+ responseId,
1212
+ emittedContent: false
1213
+ };
1214
+ }
1215
+ async *runResponsesLoop(config) {
1216
+ const toolMap = new Map((config.args.tools ?? []).map((tool) => [tool.name, tool]));
1217
+ let previousResponseId = config.firstPreviousResponseId;
1218
+ const toInput = async (messages) => messagesToResponsesInput(config.resolveMedia ? await config.resolveMedia(messages) : messages, (uri) => this.resolveUri(uri));
1219
+ let input = await toInput(config.firstMessages);
1220
+ let state = config.firstTurnState;
1221
+ // Responses carries prior turns server-side behind `previousResponseId`,
1222
+ // so `input` holds only the delta. Reserving against the delta would price
1223
+ // a turn as if the conversation restarted; the transcript is tracked here
1224
+ // so the reservation grows with the turn it is admitting.
1225
+ const transcript = [...config.firstMessages];
1226
+ const runBudget = config.runBudget ?? null;
1227
+ for (let iteration = 0; iteration < config.maxIterations; iteration++) {
1228
+ if (config.args.signal?.aborted) {
1229
+ yield PROVIDER_STOP_ABORTED;
1230
+ return;
1231
+ }
1232
+ if (runBudget?.deadline.expired()) {
1233
+ yield budgetStopItem(runBudget);
1234
+ return;
1235
+ }
1236
+ let reservation = null;
1237
+ if (config.turnBudget) {
1238
+ reservation = this._admitTurn(config.turnBudget, config.args.model, transcript);
1239
+ if (reservation === null) {
1240
+ yield budgetStopItem(runBudget);
1241
+ return;
1242
+ }
1243
+ }
1244
+ beginProviderRound(this.provider, config.args.model);
1245
+ const costBeforeTurn = config.turnBudget ? this.getTotalCost() : 0;
1246
+ // The reservation covers exactly one turn, so it has to be reconciled
1247
+ // even when that turn throws. Left outstanding, a single provider error
1248
+ // would shrink the run's headroom for good — the budget is run-level and
1249
+ // outlives the decision that hit the error.
1250
+ try {
1251
+ const request = await this.buildResponsesRequest(config.args, {
1252
+ input,
1253
+ stream: true,
1254
+ store: true,
1255
+ previousResponseId,
1256
+ providedToolsOnly: config.providedToolsOnly
1257
+ });
1258
+ const llm = context.with(providerRoundContext(), () => tracedProviderLlm(this.provider, config.args.model, transcript, () => this.collectResponsesTurn(config.args, request, config.systemHash, state)));
1259
+ yield* llm;
1260
+ }
1261
+ finally {
1262
+ if (config.turnBudget && reservation) {
1263
+ config.turnBudget.commit(reservation, this.getTotalCost() - costBeforeTurn);
1264
+ }
1265
+ }
1266
+ previousResponseId = state.responseId;
1267
+ // Native image_generation results ride the assistant message as image
1268
+ // content and don't affect pending-tool semantics.
1269
+ const assistantContent = state.images.length > 0
1270
+ ? assistantContentWithImages(state.assistantText, state.images)
1271
+ : state.assistantText || null;
1272
+ const assistantMsg = {
1273
+ role: "assistant",
1274
+ content: assistantContent,
1275
+ toolCalls: state.pending.length > 0 ? state.pending : null
1276
+ };
1277
+ yield { type: "message", message: assistantMsg };
1278
+ transcript.push(assistantMsg);
1279
+ if (state.pending.length === 0) {
1280
+ return;
1281
+ }
1282
+ if (!previousResponseId) {
1283
+ throw new Error("OpenAI Responses returned tool calls without a response id");
1284
+ }
1285
+ const toolMessages = [];
1286
+ const runTool = async (tc) => {
1287
+ // Per-tool error isolation: a thrown tool must still yield a
1288
+ // tool_result so a parallel Promise.all rejection can't discard sibling
1289
+ // results and leave a dangling tool_use (rejected by the API next turn).
1290
+ try {
1291
+ const tool = toolMap.get(tc.name);
1292
+ if (tool?.execute) {
1293
+ const execute = tool.execute;
1294
+ return await tracedToolCall(tc, () => execute(tc.args ?? {}, tc.id));
1295
+ }
1296
+ if (config.executeTool) {
1297
+ const execute = config.executeTool;
1298
+ return await tracedToolCall(tc, () => execute(tc));
1299
+ }
1300
+ return `Tool "${tc.name}" is not available`;
1301
+ }
1302
+ catch (err) {
1303
+ return `Error executing tool "${tc.name}": ${err instanceof Error ? err.message : String(err)}`;
1304
+ }
1305
+ };
1306
+ const emitToolResult = function* (tc, content) {
1307
+ const { toolContent, imageMessage } = splitToolResultImages(content);
1308
+ const toolMsg = {
1309
+ role: "tool",
1310
+ toolCallId: tc.id,
1311
+ content: toolContent
1312
+ };
1313
+ toolMessages.push(toolMsg);
1314
+ transcript.push(toolMsg);
1315
+ yield { type: "message", message: toolMsg };
1316
+ if (imageMessage) {
1317
+ toolMessages.push(imageMessage);
1318
+ // It rides the next turn's input, so it is part of what that turn
1319
+ // is billed for — and therefore part of what its reservation covers.
1320
+ transcript.push(imageMessage);
1321
+ }
1322
+ };
1323
+ let terminated = false;
1324
+ if (config.sequentialTools) {
1325
+ for (const tc of state.pending) {
1326
+ // Honor a mid-turn abort (mirrors base-provider generateLoop): a tool
1327
+ // like finish_step aborts the signal to stop the model running any
1328
+ // further tools THIS turn. Without this check the Responses loop would
1329
+ // keep executing pending tools after completion — duplicate
1330
+ // step_result/StepCompleted events, overwritten memory, and stray
1331
+ // side-effecting tools after the step is already done.
1332
+ if (config.args.signal?.aborted)
1333
+ break;
1334
+ const content = await runTool(tc);
1335
+ yield* emitToolResult(tc, content);
1336
+ const tool = toolMap.get(tc.name);
1337
+ if (tool?.terminal) {
1338
+ terminated = true;
1339
+ break;
1340
+ }
1341
+ }
1342
+ }
1343
+ else {
1344
+ const results = await Promise.all(state.pending.map(async (tc) => ({
1345
+ tc,
1346
+ content: await runTool(tc)
1347
+ })));
1348
+ for (const { tc, content } of results) {
1349
+ yield* emitToolResult(tc, content);
1350
+ const tool = toolMap.get(tc.name);
1351
+ if (tool?.terminal)
1352
+ terminated = true;
1353
+ }
1354
+ }
1355
+ input = await toInput(toolMessages);
1356
+ if (terminated)
1357
+ return;
1358
+ state = this.createResponsesTurnState(previousResponseId);
1359
+ }
1360
+ yield { type: "chunk", content: "", done: true };
1361
+ yield {
1362
+ type: "stop",
1363
+ reason: "iterations",
1364
+ detail: `stopped after ${config.maxIterations} tool-calling round(s)`
1365
+ };
1366
+ }
1367
+ async *collectResponsesTurn(args, request, systemHash, state) {
1368
+ const sessionUpdates = [];
1369
+ const enqueueSession = (responseId) => {
1370
+ if (state.responseId === responseId)
1371
+ return;
1372
+ state.responseId = responseId;
1373
+ const session = {
1374
+ providerId: this.provider,
1375
+ model: args.model,
1376
+ token: responseId,
1377
+ checkpoint: args.messages.length,
1378
+ systemHash
1379
+ };
1380
+ sessionUpdates.push({ type: "session", session });
1381
+ };
1382
+ const flushSessions = function* () {
1383
+ while (sessionUpdates.length > 0) {
1384
+ const update = sessionUpdates.shift();
1385
+ if (update)
1386
+ yield update;
1387
+ }
1388
+ };
1389
+ for await (const item of this.streamResponsesRequest(args, request, enqueueSession)) {
1390
+ yield* flushSessions();
1391
+ if (isProviderSessionUpdate(item) || isProviderMessageEvent(item)) {
1392
+ yield item;
1393
+ continue;
1394
+ }
1395
+ if ("id" in item && "name" in item && "args" in item) {
1396
+ state.pending.push(item);
1397
+ state.emittedContent = true;
1398
+ yield item;
1399
+ continue;
1400
+ }
1401
+ if (isRecord(item) && item.type === "chunk") {
1402
+ const image = imageContentFromChunk(item);
1403
+ if (image) {
1404
+ state.images.push(image);
1405
+ state.emittedContent = true;
1406
+ continue;
1407
+ }
1408
+ if (isString(item.content) && !item.thinking) {
1409
+ state.assistantText += item.content;
1410
+ if (item.content)
1411
+ state.emittedContent = true;
1412
+ }
1413
+ yield item;
1414
+ }
1415
+ }
1416
+ yield* flushSessions();
1417
+ }
627
1418
  async textToImage(params) {
628
1419
  if (!params.prompt) {
629
1420
  throw new Error("The input prompt cannot be empty.");
@@ -635,12 +1426,14 @@ export class OpenAIProvider extends BaseProvider {
635
1426
  model: params.model.id,
636
1427
  prompt
637
1428
  };
638
- const size = this.resolveImageSize(params.width ?? undefined, params.height ?? undefined);
1429
+ const size = this.resolveImageSize(params.width ?? undefined, params.height ?? undefined, params.model.id);
639
1430
  if (size)
640
1431
  request.size = size;
641
1432
  if (params.quality)
642
1433
  request.quality = params.quality;
643
- const response = (await this.getClient().images.generate(request));
1434
+ // SAFETY: a dictionary request against the SDK's closed image params.
1435
+ const response = (await this.getClient().images.generate(request, { signal: params.signal }));
1436
+ this.trackUsage(params.model.id, openAIImageUsage(response, params.quality));
644
1437
  const item = response.data?.[0];
645
1438
  if (!item) {
646
1439
  throw new Error("OpenAI image generation returned no image data.");
@@ -657,8 +1450,40 @@ export class OpenAIProvider extends BaseProvider {
657
1450
  }
658
1451
  throw new Error("OpenAI image generation returned no image data.");
659
1452
  }
660
- async imageToImage(image, params) {
661
- if (!image || image.length === 0) {
1453
+ /**
1454
+ * Convert a white-region mask (R=G=B=255, alpha = selection) into the mask
1455
+ * OpenAI's edit endpoint expects, where fully transparent pixels mark the
1456
+ * region to regenerate. Inverts the alpha channel and keeps RGBA shape.
1457
+ */
1458
+ async toOpenAiEditMask(mask) {
1459
+ const sharp = await loadSharp();
1460
+ const { data, info } = await sharp(Buffer.from(mask))
1461
+ .ensureAlpha()
1462
+ .raw()
1463
+ .toBuffer({ resolveWithObject: true });
1464
+ for (let i = 3; i < data.length; i += 4) {
1465
+ data[i] = 255 - data[i];
1466
+ }
1467
+ const png = await sharp(data, {
1468
+ raw: { width: info.width, height: info.height, channels: 4 }
1469
+ })
1470
+ .png()
1471
+ .toBuffer();
1472
+ return new Uint8Array(png);
1473
+ }
1474
+ async imageToImage(images, params) {
1475
+ return this.runImageEdit(images, params);
1476
+ }
1477
+ async inpaint(images, params) {
1478
+ return this.runImageEdit(images, params, params.mask);
1479
+ }
1480
+ /**
1481
+ * Shared OpenAI image-edit call for both image-to-image and inpaint. When
1482
+ * `mask` is supplied the masked region is regenerated.
1483
+ */
1484
+ async runImageEdit(images, params, mask) {
1485
+ const sources = images.filter((b) => b && b.length > 0);
1486
+ if (sources.length === 0) {
662
1487
  throw new Error("image must not be empty.");
663
1488
  }
664
1489
  if (!params.prompt) {
@@ -667,19 +1492,32 @@ export class OpenAIProvider extends BaseProvider {
667
1492
  const prompt = params.negativePrompt
668
1493
  ? `${params.prompt.trim()}\n\nDo not include: ${params.negativePrompt.trim()}`
669
1494
  : params.prompt;
1495
+ // gpt-image-* accepts multiple reference images as an array; fall back to a
1496
+ // single file for one input so older edit models keep working.
1497
+ const imageFiles = await Promise.all(sources.map((b, i) => toFile(Buffer.from(b), `image_${i}.png`, { type: "image/png" })));
670
1498
  const request = {
671
1499
  model: params.model.id,
672
- image: await toFile(Buffer.from(image), "image.png", {
673
- type: "image/png"
674
- }),
1500
+ image: imageFiles.length === 1 ? imageFiles[0] : imageFiles,
675
1501
  prompt
676
1502
  };
677
- const size = this.resolveImageSize(params.targetWidth ?? undefined, params.targetHeight ?? undefined);
1503
+ // Inpainting: the edit endpoint marks the region to regenerate with the
1504
+ // mask's *transparent* pixels. Our masks use the opposite convention
1505
+ // (opaque/white alpha = edit region, matching FAL), so invert the alpha
1506
+ // channel before sending.
1507
+ if (mask && mask.length > 0) {
1508
+ const openAiMask = await this.toOpenAiEditMask(mask);
1509
+ request.mask = await toFile(Buffer.from(openAiMask), "mask.png", {
1510
+ type: "image/png"
1511
+ });
1512
+ }
1513
+ const size = this.resolveImageSize(params.targetWidth ?? undefined, params.targetHeight ?? undefined, params.model.id);
678
1514
  if (size)
679
1515
  request.size = size;
680
1516
  if (params.quality)
681
1517
  request.quality = params.quality;
682
- const response = (await this.getClient().images.edit(request));
1518
+ // SAFETY: a dictionary request against the SDK's closed edit params.
1519
+ const response = (await this.getClient().images.edit(request, { signal: params.signal }));
1520
+ this.trackUsage(params.model.id, openAIImageUsage(response, params.quality));
683
1521
  const item = response.data?.[0];
684
1522
  if (!item) {
685
1523
  throw new Error("OpenAI image editing returned no image data.");
@@ -704,29 +1542,58 @@ export class OpenAIProvider extends BaseProvider {
704
1542
  const speed = Math.max(0.25, Math.min(4.0, args.speed ?? 1.0));
705
1543
  const speechApi = this.getClient().audio.speech;
706
1544
  if (speechApi.with_streaming_response?.create) {
707
- const response = await speechApi.with_streaming_response.create({
1545
+ const request = {
708
1546
  model: args.model,
709
1547
  input: args.text,
710
1548
  voice,
711
1549
  speed,
712
1550
  response_format: "pcm"
713
- });
1551
+ };
1552
+ if (args.model === "gpt-4o-mini-tts" && (args.language || args.instructions)) {
1553
+ request.instructions = [
1554
+ args.language ? `Speak in ${args.language}.` : undefined,
1555
+ args.instructions
1556
+ ]
1557
+ .filter((value) => value !== undefined)
1558
+ .join(" ");
1559
+ }
1560
+ const response = await speechApi.with_streaming_response.create(request);
1561
+ // TTS is billed per input character, not per token.
1562
+ this.trackUsage(args.model, { inputCharacters: args.text.length });
1563
+ let carry;
714
1564
  for await (const chunk of response.iterBytes(4096)) {
715
- const bytes = asUint8Array(chunk);
716
- yield { samples: toInt16Samples(bytes) };
1565
+ const incoming = asUint8Array(chunk);
1566
+ const bytes = new Uint8Array(incoming.length + (carry == null ? 0 : 1));
1567
+ if (carry != null)
1568
+ bytes[0] = carry;
1569
+ bytes.set(incoming, carry == null ? 0 : 1);
1570
+ const completeLength = bytes.length - (bytes.length % 2);
1571
+ carry =
1572
+ completeLength < bytes.length ? bytes[bytes.length - 1] : undefined;
1573
+ if (completeLength > 0) {
1574
+ yield { samples: toInt16Samples(bytes.subarray(0, completeLength)) };
1575
+ }
717
1576
  }
718
1577
  return;
719
1578
  }
720
- const response = await speechApi.create({
1579
+ const request = {
721
1580
  model: args.model,
722
1581
  input: args.text,
723
1582
  voice,
724
1583
  speed,
725
1584
  response_format: "pcm"
726
- });
727
- const bytes = asUint8Array(typeof response.arrayBuffer === "function"
728
- ? await response.arrayBuffer()
729
- : response);
1585
+ };
1586
+ if (args.model === "gpt-4o-mini-tts" && (args.language || args.instructions)) {
1587
+ request.instructions = [
1588
+ args.language ? `Speak in ${args.language}.` : undefined,
1589
+ args.instructions
1590
+ ]
1591
+ .filter((value) => value !== undefined)
1592
+ .join(" ");
1593
+ }
1594
+ const response = await speechApi.create(request);
1595
+ this.trackUsage(args.model, { inputCharacters: args.text.length });
1596
+ const bytes = asUint8Array(isCallable(response.arrayBuffer) ? await response.arrayBuffer() : response);
730
1597
  yield { samples: toInt16Samples(bytes) };
731
1598
  }
732
1599
  /**
@@ -754,16 +1621,24 @@ export class OpenAIProvider extends BaseProvider {
754
1621
  const voice = args.voice ?? "alloy";
755
1622
  const speed = Math.max(0.25, Math.min(4.0, args.speed ?? 1.0));
756
1623
  const speechApi = this.getClient().audio.speech;
757
- const response = await speechApi.create({
1624
+ const request = {
758
1625
  model: args.model,
759
1626
  input: args.text,
760
1627
  voice,
761
1628
  speed,
762
1629
  response_format: fmt
763
- });
764
- const bytes = asUint8Array(typeof response.arrayBuffer === "function"
765
- ? await response.arrayBuffer()
766
- : response);
1630
+ };
1631
+ if (args.model === "gpt-4o-mini-tts" && (args.language || args.instructions)) {
1632
+ request.instructions = [
1633
+ args.language ? `Speak in ${args.language}.` : undefined,
1634
+ args.instructions
1635
+ ]
1636
+ .filter((value) => value !== undefined)
1637
+ .join(" ");
1638
+ }
1639
+ const response = await speechApi.create(request);
1640
+ this.trackUsage(args.model, { inputCharacters: args.text.length });
1641
+ const bytes = asUint8Array(isCallable(response.arrayBuffer) ? await response.arrayBuffer() : response);
767
1642
  return { data: bytes, mimeType: formatToMime[fmt] };
768
1643
  }
769
1644
  async automaticSpeechRecognition(args) {
@@ -772,25 +1647,53 @@ export class OpenAIProvider extends BaseProvider {
772
1647
  }
773
1648
  const temperature = Math.max(0, Math.min(1, args.temperature ?? 0));
774
1649
  const audioPart = new Uint8Array(args.audio);
1650
+ // Label the upload with what the bytes actually are. Hardcoding audio/mpeg
1651
+ // made OpenAI reject anything that was not MP3 with "400 Audio file might
1652
+ // be corrupted or unsupported" — which is every clip coming through
1653
+ // `nodetool.video.ExtractAudio`, since that writes PCM WAV.
1654
+ const audioMime = sniffAudioMime(audioPart);
1655
+ const audioName = `audio.${AUDIO_MIME_EXT[audioMime] ?? "mp3"}`;
775
1656
  const fileLike = typeof File !== "undefined"
776
- ? new File([audioPart], "audio.mp3", { type: "audio/mpeg" })
777
- : Object.assign(new Blob([audioPart], { type: "audio/mpeg" }), {
778
- name: "audio.mp3"
1657
+ ? new File([audioPart], audioName, { type: audioMime })
1658
+ : Object.assign(new Blob([audioPart], { type: audioMime }), {
1659
+ name: audioName
779
1660
  });
1661
+ const isDiarizationModel = args.model.startsWith("gpt-4o-transcribe-diarize");
1662
+ if (args.word_timestamps && args.model !== "whisper-1") {
1663
+ throw new Error("Word timestamps are only supported by whisper-1");
1664
+ }
780
1665
  const requestParams = {
781
1666
  file: fileLike,
782
1667
  model: args.model,
783
1668
  language: args.language,
784
- prompt: args.prompt,
785
1669
  temperature
786
1670
  };
1671
+ if (!isDiarizationModel && args.prompt) {
1672
+ requestParams.prompt = args.prompt;
1673
+ }
1674
+ if (isDiarizationModel) {
1675
+ requestParams.response_format = "diarized_json";
1676
+ requestParams.chunking_strategy = "auto";
1677
+ }
787
1678
  if (args.word_timestamps) {
788
1679
  requestParams.response_format = "verbose_json";
789
1680
  requestParams.timestamp_granularities = ["word", "segment"];
790
1681
  }
791
1682
  const response = await this.getClient().audio.transcriptions.create(requestParams);
1683
+ // Whisper/transcribe is billed per second of audio. The duration is only
1684
+ // reported on the verbose/diarized formats (and as `usage.seconds` on the
1685
+ // newer transcribe models); skip accounting when it is absent rather than
1686
+ // guessing.
1687
+ const durationSeconds = isNumber(response.duration)
1688
+ ? response.duration
1689
+ : isNumber(response.usage?.seconds)
1690
+ ? response.usage.seconds
1691
+ : undefined;
1692
+ if (durationSeconds !== undefined) {
1693
+ this.trackUsage(args.model, { durationSeconds });
1694
+ }
792
1695
  const text = String(response.text ?? "");
793
- if (!args.word_timestamps) {
1696
+ if (!args.word_timestamps && !isDiarizationModel) {
794
1697
  return { text };
795
1698
  }
796
1699
  const chunks = [];
@@ -820,19 +1723,24 @@ export class OpenAIProvider extends BaseProvider {
820
1723
  if (!params.prompt) {
821
1724
  throw new Error("The input prompt cannot be empty.");
822
1725
  }
823
- const size = OpenAIProvider.resolveVideoSize(params.aspectRatio, params.resolution);
1726
+ const size = OpenAIProvider.resolveVideoSize(params.aspectRatio, params.resolution, params.model.id);
824
1727
  const seconds = OpenAIProvider.secondsFromParams(params) ?? 4;
825
1728
  const request = {
826
1729
  model: params.model.id,
827
1730
  prompt: params.prompt,
828
- size: size ?? "1024x1024",
1731
+ size: size ?? "1280x720",
829
1732
  seconds: String(seconds)
830
1733
  };
831
- const video = await this.getClient().videos.create(request);
1734
+ // SAFETY: a dictionary request against the SDK's closed video params.
1735
+ const video = await this.getClient().videos.create(request, { signal: params.signal });
832
1736
  if (!video?.id) {
833
1737
  throw new Error("OpenAI video create response did not contain a video id");
834
1738
  }
835
- const timeoutMs = 10 * 60 * 1000;
1739
+ const timeoutMs = params.timeoutSeconds != null &&
1740
+ Number.isFinite(params.timeoutSeconds) &&
1741
+ params.timeoutSeconds > 0
1742
+ ? params.timeoutSeconds * 1000
1743
+ : 10 * 60 * 1000;
836
1744
  const intervalMs = 2000;
837
1745
  const start = Date.now();
838
1746
  let latest = video;
@@ -840,8 +1748,17 @@ export class OpenAIProvider extends BaseProvider {
840
1748
  if (Date.now() - start > timeoutMs) {
841
1749
  throw new Error("Video generation timed out");
842
1750
  }
843
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
844
- latest = await this.getClient().videos.retrieve(video.id);
1751
+ const remainingMs = timeoutMs - (Date.now() - start);
1752
+ await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, remainingMs)));
1753
+ if (Date.now() - start >= timeoutMs) {
1754
+ throw new Error("Video generation timed out");
1755
+ }
1756
+ if (params.signal?.aborted) {
1757
+ throw new Error("Video generation cancelled");
1758
+ }
1759
+ latest = await this.getClient().videos.retrieve(video.id, {
1760
+ signal: params.signal
1761
+ });
845
1762
  }
846
1763
  if (latest.status !== "completed") {
847
1764
  throw new Error(String(latest.error ??
@@ -855,14 +1772,13 @@ export class OpenAIProvider extends BaseProvider {
855
1772
  throw new Error("The input image cannot be empty.");
856
1773
  }
857
1774
  const [width, height] = OpenAIProvider.extractImageDimensions(image);
858
- const requestedSize = OpenAIProvider.resolveVideoSize(params.aspectRatio, params.resolution);
1775
+ const requestedSize = OpenAIProvider.resolveVideoSize(params.aspectRatio, params.resolution, params.model.id);
859
1776
  const size = requestedSize ?? OpenAIProvider.snapToValidVideoDimensions(width, height);
860
1777
  const seconds = OpenAIProvider.secondsFromParams(params) ?? 4;
861
1778
  const [targetW, targetH] = size.split("x").map(Number);
862
- const sharp = (await import("sharp")).default;
863
1779
  const resized = width === targetW && height === targetH
864
1780
  ? image
865
- : new Uint8Array(await sharp(image)
1781
+ : new Uint8Array(await (await loadSharp())(image)
866
1782
  .resize(targetW, targetH, { fit: "cover", position: "centre" })
867
1783
  .png()
868
1784
  .toBuffer());
@@ -874,11 +1790,15 @@ export class OpenAIProvider extends BaseProvider {
874
1790
  }),
875
1791
  size: size,
876
1792
  seconds: String(seconds)
877
- });
1793
+ }, { signal: params.signal });
878
1794
  if (!video?.id) {
879
1795
  throw new Error("OpenAI image-to-video create response did not contain a video id");
880
1796
  }
881
- const timeoutMs = 10 * 60 * 1000;
1797
+ const timeoutMs = params.timeoutSeconds != null &&
1798
+ Number.isFinite(params.timeoutSeconds) &&
1799
+ params.timeoutSeconds > 0
1800
+ ? params.timeoutSeconds * 1000
1801
+ : 10 * 60 * 1000;
882
1802
  const intervalMs = 2000;
883
1803
  const start = Date.now();
884
1804
  let latest = video;
@@ -886,8 +1806,17 @@ export class OpenAIProvider extends BaseProvider {
886
1806
  if (Date.now() - start > timeoutMs) {
887
1807
  throw new Error("Image-to-video generation timed out");
888
1808
  }
889
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
890
- latest = await this.getClient().videos.retrieve(video.id);
1809
+ const remainingMs = timeoutMs - (Date.now() - start);
1810
+ await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, remainingMs)));
1811
+ if (Date.now() - start >= timeoutMs) {
1812
+ throw new Error("Image-to-video generation timed out");
1813
+ }
1814
+ if (params.signal?.aborted) {
1815
+ throw new Error("Image-to-video generation cancelled");
1816
+ }
1817
+ latest = await this.getClient().videos.retrieve(video.id, {
1818
+ signal: params.signal
1819
+ });
891
1820
  }
892
1821
  if (latest.status !== "completed") {
893
1822
  throw new Error(String(latest.error ??
@@ -901,13 +1830,16 @@ export class OpenAIProvider extends BaseProvider {
901
1830
  if (input.length === 0 || input.every((v) => !v)) {
902
1831
  throw new Error("text must not be empty");
903
1832
  }
904
- const response = await this.getClient().embeddings.create({
905
- model: args.model,
906
- input,
907
- ...(args.dimensions ? { dimensions: args.dimensions } : {})
908
- });
1833
+ const request = { model: args.model, input };
1834
+ if (args.dimensions) {
1835
+ request.dimensions = args.dimensions;
1836
+ }
1837
+ const response = await this.getClient().embeddings.create(request);
909
1838
  return response.data.map((row) => row.embedding);
910
1839
  }
1840
+ isContextExceededError(error) {
1841
+ return openAIContextExceeded(error) !== null;
1842
+ }
911
1843
  isContextLengthError(error) {
912
1844
  const msg = String(error).toLowerCase();
913
1845
  return msg.includes("context length") || msg.includes("maximum context");