@tangle-network/agent-integrations 0.25.7 → 0.26.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.
@@ -1,3 +1,9 @@
1
+ import {
2
+ firstHeader,
3
+ verifySlackSignature,
4
+ verifyStripeSignature
5
+ } from "./chunk-2TW2QKGZ.js";
6
+
1
7
  // src/connectors/types.ts
2
8
  var ResourceContention = class extends Error {
3
9
  constructor(message, alternatives = [], currentState) {
@@ -462,11 +468,320 @@ function readMetaString(meta, key) {
462
468
  return v;
463
469
  }
464
470
 
471
+ // src/connectors/adapters/google-drive.ts
472
+ var SCOPES_READONLY = ["https://www.googleapis.com/auth/drive.readonly"];
473
+ var SCOPE_WATCH = "https://www.googleapis.com/auth/drive";
474
+ var AUTH_URL2 = "https://accounts.google.com/o/oauth2/v2/auth";
475
+ var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
476
+ var API = "https://www.googleapis.com/drive/v3";
477
+ function googleDrive(opts) {
478
+ const { clientId, clientSecret } = opts;
479
+ const timeoutMs = opts.timeoutMs ?? 3e4;
480
+ const scopes = opts.includeWatchScope ? [...SCOPES_READONLY, SCOPE_WATCH] : SCOPES_READONLY;
481
+ const adapter = {
482
+ manifest: {
483
+ kind: "google-drive",
484
+ displayName: "Google Drive",
485
+ 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.",
486
+ auth: {
487
+ kind: "oauth2",
488
+ authorizationUrl: AUTH_URL2,
489
+ tokenUrl: TOKEN_URL2,
490
+ scopes,
491
+ clientIdEnv: "GOOGLE_OAUTH_CLIENT_ID",
492
+ clientSecretEnv: "GOOGLE_OAUTH_CLIENT_SECRET",
493
+ extraAuthParams: { access_type: "offline", prompt: "consent", include_granted_scopes: "true" }
494
+ },
495
+ category: "storage",
496
+ defaultConsistencyModel: "authoritative",
497
+ rateLimit: { requests: 1e3, windowMs: 6e4, scope: "oauth-client" },
498
+ capabilities: [
499
+ {
500
+ name: "list_files",
501
+ class: "read",
502
+ 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'").`,
503
+ parameters: {
504
+ type: "object",
505
+ properties: {
506
+ folderId: { type: "string", description: "Drive folder id; when present, restricts to direct children." },
507
+ query: { type: "string", description: "Optional Drive query expression; appended with AND if folderId is set." },
508
+ pageSize: { type: "integer", minimum: 1, maximum: 1e3, default: 100 },
509
+ pageToken: { type: "string", description: "Continuation token returned by a previous call." }
510
+ }
511
+ }
512
+ },
513
+ {
514
+ name: "read_file",
515
+ class: "read",
516
+ 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.",
517
+ parameters: {
518
+ type: "object",
519
+ properties: {
520
+ fileId: { type: "string" },
521
+ exportMime: {
522
+ type: "string",
523
+ description: "Export mime override for Google-native types. Defaults: Docs=text/plain, Sheets=text/csv, Slides=application/pdf."
524
+ }
525
+ },
526
+ required: ["fileId"]
527
+ }
528
+ },
529
+ {
530
+ name: "watch_folder",
531
+ class: "mutation",
532
+ 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.",
533
+ cas: "native-idempotency",
534
+ externalEffect: true,
535
+ requiredScopes: [SCOPE_WATCH],
536
+ parameters: {
537
+ type: "object",
538
+ properties: {
539
+ folderId: { type: "string" },
540
+ channelId: { type: "string", description: "Caller-controlled UUID; also used as idempotency key." },
541
+ address: { type: "string", description: "HTTPS URL Drive will POST change notifications to." },
542
+ ttlMs: { type: "integer", minimum: 6e4, description: "Channel lifetime in ms. Drive caps at 7 days." }
543
+ },
544
+ required: ["folderId", "channelId", "address"]
545
+ }
546
+ }
547
+ ]
548
+ },
549
+ async executeRead(inv) {
550
+ const accessToken = await ensureFreshAccessToken2(inv.source.credentials, clientId, clientSecret);
551
+ if (inv.capabilityName === "list_files") return listFiles(inv, accessToken, timeoutMs);
552
+ if (inv.capabilityName === "read_file") return readFile(inv, accessToken, timeoutMs);
553
+ throw new Error(`google-drive: unknown read capability ${inv.capabilityName}`);
554
+ },
555
+ async executeMutation(inv) {
556
+ if (inv.capabilityName !== "watch_folder") {
557
+ throw new Error(`google-drive: unknown mutation capability ${inv.capabilityName}`);
558
+ }
559
+ const accessToken = await ensureFreshAccessToken2(inv.source.credentials, clientId, clientSecret);
560
+ return watchFolder(inv, accessToken, timeoutMs);
561
+ },
562
+ async exchangeOAuth(input) {
563
+ const tokens = await exchangeAuthorizationCode({
564
+ tokenUrl: TOKEN_URL2,
565
+ clientId,
566
+ clientSecret,
567
+ code: input.code,
568
+ codeVerifier: input.codeVerifier,
569
+ redirectUri: input.redirectUri
570
+ });
571
+ return {
572
+ credentials: {
573
+ kind: "oauth2",
574
+ accessToken: tokens.accessToken,
575
+ refreshToken: tokens.refreshToken,
576
+ expiresAt: tokens.expiresIn ? Date.now() + tokens.expiresIn * 1e3 : void 0
577
+ },
578
+ scopes: tokens.scope?.split(/\s+/) ?? scopes,
579
+ metadata: {}
580
+ };
581
+ },
582
+ async refreshToken(creds) {
583
+ if (creds.kind !== "oauth2" || !creds.refreshToken) {
584
+ throw new Error("google-drive.refreshToken: missing refresh token");
585
+ }
586
+ const refreshed = await refreshAccessToken({
587
+ tokenUrl: TOKEN_URL2,
588
+ clientId,
589
+ clientSecret,
590
+ refreshToken: creds.refreshToken
591
+ });
592
+ return {
593
+ kind: "oauth2",
594
+ accessToken: refreshed.accessToken,
595
+ refreshToken: refreshed.refreshToken ?? creds.refreshToken,
596
+ expiresAt: refreshed.expiresIn ? Date.now() + refreshed.expiresIn * 1e3 : void 0
597
+ };
598
+ },
599
+ async test(source) {
600
+ try {
601
+ const accessToken = await ensureFreshAccessToken2(source.credentials, clientId, clientSecret);
602
+ const res = await fetch(`${API}/about?fields=user`, {
603
+ headers: { authorization: `Bearer ${accessToken}` },
604
+ signal: AbortSignal.timeout(8e3)
605
+ });
606
+ if (res.status === 401 || res.status === 403) {
607
+ return { ok: false, reason: `Google rejected Drive token (${res.status}) \u2014 reconnect required` };
608
+ }
609
+ if (!res.ok) return { ok: false, reason: `Google Drive returned ${res.status}` };
610
+ return { ok: true };
611
+ } catch (err) {
612
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
613
+ }
614
+ }
615
+ };
616
+ return adapter;
617
+ }
618
+ async function listFiles(inv, accessToken, timeoutMs) {
619
+ const args = inv.args ?? {};
620
+ const q = [];
621
+ if (args.folderId) q.push(`'${args.folderId.replace(/'/g, "\\'")}' in parents`);
622
+ if (args.query) q.push(`(${args.query})`);
623
+ q.push("trashed = false");
624
+ const params = new URLSearchParams({
625
+ q: q.join(" and "),
626
+ pageSize: String(args.pageSize ?? 100),
627
+ fields: "nextPageToken, files(id,name,mimeType,modifiedTime,size,md5Checksum,parents)"
628
+ });
629
+ if (args.pageToken) params.set("pageToken", args.pageToken);
630
+ const res = await fetch(`${API}/files?${params.toString()}`, {
631
+ headers: { authorization: `Bearer ${accessToken}` },
632
+ signal: AbortSignal.timeout(timeoutMs)
633
+ });
634
+ if (res.status === 401 || res.status === 403) {
635
+ throw new CredentialsExpired(`Google Drive rejected token (${res.status})`, inv.source.id);
636
+ }
637
+ if (!res.ok) {
638
+ const text = await res.text().catch(() => "");
639
+ throw new Error(`google-drive list_files ${res.status}: ${text.slice(0, 200)}`);
640
+ }
641
+ const json = await res.json();
642
+ return {
643
+ data: { files: json.files ?? [], nextPageToken: json.nextPageToken },
644
+ fetchedAt: Date.now()
645
+ };
646
+ }
647
+ var GOOGLE_NATIVE_DEFAULTS = {
648
+ "application/vnd.google-apps.document": "text/plain",
649
+ "application/vnd.google-apps.spreadsheet": "text/csv",
650
+ "application/vnd.google-apps.presentation": "application/pdf"
651
+ };
652
+ async function readFile(inv, accessToken, timeoutMs) {
653
+ const { fileId, exportMime } = inv.args ?? {};
654
+ const metaRes = await fetch(`${API}/files/${encodeURIComponent(fileId)}?fields=id,name,mimeType,modifiedTime`, {
655
+ headers: { authorization: `Bearer ${accessToken}` },
656
+ signal: AbortSignal.timeout(timeoutMs)
657
+ });
658
+ if (metaRes.status === 401 || metaRes.status === 403) {
659
+ throw new CredentialsExpired(`Google Drive rejected token (${metaRes.status})`, inv.source.id);
660
+ }
661
+ if (metaRes.status === 404) {
662
+ throw new Error(`google-drive read_file: file ${fileId} not found`);
663
+ }
664
+ if (!metaRes.ok) {
665
+ const text = await metaRes.text().catch(() => "");
666
+ throw new Error(`google-drive read_file meta ${metaRes.status}: ${text.slice(0, 200)}`);
667
+ }
668
+ const meta = await metaRes.json();
669
+ const isNative = meta.mimeType.startsWith("application/vnd.google-apps.");
670
+ const fetchedAt = Date.now();
671
+ if (isNative) {
672
+ const targetMime = exportMime ?? GOOGLE_NATIVE_DEFAULTS[meta.mimeType] ?? "text/plain";
673
+ const res2 = await fetch(`${API}/files/${encodeURIComponent(fileId)}/export?mimeType=${encodeURIComponent(targetMime)}`, {
674
+ headers: { authorization: `Bearer ${accessToken}` },
675
+ signal: AbortSignal.timeout(timeoutMs)
676
+ });
677
+ if (!res2.ok) {
678
+ const text = await res2.text().catch(() => "");
679
+ throw new Error(`google-drive read_file export ${res2.status}: ${text.slice(0, 200)}`);
680
+ }
681
+ const isTextLike2 = /^text\/|application\/(json|xml|csv|javascript)/.test(targetMime);
682
+ if (isTextLike2) {
683
+ const content = await res2.text();
684
+ return {
685
+ data: { name: meta.name, mimeType: targetMime, content, encoding: "utf-8", modifiedTime: meta.modifiedTime },
686
+ fetchedAt
687
+ };
688
+ }
689
+ const buf2 = Buffer.from(await res2.arrayBuffer());
690
+ return {
691
+ data: { name: meta.name, mimeType: targetMime, content: buf2.toString("base64"), encoding: "base64", modifiedTime: meta.modifiedTime },
692
+ fetchedAt
693
+ };
694
+ }
695
+ const res = await fetch(`${API}/files/${encodeURIComponent(fileId)}?alt=media`, {
696
+ headers: { authorization: `Bearer ${accessToken}` },
697
+ signal: AbortSignal.timeout(timeoutMs)
698
+ });
699
+ if (!res.ok) {
700
+ const text = await res.text().catch(() => "");
701
+ throw new Error(`google-drive read_file media ${res.status}: ${text.slice(0, 200)}`);
702
+ }
703
+ const isTextLike = /^text\/|application\/(json|xml|csv|javascript)/.test(meta.mimeType);
704
+ if (isTextLike) {
705
+ const content = await res.text();
706
+ return {
707
+ data: { name: meta.name, mimeType: meta.mimeType, content, encoding: "utf-8", modifiedTime: meta.modifiedTime },
708
+ fetchedAt
709
+ };
710
+ }
711
+ const buf = Buffer.from(await res.arrayBuffer());
712
+ return {
713
+ data: { name: meta.name, mimeType: meta.mimeType, content: buf.toString("base64"), encoding: "base64", modifiedTime: meta.modifiedTime },
714
+ fetchedAt
715
+ };
716
+ }
717
+ async function watchFolder(inv, accessToken, timeoutMs) {
718
+ const { folderId, channelId, address, ttlMs } = inv.args;
719
+ const body = {
720
+ id: channelId,
721
+ type: "web_hook",
722
+ address
723
+ };
724
+ if (ttlMs && ttlMs > 0) body.expiration = String(Date.now() + ttlMs);
725
+ const res = await fetch(`${API}/files/${encodeURIComponent(folderId)}/watch`, {
726
+ method: "POST",
727
+ headers: {
728
+ authorization: `Bearer ${accessToken}`,
729
+ "content-type": "application/json"
730
+ },
731
+ body: JSON.stringify(body),
732
+ signal: AbortSignal.timeout(timeoutMs)
733
+ });
734
+ if (res.status === 401 || res.status === 403) {
735
+ throw new CredentialsExpired(`Google Drive rejected token (${res.status})`, inv.source.id);
736
+ }
737
+ if (res.status === 409) {
738
+ const cached = inv.source.metadata.watchedChannels?.[channelId];
739
+ return {
740
+ status: "committed",
741
+ data: { channelId, resourceId: cached?.resourceId, expiration: cached?.expiration },
742
+ committedAt: Date.now(),
743
+ idempotentReplay: true
744
+ };
745
+ }
746
+ if (!res.ok) {
747
+ const text = await res.text().catch(() => "");
748
+ throw new Error(`google-drive watch_folder ${res.status}: ${text.slice(0, 200)}`);
749
+ }
750
+ const json = await res.json();
751
+ return {
752
+ status: "committed",
753
+ data: { channelId: json.id, resourceId: json.resourceId, expiration: json.expiration },
754
+ committedAt: Date.now(),
755
+ idempotentReplay: false
756
+ };
757
+ }
758
+ async function ensureFreshAccessToken2(creds, clientId, clientSecret) {
759
+ if (creds.kind !== "oauth2") {
760
+ throw new Error("google-drive: expected oauth2 credentials");
761
+ }
762
+ if (creds.accessToken && (!creds.expiresAt || creds.expiresAt > Date.now() + 6e4)) {
763
+ return creds.accessToken;
764
+ }
765
+ if (!creds.refreshToken) {
766
+ throw new CredentialsExpired("Google Drive access token expired and no refresh token", "");
767
+ }
768
+ const refreshed = await refreshAccessToken({
769
+ tokenUrl: TOKEN_URL2,
770
+ clientId,
771
+ clientSecret,
772
+ refreshToken: creds.refreshToken
773
+ });
774
+ creds.accessToken = refreshed.accessToken;
775
+ creds.expiresAt = refreshed.expiresIn ? Date.now() + refreshed.expiresIn * 1e3 : void 0;
776
+ if (refreshed.refreshToken) creds.refreshToken = refreshed.refreshToken;
777
+ return creds.accessToken;
778
+ }
779
+
465
780
  // src/connectors/adapters/google-sheets.ts
466
781
  import { createHash as createHash2 } from "crypto";
467
782
  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";
783
+ var AUTH_URL3 = "https://accounts.google.com/o/oauth2/v2/auth";
784
+ var TOKEN_URL3 = "https://oauth2.googleapis.com/token";
470
785
  function googleSheets(opts) {
471
786
  const { clientId, clientSecret } = opts;
472
787
  const adapter = {
@@ -476,8 +791,8 @@ function googleSheets(opts) {
476
791
  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
792
  auth: {
478
793
  kind: "oauth2",
479
- authorizationUrl: AUTH_URL2,
480
- tokenUrl: TOKEN_URL2,
794
+ authorizationUrl: AUTH_URL3,
795
+ tokenUrl: TOKEN_URL3,
481
796
  scopes: SCOPES2,
482
797
  clientIdEnv: "GOOGLE_OAUTH_CLIENT_ID",
483
798
  clientSecretEnv: "GOOGLE_OAUTH_CLIENT_SECRET",
@@ -544,7 +859,7 @@ function googleSheets(opts) {
544
859
  },
545
860
  async executeRead(inv) {
546
861
  const meta = readSheetMeta(inv.source.metadata);
547
- const accessToken = await ensureFreshAccessToken2(inv.source.credentials, clientId, clientSecret);
862
+ const accessToken = await ensureFreshAccessToken3(inv.source.credentials, clientId, clientSecret);
548
863
  const rows = await fetchAllRows(accessToken, meta);
549
864
  const limit = clampLimit(inv.args.limit, 100);
550
865
  let filtered = rows;
@@ -571,7 +886,7 @@ function googleSheets(opts) {
571
886
  throw new Error(`google-sheets: unknown mutation ${inv.capabilityName}`);
572
887
  }
573
888
  const meta = readSheetMeta(inv.source.metadata);
574
- const accessToken = await ensureFreshAccessToken2(inv.source.credentials, clientId, clientSecret);
889
+ const accessToken = await ensureFreshAccessToken3(inv.source.credentials, clientId, clientSecret);
575
890
  const { rowKey, patch, expectedFingerprint } = inv.args;
576
891
  const rows = await fetchAllRows(accessToken, meta);
577
892
  const target = rows.find((r) => normalizeKey(r.values[meta.keyColumn]) === normalizeKey(rowKey));
@@ -627,7 +942,7 @@ function googleSheets(opts) {
627
942
  },
628
943
  async exchangeOAuth(input) {
629
944
  const tokens = await exchangeAuthorizationCode({
630
- tokenUrl: TOKEN_URL2,
945
+ tokenUrl: TOKEN_URL3,
631
946
  clientId,
632
947
  clientSecret,
633
948
  code: input.code,
@@ -652,7 +967,7 @@ function googleSheets(opts) {
652
967
  throw new Error("google-sheets.refreshToken: missing refresh token");
653
968
  }
654
969
  const refreshed = await refreshAccessToken({
655
- tokenUrl: TOKEN_URL2,
970
+ tokenUrl: TOKEN_URL3,
656
971
  clientId,
657
972
  clientSecret,
658
973
  refreshToken: creds.refreshToken
@@ -666,7 +981,7 @@ function googleSheets(opts) {
666
981
  },
667
982
  async test(source) {
668
983
  try {
669
- const accessToken = await ensureFreshAccessToken2(source.credentials, clientId, clientSecret);
984
+ const accessToken = await ensureFreshAccessToken3(source.credentials, clientId, clientSecret);
670
985
  const meta = readSheetMeta(source.metadata);
671
986
  if (!meta.spreadsheetId) {
672
987
  return { ok: false, reason: "spreadsheetId not configured \u2014 pick a sheet in the connection settings" };
@@ -764,7 +1079,7 @@ function columnIndexToLetter(idx) {
764
1079
  }
765
1080
  return s;
766
1081
  }
767
- async function ensureFreshAccessToken2(creds, clientId, clientSecret) {
1082
+ async function ensureFreshAccessToken3(creds, clientId, clientSecret) {
768
1083
  if (creds.kind !== "oauth2") {
769
1084
  throw new Error("google-sheets: expected oauth2 credentials");
770
1085
  }
@@ -775,7 +1090,403 @@ async function ensureFreshAccessToken2(creds, clientId, clientSecret) {
775
1090
  throw new CredentialsExpired("Google Sheets access token expired and no refresh token", "");
776
1091
  }
777
1092
  const refreshed = await refreshAccessToken({
778
- tokenUrl: TOKEN_URL2,
1093
+ tokenUrl: TOKEN_URL3,
1094
+ clientId,
1095
+ clientSecret,
1096
+ refreshToken: creds.refreshToken
1097
+ });
1098
+ creds.accessToken = refreshed.accessToken;
1099
+ creds.expiresAt = refreshed.expiresIn ? Date.now() + refreshed.expiresIn * 1e3 : void 0;
1100
+ if (refreshed.refreshToken) creds.refreshToken = refreshed.refreshToken;
1101
+ return creds.accessToken;
1102
+ }
1103
+
1104
+ // src/connectors/adapters/gmail.ts
1105
+ var SCOPE_READ = "https://www.googleapis.com/auth/gmail.readonly";
1106
+ var SCOPE_SEND = "https://www.googleapis.com/auth/gmail.send";
1107
+ var SCOPE_MODIFY = "https://www.googleapis.com/auth/gmail.modify";
1108
+ var AUTH_URL4 = "https://accounts.google.com/o/oauth2/v2/auth";
1109
+ var TOKEN_URL4 = "https://oauth2.googleapis.com/token";
1110
+ var API2 = "https://gmail.googleapis.com/gmail/v1/users/me";
1111
+ function gmail(opts) {
1112
+ const { clientId, clientSecret } = opts;
1113
+ const timeoutMs = opts.timeoutMs ?? 3e4;
1114
+ const scopes = opts.scopes ?? [SCOPE_READ, SCOPE_SEND, SCOPE_MODIFY];
1115
+ const adapter = {
1116
+ manifest: {
1117
+ kind: "gmail",
1118
+ displayName: "Gmail",
1119
+ 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).",
1120
+ auth: {
1121
+ kind: "oauth2",
1122
+ authorizationUrl: AUTH_URL4,
1123
+ tokenUrl: TOKEN_URL4,
1124
+ scopes,
1125
+ clientIdEnv: "GOOGLE_OAUTH_CLIENT_ID",
1126
+ clientSecretEnv: "GOOGLE_OAUTH_CLIENT_SECRET",
1127
+ extraAuthParams: { access_type: "offline", prompt: "consent", include_granted_scopes: "true" }
1128
+ },
1129
+ category: "comms",
1130
+ defaultConsistencyModel: "authoritative",
1131
+ rateLimit: { requests: 250, windowMs: 1e3, scope: "oauth-client" },
1132
+ capabilities: [
1133
+ {
1134
+ name: "list_messages",
1135
+ class: "read",
1136
+ 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.",
1137
+ requiredScopes: [SCOPE_READ],
1138
+ parameters: {
1139
+ type: "object",
1140
+ properties: {
1141
+ labelIds: { type: "array", items: { type: "string" }, description: 'Default: ["INBOX"]' },
1142
+ query: { type: "string", description: "Gmail query syntax." },
1143
+ maxResults: { type: "integer", minimum: 1, maximum: 500, default: 25 },
1144
+ pageToken: { type: "string" }
1145
+ }
1146
+ }
1147
+ },
1148
+ {
1149
+ name: "read_message",
1150
+ class: "read",
1151
+ 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.",
1152
+ requiredScopes: [SCOPE_READ],
1153
+ parameters: {
1154
+ type: "object",
1155
+ properties: {
1156
+ id: { type: "string" }
1157
+ },
1158
+ required: ["id"]
1159
+ }
1160
+ },
1161
+ {
1162
+ name: "send_reply",
1163
+ class: "mutation",
1164
+ description: "Send a reply on a thread. Pulls In-Reply-To/References from the latest message in the thread. Body is text/plain.",
1165
+ cas: "native-idempotency",
1166
+ externalEffect: true,
1167
+ requiredScopes: [SCOPE_SEND, SCOPE_READ],
1168
+ parameters: {
1169
+ type: "object",
1170
+ properties: {
1171
+ threadId: { type: "string" },
1172
+ body: { type: "string", description: "text/plain body" },
1173
+ replyAll: { type: "boolean", default: false },
1174
+ cc: { type: "array", items: { type: "string" } }
1175
+ },
1176
+ required: ["threadId", "body"]
1177
+ }
1178
+ },
1179
+ {
1180
+ name: "watch_label",
1181
+ class: "mutation",
1182
+ 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.",
1183
+ cas: "native-idempotency",
1184
+ externalEffect: true,
1185
+ requiredScopes: [SCOPE_MODIFY],
1186
+ parameters: {
1187
+ type: "object",
1188
+ properties: {
1189
+ labelIds: { type: "array", items: { type: "string" }, description: 'Default: ["INBOX"]' },
1190
+ topicName: { type: "string", description: "projects/<id>/topics/<name>" },
1191
+ labelFilterAction: { type: "string", enum: ["include", "exclude"], default: "include" }
1192
+ },
1193
+ required: ["topicName"]
1194
+ }
1195
+ }
1196
+ ]
1197
+ },
1198
+ async executeRead(inv) {
1199
+ const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
1200
+ if (inv.capabilityName === "list_messages") return listMessages(inv, accessToken, timeoutMs);
1201
+ if (inv.capabilityName === "read_message") return readMessage(inv, accessToken, timeoutMs);
1202
+ throw new Error(`gmail: unknown read capability ${inv.capabilityName}`);
1203
+ },
1204
+ async executeMutation(inv) {
1205
+ const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
1206
+ if (inv.capabilityName === "send_reply") return sendReply(inv, accessToken, timeoutMs);
1207
+ if (inv.capabilityName === "watch_label") return watchLabel(inv, accessToken, timeoutMs);
1208
+ throw new Error(`gmail: unknown mutation capability ${inv.capabilityName}`);
1209
+ },
1210
+ async exchangeOAuth(input) {
1211
+ const tokens = await exchangeAuthorizationCode({
1212
+ tokenUrl: TOKEN_URL4,
1213
+ clientId,
1214
+ clientSecret,
1215
+ code: input.code,
1216
+ codeVerifier: input.codeVerifier,
1217
+ redirectUri: input.redirectUri
1218
+ });
1219
+ return {
1220
+ credentials: {
1221
+ kind: "oauth2",
1222
+ accessToken: tokens.accessToken,
1223
+ refreshToken: tokens.refreshToken,
1224
+ expiresAt: tokens.expiresIn ? Date.now() + tokens.expiresIn * 1e3 : void 0
1225
+ },
1226
+ scopes: tokens.scope?.split(/\s+/) ?? scopes,
1227
+ metadata: {}
1228
+ };
1229
+ },
1230
+ async refreshToken(creds) {
1231
+ if (creds.kind !== "oauth2" || !creds.refreshToken) {
1232
+ throw new Error("gmail.refreshToken: missing refresh token");
1233
+ }
1234
+ const refreshed = await refreshAccessToken({
1235
+ tokenUrl: TOKEN_URL4,
1236
+ clientId,
1237
+ clientSecret,
1238
+ refreshToken: creds.refreshToken
1239
+ });
1240
+ return {
1241
+ kind: "oauth2",
1242
+ accessToken: refreshed.accessToken,
1243
+ refreshToken: refreshed.refreshToken ?? creds.refreshToken,
1244
+ expiresAt: refreshed.expiresIn ? Date.now() + refreshed.expiresIn * 1e3 : void 0
1245
+ };
1246
+ },
1247
+ async test(source) {
1248
+ try {
1249
+ const accessToken = await ensureFreshAccessToken4(source.credentials, clientId, clientSecret);
1250
+ const res = await fetch(`${API2}/profile`, {
1251
+ headers: { authorization: `Bearer ${accessToken}` },
1252
+ signal: AbortSignal.timeout(8e3)
1253
+ });
1254
+ if (res.status === 401 || res.status === 403) {
1255
+ return { ok: false, reason: `Google rejected Gmail token (${res.status}) \u2014 reconnect required` };
1256
+ }
1257
+ if (!res.ok) return { ok: false, reason: `Gmail returned ${res.status}` };
1258
+ return { ok: true };
1259
+ } catch (err) {
1260
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
1261
+ }
1262
+ }
1263
+ };
1264
+ return adapter;
1265
+ }
1266
+ async function listMessages(inv, accessToken, timeoutMs) {
1267
+ const args = inv.args ?? {};
1268
+ const params = new URLSearchParams({
1269
+ maxResults: String(args.maxResults ?? 25)
1270
+ });
1271
+ for (const id of args.labelIds ?? ["INBOX"]) params.append("labelIds", id);
1272
+ if (args.query) params.set("q", args.query);
1273
+ if (args.pageToken) params.set("pageToken", args.pageToken);
1274
+ const listRes = await fetch(`${API2}/messages?${params.toString()}`, {
1275
+ headers: { authorization: `Bearer ${accessToken}` },
1276
+ signal: AbortSignal.timeout(timeoutMs)
1277
+ });
1278
+ if (listRes.status === 401 || listRes.status === 403) {
1279
+ throw new CredentialsExpired(`Gmail rejected token (${listRes.status})`, inv.source.id);
1280
+ }
1281
+ if (!listRes.ok) {
1282
+ const text = await listRes.text().catch(() => "");
1283
+ throw new Error(`gmail list_messages ${listRes.status}: ${text.slice(0, 200)}`);
1284
+ }
1285
+ const listJson = await listRes.json();
1286
+ const ids = listJson.messages ?? [];
1287
+ const metas = await Promise.all(
1288
+ ids.map(async ({ id }) => {
1289
+ const res = await fetch(`${API2}/messages/${encodeURIComponent(id)}?format=metadata&metadataHeaders=From&metadataHeaders=To&metadataHeaders=Subject&metadataHeaders=Date`, {
1290
+ headers: { authorization: `Bearer ${accessToken}` },
1291
+ signal: AbortSignal.timeout(timeoutMs)
1292
+ });
1293
+ if (!res.ok) return null;
1294
+ return await res.json();
1295
+ })
1296
+ );
1297
+ const messages = metas.filter((m) => Boolean(m)).map(toMessageSummary);
1298
+ return {
1299
+ data: { messages, nextPageToken: listJson.nextPageToken },
1300
+ fetchedAt: Date.now()
1301
+ };
1302
+ }
1303
+ function toMessageSummary(meta) {
1304
+ const headers = new Map((meta.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
1305
+ return {
1306
+ id: meta.id,
1307
+ threadId: meta.threadId,
1308
+ snippet: meta.snippet,
1309
+ internalDate: meta.internalDate,
1310
+ labelIds: meta.labelIds ?? [],
1311
+ from: headers.get("from"),
1312
+ to: headers.get("to"),
1313
+ subject: headers.get("subject"),
1314
+ date: headers.get("date")
1315
+ };
1316
+ }
1317
+ async function readMessage(inv, accessToken, timeoutMs) {
1318
+ const { id } = inv.args;
1319
+ const res = await fetch(`${API2}/messages/${encodeURIComponent(id)}?format=full`, {
1320
+ headers: { authorization: `Bearer ${accessToken}` },
1321
+ signal: AbortSignal.timeout(timeoutMs)
1322
+ });
1323
+ if (res.status === 401 || res.status === 403) {
1324
+ throw new CredentialsExpired(`Gmail rejected token (${res.status})`, inv.source.id);
1325
+ }
1326
+ if (res.status === 404) {
1327
+ throw new Error(`gmail read_message: message ${id} not found`);
1328
+ }
1329
+ if (!res.ok) {
1330
+ const text = await res.text().catch(() => "");
1331
+ throw new Error(`gmail read_message ${res.status}: ${text.slice(0, 200)}`);
1332
+ }
1333
+ const full = await res.json();
1334
+ const headers = new Map((full.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
1335
+ const body = {};
1336
+ const attachments = [];
1337
+ walkParts(full.payload, body, attachments);
1338
+ return {
1339
+ data: {
1340
+ id: full.id,
1341
+ threadId: full.threadId,
1342
+ internalDate: full.internalDate,
1343
+ labelIds: full.labelIds ?? [],
1344
+ from: headers.get("from"),
1345
+ to: headers.get("to"),
1346
+ cc: headers.get("cc"),
1347
+ subject: headers.get("subject"),
1348
+ date: headers.get("date"),
1349
+ body,
1350
+ attachments
1351
+ },
1352
+ fetchedAt: Date.now()
1353
+ };
1354
+ }
1355
+ function walkParts(part, body, attachments) {
1356
+ if (!part) return;
1357
+ if (part.body?.data && part.mimeType === "text/plain" && !body.text) {
1358
+ body.text = decodeBase64Url(part.body.data);
1359
+ } else if (part.body?.data && part.mimeType === "text/html" && !body.html) {
1360
+ body.html = decodeBase64Url(part.body.data);
1361
+ }
1362
+ if (part.filename && part.body?.attachmentId) {
1363
+ attachments.push({
1364
+ filename: part.filename,
1365
+ mimeType: part.mimeType ?? "application/octet-stream",
1366
+ attachmentId: part.body.attachmentId,
1367
+ size: part.body.size ?? 0
1368
+ });
1369
+ }
1370
+ for (const child of part.parts ?? []) walkParts(child, body, attachments);
1371
+ }
1372
+ function decodeBase64Url(s) {
1373
+ return Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf-8");
1374
+ }
1375
+ function encodeBase64Url(s) {
1376
+ return Buffer.from(s, "utf-8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1377
+ }
1378
+ async function sendReply(inv, accessToken, timeoutMs) {
1379
+ const { threadId, body, replyAll, cc } = inv.args;
1380
+ const threadRes = await fetch(`${API2}/threads/${encodeURIComponent(threadId)}?format=metadata&metadataHeaders=From&metadataHeaders=To&metadataHeaders=Cc&metadataHeaders=Subject&metadataHeaders=Message-ID&metadataHeaders=References`, {
1381
+ headers: { authorization: `Bearer ${accessToken}` },
1382
+ signal: AbortSignal.timeout(timeoutMs)
1383
+ });
1384
+ if (threadRes.status === 401 || threadRes.status === 403) {
1385
+ throw new CredentialsExpired(`Gmail rejected token (${threadRes.status})`, inv.source.id);
1386
+ }
1387
+ if (!threadRes.ok) {
1388
+ const text = await threadRes.text().catch(() => "");
1389
+ throw new Error(`gmail send_reply thread fetch ${threadRes.status}: ${text.slice(0, 200)}`);
1390
+ }
1391
+ const thread = await threadRes.json();
1392
+ const last = thread.messages?.[thread.messages.length - 1];
1393
+ if (!last) throw new Error(`gmail send_reply: thread ${threadId} has no messages`);
1394
+ const lastHeaders = new Map((last.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
1395
+ const inReplyTo = lastHeaders.get("message-id");
1396
+ const refsHeader = lastHeaders.get("references");
1397
+ const refs = refsHeader ? `${refsHeader} ${inReplyTo ?? ""}`.trim() : inReplyTo;
1398
+ const fromHeader = lastHeaders.get("from");
1399
+ const toHeader = lastHeaders.get("to");
1400
+ const ccHeader = lastHeaders.get("cc");
1401
+ const subject = lastHeaders.get("subject") ?? "";
1402
+ const rfcHeaders = [];
1403
+ if (fromHeader) rfcHeaders.push(`To: ${fromHeader}`);
1404
+ if (replyAll) {
1405
+ const extra = [toHeader, ccHeader].filter(Boolean).join(", ");
1406
+ if (extra) rfcHeaders.push(`Cc: ${extra}`);
1407
+ }
1408
+ if (cc?.length) {
1409
+ const existing = rfcHeaders.findIndex((h) => h.startsWith("Cc: "));
1410
+ if (existing >= 0) rfcHeaders[existing] = `${rfcHeaders[existing]}, ${cc.join(", ")}`;
1411
+ else rfcHeaders.push(`Cc: ${cc.join(", ")}`);
1412
+ }
1413
+ rfcHeaders.push(`Subject: ${subject.toLowerCase().startsWith("re:") ? subject : "Re: " + subject}`);
1414
+ if (inReplyTo) rfcHeaders.push(`In-Reply-To: ${inReplyTo}`);
1415
+ if (refs) rfcHeaders.push(`References: ${refs}`);
1416
+ rfcHeaders.push(`X-Tangle-Idempotency-Key: ${inv.idempotencyKey}`);
1417
+ rfcHeaders.push('Content-Type: text/plain; charset="UTF-8"');
1418
+ rfcHeaders.push("MIME-Version: 1.0");
1419
+ const raw = `${rfcHeaders.join("\r\n")}\r
1420
+ \r
1421
+ ${body}`;
1422
+ const sendBody = { threadId, raw: encodeBase64Url(raw) };
1423
+ const sendRes = await fetch(`${API2}/messages/send`, {
1424
+ method: "POST",
1425
+ headers: {
1426
+ authorization: `Bearer ${accessToken}`,
1427
+ "content-type": "application/json"
1428
+ },
1429
+ body: JSON.stringify(sendBody),
1430
+ signal: AbortSignal.timeout(timeoutMs)
1431
+ });
1432
+ if (sendRes.status === 401 || sendRes.status === 403) {
1433
+ throw new CredentialsExpired(`Gmail rejected token (${sendRes.status})`, inv.source.id);
1434
+ }
1435
+ if (!sendRes.ok) {
1436
+ const text = await sendRes.text().catch(() => "");
1437
+ throw new Error(`gmail send_reply ${sendRes.status}: ${text.slice(0, 200)}`);
1438
+ }
1439
+ const sent = await sendRes.json();
1440
+ return {
1441
+ status: "committed",
1442
+ data: { id: sent.id, threadId: sent.threadId, labelIds: sent.labelIds ?? [] },
1443
+ committedAt: Date.now(),
1444
+ idempotentReplay: false
1445
+ };
1446
+ }
1447
+ async function watchLabel(inv, accessToken, timeoutMs) {
1448
+ const { labelIds, topicName, labelFilterAction } = inv.args;
1449
+ const body = {
1450
+ topicName,
1451
+ labelIds: labelIds ?? ["INBOX"],
1452
+ labelFilterAction: labelFilterAction ?? "include"
1453
+ };
1454
+ const res = await fetch(`${API2}/watch`, {
1455
+ method: "POST",
1456
+ headers: {
1457
+ authorization: `Bearer ${accessToken}`,
1458
+ "content-type": "application/json"
1459
+ },
1460
+ body: JSON.stringify(body),
1461
+ signal: AbortSignal.timeout(timeoutMs)
1462
+ });
1463
+ if (res.status === 401 || res.status === 403) {
1464
+ throw new CredentialsExpired(`Gmail rejected token (${res.status})`, inv.source.id);
1465
+ }
1466
+ if (!res.ok) {
1467
+ const text = await res.text().catch(() => "");
1468
+ throw new Error(`gmail watch_label ${res.status}: ${text.slice(0, 200)}`);
1469
+ }
1470
+ const json = await res.json();
1471
+ return {
1472
+ status: "committed",
1473
+ data: { historyId: json.historyId, expiration: json.expiration, topicName, labelIds: body.labelIds },
1474
+ committedAt: Date.now(),
1475
+ idempotentReplay: false
1476
+ };
1477
+ }
1478
+ async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
1479
+ if (creds.kind !== "oauth2") {
1480
+ throw new Error("gmail: expected oauth2 credentials");
1481
+ }
1482
+ if (creds.accessToken && (!creds.expiresAt || creds.expiresAt > Date.now() + 6e4)) {
1483
+ return creds.accessToken;
1484
+ }
1485
+ if (!creds.refreshToken) {
1486
+ throw new CredentialsExpired("Gmail access token expired and no refresh token", "");
1487
+ }
1488
+ const refreshed = await refreshAccessToken({
1489
+ tokenUrl: TOKEN_URL4,
779
1490
  clientId,
780
1491
  clientSecret,
781
1492
  refreshToken: creds.refreshToken
@@ -794,8 +1505,8 @@ var SCOPES3 = [
794
1505
  // connection silently dies after ~1 hour.
795
1506
  "offline_access"
796
1507
  ];
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";
1508
+ var AUTH_URL5 = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
1509
+ var TOKEN_URL5 = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
799
1510
  function microsoftCalendar(opts) {
800
1511
  const { clientId, clientSecret } = opts;
801
1512
  const adapter = {
@@ -805,8 +1516,8 @@ function microsoftCalendar(opts) {
805
1516
  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
1517
  auth: {
807
1518
  kind: "oauth2",
808
- authorizationUrl: AUTH_URL3,
809
- tokenUrl: TOKEN_URL3,
1519
+ authorizationUrl: AUTH_URL5,
1520
+ tokenUrl: TOKEN_URL5,
810
1521
  scopes: SCOPES3,
811
1522
  clientIdEnv: "MS_OAUTH_CLIENT_ID",
812
1523
  clientSecretEnv: "MS_OAUTH_CLIENT_SECRET"
@@ -858,7 +1569,7 @@ function microsoftCalendar(opts) {
858
1569
  }
859
1570
  const userPrincipal = readMetaString2(inv.source.metadata, "userPrincipal");
860
1571
  const { timeMin, timeMax } = inv.args;
861
- const accessToken = await ensureFreshAccessToken3(inv.source.credentials, clientId, clientSecret);
1572
+ const accessToken = await ensureFreshAccessToken5(inv.source.credentials, clientId, clientSecret);
862
1573
  const busy = await getScheduleBusy({ accessToken, userPrincipal, timeMin, timeMax });
863
1574
  return {
864
1575
  data: { busy },
@@ -871,7 +1582,7 @@ function microsoftCalendar(opts) {
871
1582
  }
872
1583
  const userPrincipal = readMetaString2(inv.source.metadata, "userPrincipal");
873
1584
  const { start, end, summary, description, attendees } = inv.args;
874
- const accessToken = await ensureFreshAccessToken3(inv.source.credentials, clientId, clientSecret);
1585
+ const accessToken = await ensureFreshAccessToken5(inv.source.credentials, clientId, clientSecret);
875
1586
  const busy = await getScheduleBusy({ accessToken, userPrincipal, timeMin: start, timeMax: end });
876
1587
  if (busy.length > 0) {
877
1588
  const startMs = Date.parse(start);
@@ -936,7 +1647,7 @@ function microsoftCalendar(opts) {
936
1647
  throw new Error("Microsoft OAuth client not configured (MS_OAUTH_CLIENT_ID / _SECRET)");
937
1648
  }
938
1649
  const tokens = await exchangeAuthorizationCode({
939
- tokenUrl: TOKEN_URL3,
1650
+ tokenUrl: TOKEN_URL5,
940
1651
  clientId,
941
1652
  clientSecret,
942
1653
  code: input.code,
@@ -961,7 +1672,7 @@ function microsoftCalendar(opts) {
961
1672
  throw new Error("microsoft-calendar.refreshToken: missing refresh token");
962
1673
  }
963
1674
  const refreshed = await refreshAccessToken({
964
- tokenUrl: TOKEN_URL3,
1675
+ tokenUrl: TOKEN_URL5,
965
1676
  clientId,
966
1677
  clientSecret,
967
1678
  refreshToken: creds.refreshToken
@@ -975,7 +1686,7 @@ function microsoftCalendar(opts) {
975
1686
  },
976
1687
  async test(source) {
977
1688
  try {
978
- const accessToken = await ensureFreshAccessToken3(source.credentials, clientId, clientSecret);
1689
+ const accessToken = await ensureFreshAccessToken5(source.credentials, clientId, clientSecret);
979
1690
  const res = await fetch("https://graph.microsoft.com/v1.0/me?$select=id", {
980
1691
  headers: { authorization: `Bearer ${accessToken}` },
981
1692
  signal: AbortSignal.timeout(8e3)
@@ -1048,7 +1759,7 @@ async function findNextFreeSlots2(input) {
1048
1759
  }
1049
1760
  return out.slice(0, input.wanted);
1050
1761
  }
1051
- async function ensureFreshAccessToken3(creds, clientId, clientSecret) {
1762
+ async function ensureFreshAccessToken5(creds, clientId, clientSecret) {
1052
1763
  if (creds.kind !== "oauth2") {
1053
1764
  throw new Error("microsoft-calendar: expected oauth2 credentials");
1054
1765
  }
@@ -1059,7 +1770,7 @@ async function ensureFreshAccessToken3(creds, clientId, clientSecret) {
1059
1770
  throw new CredentialsExpired("Microsoft Calendar access token expired and no refresh token", "");
1060
1771
  }
1061
1772
  const refreshed = await refreshAccessToken({
1062
- tokenUrl: TOKEN_URL3,
1773
+ tokenUrl: TOKEN_URL5,
1063
1774
  clientId,
1064
1775
  clientSecret,
1065
1776
  refreshToken: creds.refreshToken
@@ -1082,9 +1793,9 @@ var SCOPES4 = [
1082
1793
  "crm.objects.contacts.read",
1083
1794
  "crm.objects.contacts.write"
1084
1795
  ];
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";
1796
+ var AUTH_URL6 = "https://app.hubspot.com/oauth/authorize";
1797
+ var TOKEN_URL6 = "https://api.hubapi.com/oauth/v1/token";
1798
+ var API3 = "https://api.hubapi.com";
1088
1799
  function hubspot(opts) {
1089
1800
  const { clientId, clientSecret } = opts;
1090
1801
  const adapter = {
@@ -1094,8 +1805,8 @@ function hubspot(opts) {
1094
1805
  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
1806
  auth: {
1096
1807
  kind: "oauth2",
1097
- authorizationUrl: AUTH_URL4,
1098
- tokenUrl: TOKEN_URL4,
1808
+ authorizationUrl: AUTH_URL6,
1809
+ tokenUrl: TOKEN_URL6,
1099
1810
  scopes: SCOPES4,
1100
1811
  clientIdEnv: "HUBSPOT_OAUTH_CLIENT_ID",
1101
1812
  clientSecretEnv: "HUBSPOT_OAUTH_CLIENT_SECRET"
@@ -1154,8 +1865,8 @@ function hubspot(opts) {
1154
1865
  throw new Error(`hubspot: unknown read capability ${inv.capabilityName}`);
1155
1866
  }
1156
1867
  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`, {
1868
+ const accessToken = await ensureFreshAccessToken6(inv.source.credentials, clientId, clientSecret);
1869
+ const res = await fetch(`${API3}/crm/v3/objects/contacts/search`, {
1159
1870
  method: "POST",
1160
1871
  headers: {
1161
1872
  authorization: `Bearer ${accessToken}`,
@@ -1187,7 +1898,7 @@ function hubspot(opts) {
1187
1898
  };
1188
1899
  },
1189
1900
  async executeMutation(inv) {
1190
- const accessToken = await ensureFreshAccessToken4(inv.source.credentials, clientId, clientSecret);
1901
+ const accessToken = await ensureFreshAccessToken6(inv.source.credentials, clientId, clientSecret);
1191
1902
  if (inv.capabilityName === "upsert_contact") {
1192
1903
  return upsertContact(inv, accessToken);
1193
1904
  }
@@ -1201,7 +1912,7 @@ function hubspot(opts) {
1201
1912
  throw new Error("HubSpot OAuth client not configured (HUBSPOT_OAUTH_CLIENT_ID / _SECRET)");
1202
1913
  }
1203
1914
  const tokens = await exchangeAuthorizationCode({
1204
- tokenUrl: TOKEN_URL4,
1915
+ tokenUrl: TOKEN_URL6,
1205
1916
  clientId,
1206
1917
  clientSecret,
1207
1918
  code: input.code,
@@ -1224,7 +1935,7 @@ function hubspot(opts) {
1224
1935
  throw new Error("hubspot.refreshToken: missing refresh token");
1225
1936
  }
1226
1937
  const refreshed = await refreshAccessToken({
1227
- tokenUrl: TOKEN_URL4,
1938
+ tokenUrl: TOKEN_URL6,
1228
1939
  clientId,
1229
1940
  clientSecret,
1230
1941
  refreshToken: creds.refreshToken
@@ -1238,8 +1949,8 @@ function hubspot(opts) {
1238
1949
  },
1239
1950
  async test(source) {
1240
1951
  try {
1241
- const accessToken = await ensureFreshAccessToken4(source.credentials, clientId, clientSecret);
1242
- const res = await fetch(`${API}/oauth/v1/access-tokens/${encodeURIComponent(accessToken)}`, {
1952
+ const accessToken = await ensureFreshAccessToken6(source.credentials, clientId, clientSecret);
1953
+ const res = await fetch(`${API3}/oauth/v1/access-tokens/${encodeURIComponent(accessToken)}`, {
1243
1954
  signal: AbortSignal.timeout(8e3)
1244
1955
  });
1245
1956
  if (res.status === 401 || res.status === 403 || res.status === 404) {
@@ -1257,7 +1968,7 @@ function hubspot(opts) {
1257
1968
  async function upsertContact(inv, accessToken) {
1258
1969
  const { email, properties } = inv.args;
1259
1970
  const idemKey = sanitizeIdempotencyKey(inv.idempotencyKey);
1260
- const url = `${API}/crm/v3/objects/contacts/batch/upsert?idempotencyKey=${encodeURIComponent(idemKey)}`;
1971
+ const url = `${API3}/crm/v3/objects/contacts/batch/upsert?idempotencyKey=${encodeURIComponent(idemKey)}`;
1261
1972
  const body = {
1262
1973
  inputs: [
1263
1974
  {
@@ -1303,7 +2014,7 @@ async function upsertContact(inv, accessToken) {
1303
2014
  async function createNote(inv, accessToken) {
1304
2015
  const { contactId, body } = inv.args;
1305
2016
  const idemKey = sanitizeIdempotencyKey(inv.idempotencyKey);
1306
- const url = `${API}/crm/v3/objects/notes/batch/create?idempotencyKey=${encodeURIComponent(idemKey)}`;
2017
+ const url = `${API3}/crm/v3/objects/notes/batch/create?idempotencyKey=${encodeURIComponent(idemKey)}`;
1307
2018
  const payload = {
1308
2019
  inputs: [
1309
2020
  {
@@ -1356,7 +2067,7 @@ async function createNote(inv, accessToken) {
1356
2067
  function sanitizeIdempotencyKey(k) {
1357
2068
  return k.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 64);
1358
2069
  }
1359
- async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
2070
+ async function ensureFreshAccessToken6(creds, clientId, clientSecret) {
1360
2071
  if (creds.kind !== "oauth2") {
1361
2072
  throw new Error("hubspot: expected oauth2 credentials");
1362
2073
  }
@@ -1367,7 +2078,7 @@ async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
1367
2078
  throw new CredentialsExpired("HubSpot access token expired and no refresh token", "");
1368
2079
  }
1369
2080
  const refreshed = await refreshAccessToken({
1370
- tokenUrl: TOKEN_URL4,
2081
+ tokenUrl: TOKEN_URL6,
1371
2082
  clientId,
1372
2083
  clientSecret,
1373
2084
  refreshToken: creds.refreshToken
@@ -1380,9 +2091,9 @@ async function ensureFreshAccessToken4(creds, clientId, clientSecret) {
1380
2091
 
1381
2092
  // src/connectors/adapters/slack.ts
1382
2093
  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";
2094
+ var AUTH_URL7 = "https://slack.com/oauth/v2/authorize";
2095
+ var TOKEN_URL7 = "https://slack.com/api/oauth.v2.access";
2096
+ var API4 = "https://slack.com/api";
1386
2097
  function slack(opts) {
1387
2098
  const { clientId, clientSecret } = opts;
1388
2099
  const adapter = {
@@ -1398,8 +2109,8 @@ function slack(opts) {
1398
2109
  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
2110
  auth: {
1400
2111
  kind: "oauth2",
1401
- authorizationUrl: AUTH_URL5,
1402
- tokenUrl: TOKEN_URL5,
2112
+ authorizationUrl: AUTH_URL7,
2113
+ tokenUrl: TOKEN_URL7,
1403
2114
  scopes: SCOPES5,
1404
2115
  clientIdEnv: "SLACK_OAUTH_CLIENT_ID",
1405
2116
  clientSecretEnv: "SLACK_OAUTH_CLIENT_SECRET"
@@ -1451,7 +2162,7 @@ function slack(opts) {
1451
2162
  const accessToken = readBotToken(inv.source.credentials);
1452
2163
  if (inv.capabilityName === "lookup_user") {
1453
2164
  const { email } = inv.args;
1454
- const url = `${API2}/users.lookupByEmail?email=${encodeURIComponent(email)}`;
2165
+ const url = `${API4}/users.lookupByEmail?email=${encodeURIComponent(email)}`;
1455
2166
  const json = await slackGet(url, accessToken, inv.source.id);
1456
2167
  if (!json.ok) {
1457
2168
  if (json.error === "users_not_found") {
@@ -1471,7 +2182,7 @@ function slack(opts) {
1471
2182
  limit: String(Math.min(Math.max(1, limit ?? 200), 1e3)),
1472
2183
  types: types ?? "public_channel,private_channel"
1473
2184
  });
1474
- const json = await slackGet(`${API2}/conversations.list?${params.toString()}`, accessToken, inv.source.id);
2185
+ const json = await slackGet(`${API4}/conversations.list?${params.toString()}`, accessToken, inv.source.id);
1475
2186
  if (!json.ok) {
1476
2187
  throw new Error(`slack list_channels: ${json.error ?? "unknown"}`);
1477
2188
  }
@@ -1489,7 +2200,7 @@ function slack(opts) {
1489
2200
  }
1490
2201
  const accessToken = readBotToken(inv.source.credentials);
1491
2202
  const { channel, text, blocks } = inv.args;
1492
- const res = await fetch(`${API2}/chat.postMessage`, {
2203
+ const res = await fetch(`${API4}/chat.postMessage`, {
1493
2204
  method: "POST",
1494
2205
  headers: {
1495
2206
  authorization: `Bearer ${accessToken}`,
@@ -1524,7 +2235,7 @@ function slack(opts) {
1524
2235
  throw new Error("Slack OAuth client not configured (SLACK_OAUTH_CLIENT_ID / _SECRET)");
1525
2236
  }
1526
2237
  const tokens = await exchangeAuthorizationCode({
1527
- tokenUrl: TOKEN_URL5,
2238
+ tokenUrl: TOKEN_URL7,
1528
2239
  clientId,
1529
2240
  clientSecret,
1530
2241
  code: input.code,
@@ -1547,7 +2258,7 @@ function slack(opts) {
1547
2258
  return creds;
1548
2259
  }
1549
2260
  const refreshed = await refreshAccessToken({
1550
- tokenUrl: TOKEN_URL5,
2261
+ tokenUrl: TOKEN_URL7,
1551
2262
  clientId,
1552
2263
  clientSecret,
1553
2264
  refreshToken: creds.refreshToken
@@ -1562,7 +2273,7 @@ function slack(opts) {
1562
2273
  async test(source) {
1563
2274
  try {
1564
2275
  const accessToken = readBotToken(source.credentials);
1565
- const res = await fetch(`${API2}/auth.test`, {
2276
+ const res = await fetch(`${API4}/auth.test`, {
1566
2277
  method: "POST",
1567
2278
  headers: { authorization: `Bearer ${accessToken}` },
1568
2279
  signal: AbortSignal.timeout(8e3)
@@ -1602,9 +2313,9 @@ async function slackGet(url, accessToken, dataSourceId) {
1602
2313
  }
1603
2314
 
1604
2315
  // 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";
2316
+ var AUTH_URL8 = "https://api.notion.com/v1/oauth/authorize";
2317
+ var TOKEN_URL8 = "https://api.notion.com/v1/oauth/token";
2318
+ var API5 = "https://api.notion.com/v1";
1608
2319
  var NOTION_VERSION = "2022-06-28";
1609
2320
  function notionDatabase(opts) {
1610
2321
  const { clientId, clientSecret } = opts;
@@ -1615,8 +2326,8 @@ function notionDatabase(opts) {
1615
2326
  description: "Query a Notion database, create new pages, and update existing ones with optimistic concurrency via last_edited_time.",
1616
2327
  auth: {
1617
2328
  kind: "oauth2",
1618
- authorizationUrl: AUTH_URL6,
1619
- tokenUrl: TOKEN_URL6,
2329
+ authorizationUrl: AUTH_URL8,
2330
+ tokenUrl: TOKEN_URL8,
1620
2331
  // Notion does not use OAuth scopes — the workspace owner picks
1621
2332
  // which pages/databases the integration sees during install. We
1622
2333
  // declare an empty scope list so the consent screen renders cleanly.
@@ -1691,7 +2402,7 @@ function notionDatabase(opts) {
1691
2402
  };
1692
2403
  if (filter) body.filter = filter;
1693
2404
  if (startCursor) body.start_cursor = startCursor;
1694
- const res = await fetch(`${API3}/databases/${encodeURIComponent(databaseId)}/query`, {
2405
+ const res = await fetch(`${API5}/databases/${encodeURIComponent(databaseId)}/query`, {
1695
2406
  method: "POST",
1696
2407
  headers: notionHeaders(accessToken),
1697
2408
  body: JSON.stringify(body),
@@ -1730,7 +2441,7 @@ function notionDatabase(opts) {
1730
2441
  redirect_uri: input.redirectUri,
1731
2442
  code_verifier: input.codeVerifier
1732
2443
  });
1733
- const res = await fetch(TOKEN_URL6, {
2444
+ const res = await fetch(TOKEN_URL8, {
1734
2445
  method: "POST",
1735
2446
  headers: {
1736
2447
  authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`,
@@ -1769,7 +2480,7 @@ function notionDatabase(opts) {
1769
2480
  throw new Error("notion-database.refreshToken: missing refresh token");
1770
2481
  }
1771
2482
  const refreshed = await refreshAccessToken({
1772
- tokenUrl: TOKEN_URL6,
2483
+ tokenUrl: TOKEN_URL8,
1773
2484
  clientId,
1774
2485
  clientSecret,
1775
2486
  refreshToken: creds.refreshToken
@@ -1784,7 +2495,7 @@ function notionDatabase(opts) {
1784
2495
  async test(source) {
1785
2496
  try {
1786
2497
  const accessToken = readToken(source.credentials);
1787
- const res = await fetch(`${API3}/users/me`, {
2498
+ const res = await fetch(`${API5}/users/me`, {
1788
2499
  headers: notionHeaders(accessToken),
1789
2500
  signal: AbortSignal.timeout(8e3)
1790
2501
  });
@@ -1801,7 +2512,7 @@ function notionDatabase(opts) {
1801
2512
  async function createPage(inv, accessToken) {
1802
2513
  const databaseId = readMetaString3(inv.source.metadata, "databaseId");
1803
2514
  const { properties } = inv.args;
1804
- const res = await fetch(`${API3}/pages`, {
2515
+ const res = await fetch(`${API5}/pages`, {
1805
2516
  method: "POST",
1806
2517
  headers: {
1807
2518
  ...notionHeaders(accessToken),
@@ -1835,7 +2546,7 @@ async function createPage(inv, accessToken) {
1835
2546
  async function updatePage(inv, accessToken) {
1836
2547
  const { pageId, properties, expectedLastEditedTime } = inv.args;
1837
2548
  if (expectedLastEditedTime) {
1838
- const headRes = await fetch(`${API3}/pages/${encodeURIComponent(pageId)}`, {
2549
+ const headRes = await fetch(`${API5}/pages/${encodeURIComponent(pageId)}`, {
1839
2550
  headers: notionHeaders(accessToken),
1840
2551
  signal: AbortSignal.timeout(1e4)
1841
2552
  });
@@ -1855,7 +2566,7 @@ async function updatePage(inv, accessToken) {
1855
2566
  );
1856
2567
  }
1857
2568
  }
1858
- const res = await fetch(`${API3}/pages/${encodeURIComponent(pageId)}`, {
2569
+ const res = await fetch(`${API5}/pages/${encodeURIComponent(pageId)}`, {
1859
2570
  method: "PATCH",
1860
2571
  headers: notionHeaders(accessToken),
1861
2572
  body: JSON.stringify({ properties }),
@@ -1901,6 +2612,304 @@ function readMetaString3(meta, key) {
1901
2612
  return v;
1902
2613
  }
1903
2614
 
2615
+ // src/connectors/adapters/docuseal.ts
2616
+ import { createHmac, timingSafeEqual } from "crypto";
2617
+ var DEFAULT_BASE = "https://api.docuseal.com";
2618
+ function readDocuSealCredentials(creds) {
2619
+ if (creds.kind === "api-key" && typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
2620
+ return { apiKey: creds.apiKey };
2621
+ }
2622
+ if (creds.kind === "custom" && creds.values && typeof creds.values.apiKey === "string" && creds.values.apiKey.length > 0) {
2623
+ const webhookSecret = typeof creds.values.webhookSecret === "string" ? creds.values.webhookSecret : void 0;
2624
+ return { apiKey: creds.values.apiKey, webhookSecret };
2625
+ }
2626
+ throw new Error("docuseal: expected api-key credentials (apiKey + optional webhookSecret)");
2627
+ }
2628
+ function docuseal(opts = {}) {
2629
+ const baseUrl = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/$/, "");
2630
+ const timeoutMs = opts.timeoutMs ?? 3e4;
2631
+ const adapter = {
2632
+ manifest: {
2633
+ kind: "docuseal",
2634
+ displayName: "DocuSeal",
2635
+ description: "Send documents for e-signature via DocuSeal, poll submission status, void in-flight submissions, and react to push events when submitters sign.",
2636
+ auth: {
2637
+ kind: "api-key",
2638
+ hint: "Paste a DocuSeal personal API key (settings \u2192 API). Optional webhook secret enables push-driven workflows."
2639
+ },
2640
+ category: "doc",
2641
+ defaultConsistencyModel: "authoritative",
2642
+ capabilities: [
2643
+ {
2644
+ name: "get_submission",
2645
+ class: "read",
2646
+ description: "Fetch the current status of a DocuSeal submission and each submitter.",
2647
+ parameters: {
2648
+ type: "object",
2649
+ properties: { submissionId: { type: "string" } },
2650
+ required: ["submissionId"]
2651
+ }
2652
+ },
2653
+ {
2654
+ name: "create_submission",
2655
+ class: "mutation",
2656
+ description: "Send a template for signature to one or more submitters. external_id is the idempotency key; retries return the original submission.",
2657
+ cas: "native-idempotency",
2658
+ externalEffect: true,
2659
+ parameters: {
2660
+ type: "object",
2661
+ properties: {
2662
+ templateId: { type: "string" },
2663
+ submitters: {
2664
+ type: "array",
2665
+ items: {
2666
+ type: "object",
2667
+ properties: {
2668
+ email: { type: "string" },
2669
+ name: { type: "string" },
2670
+ role: { type: "string" },
2671
+ values: { type: "object", additionalProperties: true }
2672
+ },
2673
+ required: ["email"]
2674
+ }
2675
+ },
2676
+ sendEmail: { type: "boolean", default: true },
2677
+ message: { type: "string" }
2678
+ },
2679
+ required: ["templateId", "submitters"]
2680
+ }
2681
+ },
2682
+ {
2683
+ name: "void_submission",
2684
+ class: "mutation",
2685
+ description: "Cancel an in-flight submission. CAS via updated_at \u2014 concurrent voids return ResourceContention.",
2686
+ cas: "etag-if-match",
2687
+ externalEffect: true,
2688
+ parameters: {
2689
+ type: "object",
2690
+ properties: {
2691
+ submissionId: { type: "string" },
2692
+ reason: { type: "string" }
2693
+ },
2694
+ required: ["submissionId"]
2695
+ }
2696
+ }
2697
+ ]
2698
+ },
2699
+ async executeRead(inv) {
2700
+ if (inv.capabilityName !== "get_submission") {
2701
+ throw new Error(`docuseal: unknown read capability ${inv.capabilityName}`);
2702
+ }
2703
+ const { apiKey } = readDocuSealCredentials(inv.source.credentials);
2704
+ const { submissionId } = inv.args;
2705
+ const res = await fetch(`${baseUrl}/submissions/${encodeURIComponent(submissionId)}`, {
2706
+ headers: { "X-Auth-Token": apiKey, accept: "application/json" },
2707
+ signal: AbortSignal.timeout(timeoutMs)
2708
+ });
2709
+ if (res.status === 401) throw new CredentialsExpired("DocuSeal rejected API key (401)", inv.source.id);
2710
+ if (res.status === 404) {
2711
+ throw new Error(`docuseal get_submission: submission ${submissionId} not found`);
2712
+ }
2713
+ if (!res.ok) {
2714
+ const text = await res.text().catch(() => "");
2715
+ throw new Error(`docuseal get_submission ${res.status}: ${text.slice(0, 200)}`);
2716
+ }
2717
+ const json = await res.json();
2718
+ return {
2719
+ data: normalizeSubmission(json),
2720
+ etag: json.updated_at,
2721
+ fetchedAt: Date.now()
2722
+ };
2723
+ },
2724
+ async executeMutation(inv) {
2725
+ const { apiKey } = readDocuSealCredentials(inv.source.credentials);
2726
+ if (inv.capabilityName === "create_submission") return createSubmission(inv, apiKey, baseUrl, timeoutMs);
2727
+ if (inv.capabilityName === "void_submission") return voidSubmission(inv, apiKey, baseUrl, timeoutMs);
2728
+ throw new Error(`docuseal: unknown mutation capability ${inv.capabilityName}`);
2729
+ },
2730
+ verifySignature({ rawBody, headers, source }) {
2731
+ const creds = (() => {
2732
+ try {
2733
+ return readDocuSealCredentials(source.credentials);
2734
+ } catch {
2735
+ return null;
2736
+ }
2737
+ })();
2738
+ if (!creds?.webhookSecret) return { valid: false, reason: "missing_webhook_secret" };
2739
+ const sig = firstHeader(headers, "x-docuseal-signature");
2740
+ if (!sig) return { valid: false, reason: "missing_signature_header" };
2741
+ const expected = createHmac("sha256", creds.webhookSecret).update(rawBody).digest("hex");
2742
+ const a = Buffer.from(sig.toLowerCase(), "utf-8");
2743
+ const b = Buffer.from(expected, "utf-8");
2744
+ if (a.length !== b.length) return { valid: false, reason: "invalid_signature" };
2745
+ return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: "invalid_signature" };
2746
+ },
2747
+ async handleInboundEvent({ rawBody }) {
2748
+ let parsed;
2749
+ try {
2750
+ parsed = JSON.parse(rawBody);
2751
+ } catch {
2752
+ return { events: [], response: { status: 400, body: { error: "invalid_json" } } };
2753
+ }
2754
+ if (!parsed || typeof parsed !== "object") {
2755
+ return { events: [], response: { status: 400, body: { error: "invalid_payload" } } };
2756
+ }
2757
+ const evt = parsed;
2758
+ const eventType = typeof evt.event_type === "string" ? `docuseal.${evt.event_type}` : "docuseal.unknown";
2759
+ const providerEventId = typeof evt.event_id === "string" ? evt.event_id : void 0;
2760
+ const events = [
2761
+ {
2762
+ eventType,
2763
+ providerEventId,
2764
+ payload: evt
2765
+ }
2766
+ ];
2767
+ return { events };
2768
+ },
2769
+ async test(source) {
2770
+ try {
2771
+ const { apiKey } = readDocuSealCredentials(source.credentials);
2772
+ const res = await fetch(`${baseUrl}/templates?limit=1`, {
2773
+ headers: { "X-Auth-Token": apiKey },
2774
+ signal: AbortSignal.timeout(8e3)
2775
+ });
2776
+ if (res.status === 401) return { ok: false, reason: "DocuSeal rejected API key (401) \u2014 reconnect required" };
2777
+ if (!res.ok) return { ok: false, reason: `DocuSeal returned ${res.status}` };
2778
+ return { ok: true };
2779
+ } catch (err) {
2780
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
2781
+ }
2782
+ }
2783
+ };
2784
+ return adapter;
2785
+ }
2786
+ function normalizeSubmission(s) {
2787
+ return {
2788
+ submissionId: String(s.id),
2789
+ status: s.status ?? "pending",
2790
+ updatedAt: s.updated_at,
2791
+ createdAt: s.created_at,
2792
+ completedAt: s.completed_at ?? void 0,
2793
+ auditLogUrl: s.audit_log_url,
2794
+ combinedDocumentUrl: s.combined_document_url,
2795
+ submitters: (s.submitters ?? []).map((sub) => ({
2796
+ email: sub.email,
2797
+ name: sub.name,
2798
+ role: sub.role,
2799
+ slug: sub.slug,
2800
+ status: sub.status ?? "awaiting",
2801
+ completedAt: sub.completed_at ?? void 0,
2802
+ url: sub.embed_src
2803
+ }))
2804
+ };
2805
+ }
2806
+ async function createSubmission(inv, apiKey, baseUrl, timeoutMs) {
2807
+ const { templateId, submitters, sendEmail, message } = inv.args;
2808
+ const body = {
2809
+ template_id: templateId,
2810
+ external_id: inv.idempotencyKey,
2811
+ send_email: sendEmail ?? true,
2812
+ message,
2813
+ submitters: submitters.map((s) => ({
2814
+ email: s.email,
2815
+ name: s.name,
2816
+ role: s.role,
2817
+ values: s.values
2818
+ }))
2819
+ };
2820
+ const res = await fetch(`${baseUrl}/submissions`, {
2821
+ method: "POST",
2822
+ headers: {
2823
+ "X-Auth-Token": apiKey,
2824
+ "content-type": "application/json",
2825
+ accept: "application/json"
2826
+ },
2827
+ body: JSON.stringify(body),
2828
+ signal: AbortSignal.timeout(timeoutMs)
2829
+ });
2830
+ if (res.status === 401) throw new CredentialsExpired("DocuSeal rejected API key (401)", inv.source.id);
2831
+ if (res.status === 429) {
2832
+ const retryAfter = Number(res.headers.get("retry-after") ?? "5");
2833
+ return {
2834
+ status: "rate-limited",
2835
+ retryAfterMs: Number.isFinite(retryAfter) ? retryAfter * 1e3 : 5e3,
2836
+ message: "DocuSeal rate-limited create_submission"
2837
+ };
2838
+ }
2839
+ if (res.status === 409) {
2840
+ const conflictJson = await res.json().catch(() => ({}));
2841
+ const original = conflictJson.submission ?? conflictJson;
2842
+ return {
2843
+ status: "committed",
2844
+ data: normalizeSubmission(original),
2845
+ etagAfter: original.updated_at,
2846
+ committedAt: Date.now(),
2847
+ idempotentReplay: true
2848
+ };
2849
+ }
2850
+ if (!res.ok) {
2851
+ const text = await res.text().catch(() => "");
2852
+ throw new Error(`docuseal create_submission ${res.status}: ${text.slice(0, 200)}`);
2853
+ }
2854
+ const created = await res.json();
2855
+ const sub = Array.isArray(created) ? created[0] : created;
2856
+ if (!sub) {
2857
+ throw new Error("docuseal create_submission: empty response body");
2858
+ }
2859
+ return {
2860
+ status: "committed",
2861
+ data: normalizeSubmission(sub),
2862
+ etagAfter: sub.updated_at,
2863
+ committedAt: Date.now(),
2864
+ idempotentReplay: false
2865
+ };
2866
+ }
2867
+ async function voidSubmission(inv, apiKey, baseUrl, timeoutMs) {
2868
+ const { submissionId, reason } = inv.args;
2869
+ const headers = {
2870
+ "X-Auth-Token": apiKey,
2871
+ accept: "application/json"
2872
+ };
2873
+ if (inv.expectedEtag) headers["if-match"] = inv.expectedEtag;
2874
+ if (reason) headers["x-docuseal-void-reason"] = reason;
2875
+ const res = await fetch(`${baseUrl}/submissions/${encodeURIComponent(submissionId)}`, {
2876
+ method: "DELETE",
2877
+ headers,
2878
+ signal: AbortSignal.timeout(timeoutMs)
2879
+ });
2880
+ if (res.status === 401) throw new CredentialsExpired("DocuSeal rejected API key (401)", inv.source.id);
2881
+ if (res.status === 404) {
2882
+ throw new Error(`docuseal void_submission: submission ${submissionId} not found`);
2883
+ }
2884
+ if (res.status === 412) {
2885
+ throw new ResourceContention(`docuseal void_submission: submission ${submissionId} updated since last read`);
2886
+ }
2887
+ if (res.status === 429) {
2888
+ const retryAfter = Number(res.headers.get("retry-after") ?? "5");
2889
+ return {
2890
+ status: "rate-limited",
2891
+ retryAfterMs: Number.isFinite(retryAfter) ? retryAfter * 1e3 : 5e3,
2892
+ message: "DocuSeal rate-limited void_submission"
2893
+ };
2894
+ }
2895
+ if (!res.ok) {
2896
+ const text = await res.text().catch(() => "");
2897
+ throw new Error(`docuseal void_submission ${res.status}: ${text.slice(0, 200)}`);
2898
+ }
2899
+ const json = await res.json().catch(() => ({}));
2900
+ return {
2901
+ status: "committed",
2902
+ data: {
2903
+ submissionId,
2904
+ status: "voided",
2905
+ voidedAt: json.updated_at ?? (/* @__PURE__ */ new Date()).toISOString()
2906
+ },
2907
+ etagAfter: json.updated_at,
2908
+ committedAt: Date.now(),
2909
+ idempotentReplay: false
2910
+ };
2911
+ }
2912
+
1904
2913
  // src/connectors/adapters/declarative-rest.ts
1905
2914
  function declarativeRestConnector(spec) {
1906
2915
  const capabilities = spec.capabilities.map(operationToCapability);
@@ -2100,7 +3109,7 @@ async function safeErrorText(res) {
2100
3109
  }
2101
3110
 
2102
3111
  // src/connectors/adapters/twilio-sms.ts
2103
- var API4 = "https://api.twilio.com/2010-04-01";
3112
+ var API6 = "https://api.twilio.com/2010-04-01";
2104
3113
  var LOOKUP_API = "https://lookups.twilio.com/v1";
2105
3114
  var twilioSmsConnector = {
2106
3115
  manifest: {
@@ -2192,7 +3201,7 @@ var twilioSmsConnector = {
2192
3201
  params.set("PageSize", String(Math.min(Math.max(1, limit ?? 20), 100)));
2193
3202
  if (to) params.set("To", to);
2194
3203
  if (from) params.set("From", from);
2195
- const url = `${API4}/Accounts/${encodeURIComponent(auth.accountSid)}/Messages.json?${params.toString()}`;
3204
+ const url = `${API6}/Accounts/${encodeURIComponent(auth.accountSid)}/Messages.json?${params.toString()}`;
2196
3205
  const res = await fetch(url, {
2197
3206
  headers: { authorization: basicAuth(auth) },
2198
3207
  signal: AbortSignal.timeout(1e4)
@@ -2218,7 +3227,7 @@ var twilioSmsConnector = {
2218
3227
  const { to, body, from } = inv.args;
2219
3228
  const fromNumber = from ?? readMetaString4(inv.source.metadata, "fromNumber");
2220
3229
  const formBody = new URLSearchParams({ To: to, From: fromNumber, Body: body });
2221
- const url = `${API4}/Accounts/${encodeURIComponent(auth.accountSid)}/Messages.json`;
3230
+ const url = `${API6}/Accounts/${encodeURIComponent(auth.accountSid)}/Messages.json`;
2222
3231
  const res = await fetch(url, {
2223
3232
  method: "POST",
2224
3233
  headers: {
@@ -2248,7 +3257,7 @@ var twilioSmsConnector = {
2248
3257
  async test(source) {
2249
3258
  try {
2250
3259
  const auth = parseAuth(source.credentials);
2251
- const res = await fetch(`${API4}/Accounts/${encodeURIComponent(auth.accountSid)}.json`, {
3260
+ const res = await fetch(`${API6}/Accounts/${encodeURIComponent(auth.accountSid)}.json`, {
2252
3261
  headers: { authorization: basicAuth(auth) },
2253
3262
  signal: AbortSignal.timeout(8e3)
2254
3263
  });
@@ -2293,15 +3302,15 @@ function readMetaString4(meta, key) {
2293
3302
  }
2294
3303
 
2295
3304
  // src/connectors/adapters/stripe-pack.ts
2296
- var API5 = "https://api.stripe.com/v1";
3305
+ var API7 = "https://api.stripe.com/v1";
2297
3306
  var stripePackConnector = {
2298
3307
  manifest: {
2299
3308
  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.",
3309
+ displayName: "Stripe (customers, invoices, checkout, subscriptions)",
3310
+ 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
3311
  auth: {
2303
3312
  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."
3313
+ hint: "Paste a Stripe restricted key (rk_live_\u2026) with read on customers + subscriptions and write on invoices + checkout + subscriptions + billing portal."
2305
3314
  },
2306
3315
  category: "commerce",
2307
3316
  defaultConsistencyModel: "authoritative",
@@ -2316,6 +3325,20 @@ var stripePackConnector = {
2316
3325
  required: ["email"]
2317
3326
  }
2318
3327
  },
3328
+ {
3329
+ name: "list_subscriptions",
3330
+ class: "read",
3331
+ description: "List a customer's subscriptions. Optionally filter by status ('active', 'past_due', 'canceled', 'all').",
3332
+ parameters: {
3333
+ type: "object",
3334
+ properties: {
3335
+ customerId: { type: "string" },
3336
+ status: { type: "string", enum: ["active", "past_due", "unpaid", "canceled", "incomplete", "trialing", "all"], default: "all" },
3337
+ limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }
3338
+ },
3339
+ required: ["customerId"]
3340
+ }
3341
+ },
2319
3342
  {
2320
3343
  name: "create_invoice",
2321
3344
  class: "mutation",
@@ -2371,16 +3394,47 @@ var stripePackConnector = {
2371
3394
  },
2372
3395
  required: ["successUrl", "cancelUrl", "lineItems"]
2373
3396
  }
3397
+ },
3398
+ {
3399
+ name: "cancel_subscription",
3400
+ class: "mutation",
3401
+ description: "Cancel a subscription. Default cancels immediately; pass atPeriodEnd=true to schedule cancellation for the end of the current billing period.",
3402
+ cas: "native-idempotency",
3403
+ externalEffect: true,
3404
+ parameters: {
3405
+ type: "object",
3406
+ properties: {
3407
+ subscriptionId: { type: "string" },
3408
+ atPeriodEnd: { type: "boolean", default: false }
3409
+ },
3410
+ required: ["subscriptionId"]
3411
+ }
3412
+ },
3413
+ {
3414
+ name: "create_billing_portal_session",
3415
+ class: "mutation",
3416
+ description: "Create a Stripe billing portal session and return its URL. Hand-off for the customer to self-serve cancel/upgrade/update-card.",
3417
+ cas: "native-idempotency",
3418
+ externalEffect: true,
3419
+ parameters: {
3420
+ type: "object",
3421
+ properties: {
3422
+ customerId: { type: "string" },
3423
+ returnUrl: { type: "string" }
3424
+ },
3425
+ required: ["customerId", "returnUrl"]
3426
+ }
2374
3427
  }
2375
3428
  ]
2376
3429
  },
2377
3430
  async executeRead(inv) {
3431
+ const apiKey = readApiKey(inv.source.credentials);
3432
+ if (inv.capabilityName === "list_subscriptions") return listSubscriptions(inv, apiKey);
2378
3433
  if (inv.capabilityName !== "find_customer") {
2379
3434
  throw new Error(`stripe-pack: unknown read capability ${inv.capabilityName}`);
2380
3435
  }
2381
- const apiKey = readApiKey(inv.source.credentials);
2382
3436
  const { email } = inv.args;
2383
- const url = `${API5}/customers/search?query=${encodeURIComponent(`email:'${email.toLowerCase()}'`)}&limit=1`;
3437
+ const url = `${API7}/customers/search?query=${encodeURIComponent(`email:'${email.toLowerCase()}'`)}&limit=1`;
2384
3438
  const res = await fetch(url, {
2385
3439
  headers: { authorization: `Bearer ${apiKey}` },
2386
3440
  signal: AbortSignal.timeout(1e4)
@@ -2403,12 +3457,14 @@ var stripePackConnector = {
2403
3457
  const apiKey = readApiKey(inv.source.credentials);
2404
3458
  if (inv.capabilityName === "create_invoice") return createInvoice(inv, apiKey);
2405
3459
  if (inv.capabilityName === "create_checkout_session") return createCheckoutSession(inv, apiKey);
3460
+ if (inv.capabilityName === "cancel_subscription") return cancelSubscription(inv, apiKey);
3461
+ if (inv.capabilityName === "create_billing_portal_session") return createBillingPortalSession(inv, apiKey);
2406
3462
  throw new Error(`stripe-pack: unknown mutation capability ${inv.capabilityName}`);
2407
3463
  },
2408
3464
  async test(source) {
2409
3465
  try {
2410
3466
  const apiKey = readApiKey(source.credentials);
2411
- const res = await fetch(`${API5}/account`, {
3467
+ const res = await fetch(`${API7}/account`, {
2412
3468
  headers: { authorization: `Bearer ${apiKey}` },
2413
3469
  signal: AbortSignal.timeout(8e3)
2414
3470
  });
@@ -2434,7 +3490,7 @@ async function createInvoice(inv, apiKey) {
2434
3490
  quantity: String(it.quantity ?? 1)
2435
3491
  });
2436
3492
  if (it.description) body.set("description", it.description);
2437
- const res = await fetch(`${API5}/invoiceitems`, {
3493
+ const res = await fetch(`${API7}/invoiceitems`, {
2438
3494
  method: "POST",
2439
3495
  headers: {
2440
3496
  authorization: `Bearer ${apiKey}`,
@@ -2459,7 +3515,7 @@ async function createInvoice(inv, apiKey) {
2459
3515
  collection_method: "send_invoice",
2460
3516
  days_until_due: "14"
2461
3517
  });
2462
- const invRes = await fetch(`${API5}/invoices`, {
3518
+ const invRes = await fetch(`${API7}/invoices`, {
2463
3519
  method: "POST",
2464
3520
  headers: {
2465
3521
  authorization: `Bearer ${apiKey}`,
@@ -2497,7 +3553,7 @@ async function createCheckoutSession(inv, apiKey) {
2497
3553
  body.set(`line_items[${i}][price]`, it.price);
2498
3554
  body.set(`line_items[${i}][quantity]`, String(it.quantity ?? 1));
2499
3555
  });
2500
- const res = await fetch(`${API5}/checkout/sessions`, {
3556
+ const res = await fetch(`${API7}/checkout/sessions`, {
2501
3557
  method: "POST",
2502
3558
  headers: {
2503
3559
  authorization: `Bearer ${apiKey}`,
@@ -2523,6 +3579,118 @@ async function createCheckoutSession(inv, apiKey) {
2523
3579
  idempotentReplay: false
2524
3580
  };
2525
3581
  }
3582
+ async function listSubscriptions(inv, apiKey) {
3583
+ const { customerId, status, limit } = inv.args;
3584
+ const params = new URLSearchParams({ customer: customerId, limit: String(limit ?? 10) });
3585
+ if (status && status !== "all") params.set("status", status);
3586
+ else params.set("status", "all");
3587
+ const res = await fetch(`${API7}/subscriptions?${params.toString()}`, {
3588
+ headers: { authorization: `Bearer ${apiKey}` },
3589
+ signal: AbortSignal.timeout(1e4)
3590
+ });
3591
+ if (res.status === 401) {
3592
+ throw new CredentialsExpired("Stripe rejected API key (401)", inv.source.id);
3593
+ }
3594
+ if (!res.ok) {
3595
+ const text = await res.text().catch(() => "");
3596
+ throw new Error(`stripe-pack list_subscriptions ${res.status}: ${text.slice(0, 200)}`);
3597
+ }
3598
+ const json = await res.json();
3599
+ const subscriptions = (json.data ?? []).map((sub) => ({
3600
+ id: sub.id,
3601
+ status: sub.status,
3602
+ currentPeriodEnd: sub.current_period_end,
3603
+ cancelAtPeriodEnd: sub.cancel_at_period_end ?? false,
3604
+ items: (sub.items?.data ?? []).map((it) => ({
3605
+ priceId: it.price?.id,
3606
+ productId: it.price?.product,
3607
+ unitAmount: it.price?.unit_amount,
3608
+ currency: it.price?.currency,
3609
+ interval: it.price?.recurring?.interval
3610
+ }))
3611
+ }));
3612
+ return {
3613
+ data: { subscriptions },
3614
+ fetchedAt: Date.now()
3615
+ };
3616
+ }
3617
+ async function cancelSubscription(inv, apiKey) {
3618
+ const { subscriptionId, atPeriodEnd } = inv.args;
3619
+ let res;
3620
+ if (atPeriodEnd) {
3621
+ const body = new URLSearchParams({ cancel_at_period_end: "true" });
3622
+ res = await fetch(`${API7}/subscriptions/${encodeURIComponent(subscriptionId)}`, {
3623
+ method: "POST",
3624
+ headers: {
3625
+ authorization: `Bearer ${apiKey}`,
3626
+ "content-type": "application/x-www-form-urlencoded",
3627
+ "idempotency-key": inv.idempotencyKey
3628
+ },
3629
+ body,
3630
+ signal: AbortSignal.timeout(15e3)
3631
+ });
3632
+ } else {
3633
+ res = await fetch(`${API7}/subscriptions/${encodeURIComponent(subscriptionId)}`, {
3634
+ method: "DELETE",
3635
+ headers: {
3636
+ authorization: `Bearer ${apiKey}`,
3637
+ "idempotency-key": inv.idempotencyKey
3638
+ },
3639
+ signal: AbortSignal.timeout(15e3)
3640
+ });
3641
+ }
3642
+ if (res.status === 401) throw new CredentialsExpired("Stripe rejected API key (401)", inv.source.id);
3643
+ if (res.status === 404) throw new Error(`stripe-pack cancel_subscription: subscription ${subscriptionId} not found`);
3644
+ if (res.status === 409) {
3645
+ throw new ResourceContention("Stripe subscription conflict \u2014 retry rejected by idempotency check");
3646
+ }
3647
+ if (!res.ok) {
3648
+ const text = await res.text().catch(() => "");
3649
+ throw new Error(`stripe-pack cancel_subscription ${res.status}: ${text.slice(0, 200)}`);
3650
+ }
3651
+ const updated = await res.json();
3652
+ return {
3653
+ status: "committed",
3654
+ data: {
3655
+ id: updated.id,
3656
+ status: updated.status,
3657
+ cancelAtPeriodEnd: updated.cancel_at_period_end ?? false,
3658
+ canceledAt: updated.canceled_at,
3659
+ currentPeriodEnd: updated.current_period_end
3660
+ },
3661
+ committedAt: Date.now(),
3662
+ idempotentReplay: false
3663
+ };
3664
+ }
3665
+ async function createBillingPortalSession(inv, apiKey) {
3666
+ const { customerId, returnUrl } = inv.args;
3667
+ const body = new URLSearchParams({ customer: customerId, return_url: returnUrl });
3668
+ const res = await fetch(`${API7}/billing_portal/sessions`, {
3669
+ method: "POST",
3670
+ headers: {
3671
+ authorization: `Bearer ${apiKey}`,
3672
+ "content-type": "application/x-www-form-urlencoded",
3673
+ "idempotency-key": inv.idempotencyKey
3674
+ },
3675
+ body,
3676
+ signal: AbortSignal.timeout(15e3)
3677
+ });
3678
+ if (res.status === 401) throw new CredentialsExpired("Stripe rejected API key (401)", inv.source.id);
3679
+ if (res.status === 409) {
3680
+ throw new ResourceContention("Stripe billing portal session conflict \u2014 retry rejected by idempotency check");
3681
+ }
3682
+ if (!res.ok) {
3683
+ const text = await res.text().catch(() => "");
3684
+ throw new Error(`stripe-pack create_billing_portal_session ${res.status}: ${text.slice(0, 200)}`);
3685
+ }
3686
+ const created = await res.json();
3687
+ return {
3688
+ status: "committed",
3689
+ data: { sessionId: created.id, url: created.url, returnUrl: created.return_url },
3690
+ committedAt: Date.now(),
3691
+ idempotentReplay: false
3692
+ };
3693
+ }
2526
3694
  function readApiKey(creds) {
2527
3695
  if (creds.kind !== "api-key" || typeof creds.apiKey !== "string" || creds.apiKey.length === 0) {
2528
3696
  throw new Error("stripe-pack: expected api-key credentials");
@@ -2531,7 +3699,7 @@ function readApiKey(creds) {
2531
3699
  }
2532
3700
 
2533
3701
  // src/connectors/adapters/webhook.ts
2534
- import { createHmac } from "crypto";
3702
+ import { createHmac as createHmac2 } from "crypto";
2535
3703
  var webhookConnector = {
2536
3704
  manifest: {
2537
3705
  kind: "webhook",
@@ -2645,96 +3813,12 @@ function signHeaders(creds, body, idempotencyKey) {
2645
3813
  "x-phony-idempotency-key": idempotencyKey
2646
3814
  };
2647
3815
  if (creds.kind === "hmac" && typeof creds.secret === "string" && creds.secret.length > 0) {
2648
- const sig = createHmac("sha256", creds.secret).update(`${ts}.${body}`).digest("hex");
3816
+ const sig = createHmac2("sha256", creds.secret).update(`${ts}.${body}`).digest("hex");
2649
3817
  headers["x-phony-signature"] = `sha256=${sig}`;
2650
3818
  }
2651
3819
  return headers;
2652
3820
  }
2653
3821
 
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
3822
  // src/connectors/adapters/stripe-webhook-receiver.ts
2739
3823
  var stripeWebhookReceiverConnector = {
2740
3824
  manifest: {
@@ -3205,19 +4289,15 @@ export {
3205
4289
  exchangeAuthorizationCode,
3206
4290
  refreshAccessToken,
3207
4291
  _resetPendingFlowsForTests,
3208
- DEFAULT_SIGNATURE_TOLERANCE_SECONDS,
3209
- parseStripeSignatureHeader,
3210
- verifyStripeSignature,
3211
- verifySlackSignature,
3212
- verifyHmacSignature,
3213
- verifyTwilioSignature,
3214
- firstHeader,
3215
4292
  googleCalendar,
4293
+ googleDrive,
3216
4294
  googleSheets,
4295
+ gmail,
3217
4296
  microsoftCalendar,
3218
4297
  hubspot,
3219
4298
  slack,
3220
4299
  notionDatabase,
4300
+ docuseal,
3221
4301
  declarativeRestConnector,
3222
4302
  twilioSmsConnector,
3223
4303
  stripePackConnector,
@@ -3230,4 +4310,4 @@ export {
3230
4310
  asanaConnector,
3231
4311
  salesforceConnector
3232
4312
  };
3233
- //# sourceMappingURL=chunk-WC63AI4Q.js.map
4313
+ //# sourceMappingURL=chunk-GA4VTE3U.js.map