@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 +111 -0
- package/dist/index.d.ts +218 -0
- package/dist/index.js +308 -0
- package/dist/index.js.map +1 -0
- package/docs/architecture.md +46 -0
- package/package.json +48 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|