@customclaw/composio 0.0.6 → 0.0.7
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 +22 -5
- package/dist/cli.js +53 -3
- package/dist/client.d.ts +34 -2
- package/dist/client.js +489 -46
- package/dist/client.test.js +295 -15
- package/dist/config.js +15 -2
- package/dist/tools/connections.d.ts +20 -2
- package/dist/tools/connections.js +39 -28
- package/dist/tools/execute.d.ts +2 -0
- package/dist/tools/execute.js +5 -1
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
package/dist/client.test.js
CHANGED
|
@@ -29,9 +29,19 @@ vi.mock("@composio/core", () => ({
|
|
|
29
29
|
data: { results: [{ tool_slug: "GMAIL_FETCH_EMAILS", index: 0, response: { successful: true, data: { messages: [] } } }] },
|
|
30
30
|
}),
|
|
31
31
|
},
|
|
32
|
+
connectedAccounts: {
|
|
33
|
+
list: vi.fn().mockResolvedValue({ items: [], next_cursor: null }),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
tools: {
|
|
37
|
+
execute: vi.fn().mockResolvedValue({
|
|
38
|
+
successful: true,
|
|
39
|
+
data: { direct: true },
|
|
40
|
+
}),
|
|
32
41
|
},
|
|
33
42
|
connectedAccounts: {
|
|
34
43
|
list: vi.fn().mockResolvedValue({ items: [] }),
|
|
44
|
+
get: vi.fn().mockResolvedValue({ toolkit: { slug: "gmail" }, status: "ACTIVE" }),
|
|
35
45
|
delete: vi.fn().mockResolvedValue({}),
|
|
36
46
|
},
|
|
37
47
|
})),
|
|
@@ -43,6 +53,11 @@ function makeClient(overrides) {
|
|
|
43
53
|
...overrides,
|
|
44
54
|
});
|
|
45
55
|
}
|
|
56
|
+
async function getLatestComposioInstance() {
|
|
57
|
+
const { Composio } = await import("@composio/core");
|
|
58
|
+
const mockResults = Composio.mock.results;
|
|
59
|
+
return mockResults[mockResults.length - 1].value;
|
|
60
|
+
}
|
|
46
61
|
describe("config parsing", () => {
|
|
47
62
|
it("reads apiKey from config object", () => {
|
|
48
63
|
const config = parseComposioConfig({ config: { apiKey: "from-config" } });
|
|
@@ -62,6 +77,19 @@ describe("config parsing", () => {
|
|
|
62
77
|
const config = parseComposioConfig({});
|
|
63
78
|
expect(config.enabled).toBe(true);
|
|
64
79
|
});
|
|
80
|
+
it("reads defaultUserId and toolkit filters from nested config object", () => {
|
|
81
|
+
const config = parseComposioConfig({
|
|
82
|
+
config: {
|
|
83
|
+
apiKey: "from-config",
|
|
84
|
+
defaultUserId: "app-user-123",
|
|
85
|
+
allowedToolkits: ["gmail", "sentry"],
|
|
86
|
+
blockedToolkits: ["github"],
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
expect(config.defaultUserId).toBe("app-user-123");
|
|
90
|
+
expect(config.allowedToolkits).toEqual(["gmail", "sentry"]);
|
|
91
|
+
expect(config.blockedToolkits).toEqual(["github"]);
|
|
92
|
+
});
|
|
65
93
|
});
|
|
66
94
|
describe("toolkit filtering", () => {
|
|
67
95
|
it("allows all toolkits when no filter set", async () => {
|
|
@@ -99,6 +127,16 @@ describe("connection status", () => {
|
|
|
99
127
|
const statuses = await client.getConnectionStatus(["sentry"]);
|
|
100
128
|
expect(statuses[0].connected).toBe(false);
|
|
101
129
|
});
|
|
130
|
+
it("reports toolkit as connected when active connected account exists", async () => {
|
|
131
|
+
const client = makeClient();
|
|
132
|
+
const instance = await getLatestComposioInstance();
|
|
133
|
+
instance.connectedAccounts.list.mockResolvedValueOnce({
|
|
134
|
+
items: [{ toolkit: { slug: "affinity" }, status: "ACTIVE" }],
|
|
135
|
+
nextCursor: null,
|
|
136
|
+
});
|
|
137
|
+
const statuses = await client.getConnectionStatus(["affinity"]);
|
|
138
|
+
expect(statuses[0].connected).toBe(true);
|
|
139
|
+
});
|
|
102
140
|
it("returns only connected toolkits when no filter", async () => {
|
|
103
141
|
const client = makeClient();
|
|
104
142
|
const statuses = await client.getConnectionStatus();
|
|
@@ -119,6 +157,123 @@ describe("execute tool", () => {
|
|
|
119
157
|
expect(result.success).toBe(false);
|
|
120
158
|
expect(result.error).toContain("not allowed");
|
|
121
159
|
});
|
|
160
|
+
it("pins execution to explicit connected_account_id", async () => {
|
|
161
|
+
const client = makeClient();
|
|
162
|
+
const instance = await getLatestComposioInstance();
|
|
163
|
+
instance.connectedAccounts.get.mockResolvedValueOnce({
|
|
164
|
+
toolkit: { slug: "gmail" },
|
|
165
|
+
status: "ACTIVE",
|
|
166
|
+
});
|
|
167
|
+
const result = await client.executeTool("GMAIL_FETCH_EMAILS", {}, "default", "ca_explicit");
|
|
168
|
+
expect(result.success).toBe(true);
|
|
169
|
+
expect(instance.toolRouter.create).toHaveBeenCalledWith("default", {
|
|
170
|
+
connectedAccounts: { gmail: "ca_explicit" },
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
it("auto-pins execution when one active account exists", async () => {
|
|
174
|
+
const client = makeClient();
|
|
175
|
+
const instance = await getLatestComposioInstance();
|
|
176
|
+
instance.client.connectedAccounts.list.mockResolvedValueOnce({
|
|
177
|
+
items: [
|
|
178
|
+
{ id: "ca_single", user_id: "default", status: "ACTIVE", toolkit: { slug: "gmail" } },
|
|
179
|
+
],
|
|
180
|
+
next_cursor: null,
|
|
181
|
+
});
|
|
182
|
+
const result = await client.executeTool("GMAIL_FETCH_EMAILS", {}, "default");
|
|
183
|
+
expect(result.success).toBe(true);
|
|
184
|
+
expect(instance.toolRouter.create).toHaveBeenCalledWith("default", {
|
|
185
|
+
connectedAccounts: { gmail: "ca_single" },
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
it("fails with clear error when multiple active accounts exist and none selected", async () => {
|
|
189
|
+
const client = makeClient();
|
|
190
|
+
const instance = await getLatestComposioInstance();
|
|
191
|
+
instance.client.connectedAccounts.list.mockResolvedValueOnce({
|
|
192
|
+
items: [
|
|
193
|
+
{ id: "ca_1", user_id: "default", status: "ACTIVE", toolkit: { slug: "gmail" } },
|
|
194
|
+
{ id: "ca_2", user_id: "default", status: "ACTIVE", toolkit: { slug: "gmail" } },
|
|
195
|
+
],
|
|
196
|
+
next_cursor: null,
|
|
197
|
+
});
|
|
198
|
+
const result = await client.executeTool("GMAIL_FETCH_EMAILS", {}, "default");
|
|
199
|
+
expect(result.success).toBe(false);
|
|
200
|
+
expect(result.error).toContain("Multiple ACTIVE 'gmail' accounts");
|
|
201
|
+
expect(result.error).toContain("ca_1");
|
|
202
|
+
expect(result.error).toContain("ca_2");
|
|
203
|
+
});
|
|
204
|
+
it("falls back to direct execute when meta-tool resolves entity as default for non-default user", async () => {
|
|
205
|
+
const client = makeClient();
|
|
206
|
+
const instance = await getLatestComposioInstance();
|
|
207
|
+
instance.client.connectedAccounts.list.mockResolvedValueOnce({
|
|
208
|
+
items: [
|
|
209
|
+
{ id: "ca_sentry", user_id: "pg-user", status: "ACTIVE", toolkit: { slug: "sentry" } },
|
|
210
|
+
],
|
|
211
|
+
next_cursor: null,
|
|
212
|
+
});
|
|
213
|
+
instance.client.tools.execute.mockResolvedValueOnce({
|
|
214
|
+
successful: false,
|
|
215
|
+
error: "1 out of 1 tools failed",
|
|
216
|
+
data: {
|
|
217
|
+
results: [{ error: "Error: No connected account found for entity ID default for toolkit sentry" }],
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
instance.tools.execute.mockResolvedValueOnce({
|
|
221
|
+
successful: true,
|
|
222
|
+
data: { ok: true },
|
|
223
|
+
});
|
|
224
|
+
const result = await client.executeTool("SENTRY_GET_ORGANIZATION_DETAILS", {}, "pg-user");
|
|
225
|
+
expect(result.success).toBe(true);
|
|
226
|
+
expect(result.data).toEqual({ ok: true });
|
|
227
|
+
expect(instance.tools.execute).toHaveBeenCalledWith("SENTRY_GET_ORGANIZATION_DETAILS", {
|
|
228
|
+
userId: "pg-user",
|
|
229
|
+
connectedAccountId: "ca_sentry",
|
|
230
|
+
arguments: {},
|
|
231
|
+
dangerouslySkipVersionCheck: true,
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
it("retries once with server-hinted identifier value", async () => {
|
|
235
|
+
const client = makeClient();
|
|
236
|
+
const instance = await getLatestComposioInstance();
|
|
237
|
+
instance.client.connectedAccounts.list.mockResolvedValueOnce({
|
|
238
|
+
items: [
|
|
239
|
+
{ id: "ca_posthog", user_id: "pg-user", status: "ACTIVE", toolkit: { slug: "posthog" } },
|
|
240
|
+
],
|
|
241
|
+
next_cursor: null,
|
|
242
|
+
});
|
|
243
|
+
instance.client.tools.execute.mockResolvedValueOnce({
|
|
244
|
+
successful: true,
|
|
245
|
+
data: {
|
|
246
|
+
results: [
|
|
247
|
+
{
|
|
248
|
+
tool_slug: "POSTHOG_RETRIEVE_USER_PROFILE_AND_TEAM_DETAILS",
|
|
249
|
+
index: 0,
|
|
250
|
+
response: {
|
|
251
|
+
successful: false,
|
|
252
|
+
error: JSON.stringify({
|
|
253
|
+
type: "authentication_error",
|
|
254
|
+
code: "permission_denied",
|
|
255
|
+
detail: "As a non-staff user you're only allowed to access the `@me` user instance.",
|
|
256
|
+
attr: null,
|
|
257
|
+
}),
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
instance.tools.execute.mockResolvedValueOnce({
|
|
264
|
+
successful: true,
|
|
265
|
+
data: { ok: true, retried: true },
|
|
266
|
+
});
|
|
267
|
+
const result = await client.executeTool("POSTHOG_RETRIEVE_USER_PROFILE_AND_TEAM_DETAILS", { uuid: "some-other-uuid" }, "pg-user");
|
|
268
|
+
expect(result.success).toBe(true);
|
|
269
|
+
expect(result.data).toEqual({ ok: true, retried: true });
|
|
270
|
+
expect(instance.tools.execute).toHaveBeenCalledWith("POSTHOG_RETRIEVE_USER_PROFILE_AND_TEAM_DETAILS", {
|
|
271
|
+
userId: "pg-user",
|
|
272
|
+
connectedAccountId: "ca_posthog",
|
|
273
|
+
arguments: { uuid: "@me" },
|
|
274
|
+
dangerouslySkipVersionCheck: true,
|
|
275
|
+
});
|
|
276
|
+
});
|
|
122
277
|
});
|
|
123
278
|
describe("create connection", () => {
|
|
124
279
|
it("returns auth URL", async () => {
|
|
@@ -135,6 +290,111 @@ describe("create connection", () => {
|
|
|
135
290
|
expect("error" in result).toBe(true);
|
|
136
291
|
});
|
|
137
292
|
});
|
|
293
|
+
describe("disconnect toolkit", () => {
|
|
294
|
+
it("disconnects single active account", async () => {
|
|
295
|
+
const client = makeClient();
|
|
296
|
+
const instance = await getLatestComposioInstance();
|
|
297
|
+
instance.client.connectedAccounts.list.mockResolvedValueOnce({
|
|
298
|
+
items: [
|
|
299
|
+
{ id: "ca_gmail", user_id: "default", status: "ACTIVE", toolkit: { slug: "gmail" } },
|
|
300
|
+
],
|
|
301
|
+
next_cursor: null,
|
|
302
|
+
});
|
|
303
|
+
const result = await client.disconnectToolkit("gmail", "default");
|
|
304
|
+
expect(result.success).toBe(true);
|
|
305
|
+
expect(instance.connectedAccounts.delete).toHaveBeenCalledWith({ connectedAccountId: "ca_gmail" });
|
|
306
|
+
});
|
|
307
|
+
it("fails safely when multiple active accounts exist", async () => {
|
|
308
|
+
const client = makeClient();
|
|
309
|
+
const instance = await getLatestComposioInstance();
|
|
310
|
+
instance.client.connectedAccounts.list.mockResolvedValueOnce({
|
|
311
|
+
items: [
|
|
312
|
+
{ id: "ca_1", user_id: "default", status: "ACTIVE", toolkit: { slug: "gmail" } },
|
|
313
|
+
{ id: "ca_2", user_id: "default", status: "ACTIVE", toolkit: { slug: "gmail" } },
|
|
314
|
+
],
|
|
315
|
+
next_cursor: null,
|
|
316
|
+
});
|
|
317
|
+
const result = await client.disconnectToolkit("gmail", "default");
|
|
318
|
+
expect(result.success).toBe(false);
|
|
319
|
+
expect(result.error).toContain("Multiple ACTIVE 'gmail' accounts");
|
|
320
|
+
expect(instance.connectedAccounts.delete).not.toHaveBeenCalled();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
describe("connected accounts discovery", () => {
|
|
324
|
+
it("lists connected accounts with user IDs from raw API", async () => {
|
|
325
|
+
const client = makeClient();
|
|
326
|
+
const instance = await getLatestComposioInstance();
|
|
327
|
+
instance.client.connectedAccounts.list.mockResolvedValueOnce({
|
|
328
|
+
items: [
|
|
329
|
+
{
|
|
330
|
+
id: "ca_1",
|
|
331
|
+
user_id: "user-a",
|
|
332
|
+
status: "ACTIVE",
|
|
333
|
+
toolkit: { slug: "sentry" },
|
|
334
|
+
auth_config: { id: "ac_1" },
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
next_cursor: null,
|
|
338
|
+
});
|
|
339
|
+
const accounts = await client.listConnectedAccounts({ toolkits: ["sentry"], statuses: ["ACTIVE"] });
|
|
340
|
+
expect(instance.client.connectedAccounts.list).toHaveBeenCalledWith({
|
|
341
|
+
toolkit_slugs: ["sentry"],
|
|
342
|
+
statuses: ["ACTIVE"],
|
|
343
|
+
limit: 100,
|
|
344
|
+
});
|
|
345
|
+
expect(accounts).toEqual([
|
|
346
|
+
{
|
|
347
|
+
id: "ca_1",
|
|
348
|
+
toolkit: "sentry",
|
|
349
|
+
userId: "user-a",
|
|
350
|
+
status: "ACTIVE",
|
|
351
|
+
authConfigId: "ac_1",
|
|
352
|
+
isDisabled: undefined,
|
|
353
|
+
createdAt: undefined,
|
|
354
|
+
updatedAt: undefined,
|
|
355
|
+
},
|
|
356
|
+
]);
|
|
357
|
+
});
|
|
358
|
+
it("falls back to SDK-normalized account list when raw API errors", async () => {
|
|
359
|
+
const client = makeClient();
|
|
360
|
+
const instance = await getLatestComposioInstance();
|
|
361
|
+
instance.client.connectedAccounts.list.mockRejectedValueOnce(new Error("raw unavailable"));
|
|
362
|
+
instance.connectedAccounts.list.mockResolvedValueOnce({
|
|
363
|
+
items: [
|
|
364
|
+
{
|
|
365
|
+
id: "ca_2",
|
|
366
|
+
status: "ACTIVE",
|
|
367
|
+
toolkit: { slug: "gmail" },
|
|
368
|
+
authConfig: { id: "ac_2" },
|
|
369
|
+
isDisabled: false,
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
nextCursor: null,
|
|
373
|
+
});
|
|
374
|
+
const accounts = await client.listConnectedAccounts({ toolkits: ["gmail"], statuses: ["ACTIVE"] });
|
|
375
|
+
expect(accounts[0]).toMatchObject({
|
|
376
|
+
id: "ca_2",
|
|
377
|
+
toolkit: "gmail",
|
|
378
|
+
status: "ACTIVE",
|
|
379
|
+
authConfigId: "ac_2",
|
|
380
|
+
isDisabled: false,
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
it("finds active user IDs for toolkit", async () => {
|
|
384
|
+
const client = makeClient();
|
|
385
|
+
const instance = await getLatestComposioInstance();
|
|
386
|
+
instance.client.connectedAccounts.list.mockResolvedValueOnce({
|
|
387
|
+
items: [
|
|
388
|
+
{ id: "ca_1", user_id: "default", status: "ACTIVE", toolkit: { slug: "sentry" } },
|
|
389
|
+
{ id: "ca_2", user_id: "user-b", status: "ACTIVE", toolkit: { slug: "sentry" } },
|
|
390
|
+
{ id: "ca_3", user_id: "default", status: "ACTIVE", toolkit: { slug: "sentry" } },
|
|
391
|
+
],
|
|
392
|
+
next_cursor: null,
|
|
393
|
+
});
|
|
394
|
+
const userIds = await client.findActiveUserIdsForToolkit("sentry");
|
|
395
|
+
expect(userIds).toEqual(["default", "user-b"]);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
138
398
|
describe("session caching", () => {
|
|
139
399
|
it("reuses session for same user", async () => {
|
|
140
400
|
const client = makeClient();
|
|
@@ -192,35 +452,55 @@ describe("connections tool", () => {
|
|
|
192
452
|
}
|
|
193
453
|
it("list action passes user_id to client", async () => {
|
|
194
454
|
const tool = makeConnectionsTool();
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
expect(
|
|
198
|
-
expect(details.toolkits).toBeInstanceOf(Array);
|
|
455
|
+
await tool.execute("test", { action: "list", user_id: "custom-user" });
|
|
456
|
+
const instance = await getLatestComposioInstance();
|
|
457
|
+
expect(instance.toolRouter.create).toHaveBeenCalledWith("custom-user", undefined);
|
|
199
458
|
});
|
|
200
|
-
it("status
|
|
459
|
+
it("status uses active connected accounts as fallback", async () => {
|
|
201
460
|
const tool = makeConnectionsTool();
|
|
461
|
+
const instance = await getLatestComposioInstance();
|
|
462
|
+
instance.connectedAccounts.list.mockResolvedValueOnce({
|
|
463
|
+
items: [{ toolkit: { slug: "affinity" }, status: "ACTIVE" }],
|
|
464
|
+
nextCursor: null,
|
|
465
|
+
});
|
|
202
466
|
const result = await tool.execute("test", { action: "status", toolkit: "affinity" });
|
|
203
467
|
const details = result.details;
|
|
204
468
|
const conn = details.connections.find((c) => c.toolkit === "affinity");
|
|
205
469
|
expect(conn.connected).toBe(true);
|
|
470
|
+
expect(instance.client.tools.execute).not.toHaveBeenCalledWith("AFFINITY_GET_METADATA_ON_ALL_LISTS", expect.anything());
|
|
206
471
|
});
|
|
207
|
-
it("status
|
|
472
|
+
it("status keeps disconnected when no active account exists", async () => {
|
|
208
473
|
const tool = makeConnectionsTool();
|
|
209
474
|
const result = await tool.execute("test", { action: "status", toolkit: "sentry" });
|
|
210
475
|
const details = result.details;
|
|
211
476
|
const conn = details.connections.find((c) => c.toolkit === "sentry");
|
|
212
477
|
expect(conn.connected).toBe(false);
|
|
213
478
|
});
|
|
214
|
-
it("
|
|
479
|
+
it("accounts action returns connected accounts", async () => {
|
|
215
480
|
const tool = makeConnectionsTool();
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
481
|
+
const instance = await getLatestComposioInstance();
|
|
482
|
+
instance.client.connectedAccounts.list.mockResolvedValueOnce({
|
|
483
|
+
items: [
|
|
484
|
+
{
|
|
485
|
+
id: "ca_1",
|
|
486
|
+
user_id: "user-a",
|
|
487
|
+
status: "ACTIVE",
|
|
488
|
+
toolkit: { slug: "sentry" },
|
|
489
|
+
auth_config: { id: "ac_1" },
|
|
490
|
+
},
|
|
491
|
+
],
|
|
492
|
+
next_cursor: null,
|
|
493
|
+
});
|
|
494
|
+
const result = await tool.execute("test", { action: "accounts", toolkit: "sentry" });
|
|
222
495
|
const details = result.details;
|
|
223
|
-
|
|
224
|
-
expect(
|
|
496
|
+
expect(details.action).toBe("accounts");
|
|
497
|
+
expect(details.count).toBe(1);
|
|
498
|
+
expect(details.accounts[0]).toMatchObject({
|
|
499
|
+
id: "ca_1",
|
|
500
|
+
toolkit: "sentry",
|
|
501
|
+
user_id: "user-a",
|
|
502
|
+
status: "ACTIVE",
|
|
503
|
+
auth_config_id: "ac_1",
|
|
504
|
+
});
|
|
225
505
|
});
|
|
226
506
|
});
|
package/dist/config.js
CHANGED
|
@@ -16,15 +16,28 @@ export function parseComposioConfig(value) {
|
|
|
16
16
|
const raw = value && typeof value === "object" && !Array.isArray(value)
|
|
17
17
|
? value
|
|
18
18
|
: {};
|
|
19
|
-
//
|
|
19
|
+
// Support both plugin entry shape ({ enabled, config: {...} }) and flat shape.
|
|
20
20
|
const configObj = raw.config;
|
|
21
|
+
const enabled = (typeof raw.enabled === "boolean" ? raw.enabled : undefined) ??
|
|
22
|
+
(typeof configObj?.enabled === "boolean" ? configObj.enabled : undefined) ??
|
|
23
|
+
true;
|
|
24
|
+
const defaultUserId = (typeof raw.defaultUserId === "string" ? raw.defaultUserId : undefined) ??
|
|
25
|
+
(typeof configObj?.defaultUserId === "string" ? configObj.defaultUserId : undefined);
|
|
26
|
+
const allowedToolkits = (Array.isArray(raw.allowedToolkits) ? raw.allowedToolkits : undefined) ??
|
|
27
|
+
(Array.isArray(configObj?.allowedToolkits) ? configObj.allowedToolkits : undefined);
|
|
28
|
+
const blockedToolkits = (Array.isArray(raw.blockedToolkits) ? raw.blockedToolkits : undefined) ??
|
|
29
|
+
(Array.isArray(configObj?.blockedToolkits) ? configObj.blockedToolkits : undefined);
|
|
30
|
+
// Allow API key from config.apiKey, top-level apiKey, or environment.
|
|
21
31
|
const apiKey = (typeof configObj?.apiKey === "string" && configObj.apiKey.trim()) ||
|
|
22
32
|
(typeof raw.apiKey === "string" && raw.apiKey.trim()) ||
|
|
23
33
|
process.env.COMPOSIO_API_KEY ||
|
|
24
34
|
"";
|
|
25
35
|
return ComposioConfigSchema.parse({
|
|
26
|
-
|
|
36
|
+
enabled,
|
|
27
37
|
apiKey,
|
|
38
|
+
defaultUserId,
|
|
39
|
+
allowedToolkits,
|
|
40
|
+
blockedToolkits,
|
|
28
41
|
});
|
|
29
42
|
}
|
|
30
43
|
/**
|
|
@@ -4,10 +4,11 @@ import type { ComposioConfig } from "../types.js";
|
|
|
4
4
|
* Tool parameters for composio_manage_connections
|
|
5
5
|
*/
|
|
6
6
|
export declare const ComposioManageConnectionsToolSchema: import("@sinclair/typebox").TObject<{
|
|
7
|
-
action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">]>;
|
|
7
|
+
action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">, import("@sinclair/typebox").TLiteral<"accounts">]>;
|
|
8
8
|
toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
9
9
|
toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
10
10
|
user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
11
|
+
statuses: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
11
12
|
}>;
|
|
12
13
|
/**
|
|
13
14
|
* Create the composio_manage_connections tool
|
|
@@ -17,10 +18,11 @@ export declare function createComposioConnectionsTool(client: ComposioClient, _c
|
|
|
17
18
|
label: string;
|
|
18
19
|
description: string;
|
|
19
20
|
parameters: import("@sinclair/typebox").TObject<{
|
|
20
|
-
action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">]>;
|
|
21
|
+
action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">, import("@sinclair/typebox").TLiteral<"accounts">]>;
|
|
21
22
|
toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
22
23
|
toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
23
24
|
user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
25
|
+
statuses: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
24
26
|
}>;
|
|
25
27
|
execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
|
|
26
28
|
content: {
|
|
@@ -32,6 +34,22 @@ export declare function createComposioConnectionsTool(client: ComposioClient, _c
|
|
|
32
34
|
count: number;
|
|
33
35
|
toolkits: string[];
|
|
34
36
|
};
|
|
37
|
+
} | {
|
|
38
|
+
content: {
|
|
39
|
+
type: string;
|
|
40
|
+
text: string;
|
|
41
|
+
}[];
|
|
42
|
+
details: {
|
|
43
|
+
action: string;
|
|
44
|
+
count: number;
|
|
45
|
+
accounts: {
|
|
46
|
+
id: string;
|
|
47
|
+
toolkit: string;
|
|
48
|
+
user_id: string | undefined;
|
|
49
|
+
status: string | undefined;
|
|
50
|
+
auth_config_id: string | undefined;
|
|
51
|
+
}[];
|
|
52
|
+
};
|
|
35
53
|
} | {
|
|
36
54
|
content: {
|
|
37
55
|
type: string;
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
const CONNECTION_PROBES = {
|
|
3
|
-
affinity: {
|
|
4
|
-
toolSlug: "AFFINITY_GET_METADATA_ON_ALL_LISTS",
|
|
5
|
-
args: { limit: 1 },
|
|
6
|
-
},
|
|
7
|
-
};
|
|
8
2
|
/**
|
|
9
3
|
* Tool parameters for composio_manage_connections
|
|
10
4
|
*/
|
|
11
5
|
export const ComposioManageConnectionsToolSchema = Type.Object({
|
|
12
|
-
action: Type.Union([Type.Literal("status"), Type.Literal("create"), Type.Literal("list")], {
|
|
13
|
-
description: "Action to perform: 'status' to check connections, 'create' to initiate auth, 'list' to list toolkits",
|
|
6
|
+
action: Type.Union([Type.Literal("status"), Type.Literal("create"), Type.Literal("list"), Type.Literal("accounts")], {
|
|
7
|
+
description: "Action to perform: 'status' to check connections, 'create' to initiate auth, 'list' to list toolkits, 'accounts' to inspect connected accounts",
|
|
14
8
|
}),
|
|
15
9
|
toolkit: Type.Optional(Type.String({
|
|
16
10
|
description: "Toolkit name for 'status' or 'create' actions (e.g., 'github', 'gmail')",
|
|
@@ -21,6 +15,9 @@ export const ComposioManageConnectionsToolSchema = Type.Object({
|
|
|
21
15
|
user_id: Type.Optional(Type.String({
|
|
22
16
|
description: "User ID for session scoping (uses default if not provided)",
|
|
23
17
|
})),
|
|
18
|
+
statuses: Type.Optional(Type.Array(Type.String(), {
|
|
19
|
+
description: "Optional connection statuses filter for 'accounts' (e.g., ['ACTIVE'])",
|
|
20
|
+
})),
|
|
24
21
|
});
|
|
25
22
|
/**
|
|
26
23
|
* Create the composio_manage_connections tool
|
|
@@ -30,7 +27,8 @@ export function createComposioConnectionsTool(client, _config) {
|
|
|
30
27
|
name: "composio_manage_connections",
|
|
31
28
|
label: "Composio Manage Connections",
|
|
32
29
|
description: "Manage Composio toolkit connections. Use action='status' to check if a toolkit is connected, " +
|
|
33
|
-
"action='create' to generate an auth URL when disconnected,
|
|
30
|
+
"action='create' to generate an auth URL when disconnected, action='list' to see available toolkits, " +
|
|
31
|
+
"or action='accounts' to inspect connected accounts across user IDs. " +
|
|
34
32
|
"Check connection status before executing tools with composio_execute_tool.",
|
|
35
33
|
parameters: ComposioManageConnectionsToolSchema,
|
|
36
34
|
async execute(_toolCallId, params) {
|
|
@@ -50,6 +48,38 @@ export function createComposioConnectionsTool(client, _config) {
|
|
|
50
48
|
details: response,
|
|
51
49
|
};
|
|
52
50
|
}
|
|
51
|
+
case "accounts": {
|
|
52
|
+
let toolkits;
|
|
53
|
+
if (typeof params.toolkit === "string" && params.toolkit.trim()) {
|
|
54
|
+
toolkits = [params.toolkit.trim()];
|
|
55
|
+
}
|
|
56
|
+
else if (Array.isArray(params.toolkits)) {
|
|
57
|
+
toolkits = params.toolkits.filter((t) => typeof t === "string" && t.trim() !== "");
|
|
58
|
+
}
|
|
59
|
+
const statuses = Array.isArray(params.statuses)
|
|
60
|
+
? params.statuses.filter((s) => typeof s === "string" && s.trim() !== "")
|
|
61
|
+
: ["ACTIVE"];
|
|
62
|
+
const accounts = await client.listConnectedAccounts({
|
|
63
|
+
toolkits,
|
|
64
|
+
userIds: userId ? [userId] : undefined,
|
|
65
|
+
statuses,
|
|
66
|
+
});
|
|
67
|
+
const response = {
|
|
68
|
+
action: "accounts",
|
|
69
|
+
count: accounts.length,
|
|
70
|
+
accounts: accounts.map((a) => ({
|
|
71
|
+
id: a.id,
|
|
72
|
+
toolkit: a.toolkit,
|
|
73
|
+
user_id: a.userId,
|
|
74
|
+
status: a.status,
|
|
75
|
+
auth_config_id: a.authConfigId,
|
|
76
|
+
})),
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
80
|
+
details: response,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
53
83
|
case "create": {
|
|
54
84
|
const toolkit = String(params.toolkit || "").trim();
|
|
55
85
|
if (!toolkit) {
|
|
@@ -89,25 +119,6 @@ export function createComposioConnectionsTool(client, _config) {
|
|
|
89
119
|
toolkitsToCheck = params.toolkits.filter((t) => typeof t === "string" && t.trim() !== "");
|
|
90
120
|
}
|
|
91
121
|
const statuses = await client.getConnectionStatus(toolkitsToCheck, userId);
|
|
92
|
-
// Fallback probe for API-key style integrations where
|
|
93
|
-
// connection.isActive can be false despite successful tool execution
|
|
94
|
-
if (toolkitsToCheck && toolkitsToCheck.length > 0) {
|
|
95
|
-
for (const status of statuses) {
|
|
96
|
-
if (status.connected)
|
|
97
|
-
continue;
|
|
98
|
-
const probe = CONNECTION_PROBES[String(status.toolkit || "").toLowerCase()];
|
|
99
|
-
if (!probe)
|
|
100
|
-
continue;
|
|
101
|
-
try {
|
|
102
|
-
const probeResult = await client.executeTool(probe.toolSlug, probe.args, userId);
|
|
103
|
-
if (probeResult?.success)
|
|
104
|
-
status.connected = true;
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// keep false if probe fails
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
122
|
const response = {
|
|
112
123
|
action: "status",
|
|
113
124
|
count: statuses.length,
|
package/dist/tools/execute.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare const ComposioExecuteToolSchema: import("@sinclair/typebox").TObj
|
|
|
7
7
|
tool_slug: import("@sinclair/typebox").TString;
|
|
8
8
|
arguments: import("@sinclair/typebox").TUnknown;
|
|
9
9
|
user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
10
|
+
connected_account_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
10
11
|
}>;
|
|
11
12
|
/**
|
|
12
13
|
* Create the composio_execute_tool tool
|
|
@@ -19,6 +20,7 @@ export declare function createComposioExecuteTool(client: ComposioClient, _confi
|
|
|
19
20
|
tool_slug: import("@sinclair/typebox").TString;
|
|
20
21
|
arguments: import("@sinclair/typebox").TUnknown;
|
|
21
22
|
user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
23
|
+
connected_account_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
22
24
|
}>;
|
|
23
25
|
execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
|
|
24
26
|
content: {
|
package/dist/tools/execute.js
CHANGED
|
@@ -12,6 +12,9 @@ export const ComposioExecuteToolSchema = Type.Object({
|
|
|
12
12
|
user_id: Type.Optional(Type.String({
|
|
13
13
|
description: "User ID for session scoping (uses default if not provided)",
|
|
14
14
|
})),
|
|
15
|
+
connected_account_id: Type.Optional(Type.String({
|
|
16
|
+
description: "Optional connected account ID to pin execution to a specific account when multiple are connected",
|
|
17
|
+
})),
|
|
15
18
|
});
|
|
16
19
|
/**
|
|
17
20
|
* Create the composio_execute_tool tool
|
|
@@ -43,8 +46,9 @@ export function createComposioExecuteTool(client, _config) {
|
|
|
43
46
|
? rawArgs
|
|
44
47
|
: {};
|
|
45
48
|
const userId = typeof params.user_id === "string" ? params.user_id : undefined;
|
|
49
|
+
const connectedAccountId = typeof params.connected_account_id === "string" ? params.connected_account_id : undefined;
|
|
46
50
|
try {
|
|
47
|
-
const result = await client.executeTool(toolSlug, args, userId);
|
|
51
|
+
const result = await client.executeTool(toolSlug, args, userId, connectedAccountId);
|
|
48
52
|
const response = {
|
|
49
53
|
tool_slug: toolSlug,
|
|
50
54
|
success: result.success,
|
package/dist/types.d.ts
CHANGED
|
@@ -26,3 +26,13 @@ export interface ConnectionStatus {
|
|
|
26
26
|
userId?: string;
|
|
27
27
|
authUrl?: string;
|
|
28
28
|
}
|
|
29
|
+
export interface ConnectedAccountSummary {
|
|
30
|
+
id: string;
|
|
31
|
+
toolkit: string;
|
|
32
|
+
userId?: string;
|
|
33
|
+
status?: string;
|
|
34
|
+
authConfigId?: string;
|
|
35
|
+
isDisabled?: boolean;
|
|
36
|
+
createdAt?: string;
|
|
37
|
+
updatedAt?: string;
|
|
38
|
+
}
|