@mastra/editor 0.14.0-alpha.5 → 0.14.0-alpha.6
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/CHANGELOG.md +30 -0
- package/dist/composio.cjs +67 -11
- package/dist/composio.cjs.map +1 -1
- package/dist/composio.d.cts +51 -1
- package/dist/composio.d.cts.map +1 -1
- package/dist/composio.d.ts +51 -1
- package/dist/composio.d.ts.map +1 -1
- package/dist/composio.js +67 -11
- package/dist/composio.js.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @mastra/editor
|
|
2
2
|
|
|
3
|
+
## 0.14.0-alpha.6
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added authenticated-user execution and deterministic connected-account routing to ComposioToolProvider. Invoker-bound connections execute as the authenticated Composio user against the exact stored connected account, including accounts shared through Composio ACLs. ([#21783](https://github.com/mastra-ai/mastra/pull/21783))
|
|
8
|
+
|
|
9
|
+
Use userIdResolver when application user IDs need mapping to Composio user IDs:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { MASTRA_USER_KEY } from '@mastra/server/auth';
|
|
13
|
+
import { ComposioToolProvider } from '@mastra/editor/composio';
|
|
14
|
+
|
|
15
|
+
const composio = new ComposioToolProvider({
|
|
16
|
+
apiKey: process.env.COMPOSIO_API_KEY!,
|
|
17
|
+
userIdResolver: ({ requestContext }) => {
|
|
18
|
+
const user = requestContext?.getRaw(MASTRA_USER_KEY);
|
|
19
|
+
if (!user || typeof user !== 'object' || !('id' in user) || typeof user.id !== 'string') return undefined;
|
|
20
|
+
return user.id;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Pinned caller-supplied connections now route to their exact account instead of allowing Composio to auto-select one.
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Updated dependencies [[`c549e2f`](https://github.com/mastra-ai/mastra/commit/c549e2f40edc1cac5d9e74e82f90da22b48df084), [`c549e2f`](https://github.com/mastra-ai/mastra/commit/c549e2f40edc1cac5d9e74e82f90da22b48df084), [`2ef2f23`](https://github.com/mastra-ai/mastra/commit/2ef2f230a7aed342e7dc3b2000cd42e4c43e08a7), [`5740ec6`](https://github.com/mastra-ai/mastra/commit/5740ec60c760ffdfbfaa59d603d03b847c864e05)]:
|
|
30
|
+
- @mastra/core@1.60.0-alpha.13
|
|
31
|
+
- @mastra/memory@1.27.0-alpha.3
|
|
32
|
+
|
|
3
33
|
## 0.14.0-alpha.5
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
package/dist/composio.cjs
CHANGED
|
@@ -43,6 +43,7 @@ var ComposioToolProvider = class extends _mastra_core_tool_provider.BaseToolProv
|
|
|
43
43
|
this.rawClient = null;
|
|
44
44
|
this.mastraClient = null;
|
|
45
45
|
this.apiKey = config.apiKey;
|
|
46
|
+
this.userIdResolver = config.userIdResolver;
|
|
46
47
|
}
|
|
47
48
|
getRawClient() {
|
|
48
49
|
if (!this.rawClient) this.rawClient = new _composio_core.Composio({ apiKey: this.apiKey });
|
|
@@ -104,18 +105,18 @@ var ComposioToolProvider = class extends _mastra_core_tool_provider.BaseToolProv
|
|
|
104
105
|
}
|
|
105
106
|
async resolveToolsVNext(opts) {
|
|
106
107
|
if (opts.toolSlugs.length === 0) return {};
|
|
107
|
-
const
|
|
108
|
+
const identity = await this.resolveExecutionIdentity(opts);
|
|
108
109
|
const composio = this.getMastraClient();
|
|
109
110
|
const sessionToolSlugs = opts.toolSlugs.filter((slug) => COMPOSIO_CONNECTION_MANAGEMENT_TOOLS.has(slug));
|
|
110
111
|
const directToolSlugs = opts.toolSlugs.filter((slug) => !COMPOSIO_CONNECTION_MANAGEMENT_TOOLS.has(slug));
|
|
111
112
|
const mastraTools = {};
|
|
112
|
-
if (directToolSlugs.length > 0) Object.assign(mastraTools, await composio.tools.get(
|
|
113
|
-
if (
|
|
113
|
+
if (directToolSlugs.length > 0) Object.assign(mastraTools, await composio.tools.get(identity.userId, { tools: directToolSlugs }, { beforeExecute: ({ params }) => {
|
|
114
|
+
if (identity.connectionId) params.connectedAccountId = identity.connectionId;
|
|
114
115
|
return params;
|
|
115
116
|
} }));
|
|
116
117
|
if (sessionToolSlugs.length > 0) {
|
|
117
118
|
const selectedToolkits = [...new Set(Object.values(opts.toolMeta).map((meta) => meta.toolkit).filter((toolkit) => typeof toolkit === "string" && toolkit.toLowerCase() !== COMPOSIO_PROVIDER_ID))];
|
|
118
|
-
const sessionTools = await (await composio.sessions.create(
|
|
119
|
+
const sessionTools = await (await composio.sessions.create(identity.userId, {
|
|
119
120
|
...selectedToolkits.length > 0 ? { toolkits: selectedToolkits } : {},
|
|
120
121
|
manageConnections: {
|
|
121
122
|
enable: true,
|
|
@@ -140,6 +141,51 @@ var ComposioToolProvider = class extends _mastra_core_tool_provider.BaseToolProv
|
|
|
140
141
|
}
|
|
141
142
|
return result;
|
|
142
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Run the configured `userIdResolver` and validate its result. Returns
|
|
146
|
+
* the resolved user id, or `undefined` when no resolver is configured or
|
|
147
|
+
* the resolver declined (returned `undefined`). Throws when the resolver
|
|
148
|
+
* returns an empty or non-string value — an empty execution identity must
|
|
149
|
+
* fail closed instead of silently falling back.
|
|
150
|
+
*/
|
|
151
|
+
async runUserIdResolver(input) {
|
|
152
|
+
if (!this.userIdResolver) return void 0;
|
|
153
|
+
const resolved = await this.userIdResolver(input);
|
|
154
|
+
if (resolved === void 0) return void 0;
|
|
155
|
+
if (typeof resolved !== "string") throw new Error("[composio] userIdResolver must return a non-empty string or undefined");
|
|
156
|
+
const normalized = resolved.trim();
|
|
157
|
+
if (normalized.length === 0) throw new Error("[composio] userIdResolver must return a non-empty string or undefined");
|
|
158
|
+
return normalized;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Resolve the effective Composio execution identity for one
|
|
162
|
+
* `resolveToolsVNext` call: the `userId` bucket to fetch tools under and
|
|
163
|
+
* the exact `connectedAccountId` to route execution to (absent = let
|
|
164
|
+
* Composio auto-resolve within the bucket).
|
|
165
|
+
*/
|
|
166
|
+
async resolveExecutionIdentity(opts) {
|
|
167
|
+
const hasAccountPin = opts.connectionId !== opts.authorId;
|
|
168
|
+
if (opts.kind === "invoker") return {
|
|
169
|
+
userId: await this.runUserIdResolver({
|
|
170
|
+
requestContext: opts.requestContext,
|
|
171
|
+
toolkit: opts.toolkit,
|
|
172
|
+
connectedAccountId: opts.connectionId
|
|
173
|
+
}) ?? resolveInvokerUserId(opts.requestContext),
|
|
174
|
+
connectionId: opts.connectionId
|
|
175
|
+
};
|
|
176
|
+
if (opts.scope === "caller-supplied") return {
|
|
177
|
+
userId: await this.runUserIdResolver({
|
|
178
|
+
requestContext: opts.requestContext,
|
|
179
|
+
toolkit: opts.toolkit,
|
|
180
|
+
connectedAccountId: hasAccountPin ? opts.connectionId : void 0
|
|
181
|
+
}) ?? resolveInternalUserId(opts.requestContext),
|
|
182
|
+
connectionId: hasAccountPin ? opts.connectionId : void 0
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
userId: opts.authorId && opts.authorId.length > 0 ? opts.authorId : resolveInternalUserId(opts.requestContext),
|
|
186
|
+
connectionId: opts.connectionId
|
|
187
|
+
};
|
|
188
|
+
}
|
|
143
189
|
async authorize(opts) {
|
|
144
190
|
const composio = this.getRawClient();
|
|
145
191
|
const { id: authConfigId, authScheme } = await this.resolveAuthConfig(opts.toolkit);
|
|
@@ -327,6 +373,11 @@ function mapComposioStatus(status, isDisabled) {
|
|
|
327
373
|
}
|
|
328
374
|
}
|
|
329
375
|
const MASTRA_USER_KEY = "mastra__user";
|
|
376
|
+
function readAuthenticatedUserId(requestContext) {
|
|
377
|
+
const user = requestContext?.getRaw(MASTRA_USER_KEY);
|
|
378
|
+
if (!user || typeof user !== "object" || !("id" in user)) return void 0;
|
|
379
|
+
return typeof user.id === "string" && user.id.length > 0 ? user.id : void 0;
|
|
380
|
+
}
|
|
330
381
|
/**
|
|
331
382
|
* Read the internal user id (Composio `userId`) from per-request context.
|
|
332
383
|
*
|
|
@@ -335,14 +386,19 @@ const MASTRA_USER_KEY = "mastra__user";
|
|
|
335
386
|
* {@link MASTRA_RESOURCE_ID_KEY}.
|
|
336
387
|
*/
|
|
337
388
|
function resolveInternalUserId(requestContext) {
|
|
338
|
-
const resourceId = requestContext?.
|
|
389
|
+
const resourceId = requestContext?.getRaw(_mastra_core_request_context.MASTRA_RESOURCE_ID_KEY);
|
|
339
390
|
if (typeof resourceId === "string" && resourceId.length > 0) return resourceId;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
391
|
+
return readAuthenticatedUserId(requestContext) ?? DEFAULT_INTERNAL_USER_ID;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Read the authenticated invoker's Composio `userId` from per-request
|
|
395
|
+
* context. Invoker connections must never fall back to the Memory resource id
|
|
396
|
+
* because a project or thread is not an authenticated connector principal.
|
|
397
|
+
*/
|
|
398
|
+
function resolveInvokerUserId(requestContext) {
|
|
399
|
+
const userId = readAuthenticatedUserId(requestContext);
|
|
400
|
+
if (userId) return userId;
|
|
401
|
+
throw new Error("[composio] kind \"invoker\" requires an authenticated user or a userIdResolver result");
|
|
346
402
|
}
|
|
347
403
|
/**
|
|
348
404
|
* Resolve `userIds[]` from `listConnections` opts.
|
package/dist/composio.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composio.cjs","names":["BaseToolProvider","Composio","MastraProvider","MASTRA_RESOURCE_ID_KEY"],"sources":["../src/providers/composio.ts"],"sourcesContent":["import type {\n AuthFlowStatus,\n AuthorizeOpts,\n ConnectionField,\n ExistingConnection,\n ListConnectionsOpts,\n ListConnectionsResult,\n ListToolsOpts,\n ListToolsResult,\n ResolveToolsOpts,\n ToolProviderCapabilities,\n ToolProviderHealth,\n ToolProviderInfo,\n ToolProviderToolkit,\n BaseToolProviderOptions,\n} from '@mastra/core/tool-provider';\nimport { BaseToolProvider } from '@mastra/core/tool-provider';\nimport type { ToolAction } from '@mastra/core/tools';\nimport { MASTRA_RESOURCE_ID_KEY } from '@mastra/core/request-context';\n\nimport { Composio } from '@composio/core';\nimport type {\n ConnectedAccountListResponse,\n Tool as ComposioTool,\n ToolListParams as ComposioToolListParams,\n ToolKitItem,\n} from '@composio/core';\nimport { MastraProvider } from '@composio/mastra';\nimport type { MastraToolCollection } from '@composio/mastra';\n\nexport interface ComposioToolProviderConfig extends BaseToolProviderOptions {\n /** Composio API key. */\n apiKey: string;\n}\n\nconst COMPOSIO_PROVIDER_ID = 'composio' as const;\nconst DEFAULT_INTERNAL_USER_ID = 'default';\nconst COMPOSIO_CONNECTION_MANAGEMENT_TOOLS = new Set(['COMPOSIO_MANAGE_CONNECTIONS', 'COMPOSIO_WAIT_FOR_CONNECTIONS']);\n\n/**\n * Composio implementation of the {@link BaseToolProvider} contract.\n *\n * Discovery (`listAllToolkits`, `listAllTools`) uses the raw Composio\n * client. Runtime (`resolveToolsVNext`) uses {@link MastraProvider} so resolved\n * tools are already in `createTool()` shape. Ordinary tools use Composio's\n * direct-tools API, while connection-management tools use a caller-scoped\n * Tool Router session. Resolved tools keep the `outputSchema` supplied by\n * `@composio/mastra`, which pre-relaxes Composio's strict API schemas\n * (nullable fields, extra properties, no `required`) so real third-party\n * responses validate while structurally invalid output is still rejected.\n *\n * Allowlist filtering is layered by {@link BaseToolProvider}; this class\n * never reads `allowedToolkits` / `allowedTools` directly.\n */\nexport class ComposioToolProvider extends BaseToolProvider {\n readonly info: ToolProviderInfo = {\n id: COMPOSIO_PROVIDER_ID,\n name: 'Composio',\n description: 'Access 10,000+ tools from 150+ apps via Composio',\n };\n readonly capabilities: ToolProviderCapabilities = {\n multipleConnectionsPerToolkit: true,\n batchConnectionStatus: true,\n reauthorizeReusesConnectionId: true,\n supportsRevoke: true,\n };\n\n private readonly apiKey: string;\n private rawClient: Composio | null = null;\n private mastraClient: Composio<MastraProvider> | null = null;\n\n constructor(config: ComposioToolProviderConfig) {\n super({\n allowedToolkits: config.allowedToolkits,\n allowedTools: config.allowedTools,\n defaultScope: config.defaultScope,\n });\n this.apiKey = config.apiKey;\n }\n\n // ── client cache ──────────────────────────────────────────────────────\n\n private getRawClient(): Composio {\n if (!this.rawClient) {\n this.rawClient = new Composio({ apiKey: this.apiKey });\n }\n return this.rawClient;\n }\n\n private getMastraClient(): Composio<MastraProvider> {\n if (!this.mastraClient) {\n this.mastraClient = new Composio({\n apiKey: this.apiKey,\n provider: new MastraProvider(),\n });\n }\n return this.mastraClient;\n }\n\n // ── catalog (BaseToolProvider adds allowlist filter on top) ───────────\n\n protected async listAllToolkits(): Promise<ToolProviderToolkit[]> {\n const composio = this.getRawClient();\n const toolkits: ToolKitItem[] = await composio.toolkits.get({});\n return toolkits.map(tk => ({\n slug: tk.slug,\n name: tk.name,\n description: tk.meta?.description,\n icon: tk.meta?.logo,\n }));\n }\n\n protected async listAllTools(opts: ListToolsOpts): Promise<ListToolsResult> {\n const composio = this.getRawClient();\n\n // Composio's `getRawComposioTools` query is a discriminated union — every\n // variant accepts `limit`, but the toolkits/search keys are exclusive in\n // the TS types. We build the variant we need, then cast to the union.\n //\n // When the caller doesn't scope to a specific toolkit, we fall back to\n // the admin allowlist so the SDK returns a flat list across allowed\n // toolkits in a single hop (vs. fanning out per toolkit).\n const limit = opts.perPage;\n const fallbackToolkits = this.allowedToolkits.length > 0 ? [...this.allowedToolkits] : undefined;\n const query: ComposioToolListParams = (\n opts.toolkit\n ? { toolkits: [opts.toolkit], limit, search: opts.search }\n : fallbackToolkits\n ? { toolkits: fallbackToolkits, limit, search: opts.search }\n : opts.search\n ? { search: opts.search, limit }\n : { toolkits: [] as string[], limit }\n ) as ComposioToolListParams;\n\n // Composio's SDK validates every tool's input/output schema against an\n // internal zod shape and throws on the first malformed tool — so one bad\n // toolkit can poison a multi-toolkit query. Treat validation errors as a\n // soft failure and return an empty page rather than a 500.\n let rawTools: ComposioTool[] = [];\n try {\n rawTools = await composio.tools.getRawComposioTools(query);\n } catch (err) {\n console.warn(\n `[ComposioToolProvider] listAllTools failed for query ${JSON.stringify(query)} — returning empty page`,\n err,\n );\n }\n\n const data = rawTools.map(tool => ({\n slug: tool.slug,\n name: tool.name ?? tool.slug,\n description: tool.description,\n toolkit: tool.toolkit?.slug ?? opts.toolkit ?? '',\n }));\n\n return {\n data,\n pagination: {\n page: opts.page ?? 1,\n perPage: limit,\n hasMore: limit !== undefined && rawTools.length >= limit,\n },\n };\n }\n\n // ── runtime ───────────────────────────────────────────────────────────\n\n async resolveToolsVNext(opts: ResolveToolsOpts): Promise<Record<string, ToolAction<any, any, any>>> {\n if (opts.toolSlugs.length === 0) return {};\n\n // Caller-supplied scope is always owned by the authenticated caller's\n // resource id. Author-bound connections use the agent author's id so the\n // same pin resolves for every invoker of that agent.\n const callerPrincipalId =\n opts.scope === 'caller-supplied'\n ? resolveInternalUserId(opts.requestContext)\n : opts.authorId && opts.authorId.length > 0\n ? opts.authorId\n : resolveInternalUserId(opts.requestContext);\n const composio = this.getMastraClient();\n const sessionToolSlugs = opts.toolSlugs.filter(slug => COMPOSIO_CONNECTION_MANAGEMENT_TOOLS.has(slug));\n const directToolSlugs = opts.toolSlugs.filter(slug => !COMPOSIO_CONNECTION_MANAGEMENT_TOOLS.has(slug));\n const mastraTools: MastraToolCollection = {};\n\n if (directToolSlugs.length > 0) {\n const modifiers = {\n // `connectedAccountId` is not threaded through Composio's `execute`\n // option bag in @composio/mastra; the only documented per-call hook\n // is `beforeExecute`, which receives the params object that flows\n // into the API call. Mutating `params.connectedAccountId` routes\n // the call to a specific account.\n beforeExecute: ({ params }: { params: { connectedAccountId?: string; userId?: string } }) => {\n // Under `caller-supplied` scope the user bucket (`callerPrincipalId`,\n // resolved from the host app's resourceId) already scopes the call to\n // the right tenant. Pinning a specific `connectedAccountId` would defeat\n // Composio's per-user-bucket auto-resolve, so we let Composio pick the\n // connected account within the bucket instead of forcing one.\n if (opts.scope !== 'caller-supplied') {\n params.connectedAccountId = opts.connectionId;\n }\n return params;\n },\n };\n\n Object.assign(\n mastraTools,\n (await composio.tools.get(callerPrincipalId, { tools: directToolSlugs }, modifiers)) as MastraToolCollection,\n );\n }\n\n if (sessionToolSlugs.length > 0) {\n const selectedToolkits = [\n ...new Set(\n Object.values(opts.toolMeta)\n .map(meta => meta.toolkit)\n .filter(\n (toolkit): toolkit is string =>\n typeof toolkit === 'string' && toolkit.toLowerCase() !== COMPOSIO_PROVIDER_ID,\n ),\n ),\n ];\n const session = await composio.sessions.create(callerPrincipalId, {\n ...(selectedToolkits.length > 0 ? { toolkits: selectedToolkits } : {}),\n manageConnections: { enable: true, waitForConnections: true },\n sandbox: { enable: false },\n });\n const sessionTools = (await session.tools()) as MastraToolCollection;\n\n for (const slug of sessionToolSlugs) {\n const tool = sessionTools[slug];\n if (tool) mastraTools[slug] = tool;\n }\n }\n\n const result: Record<string, ToolAction<any, any, any>> = {};\n\n for (const [key, tool] of Object.entries(mastraTools)) {\n if (!tool) continue;\n const slug = (tool as { id?: string }).id ?? key;\n\n const descOverride = opts.toolMeta?.[slug]?.description;\n if (descOverride) {\n try {\n (tool as unknown as { description: string }).description = descOverride;\n } catch {\n // ignore\n }\n }\n\n result[slug] = tool as ToolAction<any, any, any>;\n }\n\n return result;\n }\n\n // ── auth surface ──────────────────────────────────────────────────────\n\n async authorize(opts: AuthorizeOpts): Promise<{ url: string; authId: string }> {\n const composio = this.getRawClient();\n const { id: authConfigId, authScheme } = await this.resolveAuthConfig(opts.toolkit);\n\n // `connectionId` carries the internal user bucket for the runtime fan-out;\n // for authorize we treat it as the Composio `userId` so the new connected\n // account lands under the same bucket as the agent's resolved identity.\n const internalUserId = opts.connectionId || DEFAULT_INTERNAL_USER_ID;\n\n // `config` carries provider-specific user-supplied fields (e.g. Confluence\n // subdomain) collected by the picker via `listConnectionFields`. When it is\n // present we must use `connectedAccounts.initiate`, which accepts a\n // discriminated `{ authScheme, val }` config for programmatic account\n // creation. Composio's non-deprecated `connectedAccounts.link` (hosted\n // Connect Link) has no `config` parameter, so it cannot carry these fields.\n const initiateConfig =\n opts.config && Object.keys(opts.config).length > 0 && authScheme\n ? ({ authScheme, val: opts.config } as unknown as Parameters<\n typeof composio.connectedAccounts.initiate\n >[2] extends infer O\n ? O extends { config?: infer C }\n ? C\n : never\n : never)\n : undefined;\n\n // Prefer `link` for the Composio-managed OAuth redirect flow: `initiate`\n // is deprecated for managed OAuth. `link` allows multiple connected\n // accounts per (user, auth config) by default, so we no longer pass\n // `allowMultiple`. Fall back to `initiate` only when custom `config` fields\n // are supplied, since `link` cannot forward them.\n const request = initiateConfig\n ? await composio.connectedAccounts.initiate(internalUserId, authConfigId, {\n allowMultiple: true,\n config: initiateConfig,\n })\n : await composio.connectedAccounts.link(internalUserId, authConfigId);\n\n if (!request.redirectUrl) {\n throw new Error(`[composio] authorize did not return a redirectUrl for toolkit \"${opts.toolkit}\"`);\n }\n\n return { url: request.redirectUrl, authId: request.id };\n }\n\n async listConnectionFields({ toolkit }: { toolkit: string }): Promise<ConnectionField[]> {\n const composio = this.getRawClient();\n const { authScheme } = await this.resolveAuthConfig(toolkit);\n if (!authScheme) {\n // Without a known auth scheme we can't query the field schema — fall\n // back to no fields rather than blocking the user.\n return [];\n }\n const fields = await composio.toolkits.getConnectedAccountInitiationFields(toolkit, authScheme, {\n requiredOnly: false,\n });\n return fields.map(f => ({\n name: f.name,\n displayName: f.displayName,\n description: f.description,\n type: coerceFieldType(f.type),\n required: f.required ?? false,\n default: f.default ?? undefined,\n }));\n }\n\n async getAuthStatus(authId: string): Promise<AuthFlowStatus> {\n const composio = this.getRawClient();\n const account = await composio.connectedAccounts.get(authId);\n switch (account.status) {\n case 'ACTIVE':\n return 'completed';\n case 'INITIALIZING':\n case 'INITIATED':\n return 'pending';\n case 'FAILED':\n case 'EXPIRED':\n case 'INACTIVE':\n return 'failed';\n default:\n return 'pending';\n }\n }\n\n async getConnectionStatus(opts: {\n items: Array<{ connectionId: string; toolkit: string }>;\n }): Promise<Record<string, { connected: boolean }>> {\n if (opts.items.length === 0) return {};\n\n const composio = this.getRawClient();\n const toolkitSlugs = Array.from(new Set(opts.items.map(i => i.toolkit)));\n\n // One SDK call per `getConnectionStatus`, regardless of N items.\n // Filter by all referenced toolkits, then bucket locally by id.\n const list: ConnectedAccountListResponse = await composio.connectedAccounts.list({\n toolkitSlugs,\n });\n\n const liveById = new Map<string, { status: string; isDisabled: boolean }>();\n for (const item of list.items) {\n liveById.set(item.id, { status: item.status, isDisabled: item.isDisabled });\n }\n\n const result: Record<string, { connected: boolean }> = {};\n for (const { connectionId } of opts.items) {\n const live = liveById.get(connectionId);\n result[connectionId] = { connected: live ? live.status === 'ACTIVE' && !live.isDisabled : false };\n }\n return result;\n }\n\n async listConnections(opts: ListConnectionsOpts): Promise<ListConnectionsResult> {\n const composio = this.getRawClient();\n const page = opts.page ?? 1;\n const perPage = clampLimit(opts.perPage);\n\n // Normalize userIds[] / userId. Empty array = no buckets to list against,\n // short-circuit to avoid an unbounded Composio response.\n const userIds = resolveUserIds(opts);\n if (userIds && userIds.length === 0) {\n return { items: [], pagination: { page, perPage, hasMore: false } };\n }\n\n // Composio SDK 0.6.x uses cursor-based pagination on the wire. We surface\n // page-based pagination to keep the Mastra contract consistent with every\n // other list API. For now we only fetch the first page (page=1); paginated\n // requests for page > 1 are a follow-up — the UI does not yet paginate.\n const list: ConnectedAccountListResponse = await composio.connectedAccounts.list({\n toolkitSlugs: [opts.toolkit],\n ...(userIds ? { userIds } : {}),\n limit: perPage,\n });\n\n // Defensive: tolerate undocumented SDK shape drift where `items` is\n // missing or `nextCursor` is `null`/`undefined`/`''`.\n const items: ExistingConnection[] = (list.items ?? []).map(account => ({\n connectionId: account.id,\n status: mapComposioStatus(account.status, account.isDisabled),\n createdAt: account.createdAt,\n // `user_id` is preserved by the Composio SDK transform via spread but\n // isn't on the typed shape. Read it via a narrow cast.\n authorId: (account as unknown as { user_id?: string }).user_id,\n }));\n\n const nextCursor = (list as { nextCursor?: string | null }).nextCursor ?? null;\n const hasMore = typeof nextCursor === 'string' && nextCursor.length > 0;\n return { items, pagination: { page, perPage, hasMore } };\n }\n\n /**\n * Revoke a Composio connected account via\n * `DELETE /api/v3/connected_accounts/:nanoid`. Composio performs a soft\n * delete and responds with `{ success: boolean }`.\n *\n * Treats a 404 (account already deleted or never existed) as success so\n * the caller can drop its local pin without an error path. A `success:\n * false` response means the provider refused the delete and is surfaced\n * as an error so the caller does not delete its local row.\n */\n async revokeConnection(connectionId: string): Promise<void> {\n const composio = this.getRawClient();\n try {\n const res = (await composio.connectedAccounts.delete(connectionId)) as { success?: boolean } | undefined;\n if (res && res.success === false) {\n throw new Error(`Composio refused to delete connected account ${connectionId} (success=false)`);\n }\n } catch (err) {\n if (isNotFoundError(err)) return;\n throw err;\n }\n }\n\n async getHealth(): Promise<ToolProviderHealth> {\n try {\n const composio = this.getRawClient();\n await composio.toolkits.get({ limit: 1 } as Parameters<typeof composio.toolkits.get>[0]);\n return { ok: true };\n } catch (err) {\n return {\n ok: false,\n message: err instanceof Error ? err.message : 'Composio SDK reachability check failed',\n };\n }\n }\n\n // ── helpers ───────────────────────────────────────────────────────────\n\n /**\n * Resolve the single ENABLED auth config for `toolkit`. Throws if zero\n * or multiple configs match — the admin must enable exactly one in the\n * Composio dashboard before agents can connect.\n */\n private async resolveAuthConfig(toolkit: string): Promise<{ id: string; authScheme?: ComposioAuthScheme }> {\n const composio = this.getRawClient();\n const response = await composio.authConfigs.list({ toolkit });\n const enabled = response.items.filter(item => item.status === 'ENABLED');\n\n if (enabled.length === 0) {\n throw new Error(\n `[composio] No ENABLED auth config for toolkit \"${toolkit}\". Enable one in the Composio dashboard.`,\n );\n }\n if (enabled.length > 1) {\n const ids = enabled.map(item => item.id).join(', ');\n throw new Error(\n `[composio] Multiple ENABLED auth configs for toolkit \"${toolkit}\" (${ids}). Keep exactly one enabled.`,\n );\n }\n return { id: enabled[0]!.id, authScheme: enabled[0]!.authScheme };\n }\n}\n\ntype ComposioAuthScheme = NonNullable<\n Awaited<ReturnType<Composio['authConfigs']['list']>>['items'][number]['authScheme']\n>;\n\n/**\n * Best-effort 404 detection across the various error shapes the Composio\n * SDK surfaces (typed error with `statusCode`, HTTP-like error with\n * `status`, or a plain message containing \"404\" / \"not found\").\n */\nfunction isNotFoundError(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false;\n const e = err as { statusCode?: number; status?: number; message?: string };\n if (e.statusCode === 404 || e.status === 404) return true;\n const msg = typeof e.message === 'string' ? e.message.toLowerCase() : '';\n return msg.includes('not found') || msg.includes('404');\n}\n\n/**\n * Composio reports a free-form `type` string. Map common values to our\n * generic ConnectionField type vocabulary; everything else falls back to\n * `'string'`.\n */\nfunction coerceFieldType(type: string): 'string' | 'number' | 'boolean' {\n switch (type.toLowerCase()) {\n case 'number':\n case 'integer':\n case 'int':\n case 'float':\n return 'number';\n case 'bool':\n case 'boolean':\n return 'boolean';\n default:\n return 'string';\n }\n}\n\n/**\n * Map Composio account status + `isDisabled` to the {@link ExistingConnection}\n * status vocabulary surfaced to the picker UI.\n */\nfunction mapComposioStatus(status: string, isDisabled: boolean): ExistingConnection['status'] {\n if (isDisabled) return 'inactive';\n switch (status) {\n case 'ACTIVE':\n return 'active';\n case 'INITIALIZING':\n case 'INITIATED':\n return 'pending';\n case 'FAILED':\n case 'EXPIRED':\n return 'failed';\n case 'INACTIVE':\n return 'inactive';\n default:\n return 'pending';\n }\n}\n\n// Mirror of `MASTRA_USER_KEY` from `@mastra/server`. Inlined to avoid a\n// reverse dependency from `editor` onto `server`.\nconst MASTRA_USER_KEY = 'mastra__user';\n\n/**\n * Read the internal user id (Composio `userId`) from per-request context.\n *\n * The runtime fan-out is responsible for stamping the agent's resolved\n * author id (or `'default'`) into `requestContext` under\n * {@link MASTRA_RESOURCE_ID_KEY}.\n */\nfunction resolveInternalUserId(requestContext?: Record<string, unknown>): string {\n const resourceId = requestContext?.[MASTRA_RESOURCE_ID_KEY];\n if (typeof resourceId === 'string' && resourceId.length > 0) {\n return resourceId;\n }\n\n const user = requestContext?.[MASTRA_USER_KEY];\n if (user && typeof user === 'object' && 'id' in user) {\n const id = (user as { id: unknown }).id;\n if (typeof id === 'string' && id.length > 0) {\n return id;\n }\n }\n\n return DEFAULT_INTERNAL_USER_ID;\n}\n\n/**\n * Resolve `userIds[]` from `listConnections` opts.\n *\n * - If `userIds` is provided, use it as-is (including empty array, which\n * means \"no buckets to list against\").\n * - If `userId` is provided, normalize to `[userId]`.\n * - Otherwise fall back to the default internal user id (single-bucket).\n */\nfunction resolveUserIds(opts: ListConnectionsOpts): string[] | undefined {\n if (Array.isArray(opts.userIds)) return opts.userIds;\n if (typeof opts.userId === 'string' && opts.userId.length > 0) return [opts.userId];\n return [DEFAULT_INTERNAL_USER_ID];\n}\n\nconst DEFAULT_LIMIT = 50;\nconst MAX_LIMIT = 200;\n\nfunction clampLimit(limit: number | undefined): number {\n if (typeof limit !== 'number' || !Number.isFinite(limit) || limit <= 0) {\n return DEFAULT_LIMIT;\n }\n return Math.min(Math.floor(limit), MAX_LIMIT);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"composio.cjs","names":["BaseToolProvider","Composio","MastraProvider","MASTRA_RESOURCE_ID_KEY"],"sources":["../src/providers/composio.ts"],"sourcesContent":["import type {\n AuthFlowStatus,\n AuthorizeOpts,\n ConnectionField,\n ExistingConnection,\n ListConnectionsOpts,\n ListConnectionsResult,\n ListToolsOpts,\n ListToolsResult,\n ResolveToolsOpts,\n ToolProviderCapabilities,\n ToolProviderHealth,\n ToolProviderInfo,\n ToolProviderToolkit,\n BaseToolProviderOptions,\n} from '@mastra/core/tool-provider';\nimport { BaseToolProvider } from '@mastra/core/tool-provider';\nimport type { ToolAction } from '@mastra/core/tools';\nimport { MASTRA_RESOURCE_ID_KEY } from '@mastra/core/request-context';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { Composio } from '@composio/core';\nimport type {\n ConnectedAccountListResponse,\n Tool as ComposioTool,\n ToolListParams as ComposioToolListParams,\n ToolKitItem,\n} from '@composio/core';\nimport { MastraProvider } from '@composio/mastra';\nimport type { MastraToolCollection } from '@composio/mastra';\n\nexport interface ComposioToolProviderConfig extends BaseToolProviderOptions {\n /** Composio API key. */\n apiKey: string;\n /**\n * Server-side resolver mapping request context to the Composio `userId` the\n * call should execute as. Runs for `kind: 'invoker'` and `caller-supplied`\n * resolution, so the host application (for example, its FGA layer) can\n * derive and authorize the effective user before execution.\n *\n * Only server-populated fields within request context are trusted. When the\n * resolver is absent (or returns `undefined`), invoker connections require\n * the authenticated user (`MASTRA_USER_KEY`). Legacy `caller-supplied`\n * connections retain their existing resource-id fallback.\n *\n * The exact `connectedAccountId` always comes from the stored connection\n * pin — the resolver cannot override it.\n */\n userIdResolver?: ComposioUserIdResolver;\n}\n\n/** Inputs handed to {@link ComposioToolProviderConfig.userIdResolver}. */\nexport interface ComposioUserIdResolverInput {\n /** Live per-request context. Use `get()` for declared keys and `getRaw()` for reserved runtime keys. */\n requestContext?: RequestContext;\n /** Toolkit slug the identity is being resolved for, when known. */\n toolkit?: string;\n /**\n * The stored connection pin being resolved, when one exists. Hosts can use\n * it to validate that the invoker is allowed to use this exact account.\n */\n connectedAccountId?: string;\n}\n\n/**\n * Server-side resolver returning the effective Composio `userId` for a\n * request. Returning `undefined` falls back to the provider's default\n * identity resolution. Must never trust client-supplied context values.\n */\nexport type ComposioUserIdResolver = (\n input: ComposioUserIdResolverInput,\n) => Promise<string | undefined> | string | undefined;\n\nconst COMPOSIO_PROVIDER_ID = 'composio' as const;\nconst DEFAULT_INTERNAL_USER_ID = 'default';\nconst COMPOSIO_CONNECTION_MANAGEMENT_TOOLS = new Set(['COMPOSIO_MANAGE_CONNECTIONS', 'COMPOSIO_WAIT_FOR_CONNECTIONS']);\n\n/**\n * Composio implementation of the {@link BaseToolProvider} contract.\n *\n * Discovery (`listAllToolkits`, `listAllTools`) uses the raw Composio\n * client. Runtime (`resolveToolsVNext`) uses {@link MastraProvider} so resolved\n * tools are already in `createTool()` shape. Ordinary tools use Composio's\n * direct-tools API, while connection-management tools use a caller-scoped\n * Tool Router session. Resolved tools keep the `outputSchema` supplied by\n * `@composio/mastra`, which pre-relaxes Composio's strict API schemas\n * (nullable fields, extra properties, no `required`) so real third-party\n * responses validate while structurally invalid output is still rejected.\n *\n * Allowlist filtering is layered by {@link BaseToolProvider}; this class\n * never reads `allowedToolkits` / `allowedTools` directly.\n */\nexport class ComposioToolProvider extends BaseToolProvider {\n readonly info: ToolProviderInfo = {\n id: COMPOSIO_PROVIDER_ID,\n name: 'Composio',\n description: 'Access 10,000+ tools from 150+ apps via Composio',\n };\n readonly capabilities: ToolProviderCapabilities = {\n multipleConnectionsPerToolkit: true,\n batchConnectionStatus: true,\n reauthorizeReusesConnectionId: true,\n supportsRevoke: true,\n };\n\n readonly userIdResolver?: ComposioUserIdResolver;\n\n private readonly apiKey: string;\n private rawClient: Composio | null = null;\n private mastraClient: Composio<MastraProvider> | null = null;\n\n constructor(config: ComposioToolProviderConfig) {\n super({\n allowedToolkits: config.allowedToolkits,\n allowedTools: config.allowedTools,\n defaultScope: config.defaultScope,\n });\n this.apiKey = config.apiKey;\n this.userIdResolver = config.userIdResolver;\n }\n\n // ── client cache ──────────────────────────────────────────────────────\n\n private getRawClient(): Composio {\n if (!this.rawClient) {\n this.rawClient = new Composio({ apiKey: this.apiKey });\n }\n return this.rawClient;\n }\n\n private getMastraClient(): Composio<MastraProvider> {\n if (!this.mastraClient) {\n this.mastraClient = new Composio({\n apiKey: this.apiKey,\n provider: new MastraProvider(),\n });\n }\n return this.mastraClient;\n }\n\n // ── catalog (BaseToolProvider adds allowlist filter on top) ───────────\n\n protected async listAllToolkits(): Promise<ToolProviderToolkit[]> {\n const composio = this.getRawClient();\n const toolkits: ToolKitItem[] = await composio.toolkits.get({});\n return toolkits.map(tk => ({\n slug: tk.slug,\n name: tk.name,\n description: tk.meta?.description,\n icon: tk.meta?.logo,\n }));\n }\n\n protected async listAllTools(opts: ListToolsOpts): Promise<ListToolsResult> {\n const composio = this.getRawClient();\n\n // Composio's `getRawComposioTools` query is a discriminated union — every\n // variant accepts `limit`, but the toolkits/search keys are exclusive in\n // the TS types. We build the variant we need, then cast to the union.\n //\n // When the caller doesn't scope to a specific toolkit, we fall back to\n // the admin allowlist so the SDK returns a flat list across allowed\n // toolkits in a single hop (vs. fanning out per toolkit).\n const limit = opts.perPage;\n const fallbackToolkits = this.allowedToolkits.length > 0 ? [...this.allowedToolkits] : undefined;\n const query: ComposioToolListParams = (\n opts.toolkit\n ? { toolkits: [opts.toolkit], limit, search: opts.search }\n : fallbackToolkits\n ? { toolkits: fallbackToolkits, limit, search: opts.search }\n : opts.search\n ? { search: opts.search, limit }\n : { toolkits: [] as string[], limit }\n ) as ComposioToolListParams;\n\n // Composio's SDK validates every tool's input/output schema against an\n // internal zod shape and throws on the first malformed tool — so one bad\n // toolkit can poison a multi-toolkit query. Treat validation errors as a\n // soft failure and return an empty page rather than a 500.\n let rawTools: ComposioTool[] = [];\n try {\n rawTools = await composio.tools.getRawComposioTools(query);\n } catch (err) {\n console.warn(\n `[ComposioToolProvider] listAllTools failed for query ${JSON.stringify(query)} — returning empty page`,\n err,\n );\n }\n\n const data = rawTools.map(tool => ({\n slug: tool.slug,\n name: tool.name ?? tool.slug,\n description: tool.description,\n toolkit: tool.toolkit?.slug ?? opts.toolkit ?? '',\n }));\n\n return {\n data,\n pagination: {\n page: opts.page ?? 1,\n perPage: limit,\n hasMore: limit !== undefined && rawTools.length >= limit,\n },\n };\n }\n\n // ── runtime ───────────────────────────────────────────────────────────\n\n async resolveToolsVNext(opts: ResolveToolsOpts): Promise<Record<string, ToolAction<any, any, any>>> {\n if (opts.toolSlugs.length === 0) return {};\n\n const identity = await this.resolveExecutionIdentity(opts);\n const composio = this.getMastraClient();\n const sessionToolSlugs = opts.toolSlugs.filter(slug => COMPOSIO_CONNECTION_MANAGEMENT_TOOLS.has(slug));\n const directToolSlugs = opts.toolSlugs.filter(slug => !COMPOSIO_CONNECTION_MANAGEMENT_TOOLS.has(slug));\n const mastraTools: MastraToolCollection = {};\n\n if (directToolSlugs.length > 0) {\n const modifiers = {\n // `connectedAccountId` is not threaded through Composio's `execute`\n // option bag in @composio/mastra; the only documented per-call hook\n // is `beforeExecute`, which receives the params object that flows\n // into the API call. Mutating `params.connectedAccountId` routes\n // the call to a specific account.\n beforeExecute: ({ params }: { params: { connectedAccountId?: string; userId?: string } }) => {\n if (identity.connectionId) {\n params.connectedAccountId = identity.connectionId;\n }\n return params;\n },\n };\n\n Object.assign(\n mastraTools,\n (await composio.tools.get(identity.userId, { tools: directToolSlugs }, modifiers)) as MastraToolCollection,\n );\n }\n\n if (sessionToolSlugs.length > 0) {\n const selectedToolkits = [\n ...new Set(\n Object.values(opts.toolMeta)\n .map(meta => meta.toolkit)\n .filter(\n (toolkit): toolkit is string =>\n typeof toolkit === 'string' && toolkit.toLowerCase() !== COMPOSIO_PROVIDER_ID,\n ),\n ),\n ];\n const session = await composio.sessions.create(identity.userId, {\n ...(selectedToolkits.length > 0 ? { toolkits: selectedToolkits } : {}),\n manageConnections: { enable: true, waitForConnections: true },\n sandbox: { enable: false },\n });\n const sessionTools = (await session.tools()) as MastraToolCollection;\n\n for (const slug of sessionToolSlugs) {\n const tool = sessionTools[slug];\n if (tool) mastraTools[slug] = tool;\n }\n }\n\n const result: Record<string, ToolAction<any, any, any>> = {};\n\n for (const [key, tool] of Object.entries(mastraTools)) {\n if (!tool) continue;\n const slug = (tool as { id?: string }).id ?? key;\n\n const descOverride = opts.toolMeta?.[slug]?.description;\n if (descOverride) {\n try {\n (tool as unknown as { description: string }).description = descOverride;\n } catch {\n // ignore\n }\n }\n\n result[slug] = tool as ToolAction<any, any, any>;\n }\n\n return result;\n }\n\n /**\n * Run the configured `userIdResolver` and validate its result. Returns\n * the resolved user id, or `undefined` when no resolver is configured or\n * the resolver declined (returned `undefined`). Throws when the resolver\n * returns an empty or non-string value — an empty execution identity must\n * fail closed instead of silently falling back.\n */\n private async runUserIdResolver(input: ComposioUserIdResolverInput): Promise<string | undefined> {\n if (!this.userIdResolver) return undefined;\n const resolved = await this.userIdResolver(input);\n if (resolved === undefined) return undefined;\n if (typeof resolved !== 'string') {\n throw new Error('[composio] userIdResolver must return a non-empty string or undefined');\n }\n const normalized = resolved.trim();\n if (normalized.length === 0) {\n throw new Error('[composio] userIdResolver must return a non-empty string or undefined');\n }\n return normalized;\n }\n\n /**\n * Resolve the effective Composio execution identity for one\n * `resolveToolsVNext` call: the `userId` bucket to fetch tools under and\n * the exact `connectedAccountId` to route execution to (absent = let\n * Composio auto-resolve within the bucket).\n */\n private async resolveExecutionIdentity(opts: ResolveToolsOpts): Promise<{ userId: string; connectionId?: string }> {\n // The unpinned caller-supplied bootstrap fan-out passes the user bucket\n // itself as `connectionId` (connectionId === authorId). That is not an\n // account pin, so execution must stay on Composio's per-bucket\n // auto-resolve.\n const hasAccountPin = opts.connectionId !== opts.authorId;\n\n if (opts.kind === 'invoker') {\n const resolvedUserId = await this.runUserIdResolver({\n requestContext: opts.requestContext,\n toolkit: opts.toolkit,\n connectedAccountId: opts.connectionId,\n });\n // Invoker connections execute as the authenticated user — never the\n // Memory resource id — against the exact stored account pin (which may\n // be an account another user shared with the invoker via Composio ACL).\n return {\n userId: resolvedUserId ?? resolveInvokerUserId(opts.requestContext),\n connectionId: opts.connectionId,\n };\n }\n\n if (opts.scope === 'caller-supplied') {\n const resolvedUserId = await this.runUserIdResolver({\n requestContext: opts.requestContext,\n toolkit: opts.toolkit,\n connectedAccountId: hasAccountPin ? opts.connectionId : undefined,\n });\n return {\n userId: resolvedUserId ?? resolveInternalUserId(opts.requestContext),\n connectionId: hasAccountPin ? opts.connectionId : undefined,\n };\n }\n\n // Author-bound (and legacy) connections: the runtime fan-out passes the\n // agent author's id explicitly. Use it as the Composio user bucket so the\n // pin resolves for any invoker (not just the original author), and always\n // route execution to the pinned account.\n return {\n userId: opts.authorId && opts.authorId.length > 0 ? opts.authorId : resolveInternalUserId(opts.requestContext),\n connectionId: opts.connectionId,\n };\n }\n\n // ── auth surface ──────────────────────────────────────────────────────\n\n async authorize(opts: AuthorizeOpts): Promise<{ url: string; authId: string }> {\n const composio = this.getRawClient();\n const { id: authConfigId, authScheme } = await this.resolveAuthConfig(opts.toolkit);\n\n // `connectionId` carries the internal user bucket for the runtime fan-out;\n // for authorize we treat it as the Composio `userId` so the new connected\n // account lands under the same bucket as the agent's resolved identity.\n const internalUserId = opts.connectionId || DEFAULT_INTERNAL_USER_ID;\n\n // `config` carries provider-specific user-supplied fields (e.g. Confluence\n // subdomain) collected by the picker via `listConnectionFields`. When it is\n // present we must use `connectedAccounts.initiate`, which accepts a\n // discriminated `{ authScheme, val }` config for programmatic account\n // creation. Composio's non-deprecated `connectedAccounts.link` (hosted\n // Connect Link) has no `config` parameter, so it cannot carry these fields.\n const initiateConfig =\n opts.config && Object.keys(opts.config).length > 0 && authScheme\n ? ({ authScheme, val: opts.config } as unknown as Parameters<\n typeof composio.connectedAccounts.initiate\n >[2] extends infer O\n ? O extends { config?: infer C }\n ? C\n : never\n : never)\n : undefined;\n\n // Prefer `link` for the Composio-managed OAuth redirect flow: `initiate`\n // is deprecated for managed OAuth. `link` allows multiple connected\n // accounts per (user, auth config) by default, so we no longer pass\n // `allowMultiple`. Fall back to `initiate` only when custom `config` fields\n // are supplied, since `link` cannot forward them.\n const request = initiateConfig\n ? await composio.connectedAccounts.initiate(internalUserId, authConfigId, {\n allowMultiple: true,\n config: initiateConfig,\n })\n : await composio.connectedAccounts.link(internalUserId, authConfigId);\n\n if (!request.redirectUrl) {\n throw new Error(`[composio] authorize did not return a redirectUrl for toolkit \"${opts.toolkit}\"`);\n }\n\n return { url: request.redirectUrl, authId: request.id };\n }\n\n async listConnectionFields({ toolkit }: { toolkit: string }): Promise<ConnectionField[]> {\n const composio = this.getRawClient();\n const { authScheme } = await this.resolveAuthConfig(toolkit);\n if (!authScheme) {\n // Without a known auth scheme we can't query the field schema — fall\n // back to no fields rather than blocking the user.\n return [];\n }\n const fields = await composio.toolkits.getConnectedAccountInitiationFields(toolkit, authScheme, {\n requiredOnly: false,\n });\n return fields.map(f => ({\n name: f.name,\n displayName: f.displayName,\n description: f.description,\n type: coerceFieldType(f.type),\n required: f.required ?? false,\n default: f.default ?? undefined,\n }));\n }\n\n async getAuthStatus(authId: string): Promise<AuthFlowStatus> {\n const composio = this.getRawClient();\n const account = await composio.connectedAccounts.get(authId);\n switch (account.status) {\n case 'ACTIVE':\n return 'completed';\n case 'INITIALIZING':\n case 'INITIATED':\n return 'pending';\n case 'FAILED':\n case 'EXPIRED':\n case 'INACTIVE':\n return 'failed';\n default:\n return 'pending';\n }\n }\n\n async getConnectionStatus(opts: {\n items: Array<{ connectionId: string; toolkit: string }>;\n }): Promise<Record<string, { connected: boolean }>> {\n if (opts.items.length === 0) return {};\n\n const composio = this.getRawClient();\n const toolkitSlugs = Array.from(new Set(opts.items.map(i => i.toolkit)));\n\n // One SDK call per `getConnectionStatus`, regardless of N items.\n // Filter by all referenced toolkits, then bucket locally by id.\n const list: ConnectedAccountListResponse = await composio.connectedAccounts.list({\n toolkitSlugs,\n });\n\n const liveById = new Map<string, { status: string; isDisabled: boolean }>();\n for (const item of list.items) {\n liveById.set(item.id, { status: item.status, isDisabled: item.isDisabled });\n }\n\n const result: Record<string, { connected: boolean }> = {};\n for (const { connectionId } of opts.items) {\n const live = liveById.get(connectionId);\n result[connectionId] = { connected: live ? live.status === 'ACTIVE' && !live.isDisabled : false };\n }\n return result;\n }\n\n async listConnections(opts: ListConnectionsOpts): Promise<ListConnectionsResult> {\n const composio = this.getRawClient();\n const page = opts.page ?? 1;\n const perPage = clampLimit(opts.perPage);\n\n // Normalize userIds[] / userId. Empty array = no buckets to list against,\n // short-circuit to avoid an unbounded Composio response.\n const userIds = resolveUserIds(opts);\n if (userIds && userIds.length === 0) {\n return { items: [], pagination: { page, perPage, hasMore: false } };\n }\n\n // Composio SDK uses cursor-based pagination on the wire. We surface\n // page-based pagination to keep the Mastra contract consistent with every\n // other list API. For now we only fetch the first page (page=1); paginated\n // requests for page > 1 are a follow-up — the UI does not yet paginate.\n const list: ConnectedAccountListResponse = await composio.connectedAccounts.list({\n toolkitSlugs: [opts.toolkit],\n ...(userIds ? { userIds } : {}),\n limit: perPage,\n });\n\n // Defensive: tolerate undocumented SDK shape drift where `items` is\n // missing or `nextCursor` is `null`/`undefined`/`''`.\n const items: ExistingConnection[] = (list.items ?? []).map(account => ({\n connectionId: account.id,\n status: mapComposioStatus(account.status, account.isDisabled),\n createdAt: account.createdAt,\n // `user_id` is preserved by the Composio SDK transform via spread but\n // isn't on the typed shape. Read it via a narrow cast.\n authorId: (account as unknown as { user_id?: string }).user_id,\n }));\n\n const nextCursor = (list as { nextCursor?: string | null }).nextCursor ?? null;\n const hasMore = typeof nextCursor === 'string' && nextCursor.length > 0;\n return { items, pagination: { page, perPage, hasMore } };\n }\n\n /**\n * Revoke a Composio connected account via\n * `DELETE /api/v3/connected_accounts/:nanoid`. Composio performs a soft\n * delete and responds with `{ success: boolean }`.\n *\n * Treats a 404 (account already deleted or never existed) as success so\n * the caller can drop its local pin without an error path. A `success:\n * false` response means the provider refused the delete and is surfaced\n * as an error so the caller does not delete its local row.\n */\n async revokeConnection(connectionId: string): Promise<void> {\n const composio = this.getRawClient();\n try {\n const res = (await composio.connectedAccounts.delete(connectionId)) as { success?: boolean } | undefined;\n if (res && res.success === false) {\n throw new Error(`Composio refused to delete connected account ${connectionId} (success=false)`);\n }\n } catch (err) {\n if (isNotFoundError(err)) return;\n throw err;\n }\n }\n\n async getHealth(): Promise<ToolProviderHealth> {\n try {\n const composio = this.getRawClient();\n await composio.toolkits.get({ limit: 1 } as Parameters<typeof composio.toolkits.get>[0]);\n return { ok: true };\n } catch (err) {\n return {\n ok: false,\n message: err instanceof Error ? err.message : 'Composio SDK reachability check failed',\n };\n }\n }\n\n // ── helpers ───────────────────────────────────────────────────────────\n\n /**\n * Resolve the single ENABLED auth config for `toolkit`. Throws if zero\n * or multiple configs match — the admin must enable exactly one in the\n * Composio dashboard before agents can connect.\n */\n private async resolveAuthConfig(toolkit: string): Promise<{ id: string; authScheme?: ComposioAuthScheme }> {\n const composio = this.getRawClient();\n const response = await composio.authConfigs.list({ toolkit });\n const enabled = response.items.filter(item => item.status === 'ENABLED');\n\n if (enabled.length === 0) {\n throw new Error(\n `[composio] No ENABLED auth config for toolkit \"${toolkit}\". Enable one in the Composio dashboard.`,\n );\n }\n if (enabled.length > 1) {\n const ids = enabled.map(item => item.id).join(', ');\n throw new Error(\n `[composio] Multiple ENABLED auth configs for toolkit \"${toolkit}\" (${ids}). Keep exactly one enabled.`,\n );\n }\n return { id: enabled[0]!.id, authScheme: enabled[0]!.authScheme };\n }\n}\n\ntype ComposioAuthScheme = NonNullable<\n Awaited<ReturnType<Composio['authConfigs']['list']>>['items'][number]['authScheme']\n>;\n\n/**\n * Best-effort 404 detection across the various error shapes the Composio\n * SDK surfaces (typed error with `statusCode`, HTTP-like error with\n * `status`, or a plain message containing \"404\" / \"not found\").\n */\nfunction isNotFoundError(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false;\n const e = err as { statusCode?: number; status?: number; message?: string };\n if (e.statusCode === 404 || e.status === 404) return true;\n const msg = typeof e.message === 'string' ? e.message.toLowerCase() : '';\n return msg.includes('not found') || msg.includes('404');\n}\n\n/**\n * Composio reports a free-form `type` string. Map common values to our\n * generic ConnectionField type vocabulary; everything else falls back to\n * `'string'`.\n */\nfunction coerceFieldType(type: string): 'string' | 'number' | 'boolean' {\n switch (type.toLowerCase()) {\n case 'number':\n case 'integer':\n case 'int':\n case 'float':\n return 'number';\n case 'bool':\n case 'boolean':\n return 'boolean';\n default:\n return 'string';\n }\n}\n\n/**\n * Map Composio account status + `isDisabled` to the {@link ExistingConnection}\n * status vocabulary surfaced to the picker UI.\n */\nfunction mapComposioStatus(status: string, isDisabled: boolean): ExistingConnection['status'] {\n if (isDisabled) return 'inactive';\n switch (status) {\n case 'ACTIVE':\n return 'active';\n case 'INITIALIZING':\n case 'INITIATED':\n return 'pending';\n case 'FAILED':\n case 'EXPIRED':\n return 'failed';\n case 'INACTIVE':\n return 'inactive';\n default:\n return 'pending';\n }\n}\n\n// Mirror of `MASTRA_USER_KEY` from `@mastra/server`. Inlined to avoid a\n// reverse dependency from `editor` onto `server`.\nconst MASTRA_USER_KEY = 'mastra__user';\n\nfunction readAuthenticatedUserId(requestContext?: RequestContext): string | undefined {\n const user = requestContext?.getRaw(MASTRA_USER_KEY);\n if (!user || typeof user !== 'object' || !('id' in user)) return undefined;\n return typeof user.id === 'string' && user.id.length > 0 ? user.id : undefined;\n}\n\n/**\n * Read the internal user id (Composio `userId`) from per-request context.\n *\n * The runtime fan-out is responsible for stamping the agent's resolved\n * author id (or `'default'`) into `requestContext` under\n * {@link MASTRA_RESOURCE_ID_KEY}.\n */\nfunction resolveInternalUserId(requestContext?: RequestContext): string {\n const resourceId = requestContext?.getRaw(MASTRA_RESOURCE_ID_KEY);\n if (typeof resourceId === 'string' && resourceId.length > 0) {\n return resourceId;\n }\n\n return readAuthenticatedUserId(requestContext) ?? DEFAULT_INTERNAL_USER_ID;\n}\n\n/**\n * Read the authenticated invoker's Composio `userId` from per-request\n * context. Invoker connections must never fall back to the Memory resource id\n * because a project or thread is not an authenticated connector principal.\n */\nfunction resolveInvokerUserId(requestContext?: RequestContext): string {\n const userId = readAuthenticatedUserId(requestContext);\n if (userId) return userId;\n throw new Error('[composio] kind \"invoker\" requires an authenticated user or a userIdResolver result');\n}\n\n/**\n * Resolve `userIds[]` from `listConnections` opts.\n *\n * - If `userIds` is provided, use it as-is (including empty array, which\n * means \"no buckets to list against\").\n * - If `userId` is provided, normalize to `[userId]`.\n * - Otherwise fall back to the default internal user id (single-bucket).\n */\nfunction resolveUserIds(opts: ListConnectionsOpts): string[] | undefined {\n if (Array.isArray(opts.userIds)) return opts.userIds;\n if (typeof opts.userId === 'string' && opts.userId.length > 0) return [opts.userId];\n return [DEFAULT_INTERNAL_USER_ID];\n}\n\nconst DEFAULT_LIMIT = 50;\nconst MAX_LIMIT = 200;\n\nfunction clampLimit(limit: number | undefined): number {\n if (typeof limit !== 'number' || !Number.isFinite(limit) || limit <= 0) {\n return DEFAULT_LIMIT;\n }\n return Math.min(Math.floor(limit), MAX_LIMIT);\n}\n"],"mappings":"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"}
|
package/dist/composio.d.cts
CHANGED
|
@@ -1,10 +1,44 @@
|
|
|
1
1
|
import { AuthFlowStatus, AuthorizeOpts, BaseToolProvider, BaseToolProviderOptions, ConnectionField, ListConnectionsOpts, ListConnectionsResult, ListToolsOpts, ListToolsResult, ResolveToolsOpts, ToolProviderCapabilities, ToolProviderHealth, ToolProviderInfo, ToolProviderToolkit } from "@mastra/core/tool-provider";
|
|
2
2
|
import { ToolAction } from "@mastra/core/tools";
|
|
3
|
+
import { RequestContext } from "@mastra/core/request-context";
|
|
3
4
|
//#region src/providers/composio.d.ts
|
|
4
5
|
interface ComposioToolProviderConfig extends BaseToolProviderOptions {
|
|
5
6
|
/** Composio API key. */
|
|
6
7
|
apiKey: string;
|
|
8
|
+
/**
|
|
9
|
+
* Server-side resolver mapping request context to the Composio `userId` the
|
|
10
|
+
* call should execute as. Runs for `kind: 'invoker'` and `caller-supplied`
|
|
11
|
+
* resolution, so the host application (for example, its FGA layer) can
|
|
12
|
+
* derive and authorize the effective user before execution.
|
|
13
|
+
*
|
|
14
|
+
* Only server-populated fields within request context are trusted. When the
|
|
15
|
+
* resolver is absent (or returns `undefined`), invoker connections require
|
|
16
|
+
* the authenticated user (`MASTRA_USER_KEY`). Legacy `caller-supplied`
|
|
17
|
+
* connections retain their existing resource-id fallback.
|
|
18
|
+
*
|
|
19
|
+
* The exact `connectedAccountId` always comes from the stored connection
|
|
20
|
+
* pin — the resolver cannot override it.
|
|
21
|
+
*/
|
|
22
|
+
userIdResolver?: ComposioUserIdResolver;
|
|
23
|
+
}
|
|
24
|
+
/** Inputs handed to {@link ComposioToolProviderConfig.userIdResolver}. */
|
|
25
|
+
interface ComposioUserIdResolverInput {
|
|
26
|
+
/** Live per-request context. Use `get()` for declared keys and `getRaw()` for reserved runtime keys. */
|
|
27
|
+
requestContext?: RequestContext;
|
|
28
|
+
/** Toolkit slug the identity is being resolved for, when known. */
|
|
29
|
+
toolkit?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The stored connection pin being resolved, when one exists. Hosts can use
|
|
32
|
+
* it to validate that the invoker is allowed to use this exact account.
|
|
33
|
+
*/
|
|
34
|
+
connectedAccountId?: string;
|
|
7
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Server-side resolver returning the effective Composio `userId` for a
|
|
38
|
+
* request. Returning `undefined` falls back to the provider's default
|
|
39
|
+
* identity resolution. Must never trust client-supplied context values.
|
|
40
|
+
*/
|
|
41
|
+
type ComposioUserIdResolver = (input: ComposioUserIdResolverInput) => Promise<string | undefined> | string | undefined;
|
|
8
42
|
/**
|
|
9
43
|
* Composio implementation of the {@link BaseToolProvider} contract.
|
|
10
44
|
*
|
|
@@ -23,6 +57,7 @@ interface ComposioToolProviderConfig extends BaseToolProviderOptions {
|
|
|
23
57
|
declare class ComposioToolProvider extends BaseToolProvider {
|
|
24
58
|
readonly info: ToolProviderInfo;
|
|
25
59
|
readonly capabilities: ToolProviderCapabilities;
|
|
60
|
+
readonly userIdResolver?: ComposioUserIdResolver;
|
|
26
61
|
private readonly apiKey;
|
|
27
62
|
private rawClient;
|
|
28
63
|
private mastraClient;
|
|
@@ -32,6 +67,21 @@ declare class ComposioToolProvider extends BaseToolProvider {
|
|
|
32
67
|
protected listAllToolkits(): Promise<ToolProviderToolkit[]>;
|
|
33
68
|
protected listAllTools(opts: ListToolsOpts): Promise<ListToolsResult>;
|
|
34
69
|
resolveToolsVNext(opts: ResolveToolsOpts): Promise<Record<string, ToolAction<any, any, any>>>;
|
|
70
|
+
/**
|
|
71
|
+
* Run the configured `userIdResolver` and validate its result. Returns
|
|
72
|
+
* the resolved user id, or `undefined` when no resolver is configured or
|
|
73
|
+
* the resolver declined (returned `undefined`). Throws when the resolver
|
|
74
|
+
* returns an empty or non-string value — an empty execution identity must
|
|
75
|
+
* fail closed instead of silently falling back.
|
|
76
|
+
*/
|
|
77
|
+
private runUserIdResolver;
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the effective Composio execution identity for one
|
|
80
|
+
* `resolveToolsVNext` call: the `userId` bucket to fetch tools under and
|
|
81
|
+
* the exact `connectedAccountId` to route execution to (absent = let
|
|
82
|
+
* Composio auto-resolve within the bucket).
|
|
83
|
+
*/
|
|
84
|
+
private resolveExecutionIdentity;
|
|
35
85
|
authorize(opts: AuthorizeOpts): Promise<{
|
|
36
86
|
url: string;
|
|
37
87
|
authId: string;
|
|
@@ -69,5 +119,5 @@ declare class ComposioToolProvider extends BaseToolProvider {
|
|
|
69
119
|
private resolveAuthConfig;
|
|
70
120
|
}
|
|
71
121
|
//#endregion
|
|
72
|
-
export { ComposioToolProvider, type ComposioToolProviderConfig };
|
|
122
|
+
export { ComposioToolProvider, type ComposioToolProviderConfig, type ComposioUserIdResolver, type ComposioUserIdResolverInput };
|
|
73
123
|
//# sourceMappingURL=composio.d.cts.map
|
package/dist/composio.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composio.d.cts","names":[],"sources":["../src/providers/composio.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"composio.d.cts","names":[],"sources":["../src/providers/composio.ts"],"mappings":";;;;UA+BiB,mCAAmC;;EAElD;;;;;;;;;;;;;;;EAeA,iBAAiB;;;UAIF;;EAEf,iBAAiB;;EAEjB;;;;;EAKA;;;;;;;KAQU,0BACV,OAAO,gCACJ;;;;;;;;;;;;;;;;cAqBQ,6BAA6B;WAC/B,MAAM;WAKN,cAAc;WAOd,iBAAiB;mBAET;UACT;UACA;EAEI,YAAA,QAAQ;UAYZ;UAOA;YAYQ,mBAAmB,QAAQ;YAW3B,aAAa,MAAM,gBAAgB,QAAQ;EAuDrD,kBAAkB,MAAM,mBAAmB,QAAQ,eAAe;;;;;;;;UAkF1D;;;;;;;UAoBA;EA8CR,UAAU,MAAM,gBAAgB;IAAU;IAAa;;EA6CvD,uBAAuB;IAAa;MAAoB,QAAQ;EAqBhE,cAAc,iBAAiB,QAAQ;EAkBvC,oBAAoB;IACxB,OAAO;MAAQ;MAAsB;;MACnC,QAAQ;IAAiB;;EAyBvB,gBAAgB,MAAM,sBAAsB,QAAQ;;;;;;;;;;;EAgDpD,iBAAiB,uBAAuB;EAaxC,aAAa,QAAQ;;;;;;UAoBb"}
|
package/dist/composio.d.ts
CHANGED
|
@@ -1,10 +1,44 @@
|
|
|
1
1
|
import { AuthFlowStatus, AuthorizeOpts, BaseToolProvider, BaseToolProviderOptions, ConnectionField, ListConnectionsOpts, ListConnectionsResult, ListToolsOpts, ListToolsResult, ResolveToolsOpts, ToolProviderCapabilities, ToolProviderHealth, ToolProviderInfo, ToolProviderToolkit } from "@mastra/core/tool-provider";
|
|
2
|
+
import { RequestContext } from "@mastra/core/request-context";
|
|
2
3
|
import { ToolAction } from "@mastra/core/tools";
|
|
3
4
|
//#region src/providers/composio.d.ts
|
|
4
5
|
interface ComposioToolProviderConfig extends BaseToolProviderOptions {
|
|
5
6
|
/** Composio API key. */
|
|
6
7
|
apiKey: string;
|
|
8
|
+
/**
|
|
9
|
+
* Server-side resolver mapping request context to the Composio `userId` the
|
|
10
|
+
* call should execute as. Runs for `kind: 'invoker'` and `caller-supplied`
|
|
11
|
+
* resolution, so the host application (for example, its FGA layer) can
|
|
12
|
+
* derive and authorize the effective user before execution.
|
|
13
|
+
*
|
|
14
|
+
* Only server-populated fields within request context are trusted. When the
|
|
15
|
+
* resolver is absent (or returns `undefined`), invoker connections require
|
|
16
|
+
* the authenticated user (`MASTRA_USER_KEY`). Legacy `caller-supplied`
|
|
17
|
+
* connections retain their existing resource-id fallback.
|
|
18
|
+
*
|
|
19
|
+
* The exact `connectedAccountId` always comes from the stored connection
|
|
20
|
+
* pin — the resolver cannot override it.
|
|
21
|
+
*/
|
|
22
|
+
userIdResolver?: ComposioUserIdResolver;
|
|
23
|
+
}
|
|
24
|
+
/** Inputs handed to {@link ComposioToolProviderConfig.userIdResolver}. */
|
|
25
|
+
interface ComposioUserIdResolverInput {
|
|
26
|
+
/** Live per-request context. Use `get()` for declared keys and `getRaw()` for reserved runtime keys. */
|
|
27
|
+
requestContext?: RequestContext;
|
|
28
|
+
/** Toolkit slug the identity is being resolved for, when known. */
|
|
29
|
+
toolkit?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The stored connection pin being resolved, when one exists. Hosts can use
|
|
32
|
+
* it to validate that the invoker is allowed to use this exact account.
|
|
33
|
+
*/
|
|
34
|
+
connectedAccountId?: string;
|
|
7
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Server-side resolver returning the effective Composio `userId` for a
|
|
38
|
+
* request. Returning `undefined` falls back to the provider's default
|
|
39
|
+
* identity resolution. Must never trust client-supplied context values.
|
|
40
|
+
*/
|
|
41
|
+
type ComposioUserIdResolver = (input: ComposioUserIdResolverInput) => Promise<string | undefined> | string | undefined;
|
|
8
42
|
/**
|
|
9
43
|
* Composio implementation of the {@link BaseToolProvider} contract.
|
|
10
44
|
*
|
|
@@ -23,6 +57,7 @@ interface ComposioToolProviderConfig extends BaseToolProviderOptions {
|
|
|
23
57
|
declare class ComposioToolProvider extends BaseToolProvider {
|
|
24
58
|
readonly info: ToolProviderInfo;
|
|
25
59
|
readonly capabilities: ToolProviderCapabilities;
|
|
60
|
+
readonly userIdResolver?: ComposioUserIdResolver;
|
|
26
61
|
private readonly apiKey;
|
|
27
62
|
private rawClient;
|
|
28
63
|
private mastraClient;
|
|
@@ -32,6 +67,21 @@ declare class ComposioToolProvider extends BaseToolProvider {
|
|
|
32
67
|
protected listAllToolkits(): Promise<ToolProviderToolkit[]>;
|
|
33
68
|
protected listAllTools(opts: ListToolsOpts): Promise<ListToolsResult>;
|
|
34
69
|
resolveToolsVNext(opts: ResolveToolsOpts): Promise<Record<string, ToolAction<any, any, any>>>;
|
|
70
|
+
/**
|
|
71
|
+
* Run the configured `userIdResolver` and validate its result. Returns
|
|
72
|
+
* the resolved user id, or `undefined` when no resolver is configured or
|
|
73
|
+
* the resolver declined (returned `undefined`). Throws when the resolver
|
|
74
|
+
* returns an empty or non-string value — an empty execution identity must
|
|
75
|
+
* fail closed instead of silently falling back.
|
|
76
|
+
*/
|
|
77
|
+
private runUserIdResolver;
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the effective Composio execution identity for one
|
|
80
|
+
* `resolveToolsVNext` call: the `userId` bucket to fetch tools under and
|
|
81
|
+
* the exact `connectedAccountId` to route execution to (absent = let
|
|
82
|
+
* Composio auto-resolve within the bucket).
|
|
83
|
+
*/
|
|
84
|
+
private resolveExecutionIdentity;
|
|
35
85
|
authorize(opts: AuthorizeOpts): Promise<{
|
|
36
86
|
url: string;
|
|
37
87
|
authId: string;
|
|
@@ -69,5 +119,5 @@ declare class ComposioToolProvider extends BaseToolProvider {
|
|
|
69
119
|
private resolveAuthConfig;
|
|
70
120
|
}
|
|
71
121
|
//#endregion
|
|
72
|
-
export { ComposioToolProvider, type ComposioToolProviderConfig };
|
|
122
|
+
export { ComposioToolProvider, type ComposioToolProviderConfig, type ComposioUserIdResolver, type ComposioUserIdResolverInput };
|
|
73
123
|
//# sourceMappingURL=composio.d.ts.map
|
package/dist/composio.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composio.d.ts","names":[],"sources":["../src/providers/composio.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"composio.d.ts","names":[],"sources":["../src/providers/composio.ts"],"mappings":";;;;UA+BiB,mCAAmC;;EAElD;;;;;;;;;;;;;;;EAeA,iBAAiB;;;UAIF;;EAEf,iBAAiB;;EAEjB;;;;;EAKA;;;;;;;KAQU,0BACV,OAAO,gCACJ;;;;;;;;;;;;;;;;cAqBQ,6BAA6B;WAC/B,MAAM;WAKN,cAAc;WAOd,iBAAiB;mBAET;UACT;UACA;EAEI,YAAA,QAAQ;UAYZ;UAOA;YAYQ,mBAAmB,QAAQ;YAW3B,aAAa,MAAM,gBAAgB,QAAQ;EAuDrD,kBAAkB,MAAM,mBAAmB,QAAQ,eAAe;;;;;;;;UAkF1D;;;;;;;UAoBA;EA8CR,UAAU,MAAM,gBAAgB;IAAU;IAAa;;EA6CvD,uBAAuB;IAAa;MAAoB,QAAQ;EAqBhE,cAAc,iBAAiB,QAAQ;EAkBvC,oBAAoB;IACxB,OAAO;MAAQ;MAAsB;;MACnC,QAAQ;IAAiB;;EAyBvB,gBAAgB,MAAM,sBAAsB,QAAQ;;;;;;;;;;;EAgDpD,iBAAiB,uBAAuB;EAaxC,aAAa,QAAQ;;;;;;UAoBb"}
|
package/dist/composio.js
CHANGED
|
@@ -42,6 +42,7 @@ var ComposioToolProvider = class extends BaseToolProvider {
|
|
|
42
42
|
this.rawClient = null;
|
|
43
43
|
this.mastraClient = null;
|
|
44
44
|
this.apiKey = config.apiKey;
|
|
45
|
+
this.userIdResolver = config.userIdResolver;
|
|
45
46
|
}
|
|
46
47
|
getRawClient() {
|
|
47
48
|
if (!this.rawClient) this.rawClient = new Composio({ apiKey: this.apiKey });
|
|
@@ -103,18 +104,18 @@ var ComposioToolProvider = class extends BaseToolProvider {
|
|
|
103
104
|
}
|
|
104
105
|
async resolveToolsVNext(opts) {
|
|
105
106
|
if (opts.toolSlugs.length === 0) return {};
|
|
106
|
-
const
|
|
107
|
+
const identity = await this.resolveExecutionIdentity(opts);
|
|
107
108
|
const composio = this.getMastraClient();
|
|
108
109
|
const sessionToolSlugs = opts.toolSlugs.filter((slug) => COMPOSIO_CONNECTION_MANAGEMENT_TOOLS.has(slug));
|
|
109
110
|
const directToolSlugs = opts.toolSlugs.filter((slug) => !COMPOSIO_CONNECTION_MANAGEMENT_TOOLS.has(slug));
|
|
110
111
|
const mastraTools = {};
|
|
111
|
-
if (directToolSlugs.length > 0) Object.assign(mastraTools, await composio.tools.get(
|
|
112
|
-
if (
|
|
112
|
+
if (directToolSlugs.length > 0) Object.assign(mastraTools, await composio.tools.get(identity.userId, { tools: directToolSlugs }, { beforeExecute: ({ params }) => {
|
|
113
|
+
if (identity.connectionId) params.connectedAccountId = identity.connectionId;
|
|
113
114
|
return params;
|
|
114
115
|
} }));
|
|
115
116
|
if (sessionToolSlugs.length > 0) {
|
|
116
117
|
const selectedToolkits = [...new Set(Object.values(opts.toolMeta).map((meta) => meta.toolkit).filter((toolkit) => typeof toolkit === "string" && toolkit.toLowerCase() !== COMPOSIO_PROVIDER_ID))];
|
|
117
|
-
const sessionTools = await (await composio.sessions.create(
|
|
118
|
+
const sessionTools = await (await composio.sessions.create(identity.userId, {
|
|
118
119
|
...selectedToolkits.length > 0 ? { toolkits: selectedToolkits } : {},
|
|
119
120
|
manageConnections: {
|
|
120
121
|
enable: true,
|
|
@@ -139,6 +140,51 @@ var ComposioToolProvider = class extends BaseToolProvider {
|
|
|
139
140
|
}
|
|
140
141
|
return result;
|
|
141
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Run the configured `userIdResolver` and validate its result. Returns
|
|
145
|
+
* the resolved user id, or `undefined` when no resolver is configured or
|
|
146
|
+
* the resolver declined (returned `undefined`). Throws when the resolver
|
|
147
|
+
* returns an empty or non-string value — an empty execution identity must
|
|
148
|
+
* fail closed instead of silently falling back.
|
|
149
|
+
*/
|
|
150
|
+
async runUserIdResolver(input) {
|
|
151
|
+
if (!this.userIdResolver) return void 0;
|
|
152
|
+
const resolved = await this.userIdResolver(input);
|
|
153
|
+
if (resolved === void 0) return void 0;
|
|
154
|
+
if (typeof resolved !== "string") throw new Error("[composio] userIdResolver must return a non-empty string or undefined");
|
|
155
|
+
const normalized = resolved.trim();
|
|
156
|
+
if (normalized.length === 0) throw new Error("[composio] userIdResolver must return a non-empty string or undefined");
|
|
157
|
+
return normalized;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Resolve the effective Composio execution identity for one
|
|
161
|
+
* `resolveToolsVNext` call: the `userId` bucket to fetch tools under and
|
|
162
|
+
* the exact `connectedAccountId` to route execution to (absent = let
|
|
163
|
+
* Composio auto-resolve within the bucket).
|
|
164
|
+
*/
|
|
165
|
+
async resolveExecutionIdentity(opts) {
|
|
166
|
+
const hasAccountPin = opts.connectionId !== opts.authorId;
|
|
167
|
+
if (opts.kind === "invoker") return {
|
|
168
|
+
userId: await this.runUserIdResolver({
|
|
169
|
+
requestContext: opts.requestContext,
|
|
170
|
+
toolkit: opts.toolkit,
|
|
171
|
+
connectedAccountId: opts.connectionId
|
|
172
|
+
}) ?? resolveInvokerUserId(opts.requestContext),
|
|
173
|
+
connectionId: opts.connectionId
|
|
174
|
+
};
|
|
175
|
+
if (opts.scope === "caller-supplied") return {
|
|
176
|
+
userId: await this.runUserIdResolver({
|
|
177
|
+
requestContext: opts.requestContext,
|
|
178
|
+
toolkit: opts.toolkit,
|
|
179
|
+
connectedAccountId: hasAccountPin ? opts.connectionId : void 0
|
|
180
|
+
}) ?? resolveInternalUserId(opts.requestContext),
|
|
181
|
+
connectionId: hasAccountPin ? opts.connectionId : void 0
|
|
182
|
+
};
|
|
183
|
+
return {
|
|
184
|
+
userId: opts.authorId && opts.authorId.length > 0 ? opts.authorId : resolveInternalUserId(opts.requestContext),
|
|
185
|
+
connectionId: opts.connectionId
|
|
186
|
+
};
|
|
187
|
+
}
|
|
142
188
|
async authorize(opts) {
|
|
143
189
|
const composio = this.getRawClient();
|
|
144
190
|
const { id: authConfigId, authScheme } = await this.resolveAuthConfig(opts.toolkit);
|
|
@@ -326,6 +372,11 @@ function mapComposioStatus(status, isDisabled) {
|
|
|
326
372
|
}
|
|
327
373
|
}
|
|
328
374
|
const MASTRA_USER_KEY = "mastra__user";
|
|
375
|
+
function readAuthenticatedUserId(requestContext) {
|
|
376
|
+
const user = requestContext?.getRaw(MASTRA_USER_KEY);
|
|
377
|
+
if (!user || typeof user !== "object" || !("id" in user)) return void 0;
|
|
378
|
+
return typeof user.id === "string" && user.id.length > 0 ? user.id : void 0;
|
|
379
|
+
}
|
|
329
380
|
/**
|
|
330
381
|
* Read the internal user id (Composio `userId`) from per-request context.
|
|
331
382
|
*
|
|
@@ -334,14 +385,19 @@ const MASTRA_USER_KEY = "mastra__user";
|
|
|
334
385
|
* {@link MASTRA_RESOURCE_ID_KEY}.
|
|
335
386
|
*/
|
|
336
387
|
function resolveInternalUserId(requestContext) {
|
|
337
|
-
const resourceId = requestContext?.
|
|
388
|
+
const resourceId = requestContext?.getRaw(MASTRA_RESOURCE_ID_KEY);
|
|
338
389
|
if (typeof resourceId === "string" && resourceId.length > 0) return resourceId;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
390
|
+
return readAuthenticatedUserId(requestContext) ?? DEFAULT_INTERNAL_USER_ID;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Read the authenticated invoker's Composio `userId` from per-request
|
|
394
|
+
* context. Invoker connections must never fall back to the Memory resource id
|
|
395
|
+
* because a project or thread is not an authenticated connector principal.
|
|
396
|
+
*/
|
|
397
|
+
function resolveInvokerUserId(requestContext) {
|
|
398
|
+
const userId = readAuthenticatedUserId(requestContext);
|
|
399
|
+
if (userId) return userId;
|
|
400
|
+
throw new Error("[composio] kind \"invoker\" requires an authenticated user or a userIdResolver result");
|
|
345
401
|
}
|
|
346
402
|
/**
|
|
347
403
|
* Resolve `userIds[]` from `listConnections` opts.
|