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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (665) hide show
  1. package/README.md +29 -0
  2. package/dist/.nodetool-build-stamp +1 -0
  3. package/dist/agent-memory.d.ts +118 -0
  4. package/dist/agent-memory.d.ts.map +1 -0
  5. package/dist/agent-memory.js +186 -0
  6. package/dist/agent-memory.js.map +1 -0
  7. package/dist/async-local-storage.d.ts +20 -0
  8. package/dist/async-local-storage.d.ts.map +1 -0
  9. package/dist/async-local-storage.js +28 -0
  10. package/dist/async-local-storage.js.map +1 -0
  11. package/dist/blender-executor.d.ts +93 -0
  12. package/dist/blender-executor.d.ts.map +1 -0
  13. package/dist/blender-executor.js +294 -0
  14. package/dist/blender-executor.js.map +1 -0
  15. package/dist/comfy-executor.d.ts +23 -1
  16. package/dist/comfy-executor.d.ts.map +1 -1
  17. package/dist/comfy-executor.js +234 -55
  18. package/dist/comfy-executor.js.map +1 -1
  19. package/dist/context-packer.d.ts +1 -1
  20. package/dist/context-packer.d.ts.map +1 -1
  21. package/dist/context-packer.js +23 -25
  22. package/dist/context-packer.js.map +1 -1
  23. package/dist/context.d.ts +954 -127
  24. package/dist/context.d.ts.map +1 -1
  25. package/dist/context.js +2424 -513
  26. package/dist/context.js.map +1 -1
  27. package/dist/cost-reconciler.d.ts +32 -0
  28. package/dist/cost-reconciler.d.ts.map +1 -0
  29. package/dist/cost-reconciler.js +21 -0
  30. package/dist/cost-reconciler.js.map +1 -0
  31. package/dist/entities.d.ts +33 -0
  32. package/dist/entities.d.ts.map +1 -0
  33. package/dist/entities.js +43 -0
  34. package/dist/entities.js.map +1 -0
  35. package/dist/external-media-fetch.d.ts +29 -0
  36. package/dist/external-media-fetch.d.ts.map +1 -0
  37. package/dist/external-media-fetch.js +38 -0
  38. package/dist/external-media-fetch.js.map +1 -0
  39. package/dist/generation-receipt.d.ts +86 -0
  40. package/dist/generation-receipt.d.ts.map +1 -0
  41. package/dist/generation-receipt.js +137 -0
  42. package/dist/generation-receipt.js.map +1 -0
  43. package/dist/generation-registry.d.ts +67 -0
  44. package/dist/generation-registry.d.ts.map +1 -0
  45. package/dist/generation-registry.js +126 -0
  46. package/dist/generation-registry.js.map +1 -0
  47. package/dist/google/client.d.ts +196 -0
  48. package/dist/google/client.d.ts.map +1 -0
  49. package/dist/google/client.js +396 -0
  50. package/dist/google/client.js.map +1 -0
  51. package/dist/google/index.d.ts +3 -0
  52. package/dist/google/index.d.ts.map +1 -0
  53. package/dist/google/index.js +3 -0
  54. package/dist/google/index.js.map +1 -0
  55. package/dist/google/token.d.ts +15 -0
  56. package/dist/google/token.d.ts.map +1 -0
  57. package/dist/google/token.js +24 -0
  58. package/dist/google/token.js.map +1 -0
  59. package/dist/host-binaries.d.ts +106 -0
  60. package/dist/host-binaries.d.ts.map +1 -0
  61. package/dist/host-binaries.js +344 -0
  62. package/dist/host-binaries.js.map +1 -0
  63. package/dist/image-codec.d.ts +74 -0
  64. package/dist/image-codec.d.ts.map +1 -0
  65. package/dist/image-codec.js +273 -0
  66. package/dist/image-codec.js.map +1 -0
  67. package/dist/index.d.ts +56 -6
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +43 -4
  70. package/dist/index.js.map +1 -1
  71. package/dist/invocation-account.d.ts +43 -0
  72. package/dist/invocation-account.d.ts.map +1 -0
  73. package/dist/invocation-account.js +60 -0
  74. package/dist/invocation-account.js.map +1 -0
  75. package/dist/media-ref-bytes.d.ts +19 -0
  76. package/dist/media-ref-bytes.d.ts.map +1 -0
  77. package/dist/media-ref-bytes.js +181 -0
  78. package/dist/media-ref-bytes.js.map +1 -0
  79. package/dist/node-executor.d.ts +113 -2
  80. package/dist/node-executor.d.ts.map +1 -1
  81. package/dist/permission-gate.d.ts +152 -0
  82. package/dist/permission-gate.d.ts.map +1 -0
  83. package/dist/permission-gate.js +87 -0
  84. package/dist/permission-gate.js.map +1 -0
  85. package/dist/prefixed-storage-adapter.d.ts +33 -0
  86. package/dist/prefixed-storage-adapter.d.ts.map +1 -0
  87. package/dist/prefixed-storage-adapter.js +70 -0
  88. package/dist/prefixed-storage-adapter.js.map +1 -0
  89. package/dist/prompt-asset-refs.d.ts +222 -0
  90. package/dist/prompt-asset-refs.d.ts.map +1 -0
  91. package/dist/prompt-asset-refs.js +751 -0
  92. package/dist/prompt-asset-refs.js.map +1 -0
  93. package/dist/provider-cache.d.ts +3 -0
  94. package/dist/provider-cache.d.ts.map +1 -0
  95. package/dist/provider-cache.js +8 -0
  96. package/dist/provider-cache.js.map +1 -0
  97. package/dist/providers/aki-provider.d.ts +33 -10
  98. package/dist/providers/aki-provider.d.ts.map +1 -1
  99. package/dist/providers/aki-provider.js +84 -77
  100. package/dist/providers/aki-provider.js.map +1 -1
  101. package/dist/providers/alibaba-provider.d.ts +39 -0
  102. package/dist/providers/alibaba-provider.d.ts.map +1 -0
  103. package/dist/providers/alibaba-provider.js +86 -0
  104. package/dist/providers/alibaba-provider.js.map +1 -0
  105. package/dist/providers/anthropic-provider.d.ts +52 -3
  106. package/dist/providers/anthropic-provider.d.ts.map +1 -1
  107. package/dist/providers/anthropic-provider.js +1020 -338
  108. package/dist/providers/anthropic-provider.js.map +1 -1
  109. package/dist/providers/atlascloud-provider.d.ts +101 -0
  110. package/dist/providers/atlascloud-provider.d.ts.map +1 -0
  111. package/dist/providers/atlascloud-provider.js +1009 -0
  112. package/dist/providers/atlascloud-provider.js.map +1 -0
  113. package/dist/providers/atlascloud-transport.d.ts +100 -0
  114. package/dist/providers/atlascloud-transport.d.ts.map +1 -0
  115. package/dist/providers/atlascloud-transport.js +364 -0
  116. package/dist/providers/atlascloud-transport.js.map +1 -0
  117. package/dist/providers/atlascloud-webhook-registry.d.ts +35 -0
  118. package/dist/providers/atlascloud-webhook-registry.d.ts.map +1 -0
  119. package/dist/providers/atlascloud-webhook-registry.js +104 -0
  120. package/dist/providers/atlascloud-webhook-registry.js.map +1 -0
  121. package/dist/providers/audio-mime.d.ts +11 -0
  122. package/dist/providers/audio-mime.d.ts.map +1 -0
  123. package/dist/providers/audio-mime.js +46 -0
  124. package/dist/providers/audio-mime.js.map +1 -0
  125. package/dist/providers/base-provider.d.ts +481 -24
  126. package/dist/providers/base-provider.d.ts.map +1 -1
  127. package/dist/providers/base-provider.js +1259 -42
  128. package/dist/providers/base-provider.js.map +1 -1
  129. package/dist/providers/cassette-provider.d.ts +217 -0
  130. package/dist/providers/cassette-provider.d.ts.map +1 -0
  131. package/dist/providers/cassette-provider.js +502 -0
  132. package/dist/providers/cassette-provider.js.map +1 -0
  133. package/dist/providers/cerebras-provider.d.ts +6 -12
  134. package/dist/providers/cerebras-provider.d.ts.map +1 -1
  135. package/dist/providers/cerebras-provider.js +8 -33
  136. package/dist/providers/cerebras-provider.js.map +1 -1
  137. package/dist/providers/claude-agent-provider.d.ts +131 -68
  138. package/dist/providers/claude-agent-provider.d.ts.map +1 -1
  139. package/dist/providers/claude-agent-provider.js +1095 -515
  140. package/dist/providers/claude-agent-provider.js.map +1 -1
  141. package/dist/providers/codex-provider.d.ts +82 -0
  142. package/dist/providers/codex-provider.d.ts.map +1 -0
  143. package/dist/providers/codex-provider.js +492 -0
  144. package/dist/providers/codex-provider.js.map +1 -0
  145. package/dist/providers/cohere-provider.d.ts +5 -2
  146. package/dist/providers/cohere-provider.d.ts.map +1 -1
  147. package/dist/providers/cohere-provider.js +9 -2
  148. package/dist/providers/cohere-provider.js.map +1 -1
  149. package/dist/providers/comfy-cloud-provider.d.ts +30 -0
  150. package/dist/providers/comfy-cloud-provider.d.ts.map +1 -0
  151. package/dist/providers/comfy-cloud-provider.js +42 -0
  152. package/dist/providers/comfy-cloud-provider.js.map +1 -0
  153. package/dist/providers/content-filter.d.ts +46 -0
  154. package/dist/providers/content-filter.d.ts.map +1 -0
  155. package/dist/providers/content-filter.js +92 -0
  156. package/dist/providers/content-filter.js.map +1 -0
  157. package/dist/providers/context-exceeded.d.ts +37 -0
  158. package/dist/providers/context-exceeded.d.ts.map +1 -0
  159. package/dist/providers/context-exceeded.js +114 -0
  160. package/dist/providers/context-exceeded.js.map +1 -0
  161. package/dist/providers/context-window.d.ts +44 -0
  162. package/dist/providers/context-window.d.ts.map +1 -0
  163. package/dist/providers/context-window.js +145 -0
  164. package/dist/providers/context-window.js.map +1 -0
  165. package/dist/providers/contract/index.d.ts +4 -0
  166. package/dist/providers/contract/index.d.ts.map +1 -0
  167. package/dist/providers/contract/index.js +4 -0
  168. package/dist/providers/contract/index.js.map +1 -0
  169. package/dist/providers/contract/probe-manifest.d.ts +75 -0
  170. package/dist/providers/contract/probe-manifest.d.ts.map +1 -0
  171. package/dist/providers/contract/probe-manifest.js +379 -0
  172. package/dist/providers/contract/probe-manifest.js.map +1 -0
  173. package/dist/providers/contract/probe-runner.d.ts +57 -0
  174. package/dist/providers/contract/probe-runner.d.ts.map +1 -0
  175. package/dist/providers/contract/probe-runner.js +180 -0
  176. package/dist/providers/contract/probe-runner.js.map +1 -0
  177. package/dist/providers/contract/redact.d.ts +29 -0
  178. package/dist/providers/contract/redact.d.ts.map +1 -0
  179. package/dist/providers/contract/redact.js +113 -0
  180. package/dist/providers/contract/redact.js.map +1 -0
  181. package/dist/providers/core-tools.d.ts +74 -0
  182. package/dist/providers/core-tools.d.ts.map +1 -0
  183. package/dist/providers/core-tools.js +127 -0
  184. package/dist/providers/core-tools.js.map +1 -0
  185. package/dist/providers/cost-calculator.d.ts +69 -50
  186. package/dist/providers/cost-calculator.d.ts.map +1 -1
  187. package/dist/providers/cost-calculator.js +207 -397
  188. package/dist/providers/cost-calculator.js.map +1 -1
  189. package/dist/providers/credential-check.d.ts +45 -0
  190. package/dist/providers/credential-check.d.ts.map +1 -0
  191. package/dist/providers/credential-check.js +159 -0
  192. package/dist/providers/credential-check.js.map +1 -0
  193. package/dist/providers/custom-model-kinds.d.ts +33 -0
  194. package/dist/providers/custom-model-kinds.d.ts.map +1 -0
  195. package/dist/providers/custom-model-kinds.js +96 -0
  196. package/dist/providers/custom-model-kinds.js.map +1 -0
  197. package/dist/providers/custom-openai-provider.d.ts +81 -0
  198. package/dist/providers/custom-openai-provider.d.ts.map +1 -0
  199. package/dist/providers/custom-openai-provider.js +182 -0
  200. package/dist/providers/custom-openai-provider.js.map +1 -0
  201. package/dist/providers/custom-provider-registry.d.ts +19 -0
  202. package/dist/providers/custom-provider-registry.d.ts.map +1 -0
  203. package/dist/providers/custom-provider-registry.js +52 -0
  204. package/dist/providers/custom-provider-registry.js.map +1 -0
  205. package/dist/providers/deepseek-provider.d.ts +8 -14
  206. package/dist/providers/deepseek-provider.d.ts.map +1 -1
  207. package/dist/providers/deepseek-provider.js +6 -35
  208. package/dist/providers/deepseek-provider.js.map +1 -1
  209. package/dist/providers/defaults.d.ts +12 -0
  210. package/dist/providers/defaults.d.ts.map +1 -0
  211. package/dist/providers/defaults.js +12 -0
  212. package/dist/providers/defaults.js.map +1 -0
  213. package/dist/providers/elevenlabs-provider.d.ts +49 -0
  214. package/dist/providers/elevenlabs-provider.d.ts.map +1 -0
  215. package/dist/providers/elevenlabs-provider.js +142 -0
  216. package/dist/providers/elevenlabs-provider.js.map +1 -0
  217. package/dist/providers/entity-references.d.ts +31 -0
  218. package/dist/providers/entity-references.d.ts.map +1 -0
  219. package/dist/providers/entity-references.js +113 -0
  220. package/dist/providers/entity-references.js.map +1 -0
  221. package/dist/providers/evolink-provider.d.ts +38 -0
  222. package/dist/providers/evolink-provider.d.ts.map +1 -0
  223. package/dist/providers/evolink-provider.js +304 -0
  224. package/dist/providers/evolink-provider.js.map +1 -0
  225. package/dist/providers/fake-provider.d.ts +72 -1
  226. package/dist/providers/fake-provider.d.ts.map +1 -1
  227. package/dist/providers/fake-provider.js +203 -1
  228. package/dist/providers/fake-provider.js.map +1 -1
  229. package/dist/providers/fal-generations.d.ts +106 -0
  230. package/dist/providers/fal-generations.d.ts.map +1 -0
  231. package/dist/providers/fal-generations.js +286 -0
  232. package/dist/providers/fal-generations.js.map +1 -0
  233. package/dist/providers/fal-provider.d.ts +171 -7
  234. package/dist/providers/fal-provider.d.ts.map +1 -1
  235. package/dist/providers/fal-provider.js +1369 -64
  236. package/dist/providers/fal-provider.js.map +1 -1
  237. package/dist/providers/fal-queue.d.ts +47 -0
  238. package/dist/providers/fal-queue.d.ts.map +1 -0
  239. package/dist/providers/fal-queue.js +231 -0
  240. package/dist/providers/fal-queue.js.map +1 -0
  241. package/dist/providers/gemini-provider.d.ts +148 -8
  242. package/dist/providers/gemini-provider.d.ts.map +1 -1
  243. package/dist/providers/gemini-provider.js +1500 -316
  244. package/dist/providers/gemini-provider.js.map +1 -1
  245. package/dist/providers/gmi-provider.d.ts +21 -0
  246. package/dist/providers/gmi-provider.d.ts.map +1 -0
  247. package/dist/providers/gmi-provider.js +34 -0
  248. package/dist/providers/gmi-provider.js.map +1 -0
  249. package/dist/providers/gpt-image-size.d.ts +3 -0
  250. package/dist/providers/gpt-image-size.d.ts.map +1 -0
  251. package/dist/providers/gpt-image-size.js +37 -0
  252. package/dist/providers/gpt-image-size.js.map +1 -0
  253. package/dist/providers/groq-provider.d.ts +9 -12
  254. package/dist/providers/groq-provider.d.ts.map +1 -1
  255. package/dist/providers/groq-provider.js +19 -33
  256. package/dist/providers/groq-provider.js.map +1 -1
  257. package/dist/providers/groq-request.d.ts +38 -0
  258. package/dist/providers/groq-request.d.ts.map +1 -0
  259. package/dist/providers/groq-request.js +138 -0
  260. package/dist/providers/groq-request.js.map +1 -0
  261. package/dist/providers/higgsfield-provider.d.ts +30 -0
  262. package/dist/providers/higgsfield-provider.d.ts.map +1 -0
  263. package/dist/providers/higgsfield-provider.js +131 -0
  264. package/dist/providers/higgsfield-provider.js.map +1 -0
  265. package/dist/providers/higgsfield-transport.d.ts +106 -0
  266. package/dist/providers/higgsfield-transport.d.ts.map +1 -0
  267. package/dist/providers/higgsfield-transport.js +182 -0
  268. package/dist/providers/higgsfield-transport.js.map +1 -0
  269. package/dist/providers/http-transport.d.ts +74 -0
  270. package/dist/providers/http-transport.d.ts.map +1 -0
  271. package/dist/providers/http-transport.js +152 -0
  272. package/dist/providers/http-transport.js.map +1 -0
  273. package/dist/providers/huggingface-provider.d.ts +71 -3
  274. package/dist/providers/huggingface-provider.d.ts.map +1 -1
  275. package/dist/providers/huggingface-provider.js +230 -34
  276. package/dist/providers/huggingface-provider.js.map +1 -1
  277. package/dist/providers/image-mime.d.ts +39 -0
  278. package/dist/providers/image-mime.d.ts.map +1 -0
  279. package/dist/providers/image-mime.js +111 -0
  280. package/dist/providers/image-mime.js.map +1 -0
  281. package/dist/providers/image-ref.d.ts +21 -0
  282. package/dist/providers/image-ref.d.ts.map +1 -0
  283. package/dist/providers/image-ref.js +44 -0
  284. package/dist/providers/image-ref.js.map +1 -0
  285. package/dist/providers/index.d.ts +84 -8
  286. package/dist/providers/index.d.ts.map +1 -1
  287. package/dist/providers/index.js +239 -56
  288. package/dist/providers/index.js.map +1 -1
  289. package/dist/providers/jina-provider.d.ts +10 -2
  290. package/dist/providers/jina-provider.d.ts.map +1 -1
  291. package/dist/providers/jina-provider.js +16 -7
  292. package/dist/providers/jina-provider.js.map +1 -1
  293. package/dist/providers/kie-provider.d.ts +170 -9
  294. package/dist/providers/kie-provider.d.ts.map +1 -1
  295. package/dist/providers/kie-provider.js +1176 -228
  296. package/dist/providers/kie-provider.js.map +1 -1
  297. package/dist/providers/kie-webhook-registry.d.ts +28 -0
  298. package/dist/providers/kie-webhook-registry.d.ts.map +1 -0
  299. package/dist/providers/kie-webhook-registry.js +91 -0
  300. package/dist/providers/kie-webhook-registry.js.map +1 -0
  301. package/dist/providers/llama-provider.d.ts +21 -37
  302. package/dist/providers/llama-provider.d.ts.map +1 -1
  303. package/dist/providers/llama-provider.js +43 -354
  304. package/dist/providers/llama-provider.js.map +1 -1
  305. package/dist/providers/lmstudio-provider.d.ts +4 -7
  306. package/dist/providers/lmstudio-provider.d.ts.map +1 -1
  307. package/dist/providers/lmstudio-provider.js +24 -15
  308. package/dist/providers/lmstudio-provider.js.map +1 -1
  309. package/dist/providers/manifest-models.d.ts +341 -1
  310. package/dist/providers/manifest-models.d.ts.map +1 -1
  311. package/dist/providers/manifest-models.js +1106 -22
  312. package/dist/providers/manifest-models.js.map +1 -1
  313. package/dist/providers/media-mime.d.ts +14 -0
  314. package/dist/providers/media-mime.d.ts.map +1 -0
  315. package/dist/providers/media-mime.js +35 -0
  316. package/dist/providers/media-mime.js.map +1 -0
  317. package/dist/providers/meshy-provider.d.ts +14 -1
  318. package/dist/providers/meshy-provider.d.ts.map +1 -1
  319. package/dist/providers/meshy-provider.js +76 -46
  320. package/dist/providers/meshy-provider.js.map +1 -1
  321. package/dist/providers/meta-provider.d.ts +22 -0
  322. package/dist/providers/meta-provider.d.ts.map +1 -0
  323. package/dist/providers/meta-provider.js +54 -0
  324. package/dist/providers/meta-provider.js.map +1 -0
  325. package/dist/providers/minimax-provider.d.ts +25 -18
  326. package/dist/providers/minimax-provider.d.ts.map +1 -1
  327. package/dist/providers/minimax-provider.js +211 -72
  328. package/dist/providers/minimax-provider.js.map +1 -1
  329. package/dist/providers/mistral-provider.d.ts +6 -12
  330. package/dist/providers/mistral-provider.d.ts.map +1 -1
  331. package/dist/providers/mistral-provider.js +10 -34
  332. package/dist/providers/mistral-provider.js.map +1 -1
  333. package/dist/providers/moonshot-provider.d.ts +11 -15
  334. package/dist/providers/moonshot-provider.d.ts.map +1 -1
  335. package/dist/providers/moonshot-provider.js +33 -23
  336. package/dist/providers/moonshot-provider.js.map +1 -1
  337. package/dist/providers/node-llama-cpp-provider.d.ts +98 -0
  338. package/dist/providers/node-llama-cpp-provider.d.ts.map +1 -0
  339. package/dist/providers/node-llama-cpp-provider.js +479 -0
  340. package/dist/providers/node-llama-cpp-provider.js.map +1 -0
  341. package/dist/providers/nodetool-provider.d.ts +49 -0
  342. package/dist/providers/nodetool-provider.d.ts.map +1 -0
  343. package/dist/providers/nodetool-provider.js +224 -0
  344. package/dist/providers/nodetool-provider.js.map +1 -0
  345. package/dist/providers/oauth/browser-launcher.d.ts +23 -0
  346. package/dist/providers/oauth/browser-launcher.d.ts.map +1 -0
  347. package/dist/providers/oauth/browser-launcher.js +45 -0
  348. package/dist/providers/oauth/browser-launcher.js.map +1 -0
  349. package/dist/providers/oauth/claude-code-credentials.d.ts +74 -0
  350. package/dist/providers/oauth/claude-code-credentials.d.ts.map +1 -0
  351. package/dist/providers/oauth/claude-code-credentials.js +133 -0
  352. package/dist/providers/oauth/claude-code-credentials.js.map +1 -0
  353. package/dist/providers/oauth/claude-code-login.d.ts +124 -0
  354. package/dist/providers/oauth/claude-code-login.d.ts.map +1 -0
  355. package/dist/providers/oauth/claude-code-login.js +230 -0
  356. package/dist/providers/oauth/claude-code-login.js.map +1 -0
  357. package/dist/providers/oauth/claude-code-oauth-client.d.ts +111 -0
  358. package/dist/providers/oauth/claude-code-oauth-client.d.ts.map +1 -0
  359. package/dist/providers/oauth/claude-code-oauth-client.js +207 -0
  360. package/dist/providers/oauth/claude-code-oauth-client.js.map +1 -0
  361. package/dist/providers/oauth/errors.d.ts +54 -0
  362. package/dist/providers/oauth/errors.d.ts.map +1 -0
  363. package/dist/providers/oauth/errors.js +79 -0
  364. package/dist/providers/oauth/errors.js.map +1 -0
  365. package/dist/providers/oauth/index.d.ts +105 -0
  366. package/dist/providers/oauth/index.d.ts.map +1 -0
  367. package/dist/providers/oauth/index.js +167 -0
  368. package/dist/providers/oauth/index.js.map +1 -0
  369. package/dist/providers/oauth/jwt-claims.d.ts +23 -0
  370. package/dist/providers/oauth/jwt-claims.d.ts.map +1 -0
  371. package/dist/providers/oauth/jwt-claims.js +67 -0
  372. package/dist/providers/oauth/jwt-claims.js.map +1 -0
  373. package/dist/providers/oauth/local-callback-server.d.ts +60 -0
  374. package/dist/providers/oauth/local-callback-server.d.ts.map +1 -0
  375. package/dist/providers/oauth/local-callback-server.js +148 -0
  376. package/dist/providers/oauth/local-callback-server.js.map +1 -0
  377. package/dist/providers/oauth/oauth-client.d.ts +55 -0
  378. package/dist/providers/oauth/oauth-client.d.ts.map +1 -0
  379. package/dist/providers/oauth/oauth-client.js +161 -0
  380. package/dist/providers/oauth/oauth-client.js.map +1 -0
  381. package/dist/providers/oauth/openai-oauth-provider.d.ts +129 -0
  382. package/dist/providers/oauth/openai-oauth-provider.d.ts.map +1 -0
  383. package/dist/providers/oauth/openai-oauth-provider.js +265 -0
  384. package/dist/providers/oauth/openai-oauth-provider.js.map +1 -0
  385. package/dist/providers/oauth/pkce-helper.d.ts +43 -0
  386. package/dist/providers/oauth/pkce-helper.d.ts.map +1 -0
  387. package/dist/providers/oauth/pkce-helper.js +57 -0
  388. package/dist/providers/oauth/pkce-helper.js.map +1 -0
  389. package/dist/providers/oauth/redaction.d.ts +22 -0
  390. package/dist/providers/oauth/redaction.d.ts.map +1 -0
  391. package/dist/providers/oauth/redaction.js +59 -0
  392. package/dist/providers/oauth/redaction.js.map +1 -0
  393. package/dist/providers/oauth/secure-credential-store.d.ts +67 -0
  394. package/dist/providers/oauth/secure-credential-store.d.ts.map +1 -0
  395. package/dist/providers/oauth/secure-credential-store.js +73 -0
  396. package/dist/providers/oauth/secure-credential-store.js.map +1 -0
  397. package/dist/providers/oauth/token-store.d.ts +53 -0
  398. package/dist/providers/oauth/token-store.d.ts.map +1 -0
  399. package/dist/providers/oauth/token-store.js +78 -0
  400. package/dist/providers/oauth/token-store.js.map +1 -0
  401. package/dist/providers/oauth/types.d.ts +64 -0
  402. package/dist/providers/oauth/types.d.ts.map +1 -0
  403. package/dist/providers/oauth/types.js +12 -0
  404. package/dist/providers/oauth/types.js.map +1 -0
  405. package/dist/providers/object-tracking.d.ts +47 -0
  406. package/dist/providers/object-tracking.d.ts.map +1 -0
  407. package/dist/providers/object-tracking.js +46 -0
  408. package/dist/providers/object-tracking.js.map +1 -0
  409. package/dist/providers/offline-model-index.d.ts +20 -0
  410. package/dist/providers/offline-model-index.d.ts.map +1 -0
  411. package/dist/providers/offline-model-index.js +112 -0
  412. package/dist/providers/offline-model-index.js.map +1 -0
  413. package/dist/providers/ollama-provider.d.ts +28 -1
  414. package/dist/providers/ollama-provider.d.ts.map +1 -1
  415. package/dist/providers/ollama-provider.js +193 -43
  416. package/dist/providers/ollama-provider.js.map +1 -1
  417. package/dist/providers/openai-compat/client.d.ts +57 -0
  418. package/dist/providers/openai-compat/client.d.ts.map +1 -0
  419. package/dist/providers/openai-compat/client.js +157 -0
  420. package/dist/providers/openai-compat/client.js.map +1 -0
  421. package/dist/providers/openai-compat/decode.d.ts +36 -0
  422. package/dist/providers/openai-compat/decode.d.ts.map +1 -0
  423. package/dist/providers/openai-compat/decode.js +55 -0
  424. package/dist/providers/openai-compat/decode.js.map +1 -0
  425. package/dist/providers/openai-compat/errors.d.ts +54 -0
  426. package/dist/providers/openai-compat/errors.d.ts.map +1 -0
  427. package/dist/providers/openai-compat/errors.js +144 -0
  428. package/dist/providers/openai-compat/errors.js.map +1 -0
  429. package/dist/providers/openai-compat/index.d.ts +8 -0
  430. package/dist/providers/openai-compat/index.d.ts.map +1 -0
  431. package/dist/providers/openai-compat/index.js +5 -0
  432. package/dist/providers/openai-compat/index.js.map +1 -0
  433. package/dist/providers/openai-compat/sse.d.ts +14 -0
  434. package/dist/providers/openai-compat/sse.d.ts.map +1 -0
  435. package/dist/providers/openai-compat/sse.js +69 -0
  436. package/dist/providers/openai-compat/sse.js.map +1 -0
  437. package/dist/providers/openai-compat/types.d.ts +75 -0
  438. package/dist/providers/openai-compat/types.d.ts.map +1 -0
  439. package/dist/providers/openai-compat/types.js +9 -0
  440. package/dist/providers/openai-compat/types.js.map +1 -0
  441. package/dist/providers/openai-compat-provider.d.ts +90 -0
  442. package/dist/providers/openai-compat-provider.d.ts.map +1 -0
  443. package/dist/providers/openai-compat-provider.js +282 -0
  444. package/dist/providers/openai-compat-provider.js.map +1 -0
  445. package/dist/providers/openai-provider.d.ts +89 -26
  446. package/dist/providers/openai-provider.d.ts.map +1 -1
  447. package/dist/providers/openai-provider.js +990 -105
  448. package/dist/providers/openai-provider.js.map +1 -1
  449. package/dist/providers/openrouter-provider.d.ts +6 -12
  450. package/dist/providers/openrouter-provider.d.ts.map +1 -1
  451. package/dist/providers/openrouter-provider.js +24 -53
  452. package/dist/providers/openrouter-provider.js.map +1 -1
  453. package/dist/providers/provider-call-failure.d.ts +39 -0
  454. package/dist/providers/provider-call-failure.d.ts.map +1 -0
  455. package/dist/providers/provider-call-failure.js +132 -0
  456. package/dist/providers/provider-call-failure.js.map +1 -0
  457. package/dist/providers/provider-error.d.ts +59 -0
  458. package/dist/providers/provider-error.d.ts.map +1 -0
  459. package/dist/providers/provider-error.js +165 -0
  460. package/dist/providers/provider-error.js.map +1 -0
  461. package/dist/providers/provider-generations.d.ts +99 -0
  462. package/dist/providers/provider-generations.d.ts.map +1 -0
  463. package/dist/providers/provider-generations.js +71 -0
  464. package/dist/providers/provider-generations.js.map +1 -0
  465. package/dist/providers/provider-queue.d.ts +62 -0
  466. package/dist/providers/provider-queue.d.ts.map +1 -0
  467. package/dist/providers/provider-queue.js +28 -0
  468. package/dist/providers/provider-queue.js.map +1 -0
  469. package/dist/providers/provider-registry.d.ts +33 -14
  470. package/dist/providers/provider-registry.d.ts.map +1 -1
  471. package/dist/providers/provider-registry.js +85 -68
  472. package/dist/providers/provider-registry.js.map +1 -1
  473. package/dist/providers/provider-request-log.d.ts +53 -0
  474. package/dist/providers/provider-request-log.d.ts.map +1 -0
  475. package/dist/providers/provider-request-log.js +157 -0
  476. package/dist/providers/provider-request-log.js.map +1 -0
  477. package/dist/providers/provider-session.d.ts +3 -0
  478. package/dist/providers/provider-session.d.ts.map +1 -0
  479. package/dist/providers/provider-session.js +10 -0
  480. package/dist/providers/provider-session.js.map +1 -0
  481. package/dist/providers/provider-transport.d.ts +11 -0
  482. package/dist/providers/provider-transport.d.ts.map +1 -0
  483. package/dist/providers/provider-transport.js +11 -0
  484. package/dist/providers/provider-transport.js.map +1 -0
  485. package/dist/providers/python-provider.d.ts +20 -12
  486. package/dist/providers/python-provider.d.ts.map +1 -1
  487. package/dist/providers/python-provider.js +185 -25
  488. package/dist/providers/python-provider.js.map +1 -1
  489. package/dist/providers/replicate-provider.d.ts +89 -7
  490. package/dist/providers/replicate-provider.d.ts.map +1 -1
  491. package/dist/providers/replicate-provider.js +801 -65
  492. package/dist/providers/replicate-provider.js.map +1 -1
  493. package/dist/providers/replicate-retry.d.ts +30 -0
  494. package/dist/providers/replicate-retry.d.ts.map +1 -0
  495. package/dist/providers/replicate-retry.js +97 -0
  496. package/dist/providers/replicate-retry.js.map +1 -0
  497. package/dist/providers/responses-api.d.ts +27 -0
  498. package/dist/providers/responses-api.d.ts.map +1 -0
  499. package/dist/providers/responses-api.js +297 -0
  500. package/dist/providers/responses-api.js.map +1 -0
  501. package/dist/providers/reve-provider.d.ts +45 -0
  502. package/dist/providers/reve-provider.d.ts.map +1 -0
  503. package/dist/providers/reve-provider.js +159 -0
  504. package/dist/providers/reve-provider.js.map +1 -0
  505. package/dist/providers/rodin-provider.d.ts +23 -1
  506. package/dist/providers/rodin-provider.d.ts.map +1 -1
  507. package/dist/providers/rodin-provider.js +89 -51
  508. package/dist/providers/rodin-provider.js.map +1 -1
  509. package/dist/providers/safe-url.d.ts +70 -0
  510. package/dist/providers/safe-url.d.ts.map +1 -0
  511. package/dist/providers/safe-url.js +277 -0
  512. package/dist/providers/safe-url.js.map +1 -0
  513. package/dist/providers/scripted-provider.d.ts +9 -0
  514. package/dist/providers/scripted-provider.d.ts.map +1 -1
  515. package/dist/providers/scripted-provider.js +86 -10
  516. package/dist/providers/scripted-provider.js.map +1 -1
  517. package/dist/providers/structured-output.d.ts +42 -0
  518. package/dist/providers/structured-output.d.ts.map +1 -0
  519. package/dist/providers/structured-output.js +83 -0
  520. package/dist/providers/structured-output.js.map +1 -0
  521. package/dist/providers/together-provider.d.ts +19 -16
  522. package/dist/providers/together-provider.d.ts.map +1 -1
  523. package/dist/providers/together-provider.js +103 -225
  524. package/dist/providers/together-provider.js.map +1 -1
  525. package/dist/providers/topaz-provider.d.ts +44 -0
  526. package/dist/providers/topaz-provider.d.ts.map +1 -0
  527. package/dist/providers/topaz-provider.js +239 -0
  528. package/dist/providers/topaz-provider.js.map +1 -0
  529. package/dist/providers/types.d.ts +603 -4
  530. package/dist/providers/types.d.ts.map +1 -1
  531. package/dist/providers/types.js +81 -1
  532. package/dist/providers/types.js.map +1 -1
  533. package/dist/providers/useapi-provider.d.ts +27 -0
  534. package/dist/providers/useapi-provider.d.ts.map +1 -0
  535. package/dist/providers/useapi-provider.js +266 -0
  536. package/dist/providers/useapi-provider.js.map +1 -0
  537. package/dist/providers/video-duration.d.ts +6 -0
  538. package/dist/providers/video-duration.d.ts.map +1 -0
  539. package/dist/providers/video-duration.js +63 -0
  540. package/dist/providers/video-duration.js.map +1 -0
  541. package/dist/providers/video-frame-fallback.d.ts +39 -0
  542. package/dist/providers/video-frame-fallback.d.ts.map +1 -0
  543. package/dist/providers/video-frame-fallback.js +155 -0
  544. package/dist/providers/video-frame-fallback.js.map +1 -0
  545. package/dist/providers/video-frames.d.ts +61 -0
  546. package/dist/providers/video-frames.d.ts.map +1 -0
  547. package/dist/providers/video-frames.js +208 -0
  548. package/dist/providers/video-frames.js.map +1 -0
  549. package/dist/providers/video-mime.d.ts +14 -0
  550. package/dist/providers/video-mime.d.ts.map +1 -0
  551. package/dist/providers/video-mime.js +44 -0
  552. package/dist/providers/video-mime.js.map +1 -0
  553. package/dist/providers/video-to-audio.d.ts +34 -0
  554. package/dist/providers/video-to-audio.d.ts.map +1 -0
  555. package/dist/providers/video-to-audio.js +62 -0
  556. package/dist/providers/video-to-audio.js.map +1 -0
  557. package/dist/providers/vllm-provider.d.ts +22 -8
  558. package/dist/providers/vllm-provider.d.ts.map +1 -1
  559. package/dist/providers/vllm-provider.js +48 -23
  560. package/dist/providers/vllm-provider.js.map +1 -1
  561. package/dist/providers/voyage-provider.d.ts +10 -2
  562. package/dist/providers/voyage-provider.d.ts.map +1 -1
  563. package/dist/providers/voyage-provider.js +16 -7
  564. package/dist/providers/voyage-provider.js.map +1 -1
  565. package/dist/providers/xai-provider.d.ts +54 -13
  566. package/dist/providers/xai-provider.d.ts.map +1 -1
  567. package/dist/providers/xai-provider.js +299 -22
  568. package/dist/providers/xai-provider.js.map +1 -1
  569. package/dist/python-bridge-base.d.ts +358 -0
  570. package/dist/python-bridge-base.d.ts.map +1 -0
  571. package/dist/python-bridge-base.js +1557 -0
  572. package/dist/python-bridge-base.js.map +1 -0
  573. package/dist/python-bridge-codec.d.ts +28 -0
  574. package/dist/python-bridge-codec.d.ts.map +1 -0
  575. package/dist/python-bridge-codec.js +39 -0
  576. package/dist/python-bridge-codec.js.map +1 -0
  577. package/dist/python-bridge-factory.d.ts +52 -0
  578. package/dist/python-bridge-factory.d.ts.map +1 -0
  579. package/dist/python-bridge-factory.js +69 -0
  580. package/dist/python-bridge-factory.js.map +1 -0
  581. package/dist/python-bridge-framing.d.ts +68 -0
  582. package/dist/python-bridge-framing.d.ts.map +1 -0
  583. package/dist/python-bridge-framing.js +91 -0
  584. package/dist/python-bridge-framing.js.map +1 -0
  585. package/dist/python-bridge-types.d.ts +545 -2
  586. package/dist/python-bridge-types.d.ts.map +1 -1
  587. package/dist/python-bridge-types.js +49 -1
  588. package/dist/python-bridge-types.js.map +1 -1
  589. package/dist/python-graph-resolver.d.ts +57 -0
  590. package/dist/python-graph-resolver.d.ts.map +1 -0
  591. package/dist/python-graph-resolver.js +79 -0
  592. package/dist/python-graph-resolver.js.map +1 -0
  593. package/dist/python-node-executor.d.ts +36 -4
  594. package/dist/python-node-executor.d.ts.map +1 -1
  595. package/dist/python-node-executor.js +177 -86
  596. package/dist/python-node-executor.js.map +1 -1
  597. package/dist/python-stdio-bridge.d.ts +18 -37
  598. package/dist/python-stdio-bridge.d.ts.map +1 -1
  599. package/dist/python-stdio-bridge.js +148 -475
  600. package/dist/python-stdio-bridge.js.map +1 -1
  601. package/dist/python-websocket-bridge.d.ts +140 -0
  602. package/dist/python-websocket-bridge.d.ts.map +1 -0
  603. package/dist/python-websocket-bridge.js +561 -0
  604. package/dist/python-websocket-bridge.js.map +1 -0
  605. package/dist/python-worker-stderr.d.ts +4 -0
  606. package/dist/python-worker-stderr.d.ts.map +1 -0
  607. package/dist/python-worker-stderr.js +24 -0
  608. package/dist/python-worker-stderr.js.map +1 -0
  609. package/dist/recommended-models.d.ts +2 -2
  610. package/dist/recommended-models.d.ts.map +1 -1
  611. package/dist/recommended-models.js +24 -4
  612. package/dist/recommended-models.js.map +1 -1
  613. package/dist/recoverable-node-error.d.ts +31 -0
  614. package/dist/recoverable-node-error.d.ts.map +1 -0
  615. package/dist/recoverable-node-error.js +37 -0
  616. package/dist/recoverable-node-error.js.map +1 -0
  617. package/dist/redact-params.d.ts +13 -0
  618. package/dist/redact-params.d.ts.map +1 -0
  619. package/dist/redact-params.js +57 -0
  620. package/dist/redact-params.js.map +1 -0
  621. package/dist/sandbox-module-catalog.d.ts +43 -0
  622. package/dist/sandbox-module-catalog.d.ts.map +1 -0
  623. package/dist/sandbox-module-catalog.js +25 -0
  624. package/dist/sandbox-module-catalog.js.map +1 -0
  625. package/dist/storage-workspace.d.ts +59 -0
  626. package/dist/storage-workspace.d.ts.map +1 -0
  627. package/dist/storage-workspace.js +426 -0
  628. package/dist/storage-workspace.js.map +1 -0
  629. package/dist/swappable-python-bridge.d.ts +83 -0
  630. package/dist/swappable-python-bridge.d.ts.map +1 -0
  631. package/dist/swappable-python-bridge.js +246 -0
  632. package/dist/swappable-python-bridge.js.map +1 -0
  633. package/dist/telemetry.d.ts +7 -0
  634. package/dist/telemetry.d.ts.map +1 -1
  635. package/dist/telemetry.js +23 -0
  636. package/dist/telemetry.js.map +1 -1
  637. package/dist/testing.d.ts +81 -0
  638. package/dist/testing.d.ts.map +1 -0
  639. package/dist/testing.js +130 -0
  640. package/dist/testing.js.map +1 -0
  641. package/dist/token-counter.d.ts +30 -0
  642. package/dist/token-counter.d.ts.map +1 -0
  643. package/dist/token-counter.js +156 -0
  644. package/dist/token-counter.js.map +1 -0
  645. package/dist/trace-exporters.d.ts +10 -0
  646. package/dist/trace-exporters.d.ts.map +1 -1
  647. package/dist/trace-exporters.js +59 -20
  648. package/dist/trace-exporters.js.map +1 -1
  649. package/dist/tracing-helpers.d.ts +45 -4
  650. package/dist/tracing-helpers.d.ts.map +1 -1
  651. package/dist/tracing-helpers.js +62 -19
  652. package/dist/tracing-helpers.js.map +1 -1
  653. package/dist/turn-budget.d.ts +278 -0
  654. package/dist/turn-budget.d.ts.map +1 -0
  655. package/dist/turn-budget.js +365 -0
  656. package/dist/turn-budget.js.map +1 -0
  657. package/dist/variable-channel.d.ts +48 -0
  658. package/dist/variable-channel.d.ts.map +1 -0
  659. package/dist/variable-channel.js +110 -0
  660. package/dist/variable-channel.js.map +1 -0
  661. package/dist/workspace.d.ts +119 -0
  662. package/dist/workspace.d.ts.map +1 -0
  663. package/dist/workspace.js +112 -0
  664. package/dist/workspace.js.map +1 -0
  665. package/package.json +107 -19
