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.
- package/node_modules/glob/README.md +31 -5
- package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/glob.js +2 -1
- package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
- package/node_modules/glob/dist/commonjs/index.min.js +3 -3
- package/node_modules/glob/dist/commonjs/index.min.js.map +4 -4
- package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/pattern.js +4 -0
- package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
- package/node_modules/glob/dist/esm/glob.d.ts +8 -0
- package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/glob.js +2 -1
- package/node_modules/glob/dist/esm/glob.js.map +1 -1
- package/node_modules/glob/dist/esm/index.min.js +3 -3
- package/node_modules/glob/dist/esm/index.min.js.map +4 -4
- package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
- package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/pattern.js +4 -0
- package/node_modules/glob/dist/esm/pattern.js.map +1 -1
- package/node_modules/{@isaacs → glob/node_modules}/balanced-match/README.md +7 -10
- package/node_modules/{@isaacs → glob/node_modules}/balanced-match/package.json +7 -18
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/README.md +3 -6
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.js +6 -4
- package/node_modules/glob/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -0
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.js +6 -4
- package/node_modules/glob/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/package.json +11 -7
- package/node_modules/glob/node_modules/minimatch/README.md +76 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +4 -2
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +309 -55
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +2 -4
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +4 -4
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +81 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +232 -134
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +8 -8
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +4 -2
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +309 -55
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +2 -4
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +4 -4
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +81 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +232 -134
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +8 -8
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/package.json +17 -11
- package/node_modules/glob/package.json +10 -13
- package/node_modules/minipass/LICENSE.md +55 -0
- package/node_modules/minipass/dist/commonjs/index.d.ts +12 -16
- package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/minipass/dist/commonjs/index.js +13 -3
- package/node_modules/minipass/dist/commonjs/index.js.map +1 -1
- package/node_modules/minipass/dist/esm/index.d.ts +12 -16
- package/node_modules/minipass/dist/esm/index.d.ts.map +1 -1
- package/node_modules/minipass/dist/esm/index.js +3 -1
- package/node_modules/minipass/dist/esm/index.js.map +1 -1
- package/node_modules/minipass/package.json +9 -14
- package/node_modules/path-scurry/node_modules/lru-cache/README.md +96 -10
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.cjs.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +109 -32
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +334 -197
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +4 -4
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.mjs.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +109 -32
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +333 -196
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +4 -4
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/package.json +71 -18
- package/node_modules/path-scurry/package.json +8 -24
- package/package.json +1 -1
- package/scripts/debug-balance-probe.mjs +35 -35
- package/scripts/push-image.sh +43 -43
- package/scripts/setup-acr.sh +65 -65
- package/scripts/verify-optional-deps.js +96 -1
- package/src/__tests__/composioCliFlags.test.js +239 -239
- package/src/analyzers/CSSAnalyzer.js +298 -297
- package/src/analyzers/ConfigValidator.js +691 -690
- package/src/analyzers/ESLintAnalyzer.js +320 -320
- package/src/analyzers/JavaScriptAnalyzer.js +260 -261
- package/src/analyzers/PrettierFormatter.js +246 -247
- package/src/analyzers/PythonAnalyzer.js +283 -283
- package/src/analyzers/SecurityAnalyzer.js +729 -729
- package/src/analyzers/SparrowAnalyzer.js +341 -341
- package/src/analyzers/TypeScriptAnalyzer.js +247 -247
- package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -41
- package/src/analyzers/__tests__/ConfigValidator.test.js +362 -362
- package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -40
- package/src/analyzers/__tests__/PythonAnalyzer.test.js +205 -208
- package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -303
- package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -187
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -344
- package/src/analyzers/codeCloneDetector/detector.js +250 -250
- package/src/analyzers/codeCloneDetector/index.js +194 -192
- package/src/analyzers/codeCloneDetector/parser.js +199 -199
- package/src/core/__tests__/agentPool.test.js +866 -866
- package/src/core/__tests__/agentPoolAutoResume.test.js +209 -209
- package/src/core/__tests__/agentPoolWakeOnMessage.test.js +315 -315
- package/src/core/__tests__/agentScheduler.emptyResponseChatStall.test.js +213 -213
- package/src/core/__tests__/agentScheduler.errorCategorisation.test.js +246 -246
- package/src/core/__tests__/agentScheduler.firstChunkTimeout.test.js +138 -138
- package/src/core/__tests__/agentScheduler.modeTransitions.test.js +233 -233
- package/src/core/__tests__/agentScheduler.nativePromptPick.test.js +319 -319
- package/src/core/__tests__/agentScheduler.taskLifecycleInstruction.test.js +78 -78
- package/src/core/__tests__/agentScheduler.visualizer.test.js +258 -258
- package/src/core/__tests__/flowCheckpointStore.test.js +140 -140
- package/src/core/__tests__/flowEndToEnd.test.js +565 -565
- package/src/core/__tests__/flowFieldMapping.test.js +188 -189
- package/src/core/__tests__/flowLintClientMirror.test.js +96 -98
- package/src/core/__tests__/flowSavePayload.test.js +170 -169
- package/src/core/__tests__/flowTemplates.test.js +311 -311
- package/src/core/__tests__/flowVersionStore.test.js +123 -123
- package/src/core/__tests__/messageProcessor.test.js +669 -669
- package/src/core/__tests__/stateManager.test.js +0 -1
- package/src/core/agentPool.js +2474 -2475
- package/src/core/agentScheduler.js +1 -4
- package/src/core/contextManager.js +708 -708
- package/src/core/flowExecutor.js +1510 -1510
- package/src/core/flowFieldMapping.js +136 -138
- package/src/core/messageProcessor.js +953 -954
- package/src/core/orchestrator.js +593 -595
- package/src/core/stateManager.js +1765 -1752
- package/src/index.js +1221 -1221
- package/src/interfaces/__tests__/archivedAgentDelete.test.js +207 -207
- package/src/interfaces/__tests__/bulkAgentRoute.test.js +361 -361
- package/src/interfaces/__tests__/imageServing.test.js +228 -228
- package/src/interfaces/__tests__/remoteSessionAuth.test.js +308 -308
- package/src/interfaces/__tests__/videoJobsRoutes.test.js +178 -179
- package/src/interfaces/__tests__/webServer.marketplace.test.js +629 -629
- package/src/interfaces/schedulerRoutes.js +50 -50
- package/src/interfaces/terminal/__tests__/smoke/connection.test.js +341 -350
- package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -156
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +325 -330
- package/src/interfaces/terminal/__tests__/smoke/tools.test.js +385 -388
- package/src/interfaces/terminal/api/session.js +265 -266
- package/src/interfaces/terminal/api/websocket.js +496 -497
- package/src/interfaces/terminal/components/AgentCreator.js +691 -705
- package/src/interfaces/terminal/components/AgentEditor.js +676 -678
- package/src/interfaces/terminal/components/AgentSwitcher.js +331 -330
- package/src/interfaces/terminal/components/ErrorPanel.js +263 -264
- package/src/interfaces/terminal/components/Header.js +28 -28
- package/src/interfaces/terminal/components/Layout.js +598 -603
- package/src/interfaces/terminal/components/MessageList.js +280 -281
- package/src/interfaces/terminal/components/SettingsPanel.js +410 -415
- package/src/interfaces/terminal/components/StatusBar.js +2 -0
- package/src/interfaces/terminal/index.js +168 -168
- package/src/interfaces/terminal/state/useAgentControl.js +496 -496
- package/src/interfaces/terminal/state/useAgents.js +537 -537
- package/src/interfaces/terminal/state/useMessages.js +629 -630
- package/src/interfaces/terminal/state/useTools.js +554 -554
- package/src/interfaces/terminal/utils/debugLogger.js +44 -44
- package/src/interfaces/terminal/utils/settingsStorage.js +232 -232
- package/src/interfaces/webServer.js +7578 -7579
- package/src/interfaces/webServer.js.bak +7046 -7046
- package/src/modules/fileExplorer/__tests__/zipDownload.test.js +237 -237
- package/src/modules/fileExplorer/controller.js +470 -469
- package/src/modules/fileExplorer/routes.js +285 -286
- package/src/modules/widget/__tests__/isDisabled.test.js +41 -41
- package/src/modules/widget/__tests__/routes.test.js +677 -678
- package/src/modules/widget/__tests__/runtime.test.js +401 -401
- package/src/modules/widget/__tests__/versioning.test.js +309 -309
- package/src/modules/widget/__tests__/webComponentRuntime.test.js +565 -565
- package/src/modules/widget/__tests__/widgetTool.test.js +316 -316
- package/src/modules/widget/routes.js +435 -435
- package/src/modules/widget/runtime/bundle.js +640 -640
- package/src/modules/widget/runtime/webComponentBundle.js +470 -470
- package/src/modules/widget/schema.js +182 -181
- package/src/modules/widget/widgetTool.js +1389 -1389
- package/src/services/__tests__/agentActivityService.test.js +401 -402
- package/src/services/__tests__/benchmarkService.test.js +184 -184
- package/src/services/__tests__/contextInjectionService.test.js +246 -246
- package/src/services/__tests__/conversationQuery.test.js +721 -723
- package/src/services/__tests__/credentialVault.test.js +469 -469
- package/src/services/__tests__/discordService.integration.test.js +638 -639
- package/src/services/__tests__/flowContextService.test.js +590 -590
- package/src/services/__tests__/memoryService.test.js +1 -1
- package/src/services/__tests__/messageSource.test.js +380 -380
- package/src/services/__tests__/modelRouterNaming.test.js +111 -111
- package/src/services/__tests__/projectDetector.test.js +34 -34
- package/src/services/__tests__/promptService.test.js +242 -242
- package/src/services/__tests__/telegramService.test.js +941 -941
- package/src/services/__tests__/tokenCountingService.test.js +48 -48
- package/src/services/agentActivityService.js +419 -420
- package/src/services/aiService.js +2997 -3001
- package/src/services/apiKeyManager.js +359 -359
- package/src/services/benchmarkService.js +196 -196
- package/src/services/codebaseKnowledgeService.js +2 -2
- package/src/services/composioService.js +738 -738
- package/src/services/conversationCompactionService.js +1258 -1257
- package/src/services/credentialVault.js +685 -685
- package/src/services/discordService.js +792 -793
- package/src/services/embeddings/__tests__/azureCustomProvider.test.js +232 -232
- package/src/services/embeddings/__tests__/embeddingService.test.js +417 -417
- package/src/services/embeddings/__tests__/localProvider.test.js +263 -263
- package/src/services/embeddings/autoRecall.js +218 -219
- package/src/services/embeddings/indexers/__tests__/agentIndexer.test.js +232 -232
- package/src/services/embeddings/indexers/__tests__/memoryIndexer.test.js +418 -418
- package/src/services/embeddings/indexers/__tests__/reminisceIndexer.test.js +356 -357
- package/src/services/embeddings/indexers/__tests__/skillsIndexer.test.js +145 -145
- package/src/services/embeddings/indexers/__tests__/taskIndexer.test.js +146 -146
- package/src/services/embeddings/indexers/composioIndexer.js +279 -279
- package/src/services/embeddings/providerInterface.js +206 -206
- package/src/services/embeddings/providers/localProvider.js +11 -7
- package/src/services/embeddings/providers/openaiProvider.js +101 -101
- package/src/services/embeddings/vectorStore/inMemoryJsonStore.js +356 -356
- package/src/services/errorHandler.js +809 -809
- package/src/services/flowContextService.js +586 -586
- package/src/services/grounding/MockAdapter.js +125 -125
- package/src/services/modelRouterService.js +26 -31
- package/src/services/modelsService.js +322 -322
- package/src/services/ollamaService.js +452 -452
- package/src/services/projectDetector.js +403 -404
- package/src/services/promptService.js +418 -418
- package/src/services/qualityInspector.js +795 -795
- package/src/services/scheduleService.js +726 -726
- package/src/services/serviceRegistry.js +386 -386
- package/src/services/telegrafBot.js +174 -174
- package/src/services/telegramService.js +1972 -1972
- package/src/services/visualEditorBridge.js +1033 -1033
- package/src/services/visualEditorServer.js +1769 -1774
- package/src/services/whatsappService.js +667 -668
- package/src/tools/__tests__/agentCommunicationTool.findAgent.test.js +226 -226
- package/src/tools/__tests__/agentCommunicationTool.test.js +3 -3
- package/src/tools/__tests__/agentDelayTool.test.js +342 -342
- package/src/tools/__tests__/baseTool.test.js +3 -3
- package/src/tools/__tests__/codeMapTool.test.js +915 -915
- package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -309
- package/src/tools/__tests__/fileTreeTool.test.js +274 -274
- package/src/tools/__tests__/filesystemTool.test.js +815 -815
- package/src/tools/__tests__/foundryWebSearchTool.test.js +252 -252
- package/src/tools/__tests__/imageTool.validator.test.js +194 -194
- package/src/tools/__tests__/jobDoneTool.test.js +580 -581
- package/src/tools/__tests__/memoryTool.forgetStale.test.js +272 -272
- package/src/tools/__tests__/memoryTool.reminisce.test.js +2 -2
- package/src/tools/__tests__/memoryTool.reminisceSemanticSearch.test.js +301 -301
- package/src/tools/__tests__/memoryTool.semanticSearch.test.js +405 -405
- package/src/tools/__tests__/memoryTool.teamPool.test.js +293 -293
- package/src/tools/__tests__/memoryTool.test.js +1 -1
- package/src/tools/__tests__/seekTool.test.js +282 -282
- package/src/tools/__tests__/skillsTool.search.test.js +164 -164
- package/src/tools/__tests__/skillsTool.test.js +226 -226
- package/src/tools/__tests__/staticAnalysisTool.test.js +509 -509
- package/src/tools/__tests__/taskManagerTool.discipline.test.js +137 -137
- package/src/tools/__tests__/taskManagerTool.search.test.js +143 -143
- package/src/tools/__tests__/taskManagerTool.test.js +866 -866
- package/src/tools/__tests__/terminalTool.test.js +448 -448
- package/src/tools/__tests__/toolShapeForgiveness.test.js +259 -260
- package/src/tools/__tests__/userPromptTool.test.js +297 -297
- package/src/tools/__tests__/videoTool.jobs.test.js +147 -147
- package/src/tools/__tests__/webTool.e2e.test.js +609 -603
- package/src/tools/__tests__/webTool.unit.test.js +195 -195
- package/src/tools/__tests__/webTool.visionModel.test.js +75 -75
- package/src/tools/agentCommunicationTool.js +8 -10
- package/src/tools/agentDelayTool.js +496 -497
- package/src/tools/asyncToolManager.js +602 -603
- package/src/tools/baseTool.js +12 -11
- package/src/tools/cloneDetectionTool.js +576 -581
- package/src/tools/codeMapTool.js +0 -6
- package/src/tools/composioTool.js +617 -617
- package/src/tools/dependencyResolverTool.js +1211 -1212
- package/src/tools/desktop/DesktopTool.js +629 -638
- package/src/tools/desktop/__tests__/DesktopTool.e2e.test.js +306 -306
- package/src/tools/desktop/__tests__/DesktopTool.test.js +507 -507
- package/src/tools/desktop/__tests__/osController.test.js +364 -364
- package/src/tools/desktop/osController.js +491 -491
- package/src/tools/docxTool.js +623 -623
- package/src/tools/excelTool.js +636 -636
- package/src/tools/fileContentReplaceTool.js +5 -7
- package/src/tools/fileSystemTool.js +12 -19
- package/src/tools/fileTreeTool.js +840 -840
- package/src/tools/foundryWebSearchTool.js +273 -273
- package/src/tools/helpTool.js +198 -198
- package/src/tools/imageTool.js +1397 -1397
- package/src/tools/importAnalyzerTool.js +1056 -1056
- package/src/tools/jobDoneTool.js +495 -495
- package/src/tools/memoryTool.js +1 -1
- package/src/tools/office/pres/__tests__/presSystem.test.js +365 -365
- package/src/tools/office/pres/archetypes/agenda.js +61 -61
- package/src/tools/office/pres/archetypes/bentoGrid.js +218 -219
- package/src/tools/office/pres/archetypes/bigStat.js +140 -142
- package/src/tools/office/pres/archetypes/closing.js +70 -70
- package/src/tools/office/pres/archetypes/hero.js +70 -70
- package/src/tools/office/pres/archetypes/productHero.js +93 -94
- package/src/tools/office/pres/archetypes/table.js +73 -74
- package/src/tools/office/pres/backgrounds/orb.js +66 -66
- package/src/tools/office/pres/components.js +422 -423
- package/src/tools/officeTool.js +441 -441
- package/src/tools/pdfTool.js +625 -627
- package/src/tools/platformControlTool.js +1081 -1081
- package/src/tools/seekTool.js +917 -918
- package/src/tools/skillsTool.js +1 -1
- package/src/tools/staticAnalysisTool.js +2143 -2146
- package/src/tools/taskManagerTool.js +3324 -3324
- package/src/tools/terminalTool.js +2615 -2618
- package/src/tools/videoTool.js +1303 -1303
- package/src/tools/visionTool.js +508 -508
- package/src/tools/visualEditorTool.js +1289 -1290
- package/src/tools/webTool.js +3368 -3368
- package/src/tools/whatsappTool.js +464 -464
- package/src/types/__tests__/agent.test.js +499 -499
- package/src/types/__tests__/contextReference.test.js +606 -606
- package/src/types/__tests__/conversation.test.js +555 -555
- package/src/types/__tests__/toolCommand.test.js +584 -584
- package/src/types/contextReference.js +974 -971
- package/src/types/conversation.js +729 -729
- package/src/types/toolCommand.js +746 -746
- package/src/utilities/__tests__/attachmentValidator.test.js +80 -80
- package/src/utilities/__tests__/auditReport.test.js +328 -328
- package/src/utilities/__tests__/directoryAccessManager.test.js +388 -388
- package/src/utilities/__tests__/jsonRepair.test.js +103 -104
- package/src/utilities/__tests__/modeTransitionReasons.test.js +105 -105
- package/src/utilities/__tests__/platformUtils.test.js +80 -87
- package/src/utilities/__tests__/structuredFileValidator.test.js +261 -263
- package/src/utilities/__tests__/toolConstants.test.js +92 -94
- package/src/utilities/__tests__/useIsTouchDevice.detect.test.js +114 -114
- package/src/utilities/__tests__/webUiUtilSync.test.js +117 -117
- package/src/utilities/attachmentValidator.js +284 -288
- package/src/utilities/authCache.js.backup-1779570472481 +121 -121
- package/src/utilities/browserStealth.js +631 -630
- package/src/utilities/configManager.js +616 -617
- package/src/utilities/directoryAccessManager.js +564 -565
- package/src/utilities/fileProcessor.js +308 -307
- package/src/utilities/humanBehavior.js +454 -453
- package/src/utilities/logger.js +479 -479
- package/src/utilities/structuredFileValidator.js +696 -699
- package/src/utilities/tagParser.js +5 -10
- package/src/utilities/userDataDir.js +308 -308
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +0 -1
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +0 -1
- package/node_modules/minipass/LICENSE +0 -15
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/LICENSE.md +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/LICENSE +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts.map +0 -0
- /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
|
|
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
|
-
};
|
|
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
|
+
};
|