onbuzz 4.9.13 → 4.10.0

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 (451) hide show
  1. package/node_modules/glob/README.md +31 -5
  2. package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
  3. package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
  4. package/node_modules/glob/dist/commonjs/glob.js +2 -1
  5. package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
  6. package/node_modules/glob/dist/commonjs/index.min.js +3 -3
  7. package/node_modules/glob/dist/commonjs/index.min.js.map +4 -4
  8. package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
  9. package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
  10. package/node_modules/glob/dist/commonjs/pattern.js +4 -0
  11. package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
  12. package/node_modules/glob/dist/esm/glob.d.ts +8 -0
  13. package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
  14. package/node_modules/glob/dist/esm/glob.js +2 -1
  15. package/node_modules/glob/dist/esm/glob.js.map +1 -1
  16. package/node_modules/glob/dist/esm/index.min.js +3 -3
  17. package/node_modules/glob/dist/esm/index.min.js.map +4 -4
  18. package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
  19. package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
  20. package/node_modules/glob/dist/esm/pattern.js +4 -0
  21. package/node_modules/glob/dist/esm/pattern.js.map +1 -1
  22. package/node_modules/{@isaacs → glob/node_modules}/balanced-match/README.md +7 -10
  23. package/node_modules/{@isaacs → glob/node_modules}/balanced-match/package.json +7 -18
  24. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/README.md +3 -6
  25. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.js +6 -4
  26. package/node_modules/glob/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -0
  27. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.js +6 -4
  28. package/node_modules/glob/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
  29. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/package.json +11 -7
  30. package/node_modules/glob/node_modules/minimatch/README.md +76 -1
  31. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +1 -1
  32. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -1
  33. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -1
  34. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +4 -2
  35. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -1
  36. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +309 -55
  37. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -1
  38. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -1
  39. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +2 -4
  40. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -1
  41. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +1 -1
  42. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -1
  43. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +4 -4
  44. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -1
  45. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +81 -1
  46. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -1
  47. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +232 -134
  48. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -1
  49. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +1 -1
  50. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -1
  51. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +8 -8
  52. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -1
  53. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +1 -1
  54. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -1
  55. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
  56. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +4 -2
  57. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -1
  58. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +309 -55
  59. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -1
  60. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -1
  61. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +2 -4
  62. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
  63. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +1 -1
  64. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -1
  65. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +4 -4
  66. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -1
  67. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +81 -1
  68. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -1
  69. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +232 -134
  70. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -1
  71. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +1 -1
  72. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -1
  73. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +8 -8
  74. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
  75. package/node_modules/glob/node_modules/minimatch/package.json +17 -11
  76. package/node_modules/glob/package.json +10 -13
  77. package/node_modules/minipass/LICENSE.md +55 -0
  78. package/node_modules/minipass/dist/commonjs/index.d.ts +12 -16
  79. package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -1
  80. package/node_modules/minipass/dist/commonjs/index.js +13 -3
  81. package/node_modules/minipass/dist/commonjs/index.js.map +1 -1
  82. package/node_modules/minipass/dist/esm/index.d.ts +12 -16
  83. package/node_modules/minipass/dist/esm/index.d.ts.map +1 -1
  84. package/node_modules/minipass/dist/esm/index.js +3 -1
  85. package/node_modules/minipass/dist/esm/index.js.map +1 -1
  86. package/node_modules/minipass/package.json +9 -14
  87. package/node_modules/path-scurry/node_modules/lru-cache/README.md +96 -10
  88. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.d.ts.map +1 -0
  89. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.js.map +1 -0
  90. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.d.ts +5 -0
  91. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
  92. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +1400 -0
  93. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -0
  94. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
  95. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -0
  96. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
  97. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +7 -0
  98. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts +12 -0
  99. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts.map +1 -0
  100. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
  101. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js.map +1 -0
  102. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.cjs.map +1 -0
  103. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
  104. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
  105. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
  106. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +109 -32
  107. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
  108. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +334 -197
  109. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
  110. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
  111. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +4 -4
  112. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.d.ts.map +1 -0
  113. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.js.map +1 -0
  114. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.d.ts +5 -0
  115. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
  116. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts +1400 -0
  117. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -0
  118. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
  119. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -0
  120. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
  121. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +7 -0
  122. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts +12 -0
  123. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts.map +1 -0
  124. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
  125. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js.map +1 -0
  126. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts +12 -0
  127. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts.map +1 -0
  128. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
  129. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js.map +1 -0
  130. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.d.ts.map +1 -0
  131. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.js.map +1 -0
  132. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
  133. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
  134. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts +1400 -0
  135. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -0
  136. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
  137. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -0
  138. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
  139. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js.map +7 -0
  140. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts +12 -0
  141. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts.map +1 -0
  142. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
  143. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js.map +1 -0
  144. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
  145. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.mjs.map +1 -0
  146. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
  147. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
  148. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +109 -32
  149. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
  150. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +333 -196
  151. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -1
  152. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +1 -1
  153. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +4 -4
  154. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.d.ts.map +1 -0
  155. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.js.map +1 -0
  156. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
  157. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
  158. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts +1400 -0
  159. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -0
  160. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
  161. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js.map +1 -0
  162. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
  163. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js.map +7 -0
  164. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts +12 -0
  165. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts.map +1 -0
  166. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
  167. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js.map +1 -0
  168. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts +12 -0
  169. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts.map +1 -0
  170. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
  171. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js.map +1 -0
  172. package/node_modules/path-scurry/node_modules/lru-cache/package.json +71 -18
  173. package/node_modules/path-scurry/package.json +8 -24
  174. package/package.json +1 -1
  175. package/scripts/debug-balance-probe.mjs +35 -35
  176. package/scripts/push-image.sh +43 -43
  177. package/scripts/setup-acr.sh +65 -65
  178. package/scripts/verify-optional-deps.js +96 -1
  179. package/src/__tests__/composioCliFlags.test.js +239 -239
  180. package/src/analyzers/CSSAnalyzer.js +298 -297
  181. package/src/analyzers/ConfigValidator.js +691 -690
  182. package/src/analyzers/ESLintAnalyzer.js +320 -320
  183. package/src/analyzers/JavaScriptAnalyzer.js +260 -261
  184. package/src/analyzers/PrettierFormatter.js +246 -247
  185. package/src/analyzers/PythonAnalyzer.js +283 -283
  186. package/src/analyzers/SecurityAnalyzer.js +729 -729
  187. package/src/analyzers/SparrowAnalyzer.js +341 -341
  188. package/src/analyzers/TypeScriptAnalyzer.js +247 -247
  189. package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -41
  190. package/src/analyzers/__tests__/ConfigValidator.test.js +362 -362
  191. package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -40
  192. package/src/analyzers/__tests__/PythonAnalyzer.test.js +205 -208
  193. package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -303
  194. package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -187
  195. package/src/analyzers/codeCloneDetector/analyzer.js +344 -344
  196. package/src/analyzers/codeCloneDetector/detector.js +250 -250
  197. package/src/analyzers/codeCloneDetector/index.js +194 -192
  198. package/src/analyzers/codeCloneDetector/parser.js +199 -199
  199. package/src/core/__tests__/agentPool.test.js +866 -866
  200. package/src/core/__tests__/agentPoolAutoResume.test.js +209 -209
  201. package/src/core/__tests__/agentPoolWakeOnMessage.test.js +315 -315
  202. package/src/core/__tests__/agentScheduler.emptyResponseChatStall.test.js +213 -213
  203. package/src/core/__tests__/agentScheduler.errorCategorisation.test.js +246 -246
  204. package/src/core/__tests__/agentScheduler.firstChunkTimeout.test.js +138 -138
  205. package/src/core/__tests__/agentScheduler.modeTransitions.test.js +233 -233
  206. package/src/core/__tests__/agentScheduler.nativePromptPick.test.js +319 -319
  207. package/src/core/__tests__/agentScheduler.taskLifecycleInstruction.test.js +78 -78
  208. package/src/core/__tests__/agentScheduler.visualizer.test.js +258 -258
  209. package/src/core/__tests__/flowCheckpointStore.test.js +140 -140
  210. package/src/core/__tests__/flowEndToEnd.test.js +565 -565
  211. package/src/core/__tests__/flowFieldMapping.test.js +188 -189
  212. package/src/core/__tests__/flowLintClientMirror.test.js +96 -98
  213. package/src/core/__tests__/flowSavePayload.test.js +170 -169
  214. package/src/core/__tests__/flowTemplates.test.js +311 -311
  215. package/src/core/__tests__/flowVersionStore.test.js +123 -123
  216. package/src/core/__tests__/messageProcessor.test.js +669 -669
  217. package/src/core/__tests__/stateManager.test.js +0 -1
  218. package/src/core/agentPool.js +2474 -2475
  219. package/src/core/agentScheduler.js +1 -4
  220. package/src/core/contextManager.js +708 -708
  221. package/src/core/flowExecutor.js +1510 -1510
  222. package/src/core/flowFieldMapping.js +136 -138
  223. package/src/core/messageProcessor.js +953 -954
  224. package/src/core/orchestrator.js +593 -595
  225. package/src/core/stateManager.js +1765 -1752
  226. package/src/index.js +1221 -1221
  227. package/src/interfaces/__tests__/archivedAgentDelete.test.js +207 -207
  228. package/src/interfaces/__tests__/bulkAgentRoute.test.js +361 -361
  229. package/src/interfaces/__tests__/imageServing.test.js +228 -228
  230. package/src/interfaces/__tests__/remoteSessionAuth.test.js +308 -308
  231. package/src/interfaces/__tests__/videoJobsRoutes.test.js +178 -179
  232. package/src/interfaces/__tests__/webServer.marketplace.test.js +629 -629
  233. package/src/interfaces/schedulerRoutes.js +50 -50
  234. package/src/interfaces/terminal/__tests__/smoke/connection.test.js +341 -350
  235. package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -156
  236. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +325 -330
  237. package/src/interfaces/terminal/__tests__/smoke/tools.test.js +385 -388
  238. package/src/interfaces/terminal/api/session.js +265 -266
  239. package/src/interfaces/terminal/api/websocket.js +496 -497
  240. package/src/interfaces/terminal/components/AgentCreator.js +691 -705
  241. package/src/interfaces/terminal/components/AgentEditor.js +676 -678
  242. package/src/interfaces/terminal/components/AgentSwitcher.js +331 -330
  243. package/src/interfaces/terminal/components/ErrorPanel.js +263 -264
  244. package/src/interfaces/terminal/components/Header.js +28 -28
  245. package/src/interfaces/terminal/components/Layout.js +598 -603
  246. package/src/interfaces/terminal/components/MessageList.js +280 -281
  247. package/src/interfaces/terminal/components/SettingsPanel.js +410 -415
  248. package/src/interfaces/terminal/components/StatusBar.js +2 -0
  249. package/src/interfaces/terminal/index.js +168 -168
  250. package/src/interfaces/terminal/state/useAgentControl.js +496 -496
  251. package/src/interfaces/terminal/state/useAgents.js +537 -537
  252. package/src/interfaces/terminal/state/useMessages.js +629 -630
  253. package/src/interfaces/terminal/state/useTools.js +554 -554
  254. package/src/interfaces/terminal/utils/debugLogger.js +44 -44
  255. package/src/interfaces/terminal/utils/settingsStorage.js +232 -232
  256. package/src/interfaces/webServer.js +7578 -7579
  257. package/src/interfaces/webServer.js.bak +7046 -7046
  258. package/src/modules/fileExplorer/__tests__/zipDownload.test.js +237 -237
  259. package/src/modules/fileExplorer/controller.js +470 -469
  260. package/src/modules/fileExplorer/routes.js +285 -286
  261. package/src/modules/widget/__tests__/isDisabled.test.js +41 -41
  262. package/src/modules/widget/__tests__/routes.test.js +677 -678
  263. package/src/modules/widget/__tests__/runtime.test.js +401 -401
  264. package/src/modules/widget/__tests__/versioning.test.js +309 -309
  265. package/src/modules/widget/__tests__/webComponentRuntime.test.js +565 -565
  266. package/src/modules/widget/__tests__/widgetTool.test.js +316 -316
  267. package/src/modules/widget/routes.js +435 -435
  268. package/src/modules/widget/runtime/bundle.js +640 -640
  269. package/src/modules/widget/runtime/webComponentBundle.js +470 -470
  270. package/src/modules/widget/schema.js +182 -181
  271. package/src/modules/widget/widgetTool.js +1389 -1389
  272. package/src/services/__tests__/agentActivityService.test.js +401 -402
  273. package/src/services/__tests__/benchmarkService.test.js +184 -184
  274. package/src/services/__tests__/contextInjectionService.test.js +246 -246
  275. package/src/services/__tests__/conversationQuery.test.js +721 -723
  276. package/src/services/__tests__/credentialVault.test.js +469 -469
  277. package/src/services/__tests__/discordService.integration.test.js +638 -639
  278. package/src/services/__tests__/flowContextService.test.js +590 -590
  279. package/src/services/__tests__/memoryService.test.js +1 -1
  280. package/src/services/__tests__/messageSource.test.js +380 -380
  281. package/src/services/__tests__/modelRouterNaming.test.js +111 -111
  282. package/src/services/__tests__/projectDetector.test.js +34 -34
  283. package/src/services/__tests__/promptService.test.js +242 -242
  284. package/src/services/__tests__/telegramService.test.js +941 -941
  285. package/src/services/__tests__/tokenCountingService.test.js +48 -48
  286. package/src/services/agentActivityService.js +419 -420
  287. package/src/services/aiService.js +2997 -3001
  288. package/src/services/apiKeyManager.js +359 -359
  289. package/src/services/benchmarkService.js +196 -196
  290. package/src/services/codebaseKnowledgeService.js +2 -2
  291. package/src/services/composioService.js +738 -738
  292. package/src/services/conversationCompactionService.js +1258 -1257
  293. package/src/services/credentialVault.js +685 -685
  294. package/src/services/discordService.js +792 -793
  295. package/src/services/embeddings/__tests__/azureCustomProvider.test.js +232 -232
  296. package/src/services/embeddings/__tests__/embeddingService.test.js +417 -417
  297. package/src/services/embeddings/__tests__/localProvider.test.js +263 -263
  298. package/src/services/embeddings/autoRecall.js +218 -219
  299. package/src/services/embeddings/indexers/__tests__/agentIndexer.test.js +232 -232
  300. package/src/services/embeddings/indexers/__tests__/memoryIndexer.test.js +418 -418
  301. package/src/services/embeddings/indexers/__tests__/reminisceIndexer.test.js +356 -357
  302. package/src/services/embeddings/indexers/__tests__/skillsIndexer.test.js +145 -145
  303. package/src/services/embeddings/indexers/__tests__/taskIndexer.test.js +146 -146
  304. package/src/services/embeddings/indexers/composioIndexer.js +279 -279
  305. package/src/services/embeddings/providerInterface.js +206 -206
  306. package/src/services/embeddings/providers/localProvider.js +11 -7
  307. package/src/services/embeddings/providers/openaiProvider.js +101 -101
  308. package/src/services/embeddings/vectorStore/inMemoryJsonStore.js +356 -356
  309. package/src/services/errorHandler.js +809 -809
  310. package/src/services/flowContextService.js +586 -586
  311. package/src/services/grounding/MockAdapter.js +125 -125
  312. package/src/services/modelRouterService.js +26 -31
  313. package/src/services/modelsService.js +322 -322
  314. package/src/services/ollamaService.js +452 -452
  315. package/src/services/projectDetector.js +403 -404
  316. package/src/services/promptService.js +418 -418
  317. package/src/services/qualityInspector.js +795 -795
  318. package/src/services/scheduleService.js +726 -726
  319. package/src/services/serviceRegistry.js +386 -386
  320. package/src/services/telegrafBot.js +174 -174
  321. package/src/services/telegramService.js +1972 -1972
  322. package/src/services/visualEditorBridge.js +1033 -1033
  323. package/src/services/visualEditorServer.js +1769 -1774
  324. package/src/services/whatsappService.js +667 -668
  325. package/src/tools/__tests__/agentCommunicationTool.findAgent.test.js +226 -226
  326. package/src/tools/__tests__/agentCommunicationTool.test.js +3 -3
  327. package/src/tools/__tests__/agentDelayTool.test.js +342 -342
  328. package/src/tools/__tests__/baseTool.test.js +3 -3
  329. package/src/tools/__tests__/codeMapTool.test.js +915 -915
  330. package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -309
  331. package/src/tools/__tests__/fileTreeTool.test.js +274 -274
  332. package/src/tools/__tests__/filesystemTool.test.js +815 -815
  333. package/src/tools/__tests__/foundryWebSearchTool.test.js +252 -252
  334. package/src/tools/__tests__/imageTool.validator.test.js +194 -194
  335. package/src/tools/__tests__/jobDoneTool.test.js +580 -581
  336. package/src/tools/__tests__/memoryTool.forgetStale.test.js +272 -272
  337. package/src/tools/__tests__/memoryTool.reminisce.test.js +2 -2
  338. package/src/tools/__tests__/memoryTool.reminisceSemanticSearch.test.js +301 -301
  339. package/src/tools/__tests__/memoryTool.semanticSearch.test.js +405 -405
  340. package/src/tools/__tests__/memoryTool.teamPool.test.js +293 -293
  341. package/src/tools/__tests__/memoryTool.test.js +1 -1
  342. package/src/tools/__tests__/seekTool.test.js +282 -282
  343. package/src/tools/__tests__/skillsTool.search.test.js +164 -164
  344. package/src/tools/__tests__/skillsTool.test.js +226 -226
  345. package/src/tools/__tests__/staticAnalysisTool.test.js +509 -509
  346. package/src/tools/__tests__/taskManagerTool.discipline.test.js +137 -137
  347. package/src/tools/__tests__/taskManagerTool.search.test.js +143 -143
  348. package/src/tools/__tests__/taskManagerTool.test.js +866 -866
  349. package/src/tools/__tests__/terminalTool.test.js +448 -448
  350. package/src/tools/__tests__/toolShapeForgiveness.test.js +259 -260
  351. package/src/tools/__tests__/userPromptTool.test.js +297 -297
  352. package/src/tools/__tests__/videoTool.jobs.test.js +147 -147
  353. package/src/tools/__tests__/webTool.e2e.test.js +609 -603
  354. package/src/tools/__tests__/webTool.unit.test.js +195 -195
  355. package/src/tools/__tests__/webTool.visionModel.test.js +75 -75
  356. package/src/tools/agentCommunicationTool.js +8 -10
  357. package/src/tools/agentDelayTool.js +496 -497
  358. package/src/tools/asyncToolManager.js +602 -603
  359. package/src/tools/baseTool.js +12 -11
  360. package/src/tools/cloneDetectionTool.js +576 -581
  361. package/src/tools/codeMapTool.js +0 -6
  362. package/src/tools/composioTool.js +617 -617
  363. package/src/tools/dependencyResolverTool.js +1211 -1212
  364. package/src/tools/desktop/DesktopTool.js +629 -638
  365. package/src/tools/desktop/__tests__/DesktopTool.e2e.test.js +306 -306
  366. package/src/tools/desktop/__tests__/DesktopTool.test.js +507 -507
  367. package/src/tools/desktop/__tests__/osController.test.js +364 -364
  368. package/src/tools/desktop/osController.js +491 -491
  369. package/src/tools/docxTool.js +623 -623
  370. package/src/tools/excelTool.js +636 -636
  371. package/src/tools/fileContentReplaceTool.js +5 -7
  372. package/src/tools/fileSystemTool.js +12 -19
  373. package/src/tools/fileTreeTool.js +840 -840
  374. package/src/tools/foundryWebSearchTool.js +273 -273
  375. package/src/tools/helpTool.js +198 -198
  376. package/src/tools/imageTool.js +1397 -1397
  377. package/src/tools/importAnalyzerTool.js +1056 -1056
  378. package/src/tools/jobDoneTool.js +495 -495
  379. package/src/tools/memoryTool.js +1 -1
  380. package/src/tools/office/pres/__tests__/presSystem.test.js +365 -365
  381. package/src/tools/office/pres/archetypes/agenda.js +61 -61
  382. package/src/tools/office/pres/archetypes/bentoGrid.js +218 -219
  383. package/src/tools/office/pres/archetypes/bigStat.js +140 -142
  384. package/src/tools/office/pres/archetypes/closing.js +70 -70
  385. package/src/tools/office/pres/archetypes/hero.js +70 -70
  386. package/src/tools/office/pres/archetypes/productHero.js +93 -94
  387. package/src/tools/office/pres/archetypes/table.js +73 -74
  388. package/src/tools/office/pres/backgrounds/orb.js +66 -66
  389. package/src/tools/office/pres/components.js +422 -423
  390. package/src/tools/officeTool.js +441 -441
  391. package/src/tools/pdfTool.js +625 -627
  392. package/src/tools/platformControlTool.js +1081 -1081
  393. package/src/tools/seekTool.js +917 -918
  394. package/src/tools/skillsTool.js +1 -1
  395. package/src/tools/staticAnalysisTool.js +2143 -2146
  396. package/src/tools/taskManagerTool.js +3324 -3324
  397. package/src/tools/terminalTool.js +2615 -2618
  398. package/src/tools/videoTool.js +1303 -1303
  399. package/src/tools/visionTool.js +508 -508
  400. package/src/tools/visualEditorTool.js +1289 -1290
  401. package/src/tools/webTool.js +3368 -3368
  402. package/src/tools/whatsappTool.js +464 -464
  403. package/src/types/__tests__/agent.test.js +499 -499
  404. package/src/types/__tests__/contextReference.test.js +606 -606
  405. package/src/types/__tests__/conversation.test.js +555 -555
  406. package/src/types/__tests__/toolCommand.test.js +584 -584
  407. package/src/types/contextReference.js +974 -971
  408. package/src/types/conversation.js +729 -729
  409. package/src/types/toolCommand.js +746 -746
  410. package/src/utilities/__tests__/attachmentValidator.test.js +80 -80
  411. package/src/utilities/__tests__/auditReport.test.js +328 -328
  412. package/src/utilities/__tests__/directoryAccessManager.test.js +388 -388
  413. package/src/utilities/__tests__/jsonRepair.test.js +103 -104
  414. package/src/utilities/__tests__/modeTransitionReasons.test.js +105 -105
  415. package/src/utilities/__tests__/platformUtils.test.js +80 -87
  416. package/src/utilities/__tests__/structuredFileValidator.test.js +261 -263
  417. package/src/utilities/__tests__/toolConstants.test.js +92 -94
  418. package/src/utilities/__tests__/useIsTouchDevice.detect.test.js +114 -114
  419. package/src/utilities/__tests__/webUiUtilSync.test.js +117 -117
  420. package/src/utilities/attachmentValidator.js +284 -288
  421. package/src/utilities/authCache.js.backup-1779570472481 +121 -121
  422. package/src/utilities/browserStealth.js +631 -630
  423. package/src/utilities/configManager.js +616 -617
  424. package/src/utilities/directoryAccessManager.js +564 -565
  425. package/src/utilities/fileProcessor.js +308 -307
  426. package/src/utilities/humanBehavior.js +454 -453
  427. package/src/utilities/logger.js +479 -479
  428. package/src/utilities/structuredFileValidator.js +696 -699
  429. package/src/utilities/tagParser.js +5 -10
  430. package/src/utilities/userDataDir.js +308 -308
  431. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +0 -1
  432. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +0 -1
  433. package/node_modules/minipass/LICENSE +0 -15
  434. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/LICENSE.md +0 -0
  435. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts +0 -0
  436. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts.map +0 -0
  437. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js +0 -0
  438. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js.map +0 -0
  439. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/package.json +0 -0
  440. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts +0 -0
  441. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts.map +0 -0
  442. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js +0 -0
  443. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js.map +0 -0
  444. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/package.json +0 -0
  445. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/LICENSE +0 -0
  446. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts +0 -0
  447. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts.map +0 -0
  448. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/package.json +0 -0
  449. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts +0 -0
  450. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts.map +0 -0
  451. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/package.json +0 -0