@@ -1,601 +1,1181 @@
1
1
  /**
2
- * Claude Agent Provider
2
+ * ClaudeAgentProvider — Claude reached through the official
3
+ * `@anthropic-ai/claude-agent-sdk` instead of an API key.
3
4
  *
4
- * Uses the Claude Agent SDK (@anthropic-ai/claude-agent-sdk) to interact with
5
- * Claude through the user's Claude subscription (Claude Pro/Max/Team).
5
+ * Unlike {@link AnthropicProvider}, this provider sends no `ANTHROPIC_API_KEY`:
6
+ * the SDK drives the bundled `claude` binary (shipped as an optional platform
7
+ * dependency, so there is nothing to install on PATH) and lets it authenticate
8
+ * with the machine's logged-in Claude subscription (the credentials stored
9
+ * under `~/.claude`). The SDK streams `SDKMessage`s, which we translate into the
10
+ * cross-provider {@link ProviderStreamItem} stream.
6
11
  *
7
- * Requirements:
8
- * - Claude Code CLI installed and authenticated (`claude` command available)
9
- * - Active Claude subscription
10
- * - @anthropic-ai/claude-agent-sdk package installed
12
+ * `@anthropic-ai/claude-agent-sdk` is a *soft* (optional peer) dependency: it is
13
+ * not installed by default and must be added with the package manager before
14
+ * this provider can run. It is loaded lazily via {@link loadSdk}, so a missing
15
+ * package surfaces as a clear install hint only when the provider is actually
16
+ * used — the rest of the runtime, and the browser worker bundle, never need it.
11
17
  *
12
- * This provider does NOT require an ANTHROPIC_API_KEY — it uses the Claude Code
13
- * session which authenticates through the user's Claude subscription.
18
+ * It is a *pure LLM* provider: the agentic tool loop is collapsed to a single
19
+ * turn with every built-in tool disabled (`allowedTools: []`), filesystem
20
+ * settings/skills disabled (`settingSources: []`), permission prompts neutered
21
+ * (`permissionMode: "dontAsk"`), and the default agent system prompt fully
22
+ * replaced (`systemPrompt`), so the model behaves like a plain chat completion —
23
+ * text in, text (and thinking) out.
14
24
  *
15
- * Tool support: tools are exposed as an in-process MCP server via the SDK's
16
- * createSdkMcpServer() + tool() helpers. Claude calls tools natively through
17
- * the MCP protocol; the provider yields ToolCall items for tracking.
18
- * An onToolCall callback must be supplied for tool execution.
25
+ * Session continuity: when a `threadId` is supplied the provider routes the
26
+ * thread through a single upstream session and, on subsequent turns, resumes it
27
+ * (`options.resume`) and sends only the new user delta. The continuation token
28
+ * ({@link ProviderSession}) is surfaced as a {@link ProviderSessionUpdate} and
29
+ * persisted by the chat layer onto the assistant message; the in-memory map
30
+ * below is only a within-process cache (the DB column is authoritative).
19
31
  */
