@tangle-network/agent-integrations 0.25.7 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +11 -2
  2. package/dist/bin/tangle-catalog-runtime.js +6 -2
  3. package/dist/bin/tangle-catalog-runtime.js.map +1 -1
  4. package/dist/catalog.d.ts +4 -1
  5. package/dist/catalog.js +6 -2
  6. package/dist/chunk-2TW2QKGZ.js +94 -0
  7. package/dist/chunk-2TW2QKGZ.js.map +1 -0
  8. package/dist/chunk-ATYHZXLL.js +457 -0
  9. package/dist/chunk-ATYHZXLL.js.map +1 -0
  10. package/dist/{chunk-A5I3EYU5.js → chunk-ICSBYCE2.js} +122 -1
  11. package/dist/chunk-ICSBYCE2.js.map +1 -0
  12. package/dist/{chunk-WC63AI4Q.js → chunk-JU25UDN2.js} +1252 -225
  13. package/dist/chunk-JU25UDN2.js.map +1 -0
  14. package/dist/chunk-P24T3MLM.js +106 -0
  15. package/dist/chunk-P24T3MLM.js.map +1 -0
  16. package/dist/chunk-SVQ4PHDZ.js +129 -0
  17. package/dist/chunk-SVQ4PHDZ.js.map +1 -0
  18. package/dist/connect/index.d.ts +112 -0
  19. package/dist/connect/index.js +14 -0
  20. package/dist/connect/index.js.map +1 -0
  21. package/dist/connectors/adapters/index.d.ts +593 -1
  22. package/dist/connectors/adapters/index.js +22 -1
  23. package/dist/connectors/index.d.ts +2 -1
  24. package/dist/connectors/index.js +32 -10
  25. package/dist/index.d.ts +5 -2
  26. package/dist/index.js +57 -11
  27. package/dist/middleware/index.d.ts +137 -0
  28. package/dist/middleware/index.js +14 -0
  29. package/dist/middleware/index.js.map +1 -0
  30. package/dist/registry.d.ts +165 -2
  31. package/dist/registry.js +6 -2
  32. package/dist/runtime.d.ts +4 -1
  33. package/dist/runtime.js +6 -2
  34. package/dist/specs.d.ts +4 -1
  35. package/dist/tangle-catalog-runtime.d.ts +4 -1
  36. package/dist/tangle-catalog-runtime.js +6 -2
  37. package/dist/tangle-id-CTU4kGId.d.ts +553 -0
  38. package/dist/webhooks/index.d.ts +193 -0
  39. package/dist/webhooks/index.js +285 -0
  40. package/dist/webhooks/index.js.map +1 -0
  41. package/examples/discover-capabilities.ts +46 -0
  42. package/examples/webhook-router.ts +56 -0
  43. package/package.json +25 -12
  44. package/dist/chunk-A5I3EYU5.js.map +0 -1
  45. package/dist/chunk-WC63AI4Q.js.map +0 -1
  46. package/dist/index-BQY5ry2s.d.ts +0 -808
@@ -1,55 +1,12 @@
1
- // src/connectors/types.ts
2
- var ResourceContention = class extends Error {
3
- constructor(message, alternatives = [], currentState) {
4
- super(message);
5
- this.alternatives = alternatives;
6
- this.currentState = currentState;
7
- }
8
- alternatives;
9
- currentState;
10
- name = "ResourceContention";
11
- };
12
- var CredentialsExpired = class extends Error {
13
- constructor(message, dataSourceId) {
14
- super(message);
15
- this.dataSourceId = dataSourceId;
16
- }
17
- dataSourceId;
18
- name = "CredentialsExpired";
19
- };
20
- function validateConnectorManifest(manifest) {
21
- const issues = [];
22
- if (!manifest.kind.trim()) issues.push({ path: "kind", message: "kind is required" });
23
- if (!manifest.displayName.trim()) issues.push({ path: "displayName", message: "displayName is required" });
24
- const seen = /* @__PURE__ */ new Set();
25
- for (const [index, capability] of manifest.capabilities.entries()) {
26
- const path = `capabilities[${index}]`;
27
- if (!capability.name.trim()) issues.push({ path: `${path}.name`, message: "capability name is required" });
28
- if (seen.has(capability.name)) issues.push({ path: `${path}.name`, message: `duplicate capability name: ${capability.name}` });
29
- seen.add(capability.name);
30
- if (capability.class === "mutation") {
31
- if (!capability.cas) issues.push({ path: `${path}.cas`, message: "mutation capability must declare a CAS strategy" });
32
- if (manifest.defaultConsistencyModel === "authoritative" && capability.cas === "none") {
33
- issues.push({ path: `${path}.cas`, message: 'authoritative mutations cannot use cas="none"' });
34
- }
35
- }
36
- }
37
- if (manifest.rateLimit) {
38
- if (!Number.isFinite(manifest.rateLimit.requests) || manifest.rateLimit.requests <= 0) {
39
- issues.push({ path: "rateLimit.requests", message: "rateLimit.requests must be positive" });
40
- }
41
- if (!Number.isFinite(manifest.rateLimit.windowMs) || manifest.rateLimit.windowMs <= 0) {
42
- issues.push({ path: "rateLimit.windowMs", message: "rateLimit.windowMs must be positive" });
43
- }
44
- }
45
- return { ok: issues.length === 0, issues };
46
- }
47
- function assertValidConnectorManifest(manifest) {
48
- const result = validateConnectorManifest(manifest);
49
- if (!result.ok) {
50
- throw new Error(`Invalid connector manifest ${manifest.kind || "<unknown>"}: ${result.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
51
- }
52
- }
1
+ import {
2
+ firstHeader,
3
+ verifySlackSignature,
4
+ verifyStripeSignature
5
+ } from "./chunk-2TW2QKGZ.js";
6
+ import {
7
+ CredentialsExpired,
8
+ ResourceContention
9
+ } from "./chunk-ATYHZXLL.js";
53
10
 
54
11
  // src/connectors/oauth.ts
55
12
  import { createHash, randomBytes } from "crypto";
@@ -462,11 +419,320 @@ function readMetaString(meta, key) {
462
419
  return v;
463
420
  }
464
421
 
422
+ // src/connectors/adapters/google-drive.ts
423
+ var SCOPES_READONLY = ["https://www.googleapis.com/auth/drive.readonly"];
424
+ var SCOPE_WATCH = "https://www.googleapis.com/auth/drive";
425
+ var AUTH_URL2 = "https://accounts.google.com/o/oauth2/v2/auth";
426
+ var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
427
+ var API = "https://www.googleapis.com/drive/v3";
428
+ function googleDrive(opts) {
429
+ const { clientId, clientSecret } = opts;
430
+ const timeoutMs = opts.timeoutMs ?? 3e4;
431
+ const scopes = opts.includeWatchScope ? [...SCOPES_READONLY, SCOPE_WATCH] : SCOPES_READONLY;
432
+ const adapter = {
433
+ manifest: {
434
+ kind: "google-drive",
435
+ displayName: "Google Drive",
436
+ description: "Read and watch files in the user's Google Drive. List a folder, fetch a document's contents (Docs/Sheets/PDFs/.docx), and subscribe to folder changes via push notifications.",
437
+ auth: {
438
+ kind: "oauth2",
439
+ authorizationUrl: AUTH_URL2,
440
+ tokenUrl: TOKEN_URL2,
441
+ scopes,
442
+ clientIdEnv: "GOOGLE_OAUTH_CLIENT_ID",
443
+ clientSecretEnv: "GOOGLE_OAUTH_CLIENT_SECRET",
444
+ extraAuthParams: { access_type: "offline", prompt: "consent", include_granted_scopes: "true" }
445
+ },
446
+ category: "storage",
447
+ defaultConsistencyModel: "authoritative",
448
+ rateLimit: { requests: 1e3, windowMs: 6e4, scope: "oauth-client" },
449
+ capabilities: [
450
+ {
451
+ name: "list_files",
452
+ class: "read",
453
+ description: `List files visible to the connected Drive account. Optionally scope to a folder by id and/or pass a Drive query string (e.g., "mimeType='application/pdf' and modifiedTime > '2025-01-01T00:00:00Z'").`,
454
+ parameters: {
455
+ type: "object",
456
+ properties: {
457
+ folderId: { type: "string", description: "Drive folder id; when present, restricts to direct children." },
458
+ query: { type: "string", description: "Optional Drive query expression; appended with AND if folderId is set." },
459
+ pageSize: { type: "integer", minimum: 1, maximum: 1e3, default: 100 },
460
+ pageToken: { type: "string", description: "Continuation token returned by a previous call." }
461
+ }
462
+ }
463
+ },
464
+ {
465
+ name: "read_file",
466
+ class: "read",
467
+ description: "Read a file's contents. Google-native types are exported (Docs \u2192 text/plain by default, Sheets \u2192 text/csv, Slides \u2192 application/pdf); binary types are returned as base64.",
468
+ parameters: {
469
+ type: "object",
470
+ properties: {
471
+ fileId: { type: "string" },
472
+ exportMime: {
473
+ type: "string",
474
+ description: "Export mime override for Google-native types. Defaults: Docs=text/plain, Sheets=text/csv, Slides=application/pdf."
475
+ }
476
+ },
477
+ required: ["fileId"]
478
+ }
479
+ },
480
+ {
481
+ name: "watch_folder",
482
+ class: "mutation",
483
+ description: "Create a push-notification channel for a folder. Drive POSTs change notifications to `address`; the channel expires at `expiration` (max 7 days). Pass the same channelId on retry to replay the existing channel.",
484
+ cas: "native-idempotency",
485
+ externalEffect: true,
486
+ requiredScopes: [SCOPE_WATCH],
487
+ parameters: {
488
+ type: "object",
489
+ properties: {
490
+ folderId: { type: "string" },
491
+ channelId: { type: "string", description: "Caller-controlled UUID; also used as idempotency key." },
492
+ address: { type: "string", description: "HTTPS URL Drive will POST change notifications to." },
493
+ ttlMs: { type: "integer", minimum: 6e4, description: "Channel lifetime in ms. Drive caps at 7 days." }
494
+ },
495
+ required: ["folderId", "channelId", "address"]
496
+ }
497
+ }
498
+ ]
499
+ },
500
+ async executeRead(inv) {
501
+ const accessToken = await ensureFreshAccessToken2(inv.source.credentials, clientId, clientSecret);
502
+ if (inv.capabilityName === "list_files") return listFiles(inv, accessToken, timeoutMs);
503
+ if (inv.capabilityName === "read_file") return readFile(inv, accessToken, timeoutMs);
504
+ throw new Error(`google-drive: unknown read capability ${inv.capabilityName}`);
505
+ },
506
+ async executeMutation(inv) {
507
+ if (inv.capabilityName !== "watch_folder") {
508
+ throw new Error(`google-drive: unknown mutation capability ${inv.capabilityName}`);
509
+ }
510
+ const accessToken = await ensureFreshAccessToken2(inv.source.credentials, clientId, clientSecret);
511
+ return watchFolder(inv, accessToken, timeoutMs);
512
+ },
513
+ async exchangeOAuth(input) {
514
+ const tokens = await exchangeAuthorizationCode({
515
+ tokenUrl: TOKEN_URL2,
516
+ clientId,
517
+ clientSecret,
518
+ code: input.code,
519
+ codeVerifier: input.codeVerifier,
520
+ redirectUri: input.redirectUri
521
+ });
522
+ return {
523
+ credentials: {
524
+ kind: "oauth2",
525
+ accessToken: tokens.accessToken,
526
+ refreshToken: tokens.refreshToken,
527
+ expiresAt: tokens.expiresIn ? Date.now() + tokens.expiresIn * 1e3 : void 0
528
+ },
529
+ scopes: tokens.scope?.split(/\s+/) ?? scopes,
530
+ metadata: {}
531
+ };
532
+ },
533
+ async refreshToken(creds) {
534
+ if (creds.kind !== "oauth2" || !creds.refreshToken) {
535
+ throw new Error("google-drive.refreshToken: missing refresh token");
536
+ }
537
+ const refreshed = await refreshAccessToken({
538
+ tokenUrl: TOKEN_URL2,
539
+ clientId,
540
+ clientSecret,
541
+ refreshToken: creds.refreshToken
542
+ });
543
+ return {
544
+ kind: "oauth2",
545
+ accessToken: refreshed.accessToken,
546
+ refreshToken: refreshed.refreshToken ?? creds.refreshToken,
547
+ expiresAt: refreshed.expiresIn ? Date.now() + refreshed.expiresIn * 1e3 : void 0
548
+ };
549
+ },
550
+ async test(source) {
551
+ try {
552
+ const accessToken = await ensureFreshAccessToken2(source.credentials, clientId, clientSecret);
553
+ const res = await fetch(`${API}/about?fields=user`, {
554
+ headers: { authorization: `Bearer ${accessToken}` },
555
+ signal: AbortSignal.timeout(8e3)
556
+ });
557
+ if (res.status === 401 || res.status === 403) {
558
+ return { ok: false, reason: `Google rejected Drive token (${res.status}) \u2014 reconnect required` };
559
+ }
560
+ if (!res.ok) return { ok: false, reason: `Google Drive returned ${res.status}` };
561
+ return { ok: true };
562
+ } catch (err) {
563
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
564
+ }
565
+ }
566
+ };
567
+ return adapter;
568
+ }
569
+ async function listFiles(inv, accessToken, timeoutMs) {
570
+ const args = inv.args ?? {};
571
+ const q = [];
572
+ if (args.folderId) q.push(`'${args.folderId.replace(/'/g, "\\'")}' in parents`);
573
+ if (args.query) q.push(`(${args.query})`);
574
+ q.push("trashed = false");
575
+ const params = new URLSearchParams({
576
+ q: q.join(" and "),
577
+ pageSize: String(args.pageSize ?? 100),
578
+ fields: "nextPageToken, files(id,name,mimeType,modifiedTime,size,md5Checksum,parents)"
579
+ });
580
+ if (args.pageToken) params.set("pageToken", args.pageToken);
581
+ const res = await fetch(`${API}/files?${params.toString()}`, {
582
+ headers: { authorization: `Bearer ${accessToken}` },
583
+ signal: AbortSignal.timeout(timeoutMs)
584
+ });
585
+ if (res.status === 401 || res.status === 403) {
586
+ throw new CredentialsExpired(`Google Drive rejected token (${res.status})`, inv.source.id);
587
+ }
588
+ if (!res.ok) {
589
+ const text = await res.text().catch(() => "");
590
+ throw new Error(`google-drive list_files ${res.status}: ${text.slice(0, 200)}`);
591
+ }
592
+ const json = await res.json();
593
+ return {
594
+ data: { files: json.files ?? [], nextPageToken: json.nextPageToken },
595
+ fetchedAt: Date.now()
596
+ };
597
+ }
598
+ var GOOGLE_NATIVE_DEFAULTS = {
599
+ "application/vnd.google-apps.document": "text/plain",
600
+ "application/vnd.google-apps.spreadsheet": "text/csv",
601
+ "application/vnd.google-apps.presentation": "application/pdf"
602
+ };
603
+ async function readFile(inv, accessToken, timeoutMs) {
604
+ const { fileId, exportMime } = inv.args ?? {};
605
+ const metaRes = await fetch(`${API}/files/${encodeURIComponent(fileId)}?fields=id,name,mimeType,modifiedTime`, {
606
+ headers: { authorization: `Bearer ${accessToken}` },
607
+ signal: AbortSignal.timeout(timeoutMs)
608
+ });
609
+ if (metaRes.status === 401 || metaRes.status === 403) {
610
+ throw new CredentialsExpired(`Google Drive rejected token (${metaRes.status})`, inv.source.id);
611
+ }
612
+ if (metaRes.status === 404) {
613
+ throw new Error(`google-drive read_file: file ${fileId} not found`);
614
+ }
615
+ if (!metaRes.ok) {
616
+ const text = await metaRes.text().catch(() => "");
617
+ throw new Error(`google-drive read_file meta ${metaRes.status}: ${text.slice(0, 200)}`);
618
+ }
619
+ const meta = await metaRes.json();
620
+ const isNative = meta.mimeType.startsWith("application/vnd.google-apps.");
621
+ const fetchedAt = Date.now();
622
+ if (isNative) {
623
+ const targetMime = exportMime ?? GOOGLE_NATIVE_DEFAULTS[meta.mimeType] ?? "text/plain";
624
+ const res2 = await fetch(`${API}/files/${encodeURIComponent(fileId)}/export?mimeType=${encodeURIComponent(targetMime)}`, {
625
+ headers: { authorization: `Bearer ${accessToken}` },
626
+ signal: AbortSignal.timeout(timeoutMs)
627
+ });
628
+ if (!res2.ok) {
629
+ const text = await res2.text().catch(() => "");
630
+ throw new Error(`google-drive read_file export ${res2.status}: ${text.slice(0, 200)}`);
631
+ }
632
+ const isTextLike2 = /^text\/|application\/(json|xml|csv|javascript)/.test(targetMime);
633
+ if (isTextLike2) {
634
+ const content = await res2.text();
635
+ return {
636
+ data: { name: meta.name, mimeType: targetMime, content, encoding: "utf-8", modifiedTime: meta.modifiedTime },
637
+ fetchedAt
638
+ };
639
+ }
640
+ const buf2 = Buffer.from(await res2.arrayBuffer());
641
+ return {
642
+ data: { name: meta.name, mimeType: targetMime, content: buf2.toString("base64"), encoding: "base64", modifiedTime: meta.modifiedTime },
643
+ fetchedAt
644
+ };
645
+ }
646
+ const res = await fetch(`${API}/files/${encodeURIComponent(fileId)}?alt=media`, {
647
+ headers: { authorization: `Bearer ${accessToken}` },
648
+ signal: AbortSignal.timeout(timeoutMs)
649
+ });
650
+ if (!res.ok) {
651
+ const text = await res.text().catch(() => "");
652
+ throw new Error(`google-drive read_file media ${res.status}: ${text.slice(0, 200)}`);
653
+ }
654
+ const isTextLike = /^text\/|application\/(json|xml|csv|javascript)/.test(meta.mimeType);
655
+ if (isTextLike) {
656
+ const content = await res.text();
657
+ return {
658
+ data: { name: meta.name, mimeType: meta.mimeType, content, encoding: "utf-8", modifiedTime: meta.modifiedTime },
659
+ fetchedAt
660
+ };
661
+ }
662
+ const buf = Buffer.from(await res.arrayBuffer());
663
+ return {
664
+ data: { name: meta.name, mimeType: meta.mimeType, content: buf.toString("base64"), encoding: "base64", modifiedTime: meta.modifiedTime },
665
+ fetchedAt
666
+ };
667
+ }
668
+ async function watchFolder(inv, accessToken, timeoutMs) {
669
+ const { folderId, channelId, address, ttlMs } = inv.args;
670
+ const body = {
671
+ id: channelId,
672
+ type: "web_hook",
673
+ address
674
+ };
675
+ if (ttlMs && ttlMs > 0) body.expiration = String(Date.now() + ttlMs);
676
+ const res = await fetch(`${API}/files/${encodeURIComponent(folderId)}/watch`, {
677
+ method: "POST",
678
+ headers: {
679
+ authorization: `Bearer ${accessToken}`,
680
+ "content-type": "application/json"
681
+ },
682
+ body: JSON.stringify(body),
683
+ signal: AbortSignal.timeout(timeoutMs)
684
+ });
685
+ if (res.status === 401 || res.status === 403) {
686
+ throw new CredentialsExpired(`Google Drive rejected token (${res.status})`, inv.source.id);
687
+ }
688
+ if (res.status === 409) {
689
+ const cached = inv.source.metadata.watchedChannels?.[channelId];
690
+ return {
691
+ status: "committed",
692
+ data: { channelId, resourceId: cached?.resourceId, expiration: cached?.expiration },
693
+ committedAt: Date.now(),
694
+ idempotentReplay: true
695
+ };
696
+ }
697
+ if (!res.ok) {
698
+ const text = await res.text().catch(() => "");
699
+ throw new Error(`google-drive watch_folder ${res.status}: ${text.slice(0, 200)}`);
700
+ }
701
+ const json = await res.json();
702
+ return {
703
+ status: "committed",
704
+ data: { channelId: json.id, resourceId: json.resourceId, expiration: json.expiration },
705
+ committedAt: Date.now(),
706
+ idempotentReplay: false
707
+ };
708
+ }
709
+ async function ensureFreshAccessToken2(creds, clientId, clientSecret) {
710
+ if (creds.kind !== "oauth2") {
711
+ throw new Error("google-drive: expected oauth2 credentials");
712
+ }
713
+ if (creds.accessToken && (!creds.expiresAt || creds.expiresAt > Date.now() + 6e4)) {
714
+ return creds.accessToken;
715
+ }
716
+ if (!creds.refreshToken) {
717
+ throw new CredentialsExpired("Google Drive access token expired and no refresh token", "");
718
+ }
719
+ const refreshed = await refreshAccessToken({
720
+ tokenUrl: TOKEN_URL2,
721
+ clientId,
722
+ clientSecret,
723
+ refreshToken: creds.refreshToken
724
+ });
725
+ creds.accessToken = refreshed.accessToken;
726
+ creds.expiresAt = refreshed.expiresIn ? Date.now() + refreshed.expiresIn * 1e3 : void 0;
727
+ if (refreshed.refreshToken) creds.refreshToken = refreshed.refreshToken;
728
+ return creds.accessToken;
729
+ }
730
+
465
731
  // src/connectors/adapters/google-sheets.ts
