@muxoai/platform 1.4.0 → 1.4.2
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/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +2 -2
- package/ARCHITECTURE.md +16 -6
- package/README.md +2 -2
- package/dist/cli.js +323 -13
- package/dist/composio-catalog.d.ts +234 -0
- package/dist/composio-catalog.js +1894 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +27 -1
- package/dist/durable-workflow.d.ts +45 -0
- package/dist/durable-workflow.js +90 -0
- package/dist/engine.d.ts +293 -5
- package/dist/engine.js +1502 -88
- package/dist/mcp-schema.d.ts +286 -0
- package/dist/mcp-schema.js +2559 -0
- package/dist/mcp.d.ts +2 -30
- package/dist/mcp.js +913 -558
- package/dist/memory-graph.d.ts +21 -0
- package/dist/memory-graph.js +173 -0
- package/dist/nl.js +155 -0
- package/dist/oauth.d.ts +32 -0
- package/dist/oauth.js +134 -0
- package/dist/policy.d.ts +182 -1
- package/dist/policy.js +1524 -0
- package/dist/providers/composio-instant.js +262 -7
- package/dist/providers/composio-sdk.d.ts +125 -5
- package/dist/providers/composio-sdk.js +901 -82
- package/dist/providers/microsoft-governance.d.ts +28 -1
- package/dist/providers/microsoft-governance.js +224 -11
- package/dist/providers/stripe-billing.d.ts +42 -0
- package/dist/providers/stripe-billing.js +230 -1
- package/dist/providers/stripe-projects.d.ts +455 -3
- package/dist/providers/stripe-projects.js +2570 -129
- package/dist/providers/supermemory.d.ts +74 -1
- package/dist/providers/supermemory.js +426 -0
- package/dist/provisioning-managed.d.ts +50 -0
- package/dist/provisioning-managed.js +155 -0
- package/dist/sdk.d.ts +440 -3
- package/dist/sdk.js +305 -3
- package/dist/server.js +2343 -264
- package/dist/store.d.ts +113 -6
- package/dist/store.js +677 -52
- package/dist/types.d.ts +207 -3
- package/dist/ui/console-html.d.ts +40 -1
- package/dist/ui/console-html.js +1813 -629
- package/dist/ui/design-tokens.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -3
- package/plugins/muxo/.claude-plugin/plugin.json +2 -2
- package/plugins/muxo/.codex-plugin/plugin.json +15 -8
- package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
- package/plugins/muxo/hooks/session-start.sh +15 -3
- package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
- package/plugins/muxo/mcp.json +0 -10
- package/plugins/muxo/plugin.json +4 -4
- package/plugins/muxo/skills/muxo/SKILL.md +59 -9
- package/plugins/muxo/skills/muxo/references/cli.md +5 -1
- package/plugins/muxo/skills/muxo/references/mcp.md +13 -7
- package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
- package/plugins/muxo/skills/setup/SKILL.md +9 -1
- package/src/catalogues/stripe-projects-catalog.json +140 -0
|
@@ -1,4 +1,28 @@
|
|
|
1
|
+
import { Composio } from "@composio/core";
|
|
2
|
+
import { matchAppToolSlug } from "../mcp-schema.js";
|
|
3
|
+
import { composioAuthConfigScopes, COMPOSIO_V3_WEBHOOK_EVENTS, ensureOAuthCallbackParams, ENTERPRISE_TOOLKITS_CATALOG, filterComposioToolkits, getComposioTriggerType, getSampleActionSchema, isStripeProjectsExclusiveProvider, listComposioTriggerTypes, parseComposioWebhookEnvelope, verifyComposioWebhookSignature, wrapMuxoConnectUrl, } from "../composio-catalog.js";
|
|
1
4
|
const CURATED_APP_TOOLS = [
|
|
5
|
+
{
|
|
6
|
+
slug: "app.github.list_repos",
|
|
7
|
+
upstreamSlug: "GITHUB_LIST_REPOSITORIES_FOR_THE_AUTHENTICATED_USER",
|
|
8
|
+
name: "GitHub: List Repositories",
|
|
9
|
+
description: "List repositories accessible to the connected GitHub account via Muxo OAuth.",
|
|
10
|
+
lane: "composio_sdk",
|
|
11
|
+
providerSlug: "github",
|
|
12
|
+
providerName: "GitHub",
|
|
13
|
+
categories: ["developer_tools", "version_control", "git"],
|
|
14
|
+
riskLevel: "read",
|
|
15
|
+
requiresAuth: true,
|
|
16
|
+
creditCost: 0,
|
|
17
|
+
billingMode: "customer_oauth",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
visibility: { type: "string", enum: ["all", "public", "private"], default: "all" },
|
|
22
|
+
per_page: { type: "number", default: 20 },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
2
26
|
{
|
|
3
27
|
slug: "app.github.create_issue",
|
|
4
28
|
upstreamSlug: "GITHUB_CREATE_AN_ISSUE",
|
|
@@ -90,6 +114,27 @@ const CURATED_APP_TOOLS = [
|
|
|
90
114
|
},
|
|
91
115
|
},
|
|
92
116
|
},
|
|
117
|
+
{
|
|
118
|
+
slug: "app.gmail.list_messages",
|
|
119
|
+
upstreamSlug: "GMAIL_FETCH_EMAILS",
|
|
120
|
+
name: "Gmail: List Messages",
|
|
121
|
+
description: "Read recent email threads from the connected Gmail inbox.",
|
|
122
|
+
lane: "composio_sdk",
|
|
123
|
+
providerSlug: "gmail",
|
|
124
|
+
providerName: "Gmail",
|
|
125
|
+
categories: ["email", "communication"],
|
|
126
|
+
riskLevel: "read",
|
|
127
|
+
requiresAuth: true,
|
|
128
|
+
creditCost: 0,
|
|
129
|
+
billingMode: "customer_oauth",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
query: { type: "string", description: "Gmail search query (e.g. 'is:unread')" },
|
|
134
|
+
max_results: { type: "number", default: 10 },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
93
138
|
{
|
|
94
139
|
slug: "app.slack.send_message",
|
|
95
140
|
upstreamSlug: "SLACK_SENDS_A_MESSAGE_TO_A_SLACK_CHANNEL",
|
|
@@ -112,6 +157,28 @@ const CURATED_APP_TOOLS = [
|
|
|
112
157
|
},
|
|
113
158
|
},
|
|
114
159
|
},
|
|
160
|
+
{
|
|
161
|
+
slug: "app.slack.post_message",
|
|
162
|
+
upstreamSlug: "SLACK_SENDS_A_MESSAGE_TO_A_SLACK_CHANNEL",
|
|
163
|
+
name: "Slack: Post Channel Message",
|
|
164
|
+
description: "Post a message to a Slack channel using the connected Slack workspace.",
|
|
165
|
+
lane: "composio_sdk",
|
|
166
|
+
providerSlug: "slack",
|
|
167
|
+
providerName: "Slack",
|
|
168
|
+
categories: ["messaging", "team_chat", "communication", "chat"],
|
|
169
|
+
riskLevel: "write",
|
|
170
|
+
requiresAuth: true,
|
|
171
|
+
creditCost: 0,
|
|
172
|
+
billingMode: "customer_oauth",
|
|
173
|
+
inputSchema: {
|
|
174
|
+
type: "object",
|
|
175
|
+
required: ["channel", "text"],
|
|
176
|
+
properties: {
|
|
177
|
+
channel: { type: "string", description: "Slack channel ID or name" },
|
|
178
|
+
text: { type: "string", description: "Message text (Markdown supported)" },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
115
182
|
{
|
|
116
183
|
slug: "app.notion.search_pages",
|
|
117
184
|
upstreamSlug: "NOTION_SEARCH_NOTION_PAGE",
|
|
@@ -176,6 +243,138 @@ const CURATED_APP_TOOLS = [
|
|
|
176
243
|
},
|
|
177
244
|
},
|
|
178
245
|
},
|
|
246
|
+
{
|
|
247
|
+
slug: "app.jira.create_issue",
|
|
248
|
+
upstreamSlug: "JIRA_CREATE_ISSUE",
|
|
249
|
+
name: "Jira: Create Issue",
|
|
250
|
+
description: "Create a Jira issue in a connected Atlassian project via Muxo OAuth.",
|
|
251
|
+
lane: "composio_sdk",
|
|
252
|
+
providerSlug: "jira",
|
|
253
|
+
providerName: "Jira",
|
|
254
|
+
categories: ["project_management", "engineering"],
|
|
255
|
+
riskLevel: "write",
|
|
256
|
+
requiresAuth: true,
|
|
257
|
+
creditCost: 0,
|
|
258
|
+
billingMode: "customer_oauth",
|
|
259
|
+
inputSchema: {
|
|
260
|
+
type: "object",
|
|
261
|
+
required: ["project_key", "summary"],
|
|
262
|
+
properties: {
|
|
263
|
+
project_key: { type: "string", description: "Jira project key (e.g. ENG)" },
|
|
264
|
+
summary: { type: "string", description: "Issue summary" },
|
|
265
|
+
description: { type: "string", description: "Issue description" },
|
|
266
|
+
issue_type: { type: "string", default: "Task" },
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
slug: "app.hubspot.search_contacts",
|
|
272
|
+
upstreamSlug: "HUBSPOT_SEARCH_CONTACTS",
|
|
273
|
+
name: "HubSpot: Search CRM Contacts",
|
|
274
|
+
description: "Search CRM contacts in the connected HubSpot account via Muxo OAuth.",
|
|
275
|
+
lane: "composio_sdk",
|
|
276
|
+
providerSlug: "hubspot",
|
|
277
|
+
providerName: "HubSpot",
|
|
278
|
+
categories: ["crm", "sales"],
|
|
279
|
+
riskLevel: "read",
|
|
280
|
+
requiresAuth: true,
|
|
281
|
+
creditCost: 0,
|
|
282
|
+
billingMode: "customer_oauth",
|
|
283
|
+
inputSchema: {
|
|
284
|
+
type: "object",
|
|
285
|
+
properties: {
|
|
286
|
+
query: { type: "string", description: "Contact name, email, or company search query" },
|
|
287
|
+
limit: { type: "number", default: 10 },
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
slug: "app.discord.send_message",
|
|
293
|
+
upstreamSlug: "DISCORD_SEND_MESSAGE",
|
|
294
|
+
name: "Discord: Send Channel Message",
|
|
295
|
+
description: "Send a message to a Discord channel using the connected Discord account.",
|
|
296
|
+
lane: "composio_sdk",
|
|
297
|
+
providerSlug: "discord",
|
|
298
|
+
providerName: "Discord",
|
|
299
|
+
categories: ["communication", "chat"],
|
|
300
|
+
riskLevel: "write",
|
|
301
|
+
requiresAuth: true,
|
|
302
|
+
creditCost: 0,
|
|
303
|
+
billingMode: "customer_oauth",
|
|
304
|
+
inputSchema: {
|
|
305
|
+
type: "object",
|
|
306
|
+
required: ["channel_id", "content"],
|
|
307
|
+
properties: {
|
|
308
|
+
channel_id: { type: "string", description: "Discord channel ID" },
|
|
309
|
+
content: { type: "string", description: "Message content" },
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
slug: "app.googledrive.search_files",
|
|
315
|
+
upstreamSlug: "GOOGLEDRIVE_FIND_FILE",
|
|
316
|
+
name: "Google Drive: Search Files & Folders",
|
|
317
|
+
description: "Search files and folders in the connected Google Drive account.",
|
|
318
|
+
lane: "composio_sdk",
|
|
319
|
+
providerSlug: "googledrive",
|
|
320
|
+
providerName: "Google Drive",
|
|
321
|
+
categories: ["storage", "docs", "productivity"],
|
|
322
|
+
riskLevel: "read",
|
|
323
|
+
requiresAuth: true,
|
|
324
|
+
creditCost: 0,
|
|
325
|
+
billingMode: "customer_oauth",
|
|
326
|
+
inputSchema: {
|
|
327
|
+
type: "object",
|
|
328
|
+
properties: {
|
|
329
|
+
query: { type: "string", description: "File or folder search query" },
|
|
330
|
+
pageSize: { type: "number", default: 10 },
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
slug: "app.airtable.list_records",
|
|
336
|
+
upstreamSlug: "AIRTABLE_LIST_RECORDS",
|
|
337
|
+
name: "Airtable: List Base Records",
|
|
338
|
+
description: "List records from an Airtable base and table via Muxo OAuth.",
|
|
339
|
+
lane: "composio_sdk",
|
|
340
|
+
providerSlug: "airtable",
|
|
341
|
+
providerName: "Airtable",
|
|
342
|
+
categories: ["database", "productivity"],
|
|
343
|
+
riskLevel: "read",
|
|
344
|
+
requiresAuth: true,
|
|
345
|
+
creditCost: 0,
|
|
346
|
+
billingMode: "customer_oauth",
|
|
347
|
+
inputSchema: {
|
|
348
|
+
type: "object",
|
|
349
|
+
required: ["base_id", "table_name"],
|
|
350
|
+
properties: {
|
|
351
|
+
base_id: { type: "string", description: "Airtable Base ID" },
|
|
352
|
+
table_name: { type: "string", description: "Table name or ID" },
|
|
353
|
+
max_records: { type: "number", default: 20 },
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
slug: "app.salesforce.query_records",
|
|
359
|
+
upstreamSlug: "SALESFORCE_RUN_SOQL_QUERY",
|
|
360
|
+
name: "Salesforce: Query CRM Records",
|
|
361
|
+
description: "Run a read query against the connected Salesforce CRM org.",
|
|
362
|
+
lane: "composio_sdk",
|
|
363
|
+
providerSlug: "salesforce",
|
|
364
|
+
providerName: "Salesforce",
|
|
365
|
+
categories: ["crm", "sales"],
|
|
366
|
+
riskLevel: "read",
|
|
367
|
+
requiresAuth: true,
|
|
368
|
+
creditCost: 0,
|
|
369
|
+
billingMode: "customer_oauth",
|
|
370
|
+
inputSchema: {
|
|
371
|
+
type: "object",
|
|
372
|
+
required: ["query"],
|
|
373
|
+
properties: {
|
|
374
|
+
query: { type: "string", description: "SOQL query string" },
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
},
|
|
179
378
|
];
|
|
180
379
|
function classifyComposioRisk(slug, tags = []) {
|
|
181
380
|
const upper = `${slug} ${tags.join(" ")}`.toUpperCase();
|
|
@@ -192,11 +391,46 @@ function classifyComposioRisk(slug, tags = []) {
|
|
|
192
391
|
export class ComposioSdkAdapter {
|
|
193
392
|
config;
|
|
194
393
|
liveToolsCache = new Map();
|
|
394
|
+
sdkClients = new Map();
|
|
395
|
+
customAuthConfigs = new Map();
|
|
396
|
+
activeTriggers = new Map();
|
|
397
|
+
triggerEvents = [];
|
|
195
398
|
constructor(config) {
|
|
196
399
|
this.config = config;
|
|
197
400
|
for (const tool of CURATED_APP_TOOLS) {
|
|
198
401
|
this.liveToolsCache.set(tool.slug, tool);
|
|
199
402
|
}
|
|
403
|
+
this.hydrateEnterpriseSampleActions();
|
|
404
|
+
}
|
|
405
|
+
hydrateEnterpriseSampleActions() {
|
|
406
|
+
for (const tk of ENTERPRISE_TOOLKITS_CATALOG) {
|
|
407
|
+
if (isStripeProjectsExclusiveProvider(tk.slug))
|
|
408
|
+
continue;
|
|
409
|
+
for (const act of tk.sampleActions) {
|
|
410
|
+
if (act.muxoSlug.startsWith("resource."))
|
|
411
|
+
continue;
|
|
412
|
+
if (!this.liveToolsCache.has(act.muxoSlug)) {
|
|
413
|
+
this.liveToolsCache.set(act.muxoSlug, {
|
|
414
|
+
slug: act.muxoSlug,
|
|
415
|
+
upstreamSlug: act.upstreamSlug,
|
|
416
|
+
name: act.name,
|
|
417
|
+
description: `${tk.name}: ${act.name} (Muxo Connected Apps toolkit v${tk.pinnedVersion})`,
|
|
418
|
+
lane: "composio_sdk",
|
|
419
|
+
providerSlug: tk.slug,
|
|
420
|
+
providerName: tk.name,
|
|
421
|
+
categories: tk.categories,
|
|
422
|
+
riskLevel: act.riskLevel,
|
|
423
|
+
requiresAuth: true,
|
|
424
|
+
creditCost: act.riskLevel === "read" ? 1 : 2,
|
|
425
|
+
billingMode: "customer_oauth",
|
|
426
|
+
inputSchema: getSampleActionSchema(act.muxoSlug, act.upstreamSlug),
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
resolveApiKey(composioApiKey) {
|
|
433
|
+
return (composioApiKey || this.config.composioApiKey || "").trim();
|
|
200
434
|
}
|
|
201
435
|
get headers() {
|
|
202
436
|
return {
|
|
@@ -204,12 +438,405 @@ export class ComposioSdkAdapter {
|
|
|
204
438
|
"Content-Type": "application/json",
|
|
205
439
|
};
|
|
206
440
|
}
|
|
441
|
+
getHeaders(composioApiKey) {
|
|
442
|
+
return {
|
|
443
|
+
"x-api-key": this.resolveApiKey(composioApiKey),
|
|
444
|
+
"Content-Type": "application/json",
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
getComposioClient(composioApiKey) {
|
|
448
|
+
const apiKey = this.resolveApiKey(composioApiKey);
|
|
449
|
+
if (!apiKey)
|
|
450
|
+
return null;
|
|
451
|
+
const rawBaseUrl = (this.config.composioBaseUrl || "https://backend.composio.dev/api/v3").replace(/\/+$/, "");
|
|
452
|
+
const sdkBaseUrl = rawBaseUrl.replace(/\/api\/v3$/i, "") || "https://backend.composio.dev";
|
|
453
|
+
const cacheKey = `${apiKey}::${sdkBaseUrl}`;
|
|
454
|
+
const existing = this.sdkClients.get(cacheKey);
|
|
455
|
+
if (existing)
|
|
456
|
+
return existing;
|
|
457
|
+
try {
|
|
458
|
+
const client = new Composio({
|
|
459
|
+
apiKey,
|
|
460
|
+
baseURL: sdkBaseUrl,
|
|
461
|
+
allowTracking: false,
|
|
462
|
+
disableVersionCheck: true,
|
|
463
|
+
});
|
|
464
|
+
this.sdkClients.set(cacheKey, client);
|
|
465
|
+
return client;
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
getSdkInfo(composioApiKey) {
|
|
472
|
+
const apiKey = this.resolveApiKey(composioApiKey);
|
|
473
|
+
return {
|
|
474
|
+
sdkPackage: "@composio/core",
|
|
475
|
+
sdkVersion: "0.6.11",
|
|
476
|
+
configured: Boolean(apiKey),
|
|
477
|
+
usingCustomerByok: Boolean(composioApiKey && composioApiKey.trim()),
|
|
478
|
+
baseUrl: this.config.composioBaseUrl,
|
|
479
|
+
curatedToolCount: CURATED_APP_TOOLS.length,
|
|
480
|
+
cachedToolCount: this.liveToolsCache.size,
|
|
481
|
+
capabilities: [
|
|
482
|
+
"tools.getRawComposioTools",
|
|
483
|
+
"tools.getRawComposioToolBySlug",
|
|
484
|
+
"tools.execute",
|
|
485
|
+
"toolkits.authorize",
|
|
486
|
+
"connectedAccounts.initiate",
|
|
487
|
+
"connectedAccounts.get",
|
|
488
|
+
"connectedAccounts.list",
|
|
489
|
+
"authConfigs.list",
|
|
490
|
+
"triggers.getType",
|
|
491
|
+
"triggers.create",
|
|
492
|
+
"triggers.subscribe",
|
|
493
|
+
"triggers.verifyWebhook",
|
|
494
|
+
],
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
getWebhookConfigInfo() {
|
|
498
|
+
return {
|
|
499
|
+
webhookUrl: "https://api.muxo.ai/webhooks",
|
|
500
|
+
legacyWebhookUrl: "https://api.muxo.ai/api/v1/triggers/webhook",
|
|
501
|
+
version: "V3",
|
|
502
|
+
events: [...COMPOSIO_V3_WEBHOOK_EVENTS],
|
|
503
|
+
secretConfigured: Boolean(this.config.composioWebhookSecret),
|
|
504
|
+
subscriptionId: this.config.composioWebhookSubscriptionId || null,
|
|
505
|
+
projectId: this.config.composioProjectId || null,
|
|
506
|
+
orgId: this.config.composioOrgId || null,
|
|
507
|
+
userId: this.config.composioUserId || null,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
207
510
|
getCuratedTools() {
|
|
208
511
|
return Array.from(this.liveToolsCache.values());
|
|
209
512
|
}
|
|
210
|
-
|
|
513
|
+
static classifyActionRisk(upstreamSlug, description = "") {
|
|
514
|
+
return classifyComposioRisk(upstreamSlug, [description]);
|
|
515
|
+
}
|
|
516
|
+
async listToolkits(params) {
|
|
517
|
+
// Hydrate dynamic sample actions into Muxo's liveToolsCache so agents can plan, inspect, and execute them immediately
|
|
518
|
+
this.hydrateEnterpriseSampleActions();
|
|
519
|
+
const list = filterComposioToolkits(params);
|
|
520
|
+
return {
|
|
521
|
+
toolkits: list,
|
|
522
|
+
total: list.length,
|
|
523
|
+
source: Boolean(this.resolveApiKey(params?.composioApiKey))
|
|
524
|
+
? "composio_v3_sdk"
|
|
525
|
+
: "muxo_enterprise_catalog",
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
async createCustomAuthConfig(params) {
|
|
529
|
+
const tkSlug = params.toolkitSlug.toLowerCase().trim();
|
|
530
|
+
const scopeMode = params.scopeMode || "read_only";
|
|
531
|
+
const scopes = composioAuthConfigScopes(tkSlug, scopeMode, params.customScopes);
|
|
532
|
+
const sdk = this.getComposioClient(params.composioApiKey);
|
|
533
|
+
if (sdk) {
|
|
534
|
+
try {
|
|
535
|
+
const created = await sdk.authConfigs.create(tkSlug, {
|
|
536
|
+
name: params.name || `muxo_${params.workspaceId}_${tkSlug}_${scopeMode}`,
|
|
537
|
+
type: params.credentials?.client_id
|
|
538
|
+
? "use_custom_auth"
|
|
539
|
+
: "use_composio_managed_auth",
|
|
540
|
+
authScheme: params.authScheme,
|
|
541
|
+
credentials: {
|
|
542
|
+
...(params.credentials || {}),
|
|
543
|
+
scopes: scopes.join(","),
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
const id = String(created?.id || `ac_${tkSlug}_${Date.now()}`);
|
|
547
|
+
this.customAuthConfigs.set(`${params.workspaceId}:${tkSlug}`, {
|
|
548
|
+
authConfigId: id,
|
|
549
|
+
toolkitSlug: tkSlug,
|
|
550
|
+
authScheme: params.authScheme,
|
|
551
|
+
scopeMode,
|
|
552
|
+
scopes,
|
|
553
|
+
});
|
|
554
|
+
return {
|
|
555
|
+
authConfigId: id,
|
|
556
|
+
toolkitSlug: tkSlug,
|
|
557
|
+
authScheme: params.authScheme,
|
|
558
|
+
scopeMode,
|
|
559
|
+
scopes,
|
|
560
|
+
source: "composio_v3_sdk",
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
// Fall through to deterministic local auth config registration
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const fallbackId = `ac_muxo_${tkSlug}_${scopeMode}_${Buffer.from(params.workspaceId).toString("hex").slice(0, 8)}`;
|
|
568
|
+
this.customAuthConfigs.set(`${params.workspaceId}:${tkSlug}`, {
|
|
569
|
+
authConfigId: fallbackId,
|
|
570
|
+
toolkitSlug: tkSlug,
|
|
571
|
+
authScheme: params.authScheme,
|
|
572
|
+
scopeMode,
|
|
573
|
+
scopes,
|
|
574
|
+
});
|
|
575
|
+
return {
|
|
576
|
+
authConfigId: fallbackId,
|
|
577
|
+
toolkitSlug: tkSlug,
|
|
578
|
+
authScheme: params.authScheme,
|
|
579
|
+
scopeMode,
|
|
580
|
+
scopes,
|
|
581
|
+
source: "muxo_managed",
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
listTriggerTypes(providerSlug) {
|
|
585
|
+
return listComposioTriggerTypes(providerSlug);
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Check what configuration is required for a trigger (`composio.triggers.getType(slug)`).
|
|
589
|
+
*/
|
|
590
|
+
async getTriggerType(triggerSlug, composioApiKey) {
|
|
591
|
+
const normalizedSlug = String(triggerSlug || "GITHUB_COMMIT_EVENT")
|
|
592
|
+
.toUpperCase()
|
|
593
|
+
.trim();
|
|
594
|
+
const fallback = getComposioTriggerType(normalizedSlug);
|
|
595
|
+
const sdk = this.getComposioClient(composioApiKey);
|
|
596
|
+
if (sdk) {
|
|
597
|
+
try {
|
|
598
|
+
const triggerType = await Promise.race([
|
|
599
|
+
sdk.triggers.getType(normalizedSlug),
|
|
600
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK triggers.getType timeout")), 4500)),
|
|
601
|
+
]);
|
|
602
|
+
if (triggerType) {
|
|
603
|
+
const toolkitSlug = String(triggerType.toolkit?.slug || fallback.providerSlug).toLowerCase();
|
|
604
|
+
return {
|
|
605
|
+
slug: String(triggerType.slug || normalizedSlug),
|
|
606
|
+
providerSlug: toolkitSlug,
|
|
607
|
+
name: String(triggerType.name || fallback.name),
|
|
608
|
+
description: String(triggerType.description || fallback.description),
|
|
609
|
+
config: triggerType.config && typeof triggerType.config === "object"
|
|
610
|
+
? triggerType.config
|
|
611
|
+
: fallback.config,
|
|
612
|
+
source: "composio_v3_sdk",
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
catch {
|
|
617
|
+
// Fall back to Muxo trigger catalog when offline or unconfigured
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
...fallback,
|
|
622
|
+
source: "muxo_trigger_catalog",
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Create a trigger with required config (`composio.triggers.create(userId, slug, { triggerConfig })`)
|
|
627
|
+
* and optionally subscribe to real-time trigger events (`composio.triggers.subscribe`).
|
|
628
|
+
*/
|
|
629
|
+
async enableTrigger(params) {
|
|
630
|
+
const normalizedTriggerSlug = params.triggerSlug.toUpperCase().trim();
|
|
631
|
+
const triggerType = await this.getTriggerType(normalizedTriggerSlug, params.composioApiKey);
|
|
632
|
+
const providerSlug = (params.providerSlug ||
|
|
633
|
+
triggerType.providerSlug ||
|
|
634
|
+
"github")
|
|
635
|
+
.toLowerCase()
|
|
636
|
+
.trim();
|
|
637
|
+
const userId = params.userId || this.config.composioUserId || params.workspaceId;
|
|
638
|
+
const triggerConfig = params.config || {};
|
|
639
|
+
let upstreamTriggerId;
|
|
640
|
+
let source = "muxo_managed";
|
|
641
|
+
const sdk = this.getComposioClient(params.composioApiKey);
|
|
642
|
+
if (sdk) {
|
|
643
|
+
try {
|
|
644
|
+
const created = await Promise.race([
|
|
645
|
+
sdk.triggers.create(userId, normalizedTriggerSlug, {
|
|
646
|
+
...(params.connectedAccountId
|
|
647
|
+
? { connectedAccountId: params.connectedAccountId }
|
|
648
|
+
: {}),
|
|
649
|
+
triggerConfig,
|
|
650
|
+
}),
|
|
651
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK triggers.create timeout")), 6000)),
|
|
652
|
+
]);
|
|
653
|
+
if (created?.triggerId) {
|
|
654
|
+
upstreamTriggerId = String(created.triggerId);
|
|
655
|
+
source = "composio_v3_sdk";
|
|
656
|
+
if (params.subscribe) {
|
|
657
|
+
await this.subscribeToTriggerEvents({
|
|
658
|
+
workspaceId: params.workspaceId,
|
|
659
|
+
triggerId: upstreamTriggerId,
|
|
660
|
+
composioApiKey: params.composioApiKey,
|
|
661
|
+
}).catch(() => { });
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
// Fall through to deterministic local subscription record
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const id = upstreamTriggerId ||
|
|
670
|
+
`trg_${providerSlug}_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
671
|
+
const sub = {
|
|
672
|
+
id,
|
|
673
|
+
upstreamTriggerId,
|
|
674
|
+
workspaceId: params.workspaceId,
|
|
675
|
+
userId,
|
|
676
|
+
triggerSlug: normalizedTriggerSlug,
|
|
677
|
+
providerSlug,
|
|
678
|
+
connectedAccountId: params.connectedAccountId || `ca_${providerSlug}_${params.workspaceId.slice(0, 8)}`,
|
|
679
|
+
webhookUrl: params.webhookUrl ||
|
|
680
|
+
`https://api.muxo.ai/webhooks`,
|
|
681
|
+
config: triggerConfig,
|
|
682
|
+
status: "active",
|
|
683
|
+
source,
|
|
684
|
+
createdAt: new Date().toISOString(),
|
|
685
|
+
};
|
|
686
|
+
this.activeTriggers.set(id, sub);
|
|
687
|
+
return sub;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Subscribe to real-time trigger events via `@composio/core` (`composio.triggers.subscribe`).
|
|
691
|
+
*/
|
|
692
|
+
async subscribeToTriggerEvents(params) {
|
|
693
|
+
const sdk = this.getComposioClient(params.composioApiKey);
|
|
694
|
+
if (!sdk) {
|
|
695
|
+
return { subscribed: false, triggerId: params.triggerId };
|
|
696
|
+
}
|
|
697
|
+
await sdk.triggers.subscribe((data) => {
|
|
698
|
+
const recordPayload = (data?.payload || data || {});
|
|
699
|
+
const triggerSlug = String(data?.triggerSlug || "GITHUB_COMMIT_EVENT").toUpperCase();
|
|
700
|
+
const providerSlug = String(data?.toolkitSlug || triggerSlug.split("_")[0] || "github").toLowerCase();
|
|
701
|
+
this.recordTriggerEvent({
|
|
702
|
+
id: `trgevt_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
|
|
703
|
+
workspaceId: params.workspaceId,
|
|
704
|
+
eventType: "composio.trigger.message",
|
|
705
|
+
webhookVersion: "V3",
|
|
706
|
+
triggerId: data?.id || params.triggerId,
|
|
707
|
+
triggerSlug,
|
|
708
|
+
providerSlug,
|
|
709
|
+
connectedAccountId: data?.metadata?.connectedAccount?.id,
|
|
710
|
+
userId: data?.userId || data?.metadata?.connectedAccount?.userId,
|
|
711
|
+
receivedAt: new Date().toISOString(),
|
|
712
|
+
promptInjectionScan: {
|
|
713
|
+
safe: true,
|
|
714
|
+
riskScore: 0,
|
|
715
|
+
findings: [],
|
|
716
|
+
},
|
|
717
|
+
disposition: "dispatched",
|
|
718
|
+
payloadPreview: recordPayload,
|
|
719
|
+
});
|
|
720
|
+
params.onEvent?.(data);
|
|
721
|
+
}, {
|
|
722
|
+
...(params.triggerId ? { triggerId: params.triggerId } : {}),
|
|
723
|
+
...(params.toolkits ? { toolkits: params.toolkits } : {}),
|
|
724
|
+
});
|
|
725
|
+
return { subscribed: true, triggerId: params.triggerId };
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Verify an incoming Composio webhook payload and HMAC-SHA256 signature.
|
|
729
|
+
*/
|
|
730
|
+
async verifyWebhook(params) {
|
|
731
|
+
const secret = (params.secret ?? this.config.composioWebhookSecret ?? "").trim();
|
|
732
|
+
const sigCheck = await verifyComposioWebhookSignature({
|
|
733
|
+
rawPayload: params.rawPayload,
|
|
734
|
+
signatureHeader: params.signatureHeader,
|
|
735
|
+
webhookIdHeader: params.webhookIdHeader,
|
|
736
|
+
webhookTimestampHeader: params.webhookTimestampHeader,
|
|
737
|
+
secret,
|
|
738
|
+
toleranceSeconds: params.toleranceSeconds,
|
|
739
|
+
});
|
|
740
|
+
let parsedJson = {};
|
|
741
|
+
try {
|
|
742
|
+
parsedJson = JSON.parse(params.rawPayload || "{}");
|
|
743
|
+
}
|
|
744
|
+
catch {
|
|
745
|
+
parsedJson = {};
|
|
746
|
+
}
|
|
747
|
+
const envelope = parseComposioWebhookEnvelope(parsedJson, {
|
|
748
|
+
webhookId: params.webhookIdHeader,
|
|
749
|
+
webhookTimestamp: params.webhookTimestampHeader,
|
|
750
|
+
});
|
|
751
|
+
return {
|
|
752
|
+
verified: sigCheck.verified,
|
|
753
|
+
reason: sigCheck.reason,
|
|
754
|
+
envelope,
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
disableTriggersMatching(params) {
|
|
758
|
+
let count = 0;
|
|
759
|
+
for (const sub of this.activeTriggers.values()) {
|
|
760
|
+
if (params.workspaceId && sub.workspaceId !== params.workspaceId)
|
|
761
|
+
continue;
|
|
762
|
+
const matchesTriggerId = Boolean(params.triggerId) &&
|
|
763
|
+
(sub.id === params.triggerId || sub.upstreamTriggerId === params.triggerId);
|
|
764
|
+
const matchesConnectedAccount = Boolean(params.connectedAccountId) &&
|
|
765
|
+
sub.connectedAccountId === params.connectedAccountId;
|
|
766
|
+
const matchesSlug = Boolean(params.triggerSlug) &&
|
|
767
|
+
sub.triggerSlug.toUpperCase() === params.triggerSlug.toUpperCase();
|
|
768
|
+
if (matchesTriggerId || matchesConnectedAccount || matchesSlug) {
|
|
769
|
+
sub.status = "disabled";
|
|
770
|
+
count += 1;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return count;
|
|
774
|
+
}
|
|
775
|
+
listActiveTriggers(workspaceId) {
|
|
776
|
+
return Array.from(this.activeTriggers.values()).filter((t) => t.workspaceId === workspaceId);
|
|
777
|
+
}
|
|
778
|
+
recordTriggerEvent(record) {
|
|
779
|
+
this.triggerEvents.push(record);
|
|
780
|
+
if (this.triggerEvents.length > 500) {
|
|
781
|
+
this.triggerEvents.shift();
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
listTriggerEvents(workspaceId, limit = 50) {
|
|
785
|
+
return this.triggerEvents
|
|
786
|
+
.filter((e) => e.workspaceId === workspaceId)
|
|
787
|
+
.slice(-Math.max(1, limit))
|
|
788
|
+
.reverse();
|
|
789
|
+
}
|
|
790
|
+
mapRawComposioItemToMuxoTool(item) {
|
|
791
|
+
const upstreamSlug = String(item.slug || item.name || "");
|
|
792
|
+
if (!upstreamSlug)
|
|
793
|
+
return null;
|
|
794
|
+
const toolkitObj = (item.toolkit || {});
|
|
795
|
+
const providerSlug = String(toolkitObj.slug || upstreamSlug.split("_")[0] || "app").toLowerCase();
|
|
796
|
+
// Never surface Stripe Projects cloud infrastructure providers as Composio OAuth tools
|
|
797
|
+
if (isStripeProjectsExclusiveProvider(providerSlug)) {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
const providerName = String(toolkitObj.name || providerSlug.charAt(0).toUpperCase() + providerSlug.slice(1));
|
|
801
|
+
const actionPart = upstreamSlug
|
|
802
|
+
.toLowerCase()
|
|
803
|
+
.replace(new RegExp(`^${providerSlug}_`), "");
|
|
804
|
+
const muxoSlug = `app.${providerSlug}.${actionPart}`;
|
|
805
|
+
const rawTags = Array.isArray(item.tags) ? item.tags.map(String) : [providerSlug];
|
|
806
|
+
const inputParams = item.inputParameters ||
|
|
807
|
+
item.input_parameters || {
|
|
808
|
+
type: "object",
|
|
809
|
+
properties: {},
|
|
810
|
+
};
|
|
811
|
+
const noAuth = Boolean(item.noAuth ?? item.no_auth);
|
|
812
|
+
const tool = {
|
|
813
|
+
slug: muxoSlug,
|
|
814
|
+
upstreamSlug,
|
|
815
|
+
name: `${providerName}: ${String(item.name || actionPart)}`,
|
|
816
|
+
description: String(item.description || `Execute ${upstreamSlug} via Muxo Connected Apps`),
|
|
817
|
+
lane: "composio_sdk",
|
|
818
|
+
providerSlug,
|
|
819
|
+
providerName,
|
|
820
|
+
categories: rawTags,
|
|
821
|
+
riskLevel: classifyComposioRisk(upstreamSlug, rawTags),
|
|
822
|
+
requiresAuth: !noAuth,
|
|
823
|
+
creditCost: 0,
|
|
824
|
+
billingMode: "customer_oauth",
|
|
825
|
+
inputSchema: inputParams,
|
|
826
|
+
};
|
|
827
|
+
this.liveToolsCache.set(muxoSlug, tool);
|
|
828
|
+
return tool;
|
|
829
|
+
}
|
|
830
|
+
async searchTools(query, limit = 15, composioApiKey, toolkit) {
|
|
211
831
|
const q = (query || "").trim().toLowerCase();
|
|
832
|
+
const tk = (toolkit || "").trim().toLowerCase();
|
|
833
|
+
// Stripe Projects infrastructure providers belong to stripe_projects lane, not Composio OAuth
|
|
834
|
+
if (tk && isStripeProjectsExclusiveProvider(tk)) {
|
|
835
|
+
return [];
|
|
836
|
+
}
|
|
212
837
|
const localMatches = this.getCuratedTools().filter((t) => {
|
|
838
|
+
if (tk && t.providerSlug.toLowerCase() !== tk)
|
|
839
|
+
return false;
|
|
213
840
|
if (!q)
|
|
214
841
|
return true;
|
|
215
842
|
return (t.slug.toLowerCase().includes(q) ||
|
|
@@ -217,82 +844,119 @@ export class ComposioSdkAdapter {
|
|
|
217
844
|
t.description.toLowerCase().includes(q) ||
|
|
218
845
|
t.providerSlug.toLowerCase().includes(q));
|
|
219
846
|
});
|
|
220
|
-
|
|
847
|
+
const apiKey = this.resolveApiKey(composioApiKey);
|
|
848
|
+
if ((!q && !tk) || !apiKey) {
|
|
221
849
|
return localMatches.slice(0, limit);
|
|
222
850
|
}
|
|
851
|
+
const merged = new Map();
|
|
852
|
+
for (const m of localMatches)
|
|
853
|
+
merged.set(m.slug, m);
|
|
854
|
+
// 1. Official @composio/core SDK tool discovery
|
|
855
|
+
const sdk = this.getComposioClient(composioApiKey);
|
|
856
|
+
if (sdk) {
|
|
857
|
+
try {
|
|
858
|
+
const rawTools = await Promise.race([
|
|
859
|
+
sdk.tools.getRawComposioTools({
|
|
860
|
+
search: q || undefined,
|
|
861
|
+
toolkits: tk ? [tk] : undefined,
|
|
862
|
+
limit: Math.min(limit, 20),
|
|
863
|
+
}),
|
|
864
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK search timeout")), 4500)),
|
|
865
|
+
]);
|
|
866
|
+
if (Array.isArray(rawTools)) {
|
|
867
|
+
for (const rawItem of rawTools) {
|
|
868
|
+
const tool = this.mapRawComposioItemToMuxoTool(rawItem);
|
|
869
|
+
if (tool && !merged.has(tool.slug)) {
|
|
870
|
+
merged.set(tool.slug, tool);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (merged.size > localMatches.length) {
|
|
874
|
+
return Array.from(merged.values()).slice(0, limit);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
catch {
|
|
879
|
+
// Fall through to direct v3 REST endpoint
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
// 2. Direct Composio v3 API fallback
|
|
223
883
|
try {
|
|
224
884
|
const url = new URL(`${this.config.composioBaseUrl}/tools`);
|
|
225
|
-
|
|
885
|
+
if (q)
|
|
886
|
+
url.searchParams.set("search", q);
|
|
887
|
+
if (tk)
|
|
888
|
+
url.searchParams.set("toolkit_slug", tk);
|
|
226
889
|
url.searchParams.set("limit", String(Math.min(limit, 15)));
|
|
227
890
|
const res = await fetch(url.toString(), {
|
|
228
|
-
headers: this.
|
|
891
|
+
headers: this.getHeaders(composioApiKey),
|
|
229
892
|
signal: AbortSignal.timeout(4500),
|
|
230
893
|
});
|
|
231
894
|
if (!res.ok)
|
|
232
|
-
return
|
|
895
|
+
return Array.from(merged.values()).slice(0, limit);
|
|
233
896
|
const data = (await res.json());
|
|
234
897
|
const items = Array.isArray(data.items) ? data.items : [];
|
|
235
|
-
const merged = new Map();
|
|
236
|
-
for (const m of localMatches)
|
|
237
|
-
merged.set(m.slug, m);
|
|
238
898
|
for (const item of items) {
|
|
239
|
-
const
|
|
240
|
-
if (!
|
|
241
|
-
|
|
242
|
-
const toolkitObj = (item.toolkit || {});
|
|
243
|
-
const providerSlug = String(toolkitObj.slug || upstreamSlug.split("_")[0] || "app").toLowerCase();
|
|
244
|
-
const providerName = String(toolkitObj.name || providerSlug.charAt(0).toUpperCase() + providerSlug.slice(1));
|
|
245
|
-
const actionPart = upstreamSlug
|
|
246
|
-
.toLowerCase()
|
|
247
|
-
.replace(new RegExp(`^${providerSlug}_`), "");
|
|
248
|
-
const muxoSlug = `app.${providerSlug}.${actionPart}`;
|
|
249
|
-
const tool = {
|
|
250
|
-
slug: muxoSlug,
|
|
251
|
-
upstreamSlug,
|
|
252
|
-
name: `${providerName}: ${String(item.name || actionPart)}`,
|
|
253
|
-
description: String(item.description || `Execute ${upstreamSlug} via Composio`),
|
|
254
|
-
lane: "composio_sdk",
|
|
255
|
-
providerSlug,
|
|
256
|
-
providerName,
|
|
257
|
-
categories: Array.isArray(item.tags) ? item.tags.map(String) : [providerSlug],
|
|
258
|
-
riskLevel: classifyComposioRisk(upstreamSlug, Array.isArray(item.tags) ? item.tags.map(String) : []),
|
|
259
|
-
requiresAuth: !Boolean(item.no_auth),
|
|
260
|
-
creditCost: 0,
|
|
261
|
-
billingMode: "customer_oauth",
|
|
262
|
-
inputSchema: item.input_parameters || {
|
|
263
|
-
type: "object",
|
|
264
|
-
properties: {},
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
this.liveToolsCache.set(muxoSlug, tool);
|
|
268
|
-
if (!merged.has(muxoSlug)) {
|
|
269
|
-
merged.set(muxoSlug, tool);
|
|
899
|
+
const tool = this.mapRawComposioItemToMuxoTool(item);
|
|
900
|
+
if (tool && !merged.has(tool.slug)) {
|
|
901
|
+
merged.set(tool.slug, tool);
|
|
270
902
|
}
|
|
271
903
|
}
|
|
272
904
|
return Array.from(merged.values()).slice(0, limit);
|
|
273
905
|
}
|
|
274
906
|
catch {
|
|
275
|
-
return
|
|
907
|
+
return Array.from(merged.values()).slice(0, limit);
|
|
276
908
|
}
|
|
277
909
|
}
|
|
278
|
-
async inspectTool(slug) {
|
|
910
|
+
async inspectTool(slug, composioApiKey) {
|
|
279
911
|
const normalizedSlug = slug === "app.gmail.list_messages" ? "app.gmail.fetch_emails" : slug;
|
|
280
912
|
if (this.liveToolsCache.has(normalizedSlug)) {
|
|
281
913
|
return this.liveToolsCache.get(normalizedSlug);
|
|
282
914
|
}
|
|
283
|
-
|
|
915
|
+
const apiKey = this.resolveApiKey(composioApiKey);
|
|
916
|
+
if (!normalizedSlug.startsWith("app.") || !apiKey)
|
|
284
917
|
return null;
|
|
285
918
|
const parts = normalizedSlug.split(".");
|
|
919
|
+
if (parts.length >= 2 && isStripeProjectsExclusiveProvider(parts[1] || "")) {
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
if (parts.length >= 3) {
|
|
923
|
+
const upstreamCandidate = parts.slice(1).join("_").toUpperCase();
|
|
924
|
+
const sdk = this.getComposioClient(composioApiKey);
|
|
925
|
+
if (sdk) {
|
|
926
|
+
try {
|
|
927
|
+
const rawTool = await Promise.race([
|
|
928
|
+
sdk.tools.getRawComposioToolBySlug(upstreamCandidate),
|
|
929
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK inspect timeout")), 4000)),
|
|
930
|
+
]);
|
|
931
|
+
if (rawTool) {
|
|
932
|
+
const mapped = this.mapRawComposioItemToMuxoTool(rawTool);
|
|
933
|
+
if (mapped)
|
|
934
|
+
return mapped;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
catch {
|
|
938
|
+
// Fall through to searchTools
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
286
942
|
const query = parts.slice(1).join(" ");
|
|
287
|
-
const matches = await this.searchTools(query, 10);
|
|
288
|
-
return
|
|
943
|
+
const matches = await this.searchTools(query, 10, composioApiKey);
|
|
944
|
+
return matchAppToolSlug(normalizedSlug, matches);
|
|
289
945
|
}
|
|
290
946
|
async startConnection(params) {
|
|
291
947
|
const providerSlug = params.providerSlug.toLowerCase().replace(/^app\./, "");
|
|
292
948
|
const providerName = providerSlug.charAt(0).toUpperCase() + providerSlug.slice(1);
|
|
293
|
-
const
|
|
949
|
+
const rawCallbackUrl = params.callbackUrl ||
|
|
294
950
|
`${this.config.baseUrl}/oauth/callback?workspace_id=${encodeURIComponent(params.workspaceId)}&provider=${encodeURIComponent(providerSlug)}`;
|
|
295
|
-
|
|
951
|
+
const callbackUrl = ensureOAuthCallbackParams(rawCallbackUrl, params.workspaceId, providerSlug);
|
|
952
|
+
const wrapRedirect = (url, externalAccountId) => wrapMuxoConnectUrl(url, this.config.baseUrl, {
|
|
953
|
+
workspaceId: params.workspaceId,
|
|
954
|
+
providerSlug,
|
|
955
|
+
callbackUrl,
|
|
956
|
+
externalAccountId: externalAccountId || null,
|
|
957
|
+
});
|
|
958
|
+
const apiKey = this.resolveApiKey(params.composioApiKey);
|
|
959
|
+
if (!apiKey) {
|
|
296
960
|
const externalAccountId = `ca_ext_${providerSlug}_${params.workspaceId.slice(-8)}`;
|
|
297
961
|
return {
|
|
298
962
|
providerSlug,
|
|
@@ -304,11 +968,11 @@ export class ComposioSdkAdapter {
|
|
|
304
968
|
message: `Open the authorization URL to connect ${providerName} to your Muxo workspace.`,
|
|
305
969
|
};
|
|
306
970
|
}
|
|
307
|
-
// 1. Try creating a
|
|
971
|
+
// 1. Try creating a Tool Router Session & link for the workspace user (supports expected_input_fields + callback_url)
|
|
308
972
|
try {
|
|
309
973
|
const sessionRes = await fetch(`${this.config.composioBaseUrl}/tool_router/session`, {
|
|
310
974
|
method: "POST",
|
|
311
|
-
headers: this.
|
|
975
|
+
headers: this.getHeaders(params.composioApiKey),
|
|
312
976
|
body: JSON.stringify({
|
|
313
977
|
user_id: params.workspaceId,
|
|
314
978
|
toolkits: { enable: [providerSlug] },
|
|
@@ -321,7 +985,7 @@ export class ComposioSdkAdapter {
|
|
|
321
985
|
if (sessionId) {
|
|
322
986
|
const linkRes = await fetch(`${this.config.composioBaseUrl}/tool_router/session/${sessionId}/link`, {
|
|
323
987
|
method: "POST",
|
|
324
|
-
headers: this.
|
|
988
|
+
headers: this.getHeaders(params.composioApiKey),
|
|
325
989
|
body: JSON.stringify({
|
|
326
990
|
toolkit: providerSlug,
|
|
327
991
|
callback_url: callbackUrl,
|
|
@@ -331,13 +995,14 @@ export class ComposioSdkAdapter {
|
|
|
331
995
|
if (linkRes.ok) {
|
|
332
996
|
const linkData = (await linkRes.json());
|
|
333
997
|
if (linkData.redirect_url) {
|
|
998
|
+
const extId = linkData.connected_account_id || sessionId;
|
|
334
999
|
return {
|
|
335
1000
|
providerSlug,
|
|
336
1001
|
providerName,
|
|
337
1002
|
status: "pending",
|
|
338
|
-
redirectUrl: linkData.redirect_url,
|
|
1003
|
+
redirectUrl: wrapRedirect(linkData.redirect_url, extId),
|
|
339
1004
|
callbackUrl,
|
|
340
|
-
externalAccountId:
|
|
1005
|
+
externalAccountId: extId,
|
|
341
1006
|
message: `Open the authorization URL to connect ${providerName} to your Muxo workspace.`,
|
|
342
1007
|
};
|
|
343
1008
|
}
|
|
@@ -346,11 +1011,74 @@ export class ComposioSdkAdapter {
|
|
|
346
1011
|
}
|
|
347
1012
|
}
|
|
348
1013
|
catch {
|
|
349
|
-
// Fall through to
|
|
1014
|
+
// Fall through to @composio/core SDK / connected_accounts flow
|
|
1015
|
+
}
|
|
1016
|
+
// 2. Official @composio/core SDK Connected Accounts initiation fallback
|
|
1017
|
+
const sdk = this.getComposioClient(params.composioApiKey);
|
|
1018
|
+
if (sdk) {
|
|
1019
|
+
try {
|
|
1020
|
+
const authConfigs = await Promise.race([
|
|
1021
|
+
sdk.authConfigs.list({ toolkitSlug: providerSlug }),
|
|
1022
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK authConfigs timeout")), 5000)),
|
|
1023
|
+
]);
|
|
1024
|
+
const authConfigId = authConfigs?.items?.[0]?.id;
|
|
1025
|
+
if (authConfigId) {
|
|
1026
|
+
const connRequest = await Promise.race([
|
|
1027
|
+
sdk.connectedAccounts.initiate(params.workspaceId, authConfigId, {
|
|
1028
|
+
callbackUrl,
|
|
1029
|
+
allowMultiple: true,
|
|
1030
|
+
}),
|
|
1031
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK initiate timeout")), 6000)),
|
|
1032
|
+
]);
|
|
1033
|
+
const externalAccountId = connRequest?.id || null;
|
|
1034
|
+
const rawRedirectUrl = connRequest?.redirectUrl ||
|
|
1035
|
+
(externalAccountId
|
|
1036
|
+
? `${callbackUrl}&connected_account_id=${encodeURIComponent(externalAccountId)}`
|
|
1037
|
+
: null);
|
|
1038
|
+
const redirectUrl = wrapRedirect(rawRedirectUrl, externalAccountId);
|
|
1039
|
+
if (redirectUrl) {
|
|
1040
|
+
return {
|
|
1041
|
+
providerSlug,
|
|
1042
|
+
providerName,
|
|
1043
|
+
status: "pending",
|
|
1044
|
+
redirectUrl,
|
|
1045
|
+
callbackUrl,
|
|
1046
|
+
externalAccountId,
|
|
1047
|
+
message: `Complete ${providerName} OAuth authorization in your browser.`,
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
else {
|
|
1052
|
+
const authReq = await Promise.race([
|
|
1053
|
+
sdk.toolkits.authorize(params.workspaceId, providerSlug),
|
|
1054
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK toolkits.authorize timeout")), 6000)),
|
|
1055
|
+
]);
|
|
1056
|
+
const externalAccountId = authReq?.id || null;
|
|
1057
|
+
const rawRedirectUrl = authReq?.redirectUrl ||
|
|
1058
|
+
(externalAccountId
|
|
1059
|
+
? `${callbackUrl}&connected_account_id=${encodeURIComponent(externalAccountId)}`
|
|
1060
|
+
: null);
|
|
1061
|
+
const redirectUrl = wrapRedirect(rawRedirectUrl, externalAccountId);
|
|
1062
|
+
if (redirectUrl) {
|
|
1063
|
+
return {
|
|
1064
|
+
providerSlug,
|
|
1065
|
+
providerName,
|
|
1066
|
+
status: "pending",
|
|
1067
|
+
redirectUrl,
|
|
1068
|
+
callbackUrl,
|
|
1069
|
+
externalAccountId,
|
|
1070
|
+
message: `Complete ${providerName} OAuth authorization in your browser.`,
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
catch {
|
|
1076
|
+
// Fall through to v3 API fallback
|
|
1077
|
+
}
|
|
350
1078
|
}
|
|
351
|
-
//
|
|
1079
|
+
// 3. Fallback: look up or create an auth_config for the toolkit and initiate connected_accounts
|
|
352
1080
|
const authCfgRes = await fetch(`${this.config.composioBaseUrl}/auth_configs?toolkit_slug=${encodeURIComponent(providerSlug)}&limit=5`, {
|
|
353
|
-
headers: this.
|
|
1081
|
+
headers: this.getHeaders(params.composioApiKey),
|
|
354
1082
|
signal: AbortSignal.timeout(6000),
|
|
355
1083
|
});
|
|
356
1084
|
if (authCfgRes.ok) {
|
|
@@ -359,7 +1087,7 @@ export class ComposioSdkAdapter {
|
|
|
359
1087
|
if (authConfigId) {
|
|
360
1088
|
const connRes = await fetch(`${this.config.composioBaseUrl}/connected_accounts`, {
|
|
361
1089
|
method: "POST",
|
|
362
|
-
headers: this.
|
|
1090
|
+
headers: this.getHeaders(params.composioApiKey),
|
|
363
1091
|
body: JSON.stringify({
|
|
364
1092
|
auth_config: { id: authConfigId },
|
|
365
1093
|
connection: {
|
|
@@ -371,7 +1099,11 @@ export class ComposioSdkAdapter {
|
|
|
371
1099
|
});
|
|
372
1100
|
if (connRes.ok) {
|
|
373
1101
|
const connData = (await connRes.json());
|
|
374
|
-
const
|
|
1102
|
+
const fallbackRedirect = `${callbackUrl}&connected_account_id=${encodeURIComponent(connData.id || `ca_${providerSlug}`)}`;
|
|
1103
|
+
const rawRedirectUrl = connData.redirect_url ||
|
|
1104
|
+
connData.connectionData?.val?.redirectUrl ||
|
|
1105
|
+
fallbackRedirect;
|
|
1106
|
+
const redirectUrl = wrapRedirect(rawRedirectUrl, connData.id || null);
|
|
375
1107
|
return {
|
|
376
1108
|
providerSlug,
|
|
377
1109
|
providerName,
|
|
@@ -379,21 +1111,57 @@ export class ComposioSdkAdapter {
|
|
|
379
1111
|
redirectUrl,
|
|
380
1112
|
callbackUrl,
|
|
381
1113
|
externalAccountId: connData.id || null,
|
|
382
|
-
message:
|
|
383
|
-
? `Complete ${providerName} OAuth authorization in your browser.`
|
|
384
|
-
: `Created ${providerName} connection request (${connData.id}).`,
|
|
1114
|
+
message: `Complete ${providerName} OAuth authorization in your browser.`,
|
|
385
1115
|
};
|
|
386
1116
|
}
|
|
387
1117
|
}
|
|
388
1118
|
}
|
|
389
|
-
|
|
1119
|
+
const fallbackExtId = `ca_ext_${providerSlug}_${params.workspaceId.slice(-8)}`;
|
|
1120
|
+
return {
|
|
1121
|
+
providerSlug,
|
|
1122
|
+
providerName,
|
|
1123
|
+
status: "pending",
|
|
1124
|
+
redirectUrl: `${callbackUrl}&connected_account_id=${encodeURIComponent(fallbackExtId)}`,
|
|
1125
|
+
callbackUrl,
|
|
1126
|
+
externalAccountId: fallbackExtId,
|
|
1127
|
+
message: `Open the authorization URL to connect ${providerName} to your Muxo workspace.`,
|
|
1128
|
+
};
|
|
390
1129
|
}
|
|
391
|
-
async getConnectedAccountStatus(externalAccountId) {
|
|
392
|
-
|
|
1130
|
+
async getConnectedAccountStatus(externalAccountId, composioApiKey) {
|
|
1131
|
+
const apiKey = this.resolveApiKey(composioApiKey);
|
|
1132
|
+
if (!apiKey || !externalAccountId)
|
|
393
1133
|
return null;
|
|
1134
|
+
// 1. Official @composio/core SDK connectedAccounts.get
|
|
1135
|
+
const sdk = this.getComposioClient(composioApiKey);
|
|
1136
|
+
if (sdk) {
|
|
1137
|
+
try {
|
|
1138
|
+
const item = (await Promise.race([
|
|
1139
|
+
sdk.connectedAccounts.get(externalAccountId),
|
|
1140
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK get account timeout")), 4500)),
|
|
1141
|
+
]));
|
|
1142
|
+
if (item && (item.id || item.status)) {
|
|
1143
|
+
const toolkit = (item.toolkit || {});
|
|
1144
|
+
const slug = String(toolkit.slug || item.toolkit_slug || "unknown").toLowerCase();
|
|
1145
|
+
const rawStatus = String(item.status || "INITIALIZING").toUpperCase();
|
|
1146
|
+
const status = rawStatus === "ACTIVE"
|
|
1147
|
+
? "active"
|
|
1148
|
+
: rawStatus === "EXPIRED" || rawStatus === "FAILED"
|
|
1149
|
+
? "expired"
|
|
1150
|
+
: "pending";
|
|
1151
|
+
return {
|
|
1152
|
+
id: String(item.id || externalAccountId),
|
|
1153
|
+
providerSlug: slug,
|
|
1154
|
+
status,
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
catch {
|
|
1159
|
+
// Fall through to REST
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
394
1162
|
try {
|
|
395
1163
|
const res = await fetch(`${this.config.composioBaseUrl}/connected_accounts/${encodeURIComponent(externalAccountId)}`, {
|
|
396
|
-
headers: this.
|
|
1164
|
+
headers: this.getHeaders(composioApiKey),
|
|
397
1165
|
signal: AbortSignal.timeout(5000),
|
|
398
1166
|
});
|
|
399
1167
|
if (!res.ok)
|
|
@@ -417,21 +1185,11 @@ export class ComposioSdkAdapter {
|
|
|
417
1185
|
return null;
|
|
418
1186
|
}
|
|
419
1187
|
}
|
|
420
|
-
async listLiveConnections(workspaceId) {
|
|
421
|
-
|
|
1188
|
+
async listLiveConnections(workspaceId, composioApiKey) {
|
|
1189
|
+
const apiKey = this.resolveApiKey(composioApiKey);
|
|
1190
|
+
if (!apiKey)
|
|
422
1191
|
return [];
|
|
423
|
-
|
|
424
|
-
const url = new URL(`${this.config.composioBaseUrl}/connected_accounts`);
|
|
425
|
-
url.searchParams.set("user_ids", workspaceId);
|
|
426
|
-
url.searchParams.set("limit", "50");
|
|
427
|
-
const res = await fetch(url.toString(), {
|
|
428
|
-
headers: this.headers,
|
|
429
|
-
signal: AbortSignal.timeout(5000),
|
|
430
|
-
});
|
|
431
|
-
if (!res.ok)
|
|
432
|
-
return [];
|
|
433
|
-
const data = (await res.json());
|
|
434
|
-
const items = Array.isArray(data.items) ? data.items : [];
|
|
1192
|
+
const mapConnectionItems = (items) => {
|
|
435
1193
|
const rank = {
|
|
436
1194
|
active: 3,
|
|
437
1195
|
pending: 2,
|
|
@@ -459,6 +1217,39 @@ export class ComposioSdkAdapter {
|
|
|
459
1217
|
}
|
|
460
1218
|
}
|
|
461
1219
|
return Array.from(bySlug.values());
|
|
1220
|
+
};
|
|
1221
|
+
// 1. Official @composio/core SDK connectedAccounts.list
|
|
1222
|
+
const sdk = this.getComposioClient(composioApiKey);
|
|
1223
|
+
if (sdk) {
|
|
1224
|
+
try {
|
|
1225
|
+
const listed = (await Promise.race([
|
|
1226
|
+
sdk.connectedAccounts.list({
|
|
1227
|
+
userIds: [workspaceId],
|
|
1228
|
+
limit: 50,
|
|
1229
|
+
}),
|
|
1230
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK list accounts timeout")), 4500)),
|
|
1231
|
+
]));
|
|
1232
|
+
if (Array.isArray(listed?.items)) {
|
|
1233
|
+
return mapConnectionItems(listed.items);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
catch {
|
|
1237
|
+
// Fall through to REST
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
try {
|
|
1241
|
+
const url = new URL(`${this.config.composioBaseUrl}/connected_accounts`);
|
|
1242
|
+
url.searchParams.set("user_ids", workspaceId);
|
|
1243
|
+
url.searchParams.set("limit", "50");
|
|
1244
|
+
const res = await fetch(url.toString(), {
|
|
1245
|
+
headers: this.getHeaders(composioApiKey),
|
|
1246
|
+
signal: AbortSignal.timeout(5000),
|
|
1247
|
+
});
|
|
1248
|
+
if (!res.ok)
|
|
1249
|
+
return [];
|
|
1250
|
+
const data = (await res.json());
|
|
1251
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
1252
|
+
return mapConnectionItems(items);
|
|
462
1253
|
}
|
|
463
1254
|
catch {
|
|
464
1255
|
return [];
|
|
@@ -536,10 +1327,41 @@ export class ComposioSdkAdapter {
|
|
|
536
1327
|
},
|
|
537
1328
|
};
|
|
538
1329
|
};
|
|
539
|
-
const effectiveApiKey =
|
|
1330
|
+
const effectiveApiKey = this.resolveApiKey(params.composioApiKey);
|
|
540
1331
|
if (!effectiveApiKey) {
|
|
541
1332
|
return buildConnectedAccountFallback();
|
|
542
1333
|
}
|
|
1334
|
+
// 1. Official @composio/core SDK tool execution
|
|
1335
|
+
const sdk = this.getComposioClient(params.composioApiKey);
|
|
1336
|
+
if (sdk) {
|
|
1337
|
+
try {
|
|
1338
|
+
const sdkExec = await Promise.race([
|
|
1339
|
+
sdk.tools.execute(params.tool.upstreamSlug, {
|
|
1340
|
+
userId: params.workspaceId,
|
|
1341
|
+
arguments: params.input,
|
|
1342
|
+
connectedAccountId: params.connectedAccountId && params.connectedAccountId.startsWith("ca_")
|
|
1343
|
+
? params.connectedAccountId
|
|
1344
|
+
: undefined,
|
|
1345
|
+
dangerouslySkipVersionCheck: true,
|
|
1346
|
+
}),
|
|
1347
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK execute timeout")), 20000)),
|
|
1348
|
+
]);
|
|
1349
|
+
if (sdkExec && !sdkExec.error && sdkExec.successful !== false) {
|
|
1350
|
+
return {
|
|
1351
|
+
provider: "composio_sdk",
|
|
1352
|
+
sdk: "@composio/core",
|
|
1353
|
+
tool: params.tool.slug,
|
|
1354
|
+
upstreamTool: params.tool.upstreamSlug,
|
|
1355
|
+
connectedAccountId: params.connectedAccountId,
|
|
1356
|
+
logId: sdkExec.logId,
|
|
1357
|
+
data: sdkExec.data ?? sdkExec,
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
catch {
|
|
1362
|
+
// Fall through to direct v3 REST execute
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
543
1365
|
const body = {
|
|
544
1366
|
user_id: params.workspaceId,
|
|
545
1367
|
arguments: params.input,
|
|
@@ -550,10 +1372,7 @@ export class ComposioSdkAdapter {
|
|
|
550
1372
|
try {
|
|
551
1373
|
const res = await fetch(`${this.config.composioBaseUrl}/tools/execute/${encodeURIComponent(params.tool.upstreamSlug)}`, {
|
|
552
1374
|
method: "POST",
|
|
553
|
-
headers:
|
|
554
|
-
...this.headers,
|
|
555
|
-
"x-api-key": effectiveApiKey,
|
|
556
|
-
},
|
|
1375
|
+
headers: this.getHeaders(params.composioApiKey),
|
|
557
1376
|
body: JSON.stringify(body),
|
|
558
1377
|
signal: AbortSignal.timeout(25000),
|
|
559
1378
|
});
|