@@ -1,738 +1,738 @@
1
- /**
2
- * composioService — shared client + operations for the Composio
3
- * integration hub. Used by:
4
- *
5
- * - composioTool (agent-facing) — invokes execute/connect/etc.
6
- * - REST endpoints (UI-facing) — power the Connections page
7
- * - CLI subcommands (operator) — `loxia composio <verb>`
8
- *
9
- * Single source of truth for:
10
- * - SDK loading (dynamic + optional)
11
- * - API-key resolution (COMPOSIO_API_KEY env var)
12
- * - userId convention (agentId for agent calls; "operator" for UI/CLI)
13
- *
14
- * Every method returns a structured `{ success, ...data }` object so
15
- * callers can render rich errors instead of relying on thrown exceptions.
16
- * The SDK and API key are checked once and cached; transient SDK errors
17
- * are surfaced per-call.
18
- *
19
- * userId conventions:
20
- * - composioTool maps agentId → userId so each agent has isolated
21
- * OAuth tokens.
22
- * - UI/CLI use the literal string "operator" so the human user's
23
- * connections are shared across agents the operator manages.
24
- * (Override via `?userId=…` on the REST routes, or `--user=…`
25
- * on the CLI, if multi-tenant ever lands.)
26
- */
27
-
28
- import { getApiKeyManager } from './apiKeyManager.js';
29
-
30
- let _client = null;
31
- let _unavailableReason = null;
32
- let _activeKeyFingerprint = null; // First 8 chars of the key the cached client was built with.
33
-
34
- /**
35
- * Reset cached client + unavailability — for tests, and for live
36
- * key-rotation (the API-key endpoint calls this so the next op picks
37
- * up the new key without a process restart).
38
- */
39
- export function _resetForTests() {
40
- _client = null;
41
- _unavailableReason = null;
42
- _activeKeyFingerprint = null;
43
- _toolkitsCache = null;
44
- _toolkitsCacheAt = 0;
45
- }
46
-
47
- /**
48
- * Cache-invalidation hook for live key rotation. Production callers
49
- * (the POST /api/composio/apikey route) call this after persisting a
50
- * new key so the next service op rebuilds the SDK client with it.
51
- *
52
- * Different name than `_resetForTests` so it's obvious in production
53
- * code that this is intentional, not a test shortcut.
54
- */
55
- export function invalidateClientCache() {
56
- _client = null;
57
- _unavailableReason = null;
58
- _activeKeyFingerprint = null;
59
- // Toolkits are per-org. After a key rotation the next listToolkits()
60
- // call must hit REST again, not the previous key's cached catalog.
61
- _invalidateToolkitsCache();
62
- }
63
-
64
- /**
65
- * Resolve the Composio API key from the highest-priority source:
66
- * 1. ApiKeyManager (user-stored via the UI — encrypted on disk)
67
- * 2. process.env.COMPOSIO_API_KEY (legacy / operator override)
68
- *
69
- * Returns { key, source: 'vault' | 'env' | null }.
70
- */
71
- function resolveApiKey() {
72
- const mgr = getApiKeyManager();
73
- const vaultKey = mgr?.keys?.vendorKeys?.composio;
74
- if (vaultKey && typeof vaultKey === 'string' && vaultKey.trim()) {
75
- return { key: vaultKey.trim(), source: 'vault' };
76
- }
77
- const envKey = process.env.COMPOSIO_API_KEY;
78
- if (envKey && typeof envKey === 'string' && envKey.trim()) {
79
- return { key: envKey.trim(), source: 'env' };
80
- }
81
- return { key: null, source: null };
82
- }
83
-
84
- /**
85
- * Load the SDK on first use. Returns { client } on success or { error }
86
- * (string) when either the SDK is missing or the API key is unset.
87
- *
88
- * Exported so composioTool (agent-facing) can share the same key
89
- * resolution + client cache the REST + CLI surfaces use. Previously
90
- * the tool kept its own client and read process.env.COMPOSIO_API_KEY
91
- * directly, which meant keys saved via the UI (encrypted into
92
- * ApiKeyManager.vendorKeys.composio) were invisible to the agent
93
- * runtime — every agent invocation failed with "COMPOSIO_API_KEY env
94
- * var is not set" even though the UI had reported the key as ready.
95
- */
96
- export async function getClient() {
97
- const { key: apiKey, source } = resolveApiKey();
98
- const fingerprint = apiKey ? apiKey.slice(0, 8) : null;
99
-
100
- // If the active key has changed since we last built the client (e.g.
101
- // user just saved a new one via the UI), force a rebuild.
102
- if (_client && fingerprint !== _activeKeyFingerprint) {
103
- _client = null;
104
- _unavailableReason = null;
105
- }
106
-
107
- if (_client) return { client: _client };
108
- if (_unavailableReason) return { error: _unavailableReason };
109
-
110
- if (!apiKey) {
111
- const reason = 'No Composio API key configured. Add one on the Integrations page or set COMPOSIO_API_KEY in the environment.';
112
- _unavailableReason = reason;
113
- return { error: reason };
114
- }
115
-
116
- let Composio;
117
- try {
118
- const mod = await import('@composio/core');
119
- Composio = mod.Composio || mod.default?.Composio || mod.default;
120
- } catch (e) {
121
- // Do NOT cache this one. Background: `npm audit fix --force` has
122
- // historically nuked @composio/core (its tree carries the loudest
123
- // CVEs in the project — 16 vulns, 2 critical), then a follow-up
124
- // `npm install @composio/core@^0.6.11` restores it. If we cache the
125
- // "SDK is not installed" reason permanently, every subsequent call
126
- // returns the stale failure until the CLI is restarted — exactly the
127
- // "I installed it but the UI still complains" loop we hit. Returning
128
- // the error without caching lets the next call retry the import.
129
- const reason = `@composio/core SDK is not installed. Run \`npm install @composio/core\`. Detail: ${e.message}`;
130
- return { error: reason };
131
- }
132
-
133
- try {
134
- _client = new Composio({ apiKey });
135
- _activeKeyFingerprint = fingerprint;
136
- return { client: _client };
137
- } catch (e) {
138
- // Same reasoning as above — SDK init failures can come from transient
139
- // upstream issues (bad key, network) that don't need a CLI restart to
140
- // clear. Let the next call retry.
141
- const reason = `Composio SDK initialization failed: ${e.message}`;
142
- return { error: reason };
143
- }
144
- }
145
-
146
- /**
147
- * Some Composio SDK methods return plain Arrays (after their own
148
- * internal transform), others return paginated wrappers like
149
- * `{items: [], nextCursor}`. Normalises any of: Array | {items} |
150
- * {data} | {results} | falsy → a plain Array. Exported so callers
151
- * outside this module can use the same logic on raw SDK responses.
152
- *
153
- * Note: this used to be the entire fix for "list-toolkits returns
154
- * empty". It wasn't enough. The real bug was that we called
155
- * `client.toolkits.list()` (and `client.tools.list()`) — methods that
156
- * DON'T EXIST on @composio/core v0.6.x. The SDK's public surface is
157
- * `toolkits.get(query)` and `tools.get(userId, filter)`. Calling a
158
- * non-existent method silently returned `undefined`, which this
159
- * normaliser then turned into `[]`, masking the bug as "zero results".
160
- *
161
- * Now that listToolkits/listTools go through direct REST calls (the
162
- * SDK's `toolkits.get` drops the pagination cursor in its transform,
163
- * so we can't get past page 1 through it), this helper is mostly used
164
- * by callers that touch the SDK directly. It stays exported for them.
165
- */
166
- export function _normalizeListResult(result) {
167
- if (Array.isArray(result)) return result;
168
- if (!result || typeof result !== 'object') return [];
169
- if (Array.isArray(result.items)) return result.items;
170
- if (Array.isArray(result.data)) return result.data;
171
- if (Array.isArray(result.results)) return result.results;
172
- return [];
173
- }
174
-
175
- /* ------------------------------------------------------------------------ */
176
- /* REST helpers — paginated discovery against the Composio v3.1 API. */
177
- /* */
178
- /* Two of the SDK's "list" methods are unusable for our purposes: */
179
- /* */
180
- /* - `client.toolkits.list()` doesn't exist (no public list method). */
181
- /* The SDK exposes `toolkits.get(query)` which internally calls the */
182
- /* inner OpenAPI client's toolkits.list, then transforms the result */
183
- /* down to a flat Array — discarding `nextCursor`. So even when we */
184
- /* do call the right method, we only get page 1 (max 50 items) and */
185
- /* have no way to ask for page 2. */
186
- /* */
187
- /* - `client.tools.list()` is similarly internal-only. */
188
- /* */
189
- /* Solution: bypass the SDK for full-catalog discovery and call REST */
190
- /* directly with cursor-based pagination. The SDK is still used for */
191
- /* everything else (connect/initiate, execute, etc.). */
192
- /* ------------------------------------------------------------------------ */
193
-
194
- function _getApiBaseUrl() {
195
- return process.env.COMPOSIO_BASE_URL || 'https://backend.composio.dev';
196
- }
197
-
198
- async function _restGet(apiKey, path, query = {}) {
199
- const url = new URL(path, _getApiBaseUrl());
200
- for (const [k, v] of Object.entries(query)) {
201
- if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
202
- }
203
- const res = await fetch(url, {
204
- method: 'GET',
205
- headers: { 'x-api-key': apiKey, accept: 'application/json' },
206
- });
207
- const text = await res.text();
208
- let body; try { body = text ? JSON.parse(text) : null; } catch { body = { _rawText: text }; }
209
- if (!res.ok) {
210
- const detail = body?.error || body?.message || (text && text.slice(0, 200)) || `HTTP ${res.status}`;
211
- const e = new Error(`Composio REST ${path} → HTTP ${res.status}: ${detail}`);
212
- e.status = res.status;
213
- e.body = body;
214
- throw e;
215
- }
216
- return body;
217
- }
218
-
219
- async function _restListPaginated(apiKey, path, baseQuery = {}, options = {}) {
220
- const limit = options.limit || 50;
221
- const maxPages = options.maxPages || 100;
222
- const items = [];
223
- let cursor;
224
- let pages = 0;
225
- do {
226
- const body = await _restGet(apiKey, path, { ...baseQuery, limit, ...(cursor ? { cursor } : {}) });
227
- const pageItems = body?.items || body?.data || (Array.isArray(body) ? body : []);
228
- items.push(...pageItems);
229
- cursor = body?.next_cursor || body?.nextCursor || null;
230
- pages++;
231
- if (pages >= maxPages) break;
232
- } while (cursor);
233
- return items;
234
- }
235
-
236
- /** Map a REST v3.1 toolkit (snake_case) to the camelCase shape callers
237
- * inside this codebase already expect. */
238
- function _camelizeToolkit(t) {
239
- return {
240
- slug: t.slug,
241
- name: t.name,
242
- description: t.meta?.description,
243
- meta: t.meta ? {
244
- description: t.meta.description,
245
- logo: t.meta.logo,
246
- categories: t.meta.categories,
247
- createdAt: t.meta.created_at,
248
- updatedAt: t.meta.updated_at,
249
- toolsCount: t.meta.tools_count,
250
- triggersCount: t.meta.triggers_count,
251
- appUrl: t.meta.app_url,
252
- } : undefined,
253
- isLocalToolkit: t.is_local_toolkit,
254
- authSchemes: t.auth_schemes,
255
- composioManagedAuthSchemes: t.composio_managed_auth_schemes,
256
- noAuth: t.no_auth,
257
- };
258
- }
259
-
260
- /** Map a REST v3.1 tool (snake_case) to the camelCase shape callers
261
- * inside this codebase expect — including a few aliases (input,
262
- * parameters, inputParameters) because the various code paths each
263
- * evolved their own field name preference. */
264
- function _camelizeTool(t) {
265
- const params = t.input_parameters || t.parameters || t.input;
266
- return {
267
- slug: t.slug,
268
- name: t.name,
269
- displayName: t.display_name || t.name,
270
- description: t.description,
271
- inputParameters: params,
272
- input: params,
273
- parameters: params,
274
- outputParameters: t.output_parameters,
275
- toolkit: t.toolkit,
276
- toolkitSlug: t.toolkit?.slug || t.toolkit_slug,
277
- deprecated: t.deprecated,
278
- tags: t.tags,
279
- scopes: t.scopes,
280
- };
281
- }
282
-
283
- /**
284
- * Optionally inject a stub client. ONLY for tests.
285
- *
286
- * Also pins the fingerprint to whatever the current resolved key
287
- * happens to be, so the live-rotation check inside `getClient()`
288
- * doesn't immediately discard the stub.
289
- *
290
- * @param {object} stub
291
- */
292
- export function _injectClientForTests(stub) {
293
- _client = stub;
294
- _unavailableReason = null;
295
- const { key } = resolveApiKey();
296
- _activeKeyFingerprint = key ? key.slice(0, 8) : '__test__';
297
- }
298
-
299
- /* ------------------------------------------------------------------------ */
300
- /* Operations */
301
- /* ------------------------------------------------------------------------ */
302
-
303
- /**
304
- * Lightweight readiness check. Avoids running a real SDK call.
305
- *
306
- * Returns `keySource` so the UI can tell whether the active key came
307
- * from the encrypted vault (user set it via the form) or from the env
308
- * var (operator-managed). That changes the affordances on the
309
- * Integrations page (e.g. don't show "Forget" for env-var keys).
310
- *
311
- * @returns {Promise<{available: boolean, reason?: string, keySource?: 'vault'|'env'|null}>}
312
- */
313
- export async function isAvailable() {
314
- const { source } = resolveApiKey();
315
- const { client, error } = await getClient();
316
- if (error) return { available: false, reason: error, keySource: source };
317
- return { available: !!client, keySource: source };
318
- }
319
-
320
- /**
321
- * Persist a Composio API key via ApiKeyManager (encrypted at rest)
322
- * and invalidate the SDK client cache so the next op uses it.
323
- *
324
- * Validation: an empty string clears the key. Otherwise we trim
325
- * whitespace and require >= 8 chars (placeholder guard — the real
326
- * key shape is `comp_…` and is much longer).
327
- *
328
- * Does NOT verify the key against Composio's API here — the UI calls
329
- * isAvailable() right after to do a real probe and surface the result.
330
- *
331
- * @param {string|null} apiKey The key to persist, or empty/null to clear.
332
- * @returns {Promise<{success: true, cleared?: boolean} | {success: false, error: string}>}
333
- */
334
- export async function setApiKey(apiKey) {
335
- const mgr = getApiKeyManager();
336
- if (!mgr) {
337
- return { success: false, error: 'ApiKeyManager is not initialized; key cannot be persisted.' };
338
- }
339
- const trimmed = (apiKey || '').trim();
340
-
341
- // Empty → clear the stored key.
342
- if (!trimmed) {
343
- const existing = { ...(mgr.keys?.vendorKeys || {}) };
344
- delete existing.composio;
345
- mgr.keys.vendorKeys = existing;
346
- await mgr.persist();
347
- invalidateClientCache();
348
- return { success: true, cleared: true };
349
- }
350
-
351
- // Cheap shape guard. The full validation is "does it actually authenticate"
352
- // which the caller probes with isAvailable() after.
353
- if (trimmed.length < 8) {
354
- return { success: false, error: 'API key looks too short. Paste the full key from dashboard.composio.dev.' };
355
- }
356
-
357
- await mgr.setSessionKeys(null, { vendorKeys: { composio: trimmed } });
358
- invalidateClientCache();
359
- return { success: true };
360
- }
361
-
362
- /**
363
- * Convenience: clear the stored key. Equivalent to setApiKey('').
364
- * Exposed separately so the DELETE route reads naturally.
365
- */
366
- export async function forgetApiKey() {
367
- return setApiKey('');
368
- }
369
-
370
- /**
371
- * List ALL toolkits in Composio's catalog, paginated to exhaustion.
372
- *
373
- * Cannot use the SDK here:
374
- * - `client.toolkits.list()` doesn't exist on @composio/core ≤0.6.x
375
- * (it's `toolkits.get(query)`); we shipped the wrong method name
376
- * for a long time and got `success: true, toolkits: []` because
377
- * `client.toolkits?.list?.()` returned `undefined` and
378
- * `_normalizeListResult` dutifully turned that into `[]`.
379
- * - Even after switching to `toolkits.get`, the SDK's internal
380
- * `transformToolkitListResponse` discards `nextCursor`, so we'd
381
- * only see page 1 (max 50 toolkits — out of ~1043 in the catalog).
382
- *
383
- * So we go straight to REST. Pagination via cursor, ~21 round-trips
384
- * for the full catalog (~10s total), then we cache the result for a
385
- * minute so subsequent calls in the same agent turn are free.
386
- *
387
- * @returns {Promise<{success, toolkits?: Array, error?: string}>}
388
- */
389
- let _toolkitsCache = null;
390
- let _toolkitsCacheAt = 0;
391
- const TOOLKITS_CACHE_TTL_MS = 60_000;
392
-
393
- export async function listToolkits(options = {}) {
394
- const { key: apiKey } = resolveApiKey();
395
- if (!apiKey) {
396
- return { success: false, error: 'No Composio API key configured. Add one on the Integrations page or set COMPOSIO_API_KEY in the environment.' };
397
- }
398
- // Cache the full-catalog scan briefly — listToolkits is a hot path
399
- // for both backfill and the agent's catalog discovery.
400
- const now = Date.now();
401
- if (!options.force && _toolkitsCache && (now - _toolkitsCacheAt) < TOOLKITS_CACHE_TTL_MS) {
402
- return { success: true, toolkits: _toolkitsCache, cached: true };
403
- }
404
- try {
405
- const items = await _restListPaginated(apiKey, '/api/v3.1/toolkits');
406
- const toolkits = items.map(_camelizeToolkit);
407
- _toolkitsCache = toolkits;
408
- _toolkitsCacheAt = now;
409
- return { success: true, toolkits };
410
- } catch (err) {
411
- return { success: false, error: err.message || String(err) };
412
- }
413
- }
414
-
415
- /** Drop the in-memory toolkits cache. Called after key rotation so the
416
- * next call sees the correct key's catalog. */
417
- function _invalidateToolkitsCache() {
418
- _toolkitsCache = null;
419
- _toolkitsCacheAt = 0;
420
- }
421
-
422
- /**
423
- * @param {string} userId
424
- * @returns {Promise<{success, connections?: Array, error?: string}>}
425
- */
426
- export async function listConnections(userId) {
427
- const { client, error } = await getClient();
428
- if (error) return { success: false, error };
429
- try {
430
- const raw = await client.connectedAccounts?.list?.({ userId });
431
- const connections = _normalizeListResult(raw).map(_normalizeConnection);
432
- return { success: true, connections };
433
- } catch (err) {
434
- return { success: false, error: err.message || String(err) };
435
- }
436
- }
437
-
438
- /**
439
- * Normalize a connected-account record so callers see a consistent
440
- * shape regardless of SDK version. Two specific problems we hide:
441
- *
442
- * 1. The SDK returns `toolkit` as an OBJECT `{slug, name, ...}` —
443
- * callers (especially the UI) that wrote `c.toolkitSlug ||
444
- * c.toolkit` ended up with the whole object as the slug and
445
- * crashed on `.toLowerCase()`.
446
- * 2. Some SDK versions name the field `toolkit_slug` (snake_case
447
- * passthrough); others use `toolkitSlug`. We expose both.
448
- *
449
- * Top-level `toolkitSlug` is now guaranteed to be a string (empty if
450
- * we genuinely couldn't extract one).
451
- */
452
- function _normalizeConnection(c) {
453
- if (!c || typeof c !== 'object') return c;
454
- const toolkitSlug =
455
- (typeof c.toolkitSlug === 'string' && c.toolkitSlug) ||
456
- (typeof c.toolkit_slug === 'string' && c.toolkit_slug) ||
457
- (c.toolkit && typeof c.toolkit.slug === 'string' && c.toolkit.slug) ||
458
- (typeof c.toolkit === 'string' && c.toolkit) ||
459
- '';
460
- return { ...c, toolkitSlug };
461
- }
462
-
463
- /**
464
- * @param {string} userId
465
- * @param {string} toolkitSlug
466
- * @returns {Promise<{success, connected: boolean, status: string, connectionId?: string, error?: string}>}
467
- */
468
- export async function connectionStatus(userId, toolkitSlug) {
469
- const { client, error } = await getClient();
470
- if (error) return { success: false, connected: false, status: 'UNAVAILABLE', error };
471
- try {
472
- // The SDK's `connectedAccounts.get(id)` expects a connectionId STRING
473
- // and treats its argument as a URL path segment. Passing the
474
- // {userId, toolkit} object we previously used here ended up URL-
475
- // encoded as "[object Object]", producing the "Path parameters
476
- // result in path with invalid segments" error users hit in
477
- // production. To look up by (userId, toolkit) we LIST connections
478
- // and filter — that's the supported access path.
479
- const raw = await client.connectedAccounts?.list?.({ userId, toolkit: toolkitSlug });
480
- const connections = _normalizeListResult(raw);
481
- // The list response may not pre-filter by toolkit in older SDKs;
482
- // belt-and-braces filter on the client.
483
- const conn = connections.find(c =>
484
- c?.toolkitSlug === toolkitSlug ||
485
- c?.toolkit === toolkitSlug ||
486
- c?.toolkit?.slug === toolkitSlug
487
- ) || null;
488
- const connected = !!(conn && (conn.status === 'ACTIVE' || conn.connected === true));
489
- return {
490
- success: true,
491
- connected,
492
- status: conn?.status || 'NOT_CONNECTED',
493
- connectionId: conn?.id || null,
494
- };
495
- } catch (err) {
496
- return { success: false, connected: false, status: 'ERROR', error: err.message || String(err) };
497
- }
498
- }
499
-
500
- /**
501
- * Start an OAuth connection for `userId` to `toolkitSlug`. Returns a
502
- * Connect-Link URL the user opens in the browser.
503
- *
504
- * What changed (2026-05-28): Composio deprecated the legacy
505
- * `POST /api/v3/connected_accounts` endpoint for managed-OAuth
506
- * auth-configs. Any caller that hit it (including the SDK's
507
- * `toolkits.authorize` → `connectedAccounts.initiate` path) now gets:
508
- *
509
- * 400 ConnectedAccount_BadRequest — "Creating connections on this
510
- * endpoint for Composio-managed OAuth auth configs is no longer
511
- * supported. Use POST /api/v3/connected_accounts/link instead."
512
- *
513
- * The new flow is two-step:
514
- * 1. Find or create an auth-config for the toolkit (via the SDK's
515
- * `authConfigs.list` / `authConfigs.create` — those endpoints
516
- * weren't touched by the deprecation).
517
- * 2. Call `connectedAccounts.link(userId, authConfigId)` — the SDK
518
- * method that maps to the new `/connected_accounts/link` endpoint.
519
- *
520
- * We can't keep using `toolkits.authorize` because its last step is
521
- * the deprecated `initiate` call. Same flow, replicated here with the
522
- * link call swapped in.
523
- *
524
- * @param {string} userId
525
- * @param {string} toolkitSlug
526
- * @param {object} [options] { authConfigId?: string, callbackUrl?: string }
527
- * @returns {Promise<{success, connectLink?: string, connectionId?: string, authConfigId?: string, raw?: any, error?: string}>}
528
- */
529
- export async function connect(userId, toolkitSlug, options = {}) {
530
- const { client, error } = await getClient();
531
- if (error) return { success: false, error };
532
- if (typeof client.connectedAccounts?.link !== 'function') {
533
- return {
534
- success: false,
535
- error: '@composio/core SDK is missing connectedAccounts.link() — upgrade to >= 0.6.11 (the version that supports the new /connected_accounts/link endpoint).',
536
- };
537
- }
538
-
539
- let authConfigId = options.authConfigId;
540
- let usedExistingConfig = false;
541
- let createdConfig = false;
542
-
543
- try {
544
- // ─── Step 1: Find or create an auth-config ──────────────────────
545
- if (!authConfigId && typeof client.authConfigs?.list === 'function') {
546
- try {
547
- const existing = await client.authConfigs.list({ toolkit: toolkitSlug });
548
- const items = existing?.items || _normalizeListResult(existing);
549
- if (items.length > 0) {
550
- authConfigId = items[0].id;
551
- usedExistingConfig = true;
552
- }
553
- } catch (listErr) {
554
- // Non-fatal — fall through to create.
555
- }
556
- }
557
-
558
- if (!authConfigId && typeof client.authConfigs?.create === 'function') {
559
- try {
560
- const created = await client.authConfigs.create(toolkitSlug, {
561
- type: 'use_composio_managed_auth',
562
- name: `${toolkitSlug} Auth Config`,
563
- });
564
- authConfigId = created?.id || created?.authConfigId;
565
- createdConfig = true;
566
- } catch (createErr) {
567
- const msg = createErr?.message || String(createErr);
568
- const status = createErr?.status;
569
- // The 400 here typically means "Composio doesn't have a managed
570
- // OAuth client for this toolkit — go set one up in the dashboard".
571
- if (status === 400 || /no.*default.*auth/i.test(msg) || /must.*configure/i.test(msg)) {
572
- return {
573
- success: false,
574
- error: `Composio has no managed-OAuth client for "${toolkitSlug}". Open https://app.composio.dev → Integrations → Add Integration → "${toolkitSlug}" and complete the OAuth client setup once. Then retry.`,
575
- code: 'COMPOSIO_AUTH_CONFIG_MISSING',
576
- rawError: msg,
577
- };
578
- }
579
- throw createErr;
580
- }
581
- }
582
-
583
- if (!authConfigId) {
584
- return {
585
- success: false,
586
- error: `Could not find or create an auth-config for "${toolkitSlug}". Set one up at https://app.composio.dev → Integrations.`,
587
- code: 'COMPOSIO_AUTH_CONFIG_MISSING',
588
- };
589
- }
590
-
591
- // ─── Step 2: Create the Connect-Link via the new endpoint ──────
592
- const linkOptions = {};
593
- if (options.callbackUrl) linkOptions.callbackUrl = options.callbackUrl;
594
- const result = await client.connectedAccounts.link(userId, authConfigId, linkOptions);
595
-
596
- return {
597
- success: true,
598
- connectLink: result?.redirectUrl || result?.redirect_url || null,
599
- connectionId: result?.id || result?.connectionId || result?.connected_account_id || null,
600
- authConfigId,
601
- usedExistingConfig,
602
- createdConfig,
603
- raw: result,
604
- };
605
- } catch (err) {
606
- const rawMsg = err.message || String(err);
607
- if (/toolkit.*not found/i.test(rawMsg) || /ComposioToolkitNotFoundError/.test(rawMsg)) {
608
- return {
609
- success: false,
610
- error: `Toolkit "${toolkitSlug}" doesn't exist in Composio's catalog under that slug. Run \`composio.list-toolkits\` to see the canonical slug.`,
611
- code: 'COMPOSIO_TOOLKIT_NOT_FOUND',
612
- rawError: rawMsg,
613
- };
614
- }
615
- if (/no auth config|authConfig.*not found/i.test(rawMsg)) {
616
- return {
617
- success: false,
618
- error: `No auth configuration exists in your Composio org for "${toolkitSlug}". Open https://app.composio.dev → Integrations and add the "${toolkitSlug}" integration.`,
619
- code: 'COMPOSIO_AUTH_CONFIG_MISSING',
620
- rawError: rawMsg,
621
- authConfigId,
622
- };
623
- }
624
- return { success: false, error: rawMsg, authConfigId };
625
- }
626
- }
627
-
628
- /**
629
- * Disconnect a connection. Accepts either a connectionId (preferred) or
630
- * a {userId, toolkitSlug} pair (looks up the connection first).
631
- *
632
- * @param {{connectionId?: string, userId?: string, toolkitSlug?: string}} target
633
- * @returns {Promise<{success, error?: string}>}
634
- */
635
- export async function disconnect(target) {
636
- const { client, error } = await getClient();
637
- if (error) return { success: false, error };
638
- if (!target || (!target.connectionId && !(target.userId && target.toolkitSlug))) {
639
- return { success: false, error: 'disconnect requires connectionId OR {userId, toolkitSlug}' };
640
- }
641
- let connectionId = target.connectionId;
642
- try {
643
- if (!connectionId) {
644
- // Same `.get(object)` → "Path parameters invalid" trap as
645
- // connectionStatus. Use `.list({userId, toolkit})` instead.
646
- const raw = await client.connectedAccounts?.list?.({ userId: target.userId, toolkit: target.toolkitSlug });
647
- const conn = _normalizeListResult(raw).find(c =>
648
- c?.toolkitSlug === target.toolkitSlug ||
649
- c?.toolkit === target.toolkitSlug ||
650
- c?.toolkit?.slug === target.toolkitSlug
651
- ) || null;
652
- connectionId = conn?.id || null;
653
- if (!connectionId) {
654
- return { success: false, error: `No active connection found for toolkit "${target.toolkitSlug}"` };
655
- }
656
- }
657
- // SDK exposes connectedAccounts.delete(id) or .disconnect(id).
658
- if (typeof client.connectedAccounts?.delete === 'function') {
659
- await client.connectedAccounts.delete(connectionId);
660
- } else if (typeof client.connectedAccounts?.disconnect === 'function') {
661
- await client.connectedAccounts.disconnect(connectionId);
662
- } else {
663
- return { success: false, error: 'SDK does not expose a disconnect method' };
664
- }
665
- return { success: true, connectionId };
666
- } catch (err) {
667
- return { success: false, error: err.message || String(err) };
668
- }
669
- }
670
-
671
- /**
672
- * List the available actions for a single toolkit.
673
- *
674
- * Same story as listToolkits — `client.tools.list()` isn't a public SDK
675
- * method (it's `tools.get(userId, filter)`, which then wraps results in
676
- * the configured LLM-provider's format). We want the raw catalog shape
677
- * with slug + description + input_parameters, so we call REST directly.
678
- *
679
- * @param {string} toolkitSlug
680
- * @param {string} [userId] forwarded to REST as `user_id` so the
681
- * endpoint can scope tools the user is
682
- * authorised for. Optional.
683
- * @returns {Promise<{success, tools?: Array, error?: string}>}
684
- */
685
- export async function listTools(toolkitSlug, userId) {
686
- const { key: apiKey } = resolveApiKey();
687
- if (!apiKey) return { success: false, error: 'No Composio API key configured.' };
688
- if (!toolkitSlug) return { success: false, error: 'listTools: toolkitSlug is required.' };
689
- try {
690
- const items = await _restListPaginated(apiKey, '/api/v3.1/tools', {
691
- toolkit_slugs: toolkitSlug,
692
- ...(userId ? { user_id: userId } : {}),
693
- });
694
- const tools = items.map(_camelizeTool);
695
- return { success: true, tools };
696
- } catch (err) {
697
- return { success: false, error: err.message || String(err) };
698
- }
699
- }
700
-
701
- /**
702
- * @param {string} userId
703
- * @param {string} toolkitSlug
704
- * @param {string} actionSlug
705
- * @param {object} args
706
- */
707
- export async function execute(userId, toolkitSlug, actionSlug, args = {}) {
708
- const { client, error } = await getClient();
709
- if (error) return { success: false, error };
710
- try {
711
- const result = await client.tools?.execute?.(actionSlug, { userId, arguments: args });
712
- return {
713
- success: result?.successful !== false,
714
- output: result?.data ?? result?.output ?? result,
715
- error: result?.error || null,
716
- };
717
- } catch (err) {
718
- return { success: false, error: err.message || String(err) };
719
- }
720
- }
721
-
722
- /* ------------------------------------------------------------------------ */
723
- /* Default export — convenient grouping for callers */
724
- /* ------------------------------------------------------------------------ */
725
-
726
- export default {
727
- getClient,
728
- isAvailable,
729
- setApiKey,
730
- forgetApiKey,
731
- listToolkits,
732
- listConnections,
733
- connectionStatus,
734
- connect,
735
- disconnect,
736
- listTools,
737
- execute,
738
- };
1
+ /**
2
+ * composioService — shared client + operations for the Composio
3
+ * integration hub. Used by:
4
+ *
5
+ * - composioTool (agent-facing) — invokes execute/connect/etc.
6
+ * - REST endpoints (UI-facing) — power the Connections page
7
+ * - CLI subcommands (operator) — `loxia composio <verb>`
8
+ *
9
+ * Single source of truth for:
10
+ * - SDK loading (dynamic + optional)
11
+ * - API-key resolution (COMPOSIO_API_KEY env var)
12
+ * - userId convention (agentId for agent calls; "operator" for UI/CLI)
13
+ *
14
+ * Every method returns a structured `{ success, ...data }` object so
15
+ * callers can render rich errors instead of relying on thrown exceptions.
16
+ * The SDK and API key are checked once and cached; transient SDK errors
17
+ * are surfaced per-call.
18
+ *
19
+ * userId conventions:
20
+ * - composioTool maps agentId → userId so each agent has isolated
21
+ * OAuth tokens.
22
+ * - UI/CLI use the literal string "operator" so the human user's
23
+ * connections are shared across agents the operator manages.
24
+ * (Override via `?userId=…` on the REST routes, or `--user=…`
25
+ * on the CLI, if multi-tenant ever lands.)
26
+ */
27
+
28
+ import { getApiKeyManager } from './apiKeyManager.js';
29
+
30
+ let _client = null;
31
+ let _unavailableReason = null;
32
+ let _activeKeyFingerprint = null; // First 8 chars of the key the cached client was built with.
33
+
34
+ /**
35
+ * Reset cached client + unavailability — for tests, and for live
36
+ * key-rotation (the API-key endpoint calls this so the next op picks
37
+ * up the new key without a process restart).
38
+ */
39
+ export function _resetForTests() {
40
+ _client = null;
41
+ _unavailableReason = null;
42
+ _activeKeyFingerprint = null;
43
+ _toolkitsCache = null;
44
+ _toolkitsCacheAt = 0;
45
+ }
46
+
47
+ /**
48
+ * Cache-invalidation hook for live key rotation. Production callers
49
+ * (the POST /api/composio/apikey route) call this after persisting a
50
+ * new key so the next service op rebuilds the SDK client with it.
51
+ *
52
+ * Different name than `_resetForTests` so it's obvious in production
53
+ * code that this is intentional, not a test shortcut.
54
+ */
55
+ export function invalidateClientCache() {
56
+ _client = null;
57
+ _unavailableReason = null;
58
+ _activeKeyFingerprint = null;
59
+ // Toolkits are per-org. After a key rotation the next listToolkits()
60
+ // call must hit REST again, not the previous key's cached catalog.
61
+ _invalidateToolkitsCache();
62
+ }
63
+
64
+ /**
65
+ * Resolve the Composio API key from the highest-priority source:
66
+ * 1. ApiKeyManager (user-stored via the UI — encrypted on disk)
67
+ * 2. process.env.COMPOSIO_API_KEY (legacy / operator override)
68
+ *
69
+ * Returns { key, source: 'vault' | 'env' | null }.
70
+ */
71
+ function resolveApiKey() {
72
+ const mgr = getApiKeyManager();
73
+ const vaultKey = mgr?.keys?.vendorKeys?.composio;
74
+ if (vaultKey && typeof vaultKey === 'string' && vaultKey.trim()) {
75
+ return { key: vaultKey.trim(), source: 'vault' };
76
+ }
77
+ const envKey = process.env.COMPOSIO_API_KEY;
78
+ if (envKey && typeof envKey === 'string' && envKey.trim()) {
79
+ return { key: envKey.trim(), source: 'env' };
80
+ }
81
+ return { key: null, source: null };
82
+ }
83
+
84
+ /**
85
+ * Load the SDK on first use. Returns { client } on success or { error }
86
+ * (string) when either the SDK is missing or the API key is unset.
87
+ *
88
+ * Exported so composioTool (agent-facing) can share the same key
89
+ * resolution + client cache the REST + CLI surfaces use. Previously
90
+ * the tool kept its own client and read process.env.COMPOSIO_API_KEY
91
+ * directly, which meant keys saved via the UI (encrypted into
92
+ * ApiKeyManager.vendorKeys.composio) were invisible to the agent
93
+ * runtime — every agent invocation failed with "COMPOSIO_API_KEY env
94
+ * var is not set" even though the UI had reported the key as ready.
95
+ */
96
+ export async function getClient() {
97
+ const { key: apiKey } = resolveApiKey();
98
+ const fingerprint = apiKey ? apiKey.slice(0, 8) : null;
99
+
100
+ // If the active key has changed since we last built the client (e.g.
101
+ // user just saved a new one via the UI), force a rebuild.
102
+ if (_client && fingerprint !== _activeKeyFingerprint) {
103
+ _client = null;
104
+ _unavailableReason = null;
105
+ }
106
+
107
+ if (_client) return { client: _client };
108
+ if (_unavailableReason) return { error: _unavailableReason };
109
+
110
+ if (!apiKey) {
111
+ const reason = 'No Composio API key configured. Add one on the Integrations page or set COMPOSIO_API_KEY in the environment.';
112
+ _unavailableReason = reason;
113
+ return { error: reason };
114
+ }
115
+
116
+ let Composio;
117
+ try {
118
+ const mod = await import('@composio/core');
119
+ Composio = mod.Composio || mod.default?.Composio || mod.default;
120
+ } catch (e) {
121
+ // Do NOT cache this one. Background: `npm audit fix --force` has
122
+ // historically nuked @composio/core (its tree carries the loudest
123
+ // CVEs in the project — 16 vulns, 2 critical), then a follow-up
124
+ // `npm install @composio/core@^0.6.11` restores it. If we cache the
125
+ // "SDK is not installed" reason permanently, every subsequent call
126
+ // returns the stale failure until the CLI is restarted — exactly the
127
+ // "I installed it but the UI still complains" loop we hit. Returning
128
+ // the error without caching lets the next call retry the import.
129
+ const reason = `@composio/core SDK is not installed. Run \`npm install @composio/core\`. Detail: ${e.message}`;
130
+ return { error: reason };
131
+ }
132
+
133
+ try {
134
+ _client = new Composio({ apiKey });
135
+ _activeKeyFingerprint = fingerprint;
136
+ return { client: _client };
137
+ } catch (e) {
138
+ // Same reasoning as above — SDK init failures can come from transient
139
+ // upstream issues (bad key, network) that don't need a CLI restart to
140
+ // clear. Let the next call retry.
141
+ const reason = `Composio SDK initialization failed: ${e.message}`;
142
+ return { error: reason };
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Some Composio SDK methods return plain Arrays (after their own
148
+ * internal transform), others return paginated wrappers like
149
+ * `{items: [], nextCursor}`. Normalises any of: Array | {items} |
150
+ * {data} | {results} | falsy → a plain Array. Exported so callers
151
+ * outside this module can use the same logic on raw SDK responses.
152
+ *
153
+ * Note: this used to be the entire fix for "list-toolkits returns
154
+ * empty". It wasn't enough. The real bug was that we called
155
+ * `client.toolkits.list()` (and `client.tools.list()`) — methods that
156
+ * DON'T EXIST on @composio/core v0.6.x. The SDK's public surface is
157
+ * `toolkits.get(query)` and `tools.get(userId, filter)`. Calling a
158
+ * non-existent method silently returned `undefined`, which this
159
+ * normaliser then turned into `[]`, masking the bug as "zero results".
160
+ *
161
+ * Now that listToolkits/listTools go through direct REST calls (the
162
+ * SDK's `toolkits.get` drops the pagination cursor in its transform,
163
+ * so we can't get past page 1 through it), this helper is mostly used
164
+ * by callers that touch the SDK directly. It stays exported for them.
165
+ */
166
+ export function _normalizeListResult(result) {
167
+ if (Array.isArray(result)) return result;
168
+ if (!result || typeof result !== 'object') return [];
169
+ if (Array.isArray(result.items)) return result.items;
170
+ if (Array.isArray(result.data)) return result.data;
171
+ if (Array.isArray(result.results)) return result.results;
172
+ return [];
173
+ }
174
+
175
+ /* ------------------------------------------------------------------------ */
176
+ /* REST helpers — paginated discovery against the Composio v3.1 API. */
177
+ /* */
178
+ /* Two of the SDK's "list" methods are unusable for our purposes: */
179
+ /* */
180
+ /* - `client.toolkits.list()` doesn't exist (no public list method). */
181
+ /* The SDK exposes `toolkits.get(query)` which internally calls the */
182
+ /* inner OpenAPI client's toolkits.list, then transforms the result */
183
+ /* down to a flat Array — discarding `nextCursor`. So even when we */
184
+ /* do call the right method, we only get page 1 (max 50 items) and */
185
+ /* have no way to ask for page 2. */
186
+ /* */
187
+ /* - `client.tools.list()` is similarly internal-only. */
188
+ /* */
189
+ /* Solution: bypass the SDK for full-catalog discovery and call REST */
190
+ /* directly with cursor-based pagination. The SDK is still used for */
191
+ /* everything else (connect/initiate, execute, etc.). */
192
+ /* ------------------------------------------------------------------------ */
193
+
194
+ function _getApiBaseUrl() {
195
+ return process.env.COMPOSIO_BASE_URL || 'https://backend.composio.dev';
196
+ }
197
+
198
+ async function _restGet(apiKey, path, query = {}) {
199
+ const url = new URL(path, _getApiBaseUrl());
200
+ for (const [k, v] of Object.entries(query)) {
201
+ if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
202
+ }
203
+ const res = await fetch(url, {
204
+ method: 'GET',
205
+ headers: { 'x-api-key': apiKey, accept: 'application/json' },
206
+ });
207
+ const text = await res.text();
208
+ let body; try { body = text ? JSON.parse(text) : null; } catch { body = { _rawText: text }; }
209
+ if (!res.ok) {
210
+ const detail = body?.error || body?.message || (text && text.slice(0, 200)) || `HTTP ${res.status}`;
211
+ const e = new Error(`Composio REST ${path} → HTTP ${res.status}: ${detail}`);
212
+ e.status = res.status;
213
+ e.body = body;
214
+ throw e;
215
+ }
216
+ return body;
217
+ }
218
+
219
+ async function _restListPaginated(apiKey, path, baseQuery = {}, options = {}) {
220
+ const limit = options.limit || 50;
221
+ const maxPages = options.maxPages || 100;
222
+ const items = [];
223
+ let cursor;
224
+ let pages = 0;
225
+ do {
226
+ const body = await _restGet(apiKey, path, { ...baseQuery, limit, ...(cursor ? { cursor } : {}) });
227
+ const pageItems = body?.items || body?.data || (Array.isArray(body) ? body : []);
228
+ items.push(...pageItems);
229
+ cursor = body?.next_cursor || body?.nextCursor || null;
230
+ pages++;
231
+ if (pages >= maxPages) break;
232
+ } while (cursor);
233
+ return items;
234
+ }
235
+
236
+ /** Map a REST v3.1 toolkit (snake_case) to the camelCase shape callers
237
+ * inside this codebase already expect. */
238
+ function _camelizeToolkit(t) {
239
+ return {
240
+ slug: t.slug,
241
+ name: t.name,
242
+ description: t.meta?.description,
243
+ meta: t.meta ? {
244
+ description: t.meta.description,
245
+ logo: t.meta.logo,
246
+ categories: t.meta.categories,
247
+ createdAt: t.meta.created_at,
248
+ updatedAt: t.meta.updated_at,
249
+ toolsCount: t.meta.tools_count,
250
+ triggersCount: t.meta.triggers_count,
251
+ appUrl: t.meta.app_url,
252
+ } : undefined,
253
+ isLocalToolkit: t.is_local_toolkit,
254
+ authSchemes: t.auth_schemes,
255
+ composioManagedAuthSchemes: t.composio_managed_auth_schemes,
256
+ noAuth: t.no_auth,
257
+ };
258
+ }
259
+
260
+ /** Map a REST v3.1 tool (snake_case) to the camelCase shape callers
261
+ * inside this codebase expect — including a few aliases (input,
262
+ * parameters, inputParameters) because the various code paths each
263
+ * evolved their own field name preference. */
264
+ function _camelizeTool(t) {
265
+ const params = t.input_parameters || t.parameters || t.input;
266
+ return {
267
+ slug: t.slug,
268
+ name: t.name,
269
+ displayName: t.display_name || t.name,
270
+ description: t.description,
271
+ inputParameters: params,
272
+ input: params,
273
+ parameters: params,
274
+ outputParameters: t.output_parameters,
275
+ toolkit: t.toolkit,
276
+ toolkitSlug: t.toolkit?.slug || t.toolkit_slug,
277
+ deprecated: t.deprecated,
278
+ tags: t.tags,
279
+ scopes: t.scopes,
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Optionally inject a stub client. ONLY for tests.
285
+ *
286
+ * Also pins the fingerprint to whatever the current resolved key
287
+ * happens to be, so the live-rotation check inside `getClient()`
288
+ * doesn't immediately discard the stub.
289
+ *
290
+ * @param {object} stub
291
+ */
292
+ export function _injectClientForTests(stub) {
293
+ _client = stub;
294
+ _unavailableReason = null;
295
+ const { key } = resolveApiKey();
296
+ _activeKeyFingerprint = key ? key.slice(0, 8) : '__test__';
297
+ }
298
+
299
+ /* ------------------------------------------------------------------------ */
300
+ /* Operations */
301
+ /* ------------------------------------------------------------------------ */
302
+
303
+ /**
304
+ * Lightweight readiness check. Avoids running a real SDK call.
305
+ *
306
+ * Returns `keySource` so the UI can tell whether the active key came
307
+ * from the encrypted vault (user set it via the form) or from the env
308
+ * var (operator-managed). That changes the affordances on the
309
+ * Integrations page (e.g. don't show "Forget" for env-var keys).
310
+ *
311
+ * @returns {Promise<{available: boolean, reason?: string, keySource?: 'vault'|'env'|null}>}
312
+ */
313
+ export async function isAvailable() {
314
+ const { source } = resolveApiKey();
315
+ const { client, error } = await getClient();
316
+ if (error) return { available: false, reason: error, keySource: source };
317
+ return { available: !!client, keySource: source };
318
+ }
319
+
320
+ /**
321
+ * Persist a Composio API key via ApiKeyManager (encrypted at rest)
322
+ * and invalidate the SDK client cache so the next op uses it.
323
+ *
324
+ * Validation: an empty string clears the key. Otherwise we trim
325
+ * whitespace and require >= 8 chars (placeholder guard — the real
326
+ * key shape is `comp_…` and is much longer).
327
+ *
328
+ * Does NOT verify the key against Composio's API here — the UI calls
329
+ * isAvailable() right after to do a real probe and surface the result.
330
+ *
331
+ * @param {string|null} apiKey The key to persist, or empty/null to clear.
332
+ * @returns {Promise<{success: true, cleared?: boolean} | {success: false, error: string}>}
333
+ */
334
+ export async function setApiKey(apiKey) {
335
+ const mgr = getApiKeyManager();
336
+ if (!mgr) {
337
+ return { success: false, error: 'ApiKeyManager is not initialized; key cannot be persisted.' };
338
+ }
339
+ const trimmed = (apiKey || '').trim();
340
+
341
+ // Empty → clear the stored key.
342
+ if (!trimmed) {
343
+ const existing = { ...(mgr.keys?.vendorKeys || {}) };
344
+ delete existing.composio;
345
+ mgr.keys.vendorKeys = existing;
346
+ await mgr.persist();
347
+ invalidateClientCache();
348
+ return { success: true, cleared: true };
349
+ }
350
+
351
+ // Cheap shape guard. The full validation is "does it actually authenticate"
352
+ // which the caller probes with isAvailable() after.
353
+ if (trimmed.length < 8) {
354
+ return { success: false, error: 'API key looks too short. Paste the full key from dashboard.composio.dev.' };
355
+ }
356
+
357
+ await mgr.setSessionKeys(null, { vendorKeys: { composio: trimmed } });
358
+ invalidateClientCache();
359
+ return { success: true };
360
+ }
361
+
362
+ /**
363
+ * Convenience: clear the stored key. Equivalent to setApiKey('').
364
+ * Exposed separately so the DELETE route reads naturally.
365
+ */
366
+ export async function forgetApiKey() {
367
+ return setApiKey('');
368
+ }
369
+
370
+ /**
371
+ * List ALL toolkits in Composio's catalog, paginated to exhaustion.
372
+ *
373
+ * Cannot use the SDK here:
374
+ * - `client.toolkits.list()` doesn't exist on @composio/core ≤0.6.x
375
+ * (it's `toolkits.get(query)`); we shipped the wrong method name
376
+ * for a long time and got `success: true, toolkits: []` because
377
+ * `client.toolkits?.list?.()` returned `undefined` and
378
+ * `_normalizeListResult` dutifully turned that into `[]`.
379
+ * - Even after switching to `toolkits.get`, the SDK's internal
380
+ * `transformToolkitListResponse` discards `nextCursor`, so we'd
381
+ * only see page 1 (max 50 toolkits — out of ~1043 in the catalog).
382
+ *
383
+ * So we go straight to REST. Pagination via cursor, ~21 round-trips
384
+ * for the full catalog (~10s total), then we cache the result for a
385
+ * minute so subsequent calls in the same agent turn are free.
386
+ *
387
+ * @returns {Promise<{success, toolkits?: Array, error?: string}>}
388
+ */
389
+ let _toolkitsCache = null;
390
+ let _toolkitsCacheAt = 0;
391
+ const TOOLKITS_CACHE_TTL_MS = 60_000;
392
+
393
+ export async function listToolkits(options = {}) {
394
+ const { key: apiKey } = resolveApiKey();
395
+ if (!apiKey) {
396
+ return { success: false, error: 'No Composio API key configured. Add one on the Integrations page or set COMPOSIO_API_KEY in the environment.' };
397
+ }
398
+ // Cache the full-catalog scan briefly — listToolkits is a hot path
399
+ // for both backfill and the agent's catalog discovery.
400
+ const now = Date.now();
401
+ if (!options.force && _toolkitsCache && (now - _toolkitsCacheAt) < TOOLKITS_CACHE_TTL_MS) {
402
+ return { success: true, toolkits: _toolkitsCache, cached: true };
403
+ }
404
+ try {
405
+ const items = await _restListPaginated(apiKey, '/api/v3.1/toolkits');
406
+ const toolkits = items.map(_camelizeToolkit);
407
+ _toolkitsCache = toolkits;
408
+ _toolkitsCacheAt = now;
409
+ return { success: true, toolkits };
410
+ } catch (err) {
411
+ return { success: false, error: err.message || String(err) };
412
+ }
413
+ }
414
+
415
+ /** Drop the in-memory toolkits cache. Called after key rotation so the
416
+ * next call sees the correct key's catalog. */
417
+ function _invalidateToolkitsCache() {
418
+ _toolkitsCache = null;
419
+ _toolkitsCacheAt = 0;
420
+ }
421
+
422
+ /**
423
+ * @param {string} userId
424
+ * @returns {Promise<{success, connections?: Array, error?: string}>}
425
+ */
426
+ export async function listConnections(userId) {
427
+ const { client, error } = await getClient();
428
+ if (error) return { success: false, error };
429
+ try {
430
+ const raw = await client.connectedAccounts?.list?.({ userId });
431
+ const connections = _normalizeListResult(raw).map(_normalizeConnection);
432
+ return { success: true, connections };
433
+ } catch (err) {
434
+ return { success: false, error: err.message || String(err) };
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Normalize a connected-account record so callers see a consistent
440
+ * shape regardless of SDK version. Two specific problems we hide:
441
+ *
442
+ * 1. The SDK returns `toolkit` as an OBJECT `{slug, name, ...}` —
443
+ * callers (especially the UI) that wrote `c.toolkitSlug ||
444
+ * c.toolkit` ended up with the whole object as the slug and
445
+ * crashed on `.toLowerCase()`.
446
+ * 2. Some SDK versions name the field `toolkit_slug` (snake_case
447
+ * passthrough); others use `toolkitSlug`. We expose both.
448
+ *
449
+ * Top-level `toolkitSlug` is now guaranteed to be a string (empty if
450
+ * we genuinely couldn't extract one).
451
+ */
452
+ function _normalizeConnection(c) {
453
+ if (!c || typeof c !== 'object') return c;
454
+ const toolkitSlug =
455
+ (typeof c.toolkitSlug === 'string' && c.toolkitSlug) ||
456
+ (typeof c.toolkit_slug === 'string' && c.toolkit_slug) ||
457
+ (c.toolkit && typeof c.toolkit.slug === 'string' && c.toolkit.slug) ||
458
+ (typeof c.toolkit === 'string' && c.toolkit) ||
459
+ '';
460
+ return { ...c, toolkitSlug };
461
+ }
462
+
463
+ /**
464
+ * @param {string} userId
465
+ * @param {string} toolkitSlug
466
+ * @returns {Promise<{success, connected: boolean, status: string, connectionId?: string, error?: string}>}
467
+ */
468
+ export async function connectionStatus(userId, toolkitSlug) {
469
+ const { client, error } = await getClient();
470
+ if (error) return { success: false, connected: false, status: 'UNAVAILABLE', error };
471
+ try {
472
+ // The SDK's `connectedAccounts.get(id)` expects a connectionId STRING
473
+ // and treats its argument as a URL path segment. Passing the
474
+ // {userId, toolkit} object we previously used here ended up URL-
475
+ // encoded as "[object Object]", producing the "Path parameters
476
+ // result in path with invalid segments" error users hit in
477
+ // production. To look up by (userId, toolkit) we LIST connections
478
+ // and filter — that's the supported access path.
479
+ const raw = await client.connectedAccounts?.list?.({ userId, toolkit: toolkitSlug });
480
+ const connections = _normalizeListResult(raw);
481
+ // The list response may not pre-filter by toolkit in older SDKs;
482
+ // belt-and-braces filter on the client.
483
+ const conn = connections.find(c =>
484
+ c?.toolkitSlug === toolkitSlug ||
485
+ c?.toolkit === toolkitSlug ||
486
+ c?.toolkit?.slug === toolkitSlug
487
+ ) || null;
488
+ const connected = !!(conn && (conn.status === 'ACTIVE' || conn.connected === true));
489
+ return {
490
+ success: true,
491
+ connected,
492
+ status: conn?.status || 'NOT_CONNECTED',
493
+ connectionId: conn?.id || null,
494
+ };
495
+ } catch (err) {
496
+ return { success: false, connected: false, status: 'ERROR', error: err.message || String(err) };
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Start an OAuth connection for `userId` to `toolkitSlug`. Returns a
502
+ * Connect-Link URL the user opens in the browser.
503
+ *
504
+ * What changed (2026-05-28): Composio deprecated the legacy
505
+ * `POST /api/v3/connected_accounts` endpoint for managed-OAuth
506
+ * auth-configs. Any caller that hit it (including the SDK's
507
+ * `toolkits.authorize` → `connectedAccounts.initiate` path) now gets:
508
+ *
509
+ * 400 ConnectedAccount_BadRequest — "Creating connections on this
510
+ * endpoint for Composio-managed OAuth auth configs is no longer
511
+ * supported. Use POST /api/v3/connected_accounts/link instead."
512
+ *
513
+ * The new flow is two-step:
514
+ * 1. Find or create an auth-config for the toolkit (via the SDK's
515
+ * `authConfigs.list` / `authConfigs.create` — those endpoints
516
+ * weren't touched by the deprecation).
517
+ * 2. Call `connectedAccounts.link(userId, authConfigId)` — the SDK
518
+ * method that maps to the new `/connected_accounts/link` endpoint.
519
+ *
520
+ * We can't keep using `toolkits.authorize` because its last step is
521
+ * the deprecated `initiate` call. Same flow, replicated here with the
522
+ * link call swapped in.
523
+ *
524
+ * @param {string} userId
525
+ * @param {string} toolkitSlug
526
+ * @param {object} [options] { authConfigId?: string, callbackUrl?: string }
527
+ * @returns {Promise<{success, connectLink?: string, connectionId?: string, authConfigId?: string, raw?: any, error?: string}>}
528
+ */
529
+ export async function connect(userId, toolkitSlug, options = {}) {
530
+ const { client, error } = await getClient();
531
+ if (error) return { success: false, error };
532
+ if (typeof client.connectedAccounts?.link !== 'function') {
533
+ return {
534
+ success: false,
535
+ error: '@composio/core SDK is missing connectedAccounts.link() — upgrade to >= 0.6.11 (the version that supports the new /connected_accounts/link endpoint).',
536
+ };
537
+ }
538
+
539
+ let authConfigId = options.authConfigId;
540
+ let usedExistingConfig = false;
541
+ let createdConfig = false;
542
+
543
+ try {
544
+ // ─── Step 1: Find or create an auth-config ──────────────────────
545
+ if (!authConfigId && typeof client.authConfigs?.list === 'function') {
546
+ try {
547
+ const existing = await client.authConfigs.list({ toolkit: toolkitSlug });
548
+ const items = existing?.items || _normalizeListResult(existing);
549
+ if (items.length > 0) {
550
+ authConfigId = items[0].id;
551
+ usedExistingConfig = true;
552
+ }
553
+ } catch {
554
+ // Non-fatal — fall through to create.
555
+ }
556
+ }
557
+
558
+ if (!authConfigId && typeof client.authConfigs?.create === 'function') {
559
+ try {
560
+ const created = await client.authConfigs.create(toolkitSlug, {
561
+ type: 'use_composio_managed_auth',
562
+ name: `${toolkitSlug} Auth Config`,
563
+ });
564
+ authConfigId = created?.id || created?.authConfigId;
565
+ createdConfig = true;
566
+ } catch (createErr) {
567
+ const msg = createErr?.message || String(createErr);
568
+ const status = createErr?.status;
569
+ // The 400 here typically means "Composio doesn't have a managed
570
+ // OAuth client for this toolkit — go set one up in the dashboard".
571
+ if (status === 400 || /no.*default.*auth/i.test(msg) || /must.*configure/i.test(msg)) {
572
+ return {
573
+ success: false,
574
+ error: `Composio has no managed-OAuth client for "${toolkitSlug}". Open https://app.composio.dev → Integrations → Add Integration → "${toolkitSlug}" and complete the OAuth client setup once. Then retry.`,
575
+ code: 'COMPOSIO_AUTH_CONFIG_MISSING',
576
+ rawError: msg,
577
+ };
578
+ }
579
+ throw createErr;
580
+ }
581
+ }
582
+
583
+ if (!authConfigId) {
584
+ return {
585
+ success: false,
586
+ error: `Could not find or create an auth-config for "${toolkitSlug}". Set one up at https://app.composio.dev → Integrations.`,
587
+ code: 'COMPOSIO_AUTH_CONFIG_MISSING',
588
+ };
589
+ }
590
+
591
+ // ─── Step 2: Create the Connect-Link via the new endpoint ──────
592
+ const linkOptions = {};
593
+ if (options.callbackUrl) linkOptions.callbackUrl = options.callbackUrl;
594
+ const result = await client.connectedAccounts.link(userId, authConfigId, linkOptions);
595
+
596
+ return {
597
+ success: true,
598
+ connectLink: result?.redirectUrl || result?.redirect_url || null,
599
+ connectionId: result?.id || result?.connectionId || result?.connected_account_id || null,
600
+ authConfigId,
601
+ usedExistingConfig,
602
+ createdConfig,
603
+ raw: result,
604
+ };
605
+ } catch (err) {
606
+ const rawMsg = err.message || String(err);
607
+ if (/toolkit.*not found/i.test(rawMsg) || /ComposioToolkitNotFoundError/.test(rawMsg)) {
608
+ return {
609
+ success: false,
610
+ error: `Toolkit "${toolkitSlug}" doesn't exist in Composio's catalog under that slug. Run \`composio.list-toolkits\` to see the canonical slug.`,
611
+ code: 'COMPOSIO_TOOLKIT_NOT_FOUND',
612
+ rawError: rawMsg,
613
+ };
614
+ }
615
+ if (/no auth config|authConfig.*not found/i.test(rawMsg)) {
616
+ return {
617
+ success: false,
618
+ error: `No auth configuration exists in your Composio org for "${toolkitSlug}". Open https://app.composio.dev → Integrations and add the "${toolkitSlug}" integration.`,
619
+ code: 'COMPOSIO_AUTH_CONFIG_MISSING',
620
+ rawError: rawMsg,
621
+ authConfigId,
622
+ };
623
+ }
624
+ return { success: false, error: rawMsg, authConfigId };
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Disconnect a connection. Accepts either a connectionId (preferred) or
630
+ * a {userId, toolkitSlug} pair (looks up the connection first).
631
+ *
632
+ * @param {{connectionId?: string, userId?: string, toolkitSlug?: string}} target
633
+ * @returns {Promise<{success, error?: string}>}
634
+ */
635
+ export async function disconnect(target) {
636
+ const { client, error } = await getClient();
637
+ if (error) return { success: false, error };
638
+ if (!target || (!target.connectionId && !(target.userId && target.toolkitSlug))) {
639
+ return { success: false, error: 'disconnect requires connectionId OR {userId, toolkitSlug}' };
640
+ }
641
+ let connectionId = target.connectionId;
642
+ try {
643
+ if (!connectionId) {
644
+ // Same `.get(object)` → "Path parameters invalid" trap as
645
+ // connectionStatus. Use `.list({userId, toolkit})` instead.
646
+ const raw = await client.connectedAccounts?.list?.({ userId: target.userId, toolkit: target.toolkitSlug });
647
+ const conn = _normalizeListResult(raw).find(c =>
648
+ c?.toolkitSlug === target.toolkitSlug ||
649
+ c?.toolkit === target.toolkitSlug ||
650
+ c?.toolkit?.slug === target.toolkitSlug
651
+ ) || null;
652
+ connectionId = conn?.id || null;
653
+ if (!connectionId) {
654
+ return { success: false, error: `No active connection found for toolkit "${target.toolkitSlug}"` };
655
+ }
656
+ }
657
+ // SDK exposes connectedAccounts.delete(id) or .disconnect(id).
658
+ if (typeof client.connectedAccounts?.delete === 'function') {
659
+ await client.connectedAccounts.delete(connectionId);
660
+ } else if (typeof client.connectedAccounts?.disconnect === 'function') {
661
+ await client.connectedAccounts.disconnect(connectionId);
662
+ } else {
663
+ return { success: false, error: 'SDK does not expose a disconnect method' };
664
+ }
665
+ return { success: true, connectionId };
666
+ } catch (err) {
667
+ return { success: false, error: err.message || String(err) };
668
+ }
669
+ }
670
+
671
+ /**
672
+ * List the available actions for a single toolkit.
673
+ *
674
+ * Same story as listToolkits — `client.tools.list()` isn't a public SDK
675
+ * method (it's `tools.get(userId, filter)`, which then wraps results in
676
+ * the configured LLM-provider's format). We want the raw catalog shape
677
+ * with slug + description + input_parameters, so we call REST directly.
678
+ *
679
+ * @param {string} toolkitSlug
680
+ * @param {string} [userId] forwarded to REST as `user_id` so the
681
+ * endpoint can scope tools the user is
682
+ * authorised for. Optional.
683
+ * @returns {Promise<{success, tools?: Array, error?: string}>}
684
+ */
685
+ export async function listTools(toolkitSlug, userId) {
686
+ const { key: apiKey } = resolveApiKey();
687
+ if (!apiKey) return { success: false, error: 'No Composio API key configured.' };
688
+ if (!toolkitSlug) return { success: false, error: 'listTools: toolkitSlug is required.' };
689
+ try {
690
+ const items = await _restListPaginated(apiKey, '/api/v3.1/tools', {
691
+ toolkit_slugs: toolkitSlug,
692
+ ...(userId ? { user_id: userId } : {}),
693
+ });
694
+ const tools = items.map(_camelizeTool);
695
+ return { success: true, tools };
696
+ } catch (err) {
697
+ return { success: false, error: err.message || String(err) };
698
+ }
699
+ }
700
+
701
+ /**
702
+ * @param {string} userId
703
+ * @param {string} toolkitSlug
704
+ * @param {string} actionSlug
705
+ * @param {object} args
706
+ */
707
+ export async function execute(userId, toolkitSlug, actionSlug, args = {}) {
708
+ const { client, error } = await getClient();
709
+ if (error) return { success: false, error };
710
+ try {
711
+ const result = await client.tools?.execute?.(actionSlug, { userId, arguments: args });
712
+ return {
713
+ success: result?.successful !== false,
714
+ output: result?.data ?? result?.output ?? result,
715
+ error: result?.error || null,
716
+ };
717
+ } catch (err) {
718
+ return { success: false, error: err.message || String(err) };
719
+ }
720
+ }
721
+
722
+ /* ------------------------------------------------------------------------ */
723
+ /* Default export — convenient grouping for callers */
724
+ /* ------------------------------------------------------------------------ */
725
+
726
+ export default {
727
+ getClient,
728
+ isAvailable,
729
+ setApiKey,
730
+ forgetApiKey,
731
+ listToolkits,
732
+ listConnections,
733
+ connectionStatus,
734
+ connect,
735
+ disconnect,
736
+ listTools,
737
+ execute,
738
+ };