@nightowlsdev/integration-composio
Adapter/ToolingThe Composio IntegrationProvider backend, the first catalog-owning connector backend: 1000+ apps' tools and triggers come from Composio, which also runs the tool and holds the credentials.
What it does
@nightowlsdev/integration-composio implements the `IntegrationProvider` seam from `@nightowlsdev/connectors` against Composio, and it is the first backend that declares `ownsCatalog: true`: the tools and triggers are Composio's typed catalog rather than your own `defineConnector` definitions, and Composio executes the call and stores the user's credentials in its cloud. That makes it an optional, managed convenience, never the OSS default, the open defaults stay `@nightowlsdev/integration-native` (self-run OAuth, credentials on your infrastructure) or self-hosted Nango. The Composio SDK is INJECTED as a narrow `ComposioClient` seam, so the package carries no vendor dependency, stays engine-wall clean, and is hermetically unit-testable (the host wires the real `@composio/core`). `listTools` scopes the catalog to the connection's own app by default so a call never dumps thousands of tools into model context, then applies your optional `allow` predicate. `executeTool` maps failures onto the connector error model, a dead credential becomes a `ConnectorConnectionError` (re-auth), a transient or 429 becomes a `ConnectorRetryableError` (back off), anything else becomes a `ToolResult.error`, and success data is fenced before it reaches model context, because output from tools you did not author needs fencing more, not less. `startConnect` / `resolveConnection` / `revokeConnection` / `refreshStatus` map onto Composio's connection lifecycle; `handleInboundWebhook` appears only when you inject `parseTriggerEvent` (Composio is a ROUTED backend, so the host verifies Composio's own webhook signature at the HTTP layer first, then this parses the envelope for `dispatchTriggerEvent`), and `subscribeTrigger` appears only when the injected client can `enableTrigger`. To offer Composio for some apps and the open backends for others, compose them with `compositeProvider` rather than merging by hand.
Install
pnpm add @nightowlsdev/integration-composioKey exports
- composioProvider
- types: ComposioProviderOpts, ComposioClient, ComposioToolSpec, ComposioListToolsInput, ComposioExecuteInput, ComposioExecuteResult, ComposioConnection
Usage
import { composioProvider, type ComposioClient } from "@nightowlsdev/integration-composio";
import { compositeProvider, materializeIntegration } from "@nightowlsdev/connectors";
import { Composio } from "@composio/core"; // the SDK is INJECTED — this package has no vendor dep
// Wire the SDK to the narrow ComposioClient seam (listTools/executeTool/connections) — the
// package never imports the vendor, so the adapter is yours and the SDK can move under you.
const sdk = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
const client: ComposioClient = {
listTools: async (q) => toToolPage(await sdk.tools.get(q.entityId, { apps: q.apps, cursor: q.cursor })),
// connectionId PINS execution to the resolved connected account — forward it, never execute against "any".
executeTool: (q) => sdk.tools.execute(q.toolName, { userId: q.entityId, connectedAccountId: q.connectionId, arguments: q.args }),
initiateConnection: (q) => sdk.connectedAccounts.initiate({ userId: q.entityId, appName: q.app }),
getConnection: (q) => findActive(sdk.connectedAccounts, q),
disconnect: (q) => revoke(sdk.connectedAccounts, q),
};
// Composio owns the catalog (ownsCatalog: true): its 1000+ apps' tools, its execution, its credentials.
const composio = composioProvider({
composio: client,
entityIdFor: (ref) => `${ref.tenantId}:${ref.userId ?? "org"}`,
allow: (t) => !t.name.endsWith("_DELETE"), // optional, on top of the per-connection scoping
parseTriggerEvent, // optional: enables handleInboundWebhook
});
// Managed convenience, not the OSS default — route the sensitive apps to an open backend instead.
const provider = compositeProvider({
backends: { native: nativeProvider(/* … */), composio },
routeNew: (p) => (SENSITIVE.has(p) ? "native" : "composio"),
lookupConnectionBackend,
});
const swarm = defineSwarm({ agents, connectorTools: materializeIntegration(provider, ctxToRefs) });