@tangle-network/agent-integrations 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # Agent Integrations
2
+
3
+ `@tangle-network/agent-integrations` is a vendor-neutral integration layer for
4
+ apps, sandboxes, and agents that need user-authorized connections such as email,
5
+ calendar, Slack, CRM, storage, webhooks, and workflow triggers.
6
+
7
+ The package does not pick a single integration vendor. Nango, Pipedream,
8
+ Zapier-style platforms, Activepieces, executor services, and first-party
9
+ connectors should all sit behind the same provider interface.
10
+
11
+ ## Mental Model
12
+
13
+ ```txt
14
+ Connector catalog -> User connection -> Scoped capability -> Action or trigger
15
+ ```
16
+
17
+ - **Connectors** describe what can be connected: Gmail, Google Calendar, Slack,
18
+ HubSpot, webhooks, internal tools.
19
+ - **Connections** are user/team-owned grants. They carry secret references, not
20
+ raw credentials.
21
+ - **Capabilities** are short-lived, sandbox-safe tokens that authorize a subset
22
+ of actions on a connection.
23
+ - **Actions** are read/write/destructive operations.
24
+ - **Triggers** normalize inbound events from providers into one event shape.
25
+
26
+ ## Why This Exists
27
+
28
+ Agent Builder and sandbox apps need to support prompts like:
29
+
30
+ ```txt
31
+ At Gmail, build me an app that summarizes unread support emails and drafts replies.
32
+ ```
33
+
34
+ The generated app should be able to request Gmail access, instantiate inside the
35
+ user's sandbox, and let the agent read/write through a scoped integration
36
+ capability. The sandbox should never receive reusable provider secrets.
37
+
38
+ ## Core Usage
39
+
40
+ ```ts
41
+ import {
42
+ InMemoryConnectionStore,
43
+ IntegrationHub,
44
+ createMockIntegrationProvider,
45
+ } from '@tangle-network/agent-integrations'
46
+
47
+ const hub = new IntegrationHub({
48
+ providers: [createMockIntegrationProvider()],
49
+ store: new InMemoryConnectionStore(),
50
+ capabilitySecret: 'dev-secret',
51
+ })
52
+
53
+ const connection = await hub.upsertConnection({
54
+ id: 'conn_1',
55
+ owner: { type: 'user', id: 'user_1' },
56
+ providerId: 'mock',
57
+ connectorId: 'gmail',
58
+ status: 'active',
59
+ grantedScopes: ['email.read'],
60
+ createdAt: new Date().toISOString(),
61
+ updatedAt: new Date().toISOString(),
62
+ })
63
+
64
+ const capability = await hub.issueCapability({
65
+ subject: { type: 'sandbox', id: 'sandbox_1' },
66
+ connectionId: connection.id,
67
+ scopes: ['email.read'],
68
+ allowedActions: ['messages.search'],
69
+ ttlMs: 60_000,
70
+ })
71
+
72
+ const result = await hub.invokeWithCapability(capability.token, {
73
+ action: 'messages.search',
74
+ input: { q: 'is:unread' },
75
+ })
76
+ ```
77
+
78
+ ## Provider Boundary
79
+
80
+ Providers implement OAuth, action execution, and optional triggers. Product code
81
+ should depend on `IntegrationHub`, not on a vendor SDK.
82
+
83
+ Provider adapters are expected to store raw credentials in their own secure
84
+ vault or return secret references. Connection records should remain safe to log
85
+ after sanitization.
86
+
87
+ For a hosted integration gateway, use the generic HTTP adapter:
88
+
89
+ ```ts
90
+ import { createHttpIntegrationProvider } from '@tangle-network/agent-integrations'
91
+
92
+ const provider = createHttpIntegrationProvider({
93
+ id: 'gateway',
94
+ kind: 'pipedream',
95
+ baseUrl: 'https://integrations.example',
96
+ bearer: process.env.INTEGRATION_GATEWAY_TOKEN,
97
+ connectors: [/* normalized connector catalog */],
98
+ })
99
+ ```
100
+
101
+ The HTTP adapter keeps product code stable while the backing provider can be
102
+ Nango, Pipedream, Activepieces, a Zapier-style service, or an internal gateway.
103
+
104
+ ## Security Defaults
105
+
106
+ - Capabilities expire.
107
+ - Capability tokens contain no provider credential.
108
+ - Secret refs are redacted from public telemetry.
109
+ - Write/destructive actions can be policy-gated.
110
+ - Action invocation checks connection ownership, status, scopes, allowed
111
+ actions, and expiration.
@@ -0,0 +1,218 @@
1
+ type IntegrationProviderKind = 'first_party' | 'nango' | 'pipedream' | 'zapier' | 'activepieces' | 'executor' | 'custom';
2
+ type IntegrationConnectorCategory = 'email' | 'calendar' | 'chat' | 'crm' | 'storage' | 'docs' | 'database' | 'webhook' | 'workflow' | 'internal' | 'other';
3
+ type IntegrationActionRisk = 'read' | 'write' | 'destructive';
4
+ type IntegrationDataClass = 'public' | 'internal' | 'private' | 'sensitive' | 'secret';
5
+ interface IntegrationActor {
6
+ type: 'user' | 'team' | 'agent' | 'sandbox' | 'system';
7
+ id: string;
8
+ }
9
+ interface IntegrationConnectorAction {
10
+ id: string;
11
+ title: string;
12
+ risk: IntegrationActionRisk;
13
+ requiredScopes: string[];
14
+ dataClass: IntegrationDataClass;
15
+ description?: string;
16
+ approvalRequired?: boolean;
17
+ inputSchema?: unknown;
18
+ outputSchema?: unknown;
19
+ }
20
+ interface IntegrationConnectorTrigger {
21
+ id: string;
22
+ title: string;
23
+ requiredScopes: string[];
24
+ dataClass: IntegrationDataClass;
25
+ description?: string;
26
+ payloadSchema?: unknown;
27
+ }
28
+ interface IntegrationConnector {
29
+ id: string;
30
+ providerId: string;
31
+ title: string;
32
+ category: IntegrationConnectorCategory;
33
+ auth: 'oauth2' | 'api_key' | 'none' | 'custom';
34
+ scopes: string[];
35
+ actions: IntegrationConnectorAction[];
36
+ triggers?: IntegrationConnectorTrigger[];
37
+ metadata?: Record<string, unknown>;
38
+ }
39
+ interface SecretRef {
40
+ provider: string;
41
+ id: string;
42
+ label?: string;
43
+ }
44
+ interface IntegrationConnection {
45
+ id: string;
46
+ owner: IntegrationActor;
47
+ providerId: string;
48
+ connectorId: string;
49
+ status: 'pending' | 'active' | 'expired' | 'revoked' | 'error';
50
+ grantedScopes: string[];
51
+ account?: {
52
+ id?: string;
53
+ email?: string;
54
+ displayName?: string;
55
+ };
56
+ secretRef?: SecretRef;
57
+ createdAt: string;
58
+ updatedAt: string;
59
+ expiresAt?: string;
60
+ lastUsedAt?: string;
61
+ metadata?: Record<string, unknown>;
62
+ }
63
+ interface StartAuthRequest {
64
+ connectorId: string;
65
+ owner: IntegrationActor;
66
+ requestedScopes: string[];
67
+ redirectUri: string;
68
+ state?: string;
69
+ metadata?: Record<string, unknown>;
70
+ }
71
+ interface StartAuthResult {
72
+ providerId: string;
73
+ connectorId: string;
74
+ authUrl: string;
75
+ state: string;
76
+ expiresAt?: string;
77
+ metadata?: Record<string, unknown>;
78
+ }
79
+ interface CompleteAuthRequest {
80
+ connectorId: string;
81
+ owner: IntegrationActor;
82
+ code?: string;
83
+ state: string;
84
+ redirectUri: string;
85
+ metadata?: Record<string, unknown>;
86
+ }
87
+ interface IntegrationActionRequest {
88
+ connectionId: string;
89
+ action: string;
90
+ input?: unknown;
91
+ idempotencyKey?: string;
92
+ dryRun?: boolean;
93
+ metadata?: Record<string, unknown>;
94
+ }
95
+ interface IntegrationActionResult<T = unknown> {
96
+ ok: boolean;
97
+ action: string;
98
+ output?: T;
99
+ externalId?: string;
100
+ warnings?: string[];
101
+ metadata?: Record<string, unknown>;
102
+ }
103
+ interface IntegrationTriggerSubscription {
104
+ id: string;
105
+ connectionId: string;
106
+ trigger: string;
107
+ targetUrl?: string;
108
+ status: 'active' | 'paused' | 'error';
109
+ createdAt: string;
110
+ metadata?: Record<string, unknown>;
111
+ }
112
+ interface IntegrationTriggerEvent<T = unknown> {
113
+ id: string;
114
+ providerId: string;
115
+ connectorId: string;
116
+ connectionId: string;
117
+ trigger: string;
118
+ occurredAt: string;
119
+ payload: T;
120
+ metadata?: Record<string, unknown>;
121
+ }
122
+ interface IntegrationProvider {
123
+ id: string;
124
+ kind: IntegrationProviderKind;
125
+ listConnectors(): Promise<IntegrationConnector[]> | IntegrationConnector[];
126
+ startAuth?(request: StartAuthRequest): Promise<StartAuthResult> | StartAuthResult;
127
+ completeAuth?(request: CompleteAuthRequest): Promise<IntegrationConnection> | IntegrationConnection;
128
+ invokeAction(connection: IntegrationConnection, request: IntegrationActionRequest): Promise<IntegrationActionResult> | IntegrationActionResult;
129
+ subscribeTrigger?(connection: IntegrationConnection, trigger: string, targetUrl?: string): Promise<IntegrationTriggerSubscription> | IntegrationTriggerSubscription;
130
+ unsubscribeTrigger?(subscriptionId: string): Promise<void> | void;
131
+ normalizeTriggerEvent?(raw: unknown): Promise<IntegrationTriggerEvent> | IntegrationTriggerEvent;
132
+ }
133
+ interface IntegrationConnectionStore {
134
+ get(connectionId: string): Promise<IntegrationConnection | undefined> | IntegrationConnection | undefined;
135
+ put(connection: IntegrationConnection): Promise<void> | void;
136
+ listByOwner(owner: IntegrationActor): Promise<IntegrationConnection[]> | IntegrationConnection[];
137
+ delete?(connectionId: string): Promise<void> | void;
138
+ }
139
+ interface IssueCapabilityRequest {
140
+ subject: IntegrationActor;
141
+ connectionId: string;
142
+ scopes: string[];
143
+ allowedActions: string[];
144
+ ttlMs: number;
145
+ metadata?: Record<string, unknown>;
146
+ }
147
+ interface IntegrationCapability {
148
+ id: string;
149
+ subject: IntegrationActor;
150
+ connectionId: string;
151
+ scopes: string[];
152
+ allowedActions: string[];
153
+ issuedAt: string;
154
+ expiresAt: string;
155
+ metadata?: Record<string, unknown>;
156
+ }
157
+ interface IssuedIntegrationCapability {
158
+ capability: IntegrationCapability;
159
+ token: string;
160
+ }
161
+ interface IntegrationHubOptions {
162
+ providers: IntegrationProvider[];
163
+ store: IntegrationConnectionStore;
164
+ capabilitySecret: string;
165
+ now?: () => Date;
166
+ }
167
+ interface HttpIntegrationProviderOptions {
168
+ id: string;
169
+ kind?: IntegrationProviderKind;
170
+ connectors: IntegrationConnector[];
171
+ baseUrl: string;
172
+ bearer?: string;
173
+ fetchImpl?: typeof fetch;
174
+ }
175
+ interface InvokeWithCapabilityRequest extends Omit<IntegrationActionRequest, 'connectionId'> {
176
+ connectionId?: never;
177
+ }
178
+ declare class IntegrationError extends Error {
179
+ readonly code: 'provider_not_found' | 'connector_not_found' | 'connection_not_found' | 'connection_not_active' | 'auth_not_supported' | 'capability_invalid' | 'capability_expired' | 'scope_denied' | 'action_denied' | 'action_not_found';
180
+ constructor(message: string, code: 'provider_not_found' | 'connector_not_found' | 'connection_not_found' | 'connection_not_active' | 'auth_not_supported' | 'capability_invalid' | 'capability_expired' | 'scope_denied' | 'action_denied' | 'action_not_found');
181
+ }
182
+ declare class InMemoryConnectionStore implements IntegrationConnectionStore {
183
+ private readonly connections;
184
+ get(connectionId: string): IntegrationConnection | undefined;
185
+ put(connection: IntegrationConnection): void;
186
+ listByOwner(owner: IntegrationActor): IntegrationConnection[];
187
+ delete(connectionId: string): void;
188
+ }
189
+ declare class IntegrationHub {
190
+ private readonly providers;
191
+ private readonly store;
192
+ private readonly capabilitySecret;
193
+ private readonly now;
194
+ constructor(options: IntegrationHubOptions);
195
+ listConnectors(): Promise<IntegrationConnector[]>;
196
+ startAuth(providerId: string, request: StartAuthRequest): Promise<StartAuthResult>;
197
+ completeAuth(providerId: string, request: CompleteAuthRequest): Promise<IntegrationConnection>;
198
+ upsertConnection(connection: IntegrationConnection): Promise<IntegrationConnection>;
199
+ issueCapability(request: IssueCapabilityRequest): Promise<IssuedIntegrationCapability>;
200
+ verifyCapability(token: string): IntegrationCapability;
201
+ invokeWithCapability(token: string, request: InvokeWithCapabilityRequest): Promise<IntegrationActionResult>;
202
+ subscribeTrigger(connectionId: string, trigger: string, targetUrl?: string): Promise<IntegrationTriggerSubscription>;
203
+ private requireProvider;
204
+ private requireConnector;
205
+ private requireConnection;
206
+ private assertConnectionActive;
207
+ }
208
+ declare function sanitizeConnection(connection: IntegrationConnection): Record<string, unknown>;
209
+ declare function createMockIntegrationProvider(options?: {
210
+ id?: string;
211
+ connectors?: IntegrationConnector[];
212
+ onInvoke?: (connection: IntegrationConnection, request: IntegrationActionRequest) => IntegrationActionResult | Promise<IntegrationActionResult>;
213
+ }): IntegrationProvider;
214
+ declare function createHttpIntegrationProvider(options: HttpIntegrationProviderOptions): IntegrationProvider;
215
+ declare function signCapability(capability: IntegrationCapability, secret: string): string;
216
+ declare function verifyCapabilityToken(token: string, secret: string): IntegrationCapability;
217
+
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 };
package/dist/index.js ADDED
@@ -0,0 +1,308 @@
1
+ // src/index.ts
2
+ import { createHmac, randomUUID, timingSafeEqual } from "crypto";
3
+ var IntegrationError = class extends Error {
4
+ constructor(message, code) {
5
+ super(message);
6
+ this.code = code;
7
+ this.name = "IntegrationError";
8
+ }
9
+ code;
10
+ };
11
+ var InMemoryConnectionStore = class {
12
+ connections = /* @__PURE__ */ new Map();
13
+ get(connectionId) {
14
+ return this.connections.get(connectionId);
15
+ }
16
+ put(connection) {
17
+ this.connections.set(connection.id, connection);
18
+ }
19
+ listByOwner(owner) {
20
+ return [...this.connections.values()].filter(
21
+ (connection) => connection.owner.type === owner.type && connection.owner.id === owner.id
22
+ );
23
+ }
24
+ delete(connectionId) {
25
+ this.connections.delete(connectionId);
26
+ }
27
+ };
28
+ var IntegrationHub = class {
29
+ providers = /* @__PURE__ */ new Map();
30
+ store;
31
+ capabilitySecret;
32
+ now;
33
+ constructor(options) {
34
+ if (!options.capabilitySecret) {
35
+ throw new IntegrationError("capabilitySecret is required.", "capability_invalid");
36
+ }
37
+ for (const provider of options.providers) this.providers.set(provider.id, provider);
38
+ this.store = options.store;
39
+ this.capabilitySecret = options.capabilitySecret;
40
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
41
+ }
42
+ async listConnectors() {
43
+ const catalogs = await Promise.all([...this.providers.values()].map((provider) => provider.listConnectors()));
44
+ return catalogs.flat();
45
+ }
46
+ async startAuth(providerId, request) {
47
+ const provider = this.requireProvider(providerId);
48
+ if (!provider.startAuth) throw new IntegrationError(`Provider ${providerId} does not support auth start.`, "auth_not_supported");
49
+ await this.requireConnector(provider, request.connectorId);
50
+ return provider.startAuth(request);
51
+ }
52
+ async completeAuth(providerId, request) {
53
+ const provider = this.requireProvider(providerId);
54
+ if (!provider.completeAuth) throw new IntegrationError(`Provider ${providerId} does not support auth completion.`, "auth_not_supported");
55
+ const connection = await provider.completeAuth(request);
56
+ await this.store.put(connection);
57
+ return connection;
58
+ }
59
+ async upsertConnection(connection) {
60
+ await this.store.put(connection);
61
+ return connection;
62
+ }
63
+ async issueCapability(request) {
64
+ const connection = await this.requireConnection(request.connectionId);
65
+ this.assertConnectionActive(connection);
66
+ assertScopes(connection, request.scopes);
67
+ const now = this.now();
68
+ const capability = {
69
+ id: `cap_${randomUUID()}`,
70
+ subject: request.subject,
71
+ connectionId: request.connectionId,
72
+ scopes: unique(request.scopes),
73
+ allowedActions: unique(request.allowedActions),
74
+ issuedAt: now.toISOString(),
75
+ expiresAt: new Date(now.getTime() + request.ttlMs).toISOString(),
76
+ metadata: request.metadata
77
+ };
78
+ return { capability, token: signCapability(capability, this.capabilitySecret) };
79
+ }
80
+ verifyCapability(token) {
81
+ const capability = verifyCapabilityToken(token, this.capabilitySecret);
82
+ if (Date.parse(capability.expiresAt) <= this.now().getTime()) {
83
+ throw new IntegrationError("Integration capability expired.", "capability_expired");
84
+ }
85
+ return capability;
86
+ }
87
+ async invokeWithCapability(token, request) {
88
+ const capability = this.verifyCapability(token);
89
+ if (!capability.allowedActions.includes(request.action)) {
90
+ throw new IntegrationError(`Capability does not allow action ${request.action}.`, "action_denied");
91
+ }
92
+ const connection = await this.requireConnection(capability.connectionId);
93
+ this.assertConnectionActive(connection);
94
+ const provider = this.requireProvider(connection.providerId);
95
+ const connector = await this.requireConnector(provider, connection.connectorId);
96
+ const action = connector.actions.find((candidate) => candidate.id === request.action);
97
+ if (!action) throw new IntegrationError(`Action ${request.action} is not defined by connector ${connector.id}.`, "action_not_found");
98
+ assertScopes(connection, action.requiredScopes);
99
+ assertScopes({ ...connection, grantedScopes: capability.scopes }, action.requiredScopes);
100
+ return provider.invokeAction(connection, { ...request, connectionId: connection.id });
101
+ }
102
+ async subscribeTrigger(connectionId, trigger, targetUrl) {
103
+ const connection = await this.requireConnection(connectionId);
104
+ this.assertConnectionActive(connection);
105
+ const provider = this.requireProvider(connection.providerId);
106
+ const connector = await this.requireConnector(provider, connection.connectorId);
107
+ const spec = connector.triggers?.find((candidate) => candidate.id === trigger);
108
+ if (!spec) throw new IntegrationError(`Trigger ${trigger} is not defined by connector ${connector.id}.`, "action_not_found");
109
+ assertScopes(connection, spec.requiredScopes);
110
+ if (!provider.subscribeTrigger) {
111
+ throw new IntegrationError(`Provider ${provider.id} does not support triggers.`, "auth_not_supported");
112
+ }
113
+ return provider.subscribeTrigger(connection, trigger, targetUrl);
114
+ }
115
+ requireProvider(providerId) {
116
+ const provider = this.providers.get(providerId);
117
+ if (!provider) throw new IntegrationError(`Provider ${providerId} not found.`, "provider_not_found");
118
+ return provider;
119
+ }
120
+ async requireConnector(provider, connectorId) {
121
+ const connector = (await provider.listConnectors()).find((candidate) => candidate.id === connectorId);
122
+ if (!connector) throw new IntegrationError(`Connector ${connectorId} not found.`, "connector_not_found");
123
+ return connector;
124
+ }
125
+ async requireConnection(connectionId) {
126
+ const connection = await this.store.get(connectionId);
127
+ if (!connection) throw new IntegrationError(`Connection ${connectionId} not found.`, "connection_not_found");
128
+ return connection;
129
+ }
130
+ assertConnectionActive(connection) {
131
+ if (connection.status !== "active") {
132
+ throw new IntegrationError(`Connection ${connection.id} is ${connection.status}.`, "connection_not_active");
133
+ }
134
+ if (connection.expiresAt && Date.parse(connection.expiresAt) <= this.now().getTime()) {
135
+ throw new IntegrationError(`Connection ${connection.id} is expired.`, "connection_not_active");
136
+ }
137
+ }
138
+ };
139
+ function sanitizeConnection(connection) {
140
+ return {
141
+ id: connection.id,
142
+ owner: connection.owner,
143
+ providerId: connection.providerId,
144
+ connectorId: connection.connectorId,
145
+ status: connection.status,
146
+ grantedScopes: connection.grantedScopes,
147
+ account: connection.account,
148
+ hasSecretRef: Boolean(connection.secretRef),
149
+ createdAt: connection.createdAt,
150
+ updatedAt: connection.updatedAt,
151
+ expiresAt: connection.expiresAt,
152
+ lastUsedAt: connection.lastUsedAt
153
+ };
154
+ }
155
+ function createMockIntegrationProvider(options = {}) {
156
+ const providerId = options.id ?? "mock";
157
+ const connectors = options.connectors ?? [{
158
+ id: "gmail",
159
+ providerId,
160
+ title: "Gmail",
161
+ category: "email",
162
+ auth: "oauth2",
163
+ scopes: ["email.read", "email.write"],
164
+ actions: [
165
+ { id: "messages.search", title: "Search messages", risk: "read", requiredScopes: ["email.read"], dataClass: "private" },
166
+ { id: "drafts.create", title: "Create draft", risk: "write", requiredScopes: ["email.write"], dataClass: "private", approvalRequired: true }
167
+ ],
168
+ triggers: [
169
+ { id: "message.received", title: "Message received", requiredScopes: ["email.read"], dataClass: "private" }
170
+ ]
171
+ }];
172
+ return {
173
+ id: providerId,
174
+ kind: "custom",
175
+ listConnectors: () => connectors,
176
+ startAuth: (request) => ({
177
+ providerId,
178
+ connectorId: request.connectorId,
179
+ authUrl: `https://auth.example.test/${request.connectorId}?state=${encodeURIComponent(request.state ?? "state")}`,
180
+ state: request.state ?? "state"
181
+ }),
182
+ completeAuth: (request) => ({
183
+ id: `conn_${request.connectorId}_${request.owner.id}`,
184
+ owner: request.owner,
185
+ providerId,
186
+ connectorId: request.connectorId,
187
+ status: "active",
188
+ grantedScopes: connectors.find((connector) => connector.id === request.connectorId)?.scopes ?? [],
189
+ secretRef: { provider: providerId, id: `secret_${request.owner.id}` },
190
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
191
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
192
+ }),
193
+ invokeAction: async (connection, request) => options.onInvoke?.(connection, request) ?? {
194
+ ok: true,
195
+ action: request.action,
196
+ output: { echo: request.input ?? null }
197
+ },
198
+ subscribeTrigger: (connection, trigger, targetUrl) => ({
199
+ id: `sub_${connection.id}_${trigger}`,
200
+ connectionId: connection.id,
201
+ trigger,
202
+ targetUrl,
203
+ status: "active",
204
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString()
205
+ })
206
+ };
207
+ }
208
+ function createHttpIntegrationProvider(options) {
209
+ const fetcher = options.fetchImpl ?? fetch;
210
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
211
+ return {
212
+ id: options.id,
213
+ kind: options.kind ?? "custom",
214
+ listConnectors: () => options.connectors,
215
+ async startAuth(request) {
216
+ const response = await postJson(fetcher, `${baseUrl}/auth/start`, request, options.bearer);
217
+ return response;
218
+ },
219
+ async completeAuth(request) {
220
+ const response = await postJson(fetcher, `${baseUrl}/auth/complete`, request, options.bearer);
221
+ return response;
222
+ },
223
+ async invokeAction(connection, request) {
224
+ return postJson(fetcher, `${baseUrl}/actions/invoke`, {
225
+ connection,
226
+ request
227
+ }, options.bearer);
228
+ },
229
+ async subscribeTrigger(connection, trigger, targetUrl) {
230
+ return postJson(fetcher, `${baseUrl}/triggers/subscribe`, {
231
+ connection,
232
+ trigger,
233
+ targetUrl
234
+ }, options.bearer);
235
+ },
236
+ async unsubscribeTrigger(subscriptionId) {
237
+ await postJson(fetcher, `${baseUrl}/triggers/unsubscribe`, { subscriptionId }, options.bearer);
238
+ },
239
+ async normalizeTriggerEvent(raw) {
240
+ return postJson(fetcher, `${baseUrl}/triggers/normalize`, { raw }, options.bearer);
241
+ }
242
+ };
243
+ }
244
+ function signCapability(capability, secret) {
245
+ const payload = base64UrlEncode(JSON.stringify(capability));
246
+ const signature = hmac(payload, secret);
247
+ return `${payload}.${signature}`;
248
+ }
249
+ function verifyCapabilityToken(token, secret) {
250
+ const [payload, signature] = token.split(".");
251
+ if (!payload || !signature) throw new IntegrationError("Malformed integration capability.", "capability_invalid");
252
+ const expected = hmac(payload, secret);
253
+ if (!constantTimeEqual(signature, expected)) throw new IntegrationError("Invalid integration capability signature.", "capability_invalid");
254
+ let parsed;
255
+ try {
256
+ parsed = JSON.parse(base64UrlDecode(payload));
257
+ } catch {
258
+ throw new IntegrationError("Invalid integration capability payload.", "capability_invalid");
259
+ }
260
+ if (!parsed.id || !parsed.connectionId || !Array.isArray(parsed.scopes) || !Array.isArray(parsed.allowedActions)) {
261
+ throw new IntegrationError("Invalid integration capability payload.", "capability_invalid");
262
+ }
263
+ return parsed;
264
+ }
265
+ async function postJson(fetcher, url, body, bearer) {
266
+ const response = await fetcher(url, {
267
+ method: "POST",
268
+ headers: {
269
+ "Content-Type": "application/json",
270
+ ...bearer ? { Authorization: `Bearer ${bearer}` } : {}
271
+ },
272
+ body: JSON.stringify(body)
273
+ });
274
+ if (!response.ok) throw new IntegrationError(`Integration provider returned HTTP ${response.status}.`, "provider_not_found");
275
+ return response.json();
276
+ }
277
+ function assertScopes(connection, requiredScopes) {
278
+ const missing = requiredScopes.filter((scope) => !connection.grantedScopes.includes(scope));
279
+ if (missing.length > 0) throw new IntegrationError(`Missing integration scopes: ${missing.join(", ")}`, "scope_denied");
280
+ }
281
+ function hmac(payload, secret) {
282
+ return createHmac("sha256", secret).update(payload).digest("base64url");
283
+ }
284
+ function constantTimeEqual(a, b) {
285
+ const left = Buffer.from(a);
286
+ const right = Buffer.from(b);
287
+ return left.length === right.length && timingSafeEqual(left, right);
288
+ }
289
+ function base64UrlEncode(value) {
290
+ return Buffer.from(value, "utf8").toString("base64url");
291
+ }
292
+ function base64UrlDecode(value) {
293
+ return Buffer.from(value, "base64url").toString("utf8");
294
+ }
295
+ function unique(values) {
296
+ return [...new Set(values)];
297
+ }
298
+ export {
299
+ InMemoryConnectionStore,
300
+ IntegrationError,
301
+ IntegrationHub,
302
+ createHttpIntegrationProvider,
303
+ createMockIntegrationProvider,
304
+ sanitizeConnection,
305
+ signCapability,
306
+ verifyCapabilityToken
307
+ };
308
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":[]}
@@ -0,0 +1,46 @@
1
+ # Agent Integrations Architecture
2
+
3
+ ## Non-Goals
4
+
5
+ - Do not become a full Zapier clone in the SDK.
6
+ - Do not require one OAuth broker or workflow vendor.
7
+ - Do not expose provider tokens to sandboxes or agents.
8
+ - Do not make product repos hand-roll Gmail/Slack/calendar semantics.
9
+
10
+ ## Package Responsibilities
11
+
12
+ `agent-integrations` owns:
13
+
14
+ - stable connector, connection, action, trigger, and capability contracts
15
+ - provider adapter interface
16
+ - connection store interface
17
+ - sandbox-safe capability token minting and verification
18
+ - invocation policy enforcement
19
+ - event normalization
20
+ - redaction helpers
21
+
22
+ Provider-specific services own:
23
+
24
+ - OAuth client registration
25
+ - provider token vaulting
26
+ - refresh-token rotation
27
+ - webhook subscription lifecycle
28
+ - vendor-specific action mapping
29
+ - provider rate limits and retries
30
+
31
+ Product apps own:
32
+
33
+ - UI for connect/install/approve flows
34
+ - tenant/user ownership policy
35
+ - which connectors are enabled
36
+ - human approval before sensitive writes
37
+ - audit log persistence
38
+
39
+ ## Launch Bar
40
+
41
+ - A generated app can declare required connectors.
42
+ - Builder can ask the user to connect missing accounts.
43
+ - A sandbox can receive a short-lived capability for one user-owned connection.
44
+ - Agents can invoke only actions allowed by that capability.
45
+ - Triggers can wake or enqueue sandbox workflows without exposing credentials.
46
+ - Audit logs can show what happened without leaking secrets.
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@tangle-network/agent-integrations",
3
+ "version": "0.1.0",
4
+ "description": "Vendor-neutral integration contracts and runtime helpers for sandbox and agent apps.",
5
+ "homepage": "https://github.com/tangle-network/agent-integrations#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/tangle-network/agent-integrations.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/tangle-network/agent-integrations/issues"
12
+ },
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js",
20
+ "default": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "docs",
26
+ "README.md"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^25.6.0",
33
+ "tsup": "^8.0.0",
34
+ "typescript": "^5.7.0",
35
+ "vitest": "^3.0.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=20"
39
+ },
40
+ "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
+ }