@tangle-network/agent-integrations 0.1.1 → 0.1.2

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/dist/index.d.ts CHANGED
@@ -1,3 +1,540 @@
1
+ /**
2
+ * Connector primitives — the contract a concrete first-party integration
3
+ * (Google Calendar, HubSpot, Stripe, ...) implements. Lower level than the
4
+ * hub-side `IntegrationProvider` interface from `../index.ts`: a single
5
+ * `IntegrationProvider` typically wraps several connectors (e.g., a
6
+ * "first-party" provider that lists all your shipped connectors as a
7
+ * single catalog).
8
+ *
9
+ * Layering:
10
+ *
11
+ * IntegrationHub — vendor-neutral facade (../index.ts)
12
+ * ↓
13
+ * IntegrationProvider — one per vendor (Nango/Composio/first-party)
14
+ * ↓
15
+ * ConnectorAdapter (this file) — one per integration (Google Calendar, ...)
16
+ * ↓
17
+ * upstream HTTP API — vendor SDK / fetch / OAuth
18
+ *
19
+ * Three load-bearing decisions encoded here:
20
+ *
21
+ * 1. Capabilities are typed (`read` vs `mutation`). Every mutation MUST
22
+ * declare a CAS strategy. Conflict resolution is the SDK's job, not the
23
+ * connector's. `validateConnectorManifest()` rejects unsafe manifests
24
+ * before a connector is registered.
25
+ *
26
+ * 2. ConsistencyModel pins what the rest of the system can assume:
27
+ * authoritative → the source IS the truth (Calendar, payments)
28
+ * cache → we mirror with TTL and may serve stale (price list)
29
+ * advisory → informational only (FAQ doc)
30
+ * Agent planners can (and should) refuse to promise outcomes based on
31
+ * `cache`/`advisory` data without a live `authoritative` confirmation.
32
+ *
33
+ * 3. Capabilities surface to the calling agent's tool registry by
34
+ * transformation, not by hand-wiring. Adding a connector automatically
35
+ * expands the agent's toolbelt for that specific user without touching
36
+ * the prompt or runner.
37
+ */
38
+ /** Minimal JSON-schema shape used for capability arg validation. We
39
+ * intentionally don't pull `@types/json-schema` — most consumers already
40
+ * declare parameters as `Record<string, unknown>` and the
41
+ * shape is whatever the LLM SDK's structured-output expects. Keep the
42
+ * contract loose at the boundary; tighten via runtime zod where needed. */
43
+ type CapabilityParameterSchema = Record<string, unknown>;
44
+ /** What the rest of the system is allowed to assume about freshness. */
45
+ type ConsistencyModel = 'authoritative' | 'cache' | 'advisory';
46
+ /** Capability classes. `read` is safe to retry; `mutation` must go through
47
+ * MutationGuard (CAS + idempotency). `subscribe` is reserved for future
48
+ * push-driven sources (webhook callbacks) and is not yet wired. */
49
+ type CapabilityClass = 'read' | 'mutation' | 'subscribe';
50
+ /** Compare-and-swap strategy a mutation uses to detect conflicts. */
51
+ type CASStrategy =
52
+ /** Upstream returns an etag/sequence on read, accepts If-Match on write
53
+ * (Google Calendar, GitHub, GDocs revision_id). The connector returns
54
+ * 412 / Precondition Failed on conflict; the SDK maps to ResourceContention. */
55
+ 'etag-if-match'
56
+ /** Upstream guarantees exactly-once-per-key (Stripe, idempotent webhooks).
57
+ * The SDK passes the idempotency key through; no etag check. */
58
+ | 'native-idempotency'
59
+ /** No upstream concurrency control. Connector MUST do read-then-write
60
+ * and verify nothing changed in-between (best-effort). Suitable only
61
+ * for low-contention single-user resources; rejected for any
62
+ * consistencyModel='authoritative' write that may race. */
63
+ | 'optimistic-read-verify'
64
+ /** Source is not contended (e.g. logging, telemetry). Mutations are
65
+ * fire-and-forget. Marks the capability as not eligible for
66
+ * authoritative writes. */
67
+ | 'none';
68
+ interface CapabilityRead {
69
+ name: string;
70
+ class: 'read';
71
+ description: string;
72
+ /** JSON-schema for the tool args the agent passes when invoking. */
73
+ parameters: CapabilityParameterSchema;
74
+ /** Optional: declare which scopes (per the connector manifest) this
75
+ * capability requires. The capability is hidden from the agent's
76
+ * tool registry if the user's grant didn't include them. */
77
+ requiredScopes?: string[];
78
+ }
79
+ interface CapabilityMutation {
80
+ name: string;
81
+ class: 'mutation';
82
+ description: string;
83
+ parameters: CapabilityParameterSchema;
84
+ /** Mandatory: how does the connector guarantee at-most-once + conflict-detect? */
85
+ cas: CASStrategy;
86
+ /** True for capabilities that affect resources outside the calling user
87
+ * (e.g. booking against a shared calendar, charging a card). The agent's
88
+ * planner treats these specially: requires explicit caller confirmation
89
+ * before the call. */
90
+ externalEffect: boolean;
91
+ requiredScopes?: string[];
92
+ }
93
+ type Capability = CapabilityRead | CapabilityMutation;
94
+ /** OAuth2 scope catalog the user has granted us, plus arbitrary metadata
95
+ * the connector pinned at connect-time (calendar id, sheet id, webhook
96
+ * url, …). `metadata` MUST NOT contain secrets — those go in the
97
+ * encrypted credentials envelope. */
98
+ interface DataSourceMetadata {
99
+ scopes: string[];
100
+ [key: string]: unknown;
101
+ }
102
+ /** A connected, authenticated, ready-to-call data source for a project.
103
+ * Persistence shape mirrors the product's connection/source row but normalized — the
104
+ * encrypted credentials envelope is decrypted at hand-out time and held
105
+ * in memory only for the duration of the call. */
106
+ interface ResolvedDataSource {
107
+ id: string;
108
+ projectId: string;
109
+ publishedAgentId: string | null;
110
+ kind: string;
111
+ label: string;
112
+ consistencyModel: ConsistencyModel;
113
+ scopes: string[];
114
+ metadata: Record<string, unknown>;
115
+ /** Unwrapped credentials handed to the connector at call-time. Never
116
+ * persisted in this shape; never logged. */
117
+ credentials: ConnectorCredentials;
118
+ status: 'active' | 'revoked' | 'error';
119
+ }
120
+ /** Discriminated union of credential shapes. Connectors that need new
121
+ * shapes extend this union — `kind` is sealed via the tagged pattern so
122
+ * TypeScript catches an exhaustiveness gap at compile time. */
123
+ type ConnectorCredentials = {
124
+ kind: 'oauth2';
125
+ accessToken: string;
126
+ refreshToken?: string;
127
+ expiresAt?: number;
128
+ } | {
129
+ kind: 'api-key';
130
+ apiKey: string;
131
+ } | {
132
+ kind: 'hmac';
133
+ secret: string;
134
+ } | {
135
+ kind: 'none';
136
+ };
137
+ /** Result of a read capability invocation. */
138
+ interface CapabilityReadResult {
139
+ /** Free-form payload — the connector's data shape. The agent receives
140
+ * this as the tool result; planners consume it via JSON-shape contract
141
+ * declared in the capability's `parameters` (output schema). */
142
+ data: unknown;
143
+ /** Optional etag/sequence the caller can reuse for a subsequent CAS
144
+ * mutation. */
145
+ etag?: string;
146
+ /** When this read happened (UTC ms since epoch). */
147
+ fetchedAt: number;
148
+ }
149
+ /** Result of a mutation capability invocation. Either committed (with the
150
+ * resulting etag/sequence so the caller can chain mutations), or
151
+ * contended (the upstream rejected with a state mismatch — the agent
152
+ * should re-read and retry, or surface alternatives to the caller). */
153
+ type CapabilityMutationResult = {
154
+ status: 'committed';
155
+ data: unknown;
156
+ etagAfter?: string;
157
+ committedAt: number;
158
+ /** True iff this commit was returned from the idempotency store
159
+ * rather than executed against upstream. The caller can use this
160
+ * to suppress confirmation messages on retry. */
161
+ idempotentReplay: boolean;
162
+ } | {
163
+ status: 'conflict';
164
+ /** Best-effort alternative options the upstream surfaced (e.g.,
165
+ * next-available calendar slots after a booking conflict). */
166
+ alternatives: unknown[];
167
+ /** The current authoritative state, if the connector could re-read
168
+ * cheaply. */
169
+ currentState?: unknown;
170
+ message: string;
171
+ } | {
172
+ status: 'rate-limited';
173
+ /** Wall-clock ms the caller should wait before retrying. The SDK
174
+ * computes this from the bucket's refill schedule so the agent
175
+ * doesn't have to guess. */
176
+ retryAfterMs: number;
177
+ message: string;
178
+ };
179
+ /** Inputs the SDK passes into the connector's executeRead / executeMutation. */
180
+ interface ConnectorInvocation {
181
+ source: ResolvedDataSource;
182
+ capabilityName: string;
183
+ args: Record<string, unknown>;
184
+ /** Idempotency key the caller (or the SDK's defaulting policy) supplied.
185
+ * Always present at the connector boundary — the SDK manufactures one
186
+ * if the agent didn't pass one. */
187
+ idempotencyKey: string;
188
+ /** Optional caller-supplied etag the connector should send as If-Match. */
189
+ expectedEtag?: string;
190
+ /** Product/session id (if any) for forensic logging. */
191
+ callSessionId?: string;
192
+ }
193
+ /** A single inbound event extracted from a push payload. The webhook
194
+ * receiver persists one `InboundEvent` row per entry the connector returns. */
195
+ interface InboundEvent {
196
+ eventType: string;
197
+ providerEventId?: string;
198
+ payload: Record<string, unknown>;
199
+ }
200
+ /** Adapter response from an inbound-webhook dispatch. The receiver persists
201
+ * every `events[]` entry, then either honors the connector's `response`
202
+ * override (Slack `url_verification` echo, provider-specific 2xx body) or
203
+ * defaults to `{status: 200, body: {received: true, count: events.length}}`. */
204
+ interface EventHandlerResult {
205
+ events: InboundEvent[];
206
+ /** Optional: how to respond to the provider. Stripe wants 200 within
207
+ * 30s; Slack wants the challenge param echoed. */
208
+ response?: {
209
+ status: number;
210
+ body: unknown;
211
+ headers?: Record<string, string>;
212
+ };
213
+ }
214
+ /**
215
+ * Connector adapter — one per integration kind. Stateless. The SDK holds
216
+ * the persistence + crypto + mutation-guard concerns; the adapter only
217
+ * knows how to talk to its upstream.
218
+ */
219
+ interface ConnectorAdapter {
220
+ /** Manifest entry the registry uses to render UI + validate args. */
221
+ manifest: ConnectorManifest;
222
+ /** Read invocation. Required when manifest.capabilities contains reads.
223
+ * Should return whatever shape the capability declared
224
+ * in its parameters output schema. */
225
+ executeRead?(inv: ConnectorInvocation): Promise<CapabilityReadResult>;
226
+ /** Mutation invocation. Required when manifest.capabilities contains mutations.
227
+ * Throws ResourceContention on a CAS miss; throws
228
+ * any other Error for upstream failures. The MutationGuard wraps this
229
+ * with idempotency-key short-circuit + audit logging — adapters do
230
+ * NOT manage their own dedup. */
231
+ executeMutation?(inv: ConnectorInvocation): Promise<CapabilityMutationResult>;
232
+ /** Inbound webhook signature verifier. Called BEFORE handleInboundEvent.
233
+ * MUST use constant-time comparison (`crypto.timingSafeEqual`) for any
234
+ * HMAC check. The receiver returns 401 on `valid=false` without invoking
235
+ * handleInboundEvent. Optional: connectors that don't accept push events
236
+ * omit this method and the receiver returns 405 for the kind. */
237
+ verifySignature?(input: {
238
+ rawBody: string;
239
+ headers: Record<string, string | string[] | undefined>;
240
+ source: ResolvedDataSource;
241
+ }): {
242
+ valid: boolean;
243
+ reason?: string;
244
+ };
245
+ /** Inbound webhook dispatch. Called AFTER verifySignature passes. The
246
+ * adapter parses the provider payload and emits zero-or-more
247
+ * `InboundEvent` rows; the receiver persists them as one row each (modulo
248
+ * the (dataSourceId, providerEventId) dedup unique). The optional
249
+ * `response` overrides the receiver's default 200 (Slack `url_verification`
250
+ * needs to echo the challenge in the body to pass Slack's app-config check). */
251
+ handleInboundEvent?(input: {
252
+ source: ResolvedDataSource;
253
+ rawBody: string;
254
+ headers: Record<string, string | string[] | undefined>;
255
+ }): Promise<EventHandlerResult>;
256
+ /** OAuth callback handler — exchanges the auth code for tokens, returns
257
+ * the credentials envelope + scopes + metadata. Only present for
258
+ * oauth2-style adapters. */
259
+ exchangeOAuth?(input: {
260
+ code: string;
261
+ state: string;
262
+ codeVerifier: string;
263
+ redirectUri: string;
264
+ }): Promise<{
265
+ credentials: ConnectorCredentials;
266
+ scopes: string[];
267
+ metadata: Record<string, unknown>;
268
+ }>;
269
+ /** Refresh access token. Only required for oauth2 adapters with
270
+ * short-lived access tokens. */
271
+ refreshToken?(input: ConnectorCredentials): Promise<ConnectorCredentials>;
272
+ /** Health check — invoked when the user clicks "Test connection" in the
273
+ * UI. Should perform the cheapest possible read that proves the grant
274
+ * is still valid. Returns `{ok: false, reason}` rather than throwing
275
+ * for the common case (token expired, scope missing). */
276
+ test(source: ResolvedDataSource): Promise<{
277
+ ok: true;
278
+ } | {
279
+ ok: false;
280
+ reason: string;
281
+ }>;
282
+ }
283
+ /** Static manifest a connector module exports. Drives the UI catalog,
284
+ * scope display, capability discovery for the agent's tool registry. */
285
+ interface ConnectorManifest {
286
+ /** Stable kind id used as the foreign key in DataSource.kind. */
287
+ kind: string;
288
+ /** Human label shown in the UI catalog. */
289
+ displayName: string;
290
+ /** One-paragraph description shown next to the connect button. */
291
+ description: string;
292
+ /** Auth shape this connector requires. */
293
+ auth: AuthSpec;
294
+ /** Capability catalog — the agent's tool registry derives ToolDefinition
295
+ * entries from this list at request time. */
296
+ capabilities: Capability[];
297
+ /** ConsistencyModel default for this kind — overridable per DataSource
298
+ * if a particular instance is special (e.g., a user marks a sheet as
299
+ * `cache` because they refresh it nightly). */
300
+ defaultConsistencyModel: ConsistencyModel;
301
+ /** Connector category for UI grouping. */
302
+ category: 'calendar' | 'spreadsheet' | 'crm' | 'doc' | 'webhook' | 'storage' | 'comms' | 'commerce' | 'other';
303
+ /** Optional icon URL or named icon. */
304
+ icon?: string;
305
+ /** Optional per-kind rate-limit budget. The SDK enforces it inside
306
+ * `executeGuardedMutation` and the read path of `/invoke`. Omit to
307
+ * leave the connector unrestricted. */
308
+ rateLimit?: RateLimitSpec;
309
+ }
310
+ /** Token-bucket budget the SDK enforces against the connector's upstream.
311
+ * We meter on OUR side rather than letting the upstream reject so a
312
+ * chatty agent can't burn quota that's shared across customers (almost
313
+ * every OAuth client is). */
314
+ interface RateLimitSpec {
315
+ /** Max requests per window. */
316
+ requests: number;
317
+ /** Window in ms. */
318
+ windowMs: number;
319
+ /** Whether to apply across all DataSources sharing the same OAuth
320
+ * client (true; default), or per-DataSource (false). The former
321
+ * matches how upstreams meter (per-app), so almost always pick true. */
322
+ scope?: 'oauth-client' | 'data-source';
323
+ }
324
+ type AuthSpec = {
325
+ kind: 'oauth2';
326
+ /** Authorization endpoint URL. */
327
+ authorizationUrl: string;
328
+ /** Token endpoint URL. */
329
+ tokenUrl: string;
330
+ /** Scopes requested in the authorization grant. The user UI shows
331
+ * these so the customer knows what's being shared. */
332
+ scopes: string[];
333
+ /** Whether the connector supports incremental authorization (Google
334
+ * does; many don't). */
335
+ incremental?: boolean;
336
+ /** Env-var name holding the OAuth client_id. */
337
+ clientIdEnv: string;
338
+ /** Env-var name holding the OAuth client_secret. */
339
+ clientSecretEnv: string;
340
+ /** Optional extra params attached to the authorization URL (e.g.,
341
+ * Google's `access_type=offline&prompt=consent` to obtain refresh
342
+ * tokens). */
343
+ extraAuthParams?: Record<string, string>;
344
+ } | {
345
+ kind: 'api-key';
346
+ /** UI hint shown when collecting the key. */
347
+ hint: string;
348
+ } | {
349
+ kind: 'hmac';
350
+ } | {
351
+ kind: 'none';
352
+ };
353
+ /** Thrown by `executeMutation` when upstream rejects on CAS — caught and
354
+ * rewrapped by MutationGuard. */
355
+ declare class ResourceContention extends Error {
356
+ readonly alternatives: unknown[];
357
+ readonly currentState?: unknown | undefined;
358
+ readonly name = "ResourceContention";
359
+ constructor(message: string, alternatives?: unknown[], currentState?: unknown | undefined);
360
+ }
361
+ /** Thrown when the connector finds the user's grant has been revoked or
362
+ * the access token is no longer valid AND refresh failed. Surfaces to
363
+ * the UI as "Reconnect required". */
364
+ declare class CredentialsExpired extends Error {
365
+ readonly dataSourceId: string;
366
+ readonly name = "CredentialsExpired";
367
+ constructor(message: string, dataSourceId: string);
368
+ }
369
+ interface ConnectorManifestValidationIssue {
370
+ path: string;
371
+ message: string;
372
+ }
373
+ interface ConnectorManifestValidationResult {
374
+ ok: boolean;
375
+ issues: ConnectorManifestValidationIssue[];
376
+ }
377
+ /** Validate the static connector manifest before a provider registers it.
378
+ * This catches the expensive mistakes early: duplicate capability names,
379
+ * mutation capabilities without CAS, authoritative fire-and-forget writes,
380
+ * and invalid rate-limit specs. */
381
+ declare function validateConnectorManifest(manifest: ConnectorManifest): ConnectorManifestValidationResult;
382
+ declare function assertValidConnectorManifest(manifest: ConnectorManifest): void;
383
+
384
+ /**
385
+ * Generic OAuth2 helper used by every oauth-shaped connector (Google
386
+ * Calendar, Sheets, Drive, HubSpot, Salesforce, Zoom, ...).
387
+ *
388
+ * Everything PKCE-aware. Opaque-state CSRF guard. Refresh-token aware.
389
+ * No connector-specific logic lives here — adapters hand a `clientId`,
390
+ * `clientSecret`, `tokenUrl`, optional `extraAuthParams` and the rest is
391
+ * mechanical.
392
+ *
393
+ * State and code_verifier are kept in a short-TTL flow store keyed by the
394
+ * opaque `state` we round-trip through the provider. The default store is
395
+ * in-memory for local/dev and tests. Production deployments should inject a
396
+ * durable store backed by KV/Redis/D1/etc. so callbacks can land on any worker.
397
+ */
398
+ interface PendingOAuthFlow {
399
+ /** code_verifier for PKCE. */
400
+ codeVerifier: string;
401
+ /** Opaque-state value also returned in the OAuth redirect. */
402
+ state: string;
403
+ /** Project the user is connecting under. */
404
+ projectId: string;
405
+ /** Connector kind (e.g. 'google-calendar'). */
406
+ kind: string;
407
+ /** Operator-supplied label that becomes DataSource.label. */
408
+ label: string;
409
+ /** When we drop the entry. */
410
+ expiresAt: number;
411
+ /** The redirectUri we used in the start step — must match exactly on
412
+ * the callback exchange. */
413
+ redirectUri: string;
414
+ }
415
+ interface OAuthFlowStore {
416
+ put(state: string, flow: PendingOAuthFlow): Promise<void> | void;
417
+ consume(state: string): Promise<PendingOAuthFlow | undefined> | PendingOAuthFlow | undefined;
418
+ sweep?(now: number): Promise<void> | void;
419
+ clear?(): Promise<void> | void;
420
+ }
421
+ declare class InMemoryOAuthFlowStore implements OAuthFlowStore {
422
+ private readonly pendingFlows;
423
+ put(state: string, flow: PendingOAuthFlow): void;
424
+ consume(state: string): PendingOAuthFlow | undefined;
425
+ sweep(now: number): void;
426
+ clear(): void;
427
+ }
428
+ interface StartOAuthInput {
429
+ projectId: string;
430
+ kind: string;
431
+ label: string;
432
+ authorizationUrl: string;
433
+ scopes: string[];
434
+ clientId: string;
435
+ redirectUri: string;
436
+ /** Optional extra query params; Google needs `access_type=offline` and
437
+ * `prompt=consent` to issue refresh tokens reliably. */
438
+ extraAuthParams?: Record<string, string>;
439
+ /** Optional flow store. Use a durable store in distributed production
440
+ * runtimes; omitted means local in-memory storage. */
441
+ store?: OAuthFlowStore;
442
+ /** Override clock for tests. */
443
+ now?: number;
444
+ }
445
+ interface StartOAuthOutput {
446
+ /** URL the SPA should redirect the user to. */
447
+ authorizationUrl: string;
448
+ /** State token — caller stashes this in localStorage to verify on
449
+ * callback. */
450
+ state: string;
451
+ }
452
+ /** Build the authorization URL + state. SPA navigates the user there;
453
+ * user consents; provider redirects back to redirectUri with `code` +
454
+ * `state`. The caller's callback then invokes `consumePendingFlow`. */
455
+ declare function startOAuthFlow(input: StartOAuthInput): StartOAuthOutput;
456
+ /** Look up + remove the pending flow record. Throws if state is unknown
457
+ * or expired (CSRF guard / replay protection). */
458
+ declare function consumePendingFlow(state: string, store?: OAuthFlowStore): Promise<PendingOAuthFlow>;
459
+ interface ExchangeCodeInput {
460
+ tokenUrl: string;
461
+ clientId: string;
462
+ clientSecret: string;
463
+ code: string;
464
+ codeVerifier: string;
465
+ redirectUri: string;
466
+ }
467
+ interface OAuthTokens {
468
+ accessToken: string;
469
+ refreshToken?: string;
470
+ expiresIn?: number;
471
+ scope?: string;
472
+ tokenType?: string;
473
+ }
474
+ /** POST authorization code → token endpoint. Provider-agnostic; if a
475
+ * provider returns a non-standard JSON shape, the adapter wraps this
476
+ * call rather than reaching into the helper. */
477
+ declare function exchangeAuthorizationCode(input: ExchangeCodeInput): Promise<OAuthTokens>;
478
+ interface RefreshInput {
479
+ tokenUrl: string;
480
+ clientId: string;
481
+ clientSecret: string;
482
+ refreshToken: string;
483
+ }
484
+ /** Refresh an access token. Returns the new tokens — the connector layer
485
+ * is responsible for re-encrypting + persisting the envelope. */
486
+ declare function refreshAccessToken(input: RefreshInput): Promise<OAuthTokens>;
487
+ /** Test-only — drop pending flows between unit-test runs. */
488
+ declare function _resetPendingFlowsForTests(): void;
489
+
490
+ /**
491
+ * Inbound webhook signature verifiers — provider-specific HMAC schemes.
492
+ *
493
+ * Each signature scheme is a pure function:
494
+ * (rawBody: string, headers, secret, now?) → boolean
495
+ *
496
+ * Constant-time comparison via `crypto.timingSafeEqual`. Timestamps are
497
+ * checked against a configurable tolerance to bound replay risk; the default
498
+ * mirrors the upstream provider's documented window (Stripe: 5 min, Slack: 5 min).
499
+ *
500
+ * These verifiers are the building blocks for any inbound-webhook receiver
501
+ * (a route + a `verify` call + a per-event handler). They live in this
502
+ * package so every consumer of the integration substrate gets correct
503
+ * verification — not just one product reimplementing it.
504
+ */
505
+ /** Default replay-protection window. Providers commonly use 5 minutes. */
506
+ declare const DEFAULT_SIGNATURE_TOLERANCE_SECONDS: number;
507
+ interface ParsedStripeSignatureHeader {
508
+ t: number;
509
+ sigs: string[];
510
+ }
511
+ declare function parseStripeSignatureHeader(header: string): ParsedStripeSignatureHeader | null;
512
+ interface StripeVerifyOptions {
513
+ /** Replay-protection window in seconds. Default 300. */
514
+ toleranceSeconds?: number;
515
+ /** Override `now()` for tests. UTC seconds. */
516
+ now?: number;
517
+ }
518
+ /** Verify a Stripe webhook signature against the raw request body. */
519
+ declare function verifyStripeSignature(rawBody: string, signatureHeader: string, secret: string, options?: StripeVerifyOptions): boolean;
520
+ interface SlackVerifyOptions {
521
+ toleranceSeconds?: number;
522
+ now?: number;
523
+ }
524
+ declare function verifySlackSignature(rawBody: string, signatureHeader: string, timestampHeader: string, secret: string, options?: SlackVerifyOptions): boolean;
525
+ interface GenericHmacVerifyOptions {
526
+ /** sha256 (default) | sha1 | sha512 — matches the algorithm the receiver
527
+ * computed at sign time. */
528
+ algorithm?: 'sha256' | 'sha1' | 'sha512';
529
+ /** Optional prefix the receiver prepends to the signature in the header
530
+ * (e.g., `'sha256='`). Stripped before constant-time comparison. */
531
+ signaturePrefix?: string;
532
+ /** Lowercase comparison (most providers emit hex-lowercase). Default true. */
533
+ lowercaseHex?: boolean;
534
+ }
535
+ declare function verifyHmacSignature(rawBody: string, signatureHeader: string, secret: string, options?: GenericHmacVerifyOptions): boolean;
536
+ declare function firstHeader(headers: Record<string, string | string[] | undefined>, name: string): string | undefined;
537
+
1
538
  type IntegrationProviderKind = 'first_party' | 'nango' | 'pipedream' | 'zapier' | 'activepieces' | 'executor' | 'custom';