20
- import { BaseProvider } from "./base-provider.js";
21
- import { createLogger } from "@nodetool-ai/config";
32
+ import * as fs from "node:fs/promises";
33
+ import * as os from "node:os";
34
+ import * as path from "node:path";
35
+ import { createLogger, importOptionalModule } from "@nodetool-ai/config";
22
36
  import { z } from "zod";
23
- const log = createLogger("nodetool.runtime.providers.claude_agent");
24
- const MCP_SERVER_NAME = "nodetool-tools";
37
+ import { BaseProvider, budgetStopItem, resolveTurnBudget } from "./base-provider.js";
38
+ import { sdkNativeReplacements } from "./core-tools.js";
39
+ import { isProviderMessageEvent, isProviderSessionUpdate, isProviderStop } from "./types.js";
40
+ import { hashSystemPrompt } from "./provider-session.js";
41
+ import { PROVIDER_IDS, isFiniteNumber, isNonEmptyString, isObjectLike, isString } from "@nodetool-ai/protocol";
42
+ const log = createLogger("nodetool.runtime.providers.claude-agent");
43
+ /** Replacement system prompt used when the caller supplies none. */
44
+ const DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant.";
25
45
  /**
26
- * Error thrown by the Claude Agent provider. Carries a categorised `kind`
27
- * alongside a user-facing message so callers (UI, CLI) can decide how to
28
- * render the failure (e.g. show a "Sign in" button for `auth`).
46
+ * The Claude Agent SDK's built-in tools. Passed as `disallowedTools` on the
47
+ * tool-free path so a "pure LLM" completion cannot execute host tools (Bash,
48
+ * file, web) under bypassPermissions.
29
49
  */
