clementine-agent 1.10.4 → 1.11.0
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.
|
@@ -61,7 +61,7 @@ export declare class ComposioNeedsAuthConfigError extends Error {
|
|
|
61
61
|
readonly underlying: string;
|
|
62
62
|
constructor(slug: string, underlying: string);
|
|
63
63
|
}
|
|
64
|
-
export declare function authorizeToolkit(slug: string,
|
|
64
|
+
export declare function authorizeToolkit(slug: string, _opts?: {
|
|
65
65
|
callbackUrl?: string;
|
|
66
66
|
alias?: string;
|
|
67
67
|
}): Promise<{
|
|
@@ -98,7 +98,11 @@ export async function getPreferredUserId() {
|
|
|
98
98
|
// calls have to pass *some* user_id, and we want it to match whatever the
|
|
99
99
|
// user already has if possible. detectPreferredUserId() picks the user_id
|
|
100
100
|
// with the most existing connections, falling back to this constant.
|
|
101
|
-
|
|
101
|
+
// Empty string matches Composio's web-UI default user_id (verified by
|
|
102
|
+
// probing — connections created via dashboard.composio.dev land under '').
|
|
103
|
+
// Using the same value here keeps Clementine-authorized connections in the
|
|
104
|
+
// same bucket as anything the user already has from the web UI.
|
|
105
|
+
const DEFAULT_NEW_CONNECTION_USER_ID = '';
|
|
102
106
|
export function clementineUserId() {
|
|
103
107
|
return readComposioEnv('COMPOSIO_USER_ID') || DEFAULT_NEW_CONNECTION_USER_ID;
|
|
104
108
|
}
|
|
@@ -108,26 +112,37 @@ async function detectPreferredUserId(composio) {
|
|
|
108
112
|
const explicit = readComposioEnv('COMPOSIO_USER_ID');
|
|
109
113
|
if (explicit)
|
|
110
114
|
return explicit;
|
|
111
|
-
if (detectedPreferredUserId)
|
|
115
|
+
if (detectedPreferredUserId !== null)
|
|
112
116
|
return detectedPreferredUserId;
|
|
117
|
+
// The list response doesn't expose userId on records (verified empirically),
|
|
118
|
+
// so we can't read it from the data. Instead, probe each candidate user_id
|
|
119
|
+
// with a filtered list and pick whichever returns connections. This matches
|
|
120
|
+
// however the connections were originally created — including empty string,
|
|
121
|
+
// which is what Composio's web UI uses by default for new accounts.
|
|
122
|
+
const candidates = ['', 'default', 'user', 'me'];
|
|
123
|
+
let totalUnscoped = 0;
|
|
113
124
|
try {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
const all = await composio.connectedAccounts.list({ limit: 1 });
|
|
126
|
+
totalUnscoped = all.items.length;
|
|
127
|
+
}
|
|
128
|
+
catch { /* non-fatal */ }
|
|
129
|
+
if (totalUnscoped > 0) {
|
|
130
|
+
for (const uid of candidates) {
|
|
131
|
+
try {
|
|
132
|
+
const resp = await composio.connectedAccounts.list({ userIds: [uid], limit: 1 });
|
|
133
|
+
if (resp.items.length > 0) {
|
|
134
|
+
logger.info({ userId: uid }, 'Detected Composio user_id via probe');
|
|
135
|
+
detectedPreferredUserId = uid;
|
|
136
|
+
return uid;
|
|
137
|
+
}
|
|
120
138
|
}
|
|
139
|
+
catch { /* try next */ }
|
|
121
140
|
}
|
|
122
|
-
if (counts.size > 0) {
|
|
123
|
-
const top = [...counts.entries()].sort((a, b) => b[1] - a[1])[0][0];
|
|
124
|
-
detectedPreferredUserId = top;
|
|
125
|
-
return top;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
logger.debug({ err }, 'detectPreferredUserId failed — using default');
|
|
130
141
|
}
|
|
142
|
+
// No connections yet, or none of the common user_ids match. Fall back to
|
|
143
|
+
// empty string, which is what Composio's web UI uses by default — keeps
|
|
144
|
+
// future Clementine-authorized connections in the same bucket as anything
|
|
145
|
+
// the user creates in Composio's dashboard.
|
|
131
146
|
detectedPreferredUserId = DEFAULT_NEW_CONNECTION_USER_ID;
|
|
132
147
|
return DEFAULT_NEW_CONNECTION_USER_ID;
|
|
133
148
|
}
|
|
@@ -388,66 +403,34 @@ export class ComposioNeedsAuthConfigError extends Error {
|
|
|
388
403
|
this.name = 'ComposioNeedsAuthConfigError';
|
|
389
404
|
}
|
|
390
405
|
}
|
|
391
|
-
export async function authorizeToolkit(slug,
|
|
406
|
+
export async function authorizeToolkit(slug,
|
|
407
|
+
// Reserved for future per-call overrides (callbackUrl / alias). Currently
|
|
408
|
+
// unused because composio.toolkits.authorize doesn't surface those options
|
|
409
|
+
// — alias is set later via patch, callback uses Composio's hosted page.
|
|
410
|
+
_opts) {
|
|
392
411
|
const composio = getComposio();
|
|
393
412
|
if (!composio)
|
|
394
413
|
throw new Error('COMPOSIO_API_KEY not set');
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
catch (err) {
|
|
403
|
-
logger.error({ err, slug, step: 'authConfigs.list' }, 'Composio authorize failed');
|
|
404
|
-
throw err;
|
|
405
|
-
}
|
|
406
|
-
if (existingConfig) {
|
|
407
|
-
authConfigId = existingConfig.id;
|
|
408
|
-
logger.debug({ slug, authConfigId }, 'Reusing existing auth config');
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
try {
|
|
412
|
-
const created = await composio.authConfigs.create(slug, {
|
|
413
|
-
type: 'use_composio_managed_auth',
|
|
414
|
-
name: `${displayNameFor(slug)} Auth Config`,
|
|
415
|
-
});
|
|
416
|
-
authConfigId = created.id;
|
|
417
|
-
logger.debug({ slug, authConfigId }, 'Created managed auth config');
|
|
418
|
-
}
|
|
419
|
-
catch (err) {
|
|
420
|
-
const status = err?.status;
|
|
421
|
-
logger.warn({ err, slug, status, step: 'authConfigs.create' }, 'authConfigs.create rejected — likely needs BYO setup');
|
|
422
|
-
// 400 → Composio doesn't host a managed OAuth app for this toolkit.
|
|
423
|
-
// 401/403 → key lacks permission to create managed auth configs (common
|
|
424
|
-
// for Google services where Composio's plan tier requires you to
|
|
425
|
-
// register your own Google OAuth project). Either way, the user fix
|
|
426
|
-
// is the same: set it up once in Composio's auth-configs dashboard.
|
|
427
|
-
if (status === 400 || status === 401 || status === 403) {
|
|
428
|
-
throw new ComposioNeedsAuthConfigError(slug, String(err));
|
|
429
|
-
}
|
|
430
|
-
throw err;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
// 2. Initiate the connection. allowMultiple if there's already an active
|
|
434
|
-
// connection so we add another account instead of replacing.
|
|
435
|
-
const existing = (await listConnectedToolkits()).filter(c => c.slug === slug && c.status === 'ACTIVE');
|
|
436
|
-
// Reuse whichever user_id already owns connections in this account, so a
|
|
437
|
-
// freshly authorized Gmail lands next to the existing Outlook (etc.) under
|
|
438
|
-
// the same user_id. Falls back to env override or "default".
|
|
414
|
+
// Use the SDK's blessed one-liner — handles auth config discovery
|
|
415
|
+
// (reuses existing managed config if present) AND new-config creation
|
|
416
|
+
// automatically. Source: README + composio-B75SFMkx.d.cts. Replaces our
|
|
417
|
+
// older two-step `authConfigs.list/create + connectedAccounts.initiate`
|
|
418
|
+
// flow which tripped 401s on plans that don't allow programmatic
|
|
419
|
+
// authConfigs.create even when a managed app exists.
|
|
439
420
|
const userId = await detectPreferredUserId(composio);
|
|
440
421
|
try {
|
|
441
|
-
const conn = await composio.
|
|
442
|
-
|
|
443
|
-
...(opts?.callbackUrl ? { callbackUrl: opts.callbackUrl } : {}),
|
|
444
|
-
...(opts?.alias ? { alias: opts.alias } : {}),
|
|
445
|
-
});
|
|
446
|
-
logger.debug({ slug, userId, connectionId: conn.id }, 'Initiated connection');
|
|
422
|
+
const conn = await composio.toolkits.authorize(userId, slug);
|
|
423
|
+
logger.info({ slug, userId, connectionId: conn.id }, 'Composio authorize OK');
|
|
447
424
|
return { redirectUrl: conn.redirectUrl ?? null, connectionId: conn.id };
|
|
448
425
|
}
|
|
449
426
|
catch (err) {
|
|
450
|
-
|
|
427
|
+
const status = err?.status;
|
|
428
|
+
logger.error({ err, slug, userId, status, step: 'toolkits.authorize' }, 'Composio authorize failed');
|
|
429
|
+
// Translate the documented "no managed auth available" error codes into
|
|
430
|
+
// the friendly BYO-setup banner the dashboard already renders.
|
|
431
|
+
if (status === 400 || status === 401 || status === 403) {
|
|
432
|
+
throw new ComposioNeedsAuthConfigError(slug, String(err));
|
|
433
|
+
}
|
|
451
434
|
throw err;
|
|
452
435
|
}
|
|
453
436
|
}
|