@customclaw/composio 0.0.10 → 0.0.11
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/README.md +10 -6
- package/dist/cli.js +37 -30
- package/dist/client.d.ts +3 -13
- package/dist/client.js +62 -184
- package/dist/config.d.ts +0 -9
- package/dist/config.js +0 -6
- package/dist/index.d.ts +0 -4
- package/dist/tools/connections.d.ts +40 -20
- package/dist/tools/connections.js +64 -32
- package/dist/tools/execute.d.ts +2 -2
- package/dist/tools/execute.js +10 -4
- package/dist/tools/search.d.ts +2 -2
- package/dist/tools/search.js +10 -4
- package/dist/types.d.ts +1 -2
- package/openclaw.plugin.json +0 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,11 @@ OpenClaw plugin that connects your agent to Gmail, Sentry, and other services th
|
|
|
8
8
|
openclaw plugins install @customclaw/composio
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Identifiers
|
|
12
|
+
|
|
13
|
+
- Install spec: `@customclaw/composio`
|
|
14
|
+
- Plugin id: `composio` (used in `plugins.entries.*` and `plugins update`)
|
|
15
|
+
|
|
11
16
|
## Setup
|
|
12
17
|
|
|
13
18
|
1. Get an API key from [platform.composio.dev/settings](https://platform.composio.dev/settings)
|
|
@@ -28,7 +33,6 @@ Or add manually to `~/.openclaw/openclaw.json`:
|
|
|
28
33
|
"enabled": true,
|
|
29
34
|
"config": {
|
|
30
35
|
"apiKey": "your-api-key",
|
|
31
|
-
"defaultUserId": "my-app-user-123",
|
|
32
36
|
"allowedToolkits": ["gmail", "sentry"]
|
|
33
37
|
}
|
|
34
38
|
}
|
|
@@ -58,7 +62,8 @@ The plugin gives your agent three tools:
|
|
|
58
62
|
- `composio_execute_tool` — runs a Composio action (e.g. `GMAIL_FETCH_EMAILS`, `SENTRY_LIST_ISSUES`)
|
|
59
63
|
- `composio_manage_connections` — checks connection status and generates OAuth links when a toolkit isn't connected yet
|
|
60
64
|
|
|
61
|
-
`
|
|
65
|
+
`composio_search_tools` and `composio_execute_tool` require `user_id` so every action is explicitly scoped.
|
|
66
|
+
`composio_execute_tool` also accepts optional `connected_account_id` for deterministic account selection when multiple accounts exist.
|
|
62
67
|
|
|
63
68
|
The agent handles the rest. Ask it to "check my latest emails" and it will call the right tool, prompt you to connect Gmail if needed, and fetch the results.
|
|
64
69
|
|
|
@@ -69,6 +74,7 @@ openclaw composio setup # interactive setup for
|
|
|
69
74
|
openclaw composio list --user-id user-123 # list available toolkits for a user scope
|
|
70
75
|
openclaw composio status [toolkit] --user-id user-123 # check connection status in a user scope
|
|
71
76
|
openclaw composio accounts [toolkit] # inspect connected accounts (id/user_id/status)
|
|
77
|
+
openclaw composio accounts [toolkit] --user-id user-123 # optional filter for one user scope
|
|
72
78
|
openclaw composio connect gmail --user-id user-123 # open OAuth link for a specific user scope
|
|
73
79
|
openclaw composio disconnect gmail --user-id user-123 # remove a connection in that user scope
|
|
74
80
|
openclaw composio search "send email" --user-id user-123
|
|
@@ -79,7 +85,6 @@ openclaw composio search "send email" --user-id user-123
|
|
|
79
85
|
| Key | Description |
|
|
80
86
|
|-----|-------------|
|
|
81
87
|
| `apiKey` | Composio API key (required) |
|
|
82
|
-
| `defaultUserId` | Default Composio `user_id` scope when `--user-id` is not provided |
|
|
83
88
|
| `allowedToolkits` | Only allow these toolkits (e.g. `["gmail", "sentry"]`) |
|
|
84
89
|
| `blockedToolkits` | Block specific toolkits |
|
|
85
90
|
| `readOnlyMode` | Blocks likely-destructive actions by token matching (delete/update/create/send/etc.); use `allowedToolSlugs` to override safe exceptions |
|
|
@@ -95,14 +100,13 @@ may look disconnected.
|
|
|
95
100
|
|
|
96
101
|
Tips:
|
|
97
102
|
|
|
98
|
-
-
|
|
99
|
-
- Prefer passing `user_id`/`--user-id` explicitly when checking status, connecting, disconnecting, or executing tools.
|
|
103
|
+
- Always pass `user_id`/`--user-id` explicitly when searching, checking status, connecting, disconnecting, or executing tools.
|
|
100
104
|
- Use `openclaw composio accounts <toolkit>` to discover which `user_id` owns active connections.
|
|
101
105
|
|
|
102
106
|
## Updating
|
|
103
107
|
|
|
104
108
|
```bash
|
|
105
|
-
openclaw plugins update
|
|
109
|
+
openclaw plugins update composio
|
|
106
110
|
openclaw gateway restart
|
|
107
111
|
```
|
|
108
112
|
|
package/dist/cli.js
CHANGED
|
@@ -30,6 +30,13 @@ function normalizePluginIdList(value) {
|
|
|
30
30
|
.filter(Boolean);
|
|
31
31
|
return Array.from(new Set(normalized));
|
|
32
32
|
}
|
|
33
|
+
function requireUserId(rawUserId, logger, commandUsage) {
|
|
34
|
+
const userId = String(rawUserId || "").trim();
|
|
35
|
+
if (userId)
|
|
36
|
+
return userId;
|
|
37
|
+
logger.error(`--user-id is required. Usage: ${commandUsage}`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
33
40
|
async function readOpenClawConfig(configPath) {
|
|
34
41
|
try {
|
|
35
42
|
const raw = await readFile(configPath, "utf8");
|
|
@@ -71,7 +78,6 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
71
78
|
.description("Create or update Composio config in ~/.openclaw/openclaw.json")
|
|
72
79
|
.option("-c, --config-path <path>", "OpenClaw config file path", DEFAULT_OPENCLAW_CONFIG_PATH)
|
|
73
80
|
.option("--api-key <apiKey>", "Composio API key")
|
|
74
|
-
.option("--default-user-id <userId>", "Default user ID for Composio user scoping")
|
|
75
81
|
.option("--allowed-toolkits <toolkits>", "Comma-separated allowed toolkit slugs")
|
|
76
82
|
.option("--blocked-toolkits <toolkits>", "Comma-separated blocked toolkit slugs")
|
|
77
83
|
.option("--read-only <enabled>", "Enable read-only mode (true/false)")
|
|
@@ -120,9 +126,6 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
120
126
|
String(existingComposioConfig.apiKey || "").trim() ||
|
|
121
127
|
String(config.apiKey || "").trim() ||
|
|
122
128
|
String(process.env.COMPOSIO_API_KEY || "").trim();
|
|
123
|
-
let defaultUserId = String(options.defaultUserId || "").trim() ||
|
|
124
|
-
String(existingComposioConfig.defaultUserId || "").trim() ||
|
|
125
|
-
String(config.defaultUserId || "").trim();
|
|
126
129
|
let allowedToolkits = parseCsvToolkits(options.allowedToolkits) ||
|
|
127
130
|
(Array.isArray(existingComposioConfig.allowedToolkits)
|
|
128
131
|
? normalizeToolkitList(existingComposioConfig.allowedToolkits)
|
|
@@ -148,9 +151,6 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
148
151
|
const apiKeyPrompt = await rl.question(`Composio API key${apiKey ? " [configured]" : ""}: `);
|
|
149
152
|
if (apiKeyPrompt.trim())
|
|
150
153
|
apiKey = apiKeyPrompt.trim();
|
|
151
|
-
const defaultUserPrompt = await rl.question(`Default user ID${defaultUserId ? ` [${defaultUserId}]` : " (optional)"}: `);
|
|
152
|
-
if (defaultUserPrompt.trim())
|
|
153
|
-
defaultUserId = defaultUserPrompt.trim();
|
|
154
154
|
const allowedDefault = allowedToolkits && allowedToolkits.length > 0
|
|
155
155
|
? ` [${allowedToolkits.join(",")}]`
|
|
156
156
|
: " (optional)";
|
|
@@ -187,12 +187,7 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
187
187
|
apiKey,
|
|
188
188
|
readOnlyMode,
|
|
189
189
|
};
|
|
190
|
-
|
|
191
|
-
mergedComposioConfig.defaultUserId = defaultUserId;
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
delete mergedComposioConfig.defaultUserId;
|
|
195
|
-
}
|
|
190
|
+
delete mergedComposioConfig.defaultUserId;
|
|
196
191
|
if (allowedToolkits && allowedToolkits.length > 0) {
|
|
197
192
|
mergedComposioConfig.allowedToolkits = allowedToolkits;
|
|
198
193
|
}
|
|
@@ -217,7 +212,6 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
217
212
|
console.log("\nComposio setup saved.");
|
|
218
213
|
console.log("─".repeat(40));
|
|
219
214
|
console.log(`Config: ${configPath}`);
|
|
220
|
-
console.log(`defaultUserId: ${defaultUserId || "default"}`);
|
|
221
215
|
console.log(`readOnlyMode: ${readOnlyMode ? "enabled" : "disabled"}`);
|
|
222
216
|
if (updatedPluginSystemEnabled) {
|
|
223
217
|
console.log("plugins.enabled: set to true");
|
|
@@ -241,13 +235,16 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
241
235
|
composio
|
|
242
236
|
.command("list")
|
|
243
237
|
.description("List available Composio toolkits")
|
|
244
|
-
.option("-u, --user-id <userId>", "
|
|
238
|
+
.option("-u, --user-id <userId>", "Required user ID for session scoping")
|
|
245
239
|
.action(async (options) => {
|
|
246
240
|
const composioClient = requireClient();
|
|
247
241
|
if (!composioClient)
|
|
248
242
|
return;
|
|
243
|
+
const userId = requireUserId(options.userId, logger, "openclaw composio list --user-id <user-id>");
|
|
244
|
+
if (!userId)
|
|
245
|
+
return;
|
|
249
246
|
try {
|
|
250
|
-
const toolkits = await composioClient.listToolkits(
|
|
247
|
+
const toolkits = await composioClient.listToolkits(userId);
|
|
251
248
|
console.log("\nAvailable Composio Toolkits:");
|
|
252
249
|
console.log("─".repeat(40));
|
|
253
250
|
for (const toolkit of toolkits.sort()) {
|
|
@@ -263,19 +260,21 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
263
260
|
composio
|
|
264
261
|
.command("status [toolkit]")
|
|
265
262
|
.description("Check connection status for toolkits")
|
|
266
|
-
.option("-u, --user-id <userId>", "
|
|
263
|
+
.option("-u, --user-id <userId>", "Required user ID for session scoping")
|
|
267
264
|
.action(async (toolkit, options) => {
|
|
268
265
|
const composioClient = requireClient();
|
|
269
266
|
if (!composioClient)
|
|
270
267
|
return;
|
|
268
|
+
const userId = requireUserId(options.userId, logger, "openclaw composio status [toolkit] --user-id <user-id>");
|
|
269
|
+
if (!userId)
|
|
270
|
+
return;
|
|
271
271
|
try {
|
|
272
272
|
const toolkitSlug = toolkit ? normalizeToolkitSlug(toolkit) : undefined;
|
|
273
273
|
const toolkits = toolkitSlug ? [toolkitSlug] : undefined;
|
|
274
|
-
const
|
|
275
|
-
const statuses = await composioClient.getConnectionStatus(toolkits, options.userId);
|
|
274
|
+
const statuses = await composioClient.getConnectionStatus(toolkits, userId);
|
|
276
275
|
console.log("\nComposio Connection Status:");
|
|
277
276
|
console.log("─".repeat(40));
|
|
278
|
-
console.log(` Scope user_id: ${
|
|
277
|
+
console.log(` Scope user_id: ${userId}`);
|
|
279
278
|
if (statuses.length === 0) {
|
|
280
279
|
console.log(" No connections found");
|
|
281
280
|
}
|
|
@@ -288,7 +287,7 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
288
287
|
}
|
|
289
288
|
if (toolkitSlug && statuses.length === 1 && !statuses[0]?.connected) {
|
|
290
289
|
const activeUserIds = await composioClient.findActiveUserIdsForToolkit(toolkitSlug);
|
|
291
|
-
const otherUserIds = activeUserIds.filter((uid) => uid !==
|
|
290
|
+
const otherUserIds = activeUserIds.filter((uid) => uid !== userId);
|
|
292
291
|
if (otherUserIds.length > 0) {
|
|
293
292
|
console.log(`\n Hint: '${toolkitSlug}' is connected under other user_id(s): ${otherUserIds.join(", ")}`);
|
|
294
293
|
console.log(` Try: openclaw composio status ${toolkitSlug} --user-id <user-id>`);
|
|
@@ -341,17 +340,19 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
341
340
|
composio
|
|
342
341
|
.command("connect <toolkit>")
|
|
343
342
|
.description("Connect to a Composio toolkit (opens auth URL)")
|
|
344
|
-
.option("-u, --user-id <userId>", "
|
|
343
|
+
.option("-u, --user-id <userId>", "Required user ID for session scoping")
|
|
345
344
|
.action(async (toolkit, options) => {
|
|
346
345
|
const composioClient = requireClient();
|
|
347
346
|
if (!composioClient)
|
|
348
347
|
return;
|
|
348
|
+
const userId = requireUserId(options.userId, logger, "openclaw composio connect <toolkit> --user-id <user-id>");
|
|
349
|
+
if (!userId)
|
|
350
|
+
return;
|
|
349
351
|
try {
|
|
350
352
|
const toolkitSlug = normalizeToolkitSlug(toolkit);
|
|
351
|
-
const currentUserId = options.userId || config.defaultUserId || "default";
|
|
352
353
|
console.log(`\nInitiating connection to ${toolkitSlug}...`);
|
|
353
|
-
console.log(`Using user_id: ${
|
|
354
|
-
const result = await composioClient.createConnection(toolkitSlug,
|
|
354
|
+
console.log(`Using user_id: ${userId}`);
|
|
355
|
+
const result = await composioClient.createConnection(toolkitSlug, userId);
|
|
355
356
|
if ("error" in result) {
|
|
356
357
|
logger.error(`Failed to create connection: ${result.error}`);
|
|
357
358
|
return;
|
|
@@ -360,7 +361,7 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
360
361
|
console.log("─".repeat(40));
|
|
361
362
|
console.log(result.authUrl);
|
|
362
363
|
console.log("\nOpen this URL in your browser to authenticate.");
|
|
363
|
-
console.log(`After authentication, run 'openclaw composio status ${toolkitSlug} --user-id ${
|
|
364
|
+
console.log(`After authentication, run 'openclaw composio status ${toolkitSlug} --user-id ${userId}' to verify.\n`);
|
|
364
365
|
// Try to open URL in browser
|
|
365
366
|
try {
|
|
366
367
|
const { exec } = await import("node:child_process");
|
|
@@ -384,15 +385,18 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
384
385
|
composio
|
|
385
386
|
.command("disconnect <toolkit>")
|
|
386
387
|
.description("Disconnect from a Composio toolkit")
|
|
387
|
-
.option("-u, --user-id <userId>", "
|
|
388
|
+
.option("-u, --user-id <userId>", "Required user ID for session scoping")
|
|
388
389
|
.action(async (toolkit, options) => {
|
|
389
390
|
const composioClient = requireClient();
|
|
390
391
|
if (!composioClient)
|
|
391
392
|
return;
|
|
393
|
+
const userId = requireUserId(options.userId, logger, "openclaw composio disconnect <toolkit> --user-id <user-id>");
|
|
394
|
+
if (!userId)
|
|
395
|
+
return;
|
|
392
396
|
try {
|
|
393
397
|
const toolkitSlug = normalizeToolkitSlug(toolkit);
|
|
394
398
|
console.log(`\nDisconnecting from ${toolkitSlug}...`);
|
|
395
|
-
const result = await composioClient.disconnectToolkit(toolkitSlug,
|
|
399
|
+
const result = await composioClient.disconnectToolkit(toolkitSlug, userId);
|
|
396
400
|
if (result.success) {
|
|
397
401
|
console.log(`Successfully disconnected from ${toolkitSlug}\n`);
|
|
398
402
|
}
|
|
@@ -410,18 +414,21 @@ export function registerComposioCli({ program, getClient, config, logger }) {
|
|
|
410
414
|
.description("Search for tools matching a query")
|
|
411
415
|
.option("-t, --toolkit <toolkit>", "Limit search to a specific toolkit")
|
|
412
416
|
.option("-l, --limit <limit>", "Maximum results", "10")
|
|
413
|
-
.option("-u, --user-id <userId>", "
|
|
417
|
+
.option("-u, --user-id <userId>", "Required user ID for session scoping")
|
|
414
418
|
.action(async (query, options) => {
|
|
415
419
|
const composioClient = requireClient();
|
|
416
420
|
if (!composioClient)
|
|
417
421
|
return;
|
|
422
|
+
const userId = requireUserId(options.userId, logger, "openclaw composio search <query> --user-id <user-id>");
|
|
423
|
+
if (!userId)
|
|
424
|
+
return;
|
|
418
425
|
try {
|
|
419
426
|
const limit = parseInt(options.limit, 10) || 10;
|
|
420
427
|
const toolkits = options.toolkit ? [normalizeToolkitSlug(options.toolkit)] : undefined;
|
|
421
428
|
const results = await composioClient.searchTools(query, {
|
|
422
429
|
toolkits,
|
|
423
430
|
limit,
|
|
424
|
-
userId
|
|
431
|
+
userId,
|
|
425
432
|
});
|
|
426
433
|
console.log(`\nSearch results for "${query}":`);
|
|
427
434
|
console.log("─".repeat(60));
|
package/dist/client.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export declare class ComposioClient {
|
|
|
8
8
|
private sessionCache;
|
|
9
9
|
constructor(config: ComposioConfig);
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Resolve user ID for API calls.
|
|
12
|
+
* This plugin requires explicit user_id scoping to avoid accidental cross-user access.
|
|
12
13
|
*/
|
|
13
14
|
private getUserId;
|
|
14
15
|
/**
|
|
@@ -19,7 +20,6 @@ export declare class ComposioClient {
|
|
|
19
20
|
private buildToolRouterBlockedToolsConfig;
|
|
20
21
|
private buildSessionConfig;
|
|
21
22
|
private getSession;
|
|
22
|
-
private shouldRetrySessionWithoutToolkitFilters;
|
|
23
23
|
private clearUserSessionCache;
|
|
24
24
|
/**
|
|
25
25
|
* Check if a toolkit is allowed based on config
|
|
@@ -43,18 +43,8 @@ export declare class ComposioClient {
|
|
|
43
43
|
* Execute a single tool using COMPOSIO_MULTI_EXECUTE_TOOL
|
|
44
44
|
*/
|
|
45
45
|
executeTool(toolSlug: string, args: Record<string, unknown>, userId?: string, connectedAccountId?: string): Promise<ToolExecutionResult>;
|
|
46
|
-
private tryExecutionRecovery;
|
|
47
|
-
private tryDirectExecutionFallback;
|
|
48
|
-
private executeDirectTool;
|
|
49
|
-
private tryHintedIdentifierRetry;
|
|
50
|
-
private shouldFallbackToDirectExecution;
|
|
51
|
-
private shouldRetryFromServerHint;
|
|
52
|
-
private extractServerHintLiteral;
|
|
53
|
-
private buildRetryArgsFromHint;
|
|
54
|
-
private extractSingleMissingField;
|
|
55
|
-
private buildCombinedErrorText;
|
|
56
|
-
private extractNestedMetaError;
|
|
57
46
|
private resolveConnectedAccountForExecution;
|
|
47
|
+
private isConnectedAccountActiveForUser;
|
|
58
48
|
/**
|
|
59
49
|
* Get connection status for toolkits using session.toolkits()
|
|
60
50
|
*/
|
package/dist/client.js
CHANGED
|
@@ -58,10 +58,15 @@ export class ComposioClient {
|
|
|
58
58
|
this.client = new Composio({ apiKey: config.apiKey });
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Resolve user ID for API calls.
|
|
62
|
+
* This plugin requires explicit user_id scoping to avoid accidental cross-user access.
|
|
62
63
|
*/
|
|
63
64
|
getUserId(overrideUserId) {
|
|
64
|
-
|
|
65
|
+
const userId = String(overrideUserId || "").trim();
|
|
66
|
+
if (!userId) {
|
|
67
|
+
throw new Error("user_id is required. Pass user_id explicitly.");
|
|
68
|
+
}
|
|
69
|
+
return userId;
|
|
65
70
|
}
|
|
66
71
|
/**
|
|
67
72
|
* Get or create a Tool Router session for a user
|
|
@@ -141,32 +146,10 @@ export class ComposioClient {
|
|
|
141
146
|
return this.sessionCache.get(key);
|
|
142
147
|
}
|
|
143
148
|
const sessionConfig = this.buildSessionConfig(normalizedConnectedAccounts);
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
session = await this.client.toolRouter.create(userId, sessionConfig);
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
if (!this.shouldRetrySessionWithoutToolkitFilters(err, sessionConfig)) {
|
|
150
|
-
throw err;
|
|
151
|
-
}
|
|
152
|
-
const { toolkits: _removedToolkits, ...retryWithoutToolkits } = sessionConfig ?? {};
|
|
153
|
-
const retryConfig = Object.keys(retryWithoutToolkits).length > 0
|
|
154
|
-
? retryWithoutToolkits
|
|
155
|
-
: undefined;
|
|
156
|
-
session = await this.client.toolRouter.create(userId, retryConfig);
|
|
157
|
-
}
|
|
149
|
+
const session = await this.client.toolRouter.create(userId, sessionConfig);
|
|
158
150
|
this.sessionCache.set(key, session);
|
|
159
151
|
return session;
|
|
160
152
|
}
|
|
161
|
-
shouldRetrySessionWithoutToolkitFilters(err, sessionConfig) {
|
|
162
|
-
const enabledToolkits = sessionConfig?.toolkits?.enable;
|
|
163
|
-
if (!Array.isArray(enabledToolkits) || enabledToolkits.length === 0) {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
const message = String(err instanceof Error ? err.message : err || "").toLowerCase();
|
|
167
|
-
return (message.includes("require auth configs but none exist") &&
|
|
168
|
-
message.includes("please specify them in auth_configs"));
|
|
169
|
-
}
|
|
170
153
|
clearUserSessionCache(userId) {
|
|
171
154
|
const prefix = `uid:${userId}`;
|
|
172
155
|
for (const key of this.sessionCache.keys()) {
|
|
@@ -322,16 +305,6 @@ export class ComposioClient {
|
|
|
322
305
|
sync_response_to_workbench: false,
|
|
323
306
|
});
|
|
324
307
|
if (!response.successful) {
|
|
325
|
-
const recovered = await this.tryExecutionRecovery({
|
|
326
|
-
uid: effectiveUid,
|
|
327
|
-
toolSlug: normalizedToolSlug,
|
|
328
|
-
args,
|
|
329
|
-
connectedAccountId: accountResolution.connectedAccountId,
|
|
330
|
-
metaError: response.error,
|
|
331
|
-
metaData: response.data,
|
|
332
|
-
});
|
|
333
|
-
if (recovered)
|
|
334
|
-
return recovered;
|
|
335
308
|
return { success: false, error: response.error || "Execution failed" };
|
|
336
309
|
}
|
|
337
310
|
const results = response.data?.results || [];
|
|
@@ -341,21 +314,10 @@ export class ComposioClient {
|
|
|
341
314
|
}
|
|
342
315
|
// Response data is nested under result.response
|
|
343
316
|
const toolResponse = result.response;
|
|
344
|
-
|
|
345
|
-
const recovered = await this.tryExecutionRecovery({
|
|
346
|
-
uid: effectiveUid,
|
|
347
|
-
toolSlug: normalizedToolSlug,
|
|
348
|
-
args,
|
|
349
|
-
connectedAccountId: accountResolution.connectedAccountId,
|
|
350
|
-
metaError: toolResponse.error ?? undefined,
|
|
351
|
-
metaData: response.data,
|
|
352
|
-
});
|
|
353
|
-
if (recovered)
|
|
354
|
-
return recovered;
|
|
355
|
-
}
|
|
317
|
+
const toolData = toolResponse.data ?? toolResponse.data_preview;
|
|
356
318
|
return {
|
|
357
319
|
success: toolResponse.successful,
|
|
358
|
-
data:
|
|
320
|
+
data: toolData,
|
|
359
321
|
error: toolResponse.error ?? undefined,
|
|
360
322
|
};
|
|
361
323
|
}
|
|
@@ -366,139 +328,13 @@ export class ComposioClient {
|
|
|
366
328
|
};
|
|
367
329
|
}
|
|
368
330
|
}
|
|
369
|
-
async tryExecutionRecovery(params) {
|
|
370
|
-
const directFallback = await this.tryDirectExecutionFallback(params);
|
|
371
|
-
if (directFallback?.success)
|
|
372
|
-
return directFallback;
|
|
373
|
-
const hintedRetry = await this.tryHintedIdentifierRetry({
|
|
374
|
-
...params,
|
|
375
|
-
additionalError: directFallback?.error,
|
|
376
|
-
});
|
|
377
|
-
if (hintedRetry)
|
|
378
|
-
return hintedRetry;
|
|
379
|
-
return directFallback;
|
|
380
|
-
}
|
|
381
|
-
async tryDirectExecutionFallback(params) {
|
|
382
|
-
if (!this.shouldFallbackToDirectExecution(params.uid, params.metaError, params.metaData)) {
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
return this.executeDirectTool(params.toolSlug, params.uid, params.args, params.connectedAccountId);
|
|
386
|
-
}
|
|
387
|
-
async executeDirectTool(toolSlug, userId, args, connectedAccountId) {
|
|
388
|
-
try {
|
|
389
|
-
const response = await this.client.tools.execute(toolSlug, {
|
|
390
|
-
userId,
|
|
391
|
-
connectedAccountId,
|
|
392
|
-
arguments: args,
|
|
393
|
-
dangerouslySkipVersionCheck: true,
|
|
394
|
-
});
|
|
395
|
-
return {
|
|
396
|
-
success: Boolean(response?.successful),
|
|
397
|
-
data: response?.data,
|
|
398
|
-
error: response?.error ?? undefined,
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
catch (err) {
|
|
402
|
-
return {
|
|
403
|
-
success: false,
|
|
404
|
-
error: err instanceof Error ? err.message : String(err),
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
async tryHintedIdentifierRetry(params) {
|
|
409
|
-
const combined = this.buildCombinedErrorText(params.metaError, params.metaData, params.additionalError);
|
|
410
|
-
if (!this.shouldRetryFromServerHint(combined))
|
|
411
|
-
return null;
|
|
412
|
-
const hint = this.extractServerHintLiteral(combined);
|
|
413
|
-
if (!hint)
|
|
414
|
-
return null;
|
|
415
|
-
const retryArgs = this.buildRetryArgsFromHint(params.args, combined, hint);
|
|
416
|
-
if (!retryArgs)
|
|
417
|
-
return null;
|
|
418
|
-
return this.executeDirectTool(params.toolSlug, params.uid, retryArgs, params.connectedAccountId);
|
|
419
|
-
}
|
|
420
|
-
shouldFallbackToDirectExecution(uid, metaError, metaData) {
|
|
421
|
-
if (uid === "default")
|
|
422
|
-
return false;
|
|
423
|
-
const combined = this.buildCombinedErrorText(metaError, metaData).toLowerCase();
|
|
424
|
-
return combined.includes("no connected account found for entity id default");
|
|
425
|
-
}
|
|
426
|
-
shouldRetryFromServerHint(errorText) {
|
|
427
|
-
const lower = errorText.toLowerCase();
|
|
428
|
-
return (lower.includes("only allowed to access") ||
|
|
429
|
-
lower.includes("allowed to access the"));
|
|
430
|
-
}
|
|
431
|
-
extractServerHintLiteral(errorText) {
|
|
432
|
-
const matches = errorText.match(/`([^`]+)`/);
|
|
433
|
-
if (!matches?.[1])
|
|
434
|
-
return undefined;
|
|
435
|
-
const literal = matches[1].trim();
|
|
436
|
-
if (!literal)
|
|
437
|
-
return undefined;
|
|
438
|
-
if (literal.length > 64)
|
|
439
|
-
return undefined;
|
|
440
|
-
if (/\s/.test(literal))
|
|
441
|
-
return undefined;
|
|
442
|
-
return literal;
|
|
443
|
-
}
|
|
444
|
-
buildRetryArgsFromHint(args, errorText, hint) {
|
|
445
|
-
const stringEntries = Object.entries(args).filter(([, value]) => typeof value === "string");
|
|
446
|
-
if (stringEntries.length === 1) {
|
|
447
|
-
const [field, current] = stringEntries[0];
|
|
448
|
-
if (current === hint)
|
|
449
|
-
return null;
|
|
450
|
-
return { ...args, [field]: hint };
|
|
451
|
-
}
|
|
452
|
-
if (stringEntries.length === 0) {
|
|
453
|
-
const missing = this.extractSingleMissingField(errorText);
|
|
454
|
-
if (!missing)
|
|
455
|
-
return null;
|
|
456
|
-
return { ...args, [missing]: hint };
|
|
457
|
-
}
|
|
458
|
-
return null;
|
|
459
|
-
}
|
|
460
|
-
extractSingleMissingField(errorText) {
|
|
461
|
-
const match = errorText.match(/following fields are missing:\s*\{([^}]+)\}/i);
|
|
462
|
-
const raw = match?.[1];
|
|
463
|
-
if (!raw)
|
|
464
|
-
return undefined;
|
|
465
|
-
const fields = raw
|
|
466
|
-
.split(",")
|
|
467
|
-
.map(part => part.trim().replace(/^['"]|['"]$/g, ""))
|
|
468
|
-
.filter(Boolean);
|
|
469
|
-
return fields.length === 1 ? fields[0] : undefined;
|
|
470
|
-
}
|
|
471
|
-
buildCombinedErrorText(metaError, metaData, additionalError) {
|
|
472
|
-
return [metaError, this.extractNestedMetaError(metaData), additionalError]
|
|
473
|
-
.map(v => String(v || "").trim())
|
|
474
|
-
.filter(Boolean)
|
|
475
|
-
.join("\n");
|
|
476
|
-
}
|
|
477
|
-
extractNestedMetaError(metaData) {
|
|
478
|
-
const results = metaData?.results || [];
|
|
479
|
-
const first = results[0];
|
|
480
|
-
return String(first?.error || "");
|
|
481
|
-
}
|
|
482
331
|
async resolveConnectedAccountForExecution(params) {
|
|
483
332
|
const toolkit = normalizeToolkitSlug(params.toolkit);
|
|
484
333
|
const { userId } = params;
|
|
485
334
|
const explicitId = params.connectedAccountId?.trim();
|
|
486
335
|
if (explicitId) {
|
|
487
336
|
try {
|
|
488
|
-
|
|
489
|
-
const retrieve = this.client?.client?.connectedAccounts?.retrieve;
|
|
490
|
-
if (typeof retrieve === "function") {
|
|
491
|
-
try {
|
|
492
|
-
rawAccount = await retrieve.call(this.client.client.connectedAccounts, explicitId);
|
|
493
|
-
}
|
|
494
|
-
catch {
|
|
495
|
-
// Best-effort: fall through to SDK get() which may omit user_id.
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
if (!rawAccount) {
|
|
499
|
-
rawAccount = await this.client.connectedAccounts.get(explicitId);
|
|
500
|
-
}
|
|
501
|
-
const account = rawAccount;
|
|
337
|
+
const account = await this.client.connectedAccounts.get(explicitId);
|
|
502
338
|
const accountToolkit = normalizeToolkitSlug(String(account?.toolkit?.slug || ""));
|
|
503
339
|
const accountStatus = String(account?.status || "").toUpperCase();
|
|
504
340
|
const accountUserId = String(account?.user_id || account?.userId || "").trim();
|
|
@@ -518,7 +354,18 @@ export class ComposioClient {
|
|
|
518
354
|
`but '${userId}' was requested. Use matching user_id or omit user_id when providing connected_account_id.`,
|
|
519
355
|
};
|
|
520
356
|
}
|
|
521
|
-
|
|
357
|
+
if (!accountUserId) {
|
|
358
|
+
// Fail closed: when owner is omitted by API, verify this account is ACTIVE in requested user scope.
|
|
359
|
+
const accountMatchesRequestedUser = await this.isConnectedAccountActiveForUser(toolkit, userId, explicitId);
|
|
360
|
+
if (!accountMatchesRequestedUser) {
|
|
361
|
+
return {
|
|
362
|
+
error: `Connected account '${explicitId}' ownership could not be verified for user_id '${userId}'. ` +
|
|
363
|
+
"Use a connected_account_id that belongs to this user_id and is ACTIVE.",
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return { connectedAccountId: explicitId, userId };
|
|
367
|
+
}
|
|
368
|
+
return { connectedAccountId: explicitId, userId: accountUserId };
|
|
522
369
|
}
|
|
523
370
|
catch (err) {
|
|
524
371
|
return {
|
|
@@ -540,6 +387,14 @@ export class ComposioClient {
|
|
|
540
387
|
"Please provide connected_account_id to choose one explicitly.",
|
|
541
388
|
};
|
|
542
389
|
}
|
|
390
|
+
async isConnectedAccountActiveForUser(toolkit, userId, connectedAccountId) {
|
|
391
|
+
const activeAccounts = await this.listConnectedAccounts({
|
|
392
|
+
toolkits: [toolkit],
|
|
393
|
+
userIds: [userId],
|
|
394
|
+
statuses: ["ACTIVE"],
|
|
395
|
+
});
|
|
396
|
+
return activeAccounts.some((account) => account.id === connectedAccountId);
|
|
397
|
+
}
|
|
543
398
|
/**
|
|
544
399
|
* Get connection status for toolkits using session.toolkits()
|
|
545
400
|
*/
|
|
@@ -708,11 +563,15 @@ export class ComposioClient {
|
|
|
708
563
|
return Array.from(userIds).sort();
|
|
709
564
|
}
|
|
710
565
|
async listConnectedAccountsRaw(options) {
|
|
566
|
+
const rawList = this.client?.client?.connectedAccounts?.list;
|
|
567
|
+
if (typeof rawList !== "function") {
|
|
568
|
+
throw new Error("Raw connected accounts list API unavailable");
|
|
569
|
+
}
|
|
711
570
|
const accounts = [];
|
|
712
571
|
let cursor;
|
|
713
572
|
const seenCursors = new Set();
|
|
714
573
|
do {
|
|
715
|
-
const response = await this.client.client.connectedAccounts
|
|
574
|
+
const response = await rawList.call(this.client.client.connectedAccounts, {
|
|
716
575
|
...(options?.toolkits && options.toolkits.length > 0 ? { toolkit_slugs: options.toolkits } : {}),
|
|
717
576
|
...(options?.userIds && options.userIds.length > 0 ? { user_ids: options.userIds } : {}),
|
|
718
577
|
...(options?.statuses && options.statuses.length > 0 ? { statuses: options.statuses } : {}),
|
|
@@ -731,19 +590,31 @@ export class ComposioClient {
|
|
|
731
590
|
accounts.push({
|
|
732
591
|
id: String(item.id || ""),
|
|
733
592
|
toolkit: toolkitSlug,
|
|
734
|
-
userId: typeof item.user_id === "string"
|
|
593
|
+
userId: typeof item.user_id === "string"
|
|
594
|
+
? item.user_id
|
|
595
|
+
: (typeof item.userId === "string" ? item.userId : undefined),
|
|
735
596
|
status: typeof item.status === "string" ? item.status : undefined,
|
|
736
597
|
authConfigId: typeof item.auth_config?.id === "string"
|
|
737
598
|
? item.auth_config.id
|
|
738
|
-
:
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
599
|
+
: (typeof item.authConfig?.id === "string"
|
|
600
|
+
? item.authConfig.id
|
|
601
|
+
: undefined),
|
|
602
|
+
isDisabled: typeof item.is_disabled === "boolean"
|
|
603
|
+
? item.is_disabled
|
|
604
|
+
: (typeof item.isDisabled === "boolean" ? item.isDisabled : undefined),
|
|
605
|
+
createdAt: typeof item.created_at === "string"
|
|
606
|
+
? item.created_at
|
|
607
|
+
: (typeof item.createdAt === "string" ? item.createdAt : undefined),
|
|
608
|
+
updatedAt: typeof item.updated_at === "string"
|
|
609
|
+
? item.updated_at
|
|
610
|
+
: (typeof item.updatedAt === "string" ? item.updatedAt : undefined),
|
|
742
611
|
});
|
|
743
612
|
}
|
|
744
613
|
cursor = Array.isArray(response)
|
|
745
614
|
? null
|
|
746
|
-
: (response?.next_cursor
|
|
615
|
+
: ((response?.next_cursor)
|
|
616
|
+
?? (response?.nextCursor)
|
|
617
|
+
?? null);
|
|
747
618
|
if (!cursor)
|
|
748
619
|
break;
|
|
749
620
|
if (seenCursors.has(cursor))
|
|
@@ -776,6 +647,9 @@ export class ComposioClient {
|
|
|
776
647
|
accounts.push({
|
|
777
648
|
id: String(item.id || ""),
|
|
778
649
|
toolkit: toolkitSlug,
|
|
650
|
+
userId: typeof item.userId === "string"
|
|
651
|
+
? item.userId
|
|
652
|
+
: (typeof item.user_id === "string" ? item.user_id : undefined),
|
|
779
653
|
status: typeof item.status === "string" ? item.status : undefined,
|
|
780
654
|
authConfigId: typeof item.authConfig?.id === "string"
|
|
781
655
|
? item.authConfig.id
|
|
@@ -811,7 +685,11 @@ export class ComposioClient {
|
|
|
811
685
|
try {
|
|
812
686
|
const session = await this.getSession(uid);
|
|
813
687
|
const result = await session.authorize(toolkitSlug);
|
|
814
|
-
|
|
688
|
+
const authUrl = String(result.redirectUrl || result.url || "").trim();
|
|
689
|
+
if (!authUrl) {
|
|
690
|
+
return { error: "Auth URL was not returned by provider" };
|
|
691
|
+
}
|
|
692
|
+
return { authUrl };
|
|
815
693
|
}
|
|
816
694
|
catch (err) {
|
|
817
695
|
return {
|
package/dist/config.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ import type { ComposioConfig } from "./types.js";
|
|
|
6
6
|
export declare const ComposioConfigSchema: z.ZodObject<{
|
|
7
7
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
8
8
|
apiKey: z.ZodOptional<z.ZodString>;
|
|
9
|
-
defaultUserId: z.ZodOptional<z.ZodString>;
|
|
10
9
|
allowedToolkits: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11
10
|
blockedToolkits: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
11
|
readOnlyMode: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -36,10 +35,6 @@ export declare const composioConfigUiHints: {
|
|
|
36
35
|
help: string;
|
|
37
36
|
sensitive: boolean;
|
|
38
37
|
};
|
|
39
|
-
defaultUserId: {
|
|
40
|
-
label: string;
|
|
41
|
-
help: string;
|
|
42
|
-
};
|
|
43
38
|
allowedToolkits: {
|
|
44
39
|
label: string;
|
|
45
40
|
help: string;
|
|
@@ -86,10 +81,6 @@ export declare const composioPluginConfigSchema: {
|
|
|
86
81
|
help: string;
|
|
87
82
|
sensitive: boolean;
|
|
88
83
|
};
|
|
89
|
-
defaultUserId: {
|
|
90
|
-
label: string;
|
|
91
|
-
help: string;
|
|
92
|
-
};
|
|
93
84
|
allowedToolkits: {
|
|
94
85
|
label: string;
|
|
95
86
|
help: string;
|
package/dist/config.js
CHANGED
|
@@ -6,7 +6,6 @@ import { LEGACY_ENTRY_FLAT_CONFIG_KEYS, LEGACY_SHAPE_ERROR, SESSION_TAGS, isReco
|
|
|
6
6
|
export const ComposioConfigSchema = z.object({
|
|
7
7
|
enabled: z.boolean().default(true),
|
|
8
8
|
apiKey: z.string().optional(),
|
|
9
|
-
defaultUserId: z.string().optional(),
|
|
10
9
|
allowedToolkits: z.array(z.string()).optional(),
|
|
11
10
|
blockedToolkits: z.array(z.string()).optional(),
|
|
12
11
|
readOnlyMode: z.boolean().default(false),
|
|
@@ -35,7 +34,6 @@ export function parseComposioConfig(value) {
|
|
|
35
34
|
return ComposioConfigSchema.parse({
|
|
36
35
|
enabled,
|
|
37
36
|
apiKey,
|
|
38
|
-
defaultUserId: typeof source.defaultUserId === "string" ? source.defaultUserId : undefined,
|
|
39
37
|
allowedToolkits: normalizeToolkitList(Array.isArray(source.allowedToolkits) ? source.allowedToolkits : undefined),
|
|
40
38
|
blockedToolkits: normalizeToolkitList(Array.isArray(source.blockedToolkits) ? source.blockedToolkits : undefined),
|
|
41
39
|
readOnlyMode,
|
|
@@ -57,10 +55,6 @@ export const composioConfigUiHints = {
|
|
|
57
55
|
help: "Composio API key from platform.composio.dev/settings",
|
|
58
56
|
sensitive: true,
|
|
59
57
|
},
|
|
60
|
-
defaultUserId: {
|
|
61
|
-
label: "Default User ID",
|
|
62
|
-
help: "Default user ID for session scoping (optional)",
|
|
63
|
-
},
|
|
64
58
|
allowedToolkits: {
|
|
65
59
|
label: "Allowed Toolkits",
|
|
66
60
|
help: "Restrict to specific toolkits (e.g., github, gmail)",
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import type { ComposioClient } from "../client.js";
|
|
2
2
|
import type { ComposioConfig } from "../types.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
action: import("@sinclair/typebox").
|
|
3
|
+
export declare const ComposioManageConnectionsToolSchema: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
4
|
+
action: import("@sinclair/typebox").TLiteral<"list">;
|
|
5
|
+
user_id: import("@sinclair/typebox").TString;
|
|
6
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
7
|
+
action: import("@sinclair/typebox").TLiteral<"create">;
|
|
8
|
+
toolkit: import("@sinclair/typebox").TString;
|
|
9
|
+
user_id: import("@sinclair/typebox").TString;
|
|
10
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
11
|
+
action: import("@sinclair/typebox").TLiteral<"status">;
|
|
12
|
+
toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
13
|
+
toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
14
|
+
user_id: import("@sinclair/typebox").TString;
|
|
15
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
16
|
+
action: import("@sinclair/typebox").TLiteral<"accounts">;
|
|
8
17
|
toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
9
18
|
toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
10
19
|
user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
11
20
|
statuses: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
12
|
-
}>;
|
|
21
|
+
}>]>;
|
|
13
22
|
/**
|
|
14
23
|
* Create the composio_manage_connections tool
|
|
15
24
|
*/
|
|
@@ -17,14 +26,35 @@ export declare function createComposioConnectionsTool(client: ComposioClient, _c
|
|
|
17
26
|
name: string;
|
|
18
27
|
label: string;
|
|
19
28
|
description: string;
|
|
20
|
-
parameters: import("@sinclair/typebox").TObject<{
|
|
21
|
-
action: import("@sinclair/typebox").
|
|
29
|
+
parameters: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
30
|
+
action: import("@sinclair/typebox").TLiteral<"list">;
|
|
31
|
+
user_id: import("@sinclair/typebox").TString;
|
|
32
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
33
|
+
action: import("@sinclair/typebox").TLiteral<"create">;
|
|
34
|
+
toolkit: import("@sinclair/typebox").TString;
|
|
35
|
+
user_id: import("@sinclair/typebox").TString;
|
|
36
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
37
|
+
action: import("@sinclair/typebox").TLiteral<"status">;
|
|
38
|
+
toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
39
|
+
toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
40
|
+
user_id: import("@sinclair/typebox").TString;
|
|
41
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
42
|
+
action: import("@sinclair/typebox").TLiteral<"accounts">;
|
|
22
43
|
toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
23
44
|
toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
24
45
|
user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
25
46
|
statuses: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
26
|
-
}>;
|
|
47
|
+
}>]>;
|
|
27
48
|
execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
|
|
49
|
+
content: {
|
|
50
|
+
type: string;
|
|
51
|
+
text: string;
|
|
52
|
+
}[];
|
|
53
|
+
details: {
|
|
54
|
+
action: string;
|
|
55
|
+
error: string;
|
|
56
|
+
};
|
|
57
|
+
} | {
|
|
28
58
|
content: {
|
|
29
59
|
type: string;
|
|
30
60
|
text: string;
|
|
@@ -93,22 +123,12 @@ export declare function createComposioConnectionsTool(client: ComposioClient, _c
|
|
|
93
123
|
message: string;
|
|
94
124
|
}[] | undefined;
|
|
95
125
|
action: string;
|
|
96
|
-
checked_user_id: string
|
|
97
|
-
user_id_explicit: boolean;
|
|
126
|
+
checked_user_id: string;
|
|
98
127
|
count: number;
|
|
99
128
|
connections: {
|
|
100
129
|
toolkit: string;
|
|
101
130
|
connected: boolean;
|
|
102
131
|
}[];
|
|
103
132
|
};
|
|
104
|
-
} | {
|
|
105
|
-
content: {
|
|
106
|
-
type: string;
|
|
107
|
-
text: string;
|
|
108
|
-
}[];
|
|
109
|
-
details: {
|
|
110
|
-
action: string;
|
|
111
|
-
error: string;
|
|
112
|
-
};
|
|
113
133
|
}>;
|
|
114
134
|
};
|
|
@@ -2,23 +2,48 @@ import { Type } from "@sinclair/typebox";
|
|
|
2
2
|
/**
|
|
3
3
|
* Tool parameters for composio_manage_connections
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
toolkit: Type.Optional(Type.String({
|
|
10
|
-
description: "Toolkit name for 'status' or 'create' actions (e.g., 'github', 'gmail')",
|
|
11
|
-
})),
|
|
12
|
-
toolkits: Type.Optional(Type.Array(Type.String(), {
|
|
13
|
-
description: "Multiple toolkits to check status for",
|
|
14
|
-
})),
|
|
15
|
-
user_id: Type.Optional(Type.String({
|
|
16
|
-
description: "User ID for session scoping. Strongly recommended to avoid checking the wrong scope.",
|
|
17
|
-
})),
|
|
18
|
-
statuses: Type.Optional(Type.Array(Type.String(), {
|
|
19
|
-
description: "Optional connection statuses filter for 'accounts' (e.g., ['ACTIVE'])",
|
|
20
|
-
})),
|
|
5
|
+
const ActionDescription = "Action to perform: 'status' to check connections, 'create' to initiate auth, " +
|
|
6
|
+
"'list' to list toolkits, 'accounts' to inspect connected accounts";
|
|
7
|
+
const UserIdRequiredField = Type.String({
|
|
8
|
+
description: "Required user ID for session scoping.",
|
|
21
9
|
});
|
|
10
|
+
const UserIdOptionalField = Type.Optional(Type.String({
|
|
11
|
+
description: "Optional user ID filter for accounts lookup.",
|
|
12
|
+
}));
|
|
13
|
+
const ToolkitField = Type.Optional(Type.String({
|
|
14
|
+
description: "Toolkit name (e.g., 'github', 'gmail')",
|
|
15
|
+
}));
|
|
16
|
+
const ToolkitsField = Type.Optional(Type.Array(Type.String(), {
|
|
17
|
+
description: "Multiple toolkits to check status for",
|
|
18
|
+
}));
|
|
19
|
+
export const ComposioManageConnectionsToolSchema = Type.Union([
|
|
20
|
+
Type.Object({
|
|
21
|
+
action: Type.Literal("list", { description: ActionDescription }),
|
|
22
|
+
user_id: UserIdRequiredField,
|
|
23
|
+
}),
|
|
24
|
+
Type.Object({
|
|
25
|
+
action: Type.Literal("create", { description: ActionDescription }),
|
|
26
|
+
toolkit: Type.String({
|
|
27
|
+
description: "Toolkit name for 'create' action (e.g., 'github', 'gmail')",
|
|
28
|
+
}),
|
|
29
|
+
user_id: UserIdRequiredField,
|
|
30
|
+
}),
|
|
31
|
+
Type.Object({
|
|
32
|
+
action: Type.Literal("status", { description: ActionDescription }),
|
|
33
|
+
toolkit: ToolkitField,
|
|
34
|
+
toolkits: ToolkitsField,
|
|
35
|
+
user_id: UserIdRequiredField,
|
|
36
|
+
}),
|
|
37
|
+
Type.Object({
|
|
38
|
+
action: Type.Literal("accounts", { description: ActionDescription }),
|
|
39
|
+
toolkit: ToolkitField,
|
|
40
|
+
toolkits: ToolkitsField,
|
|
41
|
+
user_id: UserIdOptionalField,
|
|
42
|
+
statuses: Type.Optional(Type.Array(Type.String(), {
|
|
43
|
+
description: "Optional connection statuses filter for 'accounts' (e.g., ['ACTIVE'])",
|
|
44
|
+
})),
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
22
47
|
/**
|
|
23
48
|
* Create the composio_manage_connections tool
|
|
24
49
|
*/
|
|
@@ -33,8 +58,18 @@ export function createComposioConnectionsTool(client, _config) {
|
|
|
33
58
|
parameters: ComposioManageConnectionsToolSchema,
|
|
34
59
|
async execute(_toolCallId, params) {
|
|
35
60
|
const action = String(params.action || "status");
|
|
36
|
-
const userId = typeof params.user_id === "string" ? params.user_id :
|
|
37
|
-
const
|
|
61
|
+
const userId = typeof params.user_id === "string" ? params.user_id.trim() : "";
|
|
62
|
+
const requiresUserId = action === "list" || action === "status" || action === "create";
|
|
63
|
+
if (requiresUserId && !userId) {
|
|
64
|
+
const errorResponse = {
|
|
65
|
+
action,
|
|
66
|
+
error: "user_id is required for this action",
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: JSON.stringify(errorResponse, null, 2) }],
|
|
70
|
+
details: errorResponse,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
38
73
|
try {
|
|
39
74
|
switch (action) {
|
|
40
75
|
case "list": {
|
|
@@ -122,24 +157,21 @@ export function createComposioConnectionsTool(client, _config) {
|
|
|
122
157
|
const statuses = await client.getConnectionStatus(toolkitsToCheck, userId);
|
|
123
158
|
const disconnectedToolkits = statuses.filter((s) => !s.connected).map((s) => s.toolkit);
|
|
124
159
|
const hints = [];
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
});
|
|
137
|
-
}
|
|
160
|
+
for (const toolkit of disconnectedToolkits) {
|
|
161
|
+
const activeUserIds = await client.findActiveUserIdsForToolkit(toolkit);
|
|
162
|
+
const otherUserIds = activeUserIds.filter((uid) => uid !== userId);
|
|
163
|
+
if (otherUserIds.length === 0)
|
|
164
|
+
continue;
|
|
165
|
+
hints.push({
|
|
166
|
+
toolkit,
|
|
167
|
+
connected_user_ids: otherUserIds,
|
|
168
|
+
message: `'${toolkit}' has ACTIVE accounts under other user_id(s): ${otherUserIds.join(", ")}. ` +
|
|
169
|
+
"Use a matching user_id to check that scope.",
|
|
170
|
+
});
|
|
138
171
|
}
|
|
139
172
|
const response = {
|
|
140
173
|
action: "status",
|
|
141
174
|
checked_user_id: statuses[0]?.userId,
|
|
142
|
-
user_id_explicit: userIdWasExplicit,
|
|
143
175
|
count: statuses.length,
|
|
144
176
|
connections: statuses.map((s) => ({
|
|
145
177
|
toolkit: s.toolkit,
|
package/dist/tools/execute.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { ComposioConfig } from "../types.js";
|
|
|
6
6
|
export declare const ComposioExecuteToolSchema: import("@sinclair/typebox").TObject<{
|
|
7
7
|
tool_slug: import("@sinclair/typebox").TString;
|
|
8
8
|
arguments: import("@sinclair/typebox").TUnknown;
|
|
9
|
-
user_id: import("@sinclair/typebox").
|
|
9
|
+
user_id: import("@sinclair/typebox").TString;
|
|
10
10
|
connected_account_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
11
11
|
}>;
|
|
12
12
|
/**
|
|
@@ -19,7 +19,7 @@ export declare function createComposioExecuteTool(client: ComposioClient, _confi
|
|
|
19
19
|
parameters: import("@sinclair/typebox").TObject<{
|
|
20
20
|
tool_slug: import("@sinclair/typebox").TString;
|
|
21
21
|
arguments: import("@sinclair/typebox").TUnknown;
|
|
22
|
-
user_id: import("@sinclair/typebox").
|
|
22
|
+
user_id: import("@sinclair/typebox").TString;
|
|
23
23
|
connected_account_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
24
24
|
}>;
|
|
25
25
|
execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
|
package/dist/tools/execute.js
CHANGED
|
@@ -9,9 +9,9 @@ export const ComposioExecuteToolSchema = Type.Object({
|
|
|
9
9
|
arguments: Type.Unknown({
|
|
10
10
|
description: "Tool arguments matching the tool's parameter schema",
|
|
11
11
|
}),
|
|
12
|
-
user_id: Type.
|
|
13
|
-
description: "
|
|
14
|
-
})
|
|
12
|
+
user_id: Type.String({
|
|
13
|
+
description: "Required user ID for session scoping.",
|
|
14
|
+
}),
|
|
15
15
|
connected_account_id: Type.Optional(Type.String({
|
|
16
16
|
description: "Optional connected account ID to pin execution to a specific account when multiple are connected",
|
|
17
17
|
})),
|
|
@@ -45,7 +45,13 @@ export function createComposioExecuteTool(client, _config) {
|
|
|
45
45
|
const args = rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs)
|
|
46
46
|
? rawArgs
|
|
47
47
|
: {};
|
|
48
|
-
const userId = typeof params.user_id === "string" ? params.user_id :
|
|
48
|
+
const userId = typeof params.user_id === "string" ? params.user_id.trim() : "";
|
|
49
|
+
if (!userId) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text", text: JSON.stringify({ error: "user_id is required" }, null, 2) }],
|
|
52
|
+
details: { error: "user_id is required" },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
49
55
|
const connectedAccountId = typeof params.connected_account_id === "string" ? params.connected_account_id : undefined;
|
|
50
56
|
try {
|
|
51
57
|
const result = await client.executeTool(toolSlug, args, userId, connectedAccountId);
|
package/dist/tools/search.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare const ComposioSearchToolSchema: import("@sinclair/typebox").TObje
|
|
|
7
7
|
query: import("@sinclair/typebox").TString;
|
|
8
8
|
toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
9
9
|
limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
10
|
-
user_id: import("@sinclair/typebox").
|
|
10
|
+
user_id: import("@sinclair/typebox").TString;
|
|
11
11
|
}>;
|
|
12
12
|
/**
|
|
13
13
|
* Create the composio_search_tools tool
|
|
@@ -20,7 +20,7 @@ export declare function createComposioSearchTool(client: ComposioClient, _config
|
|
|
20
20
|
query: import("@sinclair/typebox").TString;
|
|
21
21
|
toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
22
22
|
limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
23
|
-
user_id: import("@sinclair/typebox").
|
|
23
|
+
user_id: import("@sinclair/typebox").TString;
|
|
24
24
|
}>;
|
|
25
25
|
execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
|
|
26
26
|
content: {
|
package/dist/tools/search.js
CHANGED
|
@@ -12,9 +12,9 @@ export const ComposioSearchToolSchema = Type.Object({
|
|
|
12
12
|
limit: Type.Optional(Type.Number({
|
|
13
13
|
description: "Maximum number of results to return (default: 10, max: 50)",
|
|
14
14
|
})),
|
|
15
|
-
user_id: Type.
|
|
16
|
-
description: "
|
|
17
|
-
})
|
|
15
|
+
user_id: Type.String({
|
|
16
|
+
description: "Required user ID for session scoping.",
|
|
17
|
+
}),
|
|
18
18
|
});
|
|
19
19
|
/**
|
|
20
20
|
* Create the composio_search_tools tool
|
|
@@ -39,7 +39,13 @@ export function createComposioSearchTool(client, _config) {
|
|
|
39
39
|
? params.toolkits.filter((t) => typeof t === "string")
|
|
40
40
|
: undefined;
|
|
41
41
|
const limit = Math.min(typeof params.limit === "number" && params.limit > 0 ? params.limit : 10, 50);
|
|
42
|
-
const userId = typeof params.user_id === "string" ? params.user_id :
|
|
42
|
+
const userId = typeof params.user_id === "string" ? params.user_id.trim() : "";
|
|
43
|
+
if (!userId) {
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: JSON.stringify({ error: "user_id is required" }, null, 2) }],
|
|
46
|
+
details: { error: "user_id is required" },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
43
49
|
try {
|
|
44
50
|
const results = await client.searchTools(query, { toolkits, limit, userId });
|
|
45
51
|
const response = {
|
package/dist/types.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ export type ComposioSessionTag = "readOnlyHint" | "destructiveHint" | "idempoten
|
|
|
5
5
|
export interface ComposioConfig {
|
|
6
6
|
enabled: boolean;
|
|
7
7
|
apiKey?: string;
|
|
8
|
-
defaultUserId?: string;
|
|
9
8
|
allowedToolkits?: string[];
|
|
10
9
|
blockedToolkits?: string[];
|
|
11
10
|
readOnlyMode?: boolean;
|
|
@@ -28,7 +27,7 @@ export interface ToolExecutionResult {
|
|
|
28
27
|
export interface ConnectionStatus {
|
|
29
28
|
toolkit: string;
|
|
30
29
|
connected: boolean;
|
|
31
|
-
userId
|
|
30
|
+
userId: string;
|
|
32
31
|
authUrl?: string;
|
|
33
32
|
}
|
|
34
33
|
export interface ConnectedAccountSummary {
|
package/openclaw.plugin.json
CHANGED
|
@@ -14,10 +14,6 @@
|
|
|
14
14
|
"type": "string",
|
|
15
15
|
"description": "Composio API key (or set COMPOSIO_API_KEY env var)"
|
|
16
16
|
},
|
|
17
|
-
"defaultUserId": {
|
|
18
|
-
"type": "string",
|
|
19
|
-
"description": "Default user ID for session scoping"
|
|
20
|
-
},
|
|
21
17
|
"allowedToolkits": {
|
|
22
18
|
"type": "array",
|
|
23
19
|
"items": { "type": "string" },
|
|
@@ -59,10 +55,6 @@
|
|
|
59
55
|
"help": "Composio API key from platform.composio.dev/settings",
|
|
60
56
|
"sensitive": true
|
|
61
57
|
},
|
|
62
|
-
"defaultUserId": {
|
|
63
|
-
"label": "Default User ID",
|
|
64
|
-
"help": "Default user ID for session scoping (optional)"
|
|
65
|
-
},
|
|
66
58
|
"allowedToolkits": {
|
|
67
59
|
"label": "Allowed Toolkits",
|
|
68
60
|
"help": "Restrict to specific toolkits (e.g., github, gmail)",
|