30
- export class ClaudeAgentError extends Error {
31
- kind;
32
- constructor(kind, message, cause) {
33
- super(message, cause !== undefined ? { cause } : undefined);
34
- this.name = "ClaudeAgentError";
35
- this.kind = kind;
36
- }
37
- }
50
+ const SDK_BUILTIN_TOOLS = [
51
+ "Bash",
52
+ "BashOutput",
53
+ "KillShell",
54
+ "Read",
55
+ "Write",
56
+ "Edit",
57
+ "MultiEdit",
58
+ "NotebookEdit",
59
+ "Glob",
60
+ "Grep",
61
+ "WebFetch",
62
+ "WebSearch",
63
+ "Task",
64
+ "TodoWrite"
65
+ ];
38
66
  /**
39
- * Classify a raw error from the Claude Agent SDK (or its child process) into
40
- * a ClaudeAgentError with an actionable, user-facing message.
67
+ * Env vars a nested Claude subscription session leaks into its children. The
68
+ * SDK spawns the bundled CLI as a subprocess; left in place these would make it
69
+ * believe it is itself nested. We strip them via `options.env` rather than any
70
+ * raw `child_process` plumbing.
41
71
  */
42
- export function classifyClaudeAgentError(error) {
43
- if (error instanceof ClaudeAgentError)
44
- return error;
45
- const original = error instanceof Error ? error.message : String(error);
46
- const lower = original.toLowerCase();
47
- // SDK package itself not installed (optional dependency missing).
48
- if (lower.includes("cannot find module") &&
49
- lower.includes("claude-agent-sdk")) {
50
- return new ClaudeAgentError("sdk_not_installed", "The @anthropic-ai/claude-agent-sdk package is not installed. " +
51
- "The Claude Agent provider requires this optional dependency — install it with " +
52
- "`npm install @anthropic-ai/claude-agent-sdk`, then restart NodeTool. " +
53
- `Original error: ${original}`, error);
54
- }
55
- // Claude Code CLI binary not on PATH.
56
- if (lower.includes("enoent") ||
57
- lower.includes("claude: not found") ||
58
- lower.includes("command not found: claude") ||
59
- (lower.includes("spawn") && lower.includes("claude"))) {
60
- return new ClaudeAgentError("cli_not_found", "Claude Code CLI not found on PATH. The Claude Agent provider spawns the " +
61
- "`claude` command — install it from https://docs.anthropic.com/claude-code/install " +
62
- "(or via `npm install -g @anthropic-ai/claude-code`), then run `claude login` to " +
63
- `authenticate. Original error: ${original}`, error);
64
- }
65
- // Running as root — SDK refuses --dangerously-skip-permissions.
66
- if (lower.includes("dangerously-skip-permissions") ||
67
- (lower.includes("root") && lower.includes("refus")) ||
68
- (lower.includes("uid") && lower.includes("0"))) {
69
- return new ClaudeAgentError("running_as_root", "The Claude Agent SDK refuses to run as root (uid=0) because it passes " +
70
- "--dangerously-skip-permissions to the Claude CLI. Run NodeTool as a non-root " +
71
- "user, or switch to the `anthropic` provider which uses ANTHROPIC_API_KEY " +
72
- `directly. Original error: ${original}`, error);
73
- }
74
- // Abort.
75
- if (lower.includes("abort") ||
76
- error instanceof Error && error.name === "AbortError") {
77
- return new ClaudeAgentError("aborted", "Claude Agent request was aborted.", error);
78
- }
79
- // Authentication failure.
80
- if (lower.includes("unauthorized") ||
81
- lower.includes("401") ||
82
- lower.includes("403") ||
83
- lower.includes("forbidden") ||
84
- lower.includes("not logged in") ||
85
- lower.includes("not authenticated") ||
86
- lower.includes("invalid api key") ||
87
- lower.includes("authentication") ||
88
- lower.includes("oauth")) {
89
- return new ClaudeAgentError("auth", "Claude Agent authentication failed. Run `claude login` in a terminal to " +
90
- "sign in with your Claude subscription, or set ANTHROPIC_API_KEY. " +
91
- `Original error: ${original}`, error);
92
- }
93
- // Rate limit.
94
- if (lower.includes("429") ||
95
- lower.includes("rate limit") ||
96
- lower.includes("too many requests") ||
97
- lower.includes("quota")) {
98
- return new ClaudeAgentError("rate_limit", "Claude Agent rate limit reached. Wait a moment and retry, or check your " +
99
- `Claude subscription usage limits. Original error: ${original}`, error);
100
- }
101
- // Context length.
102
- if (lower.includes("context length") ||
103
- lower.includes("context window") ||
104
- lower.includes("token limit") ||
105
- lower.includes("maximum context") ||
106
- lower.includes("too long") ||
107
- lower.includes("prompt is too long")) {
108
- return new ClaudeAgentError("context_length", "Claude Agent request exceeded the model's context window. Shorten the " +
109
- "conversation (clear older messages) or switch to a model with a larger " +
110
- `context window. Original error: ${original}`, error);
111
- }
112
- // Invalid / unknown model.
113
- if (lower.includes("model") &&
114
- (lower.includes("invalid") ||
115
- lower.includes("not found") ||
116
- lower.includes("unknown") ||
117
- lower.includes("does not exist"))) {
118
- return new ClaudeAgentError("invalid_model", "Claude Agent could not use the requested model. Check that the model ID " +
119
- "is valid and available on your Claude subscription. " +
120
- `Original error: ${original}`, error);
121
- }
122
- return new ClaudeAgentError("unknown", `Claude Agent provider error: ${original}`, error);
123
- }
124
- const CLAUDE_AGENT_MODELS = [
125
- {
126
- id: "claude-opus-4-7",
127
- name: "Claude Opus 4.7",
128
- provider: "claude_agent"
129
- },
130
- {
131
- id: "claude-sonnet-4-6",
132
- name: "Claude Sonnet 4.6",
133
- provider: "claude_agent"
134
- },
135
- {
136
- id: "claude-haiku-4-5",
137
- name: "Claude Haiku 4.5",
138
- provider: "claude_agent"
139
- }
140
- ];
141
- const DISALLOWED_TOOLS = [];
142
- function extractText(content) {
143
- if (typeof content === "string")
144
- return content;
145
- if (Array.isArray(content)) {
146
- return content
147
- .filter((c) => c.type === "text")
148
- .map((c) => c.text)
149
- .join("\n");
150
- }
151
- return String(content ?? "");
152
- }
72
+ const NESTED_SESSION_ENV = /^(CLAUDECODE|CLAUDE_(CODE|SESSION|ENABLE|AFTER|AUTO)_[A-Za-z0-9_]*)$/;
73
+ /** MCP server name under which NodeTool's tools are exposed to the SDK. */
74
+ const TOOL_SERVER_NAME = "nodetool_tools";
75
+ const TOOL_PREFIX = `mcp__${TOOL_SERVER_NAME}__`;
153
76
  /**
154
- * Convert a JSON Schema property to a Zod type.
155
- * Handles the common subset used by agent tools.
77
+ * The member expression the retired guest toolbelt was called through:
78
+ * `await tools.<name>({…})`. Models trained on it still emit it verbatim as a
79
+ * tool name, so it is stripped on the way in. Nothing produces it any more.
156
80
  */
157
- function jsonSchemaPropertyToZod(prop, z) {
158
- const type = prop.type;
159
- const enumValues = prop.enum;
160
- if (enumValues && Array.isArray(enumValues)) {
161
- // z.enum requires string literals
162
- if (enumValues.every((v) => typeof v === "string")) {
163
- return z.enum(enumValues);
164
- }
165
- return z.any();
166
- }
167
- switch (type) {
168
- case "string":
169
- return z.string();
170
- case "number":
171
- case "integer":
172
- return z.number();
173
- case "boolean":
174
- return z.boolean();
175
- case "array": {
176
- const items = prop.items;
177
- if (items) {
178
- return z.array(jsonSchemaPropertyToZod(items, z));
179
- }
180
- return z.array(z.any());
181
- }
182
- case "object": {
183
- const properties = prop.properties;
184
- if (properties) {
185
- const shape = {};
186
- const required = prop.required ?? [];
187
- for (const [key, val] of Object.entries(properties)) {
188
- let zodType = jsonSchemaPropertyToZod(val, z);
189
- if (!required.includes(key)) {
190
- zodType = zodType.optional();
191
- }
192
- shape[key] = zodType;
193
- }
194
- return z.object(shape);
195
- }
196
- return z.record(z.string(), z.any());
197
- }
198
- default:
199
- return z.any();
200
- }
201
- }
81
+ const GUEST_TOOL_PREFIX = "tools.";
82
+ /** Default cap on internal agent turns when tools are in play. */
83
+ const DEFAULT_TOOL_TURNS = 16;
84
+ /** Name of the local plugin the user's DB skills are materialized into. */
85
+ const SKILLS_PLUGIN_NAME = "nodetool-user-skills";
202
86
  /**
203
- * Convert a ProviderTool's JSON Schema inputSchema to a Zod raw shape
204
- * suitable for the SDK's tool() function.
87
+ * Lazily import the optional `@anthropic-ai/claude-agent-sdk`. The SDK is a soft
88
+ * (peer) dependency — it is not bundled with the app and must be installed at
89
+ * runtime from the Package Manager (Software → Claude Agent SDK), which drops it
90
+ * into the user-managed optional-node `node_modules`. {@link importOptionalModule}
91
+ * hides the specifier from bundlers (so the browser/worker bundle never pulls in
92
+ * the Node-only SDK) and falls back to resolving it from that optional root via
93
+ * `NODETOOL_OPTIONAL_NODE_MODULES`, so a plain ESM `import` (which ignores
94
+ * NODE_PATH) still finds a Package-Manager install. A missing package surfaces
95
+ * as a clear, actionable error at call time.
205
96
  */
