@tangle-network/agent-integrations 0.1.0 → 0.1.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/README.md CHANGED
@@ -101,6 +101,12 @@ const provider = createHttpIntegrationProvider({
101
101
  The HTTP adapter keeps product code stable while the backing provider can be
102
102
  Nango, Pipedream, Activepieces, a Zapier-style service, or an internal gateway.
103
103
 
104
+ See [Provider Decision Matrix](./docs/provider-decision-matrix.md) for the
105
+ build-vs-buy policy. The short version: use a vendor gateway only to compress
106
+ time-to-coverage, but keep all product and sandbox code on this package's
107
+ contracts so high-volume or strategic connectors can be moved first-party
108
+ without changing agent code.
109
+
104
110
  ## Security Defaults
105
111
 
106
112
  - Capabilities expire.
@@ -109,3 +115,6 @@ Nango, Pipedream, Activepieces, a Zapier-style service, or an internal gateway.
109
115
  - Write/destructive actions can be policy-gated.
110
116
  - Action invocation checks connection ownership, status, scopes, allowed
111
117
  actions, and expiration.
118
+ - Optional `IntegrationActionGuard` wraps every action invocation for
119
+ idempotency, audit logging, conflict detection, rate limits, and
120
+ approval gates.
package/dist/index.d.ts CHANGED
@@ -158,10 +158,50 @@ interface IssuedIntegrationCapability {
158
158
  capability: IntegrationCapability;
159
159
  token: string;
160
160
  }
161
+ /**
162
+ * Wraps every action invocation with cross-cutting discipline (idempotency,
163
+ * conflict detection, rate-limiting, audit logging). Optional. When set on
164
+ * the hub, runs BEFORE provider.invokeAction; can short-circuit (return a
165
+ * result directly) or pass through (call `proceed()` to invoke the provider).
166
+ *
167
+ * Why this hook exists: production deployments need conflict-resolution
168
+ * guarantees that span every provider — first-party, Nango, Composio,
169
+ * webhook receivers — and providers shouldn't re-implement them. The
170
+ * canonical implementation is a "MutationGuard" that:
171
+ * 1. Short-circuits on a known idempotency key (returns recorded response).
172
+ * 2. Refuses same-key-different-args (drift detection).
173
+ * 3. Wraps `proceed()` and audit-logs the outcome.
174
+ * 4. Translates upstream conflict signals into a structured result with
175
+ * alternatives the agent can act on.
176
+ *
177
+ * Implementations live in consumers (every product has different
178
+ * persistence + telemetry needs); this interface is the contract.
179
+ */
180
+ interface IntegrationActionGuard {
181
+ /** Wrap an invokeAction call. Implementations MUST call `proceed()` to
182
+ * invoke the underlying provider unless they're returning a cached or
183
+ * short-circuited result.
184
+ *
185
+ * @param ctx connection + request the hub is about to dispatch
186
+ * @param proceed call to invoke the wrapped provider; returns the
187
+ * underlying IntegrationActionResult
188
+ * @returns the result the hub should return to the caller
189
+ */
190
+ invokeAction(ctx: IntegrationGuardContext, proceed: () => Promise<IntegrationActionResult>): Promise<IntegrationActionResult>;
191
+ }
192
+ interface IntegrationGuardContext {
193
+ connection: IntegrationConnection;
194
+ request: IntegrationActionRequest;
195
+ /** The action descriptor from the connector manifest, if discovered. */
196
+ action?: IntegrationConnectorAction;
197
+ }
161
198
  interface IntegrationHubOptions {
162
199
  providers: IntegrationProvider[];
163
200
  store: IntegrationConnectionStore;
164
201
  capabilitySecret: string;
202
+ /** Optional cross-cutting guard. If provided, every invokeAction call
203
+ * passes through it before reaching the provider. See {@link IntegrationActionGuard}. */
204
+ guard?: IntegrationActionGuard;
165
205
  now?: () => Date;
166
206
  }
167
207
  interface HttpIntegrationProviderOptions {
@@ -190,6 +230,7 @@ declare class IntegrationHub {
190
230
  private readonly providers;
191
231
  private readonly store;
192
232
  private readonly capabilitySecret;
233
+ private readonly guard;
193
234
  private readonly now;
194
235
  constructor(options: IntegrationHubOptions);
195
236
  listConnectors(): Promise<IntegrationConnector[]>;
@@ -215,4 +256,4 @@ declare function createHttpIntegrationProvider(options: HttpIntegrationProviderO
215
256
  declare function signCapability(capability: IntegrationCapability, secret: string): string;
216
257
  declare function verifyCapabilityToken(token: string, secret: string): IntegrationCapability;
217
258
 
218
- export { type CompleteAuthRequest, type HttpIntegrationProviderOptions, InMemoryConnectionStore, type IntegrationActionRequest, type IntegrationActionResult, type IntegrationActionRisk, type IntegrationActor, type IntegrationCapability, type IntegrationConnection, type IntegrationConnectionStore, type IntegrationConnector, type IntegrationConnectorAction, type IntegrationConnectorCategory, type IntegrationConnectorTrigger, type IntegrationDataClass, IntegrationError, IntegrationHub, type IntegrationHubOptions, type IntegrationProvider, type IntegrationProviderKind, type IntegrationTriggerEvent, type IntegrationTriggerSubscription, type InvokeWithCapabilityRequest, type IssueCapabilityRequest, type IssuedIntegrationCapability, type SecretRef, type StartAuthRequest, type StartAuthResult, createHttpIntegrationProvider, createMockIntegrationProvider, sanitizeConnection, signCapability, verifyCapabilityToken };
259
+ export { type CompleteAuthRequest, type HttpIntegrationProviderOptions, InMemoryConnectionStore, type IntegrationActionGuard, type IntegrationActionRequest, type IntegrationActionResult, type IntegrationActionRisk, type IntegrationActor, type IntegrationCapability, type IntegrationConnection, type IntegrationConnectionStore, type IntegrationConnector, type IntegrationConnectorAction, type IntegrationConnectorCategory, type IntegrationConnectorTrigger, type IntegrationDataClass, IntegrationError, type IntegrationGuardContext, IntegrationHub, type IntegrationHubOptions, type IntegrationProvider, type IntegrationProviderKind, type IntegrationTriggerEvent, type IntegrationTriggerSubscription, type InvokeWithCapabilityRequest, type IssueCapabilityRequest, type IssuedIntegrationCapability, type SecretRef, type StartAuthRequest, type StartAuthResult, createHttpIntegrationProvider, createMockIntegrationProvider, sanitizeConnection, signCapability, verifyCapabilityToken };
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ var IntegrationHub = class {
29
29
  providers = /* @__PURE__ */ new Map();
30
30
  store;
31
31
  capabilitySecret;
32
+ guard;
32
33
  now;
33
34
  constructor(options) {
34
35
  if (!options.capabilitySecret) {
@@ -37,6 +38,7 @@ var IntegrationHub = class {
37
38
  for (const provider of options.providers) this.providers.set(provider.id, provider);
38
39
  this.store = options.store;
39
40
  this.capabilitySecret = options.capabilitySecret;
41
+ this.guard = options.guard;
40
42
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
41
43
  }
42
44
  async listConnectors() {
@@ -97,7 +99,12 @@ var IntegrationHub = class {
97
99
  if (!action) throw new IntegrationError(`Action ${request.action} is not defined by connector ${connector.id}.`, "action_not_found");
98
100
  assertScopes(connection, action.requiredScopes);
99
101
  assertScopes({ ...connection, grantedScopes: capability.scopes }, action.requiredScopes);
100
- return provider.invokeAction(connection, { ...request, connectionId: connection.id });
102
+ const fullRequest = { ...request, connectionId: connection.id };
103
+ const proceed = () => Promise.resolve(provider.invokeAction(connection, fullRequest));
104
+ if (this.guard) {
105
+ return this.guard.invokeAction({ connection, request: fullRequest, action }, proceed);
106
+ }
107
+ return proceed();
101
108
  }
102
109
  async subscribeTrigger(connectionId, trigger, targetUrl) {
103
110
  const connection = await this.requireConnection(connectionId);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto'\n\nexport type IntegrationProviderKind =\n | 'first_party'\n | 'nango'\n | 'pipedream'\n | 'zapier'\n | 'activepieces'\n | 'executor'\n | 'custom'\n\nexport type IntegrationConnectorCategory =\n | 'email'\n | 'calendar'\n | 'chat'\n | 'crm'\n | 'storage'\n | 'docs'\n | 'database'\n | 'webhook'\n | 'workflow'\n | 'internal'\n | 'other'\n\nexport type IntegrationActionRisk = 'read' | 'write' | 'destructive'\nexport type IntegrationDataClass = 'public' | 'internal' | 'private' | 'sensitive' | 'secret'\n\nexport interface IntegrationActor {\n type: 'user' | 'team' | 'agent' | 'sandbox' | 'system'\n id: string\n}\n\nexport interface IntegrationConnectorAction {\n id: string\n title: string\n risk: IntegrationActionRisk\n requiredScopes: string[]\n dataClass: IntegrationDataClass\n description?: string\n approvalRequired?: boolean\n inputSchema?: unknown\n outputSchema?: unknown\n}\n\nexport interface IntegrationConnectorTrigger {\n id: string\n title: string\n requiredScopes: string[]\n dataClass: IntegrationDataClass\n description?: string\n payloadSchema?: unknown\n}\n\nexport interface IntegrationConnector {\n id: string\n providerId: string\n title: string\n category: IntegrationConnectorCategory\n auth: 'oauth2' | 'api_key' | 'none' | 'custom'\n scopes: string[]\n actions: IntegrationConnectorAction[]\n triggers?: IntegrationConnectorTrigger[]\n metadata?: Record<string, unknown>\n}\n\nexport interface SecretRef {\n provider: string\n id: string\n label?: string\n}\n\nexport interface IntegrationConnection {\n id: string\n owner: IntegrationActor\n providerId: string\n connectorId: string\n status: 'pending' | 'active' | 'expired' | 'revoked' | 'error'\n grantedScopes: string[]\n account?: {\n id?: string\n email?: string\n displayName?: string\n }\n secretRef?: SecretRef\n createdAt: string\n updatedAt: string\n expiresAt?: string\n lastUsedAt?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface StartAuthRequest {\n connectorId: string\n owner: IntegrationActor\n requestedScopes: string[]\n redirectUri: string\n state?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface StartAuthResult {\n providerId: string\n connectorId: string\n authUrl: string\n state: string\n expiresAt?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface CompleteAuthRequest {\n connectorId: string\n owner: IntegrationActor\n code?: string\n state: string\n redirectUri: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationActionRequest {\n connectionId: string\n action: string\n input?: unknown\n idempotencyKey?: string\n dryRun?: boolean\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationActionResult<T = unknown> {\n ok: boolean\n action: string\n output?: T\n externalId?: string\n warnings?: string[]\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationTriggerSubscription {\n id: string\n connectionId: string\n trigger: string\n targetUrl?: string\n status: 'active' | 'paused' | 'error'\n createdAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationTriggerEvent<T = unknown> {\n id: string\n providerId: string\n connectorId: string\n connectionId: string\n trigger: string\n occurredAt: string\n payload: T\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationProvider {\n id: string\n kind: IntegrationProviderKind\n listConnectors(): Promise<IntegrationConnector[]> | IntegrationConnector[]\n startAuth?(request: StartAuthRequest): Promise<StartAuthResult> | StartAuthResult\n completeAuth?(request: CompleteAuthRequest): Promise<IntegrationConnection> | IntegrationConnection\n invokeAction(connection: IntegrationConnection, request: IntegrationActionRequest): Promise<IntegrationActionResult> | IntegrationActionResult\n subscribeTrigger?(connection: IntegrationConnection, trigger: string, targetUrl?: string): Promise<IntegrationTriggerSubscription> | IntegrationTriggerSubscription\n unsubscribeTrigger?(subscriptionId: string): Promise<void> | void\n normalizeTriggerEvent?(raw: unknown): Promise<IntegrationTriggerEvent> | IntegrationTriggerEvent\n}\n\nexport interface IntegrationConnectionStore {\n get(connectionId: string): Promise<IntegrationConnection | undefined> | IntegrationConnection | undefined\n put(connection: IntegrationConnection): Promise<void> | void\n listByOwner(owner: IntegrationActor): Promise<IntegrationConnection[]> | IntegrationConnection[]\n delete?(connectionId: string): Promise<void> | void\n}\n\nexport interface IssueCapabilityRequest {\n subject: IntegrationActor\n connectionId: string\n scopes: string[]\n allowedActions: string[]\n ttlMs: number\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationCapability {\n id: string\n subject: IntegrationActor\n connectionId: string\n scopes: string[]\n allowedActions: string[]\n issuedAt: string\n expiresAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IssuedIntegrationCapability {\n capability: IntegrationCapability\n token: string\n}\n\nexport interface IntegrationHubOptions {\n providers: IntegrationProvider[]\n store: IntegrationConnectionStore\n capabilitySecret: string\n now?: () => Date\n}\n\nexport interface HttpIntegrationProviderOptions {\n id: string\n kind?: IntegrationProviderKind\n connectors: IntegrationConnector[]\n baseUrl: string\n bearer?: string\n fetchImpl?: typeof fetch\n}\n\nexport interface InvokeWithCapabilityRequest extends Omit<IntegrationActionRequest, 'connectionId'> {\n connectionId?: never\n}\n\nexport class IntegrationError extends Error {\n constructor(\n message: string,\n readonly code:\n | 'provider_not_found'\n | 'connector_not_found'\n | 'connection_not_found'\n | 'connection_not_active'\n | 'auth_not_supported'\n | 'capability_invalid'\n | 'capability_expired'\n | 'scope_denied'\n | 'action_denied'\n | 'action_not_found',\n ) {\n super(message)\n this.name = 'IntegrationError'\n }\n}\n\nexport class InMemoryConnectionStore implements IntegrationConnectionStore {\n private readonly connections = new Map<string, IntegrationConnection>()\n\n get(connectionId: string): IntegrationConnection | undefined {\n return this.connections.get(connectionId)\n }\n\n put(connection: IntegrationConnection): void {\n this.connections.set(connection.id, connection)\n }\n\n listByOwner(owner: IntegrationActor): IntegrationConnection[] {\n return [...this.connections.values()].filter((connection) =>\n connection.owner.type === owner.type && connection.owner.id === owner.id,\n )\n }\n\n delete(connectionId: string): void {\n this.connections.delete(connectionId)\n }\n}\n\nexport class IntegrationHub {\n private readonly providers = new Map<string, IntegrationProvider>()\n private readonly store: IntegrationConnectionStore\n private readonly capabilitySecret: string\n private readonly now: () => Date\n\n constructor(options: IntegrationHubOptions) {\n if (!options.capabilitySecret) {\n throw new IntegrationError('capabilitySecret is required.', 'capability_invalid')\n }\n for (const provider of options.providers) this.providers.set(provider.id, provider)\n this.store = options.store\n this.capabilitySecret = options.capabilitySecret\n this.now = options.now ?? (() => new Date())\n }\n\n async listConnectors(): Promise<IntegrationConnector[]> {\n const catalogs = await Promise.all([...this.providers.values()].map((provider) => provider.listConnectors()))\n return catalogs.flat()\n }\n\n async startAuth(providerId: string, request: StartAuthRequest): Promise<StartAuthResult> {\n const provider = this.requireProvider(providerId)\n if (!provider.startAuth) throw new IntegrationError(`Provider ${providerId} does not support auth start.`, 'auth_not_supported')\n await this.requireConnector(provider, request.connectorId)\n return provider.startAuth(request)\n }\n\n async completeAuth(providerId: string, request: CompleteAuthRequest): Promise<IntegrationConnection> {\n const provider = this.requireProvider(providerId)\n if (!provider.completeAuth) throw new IntegrationError(`Provider ${providerId} does not support auth completion.`, 'auth_not_supported')\n const connection = await provider.completeAuth(request)\n await this.store.put(connection)\n return connection\n }\n\n async upsertConnection(connection: IntegrationConnection): Promise<IntegrationConnection> {\n await this.store.put(connection)\n return connection\n }\n\n async issueCapability(request: IssueCapabilityRequest): Promise<IssuedIntegrationCapability> {\n const connection = await this.requireConnection(request.connectionId)\n this.assertConnectionActive(connection)\n assertScopes(connection, request.scopes)\n const now = this.now()\n const capability: IntegrationCapability = {\n id: `cap_${randomUUID()}`,\n subject: request.subject,\n connectionId: request.connectionId,\n scopes: unique(request.scopes),\n allowedActions: unique(request.allowedActions),\n issuedAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + request.ttlMs).toISOString(),\n metadata: request.metadata,\n }\n return { capability, token: signCapability(capability, this.capabilitySecret) }\n }\n\n verifyCapability(token: string): IntegrationCapability {\n const capability = verifyCapabilityToken(token, this.capabilitySecret)\n if (Date.parse(capability.expiresAt) <= this.now().getTime()) {\n throw new IntegrationError('Integration capability expired.', 'capability_expired')\n }\n return capability\n }\n\n async invokeWithCapability(token: string, request: InvokeWithCapabilityRequest): Promise<IntegrationActionResult> {\n const capability = this.verifyCapability(token)\n if (!capability.allowedActions.includes(request.action)) {\n throw new IntegrationError(`Capability does not allow action ${request.action}.`, 'action_denied')\n }\n const connection = await this.requireConnection(capability.connectionId)\n this.assertConnectionActive(connection)\n const provider = this.requireProvider(connection.providerId)\n const connector = await this.requireConnector(provider, connection.connectorId)\n const action = connector.actions.find((candidate) => candidate.id === request.action)\n if (!action) throw new IntegrationError(`Action ${request.action} is not defined by connector ${connector.id}.`, 'action_not_found')\n assertScopes(connection, action.requiredScopes)\n assertScopes({ ...connection, grantedScopes: capability.scopes }, action.requiredScopes)\n return provider.invokeAction(connection, { ...request, connectionId: connection.id })\n }\n\n async subscribeTrigger(connectionId: string, trigger: string, targetUrl?: string): Promise<IntegrationTriggerSubscription> {\n const connection = await this.requireConnection(connectionId)\n this.assertConnectionActive(connection)\n const provider = this.requireProvider(connection.providerId)\n const connector = await this.requireConnector(provider, connection.connectorId)\n const spec = connector.triggers?.find((candidate) => candidate.id === trigger)\n if (!spec) throw new IntegrationError(`Trigger ${trigger} is not defined by connector ${connector.id}.`, 'action_not_found')\n assertScopes(connection, spec.requiredScopes)\n if (!provider.subscribeTrigger) {\n throw new IntegrationError(`Provider ${provider.id} does not support triggers.`, 'auth_not_supported')\n }\n return provider.subscribeTrigger(connection, trigger, targetUrl)\n }\n\n private requireProvider(providerId: string): IntegrationProvider {\n const provider = this.providers.get(providerId)\n if (!provider) throw new IntegrationError(`Provider ${providerId} not found.`, 'provider_not_found')\n return provider\n }\n\n private async requireConnector(provider: IntegrationProvider, connectorId: string): Promise<IntegrationConnector> {\n const connector = (await provider.listConnectors()).find((candidate) => candidate.id === connectorId)\n if (!connector) throw new IntegrationError(`Connector ${connectorId} not found.`, 'connector_not_found')\n return connector\n }\n\n private async requireConnection(connectionId: string): Promise<IntegrationConnection> {\n const connection = await this.store.get(connectionId)\n if (!connection) throw new IntegrationError(`Connection ${connectionId} not found.`, 'connection_not_found')\n return connection\n }\n\n private assertConnectionActive(connection: IntegrationConnection): void {\n if (connection.status !== 'active') {\n throw new IntegrationError(`Connection ${connection.id} is ${connection.status}.`, 'connection_not_active')\n }\n if (connection.expiresAt && Date.parse(connection.expiresAt) <= this.now().getTime()) {\n throw new IntegrationError(`Connection ${connection.id} is expired.`, 'connection_not_active')\n }\n }\n}\n\nexport function sanitizeConnection(connection: IntegrationConnection): Record<string, unknown> {\n return {\n id: connection.id,\n owner: connection.owner,\n providerId: connection.providerId,\n connectorId: connection.connectorId,\n status: connection.status,\n grantedScopes: connection.grantedScopes,\n account: connection.account,\n hasSecretRef: Boolean(connection.secretRef),\n createdAt: connection.createdAt,\n updatedAt: connection.updatedAt,\n expiresAt: connection.expiresAt,\n lastUsedAt: connection.lastUsedAt,\n }\n}\n\nexport function createMockIntegrationProvider(options: {\n id?: string\n connectors?: IntegrationConnector[]\n onInvoke?: (connection: IntegrationConnection, request: IntegrationActionRequest) => IntegrationActionResult | Promise<IntegrationActionResult>\n} = {}): IntegrationProvider {\n const providerId = options.id ?? 'mock'\n const connectors = options.connectors ?? [{\n id: 'gmail',\n providerId,\n title: 'Gmail',\n category: 'email',\n auth: 'oauth2',\n scopes: ['email.read', 'email.write'],\n actions: [\n { id: 'messages.search', title: 'Search messages', risk: 'read', requiredScopes: ['email.read'], dataClass: 'private' },\n { id: 'drafts.create', title: 'Create draft', risk: 'write', requiredScopes: ['email.write'], dataClass: 'private', approvalRequired: true },\n ],\n triggers: [\n { id: 'message.received', title: 'Message received', requiredScopes: ['email.read'], dataClass: 'private' },\n ],\n }]\n return {\n id: providerId,\n kind: 'custom',\n listConnectors: () => connectors,\n startAuth: (request) => ({\n providerId,\n connectorId: request.connectorId,\n authUrl: `https://auth.example.test/${request.connectorId}?state=${encodeURIComponent(request.state ?? 'state')}`,\n state: request.state ?? 'state',\n }),\n completeAuth: (request) => ({\n id: `conn_${request.connectorId}_${request.owner.id}`,\n owner: request.owner,\n providerId,\n connectorId: request.connectorId,\n status: 'active',\n grantedScopes: connectors.find((connector) => connector.id === request.connectorId)?.scopes ?? [],\n secretRef: { provider: providerId, id: `secret_${request.owner.id}` },\n createdAt: new Date(0).toISOString(),\n updatedAt: new Date(0).toISOString(),\n }),\n invokeAction: async (connection, request) => options.onInvoke?.(connection, request) ?? ({\n ok: true,\n action: request.action,\n output: { echo: request.input ?? null },\n }),\n subscribeTrigger: (connection, trigger, targetUrl) => ({\n id: `sub_${connection.id}_${trigger}`,\n connectionId: connection.id,\n trigger,\n targetUrl,\n status: 'active',\n createdAt: new Date(0).toISOString(),\n }),\n }\n}\n\nexport function createHttpIntegrationProvider(options: HttpIntegrationProviderOptions): IntegrationProvider {\n const fetcher = options.fetchImpl ?? fetch\n const baseUrl = options.baseUrl.replace(/\\/$/, '')\n return {\n id: options.id,\n kind: options.kind ?? 'custom',\n listConnectors: () => options.connectors,\n async startAuth(request) {\n const response = await postJson<StartAuthResult>(fetcher, `${baseUrl}/auth/start`, request, options.bearer)\n return response\n },\n async completeAuth(request) {\n const response = await postJson<IntegrationConnection>(fetcher, `${baseUrl}/auth/complete`, request, options.bearer)\n return response\n },\n async invokeAction(connection, request) {\n return postJson<IntegrationActionResult>(fetcher, `${baseUrl}/actions/invoke`, {\n connection,\n request,\n }, options.bearer)\n },\n async subscribeTrigger(connection, trigger, targetUrl) {\n return postJson<IntegrationTriggerSubscription>(fetcher, `${baseUrl}/triggers/subscribe`, {\n connection,\n trigger,\n targetUrl,\n }, options.bearer)\n },\n async unsubscribeTrigger(subscriptionId) {\n await postJson(fetcher, `${baseUrl}/triggers/unsubscribe`, { subscriptionId }, options.bearer)\n },\n async normalizeTriggerEvent(raw) {\n return postJson<IntegrationTriggerEvent>(fetcher, `${baseUrl}/triggers/normalize`, { raw }, options.bearer)\n },\n }\n}\n\nexport function signCapability(capability: IntegrationCapability, secret: string): string {\n const payload = base64UrlEncode(JSON.stringify(capability))\n const signature = hmac(payload, secret)\n return `${payload}.${signature}`\n}\n\nexport function verifyCapabilityToken(token: string, secret: string): IntegrationCapability {\n const [payload, signature] = token.split('.')\n if (!payload || !signature) throw new IntegrationError('Malformed integration capability.', 'capability_invalid')\n const expected = hmac(payload, secret)\n if (!constantTimeEqual(signature, expected)) throw new IntegrationError('Invalid integration capability signature.', 'capability_invalid')\n let parsed: IntegrationCapability\n try {\n parsed = JSON.parse(base64UrlDecode(payload)) as IntegrationCapability\n } catch {\n throw new IntegrationError('Invalid integration capability payload.', 'capability_invalid')\n }\n if (!parsed.id || !parsed.connectionId || !Array.isArray(parsed.scopes) || !Array.isArray(parsed.allowedActions)) {\n throw new IntegrationError('Invalid integration capability payload.', 'capability_invalid')\n }\n return parsed\n}\n\nasync function postJson<T = unknown>(\n fetcher: typeof fetch,\n url: string,\n body: unknown,\n bearer?: string,\n): Promise<T> {\n const response = await fetcher(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(bearer ? { Authorization: `Bearer ${bearer}` } : {}),\n },\n body: JSON.stringify(body),\n })\n if (!response.ok) throw new IntegrationError(`Integration provider returned HTTP ${response.status}.`, 'provider_not_found')\n return response.json() as Promise<T>\n}\n\nfunction assertScopes(connection: Pick<IntegrationConnection, 'grantedScopes'>, requiredScopes: string[]): void {\n const missing = requiredScopes.filter((scope) => !connection.grantedScopes.includes(scope))\n if (missing.length > 0) throw new IntegrationError(`Missing integration scopes: ${missing.join(', ')}`, 'scope_denied')\n}\n\nfunction hmac(payload: string, secret: string): string {\n return createHmac('sha256', secret).update(payload).digest('base64url')\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n const left = Buffer.from(a)\n const right = Buffer.from(b)\n return left.length === right.length && timingSafeEqual(left, right)\n}\n\nfunction base64UrlEncode(value: string): string {\n return Buffer.from(value, 'utf8').toString('base64url')\n}\n\nfunction base64UrlDecode(value: string): string {\n return Buffer.from(value, 'base64url').toString('utf8')\n}\n\nfunction unique<T>(values: T[]): T[] {\n return [...new Set(values)]\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto'\n\nexport type IntegrationProviderKind =\n | 'first_party'\n | 'nango'\n | 'pipedream'\n | 'zapier'\n | 'activepieces'\n | 'executor'\n | 'custom'\n\nexport type IntegrationConnectorCategory =\n | 'email'\n | 'calendar'\n | 'chat'\n | 'crm'\n | 'storage'\n | 'docs'\n | 'database'\n | 'webhook'\n | 'workflow'\n | 'internal'\n | 'other'\n\nexport type IntegrationActionRisk = 'read' | 'write' | 'destructive'\nexport type IntegrationDataClass = 'public' | 'internal' | 'private' | 'sensitive' | 'secret'\n\nexport interface IntegrationActor {\n type: 'user' | 'team' | 'agent' | 'sandbox' | 'system'\n id: string\n}\n\nexport interface IntegrationConnectorAction {\n id: string\n title: string\n risk: IntegrationActionRisk\n requiredScopes: string[]\n dataClass: IntegrationDataClass\n description?: string\n approvalRequired?: boolean\n inputSchema?: unknown\n outputSchema?: unknown\n}\n\nexport interface IntegrationConnectorTrigger {\n id: string\n title: string\n requiredScopes: string[]\n dataClass: IntegrationDataClass\n description?: string\n payloadSchema?: unknown\n}\n\nexport interface IntegrationConnector {\n id: string\n providerId: string\n title: string\n category: IntegrationConnectorCategory\n auth: 'oauth2' | 'api_key' | 'none' | 'custom'\n scopes: string[]\n actions: IntegrationConnectorAction[]\n triggers?: IntegrationConnectorTrigger[]\n metadata?: Record<string, unknown>\n}\n\nexport interface SecretRef {\n provider: string\n id: string\n label?: string\n}\n\nexport interface IntegrationConnection {\n id: string\n owner: IntegrationActor\n providerId: string\n connectorId: string\n status: 'pending' | 'active' | 'expired' | 'revoked' | 'error'\n grantedScopes: string[]\n account?: {\n id?: string\n email?: string\n displayName?: string\n }\n secretRef?: SecretRef\n createdAt: string\n updatedAt: string\n expiresAt?: string\n lastUsedAt?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface StartAuthRequest {\n connectorId: string\n owner: IntegrationActor\n requestedScopes: string[]\n redirectUri: string\n state?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface StartAuthResult {\n providerId: string\n connectorId: string\n authUrl: string\n state: string\n expiresAt?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface CompleteAuthRequest {\n connectorId: string\n owner: IntegrationActor\n code?: string\n state: string\n redirectUri: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationActionRequest {\n connectionId: string\n action: string\n input?: unknown\n idempotencyKey?: string\n dryRun?: boolean\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationActionResult<T = unknown> {\n ok: boolean\n action: string\n output?: T\n externalId?: string\n warnings?: string[]\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationTriggerSubscription {\n id: string\n connectionId: string\n trigger: string\n targetUrl?: string\n status: 'active' | 'paused' | 'error'\n createdAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationTriggerEvent<T = unknown> {\n id: string\n providerId: string\n connectorId: string\n connectionId: string\n trigger: string\n occurredAt: string\n payload: T\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationProvider {\n id: string\n kind: IntegrationProviderKind\n listConnectors(): Promise<IntegrationConnector[]> | IntegrationConnector[]\n startAuth?(request: StartAuthRequest): Promise<StartAuthResult> | StartAuthResult\n completeAuth?(request: CompleteAuthRequest): Promise<IntegrationConnection> | IntegrationConnection\n invokeAction(connection: IntegrationConnection, request: IntegrationActionRequest): Promise<IntegrationActionResult> | IntegrationActionResult\n subscribeTrigger?(connection: IntegrationConnection, trigger: string, targetUrl?: string): Promise<IntegrationTriggerSubscription> | IntegrationTriggerSubscription\n unsubscribeTrigger?(subscriptionId: string): Promise<void> | void\n normalizeTriggerEvent?(raw: unknown): Promise<IntegrationTriggerEvent> | IntegrationTriggerEvent\n}\n\nexport interface IntegrationConnectionStore {\n get(connectionId: string): Promise<IntegrationConnection | undefined> | IntegrationConnection | undefined\n put(connection: IntegrationConnection): Promise<void> | void\n listByOwner(owner: IntegrationActor): Promise<IntegrationConnection[]> | IntegrationConnection[]\n delete?(connectionId: string): Promise<void> | void\n}\n\nexport interface IssueCapabilityRequest {\n subject: IntegrationActor\n connectionId: string\n scopes: string[]\n allowedActions: string[]\n ttlMs: number\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationCapability {\n id: string\n subject: IntegrationActor\n connectionId: string\n scopes: string[]\n allowedActions: string[]\n issuedAt: string\n expiresAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IssuedIntegrationCapability {\n capability: IntegrationCapability\n token: string\n}\n\n/**\n * Wraps every action invocation with cross-cutting discipline (idempotency,\n * conflict detection, rate-limiting, audit logging). Optional. When set on\n * the hub, runs BEFORE provider.invokeAction; can short-circuit (return a\n * result directly) or pass through (call `proceed()` to invoke the provider).\n *\n * Why this hook exists: production deployments need conflict-resolution\n * guarantees that span every provider — first-party, Nango, Composio,\n * webhook receivers — and providers shouldn't re-implement them. The\n * canonical implementation is a \"MutationGuard\" that:\n * 1. Short-circuits on a known idempotency key (returns recorded response).\n * 2. Refuses same-key-different-args (drift detection).\n * 3. Wraps `proceed()` and audit-logs the outcome.\n * 4. Translates upstream conflict signals into a structured result with\n * alternatives the agent can act on.\n *\n * Implementations live in consumers (every product has different\n * persistence + telemetry needs); this interface is the contract.\n */\nexport interface IntegrationActionGuard {\n /** Wrap an invokeAction call. Implementations MUST call `proceed()` to\n * invoke the underlying provider unless they're returning a cached or\n * short-circuited result.\n *\n * @param ctx connection + request the hub is about to dispatch\n * @param proceed call to invoke the wrapped provider; returns the\n * underlying IntegrationActionResult\n * @returns the result the hub should return to the caller\n */\n invokeAction(\n ctx: IntegrationGuardContext,\n proceed: () => Promise<IntegrationActionResult>,\n ): Promise<IntegrationActionResult>\n}\n\nexport interface IntegrationGuardContext {\n connection: IntegrationConnection\n request: IntegrationActionRequest\n /** The action descriptor from the connector manifest, if discovered. */\n action?: IntegrationConnectorAction\n}\n\nexport interface IntegrationHubOptions {\n providers: IntegrationProvider[]\n store: IntegrationConnectionStore\n capabilitySecret: string\n /** Optional cross-cutting guard. If provided, every invokeAction call\n * passes through it before reaching the provider. See {@link IntegrationActionGuard}. */\n guard?: IntegrationActionGuard\n now?: () => Date\n}\n\nexport interface HttpIntegrationProviderOptions {\n id: string\n kind?: IntegrationProviderKind\n connectors: IntegrationConnector[]\n baseUrl: string\n bearer?: string\n fetchImpl?: typeof fetch\n}\n\nexport interface InvokeWithCapabilityRequest extends Omit<IntegrationActionRequest, 'connectionId'> {\n connectionId?: never\n}\n\nexport class IntegrationError extends Error {\n constructor(\n message: string,\n readonly code:\n | 'provider_not_found'\n | 'connector_not_found'\n | 'connection_not_found'\n | 'connection_not_active'\n | 'auth_not_supported'\n | 'capability_invalid'\n | 'capability_expired'\n | 'scope_denied'\n | 'action_denied'\n | 'action_not_found',\n ) {\n super(message)\n this.name = 'IntegrationError'\n }\n}\n\nexport class InMemoryConnectionStore implements IntegrationConnectionStore {\n private readonly connections = new Map<string, IntegrationConnection>()\n\n get(connectionId: string): IntegrationConnection | undefined {\n return this.connections.get(connectionId)\n }\n\n put(connection: IntegrationConnection): void {\n this.connections.set(connection.id, connection)\n }\n\n listByOwner(owner: IntegrationActor): IntegrationConnection[] {\n return [...this.connections.values()].filter((connection) =>\n connection.owner.type === owner.type && connection.owner.id === owner.id,\n )\n }\n\n delete(connectionId: string): void {\n this.connections.delete(connectionId)\n }\n}\n\nexport class IntegrationHub {\n private readonly providers = new Map<string, IntegrationProvider>()\n private readonly store: IntegrationConnectionStore\n private readonly capabilitySecret: string\n private readonly guard: IntegrationActionGuard | undefined\n private readonly now: () => Date\n\n constructor(options: IntegrationHubOptions) {\n if (!options.capabilitySecret) {\n throw new IntegrationError('capabilitySecret is required.', 'capability_invalid')\n }\n for (const provider of options.providers) this.providers.set(provider.id, provider)\n this.store = options.store\n this.capabilitySecret = options.capabilitySecret\n this.guard = options.guard\n this.now = options.now ?? (() => new Date())\n }\n\n async listConnectors(): Promise<IntegrationConnector[]> {\n const catalogs = await Promise.all([...this.providers.values()].map((provider) => provider.listConnectors()))\n return catalogs.flat()\n }\n\n async startAuth(providerId: string, request: StartAuthRequest): Promise<StartAuthResult> {\n const provider = this.requireProvider(providerId)\n if (!provider.startAuth) throw new IntegrationError(`Provider ${providerId} does not support auth start.`, 'auth_not_supported')\n await this.requireConnector(provider, request.connectorId)\n return provider.startAuth(request)\n }\n\n async completeAuth(providerId: string, request: CompleteAuthRequest): Promise<IntegrationConnection> {\n const provider = this.requireProvider(providerId)\n if (!provider.completeAuth) throw new IntegrationError(`Provider ${providerId} does not support auth completion.`, 'auth_not_supported')\n const connection = await provider.completeAuth(request)\n await this.store.put(connection)\n return connection\n }\n\n async upsertConnection(connection: IntegrationConnection): Promise<IntegrationConnection> {\n await this.store.put(connection)\n return connection\n }\n\n async issueCapability(request: IssueCapabilityRequest): Promise<IssuedIntegrationCapability> {\n const connection = await this.requireConnection(request.connectionId)\n this.assertConnectionActive(connection)\n assertScopes(connection, request.scopes)\n const now = this.now()\n const capability: IntegrationCapability = {\n id: `cap_${randomUUID()}`,\n subject: request.subject,\n connectionId: request.connectionId,\n scopes: unique(request.scopes),\n allowedActions: unique(request.allowedActions),\n issuedAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + request.ttlMs).toISOString(),\n metadata: request.metadata,\n }\n return { capability, token: signCapability(capability, this.capabilitySecret) }\n }\n\n verifyCapability(token: string): IntegrationCapability {\n const capability = verifyCapabilityToken(token, this.capabilitySecret)\n if (Date.parse(capability.expiresAt) <= this.now().getTime()) {\n throw new IntegrationError('Integration capability expired.', 'capability_expired')\n }\n return capability\n }\n\n async invokeWithCapability(token: string, request: InvokeWithCapabilityRequest): Promise<IntegrationActionResult> {\n const capability = this.verifyCapability(token)\n if (!capability.allowedActions.includes(request.action)) {\n throw new IntegrationError(`Capability does not allow action ${request.action}.`, 'action_denied')\n }\n const connection = await this.requireConnection(capability.connectionId)\n this.assertConnectionActive(connection)\n const provider = this.requireProvider(connection.providerId)\n const connector = await this.requireConnector(provider, connection.connectorId)\n const action = connector.actions.find((candidate) => candidate.id === request.action)\n if (!action) throw new IntegrationError(`Action ${request.action} is not defined by connector ${connector.id}.`, 'action_not_found')\n assertScopes(connection, action.requiredScopes)\n assertScopes({ ...connection, grantedScopes: capability.scopes }, action.requiredScopes)\n const fullRequest: IntegrationActionRequest = { ...request, connectionId: connection.id }\n const proceed = () => Promise.resolve(provider.invokeAction(connection, fullRequest))\n if (this.guard) {\n return this.guard.invokeAction({ connection, request: fullRequest, action }, proceed)\n }\n return proceed()\n }\n\n async subscribeTrigger(connectionId: string, trigger: string, targetUrl?: string): Promise<IntegrationTriggerSubscription> {\n const connection = await this.requireConnection(connectionId)\n this.assertConnectionActive(connection)\n const provider = this.requireProvider(connection.providerId)\n const connector = await this.requireConnector(provider, connection.connectorId)\n const spec = connector.triggers?.find((candidate) => candidate.id === trigger)\n if (!spec) throw new IntegrationError(`Trigger ${trigger} is not defined by connector ${connector.id}.`, 'action_not_found')\n assertScopes(connection, spec.requiredScopes)\n if (!provider.subscribeTrigger) {\n throw new IntegrationError(`Provider ${provider.id} does not support triggers.`, 'auth_not_supported')\n }\n return provider.subscribeTrigger(connection, trigger, targetUrl)\n }\n\n private requireProvider(providerId: string): IntegrationProvider {\n const provider = this.providers.get(providerId)\n if (!provider) throw new IntegrationError(`Provider ${providerId} not found.`, 'provider_not_found')\n return provider\n }\n\n private async requireConnector(provider: IntegrationProvider, connectorId: string): Promise<IntegrationConnector> {\n const connector = (await provider.listConnectors()).find((candidate) => candidate.id === connectorId)\n if (!connector) throw new IntegrationError(`Connector ${connectorId} not found.`, 'connector_not_found')\n return connector\n }\n\n private async requireConnection(connectionId: string): Promise<IntegrationConnection> {\n const connection = await this.store.get(connectionId)\n if (!connection) throw new IntegrationError(`Connection ${connectionId} not found.`, 'connection_not_found')\n return connection\n }\n\n private assertConnectionActive(connection: IntegrationConnection): void {\n if (connection.status !== 'active') {\n throw new IntegrationError(`Connection ${connection.id} is ${connection.status}.`, 'connection_not_active')\n }\n if (connection.expiresAt && Date.parse(connection.expiresAt) <= this.now().getTime()) {\n throw new IntegrationError(`Connection ${connection.id} is expired.`, 'connection_not_active')\n }\n }\n}\n\nexport function sanitizeConnection(connection: IntegrationConnection): Record<string, unknown> {\n return {\n id: connection.id,\n owner: connection.owner,\n providerId: connection.providerId,\n connectorId: connection.connectorId,\n status: connection.status,\n grantedScopes: connection.grantedScopes,\n account: connection.account,\n hasSecretRef: Boolean(connection.secretRef),\n createdAt: connection.createdAt,\n updatedAt: connection.updatedAt,\n expiresAt: connection.expiresAt,\n lastUsedAt: connection.lastUsedAt,\n }\n}\n\nexport function createMockIntegrationProvider(options: {\n id?: string\n connectors?: IntegrationConnector[]\n onInvoke?: (connection: IntegrationConnection, request: IntegrationActionRequest) => IntegrationActionResult | Promise<IntegrationActionResult>\n} = {}): IntegrationProvider {\n const providerId = options.id ?? 'mock'\n const connectors = options.connectors ?? [{\n id: 'gmail',\n providerId,\n title: 'Gmail',\n category: 'email',\n auth: 'oauth2',\n scopes: ['email.read', 'email.write'],\n actions: [\n { id: 'messages.search', title: 'Search messages', risk: 'read', requiredScopes: ['email.read'], dataClass: 'private' },\n { id: 'drafts.create', title: 'Create draft', risk: 'write', requiredScopes: ['email.write'], dataClass: 'private', approvalRequired: true },\n ],\n triggers: [\n { id: 'message.received', title: 'Message received', requiredScopes: ['email.read'], dataClass: 'private' },\n ],\n }]\n return {\n id: providerId,\n kind: 'custom',\n listConnectors: () => connectors,\n startAuth: (request) => ({\n providerId,\n connectorId: request.connectorId,\n authUrl: `https://auth.example.test/${request.connectorId}?state=${encodeURIComponent(request.state ?? 'state')}`,\n state: request.state ?? 'state',\n }),\n completeAuth: (request) => ({\n id: `conn_${request.connectorId}_${request.owner.id}`,\n owner: request.owner,\n providerId,\n connectorId: request.connectorId,\n status: 'active',\n grantedScopes: connectors.find((connector) => connector.id === request.connectorId)?.scopes ?? [],\n secretRef: { provider: providerId, id: `secret_${request.owner.id}` },\n createdAt: new Date(0).toISOString(),\n updatedAt: new Date(0).toISOString(),\n }),\n invokeAction: async (connection, request) => options.onInvoke?.(connection, request) ?? ({\n ok: true,\n action: request.action,\n output: { echo: request.input ?? null },\n }),\n subscribeTrigger: (connection, trigger, targetUrl) => ({\n id: `sub_${connection.id}_${trigger}`,\n connectionId: connection.id,\n trigger,\n targetUrl,\n status: 'active',\n createdAt: new Date(0).toISOString(),\n }),\n }\n}\n\nexport function createHttpIntegrationProvider(options: HttpIntegrationProviderOptions): IntegrationProvider {\n const fetcher = options.fetchImpl ?? fetch\n const baseUrl = options.baseUrl.replace(/\\/$/, '')\n return {\n id: options.id,\n kind: options.kind ?? 'custom',\n listConnectors: () => options.connectors,\n async startAuth(request) {\n const response = await postJson<StartAuthResult>(fetcher, `${baseUrl}/auth/start`, request, options.bearer)\n return response\n },\n async completeAuth(request) {\n const response = await postJson<IntegrationConnection>(fetcher, `${baseUrl}/auth/complete`, request, options.bearer)\n return response\n },\n async invokeAction(connection, request) {\n return postJson<IntegrationActionResult>(fetcher, `${baseUrl}/actions/invoke`, {\n connection,\n request,\n }, options.bearer)\n },\n async subscribeTrigger(connection, trigger, targetUrl) {\n return postJson<IntegrationTriggerSubscription>(fetcher, `${baseUrl}/triggers/subscribe`, {\n connection,\n trigger,\n targetUrl,\n }, options.bearer)\n },\n async unsubscribeTrigger(subscriptionId) {\n await postJson(fetcher, `${baseUrl}/triggers/unsubscribe`, { subscriptionId }, options.bearer)\n },\n async normalizeTriggerEvent(raw) {\n return postJson<IntegrationTriggerEvent>(fetcher, `${baseUrl}/triggers/normalize`, { raw }, options.bearer)\n },\n }\n}\n\nexport function signCapability(capability: IntegrationCapability, secret: string): string {\n const payload = base64UrlEncode(JSON.stringify(capability))\n const signature = hmac(payload, secret)\n return `${payload}.${signature}`\n}\n\nexport function verifyCapabilityToken(token: string, secret: string): IntegrationCapability {\n const [payload, signature] = token.split('.')\n if (!payload || !signature) throw new IntegrationError('Malformed integration capability.', 'capability_invalid')\n const expected = hmac(payload, secret)\n if (!constantTimeEqual(signature, expected)) throw new IntegrationError('Invalid integration capability signature.', 'capability_invalid')\n let parsed: IntegrationCapability\n try {\n parsed = JSON.parse(base64UrlDecode(payload)) as IntegrationCapability\n } catch {\n throw new IntegrationError('Invalid integration capability payload.', 'capability_invalid')\n }\n if (!parsed.id || !parsed.connectionId || !Array.isArray(parsed.scopes) || !Array.isArray(parsed.allowedActions)) {\n throw new IntegrationError('Invalid integration capability payload.', 'capability_invalid')\n }\n return parsed\n}\n\nasync function postJson<T = unknown>(\n fetcher: typeof fetch,\n url: string,\n body: unknown,\n bearer?: string,\n): Promise<T> {\n const response = await fetcher(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(bearer ? { Authorization: `Bearer ${bearer}` } : {}),\n },\n body: JSON.stringify(body),\n })\n if (!response.ok) throw new IntegrationError(`Integration provider returned HTTP ${response.status}.`, 'provider_not_found')\n return response.json() as Promise<T>\n}\n\nfunction assertScopes(connection: Pick<IntegrationConnection, 'grantedScopes'>, requiredScopes: string[]): void {\n const missing = requiredScopes.filter((scope) => !connection.grantedScopes.includes(scope))\n if (missing.length > 0) throw new IntegrationError(`Missing integration scopes: ${missing.join(', ')}`, 'scope_denied')\n}\n\nfunction hmac(payload: string, secret: string): string {\n return createHmac('sha256', secret).update(payload).digest('base64url')\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n const left = Buffer.from(a)\n const right = Buffer.from(b)\n return left.length === right.length && timingSafeEqual(left, right)\n}\n\nfunction base64UrlEncode(value: string): string {\n return Buffer.from(value, 'utf8').toString('base64url')\n}\n\nfunction base64UrlDecode(value: string): string {\n return Buffer.from(value, 'base64url').toString('utf8')\n}\n\nfunction unique<T>(values: T[]): T[] {\n return [...new Set(values)]\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,164 @@
1
+ # Provider Decision Matrix
2
+
3
+ Status date: 2026-05-04
4
+
5
+ `agent-integrations` should keep product code independent from Nango,
6
+ Pipedream, Zapier, Activepieces, Composio, executor-style services, and our own
7
+ first-party connectors. The strategic goal is not to outsource integrations
8
+ forever. The goal is to get broad connector coverage quickly while preserving a
9
+ clean path to bring important connectors in-house.
10
+
11
+ ## Default Strategy
12
+
13
+ Use a three-tier connector strategy:
14
+
15
+ | Tier | Connector type | Default implementation | Why |
16
+ | --- | --- | --- | --- |
17
+ | Tier 1 | Strategic, high-volume, high-trust connectors | First-party provider adapter | Lowest unit cost, best UX, strictest security, strongest product moat. |
18
+ | Tier 2 | Common but non-core connectors | Hosted gateway behind `createHttpIntegrationProvider` | Fast coverage without leaking vendor APIs into apps or sandboxes. |
19
+ | Tier 3 | Long-tail and experimental connectors | Gateway or generated adapter | Cheap validation before committing engineering ownership. |
20
+
21
+ The invariant: every connector, no matter who backs it, must expose the same
22
+ `IntegrationConnector`, `IntegrationConnection`, action, trigger, and capability
23
+ contracts.
24
+
25
+ ## Decision Matrix
26
+
27
+ Score each connector from 1 to 5. Higher total means stronger first-party
28
+ priority.
29
+
30
+ | Criterion | 1 | 3 | 5 |
31
+ | --- | --- | --- | --- |
32
+ | User demand | Rarely requested | Common in one vertical | Needed across many products/agents |
33
+ | Workflow criticality | Nice-to-have read path | Useful but recoverable | Blocks core product value |
34
+ | Data sensitivity | Public/low-risk | Private business data | Regulated, financial, legal, health, secrets |
35
+ | Write risk | Read-only | Reversible writes | Money movement, external comms, destructive writes |
36
+ | Volume/cost | Low call volume | Moderate calls | High volume where vendor margins matter |
37
+ | API stability | Messy/private/unstable | Usable but quirky | Stable official API + webhook model |
38
+ | Auth complexity | Simple API key | OAuth with refresh | Multi-tenant OAuth, domain-wide delegation, scoped installs |
39
+ | Product differentiation | Commodity | Some UX benefit | Better in-house UX is a moat |
40
+ | Vendor coverage quality | Vendor handles it well | Vendor has gaps | Vendor coverage is weak or too generic |
41
+ | Compliance/control need | Low | Moderate | Requires internal audit, retention, approval, residency controls |
42
+
43
+ Decision:
44
+
45
+ - `38+`: build or migrate first-party.
46
+ - `26-37`: start behind a gateway, schedule migration once usage proves out.
47
+ - `15-25`: keep gateway-backed unless a product launch depends on it.
48
+ - `<15`: do not build first-party yet; use long-tail gateway or defer.
49
+
50
+ ## Practical Launch Order
51
+
52
+ Build first-party adapters first for connectors that are both broadly useful
53
+ and expensive/risky to delegate:
54
+
55
+ 1. Gmail / Google Workspace mail
56
+ 2. Google Calendar
57
+ 3. Google Drive / Docs
58
+ 4. Slack
59
+ 5. Microsoft 365 mail/calendar/files
60
+ 6. HubSpot
61
+ 7. Salesforce
62
+ 8. Notion
63
+ 9. GitHub
64
+ 10. Webhooks / generic HTTP actions
65
+
66
+ Use gateway-backed coverage for the next large tranche:
67
+
68
+ - marketing and ad platforms
69
+ - analytics tools
70
+ - project-management tools
71
+ - form tools
72
+ - storage services
73
+ - social networks
74
+ - long-tail CRM/support/helpdesk systems
75
+ - vendor-specific workflow triggers
76
+
77
+ This gives Agent Builder useful breadth immediately without forcing us to own
78
+ hundreds of OAuth apps, refresh-token edge cases, webhook subscription models,
79
+ rate-limit policies, and provider-specific APIs on day one.
80
+
81
+ ## When To Roll Our Own
82
+
83
+ Move a connector first-party when one of these is true:
84
+
85
+ - It appears in multiple product launch paths.
86
+ - It is needed by generated sandbox apps, not just back-office automation.
87
+ - Users expect a polished native UX for connection, approval, and failure
88
+ recovery.
89
+ - The action volume makes gateway pricing materially worse than internal
90
+ maintenance.
91
+ - The connector touches high-trust data or irreversible writes.
92
+ - We need better replay, idempotency, audit, or conflict handling than a vendor
93
+ exposes.
94
+ - The vendor abstraction hides too much provider-specific capability.
95
+ - We need to publish open-source agent apps that still reliably route through
96
+ our platform keys, sandboxes, and audit controls.
97
+
98
+ ## When To Use A Gateway
99
+
100
+ Use a gateway when speed and breadth matter more than deep ownership:
101
+
102
+ - The connector is long-tail or unproven.
103
+ - The integration is read-only or low write-risk.
104
+ - The provider API is annoying but not strategic.
105
+ - The product team needs a demo or beta connector this week.
106
+ - The connector is mainly a trigger source, not a rich bidirectional app API.
107
+ - The connector is expected to churn while the product shape is still moving.
108
+
109
+ Gateways are acceptable only behind `IntegrationProvider`. Product code must
110
+ not import vendor SDKs or depend on vendor-specific connection records.
111
+
112
+ ## First-Party Adapter Requirements
113
+
114
+ A first-party connector is not just a thin API wrapper. It must ship with:
115
+
116
+ - normalized connector manifest
117
+ - OAuth/API-key start and complete flows
118
+ - encrypted token/secret reference handling
119
+ - refresh-token and expiry handling
120
+ - action schemas and output schemas
121
+ - trigger subscription and normalization if supported
122
+ - provider rate-limit and retry policy
123
+ - typed errors that agents can recover from
124
+ - `IntegrationActionGuard` compatibility for idempotency, audit, approval, and
125
+ conflict handling
126
+ - tests that cover auth, scope denial, capability denial, provider errors, and
127
+ write approval paths
128
+
129
+ ## Guard Policy
130
+
131
+ Every production hub should install an `IntegrationActionGuard`.
132
+
133
+ The guard is where products enforce cross-provider discipline:
134
+
135
+ - idempotency key replay
136
+ - same-key-different-args rejection
137
+ - human approval before risky writes
138
+ - per-tenant and per-connection rate limits
139
+ - audit logging
140
+ - conflict detection
141
+ - dry-run handling
142
+ - structured alternatives when an action cannot safely run
143
+
144
+ Providers should execute actions. Guards should decide whether an action may
145
+ run, whether it has already run, and how to record it.
146
+
147
+ ## The Cheap Path
148
+
149
+ The cheapest credible path is:
150
+
151
+ 1. Keep `agent-integrations` as the stable SDK contract.
152
+ 2. Use a hosted gateway for broad catalog coverage while demand is uncertain.
153
+ 3. Route every sandbox/app action through short-lived capabilities and a guard.
154
+ 4. Instrument connector usage, error rate, action volume, and approval friction.
155
+ 5. Promote the highest-scoring connectors to first-party adapters.
156
+ 6. Keep long-tail connectors gateway-backed until usage justifies ownership.
157
+
158
+ This avoids two bad extremes:
159
+
160
+ - locking the product into a vendor abstraction that becomes expensive and
161
+ limiting;
162
+ - spending months cloning hundreds of integrations before knowing which ones
163
+ matter.
164
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-integrations",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Vendor-neutral integration contracts and runtime helpers for sandbox and agent apps.",
5
5
  "homepage": "https://github.com/tangle-network/agent-integrations#readme",
6
6
  "repository": {
@@ -28,6 +28,14 @@
28
28
  "publishConfig": {
29
29
  "access": "public"
30
30
  },
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "dev": "tsup --watch",
34
+ "prepare": "tsup",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "typecheck": "tsc --noEmit"
38
+ },
31
39
  "devDependencies": {
32
40
  "@types/node": "^25.6.0",
33
41
  "tsup": "^8.0.0",
@@ -38,11 +46,5 @@
38
46
  "node": ">=20"
39
47
  },
40
48
  "license": "MIT",
41
- "scripts": {
42
- "build": "tsup",
43
- "dev": "tsup --watch",
44
- "test": "vitest run",
45
- "test:watch": "vitest",
46
- "typecheck": "tsc --noEmit"
47
- }
48
- }
49
+ "packageManager": "pnpm@10.28.0"
50
+ }