2
539
  type IntegrationConnectorCategory = 'email' | 'calendar' | 'chat' | 'crm' | 'storage' | 'docs' | 'database' | 'webhook' | 'workflow' | 'internal' | 'other';
3
540
  type IntegrationActionRisk = 'read' | 'write' | 'destructive';
@@ -256,4 +793,4 @@ declare function createHttpIntegrationProvider(options: HttpIntegrationProviderO
256
793
  declare function signCapability(capability: IntegrationCapability, secret: string): string;
257
794
  declare function verifyCapabilityToken(token: string, secret: string): IntegrationCapability;
258
795
 
259
- export { type CompleteAuthRequest, type HttpIntegrationProviderOptions, InMemoryConnectionStore, type IntegrationActionGuard, type IntegrationActionRequest, type IntegrationActionResult, type IntegrationActionRisk, type IntegrationActor, type IntegrationCapability, type IntegrationConnection, type IntegrationConnectionStore, type IntegrationConnector, type IntegrationConnectorAction, type IntegrationConnectorCategory, type IntegrationConnectorTrigger, type IntegrationDataClass, IntegrationError, type IntegrationGuardContext, IntegrationHub, type IntegrationHubOptions, type IntegrationProvider, type IntegrationProviderKind, type IntegrationTriggerEvent, type IntegrationTriggerSubscription, type InvokeWithCapabilityRequest, type IssueCapabilityRequest, type IssuedIntegrationCapability, type SecretRef, type StartAuthRequest, type StartAuthResult, createHttpIntegrationProvider, createMockIntegrationProvider, sanitizeConnection, signCapability, verifyCapabilityToken };
796
+ export { type AuthSpec, type CASStrategy, type Capability, type CapabilityClass, type CapabilityMutation, type CapabilityMutationResult, type CapabilityParameterSchema, type CapabilityRead, type CapabilityReadResult, type CompleteAuthRequest, type ConnectorAdapter, type ConnectorCredentials, type ConnectorInvocation, type ConnectorManifest, type ConnectorManifestValidationIssue, type ConnectorManifestValidationResult, type ConsistencyModel, CredentialsExpired, DEFAULT_SIGNATURE_TOLERANCE_SECONDS, type DataSourceMetadata, type EventHandlerResult, type ExchangeCodeInput, type GenericHmacVerifyOptions, type HttpIntegrationProviderOptions, InMemoryConnectionStore, InMemoryOAuthFlowStore, type InboundEvent, type IntegrationActionGuard, type IntegrationActionRequest, type IntegrationActionResult, type IntegrationActionRisk, type IntegrationActor, type IntegrationCapability, type IntegrationConnection, type IntegrationConnectionStore, type IntegrationConnector, type IntegrationConnectorAction, type IntegrationConnectorCategory, type IntegrationConnectorTrigger, type IntegrationDataClass, IntegrationError, type IntegrationGuardContext, IntegrationHub, type IntegrationHubOptions, type IntegrationProvider, type IntegrationProviderKind, type IntegrationTriggerEvent, type IntegrationTriggerSubscription, type InvokeWithCapabilityRequest, type IssueCapabilityRequest, type IssuedIntegrationCapability, type OAuthFlowStore, type OAuthTokens, type ParsedStripeSignatureHeader, type PendingOAuthFlow, type RateLimitSpec, type RefreshInput, type ResolvedDataSource, ResourceContention, type SecretRef, type SlackVerifyOptions, type StartAuthRequest, type StartAuthResult, type StartOAuthInput, type StartOAuthOutput, type StripeVerifyOptions, _resetPendingFlowsForTests, assertValidConnectorManifest, consumePendingFlow, createHttpIntegrationProvider, createMockIntegrationProvider, exchangeAuthorizationCode, firstHeader, parseStripeSignatureHeader, refreshAccessToken, sanitizeConnection, signCapability, startOAuthFlow, validateConnectorManifest, verifyCapabilityToken, verifyHmacSignature, verifySlackSignature, verifyStripeSignature };
package/dist/index.js CHANGED
@@ -1,5 +1,254 @@
1
1
  // src/index.ts
2
- import { createHmac, randomUUID, timingSafeEqual } from "crypto";
2
+ import { createHmac as createHmac2, randomUUID, timingSafeEqual as timingSafeEqual2 } from "crypto";
3
+
4
+ // src/connectors/types.ts
5
+ var ResourceContention = class extends Error {
6
+ constructor(message, alternatives = [], currentState) {
7
+ super(message);
8
+ this.alternatives = alternatives;
9
+ this.currentState = currentState;
10
+ }
11
+ alternatives;
12
+ currentState;
13
+ name = "ResourceContention";
14
+ };
15
+ var CredentialsExpired = class extends Error {
16
+ constructor(message, dataSourceId) {
17
+ super(message);
18
+ this.dataSourceId = dataSourceId;
19
+ }
20
+ dataSourceId;
21
+ name = "CredentialsExpired";
22
+ };
23
+ function validateConnectorManifest(manifest) {
24
+ const issues = [];
25
+ if (!manifest.kind.trim()) issues.push({ path: "kind", message: "kind is required" });
26
+ if (!manifest.displayName.trim()) issues.push({ path: "displayName", message: "displayName is required" });
27
+ const seen = /* @__PURE__ */ new Set();
28
+ for (const [index, capability] of manifest.capabilities.entries()) {
29
+ const path = `capabilities[${index}]`;
30
+ if (!capability.name.trim()) issues.push({ path: `${path}.name`, message: "capability name is required" });
31
+ if (seen.has(capability.name)) issues.push({ path: `${path}.name`, message: `duplicate capability name: ${capability.name}` });
32
+ seen.add(capability.name);
33
+ if (capability.class === "mutation") {
34
+ if (!capability.cas) issues.push({ path: `${path}.cas`, message: "mutation capability must declare a CAS strategy" });
35
+ if (manifest.defaultConsistencyModel === "authoritative" && capability.cas === "none") {
36
+ issues.push({ path: `${path}.cas`, message: 'authoritative mutations cannot use cas="none"' });
37
+ }
38
+ }
39
+ }
40
+ if (manifest.rateLimit) {
41
+ if (!Number.isFinite(manifest.rateLimit.requests) || manifest.rateLimit.requests <= 0) {
42
+ issues.push({ path: "rateLimit.requests", message: "rateLimit.requests must be positive" });
43
+ }
44
+ if (!Number.isFinite(manifest.rateLimit.windowMs) || manifest.rateLimit.windowMs <= 0) {
45
+ issues.push({ path: "rateLimit.windowMs", message: "rateLimit.windowMs must be positive" });
46
+ }
47
+ }
48
+ return { ok: issues.length === 0, issues };
49
+ }
50
+ function assertValidConnectorManifest(manifest) {
51
+ const result = validateConnectorManifest(manifest);
52
+ if (!result.ok) {
53
+ throw new Error(`Invalid connector manifest ${manifest.kind || "<unknown>"}: ${result.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
54
+ }
55
+ }
56
+
57
+ // src/connectors/oauth.ts
58
+ import { createHash, randomBytes } from "crypto";
59
+ var PENDING_TTL_MS = 10 * 60 * 1e3;
60
+ var InMemoryOAuthFlowStore = class {
61
+ pendingFlows = /* @__PURE__ */ new Map();
62
+ put(state, flow) {
63
+ this.pendingFlows.set(state, flow);
64
+ }
65
+ consume(state) {
66
+ const flow = this.pendingFlows.get(state);
67
+ this.pendingFlows.delete(state);
68
+ if (!flow || flow.expiresAt <= Date.now()) return void 0;
69
+ return flow;
70
+ }
71
+ sweep(now) {
72
+ for (const [k, v] of this.pendingFlows) {
73
+ if (v.expiresAt <= now) this.pendingFlows.delete(k);
74
+ }
75
+ }
76
+ clear() {
77
+ this.pendingFlows.clear();
78
+ }
79
+ };
80
+ var defaultFlowStore = new InMemoryOAuthFlowStore();
81
+ function startOAuthFlow(input) {
82
+ const store = input.store ?? defaultFlowStore;
83
+ const now = input.now ?? Date.now();
84
+ store.sweep?.(now);
85
+ const codeVerifier = base64Url(randomBytes(48));
86
+ const codeChallenge = base64Url(createHash("sha256").update(codeVerifier).digest());
87
+ const state = base64Url(randomBytes(24));
88
+ store.put(state, {
89
+ codeVerifier,
90
+ state,
91
+ projectId: input.projectId,
92
+ kind: input.kind,
93
+ label: input.label,
94
+ redirectUri: input.redirectUri,
95
+ expiresAt: now + PENDING_TTL_MS
96
+ });
97
+ const url = new URL(input.authorizationUrl);
98
+ url.searchParams.set("response_type", "code");
99
+ url.searchParams.set("client_id", input.clientId);
100
+ url.searchParams.set("redirect_uri", input.redirectUri);
101
+ url.searchParams.set("scope", input.scopes.join(" "));
102
+ url.searchParams.set("state", state);
103
+ url.searchParams.set("code_challenge", codeChallenge);
104
+ url.searchParams.set("code_challenge_method", "S256");
105
+ if (input.extraAuthParams) {
106
+ for (const [k, v] of Object.entries(input.extraAuthParams)) {
107
+ url.searchParams.set(k, v);
108
+ }
109
+ }
110
+ return { authorizationUrl: url.toString(), state };
111
+ }
112
+ async function consumePendingFlow(state, store = defaultFlowStore) {
113
+ await store.sweep?.(Date.now());
114
+ const flow = await store.consume(state);
115
+ if (!flow) {
116
+ throw new Error("Unknown or expired OAuth state: possible CSRF, replay, or stale flow");
117
+ }
118
+ return flow;
119
+ }
120
+ async function exchangeAuthorizationCode(input) {
121
+ const body = new URLSearchParams({
122
+ grant_type: "authorization_code",
123
+ client_id: input.clientId,
124
+ client_secret: input.clientSecret,
125
+ code: input.code,
126
+ redirect_uri: input.redirectUri,
127
+ code_verifier: input.codeVerifier
128
+ });
129
+ const res = await fetch(input.tokenUrl, {
130
+ method: "POST",
131
+ headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
132
+ body
133
+ });
134
+ if (!res.ok) {
135
+ const text = await res.text().catch(() => "");
136
+ throw new Error(`OAuth token exchange failed: ${res.status} ${res.statusText} \u2014 ${text.slice(0, 200)}`);
137
+ }
138
+ const json = await res.json();
139
+ return {
140
+ accessToken: json.access_token,
141
+ refreshToken: json.refresh_token,
142
+ expiresIn: json.expires_in,
143
+ scope: json.scope,
144
+ tokenType: json.token_type
145
+ };
146
+ }
147
+ async function refreshAccessToken(input) {
148
+ const body = new URLSearchParams({
149
+ grant_type: "refresh_token",
150
+ client_id: input.clientId,
151
+ client_secret: input.clientSecret,
152
+ refresh_token: input.refreshToken
153
+ });
154
+ const res = await fetch(input.tokenUrl, {
155
+ method: "POST",
156
+ headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
157
+ body
158
+ });
159
+ if (!res.ok) {
160
+ const text = await res.text().catch(() => "");
161
+ throw new Error(`OAuth refresh failed: ${res.status} ${res.statusText} \u2014 ${text.slice(0, 200)}`);
162
+ }
163
+ const json = await res.json();
164
+ return {
165
+ accessToken: json.access_token,
166
+ // Some providers omit refresh_token on refresh — keep the previous one
167
+ // in that case (caller passes through if undefined).
168
+ refreshToken: json.refresh_token,
169
+ expiresIn: json.expires_in,
170
+ scope: json.scope,
171
+ tokenType: json.token_type
172
+ };
173
+ }
174
+ function base64Url(buf) {
175
+ return buf.toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
176
+ }
177
+ function _resetPendingFlowsForTests() {
178
+ defaultFlowStore.clear?.();
179
+ }
180
+
181
+ // src/connectors/webhooks.ts
182
+ import { createHmac, timingSafeEqual } from "crypto";
183
+ var DEFAULT_SIGNATURE_TOLERANCE_SECONDS = 5 * 60;
184
+ function parseStripeSignatureHeader(header) {
185
+ const acc = { sigs: [] };
186
+ for (const part of header.split(",")) {
187
+ const idx = part.indexOf("=");
188
+ if (idx < 0) continue;
189
+ const key = part.slice(0, idx).trim();
190
+ const val = part.slice(idx + 1).trim();
191
+ if (key === "t") {
192
+ const n = Number(val);
193
+ if (Number.isFinite(n)) acc.ts = n;
194
+ } else if (key === "v1") {
195
+ acc.sigs.push(val);
196
+ }
197
+ }
198
+ if (acc.ts === void 0 || acc.sigs.length === 0) return null;
199
+ return { t: acc.ts, sigs: acc.sigs };
200
+ }
201
+ function verifyStripeSignature(rawBody, signatureHeader, secret, options = {}) {
202
+ const parsed = parseStripeSignatureHeader(signatureHeader);
203
+ if (!parsed) return false;
204
+ const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS;
205
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
206
+ if (Math.abs(now - parsed.t) > tolerance) return false;
207
+ const expected = createHmac("sha256", secret).update(`${parsed.t}.${rawBody}`).digest("hex");
208
+ const expectedBuf = Buffer.from(expected, "utf8");
209
+ for (const sig of parsed.sigs) {
210
+ const sigBuf = Buffer.from(sig, "utf8");
211
+ if (sigBuf.length !== expectedBuf.length) continue;
212
+ if (timingSafeEqual(sigBuf, expectedBuf)) return true;
213
+ }
214
+ return false;
215
+ }
216
+ function verifySlackSignature(rawBody, signatureHeader, timestampHeader, secret, options = {}) {
217
+ if (!signatureHeader.startsWith("v0=")) return false;
218
+ const ts = Number(timestampHeader);
219
+ if (!Number.isFinite(ts)) return false;
220
+ const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS;
221
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
222
+ if (Math.abs(now - ts) > tolerance) return false;
223
+ const expected = "v0=" + createHmac("sha256", secret).update(`v0:${ts}:${rawBody}`).digest("hex");
224
+ const expectedBuf = Buffer.from(expected, "utf8");
225
+ const sigBuf = Buffer.from(signatureHeader, "utf8");
226
+ if (sigBuf.length !== expectedBuf.length) return false;
227
+ return timingSafeEqual(sigBuf, expectedBuf);
228
+ }
229
+ function verifyHmacSignature(rawBody, signatureHeader, secret, options = {}) {
230
+ const algorithm = options.algorithm ?? "sha256";
231
+ const prefix = options.signaturePrefix ?? "";
232
+ const lower = options.lowercaseHex ?? true;
233
+ let candidate = signatureHeader;
234
+ if (prefix) {
235
+ if (!candidate.startsWith(prefix)) return false;
236
+ candidate = candidate.slice(prefix.length);
237
+ }
238
+ if (lower) candidate = candidate.toLowerCase();
239
+ const expected = createHmac(algorithm, secret).update(rawBody).digest("hex");
240
+ const expectedBuf = Buffer.from(expected, "utf8");
241
+ const sigBuf = Buffer.from(candidate, "utf8");
242
+ if (sigBuf.length !== expectedBuf.length) return false;
243
+ return timingSafeEqual(sigBuf, expectedBuf);
244
+ }
245
+ function firstHeader(headers, name) {
246
+ const v = headers[name] ?? headers[name.toLowerCase()] ?? Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
247
+ if (Array.isArray(v)) return v[0];
248
+ return typeof v === "string" ? v : void 0;
249
+ }
250
+
251
+ // src/index.ts
3
252
  var IntegrationError = class extends Error {
4
253
  constructor(message, code) {
5
254
  super(message);
@@ -286,12 +535,12 @@ function assertScopes(connection, requiredScopes) {
286
535
  if (missing.length > 0) throw new IntegrationError(`Missing integration scopes: ${missing.join(", ")}`, "scope_denied");
287
536
  }
288
537
  function hmac(payload, secret) {
289
- return createHmac("sha256", secret).update(payload).digest("base64url");
538
+ return createHmac2("sha256", secret).update(payload).digest("base64url");
290
539
  }
291
540
  function constantTimeEqual(a, b) {
292
541
  const left = Buffer.from(a);
293
542
  const right = Buffer.from(b);
294
- return left.length === right.length && timingSafeEqual(left, right);
543
+ return left.length === right.length && timingSafeEqual2(left, right);
295
544
  }
296
545
  function base64UrlEncode(value) {
297
546
  return Buffer.from(value, "utf8").toString("base64url");
@@ -303,13 +552,29 @@ function unique(values) {
303
552
  return [...new Set(values)];
304
553
  }
305
554
  export {
555
+ CredentialsExpired,
556
+ DEFAULT_SIGNATURE_TOLERANCE_SECONDS,
306
557
  InMemoryConnectionStore,
558
+ InMemoryOAuthFlowStore,
307
559
  IntegrationError,
308
560
  IntegrationHub,
561
+ ResourceContention,
562
+ _resetPendingFlowsForTests,
563
+ assertValidConnectorManifest,
564
+ consumePendingFlow,
309
565
  createHttpIntegrationProvider,
310
566
  createMockIntegrationProvider,
567
+ exchangeAuthorizationCode,
568
+ firstHeader,
569
+ parseStripeSignatureHeader,
570
+ refreshAccessToken,
311
571
  sanitizeConnection,
312
572
  signCapability,
313
- verifyCapabilityToken
573
+ startOAuthFlow,
574
+ validateConnectorManifest,
575
+ verifyCapabilityToken,
576
+ verifyHmacSignature,
577
+ verifySlackSignature,
578
+ verifyStripeSignature
314
579
  };
315
580
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto'\n\nexport type IntegrationProviderKind =\n | 'first_party'\n | 'nango'\n | 'pipedream'\n | 'zapier'\n | 'activepieces'\n | 'executor'\n | 'custom'\n\nexport type IntegrationConnectorCategory =\n | 'email'\n | 'calendar'\n | 'chat'\n | 'crm'\n | 'storage'\n | 'docs'\n | 'database'\n | 'webhook'\n | 'workflow'\n | 'internal'\n | 'other'\n\nexport type IntegrationActionRisk = 'read' | 'write' | 'destructive'\nexport type IntegrationDataClass = 'public' | 'internal' | 'private' | 'sensitive' | 'secret'\n\nexport interface IntegrationActor {\n type: 'user' | 'team' | 'agent' | 'sandbox' | 'system'\n id: string\n}\n\nexport interface IntegrationConnectorAction {\n id: string\n title: string\n risk: IntegrationActionRisk\n requiredScopes: string[]\n dataClass: IntegrationDataClass\n description?: string\n approvalRequired?: boolean\n inputSchema?: unknown\n outputSchema?: unknown\n}\n\nexport interface IntegrationConnectorTrigger {\n id: string\n title: string\n requiredScopes: string[]\n dataClass: IntegrationDataClass\n description?: string\n payloadSchema?: unknown\n}\n\nexport interface IntegrationConnector {\n id: string\n providerId: string\n title: string\n category: IntegrationConnectorCategory\n auth: 'oauth2' | 'api_key' | 'none' | 'custom'\n scopes: string[]\n actions: IntegrationConnectorAction[]\n triggers?: IntegrationConnectorTrigger[]\n metadata?: Record<string, unknown>\n}\n\nexport interface SecretRef {\n provider: string\n id: string\n label?: string\n}\n\nexport interface IntegrationConnection {\n id: string\n owner: IntegrationActor\n providerId: string\n connectorId: string\n status: 'pending' | 'active' | 'expired' | 'revoked' | 'error'\n grantedScopes: string[]\n account?: {\n id?: string\n email?: string\n displayName?: string\n }\n secretRef?: SecretRef\n createdAt: string\n updatedAt: string\n expiresAt?: string\n lastUsedAt?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface StartAuthRequest {\n connectorId: string\n owner: IntegrationActor\n requestedScopes: string[]\n redirectUri: string\n state?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface StartAuthResult {\n providerId: string\n connectorId: string\n authUrl: string\n state: string\n expiresAt?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface CompleteAuthRequest {\n connectorId: string\n owner: IntegrationActor\n code?: string\n state: string\n redirectUri: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationActionRequest {\n connectionId: string\n action: string\n input?: unknown\n idempotencyKey?: string\n dryRun?: boolean\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationActionResult<T = unknown> {\n ok: boolean\n action: string\n output?: T\n externalId?: string\n warnings?: string[]\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationTriggerSubscription {\n id: string\n connectionId: string\n trigger: string\n targetUrl?: string\n status: 'active' | 'paused' | 'error'\n createdAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationTriggerEvent<T = unknown> {\n id: string\n providerId: string\n connectorId: string\n connectionId: string\n trigger: string\n occurredAt: string\n payload: T\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationProvider {\n id: string\n kind: IntegrationProviderKind\n listConnectors(): Promise<IntegrationConnector[]> | IntegrationConnector[]\n startAuth?(request: StartAuthRequest): Promise<StartAuthResult> | StartAuthResult\n completeAuth?(request: CompleteAuthRequest): Promise<IntegrationConnection> | IntegrationConnection\n invokeAction(connection: IntegrationConnection, request: IntegrationActionRequest): Promise<IntegrationActionResult> | IntegrationActionResult\n subscribeTrigger?(connection: IntegrationConnection, trigger: string, targetUrl?: string): Promise<IntegrationTriggerSubscription> | IntegrationTriggerSubscription\n unsubscribeTrigger?(subscriptionId: string): Promise<void> | void\n normalizeTriggerEvent?(raw: unknown): Promise<IntegrationTriggerEvent> | IntegrationTriggerEvent\n}\n\nexport interface IntegrationConnectionStore {\n get(connectionId: string): Promise<IntegrationConnection | undefined> | IntegrationConnection | undefined\n put(connection: IntegrationConnection): Promise<void> | void\n listByOwner(owner: IntegrationActor): Promise<IntegrationConnection[]> | IntegrationConnection[]\n delete?(connectionId: string): Promise<void> | void\n}\n\nexport interface IssueCapabilityRequest {\n subject: IntegrationActor\n connectionId: string\n scopes: string[]\n allowedActions: string[]\n ttlMs: number\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationCapability {\n id: string\n subject: IntegrationActor\n connectionId: string\n scopes: string[]\n allowedActions: string[]\n issuedAt: string\n expiresAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IssuedIntegrationCapability {\n capability: IntegrationCapability\n token: string\n}\n\n/**\n * Wraps every action invocation with cross-cutting discipline (idempotency,\n * conflict detection, rate-limiting, audit logging). Optional. When set on\n * the hub, runs BEFORE provider.invokeAction; can short-circuit (return a\n * result directly) or pass through (call `proceed()` to invoke the provider).\n *\n * Why this hook exists: production deployments need conflict-resolution\n * guarantees that span every provider — first-party, Nango, Composio,\n * webhook receivers — and providers shouldn't re-implement them. The\n * canonical implementation is a \"MutationGuard\" that:\n * 1. Short-circuits on a known idempotency key (returns recorded response).\n * 2. Refuses same-key-different-args (drift detection).\n * 3. Wraps `proceed()` and audit-logs the outcome.\n * 4. Translates upstream conflict signals into a structured result with\n * alternatives the agent can act on.\n *\n * Implementations live in consumers (every product has different\n * persistence + telemetry needs); this interface is the contract.\n */\nexport interface IntegrationActionGuard {\n /** Wrap an invokeAction call. Implementations MUST call `proceed()` to\n * invoke the underlying provider unless they're returning a cached or\n * short-circuited result.\n *\n * @param ctx connection + request the hub is about to dispatch\n * @param proceed call to invoke the wrapped provider; returns the\n * underlying IntegrationActionResult\n * @returns the result the hub should return to the caller\n */\n invokeAction(\n ctx: IntegrationGuardContext,\n proceed: () => Promise<IntegrationActionResult>,\n ): Promise<IntegrationActionResult>\n}\n\nexport interface IntegrationGuardContext {\n connection: IntegrationConnection\n request: IntegrationActionRequest\n /** The action descriptor from the connector manifest, if discovered. */\n action?: IntegrationConnectorAction\n}\n\nexport interface IntegrationHubOptions {\n providers: IntegrationProvider[]\n store: IntegrationConnectionStore\n capabilitySecret: string\n /** Optional cross-cutting guard. If provided, every invokeAction call\n * passes through it before reaching the provider. See {@link IntegrationActionGuard}. */\n guard?: IntegrationActionGuard\n now?: () => Date\n}\n\nexport interface HttpIntegrationProviderOptions {\n id: string\n kind?: IntegrationProviderKind\n connectors: IntegrationConnector[]\n baseUrl: string\n bearer?: string\n fetchImpl?: typeof fetch\n}\n\nexport interface InvokeWithCapabilityRequest extends Omit<IntegrationActionRequest, 'connectionId'> {\n connectionId?: never\n}\n\nexport class IntegrationError extends Error {\n constructor(\n message: string,\n readonly code:\n | 'provider_not_found'\n | 'connector_not_found'\n | 'connection_not_found'\n | 'connection_not_active'\n | 'auth_not_supported'\n | 'capability_invalid'\n | 'capability_expired'\n | 'scope_denied'\n | 'action_denied'\n | 'action_not_found',\n ) {\n super(message)\n this.name = 'IntegrationError'\n }\n}\n\nexport class InMemoryConnectionStore implements IntegrationConnectionStore {\n private readonly connections = new Map<string, IntegrationConnection>()\n\n get(connectionId: string): IntegrationConnection | undefined {\n return this.connections.get(connectionId)\n }\n\n put(connection: IntegrationConnection): void {\n this.connections.set(connection.id, connection)\n }\n\n listByOwner(owner: IntegrationActor): IntegrationConnection[] {\n return [...this.connections.values()].filter((connection) =>\n connection.owner.type === owner.type && connection.owner.id === owner.id,\n )\n }\n\n delete(connectionId: string): void {\n this.connections.delete(connectionId)\n }\n}\n\nexport class IntegrationHub {\n private readonly providers = new Map<string, IntegrationProvider>()\n private readonly store: IntegrationConnectionStore\n private readonly capabilitySecret: string\n private readonly guard: IntegrationActionGuard | undefined\n private readonly now: () => Date\n\n constructor(options: IntegrationHubOptions) {\n if (!options.capabilitySecret) {\n throw new IntegrationError('capabilitySecret is required.', 'capability_invalid')\n }\n for (const provider of options.providers) this.providers.set(provider.id, provider)\n this.store = options.store\n this.capabilitySecret = options.capabilitySecret\n this.guard = options.guard\n this.now = options.now ?? (() => new Date())\n }\n\n async listConnectors(): Promise<IntegrationConnector[]> {\n const catalogs = await Promise.all([...this.providers.values()].map((provider) => provider.listConnectors()))\n return catalogs.flat()\n }\n\n async startAuth(providerId: string, request: StartAuthRequest): Promise<StartAuthResult> {\n const provider = this.requireProvider(providerId)\n if (!provider.startAuth) throw new IntegrationError(`Provider ${providerId} does not support auth start.`, 'auth_not_supported')\n await this.requireConnector(provider, request.connectorId)\n return provider.startAuth(request)\n }\n\n async completeAuth(providerId: string, request: CompleteAuthRequest): Promise<IntegrationConnection> {\n const provider = this.requireProvider(providerId)\n if (!provider.completeAuth) throw new IntegrationError(`Provider ${providerId} does not support auth completion.`, 'auth_not_supported')\n const connection = await provider.completeAuth(request)\n await this.store.put(connection)\n return connection\n }\n\n async upsertConnection(connection: IntegrationConnection): Promise<IntegrationConnection> {\n await this.store.put(connection)\n return connection\n }\n\n async issueCapability(request: IssueCapabilityRequest): Promise<IssuedIntegrationCapability> {\n const connection = await this.requireConnection(request.connectionId)\n this.assertConnectionActive(connection)\n assertScopes(connection, request.scopes)\n const now = this.now()\n const capability: IntegrationCapability = {\n id: `cap_${randomUUID()}`,\n subject: request.subject,\n connectionId: request.connectionId,\n scopes: unique(request.scopes),\n allowedActions: unique(request.allowedActions),\n issuedAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + request.ttlMs).toISOString(),\n metadata: request.metadata,\n }\n return { capability, token: signCapability(capability, this.capabilitySecret) }\n }\n\n verifyCapability(token: string): IntegrationCapability {\n const capability = verifyCapabilityToken(token, this.capabilitySecret)\n if (Date.parse(capability.expiresAt) <= this.now().getTime()) {\n throw new IntegrationError('Integration capability expired.', 'capability_expired')\n }\n return capability\n }\n\n async invokeWithCapability(token: string, request: InvokeWithCapabilityRequest): Promise<IntegrationActionResult> {\n const capability = this.verifyCapability(token)\n if (!capability.allowedActions.includes(request.action)) {\n throw new IntegrationError(`Capability does not allow action ${request.action}.`, 'action_denied')\n }\n const connection = await this.requireConnection(capability.connectionId)\n this.assertConnectionActive(connection)\n const provider = this.requireProvider(connection.providerId)\n const connector = await this.requireConnector(provider, connection.connectorId)\n const action = connector.actions.find((candidate) => candidate.id === request.action)\n if (!action) throw new IntegrationError(`Action ${request.action} is not defined by connector ${connector.id}.`, 'action_not_found')\n assertScopes(connection, action.requiredScopes)\n assertScopes({ ...connection, grantedScopes: capability.scopes }, action.requiredScopes)\n const fullRequest: IntegrationActionRequest = { ...request, connectionId: connection.id }\n const proceed = () => Promise.resolve(provider.invokeAction(connection, fullRequest))\n if (this.guard) {\n return this.guard.invokeAction({ connection, request: fullRequest, action }, proceed)\n }\n return proceed()\n }\n\n async subscribeTrigger(connectionId: string, trigger: string, targetUrl?: string): Promise<IntegrationTriggerSubscription> {\n const connection = await this.requireConnection(connectionId)\n this.assertConnectionActive(connection)\n const provider = this.requireProvider(connection.providerId)\n const connector = await this.requireConnector(provider, connection.connectorId)\n const spec = connector.triggers?.find((candidate) => candidate.id === trigger)\n if (!spec) throw new IntegrationError(`Trigger ${trigger} is not defined by connector ${connector.id}.`, 'action_not_found')\n assertScopes(connection, spec.requiredScopes)\n if (!provider.subscribeTrigger) {\n throw new IntegrationError(`Provider ${provider.id} does not support triggers.`, 'auth_not_supported')\n }\n return provider.subscribeTrigger(connection, trigger, targetUrl)\n }\n\n private requireProvider(providerId: string): IntegrationProvider {\n const provider = this.providers.get(providerId)\n if (!provider) throw new IntegrationError(`Provider ${providerId} not found.`, 'provider_not_found')\n return provider\n }\n\n private async requireConnector(provider: IntegrationProvider, connectorId: string): Promise<IntegrationConnector> {\n const connector = (await provider.listConnectors()).find((candidate) => candidate.id === connectorId)\n if (!connector) throw new IntegrationError(`Connector ${connectorId} not found.`, 'connector_not_found')\n return connector\n }\n\n private async requireConnection(connectionId: string): Promise<IntegrationConnection> {\n const connection = await this.store.get(connectionId)\n if (!connection) throw new IntegrationError(`Connection ${connectionId} not found.`, 'connection_not_found')\n return connection\n }\n\n private assertConnectionActive(connection: IntegrationConnection): void {\n if (connection.status !== 'active') {\n throw new IntegrationError(`Connection ${connection.id} is ${connection.status}.`, 'connection_not_active')\n }\n if (connection.expiresAt && Date.parse(connection.expiresAt) <= this.now().getTime()) {\n throw new IntegrationError(`Connection ${connection.id} is expired.`, 'connection_not_active')\n }\n }\n}\n\nexport function sanitizeConnection(connection: IntegrationConnection): Record<string, unknown> {\n return {\n id: connection.id,\n owner: connection.owner,\n providerId: connection.providerId,\n connectorId: connection.connectorId,\n status: connection.status,\n grantedScopes: connection.grantedScopes,\n account: connection.account,\n hasSecretRef: Boolean(connection.secretRef),\n createdAt: connection.createdAt,\n updatedAt: connection.updatedAt,\n expiresAt: connection.expiresAt,\n lastUsedAt: connection.lastUsedAt,\n }\n}\n\nexport function createMockIntegrationProvider(options: {\n id?: string\n connectors?: IntegrationConnector[]\n onInvoke?: (connection: IntegrationConnection, request: IntegrationActionRequest) => IntegrationActionResult | Promise<IntegrationActionResult>\n} = {}): IntegrationProvider {\n const providerId = options.id ?? 'mock'\n const connectors = options.connectors ?? [{\n id: 'gmail',\n providerId,\n title: 'Gmail',\n category: 'email',\n auth: 'oauth2',\n scopes: ['email.read', 'email.write'],\n actions: [\n { id: 'messages.search', title: 'Search messages', risk: 'read', requiredScopes: ['email.read'], dataClass: 'private' },\n { id: 'drafts.create', title: 'Create draft', risk: 'write', requiredScopes: ['email.write'], dataClass: 'private', approvalRequired: true },\n ],\n triggers: [\n { id: 'message.received', title: 'Message received', requiredScopes: ['email.read'], dataClass: 'private' },\n ],\n }]\n return {\n id: providerId,\n kind: 'custom',\n listConnectors: () => connectors,\n startAuth: (request) => ({\n providerId,\n connectorId: request.connectorId,\n authUrl: `https://auth.example.test/${request.connectorId}?state=${encodeURIComponent(request.state ?? 'state')}`,\n state: request.state ?? 'state',\n }),\n completeAuth: (request) => ({\n id: `conn_${request.connectorId}_${request.owner.id}`,\n owner: request.owner,\n providerId,\n connectorId: request.connectorId,\n status: 'active',\n grantedScopes: connectors.find((connector) => connector.id === request.connectorId)?.scopes ?? [],\n secretRef: { provider: providerId, id: `secret_${request.owner.id}` },\n createdAt: new Date(0).toISOString(),\n updatedAt: new Date(0).toISOString(),\n }),\n invokeAction: async (connection, request) => options.onInvoke?.(connection, request) ?? ({\n ok: true,\n action: request.action,\n output: { echo: request.input ?? null },\n }),\n subscribeTrigger: (connection, trigger, targetUrl) => ({\n id: `sub_${connection.id}_${trigger}`,\n connectionId: connection.id,\n trigger,\n targetUrl,\n status: 'active',\n createdAt: new Date(0).toISOString(),\n }),\n }\n}\n\nexport function createHttpIntegrationProvider(options: HttpIntegrationProviderOptions): IntegrationProvider {\n const fetcher = options.fetchImpl ?? fetch\n const baseUrl = options.baseUrl.replace(/\\/$/, '')\n return {\n id: options.id,\n kind: options.kind ?? 'custom',\n listConnectors: () => options.connectors,\n async startAuth(request) {\n const response = await postJson<StartAuthResult>(fetcher, `${baseUrl}/auth/start`, request, options.bearer)\n return response\n },\n async completeAuth(request) {\n const response = await postJson<IntegrationConnection>(fetcher, `${baseUrl}/auth/complete`, request, options.bearer)\n return response\n },\n async invokeAction(connection, request) {\n return postJson<IntegrationActionResult>(fetcher, `${baseUrl}/actions/invoke`, {\n connection,\n request,\n }, options.bearer)\n },\n async subscribeTrigger(connection, trigger, targetUrl) {\n return postJson<IntegrationTriggerSubscription>(fetcher, `${baseUrl}/triggers/subscribe`, {\n connection,\n trigger,\n targetUrl,\n }, options.bearer)\n },\n async unsubscribeTrigger(subscriptionId) {\n await postJson(fetcher, `${baseUrl}/triggers/unsubscribe`, { subscriptionId }, options.bearer)\n },\n async normalizeTriggerEvent(raw) {\n return postJson<IntegrationTriggerEvent>(fetcher, `${baseUrl}/triggers/normalize`, { raw }, options.bearer)\n },\n }\n}\n\nexport function signCapability(capability: IntegrationCapability, secret: string): string {\n const payload = base64UrlEncode(JSON.stringify(capability))\n const signature = hmac(payload, secret)\n return `${payload}.${signature}`\n}\n\nexport function verifyCapabilityToken(token: string, secret: string): IntegrationCapability {\n const [payload, signature] = token.split('.')\n if (!payload || !signature) throw new IntegrationError('Malformed integration capability.', 'capability_invalid')\n const expected = hmac(payload, secret)\n if (!constantTimeEqual(signature, expected)) throw new IntegrationError('Invalid integration capability signature.', 'capability_invalid')\n let parsed: IntegrationCapability\n try {\n parsed = JSON.parse(base64UrlDecode(payload)) as IntegrationCapability\n } catch {\n throw new IntegrationError('Invalid integration capability payload.', 'capability_invalid')\n }\n if (!parsed.id || !parsed.connectionId || !Array.isArray(parsed.scopes) || !Array.isArray(parsed.allowedActions)) {\n throw new IntegrationError('Invalid integration capability payload.', 'capability_invalid')\n }\n return parsed\n}\n\nasync function postJson<T = unknown>(\n fetcher: typeof fetch,\n url: string,\n body: unknown,\n bearer?: string,\n): Promise<T> {\n const response = await fetcher(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(bearer ? { Authorization: `Bearer ${bearer}` } : {}),\n },\n body: JSON.stringify(body),\n })\n if (!response.ok) throw new IntegrationError(`Integration provider returned HTTP ${response.status}.`, 'provider_not_found')\n return response.json() as Promise<T>\n}\n\nfunction assertScopes(connection: Pick<IntegrationConnection, 'grantedScopes'>, requiredScopes: string[]): void {\n const missing = requiredScopes.filter((scope) => !connection.grantedScopes.includes(scope))\n if (missing.length > 0) throw new IntegrationError(`Missing integration scopes: ${missing.join(', ')}`, 'scope_denied')\n}\n\nfunction hmac(payload: string, secret: string): string {\n return createHmac('sha256', secret).update(payload).digest('base64url')\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n const left = Buffer.from(a)\n const right = Buffer.from(b)\n return left.length === right.length && timingSafeEqual(left, right)\n}\n\nfunction base64UrlEncode(value: string): string {\n return Buffer.from(value, 'utf8').toString('base64url')\n}\n\nfunction base64UrlDecode(value: string): string {\n return Buffer.from(value, 'base64url').toString('utf8')\n}\n\nfunction unique<T>(values: T[]): T[] {\n return [...new Set(values)]\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/connectors/types.ts","../src/connectors/oauth.ts","../src/connectors/webhooks.ts"],"sourcesContent":["import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto'\n\nexport type IntegrationProviderKind =\n | 'first_party'\n | 'nango'\n | 'pipedream'\n | 'zapier'\n | 'activepieces'\n | 'executor'\n | 'custom'\n\nexport type IntegrationConnectorCategory =\n | 'email'\n | 'calendar'\n | 'chat'\n | 'crm'\n | 'storage'\n | 'docs'\n | 'database'\n | 'webhook'\n | 'workflow'\n | 'internal'\n | 'other'\n\nexport type IntegrationActionRisk = 'read' | 'write' | 'destructive'\nexport type IntegrationDataClass = 'public' | 'internal' | 'private' | 'sensitive' | 'secret'\n\nexport interface IntegrationActor {\n type: 'user' | 'team' | 'agent' | 'sandbox' | 'system'\n id: string\n}\n\nexport interface IntegrationConnectorAction {\n id: string\n title: string\n risk: IntegrationActionRisk\n requiredScopes: string[]\n dataClass: IntegrationDataClass\n description?: string\n approvalRequired?: boolean\n inputSchema?: unknown\n outputSchema?: unknown\n}\n\nexport interface IntegrationConnectorTrigger {\n id: string\n title: string\n requiredScopes: string[]\n dataClass: IntegrationDataClass\n description?: string\n payloadSchema?: unknown\n}\n\nexport interface IntegrationConnector {\n id: string\n providerId: string\n title: string\n category: IntegrationConnectorCategory\n auth: 'oauth2' | 'api_key' | 'none' | 'custom'\n scopes: string[]\n actions: IntegrationConnectorAction[]\n triggers?: IntegrationConnectorTrigger[]\n metadata?: Record<string, unknown>\n}\n\nexport interface SecretRef {\n provider: string\n id: string\n label?: string\n}\n\nexport interface IntegrationConnection {\n id: string\n owner: IntegrationActor\n providerId: string\n connectorId: string\n status: 'pending' | 'active' | 'expired' | 'revoked' | 'error'\n grantedScopes: string[]\n account?: {\n id?: string\n email?: string\n displayName?: string\n }\n secretRef?: SecretRef\n createdAt: string\n updatedAt: string\n expiresAt?: string\n lastUsedAt?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface StartAuthRequest {\n connectorId: string\n owner: IntegrationActor\n requestedScopes: string[]\n redirectUri: string\n state?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface StartAuthResult {\n providerId: string\n connectorId: string\n authUrl: string\n state: string\n expiresAt?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface CompleteAuthRequest {\n connectorId: string\n owner: IntegrationActor\n code?: string\n state: string\n redirectUri: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationActionRequest {\n connectionId: string\n action: string\n input?: unknown\n idempotencyKey?: string\n dryRun?: boolean\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationActionResult<T = unknown> {\n ok: boolean\n action: string\n output?: T\n externalId?: string\n warnings?: string[]\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationTriggerSubscription {\n id: string\n connectionId: string\n trigger: string\n targetUrl?: string\n status: 'active' | 'paused' | 'error'\n createdAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationTriggerEvent<T = unknown> {\n id: string\n providerId: string\n connectorId: string\n connectionId: string\n trigger: string\n occurredAt: string\n payload: T\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationProvider {\n id: string\n kind: IntegrationProviderKind\n listConnectors(): Promise<IntegrationConnector[]> | IntegrationConnector[]\n startAuth?(request: StartAuthRequest): Promise<StartAuthResult> | StartAuthResult\n completeAuth?(request: CompleteAuthRequest): Promise<IntegrationConnection> | IntegrationConnection\n invokeAction(connection: IntegrationConnection, request: IntegrationActionRequest): Promise<IntegrationActionResult> | IntegrationActionResult\n subscribeTrigger?(connection: IntegrationConnection, trigger: string, targetUrl?: string): Promise<IntegrationTriggerSubscription> | IntegrationTriggerSubscription\n unsubscribeTrigger?(subscriptionId: string): Promise<void> | void\n normalizeTriggerEvent?(raw: unknown): Promise<IntegrationTriggerEvent> | IntegrationTriggerEvent\n}\n\nexport interface IntegrationConnectionStore {\n get(connectionId: string): Promise<IntegrationConnection | undefined> | IntegrationConnection | undefined\n put(connection: IntegrationConnection): Promise<void> | void\n listByOwner(owner: IntegrationActor): Promise<IntegrationConnection[]> | IntegrationConnection[]\n delete?(connectionId: string): Promise<void> | void\n}\n\nexport interface IssueCapabilityRequest {\n subject: IntegrationActor\n connectionId: string\n scopes: string[]\n allowedActions: string[]\n ttlMs: number\n metadata?: Record<string, unknown>\n}\n\nexport interface IntegrationCapability {\n id: string\n subject: IntegrationActor\n connectionId: string\n scopes: string[]\n allowedActions: string[]\n issuedAt: string\n expiresAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface IssuedIntegrationCapability {\n capability: IntegrationCapability\n token: string\n}\n\n/**\n * Wraps every action invocation with cross-cutting discipline (idempotency,\n * conflict detection, rate-limiting, audit logging). Optional. When set on\n * the hub, runs BEFORE provider.invokeAction; can short-circuit (return a\n * result directly) or pass through (call `proceed()` to invoke the provider).\n *\n * Why this hook exists: production deployments need conflict-resolution\n * guarantees that span every provider — first-party, Nango, Composio,\n * webhook receivers — and providers shouldn't re-implement them. The\n * canonical implementation is a \"MutationGuard\" that:\n * 1. Short-circuits on a known idempotency key (returns recorded response).\n * 2. Refuses same-key-different-args (drift detection).\n * 3. Wraps `proceed()` and audit-logs the outcome.\n * 4. Translates upstream conflict signals into a structured result with\n * alternatives the agent can act on.\n *\n * Implementations live in consumers (every product has different\n * persistence + telemetry needs); this interface is the contract.\n */\nexport interface IntegrationActionGuard {\n /** Wrap an invokeAction call. Implementations MUST call `proceed()` to\n * invoke the underlying provider unless they're returning a cached or\n * short-circuited result.\n *\n * @param ctx connection + request the hub is about to dispatch\n * @param proceed call to invoke the wrapped provider; returns the\n * underlying IntegrationActionResult\n * @returns the result the hub should return to the caller\n */\n invokeAction(\n ctx: IntegrationGuardContext,\n proceed: () => Promise<IntegrationActionResult>,\n ): Promise<IntegrationActionResult>\n}\n\nexport interface IntegrationGuardContext {\n connection: IntegrationConnection\n request: IntegrationActionRequest\n /** The action descriptor from the connector manifest, if discovered. */\n action?: IntegrationConnectorAction\n}\n\nexport interface IntegrationHubOptions {\n providers: IntegrationProvider[]\n store: IntegrationConnectionStore\n capabilitySecret: string\n /** Optional cross-cutting guard. If provided, every invokeAction call\n * passes through it before reaching the provider. See {@link IntegrationActionGuard}. */\n guard?: IntegrationActionGuard\n now?: () => Date\n}\n\nexport interface HttpIntegrationProviderOptions {\n id: string\n kind?: IntegrationProviderKind\n connectors: IntegrationConnector[]\n baseUrl: string\n bearer?: string\n fetchImpl?: typeof fetch\n}\n\nexport interface InvokeWithCapabilityRequest extends Omit<IntegrationActionRequest, 'connectionId'> {\n connectionId?: never\n}\n\nexport class IntegrationError extends Error {\n constructor(\n message: string,\n readonly code:\n | 'provider_not_found'\n | 'connector_not_found'\n | 'connection_not_found'\n | 'connection_not_active'\n | 'auth_not_supported'\n | 'capability_invalid'\n | 'capability_expired'\n | 'scope_denied'\n | 'action_denied'\n | 'action_not_found',\n ) {\n super(message)\n this.name = 'IntegrationError'\n }\n}\n\nexport class InMemoryConnectionStore implements IntegrationConnectionStore {\n private readonly connections = new Map<string, IntegrationConnection>()\n\n get(connectionId: string): IntegrationConnection | undefined {\n return this.connections.get(connectionId)\n }\n\n put(connection: IntegrationConnection): void {\n this.connections.set(connection.id, connection)\n }\n\n listByOwner(owner: IntegrationActor): IntegrationConnection[] {\n return [...this.connections.values()].filter((connection) =>\n connection.owner.type === owner.type && connection.owner.id === owner.id,\n )\n }\n\n delete(connectionId: string): void {\n this.connections.delete(connectionId)\n }\n}\n\nexport class IntegrationHub {\n private readonly providers = new Map<string, IntegrationProvider>()\n private readonly store: IntegrationConnectionStore\n private readonly capabilitySecret: string\n private readonly guard: IntegrationActionGuard | undefined\n private readonly now: () => Date\n\n constructor(options: IntegrationHubOptions) {\n if (!options.capabilitySecret) {\n throw new IntegrationError('capabilitySecret is required.', 'capability_invalid')\n }\n for (const provider of options.providers) this.providers.set(provider.id, provider)\n this.store = options.store\n this.capabilitySecret = options.capabilitySecret\n this.guard = options.guard\n this.now = options.now ?? (() => new Date())\n }\n\n async listConnectors(): Promise<IntegrationConnector[]> {\n const catalogs = await Promise.all([...this.providers.values()].map((provider) => provider.listConnectors()))\n return catalogs.flat()\n }\n\n async startAuth(providerId: string, request: StartAuthRequest): Promise<StartAuthResult> {\n const provider = this.requireProvider(providerId)\n if (!provider.startAuth) throw new IntegrationError(`Provider ${providerId} does not support auth start.`, 'auth_not_supported')\n await this.requireConnector(provider, request.connectorId)\n return provider.startAuth(request)\n }\n\n async completeAuth(providerId: string, request: CompleteAuthRequest): Promise<IntegrationConnection> {\n const provider = this.requireProvider(providerId)\n if (!provider.completeAuth) throw new IntegrationError(`Provider ${providerId} does not support auth completion.`, 'auth_not_supported')\n const connection = await provider.completeAuth(request)\n await this.store.put(connection)\n return connection\n }\n\n async upsertConnection(connection: IntegrationConnection): Promise<IntegrationConnection> {\n await this.store.put(connection)\n return connection\n }\n\n async issueCapability(request: IssueCapabilityRequest): Promise<IssuedIntegrationCapability> {\n const connection = await this.requireConnection(request.connectionId)\n this.assertConnectionActive(connection)\n assertScopes(connection, request.scopes)\n const now = this.now()\n const capability: IntegrationCapability = {\n id: `cap_${randomUUID()}`,\n subject: request.subject,\n connectionId: request.connectionId,\n scopes: unique(request.scopes),\n allowedActions: unique(request.allowedActions),\n issuedAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + request.ttlMs).toISOString(),\n metadata: request.metadata,\n }\n return { capability, token: signCapability(capability, this.capabilitySecret) }\n }\n\n verifyCapability(token: string): IntegrationCapability {\n const capability = verifyCapabilityToken(token, this.capabilitySecret)\n if (Date.parse(capability.expiresAt) <= this.now().getTime()) {\n throw new IntegrationError('Integration capability expired.', 'capability_expired')\n }\n return capability\n }\n\n async invokeWithCapability(token: string, request: InvokeWithCapabilityRequest): Promise<IntegrationActionResult> {\n const capability = this.verifyCapability(token)\n if (!capability.allowedActions.includes(request.action)) {\n throw new IntegrationError(`Capability does not allow action ${request.action}.`, 'action_denied')\n }\n const connection = await this.requireConnection(capability.connectionId)\n this.assertConnectionActive(connection)\n const provider = this.requireProvider(connection.providerId)\n const connector = await this.requireConnector(provider, connection.connectorId)\n const action = connector.actions.find((candidate) => candidate.id === request.action)\n if (!action) throw new IntegrationError(`Action ${request.action} is not defined by connector ${connector.id}.`, 'action_not_found')\n assertScopes(connection, action.requiredScopes)\n assertScopes({ ...connection, grantedScopes: capability.scopes }, action.requiredScopes)\n const fullRequest: IntegrationActionRequest = { ...request, connectionId: connection.id }\n const proceed = () => Promise.resolve(provider.invokeAction(connection, fullRequest))\n if (this.guard) {\n return this.guard.invokeAction({ connection, request: fullRequest, action }, proceed)\n }\n return proceed()\n }\n\n async subscribeTrigger(connectionId: string, trigger: string, targetUrl?: string): Promise<IntegrationTriggerSubscription> {\n const connection = await this.requireConnection(connectionId)\n this.assertConnectionActive(connection)\n const provider = this.requireProvider(connection.providerId)\n const connector = await this.requireConnector(provider, connection.connectorId)\n const spec = connector.triggers?.find((candidate) => candidate.id === trigger)\n if (!spec) throw new IntegrationError(`Trigger ${trigger} is not defined by connector ${connector.id}.`, 'action_not_found')\n assertScopes(connection, spec.requiredScopes)\n if (!provider.subscribeTrigger) {\n throw new IntegrationError(`Provider ${provider.id} does not support triggers.`, 'auth_not_supported')\n }\n return provider.subscribeTrigger(connection, trigger, targetUrl)\n }\n\n private requireProvider(providerId: string): IntegrationProvider {\n const provider = this.providers.get(providerId)\n if (!provider) throw new IntegrationError(`Provider ${providerId} not found.`, 'provider_not_found')\n return provider\n }\n\n private async requireConnector(provider: IntegrationProvider, connectorId: string): Promise<IntegrationConnector> {\n const connector = (await provider.listConnectors()).find((candidate) => candidate.id === connectorId)\n if (!connector) throw new IntegrationError(`Connector ${connectorId} not found.`, 'connector_not_found')\n return connector\n }\n\n private async requireConnection(connectionId: string): Promise<IntegrationConnection> {\n const connection = await this.store.get(connectionId)\n if (!connection) throw new IntegrationError(`Connection ${connectionId} not found.`, 'connection_not_found')\n return connection\n }\n\n private assertConnectionActive(connection: IntegrationConnection): void {\n if (connection.status !== 'active') {\n throw new IntegrationError(`Connection ${connection.id} is ${connection.status}.`, 'connection_not_active')\n }\n if (connection.expiresAt && Date.parse(connection.expiresAt) <= this.now().getTime()) {\n throw new IntegrationError(`Connection ${connection.id} is expired.`, 'connection_not_active')\n }\n }\n}\n\nexport function sanitizeConnection(connection: IntegrationConnection): Record<string, unknown> {\n return {\n id: connection.id,\n owner: connection.owner,\n providerId: connection.providerId,\n connectorId: connection.connectorId,\n status: connection.status,\n grantedScopes: connection.grantedScopes,\n account: connection.account,\n hasSecretRef: Boolean(connection.secretRef),\n createdAt: connection.createdAt,\n updatedAt: connection.updatedAt,\n expiresAt: connection.expiresAt,\n lastUsedAt: connection.lastUsedAt,\n }\n}\n\nexport function createMockIntegrationProvider(options: {\n id?: string\n connectors?: IntegrationConnector[]\n onInvoke?: (connection: IntegrationConnection, request: IntegrationActionRequest) => IntegrationActionResult | Promise<IntegrationActionResult>\n} = {}): IntegrationProvider {\n const providerId = options.id ?? 'mock'\n const connectors = options.connectors ?? [{\n id: 'gmail',\n providerId,\n title: 'Gmail',\n category: 'email',\n auth: 'oauth2',\n scopes: ['email.read', 'email.write'],\n actions: [\n { id: 'messages.search', title: 'Search messages', risk: 'read', requiredScopes: ['email.read'], dataClass: 'private' },\n { id: 'drafts.create', title: 'Create draft', risk: 'write', requiredScopes: ['email.write'], dataClass: 'private', approvalRequired: true },\n ],\n triggers: [\n { id: 'message.received', title: 'Message received', requiredScopes: ['email.read'], dataClass: 'private' },\n ],\n }]\n return {\n id: providerId,\n kind: 'custom',\n listConnectors: () => connectors,\n startAuth: (request) => ({\n providerId,\n connectorId: request.connectorId,\n authUrl: `https://auth.example.test/${request.connectorId}?state=${encodeURIComponent(request.state ?? 'state')}`,\n state: request.state ?? 'state',\n }),\n completeAuth: (request) => ({\n id: `conn_${request.connectorId}_${request.owner.id}`,\n owner: request.owner,\n providerId,\n connectorId: request.connectorId,\n status: 'active',\n grantedScopes: connectors.find((connector) => connector.id === request.connectorId)?.scopes ?? [],\n secretRef: { provider: providerId, id: `secret_${request.owner.id}` },\n createdAt: new Date(0).toISOString(),\n updatedAt: new Date(0).toISOString(),\n }),\n invokeAction: async (connection, request) => options.onInvoke?.(connection, request) ?? ({\n ok: true,\n action: request.action,\n output: { echo: request.input ?? null },\n }),\n subscribeTrigger: (connection, trigger, targetUrl) => ({\n id: `sub_${connection.id}_${trigger}`,\n connectionId: connection.id,\n trigger,\n targetUrl,\n status: 'active',\n createdAt: new Date(0).toISOString(),\n }),\n }\n}\n\nexport function createHttpIntegrationProvider(options: HttpIntegrationProviderOptions): IntegrationProvider {\n const fetcher = options.fetchImpl ?? fetch\n const baseUrl = options.baseUrl.replace(/\\/$/, '')\n return {\n id: options.id,\n kind: options.kind ?? 'custom',\n listConnectors: () => options.connectors,\n async startAuth(request) {\n const response = await postJson<StartAuthResult>(fetcher, `${baseUrl}/auth/start`, request, options.bearer)\n return response\n },\n async completeAuth(request) {\n const response = await postJson<IntegrationConnection>(fetcher, `${baseUrl}/auth/complete`, request, options.bearer)\n return response\n },\n async invokeAction(connection, request) {\n return postJson<IntegrationActionResult>(fetcher, `${baseUrl}/actions/invoke`, {\n connection,\n request,\n }, options.bearer)\n },\n async subscribeTrigger(connection, trigger, targetUrl) {\n return postJson<IntegrationTriggerSubscription>(fetcher, `${baseUrl}/triggers/subscribe`, {\n connection,\n trigger,\n targetUrl,\n }, options.bearer)\n },\n async unsubscribeTrigger(subscriptionId) {\n await postJson(fetcher, `${baseUrl}/triggers/unsubscribe`, { subscriptionId }, options.bearer)\n },\n async normalizeTriggerEvent(raw) {\n return postJson<IntegrationTriggerEvent>(fetcher, `${baseUrl}/triggers/normalize`, { raw }, options.bearer)\n },\n }\n}\n\nexport function signCapability(capability: IntegrationCapability, secret: string): string {\n const payload = base64UrlEncode(JSON.stringify(capability))\n const signature = hmac(payload, secret)\n return `${payload}.${signature}`\n}\n\nexport function verifyCapabilityToken(token: string, secret: string): IntegrationCapability {\n const [payload, signature] = token.split('.')\n if (!payload || !signature) throw new IntegrationError('Malformed integration capability.', 'capability_invalid')\n const expected = hmac(payload, secret)\n if (!constantTimeEqual(signature, expected)) throw new IntegrationError('Invalid integration capability signature.', 'capability_invalid')\n let parsed: IntegrationCapability\n try {\n parsed = JSON.parse(base64UrlDecode(payload)) as IntegrationCapability\n } catch {\n throw new IntegrationError('Invalid integration capability payload.', 'capability_invalid')\n }\n if (!parsed.id || !parsed.connectionId || !Array.isArray(parsed.scopes) || !Array.isArray(parsed.allowedActions)) {\n throw new IntegrationError('Invalid integration capability payload.', 'capability_invalid')\n }\n return parsed\n}\n\nasync function postJson<T = unknown>(\n fetcher: typeof fetch,\n url: string,\n body: unknown,\n bearer?: string,\n): Promise<T> {\n const response = await fetcher(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(bearer ? { Authorization: `Bearer ${bearer}` } : {}),\n },\n body: JSON.stringify(body),\n })\n if (!response.ok) throw new IntegrationError(`Integration provider returned HTTP ${response.status}.`, 'provider_not_found')\n return response.json() as Promise<T>\n}\n\nfunction assertScopes(connection: Pick<IntegrationConnection, 'grantedScopes'>, requiredScopes: string[]): void {\n const missing = requiredScopes.filter((scope) => !connection.grantedScopes.includes(scope))\n if (missing.length > 0) throw new IntegrationError(`Missing integration scopes: ${missing.join(', ')}`, 'scope_denied')\n}\n\nfunction hmac(payload: string, secret: string): string {\n return createHmac('sha256', secret).update(payload).digest('base64url')\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n const left = Buffer.from(a)\n const right = Buffer.from(b)\n return left.length === right.length && timingSafeEqual(left, right)\n}\n\nfunction base64UrlEncode(value: string): string {\n return Buffer.from(value, 'utf8').toString('base64url')\n}\n\nfunction base64UrlDecode(value: string): string {\n return Buffer.from(value, 'base64url').toString('utf8')\n}\n\nfunction unique<T>(values: T[]): T[] {\n return [...new Set(values)]\n}\n\n// ─── Connectors namespace ───────────────────────────────────────────────\n//\n// Lower-level adapter primitives — the contract a concrete first-party\n// integration (Google Calendar, HubSpot, Stripe, ...) implements. The\n// hub-side `IntegrationProvider` interface is the *catalog* facade above\n// these; one provider can wrap many connectors. See `src/connectors/types.ts`\n// for the layering details.\nexport * from './connectors/index.js'\n","/**\n * Connector primitives — the contract a concrete first-party integration\n * (Google Calendar, HubSpot, Stripe, ...) implements. Lower level than the\n * hub-side `IntegrationProvider` interface from `../index.ts`: a single\n * `IntegrationProvider` typically wraps several connectors (e.g., a\n * \"first-party\" provider that lists all your shipped connectors as a\n * single catalog).\n *\n * Layering:\n *\n * IntegrationHub — vendor-neutral facade (../index.ts)\n * ↓\n * IntegrationProvider — one per vendor (Nango/Composio/first-party)\n * ↓\n * ConnectorAdapter (this file) — one per integration (Google Calendar, ...)\n * ↓\n * upstream HTTP API — vendor SDK / fetch / OAuth\n *\n * Three load-bearing decisions encoded here:\n *\n * 1. Capabilities are typed (`read` vs `mutation`). Every mutation MUST\n * declare a CAS strategy. Conflict resolution is the SDK's job, not the\n * connector's. `validateConnectorManifest()` rejects unsafe manifests\n * before a connector is registered.\n *\n * 2. ConsistencyModel pins what the rest of the system can assume:\n * authoritative → the source IS the truth (Calendar, payments)\n * cache → we mirror with TTL and may serve stale (price list)\n * advisory → informational only (FAQ doc)\n * Agent planners can (and should) refuse to promise outcomes based on\n * `cache`/`advisory` data without a live `authoritative` confirmation.\n *\n * 3. Capabilities surface to the calling agent's tool registry by\n * transformation, not by hand-wiring. Adding a connector automatically\n * expands the agent's toolbelt for that specific user without touching\n * the prompt or runner.\n */\n\n/** Minimal JSON-schema shape used for capability arg validation. We\n * intentionally don't pull `@types/json-schema` — most consumers already\n * declare parameters as `Record<string, unknown>` and the\n * shape is whatever the LLM SDK's structured-output expects. Keep the\n * contract loose at the boundary; tighten via runtime zod where needed. */\nexport type CapabilityParameterSchema = Record<string, unknown>\n\n/** What the rest of the system is allowed to assume about freshness. */\nexport type ConsistencyModel = 'authoritative' | 'cache' | 'advisory'\n\n/** Capability classes. `read` is safe to retry; `mutation` must go through\n * MutationGuard (CAS + idempotency). `subscribe` is reserved for future\n * push-driven sources (webhook callbacks) and is not yet wired. */\nexport type CapabilityClass = 'read' | 'mutation' | 'subscribe'\n\n/** Compare-and-swap strategy a mutation uses to detect conflicts. */\nexport type CASStrategy =\n /** Upstream returns an etag/sequence on read, accepts If-Match on write\n * (Google Calendar, GitHub, GDocs revision_id). The connector returns\n * 412 / Precondition Failed on conflict; the SDK maps to ResourceContention. */\n | 'etag-if-match'\n /** Upstream guarantees exactly-once-per-key (Stripe, idempotent webhooks).\n * The SDK passes the idempotency key through; no etag check. */\n | 'native-idempotency'\n /** No upstream concurrency control. Connector MUST do read-then-write\n * and verify nothing changed in-between (best-effort). Suitable only\n * for low-contention single-user resources; rejected for any\n * consistencyModel='authoritative' write that may race. */\n | 'optimistic-read-verify'\n /** Source is not contended (e.g. logging, telemetry). Mutations are\n * fire-and-forget. Marks the capability as not eligible for\n * authoritative writes. */\n | 'none'\n\nexport interface CapabilityRead {\n name: string\n class: 'read'\n description: string\n /** JSON-schema for the tool args the agent passes when invoking. */\n parameters: CapabilityParameterSchema\n /** Optional: declare which scopes (per the connector manifest) this\n * capability requires. The capability is hidden from the agent's\n * tool registry if the user's grant didn't include them. */\n requiredScopes?: string[]\n}\n\nexport interface CapabilityMutation {\n name: string\n class: 'mutation'\n description: string\n parameters: CapabilityParameterSchema\n /** Mandatory: how does the connector guarantee at-most-once + conflict-detect? */\n cas: CASStrategy\n /** True for capabilities that affect resources outside the calling user\n * (e.g. booking against a shared calendar, charging a card). The agent's\n * planner treats these specially: requires explicit caller confirmation\n * before the call. */\n externalEffect: boolean\n requiredScopes?: string[]\n}\n\nexport type Capability = CapabilityRead | CapabilityMutation\n\n/** OAuth2 scope catalog the user has granted us, plus arbitrary metadata\n * the connector pinned at connect-time (calendar id, sheet id, webhook\n * url, …). `metadata` MUST NOT contain secrets — those go in the\n * encrypted credentials envelope. */\nexport interface DataSourceMetadata {\n scopes: string[]\n [key: string]: unknown\n}\n\n/** A connected, authenticated, ready-to-call data source for a project.\n * Persistence shape mirrors the product's connection/source row but normalized — the\n * encrypted credentials envelope is decrypted at hand-out time and held\n * in memory only for the duration of the call. */\nexport interface ResolvedDataSource {\n id: string\n projectId: string\n publishedAgentId: string | null\n kind: string\n label: string\n consistencyModel: ConsistencyModel\n scopes: string[]\n metadata: Record<string, unknown>\n /** Unwrapped credentials handed to the connector at call-time. Never\n * persisted in this shape; never logged. */\n credentials: ConnectorCredentials\n status: 'active' | 'revoked' | 'error'\n}\n\n/** Discriminated union of credential shapes. Connectors that need new\n * shapes extend this union — `kind` is sealed via the tagged pattern so\n * TypeScript catches an exhaustiveness gap at compile time. */\nexport type ConnectorCredentials =\n | { kind: 'oauth2'; accessToken: string; refreshToken?: string; expiresAt?: number }\n | { kind: 'api-key'; apiKey: string }\n | { kind: 'hmac'; secret: string }\n | { kind: 'none' }\n\n/** Result of a read capability invocation. */\nexport interface CapabilityReadResult {\n /** Free-form payload — the connector's data shape. The agent receives\n * this as the tool result; planners consume it via JSON-shape contract\n * declared in the capability's `parameters` (output schema). */\n data: unknown\n /** Optional etag/sequence the caller can reuse for a subsequent CAS\n * mutation. */\n etag?: string\n /** When this read happened (UTC ms since epoch). */\n fetchedAt: number\n}\n\n/** Result of a mutation capability invocation. Either committed (with the\n * resulting etag/sequence so the caller can chain mutations), or\n * contended (the upstream rejected with a state mismatch — the agent\n * should re-read and retry, or surface alternatives to the caller). */\nexport type CapabilityMutationResult =\n | {\n status: 'committed'\n data: unknown\n etagAfter?: string\n committedAt: number\n /** True iff this commit was returned from the idempotency store\n * rather than executed against upstream. The caller can use this\n * to suppress confirmation messages on retry. */\n idempotentReplay: boolean\n }\n | {\n status: 'conflict'\n /** Best-effort alternative options the upstream surfaced (e.g.,\n * next-available calendar slots after a booking conflict). */\n alternatives: unknown[]\n /** The current authoritative state, if the connector could re-read\n * cheaply. */\n currentState?: unknown\n message: string\n }\n | {\n status: 'rate-limited'\n /** Wall-clock ms the caller should wait before retrying. The SDK\n * computes this from the bucket's refill schedule so the agent\n * doesn't have to guess. */\n retryAfterMs: number\n message: string\n }\n\n/** Inputs the SDK passes into the connector's executeRead / executeMutation. */\nexport interface ConnectorInvocation {\n source: ResolvedDataSource\n capabilityName: string\n args: Record<string, unknown>\n /** Idempotency key the caller (or the SDK's defaulting policy) supplied.\n * Always present at the connector boundary — the SDK manufactures one\n * if the agent didn't pass one. */\n idempotencyKey: string\n /** Optional caller-supplied etag the connector should send as If-Match. */\n expectedEtag?: string\n /** Product/session id (if any) for forensic logging. */\n callSessionId?: string\n}\n\n/** A single inbound event extracted from a push payload. The webhook\n * receiver persists one `InboundEvent` row per entry the connector returns. */\nexport interface InboundEvent {\n eventType: string\n providerEventId?: string\n payload: Record<string, unknown>\n}\n\n/** Adapter response from an inbound-webhook dispatch. The receiver persists\n * every `events[]` entry, then either honors the connector's `response`\n * override (Slack `url_verification` echo, provider-specific 2xx body) or\n * defaults to `{status: 200, body: {received: true, count: events.length}}`. */\nexport interface EventHandlerResult {\n events: InboundEvent[]\n /** Optional: how to respond to the provider. Stripe wants 200 within\n * 30s; Slack wants the challenge param echoed. */\n response?: { status: number; body: unknown; headers?: Record<string, string> }\n}\n\n/**\n * Connector adapter — one per integration kind. Stateless. The SDK holds\n * the persistence + crypto + mutation-guard concerns; the adapter only\n * knows how to talk to its upstream.\n */\nexport interface ConnectorAdapter {\n /** Manifest entry the registry uses to render UI + validate args. */\n manifest: ConnectorManifest\n /** Read invocation. Required when manifest.capabilities contains reads.\n * Should return whatever shape the capability declared\n * in its parameters output schema. */\n executeRead?(inv: ConnectorInvocation): Promise<CapabilityReadResult>\n /** Mutation invocation. Required when manifest.capabilities contains mutations.\n * Throws ResourceContention on a CAS miss; throws\n * any other Error for upstream failures. The MutationGuard wraps this\n * with idempotency-key short-circuit + audit logging — adapters do\n * NOT manage their own dedup. */\n executeMutation?(inv: ConnectorInvocation): Promise<CapabilityMutationResult>\n /** Inbound webhook signature verifier. Called BEFORE handleInboundEvent.\n * MUST use constant-time comparison (`crypto.timingSafeEqual`) for any\n * HMAC check. The receiver returns 401 on `valid=false` without invoking\n * handleInboundEvent. Optional: connectors that don't accept push events\n * omit this method and the receiver returns 405 for the kind. */\n verifySignature?(input: {\n rawBody: string\n headers: Record<string, string | string[] | undefined>\n source: ResolvedDataSource\n }): { valid: boolean; reason?: string }\n /** Inbound webhook dispatch. Called AFTER verifySignature passes. The\n * adapter parses the provider payload and emits zero-or-more\n * `InboundEvent` rows; the receiver persists them as one row each (modulo\n * the (dataSourceId, providerEventId) dedup unique). The optional\n * `response` overrides the receiver's default 200 (Slack `url_verification`\n * needs to echo the challenge in the body to pass Slack's app-config check). */\n handleInboundEvent?(input: {\n source: ResolvedDataSource\n rawBody: string\n headers: Record<string, string | string[] | undefined>\n }): Promise<EventHandlerResult>\n /** OAuth callback handler — exchanges the auth code for tokens, returns\n * the credentials envelope + scopes + metadata. Only present for\n * oauth2-style adapters. */\n exchangeOAuth?(input: {\n code: string\n state: string\n codeVerifier: string\n redirectUri: string\n }): Promise<{\n credentials: ConnectorCredentials\n scopes: string[]\n metadata: Record<string, unknown>\n }>\n /** Refresh access token. Only required for oauth2 adapters with\n * short-lived access tokens. */\n refreshToken?(input: ConnectorCredentials): Promise<ConnectorCredentials>\n /** Health check — invoked when the user clicks \"Test connection\" in the\n * UI. Should perform the cheapest possible read that proves the grant\n * is still valid. Returns `{ok: false, reason}` rather than throwing\n * for the common case (token expired, scope missing). */\n test(source: ResolvedDataSource): Promise<{ ok: true } | { ok: false; reason: string }>\n}\n\n/** Static manifest a connector module exports. Drives the UI catalog,\n * scope display, capability discovery for the agent's tool registry. */\nexport interface ConnectorManifest {\n /** Stable kind id used as the foreign key in DataSource.kind. */\n kind: string\n /** Human label shown in the UI catalog. */\n displayName: string\n /** One-paragraph description shown next to the connect button. */\n description: string\n /** Auth shape this connector requires. */\n auth: AuthSpec\n /** Capability catalog — the agent's tool registry derives ToolDefinition\n * entries from this list at request time. */\n capabilities: Capability[]\n /** ConsistencyModel default for this kind — overridable per DataSource\n * if a particular instance is special (e.g., a user marks a sheet as\n * `cache` because they refresh it nightly). */\n defaultConsistencyModel: ConsistencyModel\n /** Connector category for UI grouping. */\n category: 'calendar' | 'spreadsheet' | 'crm' | 'doc' | 'webhook' | 'storage' | 'comms' | 'commerce' | 'other'\n /** Optional icon URL or named icon. */\n icon?: string\n /** Optional per-kind rate-limit budget. The SDK enforces it inside\n * `executeGuardedMutation` and the read path of `/invoke`. Omit to\n * leave the connector unrestricted. */\n rateLimit?: RateLimitSpec\n}\n\n/** Token-bucket budget the SDK enforces against the connector's upstream.\n * We meter on OUR side rather than letting the upstream reject so a\n * chatty agent can't burn quota that's shared across customers (almost\n * every OAuth client is). */\nexport interface RateLimitSpec {\n /** Max requests per window. */\n requests: number\n /** Window in ms. */\n windowMs: number\n /** Whether to apply across all DataSources sharing the same OAuth\n * client (true; default), or per-DataSource (false). The former\n * matches how upstreams meter (per-app), so almost always pick true. */\n scope?: 'oauth-client' | 'data-source'\n}\n\nexport type AuthSpec =\n | {\n kind: 'oauth2'\n /** Authorization endpoint URL. */\n authorizationUrl: string\n /** Token endpoint URL. */\n tokenUrl: string\n /** Scopes requested in the authorization grant. The user UI shows\n * these so the customer knows what's being shared. */\n scopes: string[]\n /** Whether the connector supports incremental authorization (Google\n * does; many don't). */\n incremental?: boolean\n /** Env-var name holding the OAuth client_id. */\n clientIdEnv: string\n /** Env-var name holding the OAuth client_secret. */\n clientSecretEnv: string\n /** Optional extra params attached to the authorization URL (e.g.,\n * Google's `access_type=offline&prompt=consent` to obtain refresh\n * tokens). */\n extraAuthParams?: Record<string, string>\n }\n | {\n kind: 'api-key'\n /** UI hint shown when collecting the key. */\n hint: string\n }\n | { kind: 'hmac' }\n | { kind: 'none' }\n\n/** Thrown by `executeMutation` when upstream rejects on CAS — caught and\n * rewrapped by MutationGuard. */\nexport class ResourceContention extends Error {\n override readonly name = 'ResourceContention'\n constructor(\n message: string,\n public readonly alternatives: unknown[] = [],\n public readonly currentState?: unknown,\n ) {\n super(message)\n }\n}\n\n/** Thrown when the connector finds the user's grant has been revoked or\n * the access token is no longer valid AND refresh failed. Surfaces to\n * the UI as \"Reconnect required\". */\nexport class CredentialsExpired extends Error {\n override readonly name = 'CredentialsExpired'\n constructor(message: string, public readonly dataSourceId: string) {\n super(message)\n }\n}\n\nexport interface ConnectorManifestValidationIssue {\n path: string\n message: string\n}\n\nexport interface ConnectorManifestValidationResult {\n ok: boolean\n issues: ConnectorManifestValidationIssue[]\n}\n\n/** Validate the static connector manifest before a provider registers it.\n * This catches the expensive mistakes early: duplicate capability names,\n * mutation capabilities without CAS, authoritative fire-and-forget writes,\n * and invalid rate-limit specs. */\nexport function validateConnectorManifest(manifest: ConnectorManifest): ConnectorManifestValidationResult {\n const issues: ConnectorManifestValidationIssue[] = []\n if (!manifest.kind.trim()) issues.push({ path: 'kind', message: 'kind is required' })\n if (!manifest.displayName.trim()) issues.push({ path: 'displayName', message: 'displayName is required' })\n const seen = new Set<string>()\n for (const [index, capability] of manifest.capabilities.entries()) {\n const path = `capabilities[${index}]`\n if (!capability.name.trim()) issues.push({ path: `${path}.name`, message: 'capability name is required' })\n if (seen.has(capability.name)) issues.push({ path: `${path}.name`, message: `duplicate capability name: ${capability.name}` })\n seen.add(capability.name)\n if (capability.class === 'mutation') {\n if (!capability.cas) issues.push({ path: `${path}.cas`, message: 'mutation capability must declare a CAS strategy' })\n if (manifest.defaultConsistencyModel === 'authoritative' && capability.cas === 'none') {\n issues.push({ path: `${path}.cas`, message: 'authoritative mutations cannot use cas=\"none\"' })\n }\n }\n }\n if (manifest.rateLimit) {\n if (!Number.isFinite(manifest.rateLimit.requests) || manifest.rateLimit.requests <= 0) {\n issues.push({ path: 'rateLimit.requests', message: 'rateLimit.requests must be positive' })\n }\n if (!Number.isFinite(manifest.rateLimit.windowMs) || manifest.rateLimit.windowMs <= 0) {\n issues.push({ path: 'rateLimit.windowMs', message: 'rateLimit.windowMs must be positive' })\n }\n }\n return { ok: issues.length === 0, issues }\n}\n\nexport function assertValidConnectorManifest(manifest: ConnectorManifest): void {\n const result = validateConnectorManifest(manifest)\n if (!result.ok) {\n throw new Error(`Invalid connector manifest ${manifest.kind || '<unknown>'}: ${result.issues.map((issue) => `${issue.path}: ${issue.message}`).join('; ')}`)\n }\n}\n","/**\n * Generic OAuth2 helper used by every oauth-shaped connector (Google\n * Calendar, Sheets, Drive, HubSpot, Salesforce, Zoom, ...).\n *\n * Everything PKCE-aware. Opaque-state CSRF guard. Refresh-token aware.\n * No connector-specific logic lives here — adapters hand a `clientId`,\n * `clientSecret`, `tokenUrl`, optional `extraAuthParams` and the rest is\n * mechanical.\n *\n * State and code_verifier are kept in a short-TTL flow store keyed by the\n * opaque `state` we round-trip through the provider. The default store is\n * in-memory for local/dev and tests. Production deployments should inject a\n * durable store backed by KV/Redis/D1/etc. so callbacks can land on any worker.\n */\n\nimport { createHash, randomBytes } from 'crypto'\n\nexport interface PendingOAuthFlow {\n /** code_verifier for PKCE. */\n codeVerifier: string\n /** Opaque-state value also returned in the OAuth redirect. */\n state: string\n /** Project the user is connecting under. */\n projectId: string\n /** Connector kind (e.g. 'google-calendar'). */\n kind: string\n /** Operator-supplied label that becomes DataSource.label. */\n label: string\n /** When we drop the entry. */\n expiresAt: number\n /** The redirectUri we used in the start step — must match exactly on\n * the callback exchange. */\n redirectUri: string\n}\n\nconst PENDING_TTL_MS = 10 * 60 * 1000\n\nexport interface OAuthFlowStore {\n put(state: string, flow: PendingOAuthFlow): Promise<void> | void\n consume(state: string): Promise<PendingOAuthFlow | undefined> | PendingOAuthFlow | undefined\n sweep?(now: number): Promise<void> | void\n clear?(): Promise<void> | void\n}\n\nexport class InMemoryOAuthFlowStore implements OAuthFlowStore {\n private readonly pendingFlows = new Map<string, PendingOAuthFlow>()\n\n put(state: string, flow: PendingOAuthFlow): void {\n this.pendingFlows.set(state, flow)\n }\n\n consume(state: string): PendingOAuthFlow | undefined {\n const flow = this.pendingFlows.get(state)\n this.pendingFlows.delete(state)\n if (!flow || flow.expiresAt <= Date.now()) return undefined\n return flow\n }\n\n sweep(now: number): void {\n for (const [k, v] of this.pendingFlows) {\n if (v.expiresAt <= now) this.pendingFlows.delete(k)\n }\n }\n\n clear(): void {\n this.pendingFlows.clear()\n }\n}\n\nconst defaultFlowStore = new InMemoryOAuthFlowStore()\n\nexport interface StartOAuthInput {\n projectId: string\n kind: string\n label: string\n authorizationUrl: string\n scopes: string[]\n clientId: string\n redirectUri: string\n /** Optional extra query params; Google needs `access_type=offline` and\n * `prompt=consent` to issue refresh tokens reliably. */\n extraAuthParams?: Record<string, string>\n /** Optional flow store. Use a durable store in distributed production\n * runtimes; omitted means local in-memory storage. */\n store?: OAuthFlowStore\n /** Override clock for tests. */\n now?: number\n}\n\nexport interface StartOAuthOutput {\n /** URL the SPA should redirect the user to. */\n authorizationUrl: string\n /** State token — caller stashes this in localStorage to verify on\n * callback. */\n state: string\n}\n\n/** Build the authorization URL + state. SPA navigates the user there;\n * user consents; provider redirects back to redirectUri with `code` +\n * `state`. The caller's callback then invokes `consumePendingFlow`. */\nexport function startOAuthFlow(input: StartOAuthInput): StartOAuthOutput {\n const store = input.store ?? defaultFlowStore\n const now = input.now ?? Date.now()\n store.sweep?.(now)\n const codeVerifier = base64Url(randomBytes(48))\n const codeChallenge = base64Url(createHash('sha256').update(codeVerifier).digest())\n const state = base64Url(randomBytes(24))\n\n store.put(state, {\n codeVerifier,\n state,\n projectId: input.projectId,\n kind: input.kind,\n label: input.label,\n redirectUri: input.redirectUri,\n expiresAt: now + PENDING_TTL_MS,\n })\n\n const url = new URL(input.authorizationUrl)\n url.searchParams.set('response_type', 'code')\n url.searchParams.set('client_id', input.clientId)\n url.searchParams.set('redirect_uri', input.redirectUri)\n url.searchParams.set('scope', input.scopes.join(' '))\n url.searchParams.set('state', state)\n url.searchParams.set('code_challenge', codeChallenge)\n url.searchParams.set('code_challenge_method', 'S256')\n if (input.extraAuthParams) {\n for (const [k, v] of Object.entries(input.extraAuthParams)) {\n url.searchParams.set(k, v)\n }\n }\n return { authorizationUrl: url.toString(), state }\n}\n\n/** Look up + remove the pending flow record. Throws if state is unknown\n * or expired (CSRF guard / replay protection). */\nexport async function consumePendingFlow(state: string, store: OAuthFlowStore = defaultFlowStore): Promise<PendingOAuthFlow> {\n await store.sweep?.(Date.now())\n const flow = await store.consume(state)\n if (!flow) {\n throw new Error('Unknown or expired OAuth state: possible CSRF, replay, or stale flow')\n }\n return flow\n}\n\nexport interface ExchangeCodeInput {\n tokenUrl: string\n clientId: string\n clientSecret: string\n code: string\n codeVerifier: string\n redirectUri: string\n}\n\nexport interface OAuthTokens {\n accessToken: string\n refreshToken?: string\n expiresIn?: number\n scope?: string\n tokenType?: string\n}\n\n/** POST authorization code → token endpoint. Provider-agnostic; if a\n * provider returns a non-standard JSON shape, the adapter wraps this\n * call rather than reaching into the helper. */\nexport async function exchangeAuthorizationCode(input: ExchangeCodeInput): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: input.clientId,\n client_secret: input.clientSecret,\n code: input.code,\n redirect_uri: input.redirectUri,\n code_verifier: input.codeVerifier,\n })\n const res = await fetch(input.tokenUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' },\n body,\n })\n if (!res.ok) {\n const text = await res.text().catch(() => '')\n throw new Error(`OAuth token exchange failed: ${res.status} ${res.statusText} — ${text.slice(0, 200)}`)\n }\n const json = (await res.json()) as {\n access_token: string\n refresh_token?: string\n expires_in?: number\n scope?: string\n token_type?: string\n }\n return {\n accessToken: json.access_token,\n refreshToken: json.refresh_token,\n expiresIn: json.expires_in,\n scope: json.scope,\n tokenType: json.token_type,\n }\n}\n\nexport interface RefreshInput {\n tokenUrl: string\n clientId: string\n clientSecret: string\n refreshToken: string\n}\n\n/** Refresh an access token. Returns the new tokens — the connector layer\n * is responsible for re-encrypting + persisting the envelope. */\nexport async function refreshAccessToken(input: RefreshInput): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: input.clientId,\n client_secret: input.clientSecret,\n refresh_token: input.refreshToken,\n })\n const res = await fetch(input.tokenUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' },\n body,\n })\n if (!res.ok) {\n const text = await res.text().catch(() => '')\n throw new Error(`OAuth refresh failed: ${res.status} ${res.statusText} — ${text.slice(0, 200)}`)\n }\n const json = (await res.json()) as {\n access_token: string\n refresh_token?: string\n expires_in?: number\n scope?: string\n token_type?: string\n }\n return {\n accessToken: json.access_token,\n // Some providers omit refresh_token on refresh — keep the previous one\n // in that case (caller passes through if undefined).\n refreshToken: json.refresh_token,\n expiresIn: json.expires_in,\n scope: json.scope,\n tokenType: json.token_type,\n }\n}\n\nfunction base64Url(buf: Buffer): string {\n return buf.toString('base64').replace(/=+$/, '').replace(/\\+/g, '-').replace(/\\//g, '_')\n}\n\n/** Test-only — drop pending flows between unit-test runs. */\nexport function _resetPendingFlowsForTests(): void {\n defaultFlowStore.clear?.()\n}\n","/**\n * Inbound webhook signature verifiers — provider-specific HMAC schemes.\n *\n * Each signature scheme is a pure function:\n * (rawBody: string, headers, secret, now?) → boolean\n *\n * Constant-time comparison via `crypto.timingSafeEqual`. Timestamps are\n * checked against a configurable tolerance to bound replay risk; the default\n * mirrors the upstream provider's documented window (Stripe: 5 min, Slack: 5 min).\n *\n * These verifiers are the building blocks for any inbound-webhook receiver\n * (a route + a `verify` call + a per-event handler). They live in this\n * package so every consumer of the integration substrate gets correct\n * verification — not just one product reimplementing it.\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto'\n\n/** Default replay-protection window. Providers commonly use 5 minutes. */\nexport const DEFAULT_SIGNATURE_TOLERANCE_SECONDS = 5 * 60\n\n// ─── Stripe ─────────────────────────────────────────────────────────────\n//\n// Stripe signs webhooks with a single header `Stripe-Signature` of the form\n//\n// t=<timestamp>,v1=<sig1>,v1=<sig2>,...\n//\n// where `t` is the Unix timestamp the event was generated, and each `v1`\n// is `HMAC-SHA256(secret, \"<t>.<rawBody>\")`. Multiple `v1` entries appear\n// during secret rotation — any one matching is sufficient.\n//\n// https://stripe.com/docs/webhooks/signatures\n\nexport interface ParsedStripeSignatureHeader {\n t: number\n sigs: string[]\n}\n\nexport function parseStripeSignatureHeader(header: string): ParsedStripeSignatureHeader | null {\n const acc: { ts?: number; sigs: string[] } = { sigs: [] }\n for (const part of header.split(',')) {\n const idx = part.indexOf('=')\n if (idx < 0) continue\n const key = part.slice(0, idx).trim()\n const val = part.slice(idx + 1).trim()\n if (key === 't') {\n const n = Number(val)\n if (Number.isFinite(n)) acc.ts = n\n } else if (key === 'v1') {\n acc.sigs.push(val)\n }\n }\n if (acc.ts === undefined || acc.sigs.length === 0) return null\n return { t: acc.ts, sigs: acc.sigs }\n}\n\nexport interface StripeVerifyOptions {\n /** Replay-protection window in seconds. Default 300. */\n toleranceSeconds?: number\n /** Override `now()` for tests. UTC seconds. */\n now?: number\n}\n\n/** Verify a Stripe webhook signature against the raw request body. */\nexport function verifyStripeSignature(\n rawBody: string,\n signatureHeader: string,\n secret: string,\n options: StripeVerifyOptions = {},\n): boolean {\n const parsed = parseStripeSignatureHeader(signatureHeader)\n if (!parsed) return false\n const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS\n const now = options.now ?? Math.floor(Date.now() / 1000)\n if (Math.abs(now - parsed.t) > tolerance) return false\n const expected = createHmac('sha256', secret).update(`${parsed.t}.${rawBody}`).digest('hex')\n const expectedBuf = Buffer.from(expected, 'utf8')\n for (const sig of parsed.sigs) {\n const sigBuf = Buffer.from(sig, 'utf8')\n if (sigBuf.length !== expectedBuf.length) continue\n if (timingSafeEqual(sigBuf, expectedBuf)) return true\n }\n return false\n}\n\n// ─── Slack ──────────────────────────────────────────────────────────────\n//\n// Slack signs request bodies with two headers:\n//\n// X-Slack-Signature: v0=<HMAC-SHA256(secret, \"v0:<ts>:<body>\")>\n// X-Slack-Request-Timestamp: <ts>\n//\n// https://api.slack.com/authentication/verifying-requests-from-slack\n\nexport interface SlackVerifyOptions {\n toleranceSeconds?: number\n now?: number\n}\n\nexport function verifySlackSignature(\n rawBody: string,\n signatureHeader: string,\n timestampHeader: string,\n secret: string,\n options: SlackVerifyOptions = {},\n): boolean {\n if (!signatureHeader.startsWith('v0=')) return false\n const ts = Number(timestampHeader)\n if (!Number.isFinite(ts)) return false\n const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS\n const now = options.now ?? Math.floor(Date.now() / 1000)\n if (Math.abs(now - ts) > tolerance) return false\n const expected = 'v0=' + createHmac('sha256', secret).update(`v0:${ts}:${rawBody}`).digest('hex')\n const expectedBuf = Buffer.from(expected, 'utf8')\n const sigBuf = Buffer.from(signatureHeader, 'utf8')\n if (sigBuf.length !== expectedBuf.length) return false\n return timingSafeEqual(sigBuf, expectedBuf)\n}\n\n// ─── Generic HMAC ───────────────────────────────────────────────────────\n//\n// For \"we shipped a webhook URL with a shared HMAC secret\" patterns —\n// covers any custom integration where the operator picks the message\n// format. The signed message is the literal `rawBody` (no timestamp\n// prefix); replay protection is the caller's responsibility (use a\n// nonce header + a small dedup cache).\n\nexport interface GenericHmacVerifyOptions {\n /** sha256 (default) | sha1 | sha512 — matches the algorithm the receiver\n * computed at sign time. */\n algorithm?: 'sha256' | 'sha1' | 'sha512'\n /** Optional prefix the receiver prepends to the signature in the header\n * (e.g., `'sha256='`). Stripped before constant-time comparison. */\n signaturePrefix?: string\n /** Lowercase comparison (most providers emit hex-lowercase). Default true. */\n lowercaseHex?: boolean\n}\n\nexport function verifyHmacSignature(\n rawBody: string,\n signatureHeader: string,\n secret: string,\n options: GenericHmacVerifyOptions = {},\n): boolean {\n const algorithm = options.algorithm ?? 'sha256'\n const prefix = options.signaturePrefix ?? ''\n const lower = options.lowercaseHex ?? true\n let candidate = signatureHeader\n if (prefix) {\n if (!candidate.startsWith(prefix)) return false\n candidate = candidate.slice(prefix.length)\n }\n if (lower) candidate = candidate.toLowerCase()\n const expected = createHmac(algorithm, secret).update(rawBody).digest('hex')\n const expectedBuf = Buffer.from(expected, 'utf8')\n const sigBuf = Buffer.from(candidate, 'utf8')\n if (sigBuf.length !== expectedBuf.length) return false\n return timingSafeEqual(sigBuf, expectedBuf)\n}\n\n// ─── Header helper ──────────────────────────────────────────────────────\n//\n// Most fastify/express adapters expose request headers as\n// `Record<string, string | string[] | undefined>`. This helper picks the\n// first canonical value for a given name (case-insensitive).\n\nexport function firstHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string,\n): string | undefined {\n const v = headers[name]\n ?? headers[name.toLowerCase()]\n ?? Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1]\n if (Array.isArray(v)) return v[0]\n return typeof v === 'string' ? v : undefined\n}\n"],"mappings":"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","names":["createHmac","timingSafeEqual","createHmac","timingSafeEqual"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-integrations",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Vendor-neutral integration contracts and runtime helpers for sandbox and agent apps.",
5
5
  "homepage": "https://github.com/tangle-network/agent-integrations#readme",
6
6
  "repository": {