206
- function jsonSchemaToZodShape(schema, z) {
207
- if (!schema)
208
- return {};
209
- const properties = schema.properties;
210
- if (!properties)
211
- return {};
212
- const required = schema.required ?? [];
213
- const shape = {};
214
- for (const [key, prop] of Object.entries(properties)) {
215
- let zodType = jsonSchemaPropertyToZod(prop, z);
216
- if (!required.includes(key)) {
217
- zodType = zodType.optional();
218
- }
219
- if (typeof prop.description === "string") {
220
- zodType = zodType.describe(prop.description);
221
- }
222
- shape[key] = zodType;
97
+ async function loadSdk() {
98
+ try {
99
+ return await importOptionalModule("@anthropic-ai/claude-agent-sdk");
100
+ }
101
+ catch (err) {
102
+ throw new Error("The Claude Agent provider requires the optional " +
103
+ "'@anthropic-ai/claude-agent-sdk' package, which is not installed. " +
104
+ "Install it from the Package Manager (Software → Claude Agent SDK).", { cause: err });
223
105
  }
224
- return shape;
225
106
  }
226
107
  export class ClaudeAgentProvider extends BaseProvider {
108
+ /** The SDK runs with bypassPermissions, so its built-in `WebSearch` is live. */
109
+ get supportsNativeWebSearch() {
110
+ return true;
111
+ }
227
112
  static requiredSecrets() {
113
+ // Auth lives in the SDK's own credential store (~/.claude), not in an env
114
+ // secret — there is nothing for the registry to resolve.
228
115
  return [];
229
116
  }
117
+ injectedQueryFn;
118
+ injectedCreateMcpServerFn;
230
119
  /**
231
- * Maps threadId → Claude SDK session ID.
232
- * Allows resuming conversations across multiple generateMessages() calls.
120
+ * Within-process cache of the active session per thread. The persisted
121
+ * `provider_session` column is the source of truth; this only spares a DB
122
+ * round-trip for back-to-back turns in the same process.
233
123
  */
234
- _sessions = new Map();
235
- constructor(_secrets = {}) {
236
- super("claude_agent");
124
+ sessions = new Map();
125
+ constructor(_secrets = {}, options = {}) {
126
+ super(PROVIDER_IDS.CLAUDE_AGENT_SDK);
127
+ this.injectedQueryFn = options.queryFn ?? null;
128
+ this.injectedCreateMcpServerFn = options.createMcpServerFn ?? null;
129
+ }
130
+ /** The subscription token is the SDK's business — never hand it to a sandbox. */
131
+ getContainerEnv() {
132
+ return {};
237
133
  }
238
- async hasToolSupport(_model) {
134
+ /**
135
+ * Tools run inside the SDK's own agent loop ({@link generateLoop}): NodeTool's
136
+ * tools are exposed as an in-process MCP server whose handlers call back into
137
+ * the harness's tool executor. The single-turn {@link generateMessages} stays
138
+ * tool-free.
139
+ */
140
+ async hasToolSupport() {
239
141
  return true;
240
142
  }
143
+ /**
144
+ * Stable model aliases the SDK resolves to the latest dated model. Aliases
145
+ * avoid pinning a version that ages out; the concrete model id the SDK
146
+ * selects is captured from the stream for accurate cost attribution.
147
+ */
241
148
  async getAvailableLanguageModels() {
242
- return CLAUDE_AGENT_MODELS;
149
+ const provider = PROVIDER_IDS.CLAUDE_AGENT_SDK;
150
+ return [
151
+ { id: "fable", name: "Claude Fable (subscription)", provider },
152
+ { id: "opus", name: "Claude Opus (subscription)", provider },
153
+ { id: "sonnet", name: "Claude Sonnet (subscription)", provider },
154
+ { id: "haiku", name: "Claude Haiku (subscription)", provider }
155
+ ];
156
+ }
157
+ /** Resolve the SDK `query`, lazily importing it off the Node-only package. */
158
+ async loadQuery() {
159
+ if (this.injectedQueryFn)
160
+ return this.injectedQueryFn;
161
+ const mod = await loadSdk();
162
+ return mod.query;
163
+ }
164
+ /** Resolve the SDK `createSdkMcpServer`, lazily importing the Node-only pkg. */
165
+ async loadCreateMcpServer() {
166
+ if (this.injectedCreateMcpServerFn)
167
+ return this.injectedCreateMcpServerFn;
168
+ const mod = await loadSdk();
169
+ // SAFETY: the SDK's factory is generic over each tool's Zod shape; this
170
+ // provider always hands it the default `AnyZodRawShape` definitions.
171
+ return mod.createSdkMcpServer;
243
172
  }
244
173
  /**
245
- * Extract the system prompt and the last user message from the messages array.
174
+ * Run the SDK's own agent loop. Tool-free turns behave exactly like
175
+ * {@link generateMessages}; with `tools` we expose them as an in-process MCP
176
+ * server whose handlers call `executeTool`, let the SDK loop (`maxTurns > 1`),
177
+ * and translate its `tool_use`/`tool_result` stream into ToolCall items and
178
+ * persistable {@link ProviderMessageEvent}s.
246
179
  */
247
- extractPrompt(messages) {
248
- let systemPrompt = "You are a helpful assistant.";
249
- let prompt = "";
250
- for (const msg of messages) {
251
- if (msg.role === "system") {
252
- systemPrompt = extractText(msg.content);
180
+ async *generateLoop(args) {
181
+ // The SDK owns the loop, so honoring the budget is this override's job:
182
+ // reserve before the first turn, and again after every assistant turn that
183
+ // requested tools — that is the point where the SDK will make another call.
184
+ // A refusal aborts the session instead of letting it spend past the cap.
185
+ const { runBudget, turnBudget } = resolveTurnBudget(args.turnBudget);
186
+ if (runBudget?.deadline.expired()) {
187
+ yield budgetStopItem(runBudget);
188
+ return;
189
+ }
190
+ // Every turn this session makes holds its own reservation, and the whole
191
+ // set is settled in the `finally` below — the SDK reports one number for
192
+ // the session, not one per turn.
193
+ const reservations = [];
194
+ if (turnBudget) {
195
+ const first = this._admitTurn(turnBudget, args.model, args.messages);
196
+ if (first === null) {
197
+ yield budgetStopItem(runBudget);
198
+ return;
253
199
  }
200
+ reservations.push(first);
201
+ }
202
+ // Drop every NodeTool tool the SDK ships a built-in for. Those built-ins
203
+ // are live under bypassPermissions, so keeping the MCP copy would give one
204
+ // capability two surfaces and make the model pick between them.
205
+ const offered = args.tools ?? [];
206
+ const replaced = sdkNativeReplacements(offered.map((t) => t.name), args.workspaceDir);
207
+ const tools = offered.filter((t) => !replaced.has(t.name));
208
+ if (replaced.size > 0) {
209
+ log.debug("Using SDK built-ins in place of NodeTool tools", {
210
+ replaced: [...replaced]
211
+ });
254
212
  }
255
- // Walk backwards to find the last user message — that's our prompt.
256
- for (let i = messages.length - 1; i >= 0; i--) {
257
- if (messages[i].role === "user") {
258
- prompt = extractText(messages[i].content);
259
- break;
213
+ const executeTool = args.executeTool;
214
+ // A tool dispatches either through its own `execute` or the harness
215
+ // `executeTool`; build the MCP server when at least one route exists.
216
+ const hasToolExecute = tools.some((t) => t.execute);
217
+ // The SDK loop is stopped by aborting this controller — a terminal tool
218
+ // fires it after running. Bridge the caller's signal into it too, and pass
219
+ // its signal (overriding args.signal) down so runTurn cancels on either.
220
+ const abortController = new AbortController();
221
+ const onExternalAbort = () => abortController.abort();
222
+ if (args.signal) {
223
+ if (args.signal.aborted)
224
+ abortController.abort();
225
+ else
226
+ args.signal.addEventListener("abort", onExternalAbort, { once: true });
227
+ }
228
+ let mcp = null;
229
+ if (tools.length > 0 && (executeTool || hasToolExecute)) {
230
+ const createServer = await this.loadCreateMcpServer();
231
+ // MCP tool results carry typed content blocks, so image-bearing results
232
+ // (view_image) are returned as real image blocks the SDK hands to Claude —
233
+ // no flattening to text on the agent-SDK path.
234
+ const defs = tools.map((t) => toolDefinition(t, async (name, toolArgs) => {
235
+ // The SDK runs parallel calls of one tool in the same millisecond,
236
+ // so a clock alone would hand them one id.
237
+ const toolCallId = `call_${name}_${crypto.randomUUID()}`;
238
+ const result = t.execute
239
+ ? await t.execute(toolArgs, toolCallId)
240
+ : executeTool
241
+ ? await executeTool({
242
+ id: toolCallId,
243
+ name,
244
+ args: toolArgs
245
+ })
246
+ : `Tool "${name}" is not available`;
247
+ // A terminal tool ends the SDK loop after its result is delivered.
248
+ if (t.terminal)
249
+ abortController.abort();
250
+ return result;
251
+ }, args.resolveMedia));
252
+ const server = createServer({
253
+ name: TOOL_SERVER_NAME,
254
+ version: "1.0.0",
255
+ tools: defs
256
+ });
257
+ mcp = {
258
+ mcpServers: { [TOOL_SERVER_NAME]: server },
259
+ allowedTools: tools.map((t) => `${TOOL_PREFIX}${t.name}`)
260
+ };
261
+ }
262
+ // Materialize the user's skills into a throwaway local plugin the SDK's
263
+ // native skill loader discovers. Best-effort: a disk failure drops skills
264
+ // for the turn, it does not sink it. Cleaned up in the `finally` below.
265
+ let skillsPlugin = null;
266
+ if ((args.skills?.length ?? 0) > 0) {
267
+ try {
268
+ skillsPlugin = await materializeSkillsPlugin(args.skills ?? []);
269
+ }
270
+ catch (err) {
271
+ log.warn("Failed to materialize user skills for the SDK", {
272
+ error: err instanceof Error ? err.message : String(err)
273
+ });
260
274
  }
261
275
  }
262
- if (!prompt) {
263
- prompt = "Hello";
276
+ const costBefore = turnBudget ? this.getTotalCost() : 0;
277
+ // Set when the budget refuses a further turn: the SDK session is aborted
278
+ // and the stop is yielded once the stream has drained, so it stays the
279
+ // loop's final item.
280
+ let budgetStop = null;
281
+ try {
282
+ const stream = this.runWithSession({ ...args, signal: abortController.signal }, {
283
+ emitMessages: true,
284
+ // Turns are for tool rounds, and a replaced tool still costs one —
285
+ // key this on what the caller offered, not on what survived into MCP.
286
+ maxTurns: offered.length > 0 ? (args.maxIterations ?? DEFAULT_TOOL_TURNS) : 1,
287
+ mcp,
288
+ toolsOffered: offered.length > 0,
289
+ cwd: args.workspaceDir,
290
+ skillsPlugin
291
+ });
292
+ // The SDK grows the conversation internally, so reserving against the
293
+ // opening prompt every time would under-count every later turn — badly,
294
+ // on a tool-heavy loop. The messages it emits are the same ones it is
295
+ // accumulating, so the transcript is rebuilt here to reserve against.
296
+ const transcript = [...args.messages];
297
+ for await (const item of stream) {
298
+ yield item;
299
+ if (!turnBudget)
300
+ continue;
301
+ if (!isProviderMessageEvent(item))
302
+ continue;
303
+ transcript.push(item.message);
304
+ const isToolCallingTurn = item.message.role === "assistant" &&
305
+ (item.message.toolCalls?.length ?? 0) > 0;
306
+ if (!isToolCallingTurn)
307
+ continue;
308
+ const next = this._admitTurn(turnBudget, args.model, transcript);
309
+ if (next === null) {
310
+ budgetStop = budgetStopItem(runBudget);
311
+ abortController.abort();
312
+ continue;
313
+ }
314
+ reservations.push(next);
315
+ }
316
+ }
317
+ finally {
318
+ // The SDK reports usage once, on the terminal `result` message, so a
319
+ // per-turn actual doesn't exist here. Reservations accumulate untouched
320
+ // for the session's duration — conservative by construction — and the
321
+ // real number lands when the session ends.
322
+ //
323
+ // Except when it doesn't: an aborted session never emits `result`, so
324
+ // nothing is recorded even though its turns ran and were billed. That is
325
+ // the *normal* ending here — a terminal tool (`finish_step`) aborts the
326
+ // query — so booking zero would mean the cap never counted a single
327
+ // successful decision on this provider. A zero delta is therefore read
328
+ // as "unknown", and the reserved worst case is charged instead.
329
+ if (turnBudget) {
330
+ const observed = this.getTotalCost() - costBefore;
331
+ // One number covers the whole session, so it is booked against the
332
+ // first reservation and the rest settle at zero; with no number at
333
+ // all, each reservation is charged its own worst case.
334
+ let remaining = observed > 0 ? observed : null;
335
+ for (const reservation of reservations) {
336
+ turnBudget.commit(reservation, remaining);
337
+ if (remaining !== null)
338
+ remaining = 0;
339
+ }
340
+ }
341
+ if (args.signal)
342
+ args.signal.removeEventListener("abort", onExternalAbort);
343
+ // Remove the throwaway skills plugin. The SDK's subprocess has finished
344
+ // reading it by now (the stream is drained), so best-effort cleanup is
345
+ // safe; a leftover temp dir is not worth failing the turn over.
346
+ if (skillsPlugin) {
347
+ await fs
348
+ .rm(skillsPlugin.dir, { recursive: true, force: true })
349
+ .catch(() => { });
350
+ }
351
+ }
352
+ if (budgetStop) {
353
+ yield budgetStop;
264
354
  }
265
- return { systemPrompt, prompt };
266
- }
267
- /** Look up an existing Claude session for the given threadId. */
268
- getSessionId(threadId) {
269
- if (!threadId)
270
- return undefined;
271
- return this._sessions.get(threadId);
272
- }
273
- /** Store the Claude session ID for a threadId. */
274
- setSessionId(threadId, sessionId) {
275
- this._sessions.set(threadId, sessionId);
276
355
  }
277
- /** Remove a stored session (e.g. when a conversation is closed). */
278
- clearSession(threadId) {
279
- this._sessions.delete(threadId);
356
+ async *generateMessages(args) {
357
+ // Single-turn, tool-free primitive. The agent loop lives in generateLoop.
358
+ const resultTool = args.tools?.length === 1 &&
359
+ args.toolChoice === args.tools[0].name
360
+ ? args.tools[0]
361
+ : undefined;
362
+ const resultSchema = resultTool?.inputSchema;
363
+ const config = {
364
+ emitMessages: false,
365
+ maxTurns: args.maxTurns ?? 1,
366
+ mcp: null,
367
+ toolsOffered: false
368
+ };
369
+ if (resultTool && resultSchema) {
370
+ config.outputFormat = { type: "json_schema", schema: resultSchema };
371
+ config.resultToolName = resultTool.name;
372
+ }
373
+ yield* this.runWithSession(args, config);
280
374
  }
281
375
  /**
282
- * Build an in-process MCP server from ProviderTool definitions.
283
- * Each tool's handler calls the onToolCall callback.
376
+ * Shared session machinery for {@link generateMessages} and
377
+ * {@link generateLoop}: decide resume vs fresh (best-effort, with a
378
+ * full-history priming fallback), then drive one SDK turn via {@link runTurn}.
379
+ * `config` selects single-turn streaming vs the tool/message-emitting loop.
284
380
  */
285
- buildMcpServer(tools, onToolCall, sdk, z, toolCallTracker) {
286
- const mcpTools = tools.map((t) => {
287
- const zodShape = jsonSchemaToZodShape(t.inputSchema, z);
288
- return sdk.tool(t.name, t.description ?? "", zodShape, async (args) => {
289
- // Track the tool call for yielding to the caller
290
- const toolCall = {
291
- id: `call_${Date.now()}_${toolCallTracker.length}`,
292
- name: t.name,
293
- args
294
- };
295
- toolCallTracker.push(toolCall);
296
- log.debug("MCP tool called", { name: t.name, args });
297
- try {
298
- const result = await onToolCall(t.name, args);
299
- return { content: [{ type: "text", text: result }] };
300
- }
301
- catch (e) {
302
- const errorMsg = `Tool execution error: ${e}`;
303
- log.error("MCP tool error", { name: t.name, error: errorMsg });
304
- return {
305
- content: [{ type: "text", text: errorMsg }],
306
- isError: true
307
- };
308
- }
309
- });
310
- });
311
- return sdk.createSdkMcpServer({
312
- name: MCP_SERVER_NAME,
313
- version: "1.0.0",
314
- tools: mcpTools
315
- });
316
- }
317
- async *generateMessages(args) {
318
- const toolCount = args.tools?.length ?? 0;
319
- const hasOnToolCall = !!args.onToolCall;
320
- const hasTools = toolCount > 0 && hasOnToolCall;
321
- const { systemPrompt, prompt } = this.extractPrompt(args.messages);
381
+ async *runWithSession(args, config) {
382
+ const systemPrompt = extractSystemPrompt(args.messages);
383
+ const systemHash = hashSystemPrompt(systemPrompt);
322
384
  const threadId = args.threadId ?? null;
323
- const resumeSessionId = this.getSessionId(threadId);
324
- // Pre-flight: fail fast with a clear message if running as root, since the
325
- // SDK always refuses --dangerously-skip-permissions for uid=0. Without this
326
- // check the underlying error surfaces as an opaque child-process failure.
327
- if (typeof process.getuid === "function" && process.getuid() === 0) {
328
- throw new ClaudeAgentError("running_as_root", "The Claude Agent SDK refuses to run as root (uid=0) because it passes " +
329
- "--dangerously-skip-permissions to the Claude CLI. Run NodeTool as a " +
330
- "non-root user, or use the `anthropic` provider with ANTHROPIC_API_KEY.");
331
- }
332
- log.info("Claude Agent generateMessages called", {
333
- toolCount,
334
- hasOnToolCall,
335
- hasTools,
336
- toolNames: args.tools?.map((t) => t.name) ?? []
337
- });
338
- // Lazy-load the SDK only on first use. This keeps anything that imports
339
- // the provider transitively (Electron main, CLI, tests) from requiring
340
- // the SDK at startup.
341
- let sdk;
342
- try {
343
- sdk = await import("@anthropic-ai/claude-agent-sdk");
344
- }
345
- catch (err) {
346
- throw classifyClaudeAgentError(err);
347
- }
348
- // Track tool calls made during this query (populated by MCP handlers)
349
- const toolCallTracker = [];
350
- // Build MCP server if tools + callback provided
351
- let mcpServer;
352
- let allowedTools = [];
353
- if (hasTools && args.tools && args.onToolCall) {
354
- mcpServer = this.buildMcpServer(args.tools, args.onToolCall, sdk, z, toolCallTracker);
355
- allowedTools = args.tools.map((t) => `mcp__${MCP_SERVER_NAME}__${t.name}`);
356
- log.info("MCP server built", {
357
- mcpToolCount: args.tools.length,
358
- allowedTools
359
- });
385
+ // Source of truth is the token threaded in from the persisted assistant
386
+ // message; the in-memory cache is a fallback for same-process turns.
387
+ const prior = args.providerSession ??
388
+ (threadId ? (this.sessions.get(threadId) ?? null) : null);
389
+ const canResume = prior != null &&
390
+ prior.providerId === this.provider &&
391
+ prior.model === args.model &&
392
+ (prior.systemHash == null || prior.systemHash === systemHash) &&
393
+ args.messages.length > prior.checkpoint;
394
+ // Cache the session in-process only when the caller doesn't thread a token
395
+ // in (the CLI path). When it does (the websocket path), the DB is
396
+ // authoritative and the emitted checkpoint may be relative to a trimmed
397
+ // view, so caching it would corrupt a later cache-based resume.
398
+ const cacheSession = args.providerSession == null;
399
+ // RESUME is best-effort: a session file may have been pruned/expired. If the
400
+ // resume query fails *before* any content reached the consumer, fall back to
401
+ // a fresh session; if it fails mid-stream we surface the error instead.
402
+ const emitted = { content: false };
403
+ if (canResume && prior) {
404
+ const delta = buildResumeDelta(args.messages, prior.checkpoint);
405
+ try {
406
+ yield* this.runTurn(args, {
407
+ prompt: delta,
408
+ resume: prior.token,
409
+ systemPrompt,
410
+ systemHash,
411
+ threadId,
412
+ emitted,
413
+ cacheSession,
414
+ config
415
+ });
416
+ return;
417
+ }
418
+ catch (err) {
419
+ if (emitted.content)
420
+ throw err;
421
+ log.warn("Claude session resume failed; starting fresh", {
422
+ error: err instanceof Error ? err.message : String(err)
423
+ });
424
+ }
360
425
  }
361
- log.info("Claude Agent request", {
362
- model: args.model,
363
- promptLength: prompt.length,
426
+ // Fresh / fallback. When the caller handed us only the delta (a trimmed
427
+ // `messages` view) plus a loader, prime from the full conversation so prior
428
+ // context isn't lost — the session may have expired or the system prompt
429
+ // changed. Otherwise `messages` already holds the full history.
430
+ const freshMessages = args.loadFullHistory
431
+ ? await args.loadFullHistory()
432
+ : args.messages;
433
+ yield* this.runTurn(args, {
434
+ prompt: buildFreshPrompt(freshMessages),
435
+ resume: undefined,
436
+ systemPrompt,
437
+ systemHash,
364
438
  threadId,
365
- resuming: !!resumeSessionId,
366
- tools: hasTools ? args.tools.map((t) => t.name) : [],
367
- mcpEnabled: !!mcpServer
439
+ emitted,
440
+ cacheSession,
441
+ config
368
442
  });
369
- // Strip all Claude Code env vars to allow running inside a Claude Code session.
370
- // Just deleting CLAUDECODE/CLAUDE_CODE is not enough — the SDK inherits
371
- // session IDs, proxy settings, auth tokens, etc. that conflict with
372
- // spawning a nested Claude Code process.
373
- const cleanEnv = { ...process.env };
374
- for (const key of Object.keys(cleanEnv)) {
375
- if (key === "CLAUDECODE" ||
376
- key === "CLAUDE_CODE" ||
377
- key.startsWith("CLAUDE_CODE_") ||
378
- key.startsWith("CLAUDE_SESSION_") ||
379
- key.startsWith("CLAUDE_ENABLE_") ||
380
- key.startsWith("CLAUDE_AFTER_") ||
381
- key.startsWith("CLAUDE_AUTO_")) {
382
- delete cleanEnv[key];
383
- }
384
- }
385
- // Bridge AbortSignal → AbortController for the SDK
386
- let abortController;
443
+ }
444
+ /** Drive one SDK `query` turn and translate its messages into stream items. */
445
+ async *runTurn(args, plan) {
446
+ const queryFn = await this.loadQuery();
447
+ // Cancellation: the SDK stops the underlying query when its AbortController
448
+ // fires. Bridge the caller's signal onto a fresh controller per turn.
449
+ const abortController = new AbortController();
450
+ const onAbort = () => abortController.abort();
387
451
  if (args.signal) {
388
- abortController = new AbortController();
389
- if (args.signal.aborted) {
452
+ if (args.signal.aborted)
390
453
  abortController.abort();
391
- }
392
- else {
393
- args.signal.addEventListener("abort", () => abortController.abort(), { once: true });
394
- }
454
+ else
455
+ args.signal.addEventListener("abort", onAbort, { once: true });
395
456
  }
396
- let queryHandle;
397
- try {
398
- queryHandle = sdk.query({
399
- prompt,
400
- options: {
401
- model: args.model,
402
- systemPrompt,
403
- maxTurns: hasTools ? (args.maxTurns ?? 100) : 1,
404
- permissionMode: "bypassPermissions",
405
- allowDangerouslySkipPermissions: true,
406
- disallowedTools: DISALLOWED_TOOLS,
407
- allowedTools,
408
- // Isolate from the host's Claude Code config: don't load
409
- // user/project/local settings (which is where installed skills
410
- // and plugin configs live), and pass an empty plugin list so
411
- // no plugin commands/agents/hooks are registered.
412
- settingSources: [],
413
- plugins: [],
414
- env: cleanEnv,
415
- ...(abortController ? { abortController } : {}),
416
- ...(mcpServer
417
- ? { mcpServers: { [MCP_SERVER_NAME]: mcpServer } }
418
- : {}),
419
- ...(resumeSessionId ? { resume: resumeSessionId } : {})
457
+ const options = {
458
+ systemPrompt: plan.systemPrompt,
459
+ // Pass aliases ("sonnet"/"opus"/"haiku") straight through to the SDK.
460
+ model: args.model || undefined,
461
+ // 1 for a single turn; higher when tools may drive multiple rounds.
462
+ maxTurns: plan.config.maxTurns,
463
+ // Auto-approve NodeTool's MCP tools (this is the no-prompt allowlist, not
464
+ // an availability restriction). Empty (pure LLM) when tool-free.
465
+ allowedTools: plan.config.mcp?.allowedTools ?? [],
466
+ // Do NOT load repo .claude / CLAUDE.md / skills.
467
+ settingSources: [],
468
+ // Run without asking for permissions: never prompt AND never deny. This
469
+ // lets the SDK agent use its built-in tools (WebSearch/WebFetch/Bash/…)
470
+ // alongside NodeTool's MCP tools. bypassPermissions requires the explicit
471
+ // safety flag below.
472
+ permissionMode: "bypassPermissions",
473
+ allowDangerouslySkipPermissions: true,
474
+ // On the tool-free path (the documented "pure LLM" primitive used by
475
+ // generateMessage/generateMessages), explicitly disable the SDK's
476
+ // built-in tools. Under bypassPermissions they are otherwise live and
477
+ // auto-approved, so a prompt-injected "call Bash …" in untrusted text
478
+ // being summarized/classified would execute on the host. allowedTools is
479
+ // only a no-prompt approval list, not an availability restriction.
480
+ //
481
+ // The test is `toolsOffered`, not `mcp`: a caller whose every tool was
482
+ // replaced by a built-in offered tools and has no MCP server, and
483
+ // disabling the built-ins there would leave it with nothing.
484
+ //
485
+ // `ToolSearch` is disabled on BOTH paths. It searches tools the SDK has
486
+ // deferred, and NodeTool defers none — it registers its handful of tools
487
+ // in-process — so the search always comes back empty. A model that fell
488
+ // back to it after a mis-named tool call got nothing and stalled the turn.
489
+ disallowedTools: plan.config.toolsOffered
490
+ ? ["ToolSearch"]
491
+ : [...SDK_BUILTIN_TOOLS, "ToolSearch"],
492
+ includePartialMessages: true,
493
+ // Setting env REPLACES the child env, so spread process.env minus the
494
+ // nested-session leakage. Preserves PATH/HOME/ANTHROPIC_BASE_URL/proxies.
495
+ env: buildChildEnv(),
496
+ abortController
497
+ };
498
+ if (plan.config.outputFormat) {
499
+ options.outputFormat = plan.config.outputFormat;
500
+ }
501
+ // Anchor the path-scoped built-ins to the run's workspace, which is where
502
+ // the NodeTool tools they replace were contained.
503
+ if (plan.config.cwd) {
504
+ options.cwd = plan.config.cwd;
505
+ }
506
+ if (plan.config.mcp) {
507
+ options.mcpServers = plan.config.mcp.mcpServers;
508
+ }
509
+ // Enable the SDK's native skill loop over the user's DB skills, which were
510
+ // materialized into an isolated local plugin. This rides `plugins`, NOT
511
+ // `settingSources` (kept `[]`), so no `~/.claude` / `.claude` settings or
512
+ // `CLAUDE.md` are read; `skipMcpDiscovery` blocks any `.mcp.json` the dir
513
+ // might carry. `skills` names the exact user skills to enable, so any
514
+ // bundled skill is hidden from the listing and the `Skill` tool.
515
+ if (plan.config.skillsPlugin) {
516
+ options.plugins = [
517
+ {
518
+ type: "local",
519
+ path: plan.config.skillsPlugin.dir,
520
+ skipMcpDiscovery: true
420
521
  }
421
- });
522
+ ];
523
+ options.skills = plan.config.skillsPlugin.names;
422
524
  }
423
- catch (err) {
424
- const classified = classifyClaudeAgentError(err);
425
- log.error("Claude Agent sdk.query() failed", {
426
- kind: classified.kind,
427
- error: classified.message,
428
- model: args.model
429
- });
430
- throw classified;
525
+ if (plan.resume) {
526
+ options.resume = plan.resume;
431
527
  }
432
- // streamedTextLength tracks the text position within the current assistant turn.
433
- // It resets when a new turn begins (after tool execution in multi-turn MCP queries).
434
- let streamedTextLength = 0;
435
- let yieldedToolCallCount = 0;
436
- // Track whether we already yielded text via stream_event/assistant messages
437
- // so we can skip the duplicate result event.
438
- let hasYieldedText = false;
528
+ // Log the exact wire body (sans the non-serializable AbortController, MCP
529
+ // server instances, and the full env, which can carry secrets) so the
530
+ // request-log UI shows what was sent without leaking the environment.
531
+ this.recordRequestPayload({
532
+ prompt: plan.prompt,
533
+ resume: plan.resume ?? null,
534
+ options: {
535
+ ...options,
536
+ abortController: undefined,
537
+ env: undefined,
538
+ mcpServers: plan.config.mcp
539
+ ? Object.keys(plan.config.mcp.mcpServers ?? {})
540
+ : undefined
541
+ }
542
+ });
543
+ log.debug("Claude Agent SDK request", {
544
+ model: args.model,
545
+ resume: Boolean(plan.resume)
546
+ });
547
+ let resolvedModel = args.model;
548
+ // When partial deltas stream, we render from them and skip the final
549
+ // assistant message to avoid duplication; if a build omits partials we fall
550
+ // back to the final message's content blocks.
551
+ let streamedFromPartials = false;
552
+ // The SDK splits one API assistant turn into one frame per content block —
553
+ // thinking, then text, then each tool_use. Emitting a message per frame
554
+ // would hand the consumer a text-only assistant message in the middle of a
555
+ // tool round, which every chat surface reads as "the turn ended" (the chat
556
+ // UI flips the composer back to Send and hides Stop), and would write an
557
+ // empty row for a thinking-only frame. Buffer the blocks and emit one
558
+ // message per assistant turn, text and tool calls together, the way every
559
+ // other provider does.
560
+ let pending = null;
561
+ const flushPending = () => {
562
+ const buffered = pending;
563
+ pending = null;
564
+ if (!buffered)
565
+ return null;
566
+ if (!buffered.text && buffered.toolCalls.length === 0)
567
+ return null;
568
+ return {
569
+ type: "message",
570
+ message: {
571
+ role: "assistant",
572
+ content: buffered.text || null,
573
+ toolCalls: buffered.toolCalls.length ? buffered.toolCalls : null
574
+ }
575
+ };
576
+ };
439
577
  try {
440
- for await (const msg of queryHandle) {
441
- const msgObj = msg;
442
- const msgType = msgObj.type;
443
- // Capture session ID from the init event
444
- if (msgType === "system" &&
445
- msgObj.subtype === "init" &&
446
- typeof msgObj.session_id === "string" &&
447
- threadId) {
448
- this.setSessionId(threadId, msgObj.session_id);
449
- log.debug("Claude session initialized", {
450
- threadId,
451
- sessionId: msgObj.session_id
452
- });
578
+ for await (const msg of queryFn({ prompt: plan.prompt, options })) {
579
+ if (msg.type === "system" && msg.subtype === "init") {
580
+ // Capture the session and surface it immediately so a streaming
581
+ // consumer can persist it onto the assistant message it creates.
582
+ if (isNonEmptyString(msg.model))
583
+ resolvedModel = msg.model;
584
+ if (plan.threadId) {
585
+ const session = {
586
+ providerId: this.provider,
587
+ model: args.model,
588
+ token: msg.session_id,
589
+ checkpoint: args.messages.length,
590
+ systemHash: plan.systemHash
591
+ };
592
+ if (plan.cacheSession)
593
+ this.sessions.set(plan.threadId, session);
594
+ yield { type: "session", session };
595
+ }
453
596
  continue;
454
597
  }
455
- // Yield any new tool calls that were tracked by MCP handlers.
456
- // When tool calls appear, a new assistant turn follows — reset text position.
457
- if (yieldedToolCallCount < toolCallTracker.length) {
458
- while (yieldedToolCallCount < toolCallTracker.length) {
459
- yield toolCallTracker[yieldedToolCallCount++];
598
+ if (msg.type === "stream_event") {
599
+ const captured = capturedModelFromPartial(msg);
600
+ if (captured)
601
+ resolvedModel = captured;
602
+ const delta = partialDelta(msg);
603
+ if (delta?.text != null) {
604
+ streamedFromPartials = true;
605
+ plan.emitted.content = true;
606
+ yield { type: "chunk", content: delta.text, done: false };
607
+ }
608
+ else if (delta?.thinking != null) {
609
+ streamedFromPartials = true;
610
+ plan.emitted.content = true;
611
+ yield {
612
+ type: "chunk",
613
+ content: delta.thinking,
614
+ done: false,
615
+ thinking: true
616
+ };
460
617
  }
461
- // Reset for the next assistant turn's text
462
- streamedTextLength = 0;
618
+ continue;
463
619
  }
464
- // Stream events provide incremental text updates
465
- if (msgType === "stream_event") {
466
- const partial = msgObj.message;
467
- const content = partial?.content;
468
- if (Array.isArray(content)) {
469
- const text = content
470
- .filter((b) => b?.type === "text" && typeof b.text === "string")
471
- .map((b) => b.text)
472
- .join("");
473
- if (text.length > streamedTextLength) {
474
- const delta = text.slice(streamedTextLength);
475
- streamedTextLength = text.length;
476
- hasYieldedText = true;
477
- yield { type: "chunk", content: delta, done: false };
620
+ if (msg.type === "assistant") {
621
+ const m = msg.message;
622
+ if (m && isNonEmptyString(m.model))
623
+ resolvedModel = m.model;
624
+ // Fallback only: no partials arrived, so render text/thinking from the
625
+ // final content blocks — kept strictly separate, never merged.
626
+ if (!streamedFromPartials) {
627
+ for (const block of finalBlocks(msg)) {
628
+ plan.emitted.content = true;
629
+ yield block.thinking
630
+ ? {
631
+ type: "chunk",
632
+ content: block.content,
633
+ done: false,
634
+ thinking: true
635
+ }
636
+ : { type: "chunk", content: block.content, done: false };
637
+ }
638
+ }
639
+ // The loop needs each assistant turn (text + any tool calls) as a
640
+ // persistable message, and a ToolCall item per call for live display.
641
+ // The ToolCall items go out immediately; the message waits for the
642
+ // rest of the turn's blocks (see `pending`).
643
+ if (plan.config.emitMessages) {
644
+ const { text, toolCalls } = assistantParts(msg);
645
+ for (const tc of toolCalls)
646
+ yield tc;
647
+ const parentToolUseId = msg.parent_tool_use_id ?? null;
648
+ if (pending && pending.parentToolUseId !== parentToolUseId) {
649
+ // A subagent's turn and the main agent's are different turns.
650
+ const flushed = flushPending();
651
+ if (flushed)
652
+ yield flushed;
478
653
  }
654
+ pending ??= { parentToolUseId, text: "", toolCalls: [] };
655
+ pending.text += text;
656
+ pending.toolCalls.push(...toolCalls);
479
657
  }
480
658
  continue;
481
659
  }
482
- // Full assistant message — emit any remaining text not covered by stream events
483
- if (msgType === "assistant") {
484
- const message = msgObj.message;
485
- const content = message?.content;
486
- if (Array.isArray(content)) {
487
- const text = content
488
- .filter((b) => b?.type === "text" && typeof b.text === "string")
489
- .map((b) => b.text)
490
- .join("");
491
- if (text.length > streamedTextLength) {
492
- const delta = text.slice(streamedTextLength);
493
- streamedTextLength = text.length;
494
- hasYieldedText = true;
495
- yield { type: "chunk", content: delta, done: false };
660
+ // Tool results come back as a user message; surface them as tool
661
+ // messages so the harness can persist them. The assistant turn that
662
+ // asked for them is complete by now, so it goes out first.
663
+ if (msg.type === "user") {
664
+ if (plan.config.emitMessages) {
665
+ const flushed = flushPending();
666
+ if (flushed)
667
+ yield flushed;
668
+ for (const tr of toolResultsFromUser(msg)) {
669
+ yield {
670
+ type: "message",
671
+ message: {
672
+ role: "tool",
673
+ toolCallId: tr.toolCallId,
674
+ content: tr.content
675
+ }
676
+ };
496
677
  }
497
678
  }
498
- // Reset for next turn (if multi-turn agentic query)
499
- streamedTextLength = 0;
500
679
  continue;
501
680
  }
502
- // Result event — surface errors and fall back text when nothing was streamed.
503
- if (msgType === "result") {
504
- const subtype = msgObj.subtype;
505
- const isError = msgObj.is_error === true;
506
- const rawResult = msgObj.result;
507
- const resultText = typeof rawResult === "string" ? rawResult : undefined;
508
- if (isError || (subtype && subtype.startsWith("error"))) {
509
- const detail = resultText ?? this.describeErrorSubtype(subtype);
510
- const kind = subtype === "error_max_turns"
511
- ? "max_turns"
512
- : subtype === "error_during_execution"
513
- ? "execution"
514
- : "unknown";
515
- const message = kind === "max_turns"
516
- ? `Claude Agent stopped after reaching the max turn limit without ` +
517
- `finishing. Increase maxTurns, simplify the task, or break it into ` +
518
- `smaller steps. Detail: ${detail}`
519
- : kind === "execution"
520
- ? `Claude Agent execution error: ${detail}`
521
- : `Claude Agent returned an error result (${subtype ?? "unknown"}): ${detail}`;
522
- log.error("Claude Agent result error", {
523
- subtype,
524
- is_error: isError,
525
- detail
526
- });
527
- throw new ClaudeAgentError(kind, message);
681
+ if (msg.type === "result") {
682
+ // Bill every terminal result, not just the successful ones: an
683
+ // errored/max-turns run still consumed (and was charged for) tokens.
684
+ this.trackResultUsage(msg, resolvedModel);
685
+ if (msg.subtype === "success") {
686
+ const flushed = flushPending();
687
+ if (flushed)
688
+ yield flushed;
689
+ const structured = msg.structured_output;
690
+ if (plan.config.resultToolName &&
691
+ isObjectLike(structured) &&
692
+ !Array.isArray(structured)) {
693
+ yield {
694
+ id: `result_${msg.uuid}`,
695
+ name: plan.config.resultToolName,
696
+ args: structured
697
+ };
698
+ }
699
+ yield { type: "chunk", content: "", done: true };
528
700
  }
529
- if (!hasYieldedText && resultText && resultText.length > 0) {
530
- yield { type: "chunk", content: resultText, done: false };
701
+ else {
702
+ throw resultError(msg);
531
703
  }
704
+ continue;
532
705
  }
533
706
  }
707
+ // A stream that ended without a terminal `result` still owes its last
708
+ // assistant turn.
709
+ const trailing = flushPending();
710
+ if (trailing)
711
+ yield trailing;
534
712
  }
535
713
  catch (err) {
536
- const classified = classifyClaudeAgentError(err);
537
- log.error("Claude Agent stream failed", {
538
- kind: classified.kind,
539
- error: classified.message,
540
- model: args.model
541
- });
542
- throw classified;
714
+ // The query can reject (auth failure, missing session, spawn error). Never
715
+ // collapse to a generic string — surface the real message.
716
+ if (args.signal?.aborted)
717
+ return;
718
+ throw err instanceof Error ? err : new Error(String(err));
543
719
  }
544
- // Yield any remaining tool calls
545
- while (yieldedToolCallCount < toolCallTracker.length) {
546
- yield toolCallTracker[yieldedToolCallCount++];
720
+ finally {
721
+ // Terminate the SDK query on every exit path. On normal completion the
722
+ // query has already finished and this is a no-op; on an early `break`
723
+ // (consumer cancelled) or a throw it is the only thing that stops the
724
+ // subprocess, which otherwise runs its whole agentic loop to completion.
725
+ abortController.abort();
726
+ if (args.signal)
727
+ args.signal.removeEventListener("abort", onAbort);
547
728
  }
548
- // Final done signal
549
- yield { type: "chunk", content: "", done: true };
729
+ }
730
+ /** Record token usage from a terminal `result` message (success or error). */
731
+ trackResultUsage(msg, model) {
732
+ const usage = msg.usage;
733
+ if (!usage)
734
+ return;
735
+ const input = num(usage.input_tokens);
736
+ const cacheRead = num(usage.cache_read_input_tokens);
737
+ const cacheWrite = num(usage.cache_creation_input_tokens);
738
+ // genai-prices expects the full prompt total (uncached + cache read/write),
739
+ // matching AnthropicProvider's accounting.
740
+ this.trackUsage(model, {
741
+ inputTokens: input + cacheRead + cacheWrite,
742
+ outputTokens: num(usage.output_tokens),
743
+ cachedTokens: cacheRead,
744
+ cacheWriteTokens: cacheWrite
745
+ });
550
746
  }
551
747
  async generateMessage(args) {
552
- const parts = [];
748
+ let content = "";
553
749
  const toolCalls = [];
554
750
  for await (const item of this.generateMessages(args)) {
555
- if ("type" in item && item.type === "chunk") {
556
- const chunk = item;
557
- if (chunk.content)
558
- parts.push(chunk.content);
559
- }
560
- if ("id" in item && "name" in item && "args" in item) {
751
+ if (isProviderSessionUpdate(item) ||
752
+ isProviderMessageEvent(item) ||
753
+ isProviderStop(item))
754
+ continue;
755
+ if ("args" in item) {
561
756
  toolCalls.push(item);
562
757
  }
758
+ else if (!item.thinking && isString(item.content)) {
759
+ content += item.content;
760
+ }
563
761
  }
564
762
  return {
565
763
  role: "assistant",
566
- content: parts.join(""),
567
- ...(toolCalls.length > 0 ? { toolCalls } : {})
764
+ content: content || null,
765
+ toolCalls: toolCalls.length ? toolCalls : null
568
766
  };
569
767
  }
570
- describeErrorSubtype(subtype) {
571
- switch (subtype) {
572
- case "error_max_turns":
573
- return "agent reached the maximum number of turns";
574
- case "error_during_execution":
575
- return "an error occurred during agent execution";
576
- default:
577
- return subtype ?? "unknown error";
768
+ }
769
+ function num(value) {
770
+ return isFiniteNumber(value) ? value : 0;
771
+ }
772
+ /**
773
+ * `CLAUDE_CODE_OAUTH_TOKEN` matches {@link NESTED_SESSION_ENV} but is the CLI's
774
+ * credential, not session leakage: on a headless host (CI) with no interactive
775
+ * `~/.claude` login it is the *only* way the child authenticates. Keep it.
776
+ */
777
+ const CHILD_ENV_ALLOWLIST = /^CLAUDE_CODE_OAUTH_TOKEN$/;
778
+ /**
779
+ * A copy of the current environment with nested-session leakage stripped, for
780
+ * the SDK's `options.env`. `ANTHROPIC_BASE_URL` and the `HTTP(S)_PROXY` vars are
781
+ * preserved (they are not matched by {@link NESTED_SESSION_ENV}) so API routing
782
+ * keeps working; `CLAUDE_CODE_OAUTH_TOKEN` is explicitly allowlisted so
783
+ * token-based auth survives on headless hosts.
784
+ */
785
+ function buildChildEnv() {
786
+ const env = {};
787
+ const source = typeof process !== "undefined" ? process.env : {};
788
+ for (const [key, value] of Object.entries(source)) {
789
+ if (value === undefined)
790
+ continue;
791
+ if (NESTED_SESSION_ENV.test(key) && !CHILD_ENV_ALLOWLIST.test(key))
792
+ continue;
793
+ env[key] = value;
794
+ }
795
+ return env;
796
+ }
797
+ /**
798
+ * A DB skill name that is safe to use as a filesystem directory. The DB
799
+ * validates names (lowercase `a-z0-9-`), but path safety is enforced here too:
800
+ * a name with a separator or `.`-segment could otherwise escape the plugin
801
+ * dir. Defense in depth — an unsafe name is skipped, not written.
802
+ */
803
+ function isFilesystemSafeSkillName(name) {
804
+ return /^[a-z0-9][a-z0-9-]{0,63}$/.test(name);
805
+ }
806
+ /**
807
+ * Write the user's DB skills to a throwaway local plugin the SDK's native skill
808
+ * loader can discover, and return `{ dir, names }` (or null when nothing was
809
+ * written). The layout is the Claude Code plugin format:
810
+ *
811
+ * <dir>/.claude-plugin/plugin.json
812
+ * <dir>/skills/<name>/SKILL.md (name/description frontmatter + body)
813
+ *
814
+ * This is deliberately separate from `settingSources`: a `plugins` entry loads
815
+ * skills without enabling `~/.claude` / `.claude` settings or `CLAUDE.md`, and
816
+ * `skipMcpDiscovery` blocks any `.mcp.json` the dir might contain. The caller
817
+ * owns cleanup (see {@link ClaudeAgentProvider.generateLoop}'s `finally`).
818
+ */
819
+ async function materializeSkillsPlugin(skills) {
820
+ const usable = skills.filter((s) => isFilesystemSafeSkillName(s.name) && s.content.trim().length > 0);
821
+ if (usable.length === 0)
822
+ return null;
823
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "nodetool-skills-"));
824
+ await fs.mkdir(path.join(dir, ".claude-plugin"), { recursive: true });
825
+ await fs.writeFile(path.join(dir, ".claude-plugin", "plugin.json"), JSON.stringify({
826
+ name: SKILLS_PLUGIN_NAME,
827
+ version: "1.0.0",
828
+ description: "User-defined skills from NodeTool."
829
+ }), "utf8");
830
+ const names = [];
831
+ for (const skill of usable) {
832
+ const skillDir = path.join(dir, "skills", skill.name);
833
+ await fs.mkdir(skillDir, { recursive: true });
834
+ // JSON-stringify the description: a JSON string is a valid YAML double-
835
+ // quoted scalar, so colons / quotes / newlines can't break the frontmatter.
836
+ const md = [
837
+ "---",
838
+ `name: ${skill.name}`,
839
+ `description: ${JSON.stringify(skill.description)}`,
840
+ "---",
841
+ "",
842
+ skill.content.trim(),
843
+ ""
844
+ ].join("\n");
845
+ await fs.writeFile(path.join(skillDir, "SKILL.md"), md, "utf8");
846
+ names.push(skill.name);
847
+ }
848
+ return { dir, names };
849
+ }
850
+ /** Pull a text/thinking delta out of a partial `stream_event`, if any. */
851
+ function partialDelta(msg) {
852
+ const event = msg.event;
853
+ if (event.type !== "content_block_delta")
854
+ return null;
855
+ const delta = event.delta;
856
+ if (delta.type === "text_delta")
857
+ return { text: delta.text };
858
+ if (delta.type === "thinking_delta")
859
+ return { thinking: delta.thinking };
860
+ return null;
861
+ }
862
+ /** The concrete dated model id, captured from a partial `message_start`. */
863
+ function capturedModelFromPartial(msg) {
864
+ const event = msg.event;
865
+ if (event.type !== "message_start")
866
+ return null;
867
+ const model = event.message?.model;
868
+ return isNonEmptyString(model) && model !== "<synthetic>" ? model : null;
869
+ }
870
+ /** Text/thinking blocks of a final assistant message, kept separate. */
871
+ function finalBlocks(msg) {
872
+ const content = msg.message?.content;
873
+ if (!Array.isArray(content))
874
+ return [];
875
+ const out = [];
876
+ for (const block of content) {
877
+ if (block.type === "text" && isString(block.text)) {
878
+ out.push({ content: block.text, thinking: false });
879
+ }
880
+ else if (block.type === "thinking" && isString(block.thinking)) {
881
+ out.push({ content: block.thinking, thinking: true });
882
+ }
883
+ }
884
+ return out;
885
+ }
886
+ /**
887
+ * Recover the plain tool name from what the model emitted.
888
+ *
889
+ * Two prefixes get stripped, in order:
890
+ * - `mcp__<server>__`, which the SDK adds to in-process MCP tool names.
891
+ * - `tools.`, the retired guest toolbelt's call form, which models still
892
+ * turn into a top-level tool name. Without this the call reaches no tool
893
+ * at all.
894
+ */
895
+ function stripToolPrefix(name) {
896
+ let n = isString(name) ? name : "";
897
+ if (n.startsWith(TOOL_PREFIX))
898
+ n = n.slice(TOOL_PREFIX.length);
899
+ if (n.startsWith(GUEST_TOOL_PREFIX))
900
+ n = n.slice(GUEST_TOOL_PREFIX.length);
901
+ return n;
902
+ }
903
+ /** Split a final assistant message into its text and its tool calls. */
904
+ function assistantParts(msg) {
905
+ const content = msg.message?.content;
906
+ let text = "";
907
+ const toolCalls = [];
908
+ if (Array.isArray(content)) {
909
+ for (const raw of content) {
910
+ const b = raw;
911
+ if (b.type === "text" && isString(b.text)) {
912
+ text += b.text;
913
+ }
914
+ else if (b.type === "tool_use") {
915
+ toolCalls.push({
916
+ id: isString(b.id) ? b.id : `call_${toolCalls.length}`,
917
+ name: stripToolPrefix(b.name),
918
+ args: isObjectLike(b.input)
919
+ ? b.input
920
+ : {}
921
+ });
922
+ }
923
+ }
924
+ }
925
+ return { text, toolCalls };
926
+ }
927
+ /** Extract `tool_result` blocks from a user message the SDK emitted. */
928
+ function toolResultsFromUser(msg) {
929
+ const content = msg.message?.content;
930
+ const out = [];
931
+ if (Array.isArray(content)) {
932
+ for (const raw of content) {
933
+ const b = raw;
934
+ if (b.type === "tool_result" && isString(b.tool_use_id)) {
935
+ out.push({
936
+ toolCallId: b.tool_use_id,
937
+ content: toolResultText(b.content)
938
+ });
939
+ }
940
+ }
941
+ }
942
+ return out;
943
+ }
944
+ /** Flatten an MCP tool-result content payload to text. */
945
+ function toolResultText(content) {
946
+ if (isString(content))
947
+ return content;
948
+ if (Array.isArray(content)) {
949
+ return content
950
+ .map((c) => {
951
+ const cc = c;
952
+ return cc.type === "text" && isString(cc.text) ? cc.text : "";
953
+ })
954
+ .join("");
955
+ }
956
+ return "";
957
+ }
958
+ /**
959
+ * Build an in-process MCP tool definition that bridges a NodeTool
960
+ * {@link ProviderTool} to the harness's tool executor. The JSON-Schema input is
961
+ * shimmed to the Zod raw shape the SDK expects.
962
+ */
963
+ function toolDefinition(tool, run, resolveMedia) {
964
+ return {
965
+ name: tool.name,
966
+ description: tool.description ?? "",
967
+ inputSchema: jsonSchemaToZodShape(tool.inputSchema),
968
+ handler: async (toolArgs) => {
969
+ const result = await run(tool.name, toolArgs ?? {});
970
+ const resolved = Array.isArray(result) &&
971
+ result.some((part) => part.type === "image_url") &&
972
+ resolveMedia
973
+ ? await resolveMedia([{ role: "user", content: result }])
974
+ : null;
975
+ const content = resolved?.[0]?.content;
976
+ return {
977
+ content: toolResultToMcpContent(Array.isArray(content) ? content : result)
978
+ };
979
+ }
980
+ };
981
+ }
982
+ /**
983
+ * Decode a NodeTool image content part into an MCP image block (raw base64 +
984
+ * mimeType). Returns null for a remote URL we cannot inline — MCP image content
985
+ * is base64-only, so the caller reports that the image could not be shown.
986
+ */
987
+ function toMcpImageBlock(image) {
988
+ let base64;
989
+ let mimeType = image.mimeType;
990
+ const fromDataUri = (uri) => {
991
+ const match = /^data:([^;,]+)?(;base64)?,([\s\S]*)$/.exec(uri);
992
+ if (!match)
993
+ return;
994
+ mimeType = mimeType ?? match[1] ?? "image/png";
995
+ base64 = match[2]
996
+ ? (match[3] ?? "")
997
+ : Buffer.from(decodeURIComponent(match[3] ?? "")).toString("base64");
998
+ };
999
+ if (isString(image.data)) {
1000
+ if (image.data.startsWith("data:"))
1001
+ fromDataUri(image.data);
1002
+ else
1003
+ base64 = image.data;
1004
+ }
1005
+ else if (image.data instanceof Uint8Array) {
1006
+ base64 = Buffer.from(image.data).toString("base64");
1007
+ }
1008
+ if (!base64 && isString(image.uri) && image.uri.startsWith("data:")) {
1009
+ fromDataUri(image.uri);
1010
+ }
1011
+ if (!base64)
1012
+ return null;
1013
+ return { type: "image", data: base64, mimeType: mimeType ?? "image/png" };
1014
+ }
1015
+ /**
1016
+ * Convert a tool result into MCP content blocks. Text passes through; image
1017
+ * parts become MCP image blocks (the SDK forwards them to Claude as real
1018
+ * images). An image that can't be inlined becomes a failure note.
1019
+ */
1020
+ export function toolResultToMcpContent(result) {
1021
+ if (isString(result)) {
1022
+ return [{ type: "text", text: result }];
1023
+ }
1024
+ const blocks = [];
1025
+ for (const part of result) {
1026
+ if (part.type === "text") {
1027
+ blocks.push({ type: "text", text: part.text });
1028
+ }
1029
+ else if (part.type === "image_url") {
1030
+ const img = toMcpImageBlock(part.image);
1031
+ if (img)
1032
+ blocks.push(img);
1033
+ else {
1034
+ blocks.push({
1035
+ type: "text",
1036
+ text: isString(part.image.uri)
1037
+ ? `[image could not be shown: ${part.image.uri}]`
1038
+ : "[image could not be shown]"
1039
+ });
1040
+ }
1041
+ }
1042
+ }
1043
+ if (blocks.length === 0) {
1044
+ blocks.push({ type: "text", text: "[no content]" });
1045
+ }
1046
+ return blocks;
1047
+ }
1048
+ /** Convert a JSON-Schema object's properties to a Zod raw shape. */
1049
+ function jsonSchemaToZodShape(schema) {
1050
+ const props = schema?.properties ?? {};
1051
+ const required = new Set(schema?.required ?? []);
1052
+ const shape = {};
1053
+ for (const [key, raw] of Object.entries(props)) {
1054
+ let zt = jsonPropToZod(raw);
1055
+ if (!required.has(key))
1056
+ zt = zt.optional();
1057
+ shape[key] = zt;
1058
+ }
1059
+ return shape;
1060
+ }
1061
+ /** Convert one JSON-Schema property to a Zod type (the subset NodeTool uses). */
1062
+ function jsonPropToZod(prop) {
1063
+ const desc = isString(prop?.description) ? prop.description : undefined;
1064
+ let zt;
1065
+ switch (prop?.type) {
1066
+ case "string":
1067
+ zt = z.string();
1068
+ break;
1069
+ case "number":
1070
+ case "integer":
1071
+ zt = z.number();
1072
+ break;
1073
+ case "boolean":
1074
+ zt = z.boolean();
1075
+ break;
1076
+ case "array":
1077
+ zt = z.array(prop.items
1078
+ ? jsonPropToZod(prop.items)
1079
+ : z.unknown());
1080
+ break;
1081
+ case "object": {
1082
+ const shape = jsonSchemaToZodShape(prop);
1083
+ // Free-form objects (no declared sub-properties — e.g. add_node's
1084
+ // `node_properties`) must keep arbitrary keys. `z.object({})` strips
1085
+ // every key, silently dropping all node configuration on the SDK tool
1086
+ // bridge; passthrough preserves the nested values.
1087
+ zt =
1088
+ Object.keys(shape).length > 0
1089
+ ? z.object(shape)
1090
+ : z.object({}).passthrough();
1091
+ break;
578
1092
  }
1093
+ default:
1094
+ zt = z.unknown();
579
1095
  }
580
- isContextLengthError(error) {
581
- if (error instanceof ClaudeAgentError)
582
- return error.kind === "context_length";
583
- const msg = String(error).toLowerCase();
584
- return (msg.includes("context length") ||
585
- msg.includes("context window") ||
586
- msg.includes("token limit") ||
587
- msg.includes("too long") ||
588
- msg.includes("maximum context"));
589
- }
590
- isAuthError(error) {
591
- if (error instanceof ClaudeAgentError)
592
- return error.kind === "auth";
593
- return super.isAuthError(error);
594
- }
595
- isRateLimitError(error) {
596
- if (error instanceof ClaudeAgentError)
597
- return error.kind === "rate_limit";
598
- return super.isRateLimitError(error);
1096
+ return desc ? zt.describe(desc) : zt;
1097
+ }
1098
+ /** Build a descriptive Error from a non-success `result` message. */
1099
+ function resultError(msg) {
1100
+ const parts = [`Claude Agent SDK query failed (${msg.subtype})`];
1101
+ if ("errors" in msg && Array.isArray(msg.errors) && msg.errors.length) {
1102
+ parts.push(msg.errors.join("; "));
1103
+ }
1104
+ else if ("result" in msg && isNonEmptyString(msg.result)) {
1105
+ parts.push(msg.result);
1106
+ }
1107
+ const denials = (msg).permission_denials;
1108
+ if (Array.isArray(denials) && denials.length) {
1109
+ parts.push(`permission denied: ${denials.map((d) => d.tool_name ?? "?").join(", ")}`);
1110
+ }
1111
+ return new Error(parts.join(": "));
1112
+ }
1113
+ /** Flatten a message's content to plain text (text blocks only). */
1114
+ function textOf(content) {
1115
+ if (content == null)
1116
+ return "";
1117
+ if (isString(content))
1118
+ return content;
1119
+ return content
1120
+ .filter((c) => c.type === "text")
1121
+ .map((c) => c.text)
1122
+ .join("");
1123
+ }
1124
+ /** Join all system messages into the replacement system prompt. */
1125
+ function extractSystemPrompt(messages) {
1126
+ const system = messages
1127
+ .filter((m) => m.role === "system")
1128
+ .map((m) => textOf(m.content))
1129
+ .filter(Boolean)
1130
+ .join("\n\n");
1131
+ return system || DEFAULT_SYSTEM_PROMPT;
1132
+ }
1133
+ /**
1134
+ * The new turn(s) to send when resuming: only the user messages added since the
1135
+ * session's checkpoint. Assistant messages in the slice were produced by the
1136
+ * resumed session itself, so they are already known to it and must not be
1137
+ * replayed (re-feeding them would present the model its own prior answer as
1138
+ * user input).
1139
+ */
1140
+ function buildResumeDelta(messages, checkpoint) {
1141
+ return messages
1142
+ .slice(checkpoint)
1143
+ .filter((m) => m.role === "user")
1144
+ .map((m) => textOf(m.content))
1145
+ .filter(Boolean)
1146
+ .join("\n\n");
1147
+ }
1148
+ /**
1149
+ * The prompt for a fresh session. A lone user turn is sent verbatim. When there
1150
+ * is pre-existing FOREIGN history (a cold thread, a model switch, or an
1151
+ * edited/branched conversation) we prime context ONCE with a single delimited
1152
+ * user message instead of rebuilding a `Human:/Assistant:` transcript — the SDK
1153
+ * cannot import external assistant turns, so this is deliberate context priming,
1154
+ * not a faithful reconstruction. Only final assistant TEXT is included
1155
+ * (thinking is stripped by {@link textOf}).
1156
+ */
1157
+ function buildFreshPrompt(messages) {
1158
+ const convo = messages.filter((m) => m.role !== "system");
1159
+ if (convo.length === 0)
1160
+ return "";
1161
+ if (convo.length === 1 && convo[0].role === "user") {
1162
+ return textOf(convo[0].content);
599
1163
  }
1164
+ const last = convo[convo.length - 1];
1165
+ const newTurn = last.role === "user" ? textOf(last.content) : "";
1166
+ const prior = last.role === "user" ? convo.slice(0, -1) : convo;
1167
+ const transcript = prior
1168
+ .map((m) => {
1169
+ const text = textOf(m.content);
1170
+ if (!text)
1171
+ return "";
1172
+ return `${m.role === "assistant" ? "Assistant" : "User"}: ${text}`;
1173
+ })
1174
+ .filter(Boolean)
1175
+ .join("\n\n");
1176
+ const primed = transcript
1177
+ ? `<conversation_so_far>\n${transcript}\n</conversation_so_far>`
1178
+ : "";
1179
+ return [primed, newTurn].filter(Boolean).join("\n\n");
600
1180
  }
601
1181
  //# sourceMappingURL=claude-agent-provider.js.map