clementine-agent 1.11.3 → 1.12.1
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/dist/cli/dashboard.js
CHANGED
|
@@ -4402,10 +4402,22 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
4402
4402
|
res.json({ enabled: false, toolkits: [] });
|
|
4403
4403
|
return;
|
|
4404
4404
|
}
|
|
4405
|
-
|
|
4405
|
+
// Fetch live: full catalog + user's connections + auth-config slugs.
|
|
4406
|
+
// This replaces the hardcoded CURATED_TOOLKITS — slug typos are now
|
|
4407
|
+
// impossible because we render directly from Composio's data, and
|
|
4408
|
+
// newly-added services appear automatically.
|
|
4409
|
+
let catalog = [];
|
|
4410
|
+
let catalogError = null;
|
|
4411
|
+
try {
|
|
4412
|
+
catalog = await c.listAllToolkits();
|
|
4413
|
+
}
|
|
4414
|
+
catch (err) {
|
|
4415
|
+
catalogError = err?.message ?? String(err);
|
|
4416
|
+
console.error('[composio] catalog fetch failed:', catalogError);
|
|
4417
|
+
}
|
|
4418
|
+
const [connected, configured] = await Promise.all([
|
|
4406
4419
|
c.listConnectedToolkits(),
|
|
4407
4420
|
c.listToolkitSlugsWithAuthConfig(),
|
|
4408
|
-
c.listToolkitMeta(),
|
|
4409
4421
|
]);
|
|
4410
4422
|
const connectionsBySlug = new Map();
|
|
4411
4423
|
for (const conn of connected) {
|
|
@@ -4426,39 +4438,53 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
4426
4438
|
accountAvatarUrl: conn.accountAvatarUrl ?? null,
|
|
4427
4439
|
createdAt: conn.createdAt ?? null,
|
|
4428
4440
|
});
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4441
|
+
// Merge: every catalog entry, plus any connection whose slug isn't in
|
|
4442
|
+
// the catalog (defensive — catalog dedupes are rare but possible).
|
|
4443
|
+
const catalogBySlug = new Map(catalog.map(t => [t.slug, t]));
|
|
4444
|
+
const orphanSlugs = [...connectionsBySlug.keys()].filter(s => !catalogBySlug.has(s));
|
|
4445
|
+
const toolkits = [
|
|
4446
|
+
...catalog.map(t => ({
|
|
4433
4447
|
slug: t.slug,
|
|
4434
|
-
displayName: t.
|
|
4448
|
+
displayName: t.name,
|
|
4435
4449
|
authMode: t.authMode,
|
|
4436
4450
|
hasAuthConfig: configured.has(t.slug),
|
|
4437
|
-
logoUrl:
|
|
4438
|
-
description:
|
|
4439
|
-
toolCount:
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
.filter(([slug]) => !c.CURATED_TOOLKITS.some(t => t.slug === slug))
|
|
4445
|
-
.map(([slug, conns]) => {
|
|
4446
|
-
const m = meta.get(slug);
|
|
4447
|
-
// Non-curated: infer auth mode. If a custom auth config exists,
|
|
4448
|
-
// user set it up themselves (BYO). Otherwise it must be managed.
|
|
4449
|
-
const authMode = configured.has(slug) ? 'byo' : 'managed';
|
|
4450
|
-
return {
|
|
4451
|
+
logoUrl: t.logoUrl ?? null,
|
|
4452
|
+
description: t.description ?? null,
|
|
4453
|
+
toolCount: t.toolsCount ?? null,
|
|
4454
|
+
categories: t.categories,
|
|
4455
|
+
connections: (connectionsBySlug.get(t.slug) ?? []).map(toView),
|
|
4456
|
+
})),
|
|
4457
|
+
...orphanSlugs.map(slug => ({
|
|
4451
4458
|
slug,
|
|
4452
|
-
displayName:
|
|
4453
|
-
authMode,
|
|
4459
|
+
displayName: c.displayNameFor(slug),
|
|
4460
|
+
authMode: 'managed',
|
|
4454
4461
|
hasAuthConfig: configured.has(slug),
|
|
4455
|
-
logoUrl:
|
|
4456
|
-
description:
|
|
4457
|
-
toolCount:
|
|
4458
|
-
|
|
4459
|
-
|
|
4462
|
+
logoUrl: null,
|
|
4463
|
+
description: null,
|
|
4464
|
+
toolCount: null,
|
|
4465
|
+
categories: [],
|
|
4466
|
+
connections: (connectionsBySlug.get(slug) ?? []).map(toView),
|
|
4467
|
+
})),
|
|
4468
|
+
];
|
|
4469
|
+
// Featured set = toolkits with active connections (highest signal),
|
|
4470
|
+
// then top toolkits by tool count. Cap at 30 to keep the default
|
|
4471
|
+
// grid focused; the search box covers the rest.
|
|
4472
|
+
const FEATURED_LIMIT = 30;
|
|
4473
|
+
const connectedSlugs = new Set(connectionsBySlug.keys());
|
|
4474
|
+
const featured = [
|
|
4475
|
+
...toolkits.filter(t => connectedSlugs.has(t.slug)),
|
|
4476
|
+
...toolkits
|
|
4477
|
+
.filter(t => !connectedSlugs.has(t.slug))
|
|
4478
|
+
.sort((a, b) => (b.toolCount ?? 0) - (a.toolCount ?? 0))
|
|
4479
|
+
.slice(0, Math.max(0, FEATURED_LIMIT - connectedSlugs.size)),
|
|
4480
|
+
];
|
|
4481
|
+
res.json({
|
|
4482
|
+
enabled: true,
|
|
4483
|
+
toolkits,
|
|
4484
|
+
featured: featured.map(t => t.slug),
|
|
4485
|
+
totalCount: toolkits.length,
|
|
4486
|
+
catalogError,
|
|
4460
4487
|
});
|
|
4461
|
-
res.json({ enabled: true, toolkits: [...curated, ...extras] });
|
|
4462
4488
|
}
|
|
4463
4489
|
catch (err) {
|
|
4464
4490
|
res.status(500).json({ error: String(err) });
|
|
@@ -25777,56 +25803,118 @@ async function refreshComposioConnections() {
|
|
|
25777
25803
|
}
|
|
25778
25804
|
var toolkits = d.toolkits || [];
|
|
25779
25805
|
if (toolkits.length === 0) {
|
|
25780
|
-
|
|
25806
|
+
// Distinguish "catalog fetch failed" from "user has nothing connected".
|
|
25807
|
+
// Surfacing d.catalogError tells us if Composio rejected the catalog
|
|
25808
|
+
// call (different permission than connectedAccounts.list, etc.).
|
|
25809
|
+
var msg = d.catalogError
|
|
25810
|
+
? 'Could not load toolkits from Composio: <code style="color:var(--red);font-size:11px">' + esc(d.catalogError) + '</code>'
|
|
25811
|
+
: 'Composio returned an empty toolkit catalog. This is unusual — try refreshing.';
|
|
25812
|
+
container.innerHTML =
|
|
25813
|
+
'<div style="padding:16px">' +
|
|
25814
|
+
'<div style="margin-bottom:12px;font-size:13px">' + msg + '</div>' +
|
|
25815
|
+
'<div style="font-size:11px;color:var(--text-muted);margin-bottom:12px">If your existing connections (e.g. Outlook) work via the agent, your API key is valid — the catalog endpoint may require a different permission tier or there\\'s a transient API issue.</div>' +
|
|
25816
|
+
'<button class="btn btn-sm btn-primary" onclick="refreshComposioConnections()">Retry</button>' +
|
|
25817
|
+
'</div>';
|
|
25781
25818
|
return;
|
|
25782
25819
|
}
|
|
25820
|
+
// Stash full catalog in a closure so the search input can re-render
|
|
25821
|
+
// without refetching. Source of truth lives on the window object so
|
|
25822
|
+
// the input's oninput handler can find it.
|
|
25823
|
+
window._composioCatalog = {
|
|
25824
|
+
toolkits: toolkits,
|
|
25825
|
+
featured: new Set(d.featured || []),
|
|
25826
|
+
total: d.totalCount || toolkits.length,
|
|
25827
|
+
};
|
|
25828
|
+
renderComposioCatalog('');
|
|
25829
|
+
} catch (e) {
|
|
25830
|
+
container.innerHTML = '<div class="empty-state" style="color:var(--red);padding:16px">Failed to load Composio toolkits: ' + esc(String(e)) + '</div>';
|
|
25831
|
+
}
|
|
25832
|
+
}
|
|
25783
25833
|
|
|
25784
|
-
|
|
25785
|
-
|
|
25786
|
-
|
|
25787
|
-
|
|
25788
|
-
|
|
25789
|
-
|
|
25790
|
-
|
|
25834
|
+
function renderComposioCatalog(query) {
|
|
25835
|
+
var container = document.getElementById('composio-connections');
|
|
25836
|
+
if (!container) return;
|
|
25837
|
+
var cat = window._composioCatalog;
|
|
25838
|
+
if (!cat) return;
|
|
25839
|
+
var q = (query || '').trim().toLowerCase();
|
|
25840
|
+
var visible;
|
|
25841
|
+
if (q.length === 0) {
|
|
25842
|
+
// Default view: featured set (connected toolkits + most-popular ones).
|
|
25843
|
+
visible = cat.toolkits.filter(function(t) { return cat.featured.has(t.slug); });
|
|
25844
|
+
} else {
|
|
25845
|
+
// Search across slug, name, and description. Cap to 80 to keep DOM
|
|
25846
|
+
// size reasonable when the query matches a generic word.
|
|
25847
|
+
visible = cat.toolkits.filter(function(t) {
|
|
25848
|
+
return t.slug.toLowerCase().indexOf(q) !== -1
|
|
25849
|
+
|| (t.displayName||'').toLowerCase().indexOf(q) !== -1
|
|
25850
|
+
|| (t.description||'').toLowerCase().indexOf(q) !== -1;
|
|
25851
|
+
}).slice(0, 80);
|
|
25852
|
+
}
|
|
25853
|
+
// Always show connected toolkits first within the visible set.
|
|
25854
|
+
visible.sort(function(a, b) {
|
|
25855
|
+
var aConn = (a.connections||[]).some(function(c){ return c.status==='ACTIVE'; }) ? 0 : 1;
|
|
25856
|
+
var bConn = (b.connections||[]).some(function(c){ return c.status==='ACTIVE'; }) ? 0 : 1;
|
|
25857
|
+
if (aConn !== bConn) return aConn - bConn;
|
|
25858
|
+
return (b.toolCount||0) - (a.toolCount||0);
|
|
25859
|
+
});
|
|
25791
25860
|
|
|
25792
|
-
|
|
25793
|
-
|
|
25794
|
-
|
|
25795
|
-
|
|
25796
|
-
|
|
25797
|
-
html += '<div style="width:24px;height:24px;border-radius:4px;background:var(--bg-tertiary);flex-shrink:0"></div>';
|
|
25798
|
-
}
|
|
25799
|
-
html += '<div style="flex:1;min-width:0">';
|
|
25800
|
-
html += '<div style="font-size:13px;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(t.displayName) + '</div>';
|
|
25801
|
-
html += '<div style="font-size:10px;color:var(--text-muted)">' + (isConnected ? connected.length + ' account' + (connected.length !== 1 ? 's' : '') : 'Not connected') + '</div>';
|
|
25802
|
-
html += '</div>';
|
|
25803
|
-
html += '<span style="width:8px;height:8px;border-radius:50%;background:' + statusColor + ';flex-shrink:0"></span>';
|
|
25804
|
-
html += '</div>';
|
|
25861
|
+
var header = '<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">'
|
|
25862
|
+
+ '<input type="search" id="composio-search" value="' + esc(q) + '" placeholder="Search ' + cat.total + ' toolkits — Gmail, Slack, Notion, …" oninput="renderComposioCatalog(this.value)"'
|
|
25863
|
+
+ ' style="flex:1;padding:8px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg-input);color:var(--text-primary);font-size:13px">'
|
|
25864
|
+
+ '<span style="font-size:11px;color:var(--text-muted);white-space:nowrap">' + visible.length + (q.length === 0 ? ' featured' : ' match' + (visible.length !== 1 ? 'es' : '')) + ' / ' + cat.total + ' total</span>'
|
|
25865
|
+
+ '</div>';
|
|
25805
25866
|
|
|
25806
|
-
|
|
25807
|
-
|
|
25808
|
-
|
|
25809
|
-
|
|
25810
|
-
|
|
25811
|
-
html += '<span style="flex:1;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(label) + '</span>';
|
|
25812
|
-
html += '<button class="btn-sm" onclick="disconnectComposio(\\'' + esc(t.slug) + '\\',\\'' + esc(c.id) + '\\')" style="font-size:10px;padding:2px 6px;background:transparent;border:1px solid var(--border);color:var(--text-muted);border-radius:4px;cursor:pointer">Disconnect</button>';
|
|
25813
|
-
html += '</div>';
|
|
25814
|
-
});
|
|
25815
|
-
html += '</div>';
|
|
25816
|
-
}
|
|
25867
|
+
if (visible.length === 0) {
|
|
25868
|
+
container.innerHTML = header + '<div class="empty-state" style="padding:16px">No toolkits match "' + esc(q) + '". Try a different search.</div>';
|
|
25869
|
+
setTimeout(function() { var s = document.getElementById('composio-search'); if (s) { s.focus(); s.setSelectionRange(s.value.length, s.value.length); } }, 0);
|
|
25870
|
+
return;
|
|
25871
|
+
}
|
|
25817
25872
|
|
|
25818
|
-
|
|
25819
|
-
|
|
25820
|
-
|
|
25873
|
+
var html = header + '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px">';
|
|
25874
|
+
visible.forEach(function(t) {
|
|
25875
|
+
var connected = (t.connections || []).filter(function(c) { return c.status === 'ACTIVE'; });
|
|
25876
|
+
var pending = (t.connections || []).filter(function(c) { return c.status !== 'ACTIVE'; });
|
|
25877
|
+
var isConnected = connected.length > 0;
|
|
25878
|
+
var statusColor = isConnected ? 'var(--green)' : (pending.length > 0 ? 'var(--yellow,#f59e0b)' : 'var(--text-muted)');
|
|
25879
|
+
var byo = t.authMode === 'byo' && !t.hasAuthConfig;
|
|
25880
|
+
|
|
25881
|
+
html += '<div style="border:1px solid var(--border);border-radius:8px;padding:12px;background:var(--bg-secondary);display:flex;flex-direction:column;gap:8px">';
|
|
25882
|
+
html += '<div style="display:flex;align-items:center;gap:10px">';
|
|
25883
|
+
if (t.logoUrl) {
|
|
25884
|
+
html += '<img src="' + esc(t.logoUrl) + '" alt="" style="width:24px;height:24px;border-radius:4px;background:#fff;padding:2px;flex-shrink:0;object-fit:contain">';
|
|
25885
|
+
} else {
|
|
25886
|
+
html += '<div style="width:24px;height:24px;border-radius:4px;background:var(--bg-tertiary);flex-shrink:0"></div>';
|
|
25887
|
+
}
|
|
25888
|
+
html += '<div style="flex:1;min-width:0">';
|
|
25889
|
+
html += '<div style="font-size:13px;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(t.displayName) + '</div>';
|
|
25890
|
+
html += '<div style="font-size:10px;color:var(--text-muted)">' + (isConnected ? connected.length + ' account' + (connected.length !== 1 ? 's' : '') : ((t.toolCount||0) + ' tools')) + '</div>';
|
|
25891
|
+
html += '</div>';
|
|
25892
|
+
html += '<span style="width:8px;height:8px;border-radius:50%;background:' + statusColor + ';flex-shrink:0"></span>';
|
|
25893
|
+
html += '</div>';
|
|
25821
25894
|
|
|
25822
|
-
|
|
25895
|
+
if (connected.length > 0) {
|
|
25896
|
+
html += '<div style="display:flex;flex-direction:column;gap:4px">';
|
|
25897
|
+
connected.forEach(function(c) {
|
|
25898
|
+
var label = c.alias || c.accountLabel || c.accountEmail || c.accountName || 'connected';
|
|
25899
|
+
html += '<div style="display:flex;align-items:center;gap:6px;font-size:11px">';
|
|
25900
|
+
html += '<span style="flex:1;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(label) + '</span>';
|
|
25901
|
+
html += '<button class="btn-sm" onclick="disconnectComposio(\\'' + esc(t.slug) + '\\',\\'' + esc(c.id) + '\\')" style="font-size:10px;padding:2px 6px;background:transparent;border:1px solid var(--border);color:var(--text-muted);border-radius:4px;cursor:pointer">Disconnect</button>';
|
|
25902
|
+
html += '</div>';
|
|
25903
|
+
});
|
|
25823
25904
|
html += '</div>';
|
|
25824
|
-
}
|
|
25905
|
+
}
|
|
25906
|
+
|
|
25907
|
+
if (byo) {
|
|
25908
|
+
html += '<div style="font-size:10px;color:var(--yellow,#f59e0b);background:rgba(245,158,11,0.08);padding:6px 8px;border-radius:4px;line-height:1.4">Needs a BYO OAuth app — <a href="https://platform.composio.dev/auth-configs" target="_blank" style="color:inherit;text-decoration:underline">set up in Composio</a> first.</div>';
|
|
25909
|
+
}
|
|
25910
|
+
|
|
25911
|
+
html += '<button class="btn btn-sm btn-primary" onclick="connectComposio(\\'' + esc(t.slug) + '\\')" style="font-size:11px">' + (isConnected ? '+ Add account' : 'Connect') + '</button>';
|
|
25825
25912
|
html += '</div>';
|
|
25826
|
-
|
|
25827
|
-
|
|
25828
|
-
|
|
25829
|
-
|
|
25913
|
+
});
|
|
25914
|
+
html += '</div>';
|
|
25915
|
+
container.innerHTML = html;
|
|
25916
|
+
// Restore focus to the search input after re-render so users can keep typing.
|
|
25917
|
+
setTimeout(function() { var s = document.getElementById('composio-search'); if (s) { s.focus(); s.setSelectionRange(s.value.length, s.value.length); } }, 0);
|
|
25830
25918
|
}
|
|
25831
25919
|
|
|
25832
25920
|
async function saveComposioApiKey() {
|
|
@@ -54,6 +54,35 @@ export interface ToolkitMeta {
|
|
|
54
54
|
description?: string;
|
|
55
55
|
toolsCount?: number;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Full catalog entry — derived directly from Composio's API. Replaces the
|
|
59
|
+
* hardcoded CURATED_TOOLKITS for UI rendering. The dashboard uses this so
|
|
60
|
+
* users can browse/search the entire catalog (1000+ services) instead of
|
|
61
|
+
* being limited to whatever slugs are pinned in code.
|
|
62
|
+
*/
|
|
63
|
+
export interface CatalogToolkit {
|
|
64
|
+
slug: string;
|
|
65
|
+
name: string;
|
|
66
|
+
logoUrl?: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
toolsCount?: number;
|
|
69
|
+
/** managed = Composio hosts the OAuth app; byo = user must register their
|
|
70
|
+
* own; none = no auth required. Derived from composioManagedAuthSchemes. */
|
|
71
|
+
authMode: 'managed' | 'byo' | 'none';
|
|
72
|
+
categories: {
|
|
73
|
+
slug: string;
|
|
74
|
+
name: string;
|
|
75
|
+
}[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Fetch the full Composio toolkit catalog (1000+ services). Replaces the
|
|
79
|
+
* static CURATED_TOOLKITS array as the source of truth for the dashboard
|
|
80
|
+
* Connections panel — slug typos are now impossible because we render from
|
|
81
|
+
* Composio's own data, and new services appear automatically as they're
|
|
82
|
+
* added. Cached at module level for 1 hour on success; failures don't
|
|
83
|
+
* cache (so the next request retries instead of returning stale empty).
|
|
84
|
+
*/
|
|
85
|
+
export declare function listAllToolkits(): Promise<CatalogToolkit[]>;
|
|
57
86
|
export declare function listToolkitMeta(): Promise<Map<string, ToolkitMeta>>;
|
|
58
87
|
export declare function listToolkitSlugsWithAuthConfig(): Promise<Set<string>>;
|
|
59
88
|
export declare class ComposioNeedsAuthConfigError extends Error {
|
|
@@ -82,6 +82,7 @@ export function resetComposioClient() {
|
|
|
82
82
|
singleton = null;
|
|
83
83
|
identityCache.clear();
|
|
84
84
|
toolkitMetaCache = null;
|
|
85
|
+
catalogCache = null;
|
|
85
86
|
detectedPreferredUserId = null;
|
|
86
87
|
}
|
|
87
88
|
// Public: same logic as the internal detector, exposed for the MCP bridge so
|
|
@@ -334,6 +335,95 @@ export async function listConnectedToolkits() {
|
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
337
|
let toolkitMetaCache = null;
|
|
338
|
+
let catalogCache = null;
|
|
339
|
+
const CATALOG_TTL_MS = 60 * 60 * 1000; // 1h — catalog drifts very slowly
|
|
340
|
+
/**
|
|
341
|
+
* Fetch the full Composio toolkit catalog (1000+ services). Replaces the
|
|
342
|
+
* static CURATED_TOOLKITS array as the source of truth for the dashboard
|
|
343
|
+
* Connections panel — slug typos are now impossible because we render from
|
|
344
|
+
* Composio's own data, and new services appear automatically as they're
|
|
345
|
+
* added. Cached at module level for 1 hour on success; failures don't
|
|
346
|
+
* cache (so the next request retries instead of returning stale empty).
|
|
347
|
+
*/
|
|
348
|
+
export async function listAllToolkits() {
|
|
349
|
+
const now = Date.now();
|
|
350
|
+
if (catalogCache && catalogCache.data.length > 0 && now - catalogCache.at < CATALOG_TTL_MS) {
|
|
351
|
+
return catalogCache.data;
|
|
352
|
+
}
|
|
353
|
+
const composio = getComposio();
|
|
354
|
+
if (!composio)
|
|
355
|
+
return [];
|
|
356
|
+
// Try the raw client first — supports cursor pagination so we get the full
|
|
357
|
+
// 1000+ catalog. If that errors (some API keys / plans restrict it), fall
|
|
358
|
+
// back to the high-level wrapper which returns up to 500 in one shot.
|
|
359
|
+
let result = [];
|
|
360
|
+
let lastError = null;
|
|
361
|
+
try {
|
|
362
|
+
result = await fetchCatalogViaRawClient(composio);
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
lastError = err;
|
|
366
|
+
logger.warn({ err }, 'Raw-client toolkit list failed — falling back to high-level wrapper');
|
|
367
|
+
}
|
|
368
|
+
if (result.length === 0) {
|
|
369
|
+
try {
|
|
370
|
+
result = await fetchCatalogViaWrapper(composio);
|
|
371
|
+
}
|
|
372
|
+
catch (err) {
|
|
373
|
+
lastError = err;
|
|
374
|
+
logger.error({ err }, 'High-level toolkit list also failed');
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (result.length === 0) {
|
|
378
|
+
// Surface whichever error we hit so the dashboard can render something
|
|
379
|
+
// actionable instead of "no toolkits available".
|
|
380
|
+
const detail = lastError?.message ?? 'Unknown error';
|
|
381
|
+
throw new Error(`Composio catalog fetch failed: ${detail}`);
|
|
382
|
+
}
|
|
383
|
+
catalogCache = { at: now, data: result };
|
|
384
|
+
logger.info({ count: result.length }, 'Composio catalog fetched');
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
function normalizeCatalogItem(it) {
|
|
388
|
+
const managed = (it.composioManagedAuthSchemes ?? it.composio_managed_auth_schemes ?? []);
|
|
389
|
+
const schemes = (it.authSchemes ?? it.auth_schemes ?? []);
|
|
390
|
+
const noAuth = it.noAuth ?? it.no_auth ?? false;
|
|
391
|
+
return {
|
|
392
|
+
slug: it.slug,
|
|
393
|
+
name: it.name,
|
|
394
|
+
logoUrl: it.meta?.logo,
|
|
395
|
+
description: it.meta?.description,
|
|
396
|
+
toolsCount: it.meta?.toolsCount ?? it.meta?.tools_count,
|
|
397
|
+
authMode: noAuth ? 'none' : (managed.length > 0 ? 'managed' : (schemes.length > 0 ? 'byo' : 'none')),
|
|
398
|
+
categories: it.meta?.categories ?? [],
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
async function fetchCatalogViaRawClient(composio) {
|
|
402
|
+
const out = [];
|
|
403
|
+
let cursor;
|
|
404
|
+
// Bounded loop — 30 pages × 500 items = 15K ceiling.
|
|
405
|
+
for (let page = 0; page < 30; page++) {
|
|
406
|
+
const rawClient = composio.client;
|
|
407
|
+
const resp = await rawClient.toolkits.list({
|
|
408
|
+
limit: 500,
|
|
409
|
+
...(cursor ? { cursor } : {}),
|
|
410
|
+
});
|
|
411
|
+
const items = (resp?.items ?? []);
|
|
412
|
+
for (const it of items)
|
|
413
|
+
out.push(normalizeCatalogItem(it));
|
|
414
|
+
cursor = resp?.next_cursor ?? resp?.nextCursor;
|
|
415
|
+
if (!cursor || items.length === 0)
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
async function fetchCatalogViaWrapper(composio) {
|
|
421
|
+
// High-level wrapper returns an array (up to limit). No cursor support
|
|
422
|
+
// through this path, so we cap at the documented max. Better than zero.
|
|
423
|
+
const resp = await composio.toolkits.get({ limit: 500 });
|
|
424
|
+
const items = (Array.isArray(resp) ? resp : (resp?.items ?? []));
|
|
425
|
+
return items.map(normalizeCatalogItem);
|
|
426
|
+
}
|
|
337
427
|
async function fetchAllToolkitMeta() {
|
|
338
428
|
const composio = getComposio();
|
|
339
429
|
if (!composio)
|