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

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