@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,56 +1,197 @@
1
+ import { stringifyTraceContent } from "../run-trace-serialization.js";
2
+ import { tracedProviderLoop, tracedToolCall, beginProviderRound, providerRoundContext, traceContentAllowed, messageTraceContent, traceTextContent } from "./loop-tracing.js";
3
+ import { isProviderSessionUpdate, isProviderMessageEvent, isProviderStop, isProviderToolErrorResult, PROVIDER_STOP_ABORTED } from "./types.js";
4
+ import { ProviderGenerationsUnsupportedError } from "./provider-generations.js";
1
5
  import { CostCalculator } from "./cost-calculator.js";
2
6
  import { getTracer } from "../telemetry.js";
3
- import { withUsageCapture, setLastUsage, consumeLastUsage, peekLastUsage, createUsageSlot } from "../tracing-helpers.js";
4
- import { SpanStatusCode } from "@opentelemetry/api";
5
- import { createLogger, getAssetFilePath } from "@nodetool-ai/config";
7
+ import { withUsageCapture, withModalityCapture, setLastUsage, setLastRequest, consumeLastUsage, peekLastUsage, peekLastRequest, createUsageSlot } from "../tracing-helpers.js";
8
+ import { isRunBudget } from "../turn-budget.js";
9
+ import { countTokens } from "../token-counter.js";
10
+ import { isCallable, isObjectLike, isRecord, isString } from "@nodetool-ai/protocol";
11
+ import { logProviderRequestFailure } from "./provider-request-log.js";
12
+ import { annotateProviderError, markContextExceeded } from "./provider-error.js";
13
+ import { buildProviderCallFailure } from "./provider-call-failure.js";
14
+ import { applyEntityReferences } from "./entity-references.js";
15
+ import { expandVideoContentAsFrames, videoFrameFallbackEnabled } from "./video-frame-fallback.js";
16
+ import { SpanStatusCode, context, trace } from "@opentelemetry/api";
17
+ import { createLogger, getDefaultAssetsPath } from "@nodetool-ai/config";
6
18
  const log = createLogger("nodetool.runtime.provider");
7
19
  /**
8
- * Derive a provider's capability set by checking which optional `getAvailable*`
9
- * methods it overrides on its prototype. Every provider can always generate
10
- * messages; image/video/TTS/ASR/embedding are advertised only when the concrete
11
- * class overrides the matching base method.
20
+ * Split the `turnBudget` argument into the run-level bounds (when the caller
21
+ * passed a {@link RunBudget}) and the per-turn admission object every loop
22
+ * reserves against. One argument serves both, so callers holding only a bare
23
+ * {@link TurnBudget} — the supervisor — keep working unchanged.
12
24
  *
13
- * Shared by `packages/websocket/src/models-api.ts` (REST leftover) and the
14
- * `models` tRPC router so both report identical capabilities for the same
15
- * provider instance.
25
+ * A free function rather than a method: `generateLoop` is called unbound on
26
+ * duck-typed test providers (`BaseProvider.prototype.generateLoop.call(mock)`),
27
+ * where `this` has no class methods.
16
28
  */
