@customclaw/composio 0.0.5 → 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 +322 -0
- package/dist/config.js +15 -2
- package/dist/index.js +0 -16
- package/dist/tools/connections.d.ts +20 -2
- package/dist/tools/connections.js +42 -5
- package/dist/tools/execute.d.ts +2 -0
- package/dist/tools/execute.js +8 -3
- package/dist/tools/search.js +3 -2
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
package/dist/client.test.js
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, it, expect, vi } from "vitest";
|
|
|
2
2
|
import { ComposioClient } from "./client.js";
|
|
3
3
|
import { parseComposioConfig } from "./config.js";
|
|
4
4
|
import { createComposioExecuteTool } from "./tools/execute.js";
|
|
5
|
+
import { createComposioConnectionsTool } from "./tools/connections.js";
|
|
5
6
|
// Mock the Composio SDK
|
|
6
7
|
vi.mock("@composio/core", () => ({
|
|
7
8
|
Composio: vi.fn().mockImplementation(() => ({
|
|
@@ -15,6 +16,7 @@ vi.mock("@composio/core", () => ({
|
|
|
15
16
|
{ slug: "gmail", name: "Gmail", connection: { isActive: true } },
|
|
16
17
|
{ slug: "sentry", name: "Sentry", connection: { isActive: false } },
|
|
17
18
|
{ slug: "github", name: "GitHub", connection: { isActive: true } },
|
|
19
|
+
{ slug: "affinity", name: "Affinity", connection: { isActive: false } },
|
|
18
20
|
],
|
|
19
21
|
}),
|
|
20
22
|
experimental: { assistivePrompt: "" },
|
|
@@ -27,9 +29,19 @@ vi.mock("@composio/core", () => ({
|
|
|
27
29
|
data: { results: [{ tool_slug: "GMAIL_FETCH_EMAILS", index: 0, response: { successful: true, data: { messages: [] } } }] },
|
|
28
30
|
}),
|
|
29
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
|
+
}),
|
|
30
41
|
},
|
|
31
42
|
connectedAccounts: {
|
|
32
43
|
list: vi.fn().mockResolvedValue({ items: [] }),
|
|
44
|
+
get: vi.fn().mockResolvedValue({ toolkit: { slug: "gmail" }, status: "ACTIVE" }),
|
|
33
45
|
delete: vi.fn().mockResolvedValue({}),
|
|
34
46
|
},
|
|
35
47
|
})),
|
|
@@ -41,6 +53,11 @@ function makeClient(overrides) {
|
|
|
41
53
|
...overrides,
|
|
42
54
|
});
|
|
43
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
|
+
}
|
|
44
61
|
describe("config parsing", () => {
|
|
45
62
|
it("reads apiKey from config object", () => {
|
|
46
63
|
const config = parseComposioConfig({ config: { apiKey: "from-config" } });
|
|
@@ -60,6 +77,19 @@ describe("config parsing", () => {
|
|
|
60
77
|
const config = parseComposioConfig({});
|
|
61
78
|
expect(config.enabled).toBe(true);
|
|
62
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
|
+
});
|
|
63
93
|
});
|
|
64
94
|
describe("toolkit filtering", () => {
|
|
65
95
|
it("allows all toolkits when no filter set", async () => {
|
|
@@ -97,6 +127,16 @@ describe("connection status", () => {
|
|
|
97
127
|
const statuses = await client.getConnectionStatus(["sentry"]);
|
|
98
128
|
expect(statuses[0].connected).toBe(false);
|
|
99
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
|
+
});
|
|
100
140
|
it("returns only connected toolkits when no filter", async () => {
|
|
101
141
|
const client = makeClient();
|
|
102
142
|
const statuses = await client.getConnectionStatus();
|
|
@@ -117,6 +157,123 @@ describe("execute tool", () => {
|
|
|
117
157
|
expect(result.success).toBe(false);
|
|
118
158
|
expect(result.error).toContain("not allowed");
|
|
119
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
|
+
});
|
|
120
277
|
});
|
|
121
278
|
describe("create connection", () => {
|
|
122
279
|
it("returns auth URL", async () => {
|
|
@@ -133,6 +290,111 @@ describe("create connection", () => {
|
|
|
133
290
|
expect("error" in result).toBe(true);
|
|
134
291
|
});
|
|
135
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
|
+
});
|
|
136
398
|
describe("session caching", () => {
|
|
137
399
|
it("reuses session for same user", async () => {
|
|
138
400
|
const client = makeClient();
|
|
@@ -182,3 +444,63 @@ describe("execute tool string arguments (GLM-5 workaround)", () => {
|
|
|
182
444
|
expect(result.details).toHaveProperty("success", true);
|
|
183
445
|
});
|
|
184
446
|
});
|
|
447
|
+
describe("connections tool", () => {
|
|
448
|
+
function makeConnectionsTool() {
|
|
449
|
+
const client = makeClient();
|
|
450
|
+
const config = parseComposioConfig({ config: { apiKey: "test-key" } });
|
|
451
|
+
return createComposioConnectionsTool(client, config);
|
|
452
|
+
}
|
|
453
|
+
it("list action passes user_id to client", async () => {
|
|
454
|
+
const tool = makeConnectionsTool();
|
|
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);
|
|
458
|
+
});
|
|
459
|
+
it("status uses active connected accounts as fallback", async () => {
|
|
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
|
+
});
|
|
466
|
+
const result = await tool.execute("test", { action: "status", toolkit: "affinity" });
|
|
467
|
+
const details = result.details;
|
|
468
|
+
const conn = details.connections.find((c) => c.toolkit === "affinity");
|
|
469
|
+
expect(conn.connected).toBe(true);
|
|
470
|
+
expect(instance.client.tools.execute).not.toHaveBeenCalledWith("AFFINITY_GET_METADATA_ON_ALL_LISTS", expect.anything());
|
|
471
|
+
});
|
|
472
|
+
it("status keeps disconnected when no active account exists", async () => {
|
|
473
|
+
const tool = makeConnectionsTool();
|
|
474
|
+
const result = await tool.execute("test", { action: "status", toolkit: "sentry" });
|
|
475
|
+
const details = result.details;
|
|
476
|
+
const conn = details.connections.find((c) => c.toolkit === "sentry");
|
|
477
|
+
expect(conn.connected).toBe(false);
|
|
478
|
+
});
|
|
479
|
+
it("accounts action returns connected accounts", async () => {
|
|
480
|
+
const tool = makeConnectionsTool();
|
|
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" });
|
|
495
|
+
const details = result.details;
|
|
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
|
+
});
|
|
505
|
+
});
|
|
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
|
/**
|
package/dist/index.js
CHANGED
|
@@ -73,22 +73,6 @@ const composioPlugin = {
|
|
|
73
73
|
config,
|
|
74
74
|
logger: api.logger,
|
|
75
75
|
}), { commands: ["composio"] });
|
|
76
|
-
// Inject agent instructions via before_agent_start hook
|
|
77
|
-
api.on("before_agent_start", () => {
|
|
78
|
-
return {
|
|
79
|
-
prependContext: `<composio-tools>
|
|
80
|
-
You have access to Composio tools for third-party integrations (Gmail, Sentry, etc.).
|
|
81
|
-
|
|
82
|
-
## Usage
|
|
83
|
-
1. Use \`composio_search_tools\` to find tools and their parameter schemas.
|
|
84
|
-
2. Use \`composio_manage_connections\` with action="status" to check if a toolkit is connected. Use action="create" to generate an auth URL if needed.
|
|
85
|
-
3. Use \`composio_execute_tool\` with the tool_slug and arguments from search results.
|
|
86
|
-
|
|
87
|
-
Always search first to get the correct parameter schema before executing a tool.
|
|
88
|
-
Tool slugs are uppercase. If a tool fails with auth errors, prompt the user to connect the toolkit.
|
|
89
|
-
</composio-tools>`,
|
|
90
|
-
};
|
|
91
|
-
});
|
|
92
76
|
api.logger.info("[composio] Plugin registered with 3 tools and CLI commands");
|
|
93
77
|
},
|
|
94
78
|
};
|
|
@@ -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;
|
|
@@ -3,8 +3,8 @@ import { Type } from "@sinclair/typebox";
|
|
|
3
3
|
* Tool parameters for composio_manage_connections
|
|
4
4
|
*/
|
|
5
5
|
export const ComposioManageConnectionsToolSchema = Type.Object({
|
|
6
|
-
action: Type.Union([Type.Literal("status"), Type.Literal("create"), Type.Literal("list")], {
|
|
7
|
-
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",
|
|
8
8
|
}),
|
|
9
9
|
toolkit: Type.Optional(Type.String({
|
|
10
10
|
description: "Toolkit name for 'status' or 'create' actions (e.g., 'github', 'gmail')",
|
|
@@ -15,6 +15,9 @@ export const ComposioManageConnectionsToolSchema = Type.Object({
|
|
|
15
15
|
user_id: Type.Optional(Type.String({
|
|
16
16
|
description: "User ID for session scoping (uses default if not provided)",
|
|
17
17
|
})),
|
|
18
|
+
statuses: Type.Optional(Type.Array(Type.String(), {
|
|
19
|
+
description: "Optional connection statuses filter for 'accounts' (e.g., ['ACTIVE'])",
|
|
20
|
+
})),
|
|
18
21
|
});
|
|
19
22
|
/**
|
|
20
23
|
* Create the composio_manage_connections tool
|
|
@@ -23,8 +26,10 @@ export function createComposioConnectionsTool(client, _config) {
|
|
|
23
26
|
return {
|
|
24
27
|
name: "composio_manage_connections",
|
|
25
28
|
label: "Composio Manage Connections",
|
|
26
|
-
description: "Manage Composio toolkit connections.
|
|
27
|
-
"
|
|
29
|
+
description: "Manage Composio toolkit connections. Use action='status' to check if a toolkit is connected, " +
|
|
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. " +
|
|
32
|
+
"Check connection status before executing tools with composio_execute_tool.",
|
|
28
33
|
parameters: ComposioManageConnectionsToolSchema,
|
|
29
34
|
async execute(_toolCallId, params) {
|
|
30
35
|
const action = String(params.action || "status");
|
|
@@ -32,7 +37,7 @@ export function createComposioConnectionsTool(client, _config) {
|
|
|
32
37
|
try {
|
|
33
38
|
switch (action) {
|
|
34
39
|
case "list": {
|
|
35
|
-
const toolkits = await client.listToolkits();
|
|
40
|
+
const toolkits = await client.listToolkits(userId);
|
|
36
41
|
const response = {
|
|
37
42
|
action: "list",
|
|
38
43
|
count: toolkits.length,
|
|
@@ -43,6 +48,38 @@ export function createComposioConnectionsTool(client, _config) {
|
|
|
43
48
|
details: response,
|
|
44
49
|
};
|
|
45
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
|
+
}
|
|
46
83
|
case "create": {
|
|
47
84
|
const toolkit = String(params.toolkit || "").trim();
|
|
48
85
|
if (!toolkit) {
|
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
|
|
@@ -20,8 +23,9 @@ export function createComposioExecuteTool(client, _config) {
|
|
|
20
23
|
return {
|
|
21
24
|
name: "composio_execute_tool",
|
|
22
25
|
label: "Composio Execute Tool",
|
|
23
|
-
description: "Execute a single Composio tool. Use composio_search_tools first to find the tool slug " +
|
|
24
|
-
"and parameter schema. The
|
|
26
|
+
description: "Execute a single Composio tool. Use composio_search_tools first to find the UPPERCASE tool slug " +
|
|
27
|
+
"and parameter schema. The toolkit must be connected — use composio_manage_connections to check " +
|
|
28
|
+
"status or create an auth link. If execution fails with auth errors, prompt the user to reconnect.",
|
|
25
29
|
parameters: ComposioExecuteToolSchema,
|
|
26
30
|
async execute(_toolCallId, params) {
|
|
27
31
|
const toolSlug = String(params.tool_slug || "").trim();
|
|
@@ -42,8 +46,9 @@ export function createComposioExecuteTool(client, _config) {
|
|
|
42
46
|
? rawArgs
|
|
43
47
|
: {};
|
|
44
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;
|
|
45
50
|
try {
|
|
46
|
-
const result = await client.executeTool(toolSlug, args, userId);
|
|
51
|
+
const result = await client.executeTool(toolSlug, args, userId, connectedAccountId);
|
|
47
52
|
const response = {
|
|
48
53
|
tool_slug: toolSlug,
|
|
49
54
|
success: result.success,
|
package/dist/tools/search.js
CHANGED
|
@@ -23,8 +23,9 @@ export function createComposioSearchTool(client, _config) {
|
|
|
23
23
|
return {
|
|
24
24
|
name: "composio_search_tools",
|
|
25
25
|
label: "Composio Search Tools",
|
|
26
|
-
description: "Search for tools across 1000+ integrations (Gmail, Slack, GitHub, Notion, etc.) " +
|
|
27
|
-
"by describing what you want to accomplish. Returns matching tools with their schemas."
|
|
26
|
+
description: "Search for Composio tools across 1000+ integrations (Gmail, Slack, GitHub, Notion, etc.) " +
|
|
27
|
+
"by describing what you want to accomplish. Returns matching tools with their UPPERCASE slugs and parameter schemas. " +
|
|
28
|
+
"Always search first before executing a tool with composio_execute_tool.",
|
|
28
29
|
parameters: ComposioSearchToolSchema,
|
|
29
30
|
async execute(_toolCallId, params) {
|
|
30
31
|
const query = String(params.query || "").trim();
|
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
|
+
}
|