466
732
  import { createHash as createHash2 } from "crypto";
467
733
  var SCOPES2 = ["https://www.googleapis.com/auth/spreadsheets"];
468
- var AUTH_URL2 = "https://accounts.google.com/o/oauth2/v2/auth";
469
- var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
734
+ var AUTH_URL3 = "https://accounts.google.com/o/oauth2/v2/auth";
735
+ var TOKEN_URL3 = "https://oauth2.googleapis.com/token";
470
736
  function googleSheets(opts) {
471
737
  const { clientId, clientSecret } = opts;
472
738
  const adapter = {
@@ -476,8 +742,8 @@ function googleSheets(opts) {
476
742
  description: "Bind your agent's knowledge base or pricing/availability lookup to a live Google Sheet. Edit the sheet, and the agent picks up changes \u2014 no redeploys.",
477
743
  auth: {
478
744
  kind: "oauth2",
479
- authorizationUrl: AUTH_URL2,
480
- tokenUrl: TOKEN_URL2,
745
+ authorizationUrl: AUTH_URL3,
746
+ tokenUrl: TOKEN_URL3,
481
747
  scopes: SCOPES2,
482
748
  clientIdEnv: "GOOGLE_OAUTH_CLIENT_ID",
483
749
  clientSecretEnv: "GOOGLE_OAUTH_CLIENT_SECRET",
@@ -544,7 +810,7 @@ function googleSheets(opts) {
544
810
  },
545
811
  async executeRead(inv) {
546
812
  const meta = readSheetMeta(inv.source.metadata);
547
- const accessToken = await ensureFreshAccessToken2(inv.source.credentials, clientId, clientSecret);
813
+ const accessToken = await ensureFreshAccessToken3(inv.source.credentials, clientId, clientSecret);
548
814
  const rows = await fetchAllRows(accessToken, meta);
549
815
  const limit = clampLimit(inv.args.limit, 100);
550
816
  let filtered = rows;
@@ -571,7 +837,7 @@ function googleSheets(opts) {
571
837
  throw new Error(`google-sheets: unknown mutation ${inv.capabilityName}`);
572
838
  }
573
839
  const meta = readSheetMeta(inv.source.metadata);
574
- const accessToken = await ensureFreshAccessToken2(inv.source.credentials, clientId, clientSecret);
840
+ const accessToken = await ensureFreshAccessToken3(inv.source.credentials, clientId, clientSecret);
575
841
  const { rowKey, patch, expectedFingerprint } = inv.args;
576
842
  const rows = await fetchAllRows(accessToken, meta);
577
843
  const target = rows.find((r) => normalizeKey(r.values[meta.keyColumn]) === normalizeKey(rowKey));
@@ -627,7 +893,7 @@ function googleSheets(opts) {
627
893
  },
628
894
  async exchangeOAuth(input) {
629
895
  const tokens = await exchangeAuthorizationCode({
630
- tokenUrl: TOKEN_URL2,
896
+ tokenUrl: TOKEN_URL3,
631
897
  clientId,
632
898
  clientSecret,
633
899
  code: input.code,
@@ -652,7 +918,7 @@ function googleSheets(opts) {
652
918
  throw new Error("google-sheets.refreshToken: missing refresh token");
653
919
  }
654
920
  const refreshed = await refreshAccessToken({
655
- tokenUrl: TOKEN_URL2,
921
+ tokenUrl: TOKEN_URL3,
656
922
  clientId,
657
923
  clientSecret,
658
924
  refreshToken: creds.refreshToken
@@ -666,7 +932,7 @@ function googleSheets(opts) {
666
932
  },
667
933
  async test(source) {
668
934
  try {
669
- const accessToken = await ensureFreshAccessToken2(source.credentials, clientId, clientSecret);
935
+ const accessToken = await ensureFreshAccessToken3(source.credentials, clientId, clientSecret);
670
936
  const meta = readSheetMeta(source.metadata);
671
937
  if (!meta.spreadsheetId) {
672
938
  return { ok: false, reason: "spreadsheetId not configured \u2014 pick a sheet in the connection settings" };
@@ -764,7 +1030,7 @@ function columnIndexToLetter(idx) {
764
1030
  }
765
1031
  return s;
766
1032
  }
767
- async function ensureFreshAccessToken2(creds, clientId, clientSecret) {
1033
+ async function ensureFreshAccessToken3(creds, clientId, clientSecret) {
768
1034
  if (creds.kind !== "oauth2") {
769
1035
  throw new Error("google-sheets: expected oauth2 credentials");
770
1036
  }
@@ -775,7 +1041,403 @@ async function ensureFreshAccessToken2(creds, clientId, clientSecret) {
775
1041
  throw new CredentialsExpired("Google Sheets access token expired and no refresh token", "");
776
1042
  }
777
1043
  const refreshed = await refreshAccessToken({
778
- tokenUrl: TOKEN_URL2,
1044
+ tokenUrl: TOKEN_URL3,
1045
+ clientId,
1046
+ clientSecret,
1047
+ refreshToken: creds.refreshToken
1048
+ });
1049
+ creds.accessToken = refreshed.accessToken;
1050
+ creds.expiresAt = refreshed.expiresIn ? Date.now() + refreshed.expiresIn * 1e3 : void 0;
1051
+ if (refreshed.refreshToken) creds.refreshToken = refreshed.refreshToken;
1052
+ return creds.accessToken;
1053
+ }
1054
+
1055
+ // src/connectors/adapters/gmail.ts
1056
+ var SCOPE_READ = "https://www.googleapis.com/auth/gmail.readonly";
1057
+ var SCOPE_SEND = "https://www.googleapis.com/auth/gmail.send";
1058
+ var SCOPE_MODIFY = "https://www.googleapis.com/auth/gmail.modify";
1059
+ var AUTH_URL4 = "https://accounts.google.com/o/oauth2/v2/auth";
1060
+ var TOKEN_URL4 = "https://oauth2.googleapis.com/token";
1061
+ var API2 = "https://gmail.googleapis.com/gmail/v1/users/me";
1062
+ function gmail(opts) {
1063
+ const { clientId, clientSecret } = opts;
1064
+ const timeoutMs = opts.timeoutMs ?? 3e4;
1065
+ const scopes = opts.scopes ?? [SCOPE_READ, SCOPE_SEND, SCOPE_MODIFY];
1066
+ const adapter = {
1067
+ manifest: {
1068
+ kind: "gmail",
1069
+ displayName: "Gmail",
1070
+ description: "Read inbox messages by label or query, fetch a single message including MIME bodies and attachment manifests, reply on a thread, and watch a label for new mail (Cloud Pub/Sub push).",
1071
+ auth: {
1072
+ kind: "oauth2",
1073
+ authorizationUrl: AUTH_URL4,
1074
+ tokenUrl: TOKEN_URL4,
1075
+ scopes,
1076
+ clientIdEnv: "GOOGLE_OAUTH_CLIENT_ID",
1077
+ clientSecretEnv: "GOOGLE_OAUTH_CLIENT_SECRET",
1078
+ extraAuthParams: { access_type: "offline", prompt: "consent", include_granted_scopes: "true" }
1079
+ },
1080
+ category: "comms",
1081
+ defaultConsistencyModel: "authoritative",
1082
+ rateLimit: { requests: 250, windowMs: 1e3, scope: "oauth-client" },
1083
+ capabilities: [
1084
+ {
1085
+ name: "list_messages",
1086
+ class: "read",
1087
+ description: "List inbox messages. Filter by labelIds (default INBOX) and/or a Gmail query (e.g., 'from:billing@stripe.com newer_than:7d'). Returns headers (from/to/subject/date) not bodies.",
1088
+ requiredScopes: [SCOPE_READ],
1089
+ parameters: {
1090
+ type: "object",
1091
+ properties: {
1092
+ labelIds: { type: "array", items: { type: "string" }, description: 'Default: ["INBOX"]' },
1093
+ query: { type: "string", description: "Gmail query syntax." },
1094
+ maxResults: { type: "integer", minimum: 1, maximum: 500, default: 25 },
1095
+ pageToken: { type: "string" }
1096
+ }
1097
+ }
1098
+ },
1099
+ {
1100
+ name: "read_message",
1101
+ class: "read",
1102
+ description: "Read a single Gmail message including parsed text and html bodies and a flat manifest of attachments. Bodies are inlined; attachment bytes are not.",
1103
+ requiredScopes: [SCOPE_READ],
1104
+ parameters: {
1105
+ type: "object",
1106
+ properties: {
1107
+ id: { type: "string" }
1108
+ },
1109
+ required: ["id"]
1110
+ }
1111
+ },
1112
+ {
1113
+ name: "send_reply",
1114
+ class: "mutation",
1115
+ description: "Send a reply on a thread. Pulls In-Reply-To/References from the latest message in the thread. Body is text/plain.",
1116
+ cas: "native-idempotency",
1117
+ externalEffect: true,
1118
+ requiredScopes: [SCOPE_SEND, SCOPE_READ],
1119
+ parameters: {
1120
+ type: "object",
1121
+ properties: {
1122
+ threadId: { type: "string" },
1123
+ body: { type: "string", description: "text/plain body" },
1124
+ replyAll: { type: "boolean", default: false },
1125
+ cc: { type: "array", items: { type: "string" } }
1126
+ },
1127
+ required: ["threadId", "body"]
1128
+ }
1129
+ },
1130
+ {
1131
+ name: "watch_label",
1132
+ class: "mutation",
1133
+ description: "Register a Cloud Pub/Sub topic to receive push notifications when a label changes. Returns the upstream historyId + expiration. Re-issue every 7 days.",
1134
+ cas: "native-idempotency",
1135
+ externalEffect: true,
1136
+ requiredScopes: [SCOPE_MODIFY],
1137
+ parameters: {
1138
+ type: "object",
1139
+ properties: {
1140
+ labelIds: { type: "array", items: { type: "string" }, description: 'Default: ["INBOX"]' },
1141
+ topicName: { type: "string", description: "projects/<id>/topics/<name>" },
1142
+ labelFilterAction: { type: "string", enum: ["include", "exclude"], default: "include" }
1143
+ },
1144
+ required: ["topicName"]
1145
+ }
1146
+ }
1147
+ ]
1148
+ },
1149
+ async executeRead(inv) {
1150
+ const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
1151
+ if (inv.capabilityName === "list_messages") return listMessages(inv, accessToken, timeoutMs);
1152
+ if (inv.capabilityName === "read_message") return readMessage(inv, accessToken, timeoutMs);
1153
+ throw new Error(`gmail: unknown read capability ${inv.capabilityName}`);
1154
+ },
1155
+ async executeMutation(inv) {
1156
+ const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
1157
+ if (inv.capabilityName === "send_reply") return sendReply(inv, accessToken, timeoutMs);
1158
+ if (inv.capabilityName === "watch_label") return watchLabel(inv, accessToken, timeoutMs);
1159
+ throw new Error(`gmail: unknown mutation capability ${inv.capabilityName}`);
1160
+ },
1161
+ async exchangeOAuth(input) {
1162
+ const tokens = await exchangeAuthorizationCode({
1163
+ tokenUrl: TOKEN_URL4,
1164
+ clientId,
1165
+ clientSecret,
1166
+ code: input.code,
1167
+ codeVerifier: input.codeVerifier,
1168
+ redirectUri: input.redirectUri
1169
+ });
1170
+ return {
1171
+ credentials: {
1172
+ kind: "oauth2",
1173
+ accessToken: tokens.accessToken,
1174
+ refreshToken: tokens.refreshToken,
1175
+ expiresAt: tokens.expiresIn ? Date.now() + tokens.expiresIn * 1e3 : void 0
1176
+ },
1177
+ scopes: tokens.scope?.split(/\s+/) ?? scopes,
1178
+ metadata: {}
1179
+ };
1180
+ },
1181
+ async refreshToken(creds) {
1182
+ if (creds.kind !== "oauth2" || !creds.refreshToken) {
1183
+ throw new Error("gmail.refreshToken: missing refresh token");
1184
+ }
1185
+ const refreshed = await refreshAccessToken({
1186
+ tokenUrl: TOKEN_URL4,
1187
+ clientId,
1188
+ clientSecret,
1189
+ refreshToken: creds.refreshToken
1190
+ });
1191
+ return {
1192
+ kind: "oauth2",
1193
+ accessToken: refreshed.accessToken,
1194
+ refreshToken: refreshed.refreshToken ?? creds.refreshToken,
1195
+ expiresAt: refreshed.expiresIn ? Date.now() + refreshed.expiresIn * 1e3 : void 0
1196
+ };
1197
+ },
1198
+ async test(source) {
1199
+ try {
1200
+ const accessToken = await ensureFreshAccessToken4(source.credentials, clientId, clientSecret);
1201
+ const res = await fetch(`${API2}/profile`, {
1202
+ headers: { authorization: `Bearer ${accessToken}` },
1203
+ signal: AbortSignal.timeout(8e3)
1204
+ });
1205
+ if (res.status === 401 || res.status === 403) {
1206
+ return { ok: false, reason: `Google rejected Gmail token (${res.status}) \u2014 reconnect required` };
1207
+ }
1208
+ if (!res.ok) return { ok: false, reason: `Gmail returned ${res.status}` };
1209
+ return { ok: true };
1210
+ } catch (err) {
1211
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
1212
+ }
1213
+ }
1214
+ };
1215
+ return adapter;
1216
+ }
1217
+ async function listMessages(inv, accessToken, timeoutMs) {
1218
+ const args = inv.args ?? {};
1219
+ const params = new URLSearchParams({
1220
+ maxResults: String(args.maxResults ?? 25)
1221
+ });
1222
+ for (const id of args.labelIds ?? ["INBOX"]) params.append("labelIds", id);
1223
+ if (args.query) params.set("q", args.query);
1224
+ if (args.pageToken) params.set("pageToken", args.pageToken);
1225
+ const listRes = await fetch(`${API2}/messages?${params.toString()}`, {
1226
+ headers: { authorization: `Bearer ${accessToken}` },
1227
+ signal: AbortSignal.timeout(timeoutMs)
1228
+ });
1229
+ if (listRes.status === 401 || listRes.status === 403) {
1230
+ throw new CredentialsExpired(`Gmail rejected token (${listRes.status})`, inv.source.id);
1231
+ }
1232
+ if (!listRes.ok) {
1233
+ const text = await listRes.text().catch(() => "");
1234
+ throw new Error(`gmail list_messages ${listRes.status}: ${text.slice(0, 200)}`);
1235
+ }
1236
+ const listJson = await listRes.json();
1237
+ const ids = listJson.messages ?? [];
1238
+ const metas = await Promise.all(
1239
+ ids.map(async ({ id }) => {
1240
+ const res = await fetch(`${API2}/messages/${encodeURIComponent(id)}?format=metadata&metadataHeaders=From&metadataHeaders=To&metadataHeaders=Subject&metadataHeaders=Date`, {
1241
+ headers: { authorization: `Bearer ${accessToken}` },
1242
+ signal: AbortSignal.timeout(timeoutMs)
1243
+ });
1244
+ if (!res.ok) return null;
1245
+ return await res.json();
1246
+ })
1247
+ );
1248
+ const messages = metas.filter((m) => Boolean(m)).map(toMessageSummary);
1249
+ return {
1250
+ data: { messages, nextPageToken: listJson.nextPageToken },
1251
+ fetchedAt: Date.now()
1252
+ };
1253
+ }
1254
+ function toMessageSummary(meta) {
1255
+ const headers = new Map((meta.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
1256
+ return {
1257
+ id: meta.id,
1258
+ threadId: meta.threadId,
1259
+ snippet: meta.snippet,
1260
+ internalDate: meta.internalDate,
1261
+ labelIds: meta.labelIds ?? [],
1262
+ from: headers.get("from"),
1263
+ to: headers.get("to"),
1264
+ subject: headers.get("subject"),
1265
+ date: headers.get("date")
1266
+ };
1267
+ }
1268
+ async function readMessage(inv, accessToken, timeoutMs) {
1269
+ const { id } = inv.args;
1270
+ const res = await fetch(`${API2}/messages/${encodeURIComponent(id)}?format=full`, {
1271
+ headers: { authorization: `Bearer ${accessToken}` },
1272
+ signal: AbortSignal.timeout(timeoutMs)
1273
+ });
1274
+ if (res.status === 401 || res.status === 403) {
1275
+ throw new CredentialsExpired(`Gmail rejected token (${res.status})`, inv.source.id);
1276
+ }
1277
+ if (res.status === 404) {
1278
+ throw new Error(`gmail read_message: message ${id} not found`);
1279
+ }
1280
+ if (!res.ok) {
1281
+ const text = await res.text().catch(() => "");
1282
+ throw new Error(`gmail read_message ${res.status}: ${text.slice(0, 200)}`);
1283
+ }
1284
+ const full = await res.json();
1285
+ const headers = new Map((full.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
1286
+ const body = {};
1287
+ const attachments = [];
1288
+ walkParts(full.payload, body, attachments);
1289
+ return {
1290
+ data: {
1291
+ id: full.id,
1292
+ threadId: full.threadId,
1293
+ internalDate: full.internalDate,
1294
+ labelIds: full.labelIds ?? [],
1295
+ from: headers.get("from"),
1296
+ to: headers.get("to"),
1297
+ cc: headers.get("cc"),
1298
+ subject: headers.get("subject"),
1299
+ date: headers.get("date"),
1300
+ body,
1301
+ attachments
1302
+ },
1303
+ fetchedAt: Date.now()
1304
+ };
1305
+ }
1306
+ function walkParts(part, body, attachments) {
1307
+ if (!part) return;
1308
+ if (part.body?.data && part.mimeType === "text/plain" && !body.text) {
1309
+ body.text = decodeBase64Url(part.body.data);
1310
+ } else if (part.body?.data && part.mimeType === "text/html" && !body.html) {
1311
+ body.html = decodeBase64Url(part.body.data);
1312
+ }
1313
+ if (part.filename && part.body?.attachmentId) {
1314
+ attachments.push({
1315
+ filename: part.filename,
1316
+ mimeType: part.mimeType ?? "application/octet-stream",
1317
+ attachmentId: part.body.attachmentId,
1318
+ size: part.body.size ?? 0
1319
+ });
1320
+ }
1321
+ for (const child of part.parts ?? []) walkParts(child, body, attachments);
1322
+ }
1323
+ function decodeBase64Url(s) {
1324
+ return Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf-8");
1325
+ }
1326
+ function encodeBase64Url(s) {
1327
+ return Buffer.from(s, "utf-8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1328
+ }
1329
+ async function sendReply(inv, accessToken, timeoutMs) {
1330
+ const { threadId, body, replyAll, cc } = inv.args;
1331
+ const threadRes = await fetch(`${API2}/threads/${encodeURIComponent(threadId)}?format=metadata&metadataHeaders=From&metadataHeaders=To&metadataHeaders=Cc&metadataHeaders=Subject&metadataHeaders=Message-ID&metadataHeaders=References`, {
1332
+ headers: { authorization: `Bearer ${accessToken}` },
1333
+ signal: AbortSignal.timeout(timeoutMs)
1334
+ });
1335
+ if (threadRes.status === 401 || threadRes.status === 403) {
1336
+ throw new CredentialsExpired(`Gmail rejected token (${threadRes.status})`, inv.source.id);
1337
+ }
1338
+ if (!threadRes.ok) {
1339
+ const text = await threadRes.text().catch(() => "");
1340
+ throw new Error(`gmail send_reply thread fetch ${threadRes.status}: ${text.slice(0, 200)}`);
1341
+ }
1342
+ const thread = await threadRes.json();
1343
+ const last = thread.messages?.[thread.messages.length - 1];
1344
+ if (!last) throw new Error(`gmail send_reply: thread ${threadId} has no messages`);
1345
+ const lastHeaders = new Map((last.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
1346
+ const inReplyTo = lastHeaders.get("message-id");
1347
+ const refsHeader = lastHeaders.get("references");
1348
+ const refs = refsHeader ? `${refsHeader} ${inReplyTo ?? ""}`.trim() : inReplyTo;
1349
+ const fromHeader = lastHeaders.get("from");
1350
+ const toHeader = lastHeaders.get("to");
1351
+ const ccHeader = lastHeaders.get("cc");
1352
+ const subject = lastHeaders.get("subject") ?? "";
1353
+ const rfcHeaders = [];
1354
+ if (fromHeader) rfcHeaders.push(`To: ${fromHeader}`);
1355
+ if (replyAll) {
1356
+ const extra = [toHeader, ccHeader].filter(Boolean).join(", ");
1357
+ if (extra) rfcHeaders.push(`Cc: ${extra}`);
1358
+ }
1359
+ if (cc?.length) {
1360
+ const existing = rfcHeaders.findIndex((h) => h.startsWith("Cc: "));
1361
+ if (existing >= 0) rfcHeaders[existing] = `${rfcHeaders[existing]}, ${cc.join(", ")}`;
1362
+ else rfcHeaders.push(`Cc: ${cc.join(", ")}`);
1363
+ }
1364
+ rfcHeaders.push(`Subject: ${subject.toLowerCase().startsWith("re:") ? subject : "Re: " + subject}`);
1365
+ if (inReplyTo) rfcHeaders.push(`In-Reply-To: ${inReplyTo}`);
1366
+ if (refs) rfcHeaders.push(`References: ${refs}`);
1367
+ rfcHeaders.push(`X-Tangle-Idempotency-Key: ${inv.idempotencyKey}`);
1368
+ rfcHeaders.push('Content-Type: text/plain; charset="UTF-8"');
1369
+ rfcHeaders.push("MIME-Version: 1.0");
1370
+ const raw = `${rfcHeaders.join("\r\n")}\r
1371
+ \r
1372
+ ${body}`;
1373
+ const sendBody = { threadId, raw: encodeBase64Url(raw) };
1374
+ const sendRes = await fetch(`${API2}/messages/send`, {
1375
+ method: "POST",
1376
+ headers: {
1377
+ authorization: `Bearer ${accessToken}`,
1378
+ "content-type": "application/json"
1379
+ },
1380
+ body: JSON.stringify(sendBody),
1381
+ signal: AbortSignal.timeout(timeoutMs)
1382
+ });
1383
+ if (sendRes.status === 401 || sendRes.status === 403) {
1384
+ throw new CredentialsExpired(`Gmail rejected token (${sendRes.status})`, inv.source.id);
1385
+ }
1386
+ if (!sendRes.ok) {
1387
+ const text = await sendRes.text().catch(() => "");
1388
+ throw new Error(`gmail send_reply ${sendRes.status}: ${text.slice(0, 200)}`);
1389
+ }
1390
+ const sent = await sendRes.json();
1391
+ return {
1392
+ status: "committed",
1393
+ data: { id: sent.id, threadId: sent.threadId, labelIds: sent.labelIds ?? [] },
1394
+ committedAt: Date.now(),
1395
+ idempotentReplay: false
1396
+ };
1397
+ }
1398
+ async function watchLabel(inv, accessToken, timeoutMs) {
1399
+ const { labelIds, topicName, labelFilterAction } = inv.args;
1400
+ const body = {
1401
+ topicName,
1402
+ labelIds: labelIds ?? ["INBOX"],
1403
+ labelFilterAction: labelFilterAction ?? "include"
1404
+ };
1405
+ const res = await fetch(`${API2}/watch`, {
1406
+ method: "POST",
1407
+ headers: {
1408
+ authorization: `Bearer ${accessToken}`,
1409
+ "content-type": "application/json"
1410
+ },
1411
+ body: JSON.stringify(body),
1412
+ signal: AbortSignal.timeout(timeoutMs)
1413
+ });
1414
+ if (res.status === 401 || res.status === 403) {
1415
+ throw new CredentialsExpired(`Gmail rejected token (${res.status})`, inv.source.id);
1416
+ }
1417
+ if (!res.ok) {
1418
+ const text = await res.text().catch(() => "");
1419
+ throw new Error(`gmail watch_label ${res.status}: ${text.slice(0, 200)}`);
1420
+ }
1421
+ const json = await res.json();
1422
+ return {
1423
+ status: "committed",
1424
+ data: { historyId: json.historyId, expiration: json.expiration, topicName, labelIds: body.labelIds },
1425
+ committedAt: Date.now(),
1426
+ idempotentReplay: false
1427
+ };
1428
+ }
1429
+ async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
1430
+ if (creds.kind !== "oauth2") {
1431
+ throw new Error("gmail: expected oauth2 credentials");
1432
+ }
1433
+ if (creds.accessToken && (!creds.expiresAt || creds.expiresAt > Date.now() + 6e4)) {
1434
+ return creds.accessToken;
1435
+ }
1436
+ if (!creds.refreshToken) {
1437
+ throw new CredentialsExpired("Gmail access token expired and no refresh token", "");
1438
+ }
1439
+ const refreshed = await refreshAccessToken({
1440
+ tokenUrl: TOKEN_URL4,
779
1441
  clientId,
780
1442
  clientSecret,
781
1443
  refreshToken: creds.refreshToken
@@ -794,8 +1456,8 @@ var SCOPES3 = [
794
1456
  // connection silently dies after ~1 hour.
795
1457
  "offline_access"
796
1458
  ];
797
- var AUTH_URL3 = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
798
- var TOKEN_URL3 = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
1459
+ var AUTH_URL5 = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
1460
+ var TOKEN_URL5 = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
799
1461
  function microsoftCalendar(opts) {
800
1462
  const { clientId, clientSecret } = opts;
801
1463
  const adapter = {
@@ -805,8 +1467,8 @@ function microsoftCalendar(opts) {
805
1467
  description: "Let your agent check availability and book against an Outlook / Microsoft 365 calendar. Conflict-resolved via Graph's getSchedule pre-flight; etag-guarded on event updates.",
806
1468
  auth: {
807
1469
  kind: "oauth2",
808
- authorizationUrl: AUTH_URL3,
809
- tokenUrl: TOKEN_URL3,
1470
+ authorizationUrl: AUTH_URL5,
1471
+ tokenUrl: TOKEN_URL5,
810
1472
  scopes: SCOPES3,
811
1473
  clientIdEnv: "MS_OAUTH_CLIENT_ID",
812
1474
  clientSecretEnv: "MS_OAUTH_CLIENT_SECRET"
@@ -858,7 +1520,7 @@ function microsoftCalendar(opts) {
858
1520
  }
859
1521
  const userPrincipal = readMetaString2(inv.source.metadata, "userPrincipal");
860
1522
  const { timeMin, timeMax } = inv.args;
861
- const accessToken = await ensureFreshAccessToken3(inv.source.credentials, clientId, clientSecret);
1523
+ const accessToken = await ensureFreshAccessToken5(inv.source.credentials, clientId, clientSecret);
862
1524
  const busy = await getScheduleBusy({ accessToken, userPrincipal, timeMin, timeMax });
863
1525
  return {
864
1526
  data: { busy },
@@ -871,7 +1533,7 @@ function microsoftCalendar(opts) {
871
1533
  }
872
1534
  const userPrincipal = readMetaString2(inv.source.metadata, "userPrincipal");
873
1535
  const { start, end, summary, description, attendees } = inv.args;
874
- const accessToken = await ensureFreshAccessToken3(inv.source.credentials, clientId, clientSecret);
1536
+ const accessToken = await ensureFreshAccessToken5(inv.source.credentials, clientId, clientSecret);
875
1537
  const busy = await getScheduleBusy({ accessToken, userPrincipal, timeMin: start, timeMax: end });
876
1538
  if (busy.length > 0) {
877
1539
  const startMs = Date.parse(start);
@@ -936,7 +1598,7 @@ function microsoftCalendar(opts) {
936
1598
  throw new Error("Microsoft OAuth client not configured (MS_OAUTH_CLIENT_ID / _SECRET)");
937
1599
  }
938
1600
  const tokens = await exchangeAuthorizationCode({
939
- tokenUrl: TOKEN_URL3,
1601
+ tokenUrl: TOKEN_URL5,
940
1602
  clientId,
941
1603
  clientSecret,
942
1604
  code: input.code,
@@ -961,7 +1623,7 @@ function microsoftCalendar(opts) {
961
1623
  throw new Error("microsoft-calendar.refreshToken: missing refresh token");
962
1624
  }
963
1625
  const refreshed = await refreshAccessToken({
964
- tokenUrl: TOKEN_URL3,
1626
+ tokenUrl: TOKEN_URL5,
965
1627
  clientId,
966
1628
  clientSecret,
967
1629
  refreshToken: creds.refreshToken
@@ -975,7 +1637,7 @@ function microsoftCalendar(opts) {
975
1637
  },
976
1638
  async test(source) {
977
1639
  try {
978
- const accessToken = await ensureFreshAccessToken3(source.credentials, clientId, clientSecret);
1640
+ const accessToken = await ensureFreshAccessToken5(source.credentials, clientId, clientSecret);
979
1641
  const res = await fetch("https://graph.microsoft.com/v1.0/me?$select=id", {
980
1642
  headers: { authorization: `Bearer ${accessToken}` },
981
1643
  signal: AbortSignal.timeout(8e3)
@@ -1048,7 +1710,7 @@ async function findNextFreeSlots2(input) {
1048
1710
  }
1049
1711
  return out.slice(0, input.wanted);
1050
1712
  }
1051
- async function ensureFreshAccessToken3(creds, clientId, clientSecret) {
1713
+ async function ensureFreshAccessToken5(creds, clientId, clientSecret) {
1052
1714
  if (creds.kind !== "oauth2") {
1053
1715
  throw new Error("microsoft-calendar: expected oauth2 credentials");
1054
1716
  }
@@ -1059,7 +1721,7 @@ async function ensureFreshAccessToken3(creds, clientId, clientSecret) {
1059
1721
  throw new CredentialsExpired("Microsoft Calendar access token expired and no refresh token", "");
1060
1722
  }
1061
1723
  const refreshed = await refreshAccessToken({
1062
- tokenUrl: TOKEN_URL3,
1724
+ tokenUrl: TOKEN_URL5,
1063
1725
  clientId,
1064
1726
  clientSecret,
1065
1727
  refreshToken: creds.refreshToken
@@ -1082,9 +1744,9 @@ var SCOPES4 = [
1082
1744
  "crm.objects.contacts.read",
1083
1745
  "crm.objects.contacts.write"
1084
1746
  ];
1085
- var AUTH_URL4 = "https://app.hubspot.com/oauth/authorize";
1086
- var TOKEN_URL4 = "https://api.hubapi.com/oauth/v1/token";
1087
- var API = "https://api.hubapi.com";
1747
+ var AUTH_URL6 = "https://app.hubspot.com/oauth/authorize";
1748
+ var TOKEN_URL6 = "https://api.hubapi.com/oauth/v1/token";
1749
+ var API3 = "https://api.hubapi.com";
1088
1750
  function hubspot(opts) {
1089
1751
  const { clientId, clientSecret } = opts;
1090
1752
  const adapter = {
@@ -1094,8 +1756,8 @@ function hubspot(opts) {
1094
1756
  description: "Look up callers in HubSpot, upsert contacts without duplicates, and log call notes as CRM activities. Three capabilities \u2014 the voice-agent's CRM hot path.",
1095
1757
  auth: {
1096
1758
  kind: "oauth2",
1097
- authorizationUrl: AUTH_URL4,
1098
- tokenUrl: TOKEN_URL4,
1759
+ authorizationUrl: AUTH_URL6,
1760
+ tokenUrl: TOKEN_URL6,
1099
1761
  scopes: SCOPES4,
1100
1762
  clientIdEnv: "HUBSPOT_OAUTH_CLIENT_ID",
1101
1763
  clientSecretEnv: "HUBSPOT_OAUTH_CLIENT_SECRET"
@@ -1154,8 +1816,8 @@ function hubspot(opts) {
1154
1816
  throw new Error(`hubspot: unknown read capability ${inv.capabilityName}`);
1155
1817
  }
1156
1818
  const { email } = inv.args;
1157
- const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
1158
- const res = await fetch(`${API}/crm/v3/objects/contacts/search`, {
1819
+ const accessToken = await ensureFreshAccessToken6(inv.source.credentials, clientId, clientSecret);
1820
+ const res = await fetch(`${API3}/crm/v3/objects/contacts/search`, {
1159
1821
  method: "POST",
1160
1822
  headers: {
1161
1823
  authorization: `Bearer ${accessToken}`,
@@ -1187,7 +1849,7 @@ function hubspot(opts) {
1187
1849
  };
1188
1850
  },
1189
1851
  async executeMutation(inv) {
1190
- const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
1852
+ const accessToken = await ensureFreshAccessToken6(inv.source.credentials, clientId, clientSecret);
1191
1853
  if (inv.capabilityName === "upsert_contact") {
1192
1854
  return upsertContact(inv, accessToken);
1193
1855
  }
@@ -1201,7 +1863,7 @@ function hubspot(opts) {
1201
1863
  throw new Error("HubSpot OAuth client not configured (HUBSPOT_OAUTH_CLIENT_ID / _SECRET)");
1202
1864
  }
1203
1865
  const tokens = await exchangeAuthorizationCode({
1204
- tokenUrl: TOKEN_URL4,
1866
+ tokenUrl: TOKEN_URL6,
1205
1867
  clientId,
1206
1868
  clientSecret,
1207
1869
  code: input.code,
@@ -1224,7 +1886,7 @@ function hubspot(opts) {
1224
1886
  throw new Error("hubspot.refreshToken: missing refresh token");
1225
1887
  }
1226
1888
  const refreshed = await refreshAccessToken({
1227
- tokenUrl: TOKEN_URL4,
1889
+ tokenUrl: TOKEN_URL6,
1228
1890
  clientId,
1229
1891
  clientSecret,
1230
1892
  refreshToken: creds.refreshToken
@@ -1238,8 +1900,8 @@ function hubspot(opts) {
1238
1900
  },
1239
1901
  async test(source) {
1240
1902
  try {
1241
- const accessToken = await ensureFreshAccessToken4(source.credentials, clientId, clientSecret);
1242
- const res = await fetch(`${API}/oauth/v1/access-tokens/${encodeURIComponent(accessToken)}`, {
1903
+ const accessToken = await ensureFreshAccessToken6(source.credentials, clientId, clientSecret);
1904
+ const res = await fetch(`${API3}/oauth/v1/access-tokens/${encodeURIComponent(accessToken)}`, {
1243
1905
  signal: AbortSignal.timeout(8e3)
1244
1906
  });
1245
1907
  if (res.status === 401 || res.status === 403 || res.status === 404) {
@@ -1257,7 +1919,7 @@ function hubspot(opts) {
1257
1919
  async function upsertContact(inv, accessToken) {
1258
1920
  const { email, properties } = inv.args;
1259
1921
  const idemKey = sanitizeIdempotencyKey(inv.idempotencyKey);
1260
- const url = `${API}/crm/v3/objects/contacts/batch/upsert?idempotencyKey=${encodeURIComponent(idemKey)}`;
1922
+ const url = `${API3}/crm/v3/objects/contacts/batch/upsert?idempotencyKey=${encodeURIComponent(idemKey)}`;
1261
1923
  const body = {
1262
1924
  inputs: [
1263
1925
  {
@@ -1303,7 +1965,7 @@ async function upsertContact(inv, accessToken) {
1303
1965
  async function createNote(inv, accessToken) {
1304
1966
  const { contactId, body } = inv.args;
1305
1967
  const idemKey = sanitizeIdempotencyKey(inv.idempotencyKey);
1306
- const url = `${API}/crm/v3/objects/notes/batch/create?idempotencyKey=${encodeURIComponent(idemKey)}`;
1968
+ const url = `${API3}/crm/v3/objects/notes/batch/create?idempotencyKey=${encodeURIComponent(idemKey)}`;
1307
1969
  const payload = {
1308
1970
  inputs: [
1309
1971
  {
@@ -1356,7 +2018,7 @@ async function createNote(inv, accessToken) {
1356
2018
  function sanitizeIdempotencyKey(k) {
1357
2019
  return k.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 64);
1358
2020
  }
1359
- async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
2021
+ async function ensureFreshAccessToken6(creds, clientId, clientSecret) {
1360
2022
  if (creds.kind !== "oauth2") {
1361
2023
  throw new Error("hubspot: expected oauth2 credentials");
1362
2024
  }
@@ -1367,7 +2029,7 @@ async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
1367
2029
  throw new CredentialsExpired("HubSpot access token expired and no refresh token", "");
1368
2030
  }
1369
2031
  const refreshed = await refreshAccessToken({
1370
- tokenUrl: TOKEN_URL4,
2032
+ tokenUrl: TOKEN_URL6,
1371
2033
  clientId,
1372
2034
  clientSecret,
1373
2035
  refreshToken: creds.refreshToken
@@ -1380,9 +2042,9 @@ async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
1380
2042
 
1381
2043
  // src/connectors/adapters/slack.ts
1382
2044
  var SCOPES5 = ["chat:write", "users:read", "users:read.email", "channels:read"];
1383
- var AUTH_URL5 = "https://slack.com/oauth/v2/authorize";
1384
- var TOKEN_URL5 = "https://slack.com/api/oauth.v2.access";
1385
- var API2 = "https://slack.com/api";
2045
+ var AUTH_URL7 = "https://slack.com/oauth/v2/authorize";
2046
+ var TOKEN_URL7 = "https://slack.com/api/oauth.v2.access";
2047
+ var API4 = "https://slack.com/api";
1386
2048
  function slack(opts) {
1387
2049
  const { clientId, clientSecret } = opts;
1388
2050
  const adapter = {
@@ -1398,8 +2060,8 @@ function slack(opts) {
1398
2060
  description: "Post messages from the agent into Slack, look up users by email, and list channels. Advisory surface \u2014 Slack posts are informational, not transactional.",
1399
2061
  auth: {
1400
2062
  kind: "oauth2",
1401
- authorizationUrl: AUTH_URL5,
1402
- tokenUrl: TOKEN_URL5,
2063
+ authorizationUrl: AUTH_URL7,
2064
+ tokenUrl: TOKEN_URL7,
1403
2065
  scopes: SCOPES5,
1404
2066
  clientIdEnv: "SLACK_OAUTH_CLIENT_ID",
1405
2067
  clientSecretEnv: "SLACK_OAUTH_CLIENT_SECRET"
@@ -1451,7 +2113,7 @@ function slack(opts) {
1451
2113
  const accessToken = readBotToken(inv.source.credentials);
1452
2114
  if (inv.capabilityName === "lookup_user") {
1453
2115
  const { email } = inv.args;
1454
- const url = `${API2}/users.lookupByEmail?email=${encodeURIComponent(email)}`;
2116
+ const url = `${API4}/users.lookupByEmail?email=${encodeURIComponent(email)}`;
1455
2117
  const json = await slackGet(url, accessToken, inv.source.id);
1456
2118
  if (!json.ok) {
1457
2119
  if (json.error === "users_not_found") {
@@ -1471,7 +2133,7 @@ function slack(opts) {
1471
2133
  limit: String(Math.min(Math.max(1, limit ?? 200), 1e3)),
1472
2134
  types: types ?? "public_channel,private_channel"
1473
2135
  });
1474
- const json = await slackGet(`${API2}/conversations.list?${params.toString()}`, accessToken, inv.source.id);
2136
+ const json = await slackGet(`${API4}/conversations.list?${params.toString()}`, accessToken, inv.source.id);
1475
2137
  if (!json.ok) {
1476
2138
  throw new Error(`slack list_channels: ${json.error ?? "unknown"}`);
1477
2139
  }
@@ -1489,7 +2151,7 @@ function slack(opts) {
1489
2151
  }
1490
2152
  const accessToken = readBotToken(inv.source.credentials);
1491
2153
  const { channel, text, blocks } = inv.args;
1492
- const res = await fetch(`${API2}/chat.postMessage`, {
2154
+ const res = await fetch(`${API4}/chat.postMessage`, {
1493
2155
  method: "POST",
1494
2156
  headers: {
1495
2157
  authorization: `Bearer ${accessToken}`,
@@ -1524,7 +2186,7 @@ function slack(opts) {
1524
2186
  throw new Error("Slack OAuth client not configured (SLACK_OAUTH_CLIENT_ID / _SECRET)");
1525
2187
  }
1526
2188
  const tokens = await exchangeAuthorizationCode({
1527
- tokenUrl: TOKEN_URL5,
2189
+ tokenUrl: TOKEN_URL7,
1528
2190
  clientId,
1529
2191
  clientSecret,
1530
2192
  code: input.code,
@@ -1547,7 +2209,7 @@ function slack(opts) {
1547
2209
  return creds;
1548
2210
  }
1549
2211
  const refreshed = await refreshAccessToken({
1550
- tokenUrl: TOKEN_URL5,
2212
+ tokenUrl: TOKEN_URL7,
1551
2213
  clientId,
1552
2214
  clientSecret,
1553
2215
  refreshToken: creds.refreshToken
@@ -1562,7 +2224,7 @@ function slack(opts) {
1562
2224
  async test(source) {
1563
2225
  try {
1564
2226
  const accessToken = readBotToken(source.credentials);
1565
- const res = await fetch(`${API2}/auth.test`, {
2227
+ const res = await fetch(`${API4}/auth.test`, {
1566
2228
  method: "POST",
1567
2229
  headers: { authorization: `Bearer ${accessToken}` },
1568
2230
  signal: AbortSignal.timeout(8e3)
@@ -1602,9 +2264,9 @@ async function slackGet(url, accessToken, dataSourceId) {
1602
2264
  }
1603
2265
 
1604
2266
  // src/connectors/adapters/notion-database.ts
1605
- var AUTH_URL6 = "https://api.notion.com/v1/oauth/authorize";
1606
- var TOKEN_URL6 = "https://api.notion.com/v1/oauth/token";
1607
- var API3 = "https://api.notion.com/v1";
2267
+ var AUTH_URL8 = "https://api.notion.com/v1/oauth/authorize";
2268
+ var TOKEN_URL8 = "https://api.notion.com/v1/oauth/token";
2269
+ var API5 = "https://api.notion.com/v1";
1608
2270
  var NOTION_VERSION = "2022-06-28";
1609
2271
  function notionDatabase(opts) {
1610
2272
  const { clientId, clientSecret } = opts;
@@ -1615,8 +2277,8 @@ function notionDatabase(opts) {
1615
2277
  description: "Query a Notion database, create new pages, and update existing ones with optimistic concurrency via last_edited_time.",
1616
2278
  auth: {
1617
2279
  kind: "oauth2",
1618
- authorizationUrl: AUTH_URL6,
1619
- tokenUrl: TOKEN_URL6,
2280
+ authorizationUrl: AUTH_URL8,
2281
+ tokenUrl: TOKEN_URL8,
1620
2282
  // Notion does not use OAuth scopes — the workspace owner picks
1621
2283
  // which pages/databases the integration sees during install. We
1622
2284
  // declare an empty scope list so the consent screen renders cleanly.
@@ -1691,7 +2353,7 @@ function notionDatabase(opts) {
1691
2353
  };
1692
2354
  if (filter) body.filter = filter;
1693
2355
  if (startCursor) body.start_cursor = startCursor;
1694
- const res = await fetch(`${API3}/databases/${encodeURIComponent(databaseId)}/query`, {
2356
+ const res = await fetch(`${API5}/databases/${encodeURIComponent(databaseId)}/query`, {
1695
2357
  method: "POST",
1696
2358
  headers: notionHeaders(accessToken),
1697
2359
  body: JSON.stringify(body),
@@ -1730,7 +2392,7 @@ function notionDatabase(opts) {
1730
2392
  redirect_uri: input.redirectUri,
1731
2393
  code_verifier: input.codeVerifier
1732
2394
  });
1733
- const res = await fetch(TOKEN_URL6, {
2395
+ const res = await fetch(TOKEN_URL8, {
1734
2396
  method: "POST",
1735
2397
  headers: {
1736
2398
  authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`,
@@ -1769,7 +2431,7 @@ function notionDatabase(opts) {
1769
2431
  throw new Error("notion-database.refreshToken: missing refresh token");
1770
2432
  }
1771
2433
  const refreshed = await refreshAccessToken({
1772
- tokenUrl: TOKEN_URL6,
2434
+ tokenUrl: TOKEN_URL8,
1773
2435
  clientId,
1774
2436
  clientSecret,
1775
2437
  refreshToken: creds.refreshToken
@@ -1784,7 +2446,7 @@ function notionDatabase(opts) {
1784
2446
  async test(source) {
1785
2447
  try {
1786
2448
  const accessToken = readToken(source.credentials);
1787
- const res = await fetch(`${API3}/users/me`, {
2449
+ const res = await fetch(`${API5}/users/me`, {
1788
2450
  headers: notionHeaders(accessToken),
1789
2451
  signal: AbortSignal.timeout(8e3)
1790
2452
  });
@@ -1801,7 +2463,7 @@ function notionDatabase(opts) {
1801
2463
  async function createPage(inv, accessToken) {
1802
2464
  const databaseId = readMetaString3(inv.source.metadata, "databaseId");
1803
2465
  const { properties } = inv.args;
1804
- const res = await fetch(`${API3}/pages`, {
2466
+ const res = await fetch(`${API5}/pages`, {
1805
2467
  method: "POST",
1806
2468
  headers: {
1807
2469
  ...notionHeaders(accessToken),
@@ -1835,7 +2497,7 @@ async function createPage(inv, accessToken) {
1835
2497
  async function updatePage(inv, accessToken) {
1836
2498
  const { pageId, properties, expectedLastEditedTime } = inv.args;
1837
2499
  if (expectedLastEditedTime) {
1838
- const headRes = await fetch(`${API3}/pages/${encodeURIComponent(pageId)}`, {
2500
+ const headRes = await fetch(`${API5}/pages/${encodeURIComponent(pageId)}`, {
1839
2501
  headers: notionHeaders(accessToken),
1840
2502
  signal: AbortSignal.timeout(1e4)
1841
2503
  });
@@ -1855,7 +2517,7 @@ async function updatePage(inv, accessToken) {
1855
2517
  );
1856
2518
  }
1857
2519
  }
1858
- const res = await fetch(`${API3}/pages/${encodeURIComponent(pageId)}`, {
2520
+ const res = await fetch(`${API5}/pages/${encodeURIComponent(pageId)}`, {
1859
2521
  method: "PATCH",
1860
2522
  headers: notionHeaders(accessToken),
1861
2523
  body: JSON.stringify({ properties }),
@@ -1901,6 +2563,304 @@ function readMetaString3(meta, key) {
1901
2563
  return v;
1902
2564
  }
1903
2565
 
2566
+ // src/connectors/adapters/docuseal.ts
2567
+ import { createHmac, timingSafeEqual } from "crypto";
2568
+ var DEFAULT_BASE = "https://api.docuseal.com";
2569
+ function readDocuSealCredentials(creds) {
2570
+ if (creds.kind === "api-key" && typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
2571
+ return { apiKey: creds.apiKey };
2572
+ }
2573
+ if (creds.kind === "custom" && creds.values && typeof creds.values.apiKey === "string" && creds.values.apiKey.length > 0) {
2574
+ const webhookSecret = typeof creds.values.webhookSecret === "string" ? creds.values.webhookSecret : void 0;
2575
+ return { apiKey: creds.values.apiKey, webhookSecret };
2576
+ }
2577
+ throw new Error("docuseal: expected api-key credentials (apiKey + optional webhookSecret)");
2578
+ }
2579
+ function docuseal(opts = {}) {
2580
+ const baseUrl = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/$/, "");
2581
+ const timeoutMs = opts.timeoutMs ?? 3e4;
2582
+ const adapter = {
2583
+ manifest: {
2584
+ kind: "docuseal",
2585
+ displayName: "DocuSeal",
2586
+ description: "Send documents for e-signature via DocuSeal, poll submission status, void in-flight submissions, and react to push events when submitters sign.",
2587
+ auth: {
2588
+ kind: "api-key",
2589
+ hint: "Paste a DocuSeal personal API key (settings \u2192 API). Optional webhook secret enables push-driven workflows."
2590
+ },
2591
+ category: "doc",
2592
+ defaultConsistencyModel: "authoritative",
2593
+ capabilities: [
2594
+ {
2595
+ name: "get_submission",
2596
+ class: "read",
2597
+ description: "Fetch the current status of a DocuSeal submission and each submitter.",
2598
+ parameters: {
2599
+ type: "object",
2600
+ properties: { submissionId: { type: "string" } },
2601
+ required: ["submissionId"]
2602
+ }
2603
+ },
2604
+ {
2605
+ name: "create_submission",
2606
+ class: "mutation",
2607
+ description: "Send a template for signature to one or more submitters. external_id is the idempotency key; retries return the original submission.",
2608
+ cas: "native-idempotency",
2609
+ externalEffect: true,
2610
+ parameters: {
2611
+ type: "object",
2612
+ properties: {
2613
+ templateId: { type: "string" },
2614
+ submitters: {
2615
+ type: "array",
2616
+ items: {
2617
+ type: "object",
2618
+ properties: {
2619
+ email: { type: "string" },
2620
+ name: { type: "string" },
2621
+ role: { type: "string" },
2622
+ values: { type: "object", additionalProperties: true }
2623
+ },
2624
+ required: ["email"]
2625
+ }
2626
+ },
2627
+ sendEmail: { type: "boolean", default: true },
2628
+ message: { type: "string" }
2629
+ },
2630
+ required: ["templateId", "submitters"]
2631
+ }
2632
+ },
2633
+ {
2634
+ name: "void_submission",
2635
+ class: "mutation",
2636
+ description: "Cancel an in-flight submission. CAS via updated_at \u2014 concurrent voids return ResourceContention.",
2637
+ cas: "etag-if-match",
2638
+ externalEffect: true,
2639
+ parameters: {
2640
+ type: "object",
2641
+ properties: {
2642
+ submissionId: { type: "string" },
2643
+ reason: { type: "string" }
2644
+ },
2645
+ required: ["submissionId"]
2646
+ }
2647
+ }
2648
+ ]
2649
+ },
2650
+ async executeRead(inv) {
2651
+ if (inv.capabilityName !== "get_submission") {
2652
+ throw new Error(`docuseal: unknown read capability ${inv.capabilityName}`);
2653
+ }
2654
+ const { apiKey } = readDocuSealCredentials(inv.source.credentials);
2655
+ const { submissionId } = inv.args;
2656
+ const res = await fetch(`${baseUrl}/submissions/${encodeURIComponent(submissionId)}`, {
2657
+ headers: { "X-Auth-Token": apiKey, accept: "application/json" },
2658
+ signal: AbortSignal.timeout(timeoutMs)
2659
+ });
2660
+ if (res.status === 401) throw new CredentialsExpired("DocuSeal rejected API key (401)", inv.source.id);
2661
+ if (res.status === 404) {
2662
+ throw new Error(`docuseal get_submission: submission ${submissionId} not found`);
2663
+ }
2664
+ if (!res.ok) {
2665
+ const text = await res.text().catch(() => "");
2666
+ throw new Error(`docuseal get_submission ${res.status}: ${text.slice(0, 200)}`);
2667
+ }
2668
+ const json = await res.json();
2669
+ return {
2670
+ data: normalizeSubmission(json),
2671
+ etag: json.updated_at,
2672
+ fetchedAt: Date.now()
2673
+ };
2674
+ },
2675
+ async executeMutation(inv) {
2676
+ const { apiKey } = readDocuSealCredentials(inv.source.credentials);
2677
+ if (inv.capabilityName === "create_submission") return createSubmission(inv, apiKey, baseUrl, timeoutMs);
2678
+ if (inv.capabilityName === "void_submission") return voidSubmission(inv, apiKey, baseUrl, timeoutMs);
2679
+ throw new Error(`docuseal: unknown mutation capability ${inv.capabilityName}`);
2680
+ },
2681
+ verifySignature({ rawBody, headers, source }) {
2682
+ const creds = (() => {
2683
+ try {
2684
+ return readDocuSealCredentials(source.credentials);
2685
+ } catch {
2686
+ return null;
2687
+ }
2688
+ })();
2689
+ if (!creds?.webhookSecret) return { valid: false, reason: "missing_webhook_secret" };
2690
+ const sig = firstHeader(headers, "x-docuseal-signature");
2691
+ if (!sig) return { valid: false, reason: "missing_signature_header" };
2692
+ const expected = createHmac("sha256", creds.webhookSecret).update(rawBody).digest("hex");
2693
+ const a = Buffer.from(sig.toLowerCase(), "utf-8");
2694
+ const b = Buffer.from(expected, "utf-8");
2695
+ if (a.length !== b.length) return { valid: false, reason: "invalid_signature" };
2696
+ return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: "invalid_signature" };
2697
+ },
2698
+ async handleInboundEvent({ rawBody }) {
2699
+ let parsed;
2700
+ try {
2701
+ parsed = JSON.parse(rawBody);
2702
+ } catch {
2703
+ return { events: [], response: { status: 400, body: { error: "invalid_json" } } };
2704
+ }
2705
+ if (!parsed || typeof parsed !== "object") {
2706
+ return { events: [], response: { status: 400, body: { error: "invalid_payload" } } };
2707
+ }
2708
+ const evt = parsed;
2709
+ const eventType = typeof evt.event_type === "string" ? `docuseal.${evt.event_type}` : "docuseal.unknown";
2710
+ const providerEventId = typeof evt.event_id === "string" ? evt.event_id : void 0;
2711
+ const events = [
2712
+ {
2713
+ eventType,
2714
+ providerEventId,
2715
+ payload: evt
2716
+ }
2717
+ ];
2718
+ return { events };
2719
+ },
2720
+ async test(source) {
2721
+ try {
2722
+ const { apiKey } = readDocuSealCredentials(source.credentials);
2723
+ const res = await fetch(`${baseUrl}/templates?limit=1`, {
2724
+ headers: { "X-Auth-Token": apiKey },
2725
+ signal: AbortSignal.timeout(8e3)
2726
+ });
2727
+ if (res.status === 401) return { ok: false, reason: "DocuSeal rejected API key (401) \u2014 reconnect required" };
2728
+ if (!res.ok) return { ok: false, reason: `DocuSeal returned ${res.status}` };
2729
+ return { ok: true };
2730
+ } catch (err) {
2731
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
2732
+ }
2733
+ }
2734
+ };
2735
+ return adapter;
2736
+ }
2737
+ function normalizeSubmission(s) {
2738
+ return {
2739
+ submissionId: String(s.id),
2740
+ status: s.status ?? "pending",
2741
+ updatedAt: s.updated_at,
2742
+ createdAt: s.created_at,
2743
+ completedAt: s.completed_at ?? void 0,
2744
+ auditLogUrl: s.audit_log_url,
2745
+ combinedDocumentUrl: s.combined_document_url,
2746
+ submitters: (s.submitters ?? []).map((sub) => ({
2747
+ email: sub.email,
2748
+ name: sub.name,
2749
+ role: sub.role,
2750
+ slug: sub.slug,
2751
+ status: sub.status ?? "awaiting",
2752
+ completedAt: sub.completed_at ?? void 0,
2753
+ url: sub.embed_src
2754
+ }))
2755
+ };
2756
+ }
2757
+ async function createSubmission(inv, apiKey, baseUrl, timeoutMs) {
2758
+ const { templateId, submitters, sendEmail, message } = inv.args;
2759
+ const body = {
2760
+ template_id: templateId,
2761
+ external_id: inv.idempotencyKey,
2762
+ send_email: sendEmail ?? true,
2763
+ message,
2764
+ submitters: submitters.map((s) => ({
2765
+ email: s.email,
2766
+ name: s.name,
2767
+ role: s.role,
2768
+ values: s.values
2769
+ }))
2770
+ };
2771
+ const res = await fetch(`${baseUrl}/submissions`, {
2772
+ method: "POST",
2773
+ headers: {
2774
+ "X-Auth-Token": apiKey,
2775
+ "content-type": "application/json",
2776
+ accept: "application/json"
2777
+ },
2778
+ body: JSON.stringify(body),
2779
+ signal: AbortSignal.timeout(timeoutMs)
2780
+ });
2781
+ if (res.status === 401) throw new CredentialsExpired("DocuSeal rejected API key (401)", inv.source.id);
2782
+ if (res.status === 429) {
2783
+ const retryAfter = Number(res.headers.get("retry-after") ?? "5");
2784
+ return {
2785
+ status: "rate-limited",
2786
+ retryAfterMs: Number.isFinite(retryAfter) ? retryAfter * 1e3 : 5e3,
2787
+ message: "DocuSeal rate-limited create_submission"
2788
+ };
2789
+ }
2790
+ if (res.status === 409) {
2791
+ const conflictJson = await res.json().catch(() => ({}));
2792
+ const original = conflictJson.submission ?? conflictJson;
2793
+ return {
2794
+ status: "committed",
2795
+ data: normalizeSubmission(original),
2796
+ etagAfter: original.updated_at,
2797
+ committedAt: Date.now(),
2798
+ idempotentReplay: true
2799
+ };
2800
+ }
2801
+ if (!res.ok) {
2802
+ const text = await res.text().catch(() => "");
2803
+ throw new Error(`docuseal create_submission ${res.status}: ${text.slice(0, 200)}`);
2804
+ }
2805
+ const created = await res.json();
2806
+ const sub = Array.isArray(created) ? created[0] : created;
2807
+ if (!sub) {
2808
+ throw new Error("docuseal create_submission: empty response body");
2809
+ }
2810
+ return {
2811
+ status: "committed",
2812
+ data: normalizeSubmission(sub),
2813
+ etagAfter: sub.updated_at,
2814
+ committedAt: Date.now(),
2815
+ idempotentReplay: false
2816
+ };
2817
+ }
2818
+ async function voidSubmission(inv, apiKey, baseUrl, timeoutMs) {
2819
+ const { submissionId, reason } = inv.args;
2820
+ const headers = {
2821
+ "X-Auth-Token": apiKey,
2822
+ accept: "application/json"
2823
+ };
2824
+ if (inv.expectedEtag) headers["if-match"] = inv.expectedEtag;
2825
+ if (reason) headers["x-docuseal-void-reason"] = reason;
2826
+ const res = await fetch(`${baseUrl}/submissions/${encodeURIComponent(submissionId)}`, {
2827
+ method: "DELETE",
2828
+ headers,
2829
+ signal: AbortSignal.timeout(timeoutMs)
2830
+ });
2831
+ if (res.status === 401) throw new CredentialsExpired("DocuSeal rejected API key (401)", inv.source.id);
2832
+ if (res.status === 404) {
2833
+ throw new Error(`docuseal void_submission: submission ${submissionId} not found`);
2834
+ }
2835
+ if (res.status === 412) {
2836
+ throw new ResourceContention(`docuseal void_submission: submission ${submissionId} updated since last read`);
2837
+ }
2838
+ if (res.status === 429) {
2839
+ const retryAfter = Number(res.headers.get("retry-after") ?? "5");
2840
+ return {
2841
+ status: "rate-limited",
2842
+ retryAfterMs: Number.isFinite(retryAfter) ? retryAfter * 1e3 : 5e3,
2843
+ message: "DocuSeal rate-limited void_submission"
2844
+ };
2845
+ }
2846
+ if (!res.ok) {
2847
+ const text = await res.text().catch(() => "");
2848
+ throw new Error(`docuseal void_submission ${res.status}: ${text.slice(0, 200)}`);
2849
+ }
2850
+ const json = await res.json().catch(() => ({}));
2851
+ return {
2852
+ status: "committed",
2853
+ data: {
2854
+ submissionId,
2855
+ status: "voided",
2856
+ voidedAt: json.updated_at ?? (/* @__PURE__ */ new Date()).toISOString()
2857
+ },
2858
+ etagAfter: json.updated_at,
2859
+ committedAt: Date.now(),
2860
+ idempotentReplay: false
2861
+ };
2862
+ }
2863
+
1904
2864
  // src/connectors/adapters/declarative-rest.ts
1905
2865
  function declarativeRestConnector(spec) {
1906
2866
  const capabilities = spec.capabilities.map(operationToCapability);
@@ -2100,7 +3060,7 @@ async function safeErrorText(res) {
2100
3060
  }
2101
3061
 
2102
3062
  // src/connectors/adapters/twilio-sms.ts
2103
- var API4 = "https://api.twilio.com/2010-04-01";
3063
+ var API6 = "https://api.twilio.com/2010-04-01";
2104
3064
  var LOOKUP_API = "https://lookups.twilio.com/v1";
2105
3065
  var twilioSmsConnector = {
2106
3066
  manifest: {
@@ -2192,7 +3152,7 @@ var twilioSmsConnector = {
2192
3152
  params.set("PageSize", String(Math.min(Math.max(1, limit ?? 20), 100)));
2193
3153
  if (to) params.set("To", to);
2194
3154
  if (from) params.set("From", from);
2195
- const url = `${API4}/Accounts/${encodeURIComponent(auth.accountSid)}/Messages.json?${params.toString()}`;
3155
+ const url = `${API6}/Accounts/${encodeURIComponent(auth.accountSid)}/Messages.json?${params.toString()}`;
2196
3156
  const res = await fetch(url, {
2197
3157
  headers: { authorization: basicAuth(auth) },
2198
3158
  signal: AbortSignal.timeout(1e4)
@@ -2218,7 +3178,7 @@ var twilioSmsConnector = {
2218
3178
  const { to, body, from } = inv.args;
2219
3179
  const fromNumber = from ?? readMetaString4(inv.source.metadata, "fromNumber");
2220
3180
  const formBody = new URLSearchParams({ To: to, From: fromNumber, Body: body });
2221
- const url = `${API4}/Accounts/${encodeURIComponent(auth.accountSid)}/Messages.json`;
3181
+ const url = `${API6}/Accounts/${encodeURIComponent(auth.accountSid)}/Messages.json`;
2222
3182
  const res = await fetch(url, {
2223
3183
  method: "POST",
2224
3184
  headers: {
@@ -2248,7 +3208,7 @@ var twilioSmsConnector = {
2248
3208
  async test(source) {
2249
3209
  try {
2250
3210
  const auth = parseAuth(source.credentials);
2251
- const res = await fetch(`${API4}/Accounts/${encodeURIComponent(auth.accountSid)}.json`, {
3211
+ const res = await fetch(`${API6}/Accounts/${encodeURIComponent(auth.accountSid)}.json`, {
2252
3212
  headers: { authorization: basicAuth(auth) },
2253
3213
  signal: AbortSignal.timeout(8e3)
2254
3214
  });
@@ -2293,15 +3253,15 @@ function readMetaString4(meta, key) {
2293
3253
  }
2294
3254
 
2295
3255
  // src/connectors/adapters/stripe-pack.ts
2296
- var API5 = "https://api.stripe.com/v1";
3256
+ var API7 = "https://api.stripe.com/v1";
2297
3257
  var stripePackConnector = {
2298
3258
  manifest: {
2299
3259
  kind: "stripe-pack",
2300
- displayName: "Stripe (customers, invoices, checkout)",
2301
- description: "Look up Stripe customers, draft invoices, and spin up hosted Checkout sessions from a single Stripe restricted key. Idempotency-Key forwarded on every mutation.",
3260
+ displayName: "Stripe (customers, invoices, checkout, subscriptions)",
3261
+ description: "Look up Stripe customers, draft invoices, spin up hosted Checkout sessions, manage subscriptions, and hand off to the customer billing portal \u2014 all from one Stripe restricted key. Idempotency-Key forwarded on every mutation.",
2302
3262
  auth: {
2303
3263
  kind: "api-key",
2304
- hint: "Paste a Stripe restricted key (rk_live_\u2026) with read access on customers and write access on invoices + checkout sessions."
3264
+ hint: "Paste a Stripe restricted key (rk_live_\u2026) with read on customers + subscriptions and write on invoices + checkout + subscriptions + billing portal."
2305
3265
  },
2306
3266
  category: "commerce",
2307
3267
  defaultConsistencyModel: "authoritative",
@@ -2316,6 +3276,20 @@ var stripePackConnector = {
2316
3276
  required: ["email"]
2317
3277
  }
2318
3278
  },
3279
+ {
3280
+ name: "list_subscriptions",
3281
+ class: "read",
3282
+ description: "List a customer's subscriptions. Optionally filter by status ('active', 'past_due', 'canceled', 'all').",
3283
+ parameters: {
3284
+ type: "object",
3285
+ properties: {
3286
+ customerId: { type: "string" },
3287
+ status: { type: "string", enum: ["active", "past_due", "unpaid", "canceled", "incomplete", "trialing", "all"], default: "all" },
3288
+ limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }
3289
+ },
3290
+ required: ["customerId"]
3291
+ }
3292
+ },
2319
3293
  {
2320
3294
  name: "create_invoice",
2321
3295
  class: "mutation",
@@ -2371,16 +3345,47 @@ var stripePackConnector = {
2371
3345
  },
2372
3346
  required: ["successUrl", "cancelUrl", "lineItems"]
2373
3347
  }
3348
+ },
3349
+ {
3350
+ name: "cancel_subscription",
3351
+ class: "mutation",
3352
+ description: "Cancel a subscription. Default cancels immediately; pass atPeriodEnd=true to schedule cancellation for the end of the current billing period.",
3353
+ cas: "native-idempotency",
3354
+ externalEffect: true,
3355
+ parameters: {
3356
+ type: "object",
3357
+ properties: {
3358
+ subscriptionId: { type: "string" },
3359
+ atPeriodEnd: { type: "boolean", default: false }
3360
+ },
3361
+ required: ["subscriptionId"]
3362
+ }
3363
+ },
3364
+ {
3365
+ name: "create_billing_portal_session",
3366
+ class: "mutation",
3367
+ description: "Create a Stripe billing portal session and return its URL. Hand-off for the customer to self-serve cancel/upgrade/update-card.",
3368
+ cas: "native-idempotency",
3369
+ externalEffect: true,
3370
+ parameters: {
3371
+ type: "object",
3372
+ properties: {
3373
+ customerId: { type: "string" },
3374
+ returnUrl: { type: "string" }
3375
+ },
3376
+ required: ["customerId", "returnUrl"]
3377
+ }
2374
3378
  }
2375
3379
  ]
2376
3380
  },
2377
3381
  async executeRead(inv) {
3382
+ const apiKey = readApiKey(inv.source.credentials);
3383
+ if (inv.capabilityName === "list_subscriptions") return listSubscriptions(inv, apiKey);
2378
3384
  if (inv.capabilityName !== "find_customer") {
2379
3385
  throw new Error(`stripe-pack: unknown read capability ${inv.capabilityName}`);
2380
3386
  }
2381
- const apiKey = readApiKey(inv.source.credentials);
2382
3387
  const { email } = inv.args;
2383
- const url = `${API5}/customers/search?query=${encodeURIComponent(`email:'${email.toLowerCase()}'`)}&limit=1`;
3388
+ const url = `${API7}/customers/search?query=${encodeURIComponent(`email:'${email.toLowerCase()}'`)}&limit=1`;
2384
3389
  const res = await fetch(url, {
2385
3390
  headers: { authorization: `Bearer ${apiKey}` },
2386
3391
  signal: AbortSignal.timeout(1e4)
@@ -2403,12 +3408,14 @@ var stripePackConnector = {
2403
3408
  const apiKey = readApiKey(inv.source.credentials);
2404
3409
  if (inv.capabilityName === "create_invoice") return createInvoice(inv, apiKey);
2405
3410
  if (inv.capabilityName === "create_checkout_session") return createCheckoutSession(inv, apiKey);
3411
+ if (inv.capabilityName === "cancel_subscription") return cancelSubscription(inv, apiKey);
3412
+ if (inv.capabilityName === "create_billing_portal_session") return createBillingPortalSession(inv, apiKey);
2406
3413
  throw new Error(`stripe-pack: unknown mutation capability ${inv.capabilityName}`);
2407
3414
  },
2408
3415
  async test(source) {
2409
3416
  try {
2410
3417
  const apiKey = readApiKey(source.credentials);
2411
- const res = await fetch(`${API5}/account`, {
3418
+ const res = await fetch(`${API7}/account`, {
2412
3419
  headers: { authorization: `Bearer ${apiKey}` },
2413
3420
  signal: AbortSignal.timeout(8e3)
2414
3421
  });
@@ -2434,7 +3441,7 @@ async function createInvoice(inv, apiKey) {
2434
3441
  quantity: String(it.quantity ?? 1)
2435
3442
  });
2436
3443
  if (it.description) body.set("description", it.description);
2437
- const res = await fetch(`${API5}/invoiceitems`, {
3444
+ const res = await fetch(`${API7}/invoiceitems`, {
2438
3445
  method: "POST",
2439
3446
  headers: {
2440
3447
  authorization: `Bearer ${apiKey}`,
@@ -2459,7 +3466,7 @@ async function createInvoice(inv, apiKey) {
2459
3466
  collection_method: "send_invoice",
2460
3467
  days_until_due: "14"
2461
3468
  });
2462
- const invRes = await fetch(`${API5}/invoices`, {
3469
+ const invRes = await fetch(`${API7}/invoices`, {
2463
3470
  method: "POST",
2464
3471
  headers: {
2465
3472
  authorization: `Bearer ${apiKey}`,
@@ -2497,7 +3504,7 @@ async function createCheckoutSession(inv, apiKey) {
2497
3504
  body.set(`line_items[${i}][price]`, it.price);
2498
3505
  body.set(`line_items[${i}][quantity]`, String(it.quantity ?? 1));
2499
3506
  });
2500
- const res = await fetch(`${API5}/checkout/sessions`, {
3507
+ const res = await fetch(`${API7}/checkout/sessions`, {
2501
3508
  method: "POST",
2502
3509
  headers: {
2503
3510
  authorization: `Bearer ${apiKey}`,
@@ -2523,6 +3530,118 @@ async function createCheckoutSession(inv, apiKey) {
2523
3530
  idempotentReplay: false
2524
3531
  };
2525
3532
  }
3533
+ async function listSubscriptions(inv, apiKey) {
3534
+ const { customerId, status, limit } = inv.args;
3535
+ const params = new URLSearchParams({ customer: customerId, limit: String(limit ?? 10) });
3536
+ if (status && status !== "all") params.set("status", status);
3537
+ else params.set("status", "all");
3538
+ const res = await fetch(`${API7}/subscriptions?${params.toString()}`, {
3539
+ headers: { authorization: `Bearer ${apiKey}` },
3540
+ signal: AbortSignal.timeout(1e4)
3541
+ });
3542
+ if (res.status === 401) {
3543
+ throw new CredentialsExpired("Stripe rejected API key (401)", inv.source.id);
3544
+ }
3545
+ if (!res.ok) {
3546
+ const text = await res.text().catch(() => "");
3547
+ throw new Error(`stripe-pack list_subscriptions ${res.status}: ${text.slice(0, 200)}`);
3548
+ }
3549
+ const json = await res.json();
3550
+ const subscriptions = (json.data ?? []).map((sub) => ({
3551
+ id: sub.id,
3552
+ status: sub.status,
3553
+ currentPeriodEnd: sub.current_period_end,
3554
+ cancelAtPeriodEnd: sub.cancel_at_period_end ?? false,
3555
+ items: (sub.items?.data ?? []).map((it) => ({
3556
+ priceId: it.price?.id,
3557
+ productId: it.price?.product,
3558
+ unitAmount: it.price?.unit_amount,
3559
+ currency: it.price?.currency,
3560
+ interval: it.price?.recurring?.interval
3561
+ }))
3562
+ }));
3563
+ return {
3564
+ data: { subscriptions },
3565
+ fetchedAt: Date.now()
3566
+ };
3567
+ }
3568
+ async function cancelSubscription(inv, apiKey) {
3569
+ const { subscriptionId, atPeriodEnd } = inv.args;
3570
+ let res;
3571
+ if (atPeriodEnd) {
3572
+ const body = new URLSearchParams({ cancel_at_period_end: "true" });
3573
+ res = await fetch(`${API7}/subscriptions/${encodeURIComponent(subscriptionId)}`, {
3574
+ method: "POST",
3575
+ headers: {
3576
+ authorization: `Bearer ${apiKey}`,
3577
+ "content-type": "application/x-www-form-urlencoded",
3578
+ "idempotency-key": inv.idempotencyKey
3579
+ },
3580
+ body,
3581
+ signal: AbortSignal.timeout(15e3)
3582
+ });
3583
+ } else {
3584
+ res = await fetch(`${API7}/subscriptions/${encodeURIComponent(subscriptionId)}`, {
3585
+ method: "DELETE",
3586
+ headers: {
3587
+ authorization: `Bearer ${apiKey}`,
3588
+ "idempotency-key": inv.idempotencyKey
3589
+ },
3590
+ signal: AbortSignal.timeout(15e3)
3591
+ });
3592
+ }
3593
+ if (res.status === 401) throw new CredentialsExpired("Stripe rejected API key (401)", inv.source.id);
3594
+ if (res.status === 404) throw new Error(`stripe-pack cancel_subscription: subscription ${subscriptionId} not found`);
3595
+ if (res.status === 409) {
3596
+ throw new ResourceContention("Stripe subscription conflict \u2014 retry rejected by idempotency check");
3597
+ }
3598
+ if (!res.ok) {
3599
+ const text = await res.text().catch(() => "");
3600
+ throw new Error(`stripe-pack cancel_subscription ${res.status}: ${text.slice(0, 200)}`);
3601
+ }
3602
+ const updated = await res.json();
3603
+ return {
3604
+ status: "committed",
3605
+ data: {
3606
+ id: updated.id,
3607
+ status: updated.status,
3608
+ cancelAtPeriodEnd: updated.cancel_at_period_end ?? false,
3609
+ canceledAt: updated.canceled_at,
3610
+ currentPeriodEnd: updated.current_period_end
3611
+ },
3612
+ committedAt: Date.now(),
3613
+ idempotentReplay: false
3614
+ };
3615
+ }
3616
+ async function createBillingPortalSession(inv, apiKey) {
3617
+ const { customerId, returnUrl } = inv.args;
3618
+ const body = new URLSearchParams({ customer: customerId, return_url: returnUrl });
3619
+ const res = await fetch(`${API7}/billing_portal/sessions`, {
3620
+ method: "POST",
3621
+ headers: {
3622
+ authorization: `Bearer ${apiKey}`,
3623
+ "content-type": "application/x-www-form-urlencoded",
3624
+ "idempotency-key": inv.idempotencyKey
3625
+ },
3626
+ body,
3627
+ signal: AbortSignal.timeout(15e3)
3628
+ });
3629
+ if (res.status === 401) throw new CredentialsExpired("Stripe rejected API key (401)", inv.source.id);
3630
+ if (res.status === 409) {
3631
+ throw new ResourceContention("Stripe billing portal session conflict \u2014 retry rejected by idempotency check");
3632
+ }
3633
+ if (!res.ok) {
3634
+ const text = await res.text().catch(() => "");
3635
+ throw new Error(`stripe-pack create_billing_portal_session ${res.status}: ${text.slice(0, 200)}`);
3636
+ }
3637
+ const created = await res.json();
3638
+ return {
3639
+ status: "committed",
3640
+ data: { sessionId: created.id, url: created.url, returnUrl: created.return_url },
3641
+ committedAt: Date.now(),
3642
+ idempotentReplay: false
3643
+ };
3644
+ }
2526
3645
  function readApiKey(creds) {
2527
3646
  if (creds.kind !== "api-key" || typeof creds.apiKey !== "string" || creds.apiKey.length === 0) {
2528
3647
  throw new Error("stripe-pack: expected api-key credentials");
@@ -2531,7 +3650,7 @@ function readApiKey(creds) {
2531
3650
  }
2532
3651
 
2533
3652
  // src/connectors/adapters/webhook.ts
2534
- import { createHmac } from "crypto";
3653
+ import { createHmac as createHmac2 } from "crypto";
2535
3654
  var webhookConnector = {
2536
3655
  manifest: {
2537
3656
  kind: "webhook",
@@ -2645,96 +3764,12 @@ function signHeaders(creds, body, idempotencyKey) {
2645
3764
  "x-phony-idempotency-key": idempotencyKey
2646
3765
  };
2647
3766
  if (creds.kind === "hmac" && typeof creds.secret === "string" && creds.secret.length > 0) {
2648
- const sig = createHmac("sha256", creds.secret).update(`${ts}.${body}`).digest("hex");
3767
+ const sig = createHmac2("sha256", creds.secret).update(`${ts}.${body}`).digest("hex");
2649
3768
  headers["x-phony-signature"] = `sha256=${sig}`;
2650
3769
  }
2651
3770
  return headers;
2652
3771
  }
2653
3772
 
2654
- // src/connectors/webhooks.ts
2655
- import { createHmac as createHmac2, timingSafeEqual } from "crypto";
2656
- var DEFAULT_SIGNATURE_TOLERANCE_SECONDS = 5 * 60;
2657
- function parseStripeSignatureHeader(header) {
2658
- const acc = { sigs: [] };
2659
- for (const part of header.split(",")) {
2660
- const idx = part.indexOf("=");
2661
- if (idx < 0) continue;
2662
- const key = part.slice(0, idx).trim();
2663
- const val = part.slice(idx + 1).trim();
2664
- if (key === "t") {
2665
- const n = Number(val);
2666
- if (Number.isFinite(n)) acc.ts = n;
2667
- } else if (key === "v1") {
2668
- acc.sigs.push(val);
2669
- }
2670
- }
2671
- if (acc.ts === void 0 || acc.sigs.length === 0) return null;
2672
- return { t: acc.ts, sigs: acc.sigs };
2673
- }
2674
- function verifyStripeSignature(rawBody, signatureHeader, secret, options = {}) {
2675
- const parsed = parseStripeSignatureHeader(signatureHeader);
2676
- if (!parsed) return false;
2677
- const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS;
2678
- const now = options.now ?? Math.floor(Date.now() / 1e3);
2679
- if (Math.abs(now - parsed.t) > tolerance) return false;
2680
- const expected = createHmac2("sha256", secret).update(`${parsed.t}.${rawBody}`).digest("hex");
2681
- const expectedBuf = Buffer.from(expected, "utf8");
2682
- for (const sig of parsed.sigs) {
2683
- const sigBuf = Buffer.from(sig, "utf8");
2684
- if (sigBuf.length !== expectedBuf.length) continue;
2685
- if (timingSafeEqual(sigBuf, expectedBuf)) return true;
2686
- }
2687
- return false;
2688
- }
2689
- function verifySlackSignature(rawBody, signatureHeader, timestampHeader, secret, options = {}) {
2690
- if (!signatureHeader.startsWith("v0=")) return false;
2691
- const ts = Number(timestampHeader);
2692
- if (!Number.isFinite(ts)) return false;
2693
- const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS;
2694
- const now = options.now ?? Math.floor(Date.now() / 1e3);
2695
- if (Math.abs(now - ts) > tolerance) return false;
2696
- const expected = "v0=" + createHmac2("sha256", secret).update(`v0:${ts}:${rawBody}`).digest("hex");
2697
- const expectedBuf = Buffer.from(expected, "utf8");
2698
- const sigBuf = Buffer.from(signatureHeader, "utf8");
2699
- if (sigBuf.length !== expectedBuf.length) return false;
2700
- return timingSafeEqual(sigBuf, expectedBuf);
2701
- }
2702
- function verifyHmacSignature(rawBody, signatureHeader, secret, options = {}) {
2703
- const algorithm = options.algorithm ?? "sha256";
2704
- const prefix = options.signaturePrefix ?? "";
2705
- const lower = options.lowercaseHex ?? true;
2706
- let candidate = signatureHeader;
2707
- if (prefix) {
2708
- if (!candidate.startsWith(prefix)) return false;
2709
- candidate = candidate.slice(prefix.length);
2710
- }
2711
- if (lower) candidate = candidate.toLowerCase();
2712
- const expected = createHmac2(algorithm, secret).update(rawBody).digest("hex");
2713
- const expectedBuf = Buffer.from(expected, "utf8");
2714
- const sigBuf = Buffer.from(candidate, "utf8");
2715
- if (sigBuf.length !== expectedBuf.length) return false;
2716
- return timingSafeEqual(sigBuf, expectedBuf);
2717
- }
2718
- function verifyTwilioSignature(input, options = {}) {
2719
- if (!input.authToken) {
2720
- return options.skipWhenAuthTokenMissing === true;
2721
- }
2722
- const signature = input.signatureHeader;
2723
- if (!signature || Array.isArray(signature)) return false;
2724
- if (!input.fullUrl) return false;
2725
- const data = options.bodyAsRaw === true ? input.fullUrl + (options.rawBody ?? "") : Object.keys(input.params ?? {}).sort().reduce((acc, key) => acc + key + (input.params[key] ?? ""), input.fullUrl);
2726
- const expected = createHmac2("sha1", input.authToken).update(data).digest("base64");
2727
- const expectedBuf = Buffer.from(expected);
2728
- const sigBuf = Buffer.from(signature);
2729
- if (expectedBuf.length !== sigBuf.length) return false;
2730
- return timingSafeEqual(expectedBuf, sigBuf);
2731
- }
2732
- function firstHeader(headers, name) {
2733
- const v = headers[name] ?? headers[name.toLowerCase()] ?? Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
2734
- if (Array.isArray(v)) return v[0];
2735
- return typeof v === "string" ? v : void 0;
2736
- }
2737
-
2738
3773
  // src/connectors/adapters/stripe-webhook-receiver.ts
2739
3774
  var stripeWebhookReceiverConnector = {
2740
3775
  manifest: {
@@ -3195,29 +4230,21 @@ var salesforceConnector = declarativeRestConnector({
3195
4230
  });
3196
4231
 
3197
4232
  export {
3198
- ResourceContention,
3199
- CredentialsExpired,
3200
- validateConnectorManifest,
3201
- assertValidConnectorManifest,
3202
4233
  InMemoryOAuthFlowStore,
3203
4234
  startOAuthFlow,
3204
4235
  consumePendingFlow,
3205
4236
  exchangeAuthorizationCode,
3206
4237
  refreshAccessToken,
3207
4238
  _resetPendingFlowsForTests,
3208
- DEFAULT_SIGNATURE_TOLERANCE_SECONDS,
3209
- parseStripeSignatureHeader,
3210
- verifyStripeSignature,
3211
- verifySlackSignature,
3212
- verifyHmacSignature,
3213
- verifyTwilioSignature,
3214
- firstHeader,
3215
4239
  googleCalendar,
4240
+ googleDrive,
3216
4241
  googleSheets,
4242
+ gmail,
3217
4243
  microsoftCalendar,
3218
4244
  hubspot,
3219
4245
  slack,
3220
4246
  notionDatabase,
4247
+ docuseal,
3221
4248
  declarativeRestConnector,
3222
4249
  twilioSmsConnector,
3223
4250
  stripePackConnector,
@@ -3230,4 +4257,4 @@ export {
3230
4257
  asanaConnector,
3231
4258
  salesforceConnector
3232
4259
  };
3233
- //# sourceMappingURL=chunk-WC63AI4Q.js.map
4260
+ //# sourceMappingURL=chunk-JU25UDN2.js.map