17
- export function providerCapabilities(instance) {
18
- const capabilities = [
19
- "generate_message",
20
- "generate_messages"
21
- ];
22
- if (instance.getAvailableImageModels !==
23
- BaseProvider.prototype.getAvailableImageModels) {
24
- capabilities.push("text_to_image", "image_to_image");
25
- }
26
- if (instance.getAvailableVideoModels !==
27
- BaseProvider.prototype.getAvailableVideoModels) {
28
- capabilities.push("text_to_video", "image_to_video");
29
- }
30
- if (instance.getAvailableTTSModels !==
31
- BaseProvider.prototype.getAvailableTTSModels) {
32
- capabilities.push("text_to_speech");
33
- }
34
- if (instance.getAvailableASRModels !==
35
- BaseProvider.prototype.getAvailableASRModels) {
36
- capabilities.push("automatic_speech_recognition");
37
- }
38
- if (instance.getAvailableEmbeddingModels !==
39
- BaseProvider.prototype.getAvailableEmbeddingModels) {
40
- capabilities.push("generate_embedding");
41
- }
42
- if (instance.getAvailable3DModels !==
43
- BaseProvider.prototype.getAvailable3DModels) {
44
- // Providers that expose 3D models are assumed to support both task types;
45
- // individual `textTo3D` / `imageTo3D` calls will throw if not implemented.
46
- capabilities.push("text_to_3d", "image_to_3d");
47
- }
48
- return capabilities;
29
+ export function resolveTurnBudget(budget) {
30
+ if (isRunBudget(budget)) {
31
+ return { runBudget: budget, turnBudget: budget.turns };
32
+ }
33
+ return { runBudget: null, turnBudget: budget };
34
+ }
35
+ /**
36
+ * The stop item for a refused turn. A {@link RunBudget} says which ceiling
37
+ * refused it; a bare {@link TurnBudget} only knows that one did.
38
+ */
39
+ export function budgetStopItem(runBudget) {
40
+ const exhausted = runBudget?.exhausted ?? null;
41
+ return {
42
+ type: "stop",
43
+ reason: exhausted?.kind === "deadline" ? "deadline" : "budget",
44
+ detail: exhausted?.detail ?? "the turn budget refused the next model turn"
45
+ };
46
+ }
47
+ /**
48
+ * Collapse a tool result to plain text. Used by string-only tool channels (the
49
+ * legacy inline callback, the Claude Agent SDK's MCP tools) when a tool returns
50
+ * {@link MessageContent} blocks they can't render — keep the text, drop images.
51
+ */
52
+ export function toolResultToText(result) {
53
+ if (isString(result))
54
+ return result;
55
+ const text = result
56
+ .filter((c) => c.type === "text")
57
+ .map((c) => c.text)
58
+ .join("\n");
59
+ return text || "[image result]";
60
+ }
61
+ export function splitToolResultImages(result) {
62
+ if (isString(result)) {
63
+ return { toolContent: result, imageMessage: null };
64
+ }
65
+ const images = result.filter((c) => c.type === "image_url");
66
+ if (images.length === 0) {
67
+ return { toolContent: result, imageMessage: null };
68
+ }
69
+ const texts = result.filter((c) => c.type === "text");
70
+ const note = texts.map((c) => c.text).join("\n") ||
71
+ "Returned an image, shown in the next message.";
72
+ return {
73
+ toolContent: note,
74
+ imageMessage: { role: "user", content: [...texts, ...images] }
75
+ };
76
+ }
77
+ /**
78
+ * What one attached medium costs a vision/audio model, per block. These are
79
+ * modality constants, not measurements of the bytes: by the time a turn is
80
+ * estimated, `resolveMessageMediaUris` has inlined every `asset://` as a
81
+ * `data:` uri, and BPE-tokenizing that base64 scores a 50 KB image at ~49,000
82
+ * tokens against a real bill of ~1,600. A fixed figure per block is the right
83
+ * order of magnitude; the base64 length is off by two of them.
84
+ *
85
+ * - Image: 1,600 — the common vision-model charge for a full-size image
86
+ * (Gemini bills a large image in the 1,000-1,600 band, GPT-4o high-detail
87
+ * lands near 1,100). Take the top of the band, since a reservation is a
88
+ * worst case.
89
+ * - Audio: 2,000 — roughly a minute at the ~32 tokens/second Gemini charges
90
+ * for audio. Longer clips under-count, but audio in a chat turn is short.
91
+ * - Video: 4,000 — video is billed per sampled frame (~250-300 tokens/second
92
+ * at 1 fps), so this covers a short clip of ~15 seconds.
93
+ * - Document: 3,000 — a few pages of extracted PDF text.
94
+ */
95
+ const IMAGE_CONTENT_TOKENS = 1_600;
96
+ const AUDIO_CONTENT_TOKENS = 2_000;
97
+ const VIDEO_CONTENT_TOKENS = 4_000;
98
+ const DOCUMENT_CONTENT_TOKENS = 3_000;
99
+ /** Any content block that is neither text nor a modality named above. */
100
+ const UNKNOWN_CONTENT_TOKENS = 1_600;
101
+ function estimateContentPartTokens(part) {
102
+ switch (part.type) {
103
+ case "text":
104
+ return countTokens(part.text);
105
+ case "image_url":
106
+ return IMAGE_CONTENT_TOKENS;
107
+ case "audio":
108
+ return AUDIO_CONTENT_TOKENS;
109
+ case "video":
110
+ return VIDEO_CONTENT_TOKENS;
111
+ case "document":
112
+ return DOCUMENT_CONTENT_TOKENS;
113
+ default: {
114
+ // A block of some shape this build does not narrow — a `thought` block
115
+ // from the protocol union, or a newer type. Count it when it carries
116
+ // text; otherwise charge the unknown-block figure rather than tokenize
117
+ // it, since it may hold a `data:` uri.
118
+ const text = part.text;
119
+ return isString(text) ? countTokens(text) : UNKNOWN_CONTENT_TOKENS;
120
+ }
121
+ }
122
+ }
123
+ /**
124
+ * Prompt tokens a turn will send, for reservation purposes. Deliberately a
125
+ * rough count: a reservation is a worst case, and a per-provider exact count
126
+ * would cost a tokenizer round-trip per turn to refine a number that only has
127
+ * to be an upper-ish bound.
128
+ *
129
+ * Text is tokenized. Media blocks get a fixed per-modality figure, so an
130
+ * inlined `data:` uri is never fed to the tokenizer.
131
+ */
132
+ export function estimatePromptTokens(messages) {
133
+ let total = 0;
134
+ for (const message of messages) {
135
+ const content = message.content;
136
+ if (isString(content)) {
137
+ total += countTokens(content);
138
+ }
139
+ else if (Array.isArray(content)) {
140
+ for (const part of content) {
141
+ total += estimateContentPartTokens(part);
142
+ }
143
+ }
144
+ else if (content != null) {
145
+ total += countTokens(JSON.stringify(content));
146
+ }
147
+ if (message.toolCalls?.length) {
148
+ total += countTokens(JSON.stringify(message.toolCalls));
149
+ }
150
+ }
151
+ return total;
49
152
  }
50
153
  export class BaseProvider {
51
154
  provider;
52
155
  _cost = 0;
156
+ _usageTotals = {
157
+ inputTokens: 0,
158
+ outputTokens: 0,
159
+ cachedTokens: 0
160
+ };
161
+ _unpricedReason = null;
162
+ _lastCallTokens = null;
53
163
  _emitMessage = null;
164
+ /**
165
+ * Whether this provider has a built-in, server-side web search it runs
166
+ * itself (e.g. Anthropic's `web_search_20250305` server tool, the Claude
167
+ * Agent SDK's `WebSearch`). When `true`, a tool named
168
+ * {@link WEB_SEARCH_TOOL_NAME} is fulfilled by the provider natively rather
169
+ * than the SerpAPI-backed `WebSearchTool`. Default `false` (use the fallback).
170
+ */
171
+ get supportsNativeWebSearch() {
172
+ return false;
173
+ }
174
+ /**
175
+ * Whether this provider has a built-in, server-side image generator (e.g.
176
+ * the OpenAI Responses `image_generation` tool). When `true`, a tool named
177
+ * {@link IMAGE_GENERATION_TOOL_NAME} is rendered as the provider's native
178
+ * tool — its result arrives as image content on the assistant message, not
179
+ * as a function-call round-trip. Default `false` (use the fallback function
180
+ * tool).
181
+ */
182
+ get supportsNativeImageGeneration() {
183
+ return false;
184
+ }
185
+ /**
186
+ * Whether this provider's chat API accepts a `video` content part. Only
187
+ * providers that read a whole clip natively answer `true` — an
188
+ * OpenAI-compatible endpoint re-hosting a video-capable model (AtlasCloud
189
+ * serving `google/gemini-2.5-flash`, say) does not, because the wire format
190
+ * has nowhere to put the bytes. Default `false`.
191
+ */
192
+ get supportsVideoInput() {
193
+ return false;
194
+ }
54
195
  setMessageEmitter(fn) {
55
196
  this._emitMessage = fn;
56
197
  }
@@ -58,29 +199,332 @@ export class BaseProvider {
58
199
  if (this._emitMessage)
59
200
  this._emitMessage(msg);
60
201
  }
202
+ /**
203
+ * Report one non-chat provider call (image, video, speech, ASR, embedding…)
204
+ * as an `llm_call` so the trace shows every provider call, not only chat.
205
+ * Carries no token counts: those modalities bill per unit, not per token.
206
+ * @internal
207
+ */
208
+ recordModalityCall(input) {
209
+ if (!this._emitMessage)
210
+ return;
211
+ const prompt = input.args
212
+ .map((arg) => isObjectLike(arg) ? arg.prompt : undefined)
213
+ .find(isString);
214
+ this.emitMessage({
215
+ type: "llm_call",
216
+ node_id: "",
217
+ provider: this.provider,
218
+ operation: input.operation,
219
+ model: extractModelId(input.args),
220
+ messages: prompt ? [{ role: "user", content: prompt }] : [],
221
+ response: null,
222
+ tool_calls: null,
223
+ tokens_input: null,
224
+ tokens_output: null,
225
+ cost: null,
226
+ duration_ms: Date.now() - input.startedAt,
227
+ error: input.error === undefined ? null : String(input.error),
228
+ timestamp: new Date(input.startedAt).toISOString()
229
+ });
230
+ }
231
+ /**
232
+ * Record a failed provider call: a server-side log with the exact request,
233
+ * and a `provider_call_failed` message so the surface showing the failure
234
+ * can report it without the user copying a status code out of a toast.
235
+ *
236
+ * One call, so the log and the report can never disagree about what failed.
237
+ * Public because the modality generator wrapper below is a free function.
238
+ * @internal
239
+ */
240
+ recordCallFailure(input) {
241
+ logProviderRequestFailure({
242
+ provider: this.provider,
243
+ model: input.model ?? "unknown",
244
+ request: input.request,
245
+ nodetoolArgs: input.nodetoolArgs,
246
+ error: input.error
247
+ });
248
+ if (!this._emitMessage)
249
+ return;
250
+ const failure = buildProviderCallFailure({
251
+ ...input,
252
+ provider: this.provider
253
+ });
254
+ if (failure)
255
+ this.emitMessage(failure);
256
+ }
257
+ /**
258
+ * Record the exact payload this provider is about to send to the upstream
259
+ * API. Call this right before the network request in `generateMessage` /
260
+ * `generateMessages`. On failure, the traced wrappers log precisely what was
261
+ * sent (secrets redacted, large fields truncated). Safe to call even outside
262
+ * a traced call — it is a no-op when no capture slot is active.
263
+ */
264
+ // HOLDOUT (anti-slop/no-unknown-parameters): see `setLastRequest` — the
265
+ // honest type is `object | null`, which `no-object-parameters` forbids.
266
+ recordRequestPayload(payload) {
267
+ setLastRequest(payload);
268
+ }
61
269
  constructor(provider) {
62
270
  this.provider = provider;
271
+ this.installModalityFailureLogging();
272
+ this.installVideoFrameFallback();
273
+ }
274
+ /**
275
+ * Chat already records what it sent and logs it on failure through
276
+ * {@link generateMessageTraced} / {@link generateMessagesTraced}. The
277
+ * non-chat modalities (image/video/audio/3D/embedding) are called directly,
278
+ * so wrap every one a concrete provider actually overrides with the same
279
+ * central failure logging.
280
+ *
281
+ * We replace methods on the instance (not the prototype), and only the ones
282
+ * that differ from the base — so an unsupported modality keeps its base
283
+ * "does not support" throw and never logs a spurious failure. Providers may
284
+ * additionally call {@link recordRequestPayload} to log the exact wire body;
285
+ * otherwise the call arguments are logged (binary buffers reduced to size
286
+ * markers, secrets redacted).
287
+ */
288
+ installModalityFailureLogging() {
289
+ for (const name of MODALITY_PROMISE_METHODS) {
290
+ const fn = this[name];
291
+ if (!isCallable(fn) || fn === BaseProvider.prototype[name]) {
292
+ continue;
293
+ }
294
+ // SAFETY: `name` comes from MODALITY_PROMISE_METHODS and the check above
295
+ // proved this instance overrides it, so `fn` is that modality method.
296
+ const original = fn;
297
+ Reflect.set(this, name, (...rawArgs) => withModalityCapture(async (alreadyActive) => {
298
+ // Central entity expansion: descriptors into the prompt, reference
299
+ // images onto the source list, before the concrete provider runs.
300
+ const args = applyEntityReferences(name, rawArgs);
301
+ const startedAt = Date.now();
302
+ try {
303
+ const result = await original.apply(this, args);
304
+ if (!alreadyActive) {
305
+ this.recordModalityCall({ operation: name, args, startedAt });
306
+ }
307
+ return result;
308
+ }
309
+ catch (err) {
310
+ annotateProviderError(err, {
311
+ provider: this.provider,
312
+ model: extractModelId(args)
313
+ });
314
+ if (!alreadyActive) {
315
+ this.recordModalityCall({
316
+ operation: name,
317
+ args,
318
+ startedAt,
319
+ error: err
320
+ });
321
+ const failureArgs = name === "referenceToVideo"
322
+ ? [referenceVideoDiagnostics(args)]
323
+ : args;
324
+ this.recordCallFailure({
325
+ model: extractModelId(args),
326
+ operation: name,
327
+ request: name === "referenceToVideo" ? undefined : peekLastRequest(),
328
+ nodetoolArgs: failureArgs,
329
+ error: err,
330
+ startedAt
331
+ });
332
+ }
333
+ throw err;
334
+ }
335
+ }));
336
+ }
337
+ for (const name of MODALITY_GENERATOR_METHODS) {
338
+ const fn = this[name];
339
+ if (!isCallable(fn) || fn === BaseProvider.prototype[name]) {
340
+ continue;
341
+ }
342
+ const original = fn;
343
+ Reflect.set(this, name, (...args) => wrapModalityGenerator(this, original, args, name));
344
+ }
345
+ }
346
+ /**
347
+ * Make a `video` content part readable by a provider whose chat API has no
348
+ * place to put one: sample the clip into stills and send those instead.
349
+ *
350
+ * Wrapping the instance's own methods (the pattern
351
+ * {@link installModalityFailureLogging} uses) is what makes it transparent.
352
+ * Not every caller goes through the traced wrappers — `packages/llm-nodes`
353
+ * and the jtbd optimizer call `generateMessages` straight — and a seam only
354
+ * some callers pass through is not a fallback, it is a coin flip.
355
+ *
356
+ * A provider that reads video natively is left alone, and so is one whose
357
+ * `generateMessage` delegates to its own `generateMessages`: by then the
358
+ * video parts are already frames, and the second pass is a no-op.
359
+ */
360
+ installVideoFrameFallback() {
361
+ // An arrow, so the `async function*` below (which binds its own `this`)
362
+ // still reaches this instance.
363
+ const sample = (args) => this.sampleVideoAsFrames(args);
364
+ // `generateMessage` and `generateMessages` are abstract, so a subclass that
365
+ // implements only one leaves the other undefined at construction — wrap
366
+ // what is there, the way installModalityFailureLogging does.
367
+ const message = this.generateMessage;
368
+ if (isCallable(message)) {
369
+ const original = message.bind(this);
370
+ Reflect.set(this, "generateMessage", async (args) => original({ ...args, messages: await sample(args) }));
371
+ }
372
+ const messages = this.generateMessages;
373
+ if (isCallable(messages)) {
374
+ const original = messages.bind(this);
375
+ Reflect.set(this, "generateMessages", async function* (args) {
376
+ yield* original({
377
+ ...args,
378
+ messages: await sample(args)
379
+ });
380
+ });
381
+ }
382
+ }
383
+ /**
384
+ * The messages to actually send: unchanged unless this provider cannot take
385
+ * video and the conversation carries some.
386
+ */
387
+ async sampleVideoAsFrames(args) {
388
+ if (this.supportsVideoInput || !videoFrameFallbackEnabled()) {
389
+ return args.messages;
390
+ }
391
+ // Returns the same array when nothing carries video, which is every call
392
+ // but the rare one.
393
+ return await expandVideoContentAsFrames(args.messages, {
394
+ resolveUri: (uri) => this.resolveUri(uri),
395
+ signal: args.signal,
396
+ provider: this.provider
397
+ });
63
398
  }
64
399
  static requiredSecrets() {
65
400
  return [];
66
401
  }
402
+ /**
403
+ * Why this provider cannot serve a call right now, or `null` when it can.
404
+ *
405
+ * The credential check (`isProviderConfigured`) runs before a provider is
406
+ * ever constructed, so a provider that reaches this point has its keys. This
407
+ * answers what a key cannot: an optional SDK nobody installed, a local
408
+ * runtime the host lacks. `find_model` and `list_models` read it, so a model
409
+ * nothing here can run is not offered as the best match — the failure lands
410
+ * on the discovery call instead of on the paid generation call.
411
+ */
412
+ async unavailableReason() {
413
+ return null;
414
+ }
415
+ /**
416
+ * The operations beyond chat that this provider implements. Each concrete
417
+ * provider lists its own; the base implements none. A capability belongs
418
+ * here only when the matching method has a working implementation.
419
+ */
420
+ declaredCapabilities() {
421
+ return [];
422
+ }
423
+ /**
424
+ * The capabilities this provider exposes: chat generation, which every
425
+ * provider has, plus its {@link declaredCapabilities}.
426
+ */
427
+ getCapabilities() {
428
+ return [
429
+ ...new Set([
430
+ "generate_message",
431
+ "generate_messages",
432
+ ...this.declaredCapabilities()
433
+ ])
434
+ ];
435
+ }
436
+ /**
437
+ * The provider's own record of the generations this account ran, newest
438
+ * first. Only providers with a request-history API override this; the base
439
+ * throws {@link ProviderGenerationsUnsupportedError} so a caller can tell
440
+ * "this provider cannot answer" from "this provider ran nothing".
441
+ *
442
+ * This is the provider's truth, not NodeTool's: it covers generations made
443
+ * outside this installation and carries the billed cost rather than the
444
+ * estimate the local row holds until reconciliation.
445
+ */
446
+ async listGenerations(_query = {}) {
447
+ throw new ProviderGenerationsUnsupportedError(this.provider, "list");
448
+ }
449
+ /**
450
+ * One generation at the provider, by the provider's own request id — the id
451
+ * the local row keeps as `provider_request_id`. Returns null when the
452
+ * provider answers that it has no such request. Throws
453
+ * {@link ProviderGenerationsUnsupportedError} when the provider has no
454
+ * lookup API at all.
455
+ */
456
+ async getGeneration(_requestId, _options = {}) {
457
+ throw new ProviderGenerationsUnsupportedError(this.provider, "get");
458
+ }
67
459
  getContainerEnv() {
68
460
  return {};
69
461
  }
462
+ /**
463
+ * Release any resources held by this provider (HTTP connection pools, cached
464
+ * model metadata, child processes, etc.). Default is a no-op; providers that
465
+ * hold long-lived resources should override it. Safe to call more than once.
466
+ * Callers that own a provider instance should invoke this on shutdown.
467
+ */
468
+ async close() { }
70
469
  async hasToolSupport(_model) {
71
470
  return true;
72
471
  }
472
+ /**
473
+ * The input window, in tokens, this provider's own API reports for `model`,
474
+ * or null when it has no such endpoint. Callers go through
475
+ * `resolveContextWindow`, which falls back to models.dev.
476
+ */
477
+ async getContextWindow(_model) {
478
+ return null;
479
+ }
73
480
  trackUsage(model, usage) {
74
- const cost = CostCalculator.calculate(model, usage, this.provider);
75
- this._cost += cost;
76
- setLastUsage({
481
+ // Accounting must never break a generation that already ran (and was
482
+ // already billed by the provider): keep the token counts and record the
483
+ // call as *unpriced*. A zero here would be written to the ledger as a real
484
+ // zero, which reads as "this call was free" — the one thing it is not.
485
+ let cost;
486
+ try {
487
+ cost = CostCalculator.calculate(model, usage, this.provider);
488
+ }
489
+ catch (error) {
490
+ const reason = error instanceof Error ? error.message : String(error);
491
+ this._unpricedReason = `No unit price for ${this.provider}/${model}: ${reason}`;
492
+ log.warn("Cost calculation failed; recording usage as unpriced", {
493
+ model,
494
+ provider: this.provider,
495
+ error: reason
496
+ });
497
+ }
498
+ this._cost += cost ?? 0;
499
+ this._usageTotals.inputTokens += usage.inputTokens ?? 0;
500
+ this._usageTotals.outputTokens += usage.outputTokens ?? 0;
501
+ this._usageTotals.cachedTokens += usage.cachedTokens ?? 0;
502
+ if (usage.inputTokens !== undefined) {
503
+ this._lastCallTokens = usage.inputTokens + (usage.outputTokens ?? 0);
504
+ }
505
+ const tracked = {
77
506
  inputTokens: usage.inputTokens ?? 0,
78
507
  outputTokens: usage.outputTokens ?? 0,
79
508
  cachedInputTokens: usage.cachedTokens,
80
- cost
81
- });
509
+ cacheWriteTokens: usage.cacheWriteTokens
510
+ };
511
+ // Left unset rather than zero: `setLastUsage` charges the enclosing node
512
+ // invocation with it, and an invented zero is spend nobody can audit.
513
+ if (cost !== undefined) {
514
+ tracked.cost = cost;
515
+ }
516
+ setLastUsage(tracked);
82
517
  log.debug("Cost tracked", { model, cost, total: this._cost });
83
- return cost;
518
+ return cost ?? 0;
519
+ }
520
+ /**
521
+ * Why this provider's cost total is missing spend, when a price lookup
522
+ * failed. Null when every tracked call was priced. A host writing a ledger
523
+ * row reads it to record the row as unpriced (`cost: null`) instead of
524
+ * booking an unpriced call as free.
525
+ */
526
+ get unpricedReason() {
527
+ return this._unpricedReason;
84
528
  }
85
529
  /** Alias for getTotalCost() — mirrors Python's BaseProvider.cost property. */
86
530
  get cost() {
@@ -89,6 +533,20 @@ export class BaseProvider {
89
533
  getTotalCost() {
90
534
  return this._cost;
91
535
  }
536
+ /**
537
+ * Input (cache reads and writes included) plus output tokens of the most
538
+ * recent tracked call, as the provider reported them. The prompt of the next
539
+ * call on the same transcript is at least this large, and unlike a local
540
+ * estimate it counts the tool definitions and system prompt too. Null until
541
+ * a call reports usage.
542
+ */
543
+ get lastCallTokens() {
544
+ return this._lastCallTokens;
545
+ }
546
+ /** Running token counts across every call tracked on this instance. */
547
+ get usageTotals() {
548
+ return { ...this._usageTotals };
549
+ }
92
550
  /**
93
551
  * Log a provider call for cost tracking.
94
552
  * Base implementation logs to debug; subclasses or callers may persist to a Prediction model.
@@ -104,6 +562,9 @@ export class BaseProvider {
104
562
  }
105
563
  resetCost() {
106
564
  this._cost = 0;
565
+ this._unpricedReason = null;
566
+ this._usageTotals = { inputTokens: 0, outputTokens: 0, cachedTokens: 0 };
567
+ this._lastCallTokens = null;
107
568
  }
108
569
  async getAvailableLanguageModels() {
109
570
  return [];
@@ -120,9 +581,31 @@ export class BaseProvider {
120
581
  async getAvailableASRModels() {
121
582
  return [];
122
583
  }
584
+ /**
585
+ * Music **generation** models exposed by this provider (e.g. MusicGen,
586
+ * Stable Audio, Suno, MiniMax Music). Override on providers that can
587
+ * synthesize music from a text prompt; the base returns none so the
588
+ * `text_to_music` capability is advertised only when overridden.
589
+ */
590
+ async getAvailableMusicModels() {
591
+ return [];
592
+ }
593
+ /**
594
+ * Audio **transform** models exposed by this provider (voice changers, stem
595
+ * separators, denoisers, audio super-resolution). Override on providers that
596
+ * can rewrite a recording; the base returns none so the `audio_to_audio`
597
+ * capability is advertised only when overridden.
598
+ */
599
+ async getAvailableAudioToAudioModels() {
600
+ return [];
601
+ }
123
602
  async getAvailableEmbeddingModels() {
124
603
  return [];
125
604
  }
605
+ /** Reranking models exposed by this provider. */
606
+ async getAvailableRerankModels() {
607
+ return [];
608
+ }
126
609
  /**
127
610
  * 3D **generation** models exposed by this provider (e.g. Meshy's `meshy-4`,
128
611
  * Rodin's `rodin-regular`). Override on providers that can synthesize 3D
@@ -142,14 +625,16 @@ export class BaseProvider {
142
625
  this.applyLlmRequestAttributes(span, args, false);
143
626
  try {
144
627
  const result = await this.generateMessage(args);
145
- const content = typeof result.content === "string"
628
+ const content = isString(result.content)
146
629
  ? result.content
147
- : JSON.stringify(result.content);
630
+ : stringifyTraceContent(result.content);
148
631
  span.setAttributes({
149
632
  "llm.response.role": result.role,
150
- "llm.response.content": content.slice(0, 2000),
151
633
  "llm.response.tool_calls_count": result.toolCalls?.length ?? 0
152
634
  });
635
+ if (traceContentAllowed()) {
636
+ span.setAttribute("llm.response.content", traceTextContent(content));
637
+ }
153
638
  span.setStatus({ code: SpanStatusCode.OK });
154
639
  return result;
155
640
  }
@@ -170,18 +655,45 @@ export class BaseProvider {
170
655
  let result;
171
656
  let error;
172
657
  let usage = null;
658
+ let requestPayload = null;
659
+ const startedAt = Date.now();
173
660
  try {
174
661
  // withUsageCapture creates a per-call AsyncLocalStorage slot so
175
662
  // trackUsage() in the provider hands its numbers back to us.
176
663
  result = await withUsageCapture(async () => {
177
- const r = await doCall();
178
- usage = consumeLastUsage();
179
- return r;
664
+ try {
665
+ const r = await doCall();
666
+ usage = consumeLastUsage();
667
+ return r;
668
+ }
669
+ finally {
670
+ // Read the recorded wire payload while still inside the capture slot.
671
+ requestPayload = peekLastRequest();
672
+ }
180
673
  });
181
674
  return result;
182
675
  }
183
676
  catch (err) {
677
+ annotateProviderError(err, {
678
+ provider: this.provider,
679
+ model: args.model
680
+ });
681
+ if (this.isContextExceededError(err)) {
682
+ markContextExceeded(err, this.provider);
683
+ }
184
684
  error = String(err);
685
+ this.recordCallFailure({
686
+ model: args.model,
687
+ operation: "generateMessage",
688
+ request: requestPayload,
689
+ nodetoolArgs: {
690
+ model: args.model,
691
+ messages: args.messages,
692
+ tools: args.tools
693
+ },
694
+ error: err,
695
+ startedAt
696
+ });
185
697
  throw err;
186
698
  }
187
699
  finally {
@@ -210,6 +722,10 @@ export class BaseProvider {
210
722
  }
211
723
  }
212
724
  applyLlmRequestAttributes(span, args, streaming) {
725
+ if (traceContentAllowed()) {
726
+ span.setAttribute("llm.request.messages", messageTraceContent(args.messages.filter((message) => typeof message === "object" && message !== null && "role" in message)));
727
+ span.setAttribute("llm.tool.names", stringifyTraceContent((args.tools ?? []).map((tool) => typeof tool === "object" && tool !== null && "name" in tool ? tool.name : null)));
728
+ }
213
729
  span.setAttributes({
214
730
  "llm.provider": this.provider,
215
731
  "llm.model": args.model,
@@ -245,12 +761,13 @@ export class BaseProvider {
245
761
  let error;
246
762
  // Per-call usage slot survives across the generator's yields by wrapping
247
763
  // each `next()` call in `runInSlot`.
248
- const { runInSlot, getUsage } = createUsageSlot();
764
+ const { runInSlot, getUsage, getRequest } = createUsageSlot();
249
765
  const tracer = getTracer();
250
766
  const source = tracer
251
767
  ? this._tracedStream(args, tracer)
252
768
  : this.generateMessages(args);
253
769
  let exhausted = false;
770
+ const startedAt = Date.now();
254
771
  try {
255
772
  while (true) {
256
773
  const result = await runInSlot(() => source.next());
@@ -280,7 +797,26 @@ export class BaseProvider {
280
797
  });
281
798
  }
282
799
  catch (err) {
800
+ annotateProviderError(err, {
801
+ provider: this.provider,
802
+ model: args.model
803
+ });
804
+ if (this.isContextExceededError(err)) {
805
+ markContextExceeded(err, this.provider);
806
+ }
283
807
  error = String(err);
808
+ this.recordCallFailure({
809
+ model: args.model,
810
+ operation: "generateMessages",
811
+ request: getRequest(),
812
+ nodetoolArgs: {
813
+ model: args.model,
814
+ messages: args.messages,
815
+ tools: args.tools
816
+ },
817
+ error: err,
818
+ startedAt
819
+ });
284
820
  throw err;
285
821
  }
286
822
  finally {
@@ -288,7 +824,11 @@ export class BaseProvider {
288
824
  // called return()), give the underlying generator a chance to close
289
825
  // so _tracedStream's finally runs and the LLM stream span ends.
290
826
  if (!exhausted) {
291
- await runInSlot(() => source.return?.(undefined) ?? Promise.resolve({ done: true, value: undefined })).catch(() => { });
827
+ await runInSlot(() => source.return?.(undefined) ??
828
+ Promise.resolve({
829
+ done: true,
830
+ value: undefined
831
+ })).catch(() => { });
292
832
  }
293
833
  const usage = getUsage();
294
834
  this.emitMessage({
@@ -311,17 +851,339 @@ export class BaseProvider {
311
851
  });
312
852
  }
313
853
  }
854
+ /**
855
+ * Run the agentic tool-calling loop and stream its events. This is the entry
856
+ * point an agent harness calls; the harness no longer implements the loop
857
+ * itself. The default implementation drives the standard external loop:
858
+ * stream one {@link generateMessages} turn, collect any tool calls, run them
859
+ * via the harness-supplied {@link executeTool}, append the results, and repeat
860
+ * until the model stops calling tools (or `maxIterations` is hit).
861
+ *
862
+ * It yields the same {@link Chunk}/{@link ToolCall}/{@link ProviderSessionUpdate}
863
+ * items a single turn does — for live display — plus a
864
+ * {@link ProviderMessageEvent} for every finalized message (assistant turns
865
+ * and tool results) so the harness can persist/collect them without
866
+ * reassembling them from chunks.
867
+ *
868
+ * Providers whose backend runs its own agent loop (e.g. the Claude Agent SDK)
869
+ * override this to drive that loop instead, while still emitting the same
870
+ * stream items. `generateMessages` remains the single-turn primitive.
871
+ */
872
+ /**
873
+ * Ask a {@link TurnBudget} to admit the turn that is about to be made.
874
+ * Exposed to subclasses because a provider driving a backend's own agent
875
+ * loop has to make the same call at the same point.
876
+ *
877
+ * Answers the reservation to hand back to `commit` after the turn, or `null`
878
+ * when the budget refused. The handle belongs to this turn alone: another
879
+ * loop sharing the budget settles its own.
880
+ */
881
+ _admitTurn(budget, model, messages) {
882
+ return budget.reserve({
883
+ model,
884
+ provider: this.provider,
885
+ inputTokens: estimatePromptTokens(messages)
886
+ });
887
+ }
888
+ async *generateLoop(args) {
889
+ return yield* tracedProviderLoop(this.provider, args.model, () => BaseProvider.prototype.generateBaseLoop.call(this, args));
890
+ }
891
+ async *generateBaseLoop(args) {
892
+ const maxIterations = args.maxIterations ?? 25;
893
+ const { executeTool, maxIterations: _omitMax, sequentialTools, providedToolsOnly: _omitProvidedToolsOnly, turnBudget: budgetArg, workspaceDir: _omitWorkspaceDir, skills: _omitSkills, resolveMedia, ...turnArgs } = args;
894
+ const { runBudget, turnBudget } = resolveTurnBudget(budgetArg);
895
+ const messages = [...args.messages];
896
+ // What the provider is actually sent: `messages` with every media
897
+ // reference dereferenced. Kept beside `messages` rather than replacing it,
898
+ // so the budget still reserves against the references (what the loop
899
+ // carries) and the tail is resolved once instead of once per round.
900
+ const sent = [];
901
+ let resolvedUpTo = 0;
902
+ const resolvedMessages = async () => {
903
+ if (!resolveMedia)
904
+ return messages;
905
+ if (resolvedUpTo < messages.length) {
906
+ sent.push(...(await resolveMedia(messages.slice(resolvedUpTo))));
907
+ resolvedUpTo = messages.length;
908
+ }
909
+ return sent;
910
+ };
911
+ // Tools may carry their own `execute` (provider-dispatched) and/or a
912
+ // `terminal` flag (end the loop after they run). Index by name so the
913
+ // tool-execution section can look up the matching ProviderTool per call.
914
+ const toolMap = new Map((args.tools ?? []).map((t) => [t.name, t]));
915
+ // Bridge to the legacy inline tool callback for any provider that executes
916
+ // tools mid-stream instead of yielding ToolCall items. Real providers yield
917
+ // ToolCall (handled below); this keeps the inline path working too. That
918
+ // channel is string-only, so image results collapse to their text.
919
+ let toolCallSeq = 0;
920
+ const onToolCall = executeTool
921
+ ? async (name, toolArgs) => {
922
+ const call = { id: `call_${++toolCallSeq}`, name, args: toolArgs };
923
+ const result = await tracedToolCall(call, () => executeTool(call));
924
+ if (isProviderToolErrorResult(result)) {
925
+ return toolResultToText(result.content);
926
+ }
927
+ return isString(result) ? result : toolResultToText(result);
928
+ }
929
+ : turnArgs.onToolCall;
930
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
931
+ if (turnArgs.signal?.aborted) {
932
+ yield PROVIDER_STOP_ABORTED;
933
+ return;
934
+ }
935
+ if (runBudget?.deadline.expired()) {
936
+ yield budgetStopItem(runBudget);
937
+ return;
938
+ }
939
+ let reservation = null;
940
+ if (turnBudget) {
941
+ reservation = this._admitTurn(turnBudget, args.model, messages);
942
+ if (reservation === null) {
943
+ yield budgetStopItem(runBudget);
944
+ return;
945
+ }
946
+ }
947
+ beginProviderRound(this.provider, args.model);
948
+ const costBeforeTurn = turnBudget ? this.getTotalCost() : 0;
949
+ let assistantText = "";
950
+ let finalizedAssistant;
951
+ const pending = [];
952
+ // The round's `done: true` chunk, withheld until we know if it ends the
953
+ // turn. Discarded when tools are pending — the next round supplies its own.
954
+ let terminalChunk;
955
+ // The reservation covers exactly one turn, so it is reconciled in a
956
+ // `finally`: a turn that throws must release it. Left outstanding, a
957
+ // single provider error would shrink the budget's headroom for good —
958
+ // it is run-level and outlives the decision that hit the error.
959
+ try {
960
+ for await (const item of this.generateMessagesTraced({
961
+ ...turnArgs,
962
+ messages: await resolvedMessages(),
963
+ onToolCall
964
+ })) {
965
+ if (turnArgs.signal?.aborted)
966
+ break;
967
+ if (isProviderMessageEvent(item)) {
968
+ if (item.message.role === "assistant") {
969
+ finalizedAssistant = item.message;
970
+ }
971
+ yield item;
972
+ continue;
973
+ }
974
+ if (isProviderSessionUpdate(item)) {
975
+ yield item;
976
+ continue;
977
+ }
978
+ if ("id" in item && "name" in item && "args" in item) {
979
+ pending.push(item);
980
+ yield item;
981
+ continue;
982
+ }
983
+ if (isProviderStop(item)) {
984
+ // Only this loop ends a loop. A single turn never yields a stop,
985
+ // so one arriving from `generateMessages` is not the loop's stop
986
+ // and must not be forwarded as if it were.
987
+ continue;
988
+ }
989
+ const chunk = item;
990
+ // Only text belongs in the assistant message. Audio/image chunks carry
991
+ // base64 in the same `content` field; concatenating those produced
992
+ // assistant messages full of binary garbage.
993
+ const isTextChunk = chunk.content_type === undefined || chunk.content_type === "text";
994
+ if (isString(chunk.content) &&
995
+ !chunk.thinking &&
996
+ isTextChunk) {
997
+ assistantText += chunk.content;
998
+ }
999
+ // `done: true` closes the provider's *completion*, which is not the same
1000
+ // as the end of the turn — every provider emits one per tool-calling
1001
+ // round. Forwarding them made consumers (the chat composer's Stop
1002
+ // button) treat each round as the end of the turn. Hold the terminal
1003
+ // chunk until we know whether tools are pending; release it below only
1004
+ // when this round really is the last one.
1005
+ if (chunk.done === true) {
1006
+ terminalChunk = item;
1007
+ continue;
1008
+ }
1009
+ yield item;
1010
+ }
1011
+ }
1012
+ finally {
1013
+ // Replace the reservation with what the turn really cost. A zero delta
1014
+ // here is a real zero, not an unknown: `trackUsage` fires on every
1015
+ // completed call, so nothing recorded means nothing was billed. (The
1016
+ // Claude Agent SDK path cannot say that, which is why it commits
1017
+ // `null` instead — see its override.)
1018
+ if (turnBudget && reservation) {
1019
+ turnBudget.commit(reservation, this.getTotalCost() - costBeforeTurn);
1020
+ }
1021
+ }
1022
+ if (pending.length === 0) {
1023
+ if (terminalChunk !== undefined) {
1024
+ yield terminalChunk;
1025
+ }
1026
+ if (finalizedAssistant) {
1027
+ messages.push(finalizedAssistant);
1028
+ }
1029
+ else {
1030
+ const assistantMsg = {
1031
+ role: "assistant",
1032
+ content: assistantText || null
1033
+ };
1034
+ messages.push(assistantMsg);
1035
+ yield { type: "message", message: assistantMsg };
1036
+ }
1037
+ // An abort mid-stream cut the turn short; the model did not end it.
1038
+ if (turnArgs.signal?.aborted) {
1039
+ yield PROVIDER_STOP_ABORTED;
1040
+ }
1041
+ return;
1042
+ }
1043
+ // The assistant turn requested tools — emit it (with the calls), run them,
1044
+ // then loop with the results appended.
1045
+ const assistantMsg = {
1046
+ role: "assistant",
1047
+ content: assistantText || null,
1048
+ toolCalls: pending
1049
+ };
1050
+ // Carry Gemini thought-signature parts forward so multi-turn function
1051
+ // calling keeps working — the loop owns the message now, so a tool call
1052
+ // that arrived with raw parts can't stash them on the message itself.
1053
+ const rawParts = pending.find((tc) => tc._rawGeminiParts)?._rawGeminiParts;
1054
+ if (rawParts) {
1055
+ assistantMsg._rawGeminiParts = rawParts;
1056
+ }
1057
+ const thinkingBlocks = pending.find((tc) => tc._anthropicThinkingBlocks)?._anthropicThinkingBlocks;
1058
+ if (thinkingBlocks) {
1059
+ assistantMsg._anthropicThinkingBlocks = thinkingBlocks;
1060
+ }
1061
+ messages.push(assistantMsg);
1062
+ yield { type: "message", message: assistantMsg };
1063
+ // Dispatch order: a tool's own `execute` (provider-driven) wins; else the
1064
+ // harness-supplied `executeTool` callback; else the tool is unavailable.
1065
+ const runTool = async (tc) => {
1066
+ const tool = toolMap.get(tc.name);
1067
+ try {
1068
+ if (tool?.execute) {
1069
+ const execute = tool.execute;
1070
+ return await tracedToolCall(tc, () => execute(tc.args ?? {}, tc.id));
1071
+ }
1072
+ if (executeTool)
1073
+ return await tracedToolCall(tc, () => executeTool(tc));
1074
+ return `Tool "${tc.name}" is not available`;
1075
+ }
1076
+ catch (err) {
1077
+ // A throwing tool must not destroy the whole assistant turn: turn the
1078
+ // failure into an error result so the model still receives output for
1079
+ // every tool call (and sibling results are preserved). Without this,
1080
+ // Promise.all in the parallel path would reject before any result is
1081
+ // emitted, discarding the turn.
1082
+ const message = err instanceof Error ? err.message : String(err);
1083
+ return {
1084
+ content: `Error executing tool "${tc.name}": ${message}`,
1085
+ isError: true
1086
+ };
1087
+ }
1088
+ };
1089
+ // A tool flagged `terminal` ends the loop once its turn's results are
1090
+ // emitted (e.g. a finish/submit tool).
1091
+ let terminated = false;
1092
+ const imageMessages = [];
1093
+ const emitToolResult = function* (tc, result) {
1094
+ let content;
1095
+ let isError = false;
1096
+ if (isProviderToolErrorResult(result)) {
1097
+ content = result.content;
1098
+ isError = true;
1099
+ }
1100
+ else {
1101
+ content = result;
1102
+ }
1103
+ const { toolContent, imageMessage } = splitToolResultImages(content);
1104
+ const toolMsg = {
1105
+ role: "tool",
1106
+ toolCallId: tc.id,
1107
+ content: toolContent
1108
+ };
1109
+ if (isError) {
1110
+ toolMsg.isError = true;
1111
+ }
1112
+ messages.push(toolMsg);
1113
+ yield { type: "message", message: toolMsg };
1114
+ if (imageMessage) {
1115
+ // The pixels ride a user message for the next model turn (most
1116
+ // providers can't render an image in a tool-result message). It is
1117
+ // in-flight only — pushed to the loop's messages but never yielded,
1118
+ // so it is never persisted or echoed, keeping saved history cheap.
1119
+ imageMessages.push(imageMessage);
1120
+ }
1121
+ };
1122
+ if (sequentialTools) {
1123
+ // One at a time so a tool can read the state a prior tool wrote, and so
1124
+ // a terminal tool can abort the loop mid-turn (skipping the rest).
1125
+ for (const tc of pending) {
1126
+ if (turnArgs.signal?.aborted)
1127
+ break;
1128
+ yield* emitToolResult(tc, await runTool(tc));
1129
+ if (toolMap.get(tc.name)?.terminal) {
1130
+ terminated = true;
1131
+ break;
1132
+ }
1133
+ }
1134
+ }
1135
+ else {
1136
+ if (turnArgs.signal?.aborted) {
1137
+ yield PROVIDER_STOP_ABORTED;
1138
+ return;
1139
+ }
1140
+ const results = await Promise.all(pending.map(async (tc) => ({ tc, content: await runTool(tc) })));
1141
+ for (const { tc, content } of results) {
1142
+ yield* emitToolResult(tc, content);
1143
+ if (toolMap.get(tc.name)?.terminal)
1144
+ terminated = true;
1145
+ }
1146
+ }
1147
+ messages.push(...imageMessages);
1148
+ // A terminal tool ran this turn — stop after emitting its results. That
1149
+ // is the loop doing what it was asked, not a limit being hit, so it
1150
+ // carries no stop item.
1151
+ if (terminated)
1152
+ return;
1153
+ }
1154
+ yield {
1155
+ type: "stop",
1156
+ reason: "iterations",
1157
+ detail: `stopped after ${maxIterations} tool-calling round(s)`
1158
+ };
1159
+ }
314
1160
  /** Internal: wrap generateMessages with OTel span */
315
1161
  async *_tracedStream(args, tracer) {
316
- const span = tracer.startSpan(`llm.stream ${this.provider}/${args.model}`);
1162
+ const span = tracer.startSpan(`llm.stream ${this.provider}/${args.model}`, {}, providerRoundContext());
317
1163
  this.applyLlmRequestAttributes(span, args, true);
318
1164
  let chunkCount = 0;
1165
+ let response = "";
1166
+ const stream = this.generateMessages(args);
1167
+ const active = trace.setSpan(providerRoundContext(), span);
1168
+ let exhausted = false;
319
1169
  try {
320
- for await (const item of this.generateMessages(args)) {
1170
+ while (true) {
1171
+ const next = await context.with(active, () => stream.next());
1172
+ if (next.done) {
1173
+ exhausted = true;
1174
+ break;
1175
+ }
1176
+ const item = next.value;
1177
+ if (traceContentAllowed() && "content" in item && typeof item.content === "string" && !("thinking" in item && item.thinking) && (!("content_type" in item) || item.content_type === "text")) {
1178
+ response = traceTextContent(response + item.content);
1179
+ }
321
1180
  chunkCount++;
322
1181
  yield item;
323
1182
  }
324
- span.setAttributes({ "llm.response.chunk_count": chunkCount });
1183
+ span.setAttribute("llm.response.chunk_count", chunkCount);
1184
+ if (traceContentAllowed()) {
1185
+ span.setAttribute("llm.response.content", response);
1186
+ }
325
1187
  span.setStatus({ code: SpanStatusCode.OK });
326
1188
  }
327
1189
  catch (err) {
@@ -330,6 +1192,9 @@ export class BaseProvider {
330
1192
  throw err;
331
1193
  }
332
1194
  finally {
1195
+ if (!exhausted) {
1196
+ await context.with(active, () => stream.return(undefined));
1197
+ }
333
1198
  applyUsageAttributes(span, peekLastUsage());
334
1199
  span.end();
335
1200
  }
@@ -351,7 +1216,14 @@ export class BaseProvider {
351
1216
  }
352
1217
  return results;
353
1218
  }
354
- async imageToImage(_image, _params) {
1219
+ /**
1220
+ * Transform one or more source images into a single output image.
1221
+ *
1222
+ * The first image is the primary subject; providers that accept multiple
1223
+ * reference images (e.g. multi-image editing / composition) use the rest,
1224
+ * while single-image providers operate on `images[0]`.
1225
+ */
1226
+ async imageToImage(_images, _params) {
355
1227
  throw new Error(`${this.provider} does not support imageToImage`);
356
1228
  }
357
1229
  /**
@@ -360,13 +1232,76 @@ export class BaseProvider {
360
1232
  * Default implementation calls imageToImage() sequentially as a fallback.
361
1233
  * Providers that support native batch generation should override this.
362
1234
  */
363
- async imageToImages(image, params, numImages) {
1235
+ async imageToImages(images, params, numImages) {
364
1236
  const results = [];
365
1237
  for (let i = 0; i < numImages; i++) {
366
- results.push(await this.imageToImage(image, params));
1238
+ results.push(await this.imageToImage(images, params));
367
1239
  }
368
1240
  return results;
369
1241
  }
1242
+ /**
1243
+ * Inpaint: regenerate the masked region of one or more source images
1244
+ * according to a prompt. Like {@link imageToImage} but mask-guided — the mask
1245
+ * is routed to whatever mask input the target endpoint declares.
1246
+ *
1247
+ * The first image is the primary subject; the mask in `params.mask` marks the
1248
+ * region to regenerate (white = regenerate).
1249
+ */
1250
+ async inpaint(_images, _params) {
1251
+ throw new Error(`${this.provider} does not support inpaint`);
1252
+ }
1253
+ /**
1254
+ * Generate multiple inpaint results in one logical call.
1255
+ *
1256
+ * Default implementation calls inpaint() sequentially as a fallback.
1257
+ * Providers that support native batch generation should override this.
1258
+ */
1259
+ async inpaintImages(images, params, numImages) {
1260
+ const results = [];
1261
+ for (let i = 0; i < numImages; i++) {
1262
+ results.push(await this.inpaint(images, params));
1263
+ }
1264
+ return results;
1265
+ }
1266
+ /**
1267
+ * Outpaint (uncrop): extend an image past its frame, filling the added area
1268
+ * with content that continues the scene. The first image is the subject;
1269
+ * providers that take reference images use the rest.
1270
+ */
1271
+ async outpaintImage(_images, _params) {
1272
+ throw new Error(`${this.provider} does not support outpaintImage`);
1273
+ }
1274
+ /** Increase the resolution / detail of an image. */
1275
+ async upscaleImage(_image, _params) {
1276
+ throw new Error(`${this.provider} does not support upscaleImage`);
1277
+ }
1278
+ /** Remove the background from an image, returning an image with alpha. */
1279
+ async removeBackground(_image, _params) {
1280
+ throw new Error(`${this.provider} does not support removeBackground`);
1281
+ }
1282
+ /**
1283
+ * Estimate per-pixel depth. Returns a grayscale PNG at the input's size,
1284
+ * where brighter pixels are nearer to the camera.
1285
+ */
1286
+ async estimateDepth(_image, _params) {
1287
+ throw new Error(`${this.provider} does not support estimateDepth`);
1288
+ }
1289
+ /**
1290
+ * Segment an image into object masks. Returns one entry per object the model
1291
+ * found, in the model's own order; an empty array means it found nothing,
1292
+ * which is an answer rather than a failure.
1293
+ */
1294
+ async segmentImage(_image, _params) {
1295
+ throw new Error(`${this.provider} does not support segmentImage`);
1296
+ }
1297
+ /** Re-light a subject according to a prompt / background reference. */
1298
+ async relightImage(_image, _params) {
1299
+ throw new Error(`${this.provider} does not support relightImage`);
1300
+ }
1301
+ /** Convert a raster image into a vector (SVG) representation. */
1302
+ async vectorizeImage(_image, _params) {
1303
+ throw new Error(`${this.provider} does not support vectorizeImage`);
1304
+ }
370
1305
  async *textToSpeech(_args) {
371
1306
  yield* [];
372
1307
  throw new Error(`${this.provider} does not support textToSpeech`);
@@ -383,15 +1318,87 @@ export class BaseProvider {
383
1318
  async textToSpeechEncoded(_args) {
384
1319
  return null;
385
1320
  }
1321
+ /**
1322
+ * True when this provider streams raw PCM via {@link textToSpeech}. Providers
1323
+ * that only return encoded audio files (e.g. FAL/KIE) leave the base method in
1324
+ * place and are consumed via {@link textToSpeechEncoded} instead.
1325
+ */
1326
+ supportsStreamingTextToSpeech() {
1327
+ return this.textToSpeech !== BaseProvider.prototype.textToSpeech;
1328
+ }
1329
+ /**
1330
+ * Generate music / instrumental audio from a text prompt. Music providers
1331
+ * return a fully-encoded audio file (mp3/wav/flac), so this resolves to an
1332
+ * {@link EncodedAudioResult} rather than streaming raw PCM. Providers that
1333
+ * expose music models via {@link getAvailableMusicModels} must override this.
1334
+ */
1335
+ async textToMusic(_params) {
1336
+ throw new Error(`${this.provider} does not support textToMusic`);
1337
+ }
1338
+ /**
1339
+ * Optional video-conditioned sound task. Call through requestVideoToAudio
1340
+ * for input and capability validation before dispatch.
1341
+ */
1342
+ async videoToAudio(_video, _params) {
1343
+ throw new Error(`${this.provider} does not support videoToAudio`);
1344
+ }
1345
+ /**
1346
+ * Rewrite a recording: convert the voice, isolate or denoise it, separate a
1347
+ * stem, raise its sample rate. The source bytes are passed positionally, the
1348
+ * direction rides in the params. Like {@link textToMusic} this resolves to an
1349
+ * {@link EncodedAudioResult} — every provider that offers these endpoints
1350
+ * hands back a file, not PCM.
1351
+ */
1352
+ async audioToAudio(_audio, _params) {
1353
+ throw new Error(`${this.provider} does not support audioToAudio`);
1354
+ }
386
1355
  async automaticSpeechRecognition(_args) {
387
1356
  throw new Error(`${this.provider} does not support automaticSpeechRecognition`);
388
1357
  }
389
1358
  async textToVideo(_params) {
390
1359
  throw new Error(`${this.provider} does not support textToVideo`);
391
1360
  }
1361
+ /**
1362
+ * Animate a single source image into a video. Reference media uses
1363
+ * {@link referenceToVideo} so the input role remains explicit.
1364
+ */
392
1365
  async imageToVideo(_image, _params) {
393
1366
  throw new Error(`${this.provider} does not support imageToVideo`);
394
1367
  }
1368
+ async referenceToVideo(_inputs, _params) {
1369
+ throw new Error(`${this.provider} does not support referenceToVideo`);
1370
+ }
1371
+ /** Track a source-frame box and return samples in absolute source milliseconds. */
1372
+ async trackObject(_video, _params) {
1373
+ throw new Error(`${this.provider} does not support object tracking`);
1374
+ }
1375
+ /** Restyle / edit an existing video, guided by a prompt. */
1376
+ async videoToVideo(_video, _params) {
1377
+ throw new Error(`${this.provider} does not support videoToVideo`);
1378
+ }
1379
+ /** Add source time before or after an existing video window. */
1380
+ async extendVideo(_video, _params) {
1381
+ throw new Error(`${this.provider} does not support extendVideo`);
1382
+ }
1383
+ /** Increase the resolution / detail of a video. */
1384
+ async upscaleVideo(_video, _params) {
1385
+ throw new Error(`${this.provider} does not support upscaleVideo`);
1386
+ }
1387
+ /**
1388
+ * Synthesize intermediate frames so a clip plays at a higher frame rate (or
1389
+ * in slow motion at the same one).
1390
+ */
1391
+ async interpolateVideo(_video, _params) {
1392
+ throw new Error(`${this.provider} does not support interpolateVideo`);
1393
+ }
1394
+ /** Extend a video past its frame, consistently across its frames. */
1395
+ async outpaintVideo(_video, _params) {
1396
+ throw new Error(`${this.provider} does not support outpaintVideo`);
1397
+ }
1398
+ /** Drive a face in a video/image to match the speech in an audio track. */
1399
+ async lipSync(_video, _params) {
1400
+ throw new Error(`${this.provider} does not support lipSync`);
1401
+ }
395
1402
  /**
396
1403
  * Generate a 3D asset from a text prompt. Returns the encoded asset bytes
397
1404
  * (typically GLB). Providers that support 3D generation should override.
@@ -410,6 +1417,20 @@ export class BaseProvider {
410
1417
  async generateEmbedding(_args) {
411
1418
  throw new Error(`${this.provider} does not support generateEmbedding`);
412
1419
  }
1420
+ /** Score each document against the query, most relevant first. */
1421
+ async rerank(_params) {
1422
+ throw new Error(`${this.provider} does not support rerank`);
1423
+ }
1424
+ /**
1425
+ * Whether `error` is this provider refusing the request because it exceeds
1426
+ * the model's context window. Default `false`: a provider whose signal has
1427
+ * not been established must not have one guessed for it, because a false
1428
+ * positive rewrites a transcript over an unrelated failure. Overridden by the
1429
+ * providers in `context-exceeded.ts`.
1430
+ */
1431
+ isContextExceededError(_error) {
1432
+ return false;
1433
+ }
413
1434
  isContextLengthError(error) {
414
1435
  const msg = String(error).toLowerCase();
415
1436
  return msg.includes("context length") || msg.includes("maximum context");
@@ -420,7 +1441,9 @@ export class BaseProvider {
420
1441
  */
421
1442
  isRateLimitError(error) {
422
1443
  const msg = error instanceof Error ? error.message : String(error);
423
- return /429|rate.?limit|too many requests/i.test(msg);
1444
+ // Match 429 as a standalone token, not as digits inside a larger number
1445
+ // (e.g. "request 4290", a byte count, or a model id like "gpt-4-0429").
1446
+ return /\b429\b|rate.?limit|too many requests/i.test(msg);
424
1447
  }
425
1448
  /**
426
1449
  * Check if an error is an authentication error (HTTP 401/403).
@@ -428,18 +1451,21 @@ export class BaseProvider {
428
1451
  */
429
1452
  isAuthError(error) {
430
1453
  const msg = error instanceof Error ? error.message : String(error);
431
- return /401|403|unauthorized|forbidden|invalid.*api.*key|authentication/i.test(msg);
1454
+ // Match 401/403 as standalone tokens, not as digits inside a larger number
1455
+ // (e.g. "model gpt-4-0403 not found") which would misclassify unrelated
1456
+ // errors as auth failures and skip a retry that should have happened.
1457
+ return /\b401\b|\b403\b|unauthorized|forbidden|invalid.*api.*key|authentication/i.test(msg);
432
1458
  }
433
1459
  parseToolCallArgs(raw) {
434
- if (raw && typeof raw === "object" && !Array.isArray(raw)) {
1460
+ if (isRecord(raw)) {
435
1461
  return raw;
436
1462
  }
437
- if (typeof raw !== "string") {
1463
+ if (!isString(raw)) {
438
1464
  return {};
439
1465
  }
440
1466
  try {
441
1467
  const parsed = JSON.parse(raw);
442
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1468
+ if (isRecord(parsed)) {
443
1469
  return parsed;
444
1470
  }
445
1471
  return {};
@@ -458,42 +1484,202 @@ export class BaseProvider {
458
1484
  /**
459
1485
  * Resolve a URI to a `data:` URI that providers can consume directly.
460
1486
  *
461
- * - `file://...` → read from disk, return `data:<mime>;base64,…`
1487
+ * - `file://...` → read an asset file, return `data:<mime>;base64,…`
462
1488
  * - `/api/storage/<k>` → read from getDefaultAssetsPath()/<k> (legacy fallback)
463
1489
  * - Everything else → returned unchanged (http/https/data: pass through)
464
1490
  */
465
1491
  async resolveUri(uri) {
466
- const { readFile } = await import("node:fs/promises");
1492
+ const { importNodeBuiltin } = await import("@nodetool-ai/config");
1493
+ const fsP = await importNodeBuiltin("node:fs/promises");
1494
+ if (!fsP) {
1495
+ throw new Error("resolveUri requires node:fs/promises (Node-only feature)");
1496
+ }
1497
+ const pathMod = await importNodeBuiltin("node:path");
1498
+ if (!pathMod) {
1499
+ throw new Error("resolveUri requires node:path (Node-only feature)");
1500
+ }
1501
+ const getAssetsRoot = () => fsP.realpath(getDefaultAssetsPath());
1502
+ const readAssetFile = async (filePath) => {
1503
+ const assetsRoot = await getAssetsRoot();
1504
+ const assertContained = (candidate) => {
1505
+ const relative = pathMod.relative(assetsRoot, candidate);
1506
+ if (relative.startsWith("..") || pathMod.isAbsolute(relative)) {
1507
+ throw new Error("Asset URI resolves outside the asset directory");
1508
+ }
1509
+ };
1510
+ const requestedPath = pathMod.resolve(filePath);
1511
+ assertContained(requestedPath);
1512
+ const realPath = await fsP.realpath(requestedPath);
1513
+ assertContained(realPath);
1514
+ return await fsP.readFile(realPath);
1515
+ };
467
1516
  if (uri.startsWith("file://")) {
468
- try {
469
- const { fileURLToPath } = await import("node:url");
470
- const filePath = fileURLToPath(uri);
471
- const bytes = await readFile(filePath);
472
- const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
473
- const mime = EXT_TO_MIME[ext] ?? "application/octet-stream";
474
- return `data:${mime};base64,${bytes.toString("base64")}`;
475
- }
476
- catch {
477
- return uri;
1517
+ const urlMod = await importNodeBuiltin("node:url");
1518
+ if (!urlMod) {
1519
+ throw new Error("resolveUri file:// requires node:url");
478
1520
  }
1521
+ const filePath = urlMod.fileURLToPath(uri);
1522
+ const bytes = await readAssetFile(filePath);
1523
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
1524
+ const mime = EXT_TO_MIME[ext] ?? "application/octet-stream";
1525
+ return `data:${mime};base64,${Buffer.from(bytes).toString("base64")}`;
479
1526
  }
480
1527
  // Legacy: old messages stored with browser-facing /api/storage/ path
481
1528
  if (uri.startsWith("/api/storage/")) {
482
1529
  const key = uri.slice("/api/storage/".length);
483
1530
  try {
484
- const bytes = await readFile(getAssetFilePath(key));
1531
+ const assetsRoot = await getAssetsRoot();
1532
+ const decodedKey = decodeURIComponent(key);
1533
+ const bytes = await readAssetFile(pathMod.resolve(assetsRoot, decodedKey.replace(/^\/+/, "")));
485
1534
  const ext = key.split(".").pop()?.toLowerCase() ?? "";
486
1535
  const mime = EXT_TO_MIME[ext] ?? "application/octet-stream";
487
- return `data:${mime};base64,${bytes.toString("base64")}`;
1536
+ return `data:${mime};base64,${Buffer.from(bytes).toString("base64")}`;
488
1537
  }
489
- catch {
1538
+ catch (error) {
1539
+ if (error instanceof Error &&
1540
+ error.message === "Asset URI resolves outside the asset directory") {
1541
+ throw error;
1542
+ }
490
1543
  // Remote deployment: file not local, fall back to absolute HTTP
491
1544
  return `http://127.0.0.1:${process.env.PORT ?? 7777}${uri}`;
492
1545
  }
493
1546
  }
1547
+ // A reference scheme reaching a provider means the caller never
1548
+ // dereferenced it (see `ProcessingContext.resolveMessageMediaUris`). Say
1549
+ // that, instead of handing `asset://…` to `fetch` and reporting the SSRF
1550
+ // guard's "Refusing to fetch unsafe URL" — which names neither the asset
1551
+ // nor the real cause.
1552
+ if (uri.startsWith("asset://") || uri.startsWith("package://")) {
1553
+ throw new Error(`Unresolved asset reference reached the model provider: ${uri}. ` +
1554
+ `The asset bytes could not be loaded from storage.`);
1555
+ }
494
1556
  return uri;
495
1557
  }
496
1558
  }
1559
+ function referenceVideoDiagnostics(args) {
1560
+ const inputs = args[0];
1561
+ const params = args[1];
1562
+ const media = isRecord(inputs) ? inputs : {};
1563
+ const listBytes = (value) => Array.isArray(value)
1564
+ ? value.reduce((total, item) => total + (item instanceof Uint8Array ? item.byteLength : 0), 0)
1565
+ : 0;
1566
+ return {
1567
+ image_count: Array.isArray(media.images) ? media.images.length : 0,
1568
+ video_count: Array.isArray(media.videos) ? media.videos.length : 0,
1569
+ image_bytes: listBytes(media.images),
1570
+ video_bytes: listBytes(media.videos),
1571
+ model: extractModelId([params])
1572
+ };
1573
+ }
1574
+ const MODALITY_PROMISE_METHODS = [
1575
+ "textToImage",
1576
+ "textToImages",
1577
+ "imageToImage",
1578
+ "imageToImages",
1579
+ "inpaint",
1580
+ "inpaintImages",
1581
+ "outpaintImage",
1582
+ "upscaleImage",
1583
+ "removeBackground",
1584
+ "estimateDepth",
1585
+ "relightImage",
1586
+ "vectorizeImage",
1587
+ "segmentImage",
1588
+ "textToSpeechEncoded",
1589
+ "textToMusic",
1590
+ "videoToAudio",
1591
+ "automaticSpeechRecognition",
1592
+ "textToVideo",
1593
+ "imageToVideo",
1594
+ "referenceToVideo",
1595
+ "videoToVideo",
1596
+ "trackObject",
1597
+ "extendVideo",
1598
+ "upscaleVideo",
1599
+ "interpolateVideo",
1600
+ "outpaintVideo",
1601
+ "lipSync",
1602
+ "textTo3D",
1603
+ "imageTo3D",
1604
+ "generateEmbedding",
1605
+ "rerank"
1606
+ ];
1607
+ /** Async-generator non-chat modality methods wrapped with failure logging. */
1608
+ const MODALITY_GENERATOR_METHODS = ["textToSpeech"];
1609
+ /**
1610
+ * Best-effort model id for failure logging. Modality params carry the model
1611
+ * either as a plain string (ASR, embeddings) or as a model object with an `id`
1612
+ * (image/video tasks). Returns "unknown" when neither is present.
1613
+ */
1614
+ function extractModelId(args) {
1615
+ for (const arg of args) {
1616
+ if (!isObjectLike(arg))
1617
+ continue;
1618
+ const model = arg.model;
1619
+ if (isString(model))
1620
+ return model;
1621
+ if (isObjectLike(model)) {
1622
+ const id = model.id;
1623
+ if (isString(id))
1624
+ return id;
1625
+ }
1626
+ }
1627
+ return "unknown";
1628
+ }
1629
+ /**
1630
+ * Drive a provider's async-generator modality method (e.g. textToSpeech)
1631
+ * through a per-call slot — so `recordRequestPayload` is visible across yields —
1632
+ * and log centrally if it throws. The slot is wrapped around each `next()`
1633
+ * because AsyncLocalStorage does not persist across generator suspension.
1634
+ */
1635
+ async function* wrapModalityGenerator(provider, original, args, operationName) {
1636
+ const { runInSlot, getRequest } = createUsageSlot();
1637
+ const startedAt = Date.now();
1638
+ const source = original.apply(provider, args);
1639
+ let exhausted = false;
1640
+ let failure;
1641
+ try {
1642
+ while (true) {
1643
+ const result = await runInSlot(() => source.next());
1644
+ if (result.done) {
1645
+ exhausted = true;
1646
+ return result.value;
1647
+ }
1648
+ yield result.value;
1649
+ }
1650
+ }
1651
+ catch (err) {
1652
+ failure = err;
1653
+ annotateProviderError(err, {
1654
+ provider: provider.provider,
1655
+ model: extractModelId(args)
1656
+ });
1657
+ provider.recordCallFailure({
1658
+ model: extractModelId(args),
1659
+ operation: operationName,
1660
+ request: getRequest(),
1661
+ nodetoolArgs: args,
1662
+ error: err,
1663
+ startedAt
1664
+ });
1665
+ throw err;
1666
+ }
1667
+ finally {
1668
+ if (!exhausted) {
1669
+ await runInSlot(() => source.return?.(undefined) ??
1670
+ Promise.resolve({
1671
+ done: true,
1672
+ value: undefined
1673
+ })).catch(() => { });
1674
+ }
1675
+ provider.recordModalityCall({
1676
+ operation: operationName,
1677
+ args,
1678
+ startedAt,
1679
+ error: failure
1680
+ });
1681
+ }
1682
+ }
497
1683
  /** Attach gen_ai usage attributes to a span (no-op if usage is null). */
498
1684
  function applyUsageAttributes(span, usage) {
499
1685
  if (!usage)
@@ -505,14 +1691,21 @@ function applyUsageAttributes(span, usage) {
505
1691
  ...(usage.cachedInputTokens !== undefined && {
506
1692
  "gen_ai.usage.cached_input_tokens": usage.cachedInputTokens
507
1693
  }),
508
- ...(usage.cost !== undefined && { "gen_ai.usage.cost_credits": usage.cost })
1694
+ ...(usage.cost !== undefined && { "gen_ai.usage.cost_usd": usage.cost })
509
1695
  });
510
1696
  }
511
1697
  const EXT_TO_MIME = {
512
- jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png",
513
- gif: "image/gif", webp: "image/webp", svg: "image/svg+xml",
514
- mp4: "video/mp4", webm: "video/webm",
515
- mp3: "audio/mpeg", wav: "audio/wav", ogg: "audio/ogg",
1698
+ jpg: "image/jpeg",
1699
+ jpeg: "image/jpeg",
1700
+ png: "image/png",
1701
+ gif: "image/gif",
1702
+ webp: "image/webp",
1703
+ svg: "image/svg+xml",
1704
+ mp4: "video/mp4",
1705
+ webm: "video/webm",
1706
+ mp3: "audio/mpeg",
1707
+ wav: "audio/wav",
1708
+ ogg: "audio/ogg",
516
1709
  pdf: "application/pdf"
517
1710
  };
518
1711
  //# sourceMappingURL=base-provider.js.map