@tangle-network/agent-integrations 0.44.0 → 0.46.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 (41) hide show
  1. package/dist/catalog.d.ts +2 -2
  2. package/dist/catalog.js +6 -6
  3. package/dist/{chunk-YA52626Y.js → chunk-CPZUNSKB.js} +2 -2
  4. package/dist/{chunk-ZBMMGAE2.js → chunk-F6MF4YNI.js} +603 -248
  5. package/dist/{chunk-ZBMMGAE2.js.map → chunk-F6MF4YNI.js.map} +1 -1
  6. package/dist/{chunk-U4W37I5T.js → chunk-LA76XB7M.js} +2 -2
  7. package/dist/{chunk-ITA67K5M.js → chunk-S2BUASSV.js} +2 -2
  8. package/dist/{chunk-BDSGHMY6.js → chunk-UWF33ESC.js} +38 -2
  9. package/dist/chunk-UWF33ESC.js.map +1 -0
  10. package/dist/{chunk-HNHR2MKW.js → chunk-VLVSP7SP.js} +2 -2
  11. package/dist/connect/index.d.ts +2 -2
  12. package/dist/connect/index.js +2 -2
  13. package/dist/connectors/adapters/index.d.ts +79 -7
  14. package/dist/connectors/adapters/index.js +12 -2
  15. package/dist/connectors/index.d.ts +3 -3
  16. package/dist/connectors/index.js +16 -2
  17. package/dist/{consumer-rANg4Qhz.d.ts → consumer-CTMB1xcc.d.ts} +1 -1
  18. package/dist/consumer.d.ts +3 -3
  19. package/dist/consumer.js +2 -2
  20. package/dist/{core-types-ZABxT4_F.d.ts → core-types-BZLzY8Oa.d.ts} +1 -1
  21. package/dist/coverage-catalog.d.ts +2 -2
  22. package/dist/{importers-CWmyogMo.d.ts → importers-6Beu0SQ9.d.ts} +1 -1
  23. package/dist/index.d.ts +9 -9
  24. package/dist/index.js +20 -6
  25. package/dist/mcp.d.ts +3 -3
  26. package/dist/mcp.js +6 -6
  27. package/dist/middleware/index.d.ts +2 -2
  28. package/dist/middleware/index.js +2 -2
  29. package/dist/registry.d.ts +2 -2
  30. package/dist/registry.js +6 -6
  31. package/dist/runtime.d.ts +2 -2
  32. package/dist/runtime.js +6 -6
  33. package/dist/specs.d.ts +2 -2
  34. package/dist/{tangle-id-DE1QrpAY.d.ts → tangle-id-_uZ3ywCF.d.ts} +1 -1
  35. package/dist/{types-DzPmyjGl.d.ts → types-rBPdMcpb.d.ts} +52 -5
  36. package/package.json +1 -1
  37. package/dist/chunk-BDSGHMY6.js.map +0 -1
  38. /package/dist/{chunk-YA52626Y.js.map → chunk-CPZUNSKB.js.map} +0 -0
  39. /package/dist/{chunk-U4W37I5T.js.map → chunk-LA76XB7M.js.map} +0 -0
  40. /package/dist/{chunk-ITA67K5M.js.map → chunk-S2BUASSV.js.map} +0 -0
  41. /package/dist/{chunk-HNHR2MKW.js.map → chunk-VLVSP7SP.js.map} +0 -0
@@ -6,13 +6,15 @@ import {
6
6
  import {
7
7
  CredentialsExpired,
8
8
  DEFAULT_TANGLE_PLATFORM_URL,
9
+ ProviderConfigError,
10
+ ProviderRateLimited,
9
11
  ResourceContention,
10
12
  TANGLE_API_KEY_PREFIX,
11
13
  TANGLE_SERVICE_TOKEN_PREFIX,
12
14
  TangleIdentityUnreachableError,
13
15
  createTangleIdentityClient,
14
16
  tangleIdentity
15
- } from "./chunk-BDSGHMY6.js";
17
+ } from "./chunk-UWF33ESC.js";
16
18
  import {
17
19
  __export
18
20
  } from "./chunk-PZ5AY32C.js";
@@ -112,6 +114,8 @@ __export(adapters_exports, {
112
114
  chatwootConnector: () => chatwootConnector,
113
115
  checkoutConnector: () => checkoutConnector,
114
116
  circleConnector: () => circleConnector,
117
+ classifyGoogleError: () => classifyGoogleError,
118
+ classifyGoogleReason: () => classifyGoogleReason,
115
119
  clayConnector: () => clayConnector,
116
120
  clearoutConnector: () => clearoutConnector,
117
121
  clicdataConnector: () => clicdataConnector,
@@ -215,12 +219,14 @@ __export(adapters_exports, {
215
219
  gmail: () => gmail,
216
220
  gongConnector: () => gongConnector,
217
221
  googleAnalyticsConnector: () => googleAnalyticsConnector,
222
+ googleApiError: () => googleApiError,
218
223
  googleBigqueryConnector: () => googleBigqueryConnector,
219
224
  googleCalendar: () => googleCalendar,
220
225
  googleCloudStorageConnector: () => googleCloudStorageConnector,
221
226
  googleContactsConnector: () => googleContactsConnector,
222
227
  googleDocs: () => googleDocs,
223
228
  googleDrive: () => googleDrive,
229
+ googleErrorReason: () => googleErrorReason,
224
230
  googleForms: () => googleForms,
225
231
  googleGeminiConnector: () => googleGeminiConnector,
226
232
  googleMeetConnector: () => googleMeetConnector,
@@ -230,6 +236,7 @@ __export(adapters_exports, {
230
236
  googleSheets: () => googleSheets,
231
237
  googleSlidesConnector: () => googleSlidesConnector,
232
238
  googleTasksConnector: () => googleTasksConnector,
239
+ googleTestFailureReason: () => googleTestFailureReason,
233
240
  googleVertexaiConnector: () => googleVertexaiConnector,
234
241
  googlechatConnector: () => googlechatConnector,
235
242
  gorgiasConnector: () => gorgiasConnector,
@@ -689,6 +696,117 @@ function _resetPendingFlowsForTests() {
689
696
  defaultFlowStore.clear?.();
690
697
  }
691
698
 
699
+ // src/connectors/adapters/google-errors.ts
700
+ var AUTH_REASONS = /* @__PURE__ */ new Set([
701
+ "autherror",
702
+ "authenticationerror",
703
+ "invalidcredentials",
704
+ "invalid_grant",
705
+ "invalidgrant",
706
+ "unauthorized",
707
+ "unauthenticated",
708
+ "logindenied"
709
+ ]);
710
+ var CONFIG_REASONS = /* @__PURE__ */ new Set([
711
+ "accessnotconfigured",
712
+ "insufficientpermissions",
713
+ "insufficientscopes",
714
+ "forbidden",
715
+ "permissiondenied",
716
+ "permission_denied",
717
+ "ssorequired"
718
+ ]);
719
+ var QUOTA_REASONS = /* @__PURE__ */ new Set([
720
+ "ratelimitexceeded",
721
+ "userratelimitexceeded",
722
+ "userratelimitexceededunreg",
723
+ "dailylimitexceeded",
724
+ "quotaexceeded",
725
+ "resourceexhausted",
726
+ "resource_exhausted"
727
+ ]);
728
+ function googleErrorReason(body) {
729
+ if (!body || typeof body !== "object") return void 0;
730
+ const error = body.error;
731
+ if (!error || typeof error !== "object") return void 0;
732
+ const fromErrors = error.errors?.find(
733
+ (entry) => typeof entry?.reason === "string" && entry.reason.length > 0
734
+ )?.reason;
735
+ if (fromErrors) return fromErrors;
736
+ if (typeof error.status === "string" && error.status.length > 0) {
737
+ return error.status;
738
+ }
739
+ return void 0;
740
+ }
741
+ function classifyGoogleReason(reason) {
742
+ if (!reason) return void 0;
743
+ const key = reason.toLowerCase();
744
+ if (QUOTA_REASONS.has(key)) return "quota";
745
+ if (AUTH_REASONS.has(key)) return "auth";
746
+ if (CONFIG_REASONS.has(key)) return "config";
747
+ return void 0;
748
+ }
749
+ function stringifyBody(body) {
750
+ const text = typeof body === "string" ? body : JSON.stringify(body ?? {});
751
+ return text.slice(0, 200);
752
+ }
753
+ function classifyGoogleError(status, body, context, dataSourceId) {
754
+ const reason = googleErrorReason(body);
755
+ const cls = classifyGoogleReason(reason);
756
+ const init = { status, reason, body };
757
+ const suffix = reason ? `: ${reason}` : "";
758
+ if (cls === "quota" || status === 429) {
759
+ return new ProviderRateLimited(
760
+ `${context} rate limit (${status})${suffix}`,
761
+ dataSourceId,
762
+ init
763
+ );
764
+ }
765
+ if (cls === "auth" || status === 401) {
766
+ return new CredentialsExpired(
767
+ `${context} rejected token (${status})${suffix}`,
768
+ dataSourceId,
769
+ init
770
+ );
771
+ }
772
+ if (cls === "config" || status === 403) {
773
+ return new ProviderConfigError(
774
+ `${context} permission/config error (${status})${suffix}`,
775
+ dataSourceId,
776
+ init
777
+ );
778
+ }
779
+ return new Error(`${context} ${status}: ${stringifyBody(body)}`);
780
+ }
781
+ async function googleApiError(res, context, dataSourceId) {
782
+ const body = await readErrorBody(res);
783
+ return classifyGoogleError(res.status, body, context, dataSourceId);
784
+ }
785
+ function googleTestFailureReason(status, body, provider) {
786
+ const reason = googleErrorReason(body);
787
+ const cls = classifyGoogleReason(reason);
788
+ const detail = reason ? `, ${reason}` : "";
789
+ if (cls === "quota" || status === 429) {
790
+ return `${provider} rate limit (${status}${detail}) \u2014 retry later`;
791
+ }
792
+ if (cls === "auth" || status === 401) {
793
+ return `${provider} rejected token (${status}${detail}) \u2014 reconnect required`;
794
+ }
795
+ if (cls === "config" || status === 403) {
796
+ return `${provider} permission/config error (${status}${detail}) \u2014 check the API is enabled and scopes are granted`;
797
+ }
798
+ return `${provider} returned ${status}`;
799
+ }
800
+ async function readErrorBody(res) {
801
+ const text = await res.text().catch(() => "");
802
+ if (!text) return void 0;
803
+ try {
804
+ return JSON.parse(text);
805
+ } catch {
806
+ return text;
807
+ }
808
+ }
809
+
692
810
  // src/connectors/adapters/google-calendar.ts
693
811
  var SCOPE_CALENDAR = "https://www.googleapis.com/auth/calendar";
694
812
  var SCOPE_WRITE = "https://www.googleapis.com/auth/calendar.events";
@@ -871,7 +989,7 @@ function googleCalendar(opts) {
871
989
  if (inv.capabilityName === "list_availability") {
872
990
  const calendarId = readMetaString(inv.source.metadata, "calendarId");
873
991
  const { timeMin, timeMax } = inv.args;
874
- const fb = await freebusyQuery({ accessToken, calendarId, timeMin, timeMax });
992
+ const fb = await freebusyQuery({ accessToken, calendarId, timeMin, timeMax, sourceId: inv.source.id });
875
993
  return {
876
994
  data: { busy: fb.busy },
877
995
  fetchedAt: Date.now()
@@ -891,7 +1009,7 @@ function googleCalendar(opts) {
891
1009
  }
892
1010
  const calendarId = readMetaString(inv.source.metadata, "calendarId");
893
1011
  const { start, end, summary, description, attendees } = inv.args;
894
- const fb = await freebusyQuery({ accessToken, calendarId, timeMin: start, timeMax: end });
1012
+ const fb = await freebusyQuery({ accessToken, calendarId, timeMin: start, timeMax: end, sourceId: inv.source.id });
895
1013
  if (fb.busy.length > 0) {
896
1014
  const startMs = Date.parse(start);
897
1015
  const endMs = Date.parse(end);
@@ -901,7 +1019,8 @@ function googleCalendar(opts) {
901
1019
  calendarId,
902
1020
  searchFromMs: endMs,
903
1021
  durationMs: durMs,
904
- wanted: 3
1022
+ wanted: 3,
1023
+ sourceId: inv.source.id
905
1024
  });
906
1025
  throw new ResourceContention(
907
1026
  `requested slot ${start}\u2013${end} is no longer free`,
@@ -939,12 +1058,8 @@ function googleCalendar(opts) {
939
1058
  idempotentReplay: true
940
1059
  };
941
1060
  }
942
- if (res.status === 401 || res.status === 403) {
943
- throw new CredentialsExpired(`Google Calendar rejected token (${res.status})`, inv.source.id);
944
- }
945
1061
  if (!res.ok) {
946
- const text = await res.text().catch(() => "");
947
- throw new Error(`google-calendar book_slot ${res.status}: ${text.slice(0, 200)}`);
1062
+ throw await googleApiError(res, "google-calendar book_slot", inv.source.id);
948
1063
  }
949
1064
  const created = await res.json();
950
1065
  return {
@@ -1001,10 +1116,10 @@ function googleCalendar(opts) {
1001
1116
  headers: { authorization: `Bearer ${accessToken}` },
1002
1117
  signal: AbortSignal.timeout(8e3)
1003
1118
  });
1004
- if (res.status === 401 || res.status === 403) {
1005
- return { ok: false, reason: `Google rejected token (${res.status}) \u2014 reconnect required` };
1119
+ if (!res.ok) {
1120
+ const body = await res.json().catch(() => void 0);
1121
+ return { ok: false, reason: googleTestFailureReason(res.status, body, "Google Calendar") };
1006
1122
  }
1007
- if (!res.ok) return { ok: false, reason: `Google returned ${res.status}` };
1008
1123
  return { ok: true };
1009
1124
  } catch (err) {
1010
1125
  return { ok: false, reason: err instanceof Error ? err.message : String(err) };
@@ -1048,12 +1163,8 @@ async function createEvent(inv, accessToken, timeoutMs) {
1048
1163
  body: JSON.stringify(event),
1049
1164
  signal: AbortSignal.timeout(timeoutMs)
1050
1165
  });
1051
- if (res.status === 401 || res.status === 403) {
1052
- throw new CredentialsExpired(`Google Calendar rejected token (${res.status})`, inv.source.id);
1053
- }
1054
1166
  if (!res.ok) {
1055
- const text = await res.text().catch(() => "");
1056
- throw new Error(`google-calendar create_event ${res.status}: ${text.slice(0, 200)}`);
1167
+ throw await googleApiError(res, "google-calendar create_event", inv.source.id);
1057
1168
  }
1058
1169
  const created = await res.json();
1059
1170
  return {
@@ -1093,12 +1204,8 @@ async function updateEvent(inv, accessToken, timeoutMs) {
1093
1204
  body: JSON.stringify(patch),
1094
1205
  signal: AbortSignal.timeout(timeoutMs)
1095
1206
  });
1096
- if (res.status === 401 || res.status === 403) {
1097
- throw new CredentialsExpired(`Google Calendar rejected token (${res.status})`, inv.source.id);
1098
- }
1099
1207
  if (!res.ok) {
1100
- const text = await res.text().catch(() => "");
1101
- throw new Error(`google-calendar update_event ${res.status}: ${text.slice(0, 200)}`);
1208
+ throw await googleApiError(res, "google-calendar update_event", inv.source.id);
1102
1209
  }
1103
1210
  const updated = await res.json();
1104
1211
  return {
@@ -1124,13 +1231,9 @@ async function deleteEvent(inv, accessToken, timeoutMs) {
1124
1231
  headers: { authorization: `Bearer ${accessToken}` },
1125
1232
  signal: AbortSignal.timeout(timeoutMs)
1126
1233
  });
1127
- if (res.status === 401 || res.status === 403) {
1128
- throw new CredentialsExpired(`Google Calendar rejected token (${res.status})`, inv.source.id);
1129
- }
1130
1234
  const idempotent = res.status === 404 || res.status === 410;
1131
1235
  if (!res.ok && !idempotent) {
1132
- const text = await res.text().catch(() => "");
1133
- throw new Error(`google-calendar delete_event ${res.status}: ${text.slice(0, 200)}`);
1236
+ throw await googleApiError(res, "google-calendar delete_event", inv.source.id);
1134
1237
  }
1135
1238
  return {
1136
1239
  status: "committed",
@@ -1148,12 +1251,8 @@ async function getEvent(inv, accessToken, timeoutMs) {
1148
1251
  headers: { authorization: `Bearer ${accessToken}` },
1149
1252
  signal: AbortSignal.timeout(timeoutMs)
1150
1253
  });
1151
- if (res.status === 401 || res.status === 403) {
1152
- throw new CredentialsExpired(`Google Calendar rejected token (${res.status})`, inv.source.id);
1153
- }
1154
1254
  if (!res.ok) {
1155
- const text = await res.text().catch(() => "");
1156
- throw new Error(`google-calendar get_event ${res.status}: ${text.slice(0, 200)}`);
1255
+ throw await googleApiError(res, "google-calendar get_event", inv.source.id);
1157
1256
  }
1158
1257
  const event = await res.json();
1159
1258
  return {
@@ -1178,12 +1277,8 @@ async function listEvents(inv, accessToken, timeoutMs) {
1178
1277
  headers: { authorization: `Bearer ${accessToken}` },
1179
1278
  signal: AbortSignal.timeout(timeoutMs)
1180
1279
  });
1181
- if (res.status === 401 || res.status === 403) {
1182
- throw new CredentialsExpired(`Google Calendar rejected token (${res.status})`, inv.source.id);
1183
- }
1184
1280
  if (!res.ok) {
1185
- const text = await res.text().catch(() => "");
1186
- throw new Error(`google-calendar list_events ${res.status}: ${text.slice(0, 200)}`);
1281
+ throw await googleApiError(res, "google-calendar list_events", inv.source.id);
1187
1282
  }
1188
1283
  const json = await res.json();
1189
1284
  return {
@@ -1210,8 +1305,7 @@ async function freebusyQuery(input) {
1210
1305
  signal: AbortSignal.timeout(1e4)
1211
1306
  });
1212
1307
  if (!res.ok) {
1213
- const text = await res.text().catch(() => "");
1214
- throw new Error(`freebusy ${res.status}: ${text.slice(0, 200)}`);
1308
+ throw await googleApiError(res, "google-calendar freebusy", input.sourceId);
1215
1309
  }
1216
1310
  const json = await res.json();
1217
1311
  return { busy: json.calendars?.[input.calendarId]?.busy ?? [] };
@@ -1226,7 +1320,8 @@ async function findNextFreeSlots(input) {
1226
1320
  accessToken: input.accessToken,
1227
1321
  calendarId: input.calendarId,
1228
1322
  timeMin: new Date(cursor).toISOString(),
1229
- timeMax: new Date(windowEnd).toISOString()
1323
+ timeMax: new Date(windowEnd).toISOString(),
1324
+ sourceId: input.sourceId
1230
1325
  });
1231
1326
  const busy = fb.busy.map((b) => ({ s: Date.parse(b.start), e: Date.parse(b.end) })).filter((b) => Number.isFinite(b.s) && Number.isFinite(b.e)).sort((a, b) => a.s - b.s);
1232
1327
  let pos = cursor;
@@ -1480,10 +1575,10 @@ function googleDrive(opts) {
1480
1575
  headers: { authorization: `Bearer ${accessToken}` },
1481
1576
  signal: AbortSignal.timeout(8e3)
1482
1577
  });
1483
- if (res.status === 401 || res.status === 403) {
1484
- return { ok: false, reason: `Google rejected Drive token (${res.status}) \u2014 reconnect required` };
1578
+ if (!res.ok) {
1579
+ const body = await res.json().catch(() => void 0);
1580
+ return { ok: false, reason: googleTestFailureReason(res.status, body, "Google Drive") };
1485
1581
  }
1486
- if (!res.ok) return { ok: false, reason: `Google Drive returned ${res.status}` };
1487
1582
  return { ok: true };
1488
1583
  } catch (err) {
1489
1584
  return { ok: false, reason: err instanceof Error ? err.message : String(err) };
@@ -1508,12 +1603,8 @@ async function listFiles(inv, accessToken, timeoutMs) {
1508
1603
  headers: { authorization: `Bearer ${accessToken}` },
1509
1604
  signal: AbortSignal.timeout(timeoutMs)
1510
1605
  });
1511
- if (res.status === 401 || res.status === 403) {
1512
- throw new CredentialsExpired(`Google Drive rejected token (${res.status})`, inv.source.id);
1513
- }
1514
1606
  if (!res.ok) {
1515
- const text = await res.text().catch(() => "");
1516
- throw new Error(`google-drive list_files ${res.status}: ${text.slice(0, 200)}`);
1607
+ throw await googleApiError(res, "google-drive list_files", inv.source.id);
1517
1608
  }
1518
1609
  const json = await res.json();
1519
1610
  return {
@@ -1532,15 +1623,11 @@ async function readFile(inv, accessToken, timeoutMs) {
1532
1623
  headers: { authorization: `Bearer ${accessToken}` },
1533
1624
  signal: AbortSignal.timeout(timeoutMs)
1534
1625
  });
1535
- if (metaRes.status === 401 || metaRes.status === 403) {
1536
- throw new CredentialsExpired(`Google Drive rejected token (${metaRes.status})`, inv.source.id);
1537
- }
1538
1626
  if (metaRes.status === 404) {
1539
1627
  throw new Error(`google-drive read_file: file ${fileId} not found`);
1540
1628
  }
1541
1629
  if (!metaRes.ok) {
1542
- const text = await metaRes.text().catch(() => "");
1543
- throw new Error(`google-drive read_file meta ${metaRes.status}: ${text.slice(0, 200)}`);
1630
+ throw await googleApiError(metaRes, "google-drive read_file meta", inv.source.id);
1544
1631
  }
1545
1632
  const meta = await metaRes.json();
1546
1633
  const isNative = meta.mimeType.startsWith("application/vnd.google-apps.");
@@ -1552,8 +1639,7 @@ async function readFile(inv, accessToken, timeoutMs) {
1552
1639
  signal: AbortSignal.timeout(timeoutMs)
1553
1640
  });
1554
1641
  if (!res2.ok) {
1555
- const text = await res2.text().catch(() => "");
1556
- throw new Error(`google-drive read_file export ${res2.status}: ${text.slice(0, 200)}`);
1642
+ throw await googleApiError(res2, "google-drive read_file export", inv.source.id);
1557
1643
  }
1558
1644
  const isTextLike2 = /^text\/|application\/(json|xml|csv|javascript)/.test(targetMime);
1559
1645
  if (isTextLike2) {
@@ -1574,8 +1660,7 @@ async function readFile(inv, accessToken, timeoutMs) {
1574
1660
  signal: AbortSignal.timeout(timeoutMs)
1575
1661
  });
1576
1662
  if (!res.ok) {
1577
- const text = await res.text().catch(() => "");
1578
- throw new Error(`google-drive read_file media ${res.status}: ${text.slice(0, 200)}`);
1663
+ throw await googleApiError(res, "google-drive read_file media", inv.source.id);
1579
1664
  }
1580
1665
  const isTextLike = /^text\/|application\/(json|xml|csv|javascript)/.test(meta.mimeType);
1581
1666
  if (isTextLike) {
@@ -1608,9 +1693,6 @@ async function watchFolder(inv, accessToken, timeoutMs) {
1608
1693
  body: JSON.stringify(body),
1609
1694
  signal: AbortSignal.timeout(timeoutMs)
1610
1695
  });
1611
- if (res.status === 401 || res.status === 403) {
1612
- throw new CredentialsExpired(`Google Drive rejected token (${res.status})`, inv.source.id);
1613
- }
1614
1696
  if (res.status === 409) {
1615
1697
  const cached = inv.source.metadata.watchedChannels?.[channelId];
1616
1698
  return {
@@ -1621,8 +1703,7 @@ async function watchFolder(inv, accessToken, timeoutMs) {
1621
1703
  };
1622
1704
  }
1623
1705
  if (!res.ok) {
1624
- const text = await res.text().catch(() => "");
1625
- throw new Error(`google-drive watch_folder ${res.status}: ${text.slice(0, 200)}`);
1706
+ throw await googleApiError(res, "google-drive watch_folder", inv.source.id);
1626
1707
  }
1627
1708
  const json = await res.json();
1628
1709
  return {
@@ -1667,12 +1748,8 @@ Content-Type: ${mimeType}\r
1667
1748
  body,
1668
1749
  signal: AbortSignal.timeout(timeoutMs)
1669
1750
  });
1670
- if (res.status === 401 || res.status === 403) {
1671
- throw new CredentialsExpired(`Google Drive rejected token (${res.status})`, inv.source.id);
1672
- }
1673
1751
  if (!res.ok) {
1674
- const text = await res.text().catch(() => "");
1675
- throw new Error(`google-drive upload_file ${res.status}: ${text.slice(0, 200)}`);
1752
+ throw await googleApiError(res, "google-drive upload_file", inv.source.id);
1676
1753
  }
1677
1754
  const json = await res.json();
1678
1755
  return {
@@ -1707,12 +1784,8 @@ async function createFolder(inv, accessToken, timeoutMs) {
1707
1784
  body: JSON.stringify(body),
1708
1785
  signal: AbortSignal.timeout(timeoutMs)
1709
1786
  });
1710
- if (res.status === 401 || res.status === 403) {
1711
- throw new CredentialsExpired(`Google Drive rejected token (${res.status})`, inv.source.id);
1712
- }
1713
1787
  if (!res.ok) {
1714
- const text = await res.text().catch(() => "");
1715
- throw new Error(`google-drive create_folder ${res.status}: ${text.slice(0, 200)}`);
1788
+ throw await googleApiError(res, "google-drive create_folder", inv.source.id);
1716
1789
  }
1717
1790
  const json = await res.json();
1718
1791
  return {
@@ -1736,12 +1809,8 @@ async function deleteFile(inv, accessToken, timeoutMs) {
1736
1809
  headers: { authorization: `Bearer ${accessToken}` },
1737
1810
  signal: AbortSignal.timeout(timeoutMs)
1738
1811
  });
1739
- if (res.status === 401 || res.status === 403) {
1740
- throw new CredentialsExpired(`Google Drive rejected token (${res.status})`, inv.source.id);
1741
- }
1742
1812
  if (!res.ok && res.status !== 204) {
1743
- const text = await res.text().catch(() => "");
1744
- throw new Error(`google-drive delete_file ${res.status}: ${text.slice(0, 200)}`);
1813
+ throw await googleApiError(res, "google-drive delete_file", inv.source.id);
1745
1814
  }
1746
1815
  return {
1747
1816
  status: "committed",
@@ -1771,12 +1840,8 @@ async function moveFile(inv, accessToken, timeoutMs) {
1771
1840
  body: "{}",
1772
1841
  signal: AbortSignal.timeout(timeoutMs)
1773
1842
  });
1774
- if (res.status === 401 || res.status === 403) {
1775
- throw new CredentialsExpired(`Google Drive rejected token (${res.status})`, inv.source.id);
1776
- }
1777
1843
  if (!res.ok) {
1778
- const text = await res.text().catch(() => "");
1779
- throw new Error(`google-drive move_file ${res.status}: ${text.slice(0, 200)}`);
1844
+ throw await googleApiError(res, "google-drive move_file", inv.source.id);
1780
1845
  }
1781
1846
  const json = await res.json();
1782
1847
  return {
@@ -2030,10 +2095,10 @@ function googleSheets(opts) {
2030
2095
  headers: { authorization: `Bearer ${accessToken}` },
2031
2096
  signal: AbortSignal.timeout(8e3)
2032
2097
  });
2033
- if (res.status === 401 || res.status === 403) {
2034
- return { ok: false, reason: `Google rejected token (${res.status}) \u2014 reconnect required` };
2098
+ if (!res.ok) {
2099
+ const body = await res.json().catch(() => void 0);
2100
+ return { ok: false, reason: googleTestFailureReason(res.status, body, "Google Sheets") };
2035
2101
  }
2036
- if (!res.ok) return { ok: false, reason: `Google returned ${res.status}` };
2037
2102
  return { ok: true };
2038
2103
  } catch (err) {
2039
2104
  return { ok: false, reason: err instanceof Error ? err.message : String(err) };
@@ -2060,12 +2125,8 @@ async function fetchAllRows(accessToken, meta) {
2060
2125
  headers: { authorization: `Bearer ${accessToken}` },
2061
2126
  signal: AbortSignal.timeout(15e3)
2062
2127
  });
2063
- if (res.status === 401 || res.status === 403) {
2064
- throw new CredentialsExpired(`Google Sheets rejected token (${res.status})`, "");
2065
- }
2066
2128
  if (!res.ok) {
2067
- const text = await res.text().catch(() => "");
2068
- throw new Error(`google-sheets values.get ${res.status}: ${text.slice(0, 200)}`);
2129
+ throw await googleApiError(res, "google-sheets values.get", "");
2069
2130
  }
2070
2131
  const json = await res.json();
2071
2132
  const grid = json.values ?? [];
@@ -2178,12 +2239,8 @@ async function updateRow(inv, accessToken, timeoutMs) {
2178
2239
  body: JSON.stringify({ values: [updatedValues] }),
2179
2240
  signal: AbortSignal.timeout(timeoutMs)
2180
2241
  });
2181
- if (res.status === 401 || res.status === 403) {
2182
- throw new CredentialsExpired(`Google Sheets rejected token (${res.status})`, inv.source.id);
2183
- }
2184
2242
  if (!res.ok) {
2185
- const text = await res.text().catch(() => "");
2186
- throw new Error(`google-sheets update_row ${res.status}: ${text.slice(0, 200)}`);
2243
+ throw await googleApiError(res, "google-sheets update_row", inv.source.id);
2187
2244
  }
2188
2245
  const updatedValuesByHeader = Object.fromEntries(
2189
2246
  meta.headers.map((h, i) => [h, updatedValues[i] ?? ""])
@@ -2218,12 +2275,8 @@ async function appendRow(inv, accessToken, timeoutMs) {
2218
2275
  body: JSON.stringify({ values: args.values }),
2219
2276
  signal: AbortSignal.timeout(timeoutMs)
2220
2277
  });
2221
- if (res.status === 401 || res.status === 403) {
2222
- throw new CredentialsExpired(`Google Sheets rejected token (${res.status})`, inv.source.id);
2223
- }
2224
2278
  if (!res.ok) {
2225
- const text = await res.text().catch(() => "");
2226
- throw new Error(`google-sheets append_row ${res.status}: ${text.slice(0, 200)}`);
2279
+ throw await googleApiError(res, "google-sheets append_row", inv.source.id);
2227
2280
  }
2228
2281
  const json = await res.json();
2229
2282
  return {
@@ -2254,12 +2307,8 @@ async function clearRange(inv, accessToken, timeoutMs) {
2254
2307
  body: "{}",
2255
2308
  signal: AbortSignal.timeout(timeoutMs)
2256
2309
  });
2257
- if (res.status === 401 || res.status === 403) {
2258
- throw new CredentialsExpired(`Google Sheets rejected token (${res.status})`, inv.source.id);
2259
- }
2260
2310
  if (!res.ok) {
2261
- const text = await res.text().catch(() => "");
2262
- throw new Error(`google-sheets clear_range ${res.status}: ${text.slice(0, 200)}`);
2311
+ throw await googleApiError(res, "google-sheets clear_range", inv.source.id);
2263
2312
  }
2264
2313
  const json = await res.json().catch(() => ({}));
2265
2314
  return {
@@ -2285,12 +2334,8 @@ async function createSheet(inv, accessToken, timeoutMs) {
2285
2334
  body: JSON.stringify({ properties: { title: args.title } }),
2286
2335
  signal: AbortSignal.timeout(timeoutMs)
2287
2336
  });
2288
- if (res.status === 401 || res.status === 403) {
2289
- throw new CredentialsExpired(`Google Sheets rejected token (${res.status})`, inv.source.id);
2290
- }
2291
2337
  if (!res.ok) {
2292
- const text = await res.text().catch(() => "");
2293
- throw new Error(`google-sheets create_sheet ${res.status}: ${text.slice(0, 200)}`);
2338
+ throw await googleApiError(res, "google-sheets create_sheet", inv.source.id);
2294
2339
  }
2295
2340
  const json = await res.json();
2296
2341
  return {
@@ -2305,6 +2350,119 @@ async function createSheet(inv, accessToken, timeoutMs) {
2305
2350
  };
2306
2351
  }
2307
2352
 
2353
+ // src/connectors/adapters/aws-sigv4.ts
2354
+ import { createHash as createHash3, createHmac } from "crypto";
2355
+ var ALGORITHM = "AWS4-HMAC-SHA256";
2356
+ function parseAwsCredentialBundle(credentials) {
2357
+ if (credentials.kind !== "api-key") {
2358
+ throw new Error(`AWS SigV4 connectors require api-key credentials, got ${credentials.kind}`);
2359
+ }
2360
+ let raw;
2361
+ try {
2362
+ raw = JSON.parse(credentials.apiKey);
2363
+ } catch {
2364
+ throw new Error(
2365
+ "AWS credential must be a JSON object with accessKeyId, secretAccessKey, and region \u2014 received a non-JSON string"
2366
+ );
2367
+ }
2368
+ if (!raw || typeof raw !== "object") {
2369
+ throw new Error("AWS credential bundle must be a JSON object with accessKeyId, secretAccessKey, and region");
2370
+ }
2371
+ const o = raw;
2372
+ const accessKeyId = pickString(o, ["accessKeyId", "access_key_id", "awsAccessKeyId", "AccessKeyId"]);
2373
+ const secretAccessKey = pickString(o, [
2374
+ "secretAccessKey",
2375
+ "secret_access_key",
2376
+ "awsSecretAccessKey",
2377
+ "SecretAccessKey",
2378
+ "secretKey"
2379
+ ]);
2380
+ const region = pickString(o, ["region", "awsRegion", "Region"]) ?? "";
2381
+ const sessionToken = pickString(o, ["sessionToken", "session_token", "awsSessionToken", "SessionToken"]);
2382
+ const endpoint = pickString(o, ["endpoint", "Endpoint"]);
2383
+ if (!accessKeyId || !secretAccessKey) {
2384
+ throw new Error("AWS credential bundle is missing accessKeyId and/or secretAccessKey");
2385
+ }
2386
+ return { accessKeyId, secretAccessKey, region, sessionToken, endpoint };
2387
+ }
2388
+ function signSigV4(input) {
2389
+ const { method, url, body, service, region, accessKeyId, secretAccessKey, sessionToken, amzDate } = input;
2390
+ const dateStamp = amzDate.slice(0, 8);
2391
+ const payloadHash = hashSha256Hex(body);
2392
+ const headerMap = /* @__PURE__ */ new Map();
2393
+ for (const [key, value] of Object.entries(input.headers)) {
2394
+ const name = key.toLowerCase().trim();
2395
+ if (name === "authorization") continue;
2396
+ headerMap.set(name, collapseWhitespace(value));
2397
+ }
2398
+ headerMap.set("host", url.host);
2399
+ headerMap.set("x-amz-date", amzDate);
2400
+ if (sessionToken) headerMap.set("x-amz-security-token", sessionToken);
2401
+ const sortedNames = [...headerMap.keys()].sort();
2402
+ const canonicalHeaders = sortedNames.map((name) => `${name}:${headerMap.get(name)}
2403
+ `).join("");
2404
+ const signedHeaders = sortedNames.join(";");
2405
+ const canonicalRequest = [
2406
+ method.toUpperCase(),
2407
+ canonicalUri(url.pathname, service),
2408
+ canonicalizeAwsQuery(url.searchParams),
2409
+ canonicalHeaders,
2410
+ signedHeaders,
2411
+ payloadHash
2412
+ ].join("\n");
2413
+ const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
2414
+ const stringToSign = [ALGORITHM, amzDate, credentialScope, hashSha256Hex(canonicalRequest)].join("\n");
2415
+ const signingKey = deriveSigningKey(secretAccessKey, dateStamp, region, service);
2416
+ const signature = createHmac("sha256", signingKey).update(stringToSign, "utf8").digest("hex");
2417
+ const authorization = `${ALGORITHM} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
2418
+ return { amzDate, signedHeaders, canonicalRequest, stringToSign, signature, authorization };
2419
+ }
2420
+ function amzDateNow(date = /* @__PURE__ */ new Date()) {
2421
+ return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
2422
+ }
2423
+ function hashSha256Hex(data) {
2424
+ return createHash3("sha256").update(data, "utf8").digest("hex");
2425
+ }
2426
+ function deriveSigningKey(secretAccessKey, dateStamp, region, service) {
2427
+ const kDate = createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp, "utf8").digest();
2428
+ const kRegion = createHmac("sha256", kDate).update(region, "utf8").digest();
2429
+ const kService = createHmac("sha256", kRegion).update(service, "utf8").digest();
2430
+ return createHmac("sha256", kService).update("aws4_request", "utf8").digest();
2431
+ }
2432
+ function canonicalUri(pathname, service) {
2433
+ if (!pathname) return "/";
2434
+ if (service === "s3") return pathname;
2435
+ const segments = [];
2436
+ for (const segment of pathname.split("/")) {
2437
+ if (segment.length === 0 || segment === ".") continue;
2438
+ if (segment === "..") segments.pop();
2439
+ else segments.push(segment);
2440
+ }
2441
+ const normalized = `/${segments.join("/")}${segments.length > 0 && pathname.endsWith("/") ? "/" : ""}`;
2442
+ return awsUriEncode(normalized).replace(/%2F/g, "/");
2443
+ }
2444
+ function canonicalizeAwsQuery(params) {
2445
+ const pairs = [];
2446
+ for (const [key, value] of params) {
2447
+ pairs.push([awsUriEncode(key), awsUriEncode(value)]);
2448
+ }
2449
+ pairs.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0);
2450
+ return pairs.map(([key, value]) => `${key}=${value}`).join("&");
2451
+ }
2452
+ function awsUriEncode(value) {
2453
+ return encodeURIComponent(value).replace(/[!'()*]/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`);
2454
+ }
2455
+ function collapseWhitespace(value) {
2456
+ return value.trim().replace(/\s+/g, " ");
2457
+ }
2458
+ function pickString(obj, keys) {
2459
+ for (const key of keys) {
2460
+ const value = obj[key];
2461
+ if (typeof value === "string" && value.length > 0) return value;
2462
+ }
2463
+ return void 0;
2464
+ }
2465
+
2308
2466
  // src/connectors/adapters/declarative-rest.ts
2309
2467
  function declarativeRestConnector(spec) {
2310
2468
  const capabilities = spec.capabilities.map(operationToCapability);
@@ -2404,7 +2562,11 @@ function requiredArgsOf(parameters) {
2404
2562
  return Array.isArray(required) ? required.filter((entry) => typeof entry === "string") : void 0;
2405
2563
  }
2406
2564
  async function executeRestRequest(spec, request, inv, requiredArgs) {
2407
- const baseUrl = resolveBaseUrl(spec.baseUrl, inv.source.metadata);
2565
+ const placement = spec.credentialPlacement ?? { kind: "bearer" };
2566
+ const aws = placement.kind === "aws-sigv4" ? parseAwsCredentialBundle(inv.source.credentials) : void 0;
2567
+ const awsRegion = aws ? resolveAwsRegion(aws, inv.source.metadata, placement) : void 0;
2568
+ let baseUrl = resolveBaseUrl(spec.baseUrl, inv.source.metadata);
2569
+ if (aws) baseUrl = applyRegionTemplate(baseUrl, awsRegion, aws.endpoint);
2408
2570
  const renderedPath = interpolate(request.path, inv.args).replace(/^\/+/, "");
2409
2571
  const baseWithSlash = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
2410
2572
  const url = new URL(renderedPath, baseWithSlash);
@@ -2412,21 +2574,36 @@ async function executeRestRequest(spec, request, inv, requiredArgs) {
2412
2574
  const rendered = renderQueryValue(value, inv.args);
2413
2575
  if (rendered !== void 0 && rendered !== "") url.searchParams.set(key, String(rendered));
2414
2576
  }
2577
+ const requestHeaders = request.headers ?? {};
2578
+ const hostOverride = aws ? Object.entries(requestHeaders).find(([key]) => key.toLowerCase() === "host")?.[1] : void 0;
2579
+ const renderableHeaders = hostOverride ? Object.fromEntries(Object.entries(requestHeaders).filter(([key]) => key.toLowerCase() !== "host")) : requestHeaders;
2415
2580
  const headers = {
2416
2581
  accept: "application/json",
2417
2582
  ...spec.defaultHeaders,
2418
- ...renderHeaders(request.headers ?? {}, inv.args)
2583
+ ...renderHeaders(renderableHeaders, inv.args)
2419
2584
  };
2420
- applyCredentials(headers, url, spec.credentialPlacement ?? { kind: "bearer" }, inv.source.credentials);
2585
+ if (hostOverride) url.host = hostOverride.replace(/\{region\}/g, awsRegion);
2421
2586
  if (inv.expectedEtag) headers["if-match"] = inv.expectedEtag;
2422
2587
  const sendsBody = request.method !== "GET" && (request.method !== "DELETE" || request.body !== void 0);
2423
- if (sendsBody) {
2424
- headers["content-type"] = headers["content-type"] ?? "application/json";
2588
+ if (sendsBody && getHeaderCI(headers, "content-type") === void 0) {
2589
+ headers["content-type"] = "application/json";
2590
+ }
2591
+ const bodyString = sendsBody ? JSON.stringify(resolveBody(request.body, inv.args, requiredArgs)) : void 0;
2592
+ if (placement.kind === "aws-sigv4") {
2593
+ signAwsRequest(headers, url, {
2594
+ method: request.method,
2595
+ body: bodyString ?? "",
2596
+ service: placement.service,
2597
+ region: awsRegion,
2598
+ bundle: aws
2599
+ });
2600
+ } else {
2601
+ applyCredentials(headers, url, placement, inv.source.credentials);
2425
2602
  }
2426
2603
  const res = await fetch(url, {
2427
2604
  method: request.method,
2428
2605
  headers,
2429
- body: sendsBody ? JSON.stringify(resolveBody(request.body, inv.args, requiredArgs)) : void 0,
2606
+ body: bodyString,
2430
2607
  signal: AbortSignal.timeout(2e4)
2431
2608
  });
2432
2609
  if (res.status === 401 || res.status === 403) {
@@ -2450,6 +2627,9 @@ async function executeRestRequest(spec, request, inv, requiredArgs) {
2450
2627
  }
2451
2628
  };
2452
2629
  }
2630
+ if (request.existenceCheck && (res.status === 204 || res.status === 404)) {
2631
+ return { data: { exists: res.status === 204 } };
2632
+ }
2453
2633
  if (!res.ok) {
2454
2634
  throw new Error(`${spec.kind} ${request.method} ${url.pathname} HTTP ${res.status}: ${(await safeErrorText(res)).slice(0, 300)}`);
2455
2635
  }
@@ -2482,6 +2662,42 @@ function credentialToken(credentials) {
2482
2662
  if (credentials.kind === "api-key") return credentials.apiKey;
2483
2663
  throw new Error(`declarative REST connectors require oauth2 or api-key credentials, got ${credentials.kind}`);
2484
2664
  }
2665
+ function resolveAwsRegion(bundle, metadata, placement) {
2666
+ if (bundle.region) return bundle.region;
2667
+ if (typeof metadata.region === "string" && metadata.region.trim()) return metadata.region;
2668
+ return placement.defaultRegion ?? "us-east-1";
2669
+ }
2670
+ function applyRegionTemplate(baseUrl, region, endpoint) {
2671
+ if (endpoint) return endpoint;
2672
+ return baseUrl.replace(/\{region\}/g, region);
2673
+ }
2674
+ function getHeaderCI(headers, name) {
2675
+ const lower = name.toLowerCase();
2676
+ for (const [key, value] of Object.entries(headers)) {
2677
+ if (key.toLowerCase() === lower) return value;
2678
+ }
2679
+ return void 0;
2680
+ }
2681
+ function signAwsRequest(headers, url, opts) {
2682
+ const canonicalSearch = canonicalizeAwsQuery(url.searchParams);
2683
+ url.search = canonicalSearch ? `?${canonicalSearch}` : "";
2684
+ headers["x-amz-content-sha256"] = hashSha256Hex(opts.body);
2685
+ if (opts.bundle.sessionToken) headers["x-amz-security-token"] = opts.bundle.sessionToken;
2686
+ const signed = signSigV4({
2687
+ method: opts.method,
2688
+ url,
2689
+ headers,
2690
+ body: opts.body,
2691
+ service: opts.service,
2692
+ region: opts.region,
2693
+ accessKeyId: opts.bundle.accessKeyId,
2694
+ secretAccessKey: opts.bundle.secretAccessKey,
2695
+ sessionToken: opts.bundle.sessionToken,
2696
+ amzDate: amzDateNow()
2697
+ });
2698
+ headers["x-amz-date"] = signed.amzDate;
2699
+ headers.authorization = signed.authorization;
2700
+ }
2485
2701
  function resolveBody(body, args, requiredArgs) {
2486
2702
  if (!body || body === "args") return args;
2487
2703
  if (typeof body === "string") return renderValue(body, args, requiredArgs);
@@ -2882,10 +3098,10 @@ function gmail(opts) {
2882
3098
  headers: { authorization: `Bearer ${accessToken}` },
2883
3099
  signal: AbortSignal.timeout(8e3)
2884
3100
  });
2885
- if (res.status === 401 || res.status === 403) {
2886
- return { ok: false, reason: `Google rejected Gmail token (${res.status}) \u2014 reconnect required` };
3101
+ if (!res.ok) {
3102
+ const body = await res.json().catch(() => void 0);
3103
+ return { ok: false, reason: googleTestFailureReason(res.status, body, "Gmail") };
2887
3104
  }
2888
- if (!res.ok) return { ok: false, reason: `Gmail returned ${res.status}` };
2889
3105
  return { ok: true };
2890
3106
  } catch (err) {
2891
3107
  return { ok: false, reason: err instanceof Error ? err.message : String(err) };
@@ -2906,12 +3122,8 @@ async function listMessages(inv, accessToken, timeoutMs) {
2906
3122
  headers: { authorization: `Bearer ${accessToken}` },
2907
3123
  signal: AbortSignal.timeout(timeoutMs)
2908
3124
  });
2909
- if (listRes.status === 401 || listRes.status === 403) {
2910
- throw new CredentialsExpired(`Gmail rejected token (${listRes.status})`, inv.source.id);
2911
- }
2912
3125
  if (!listRes.ok) {
2913
- const text = await listRes.text().catch(() => "");
2914
- throw new Error(`gmail list_messages ${listRes.status}: ${text.slice(0, 200)}`);
3126
+ throw await googleApiError(listRes, "gmail list_messages", inv.source.id);
2915
3127
  }
2916
3128
  const listJson = await listRes.json();
2917
3129
  const ids = listJson.messages ?? [];
@@ -2951,15 +3163,11 @@ async function readMessage(inv, accessToken, timeoutMs) {
2951
3163
  headers: { authorization: `Bearer ${accessToken}` },
2952
3164
  signal: AbortSignal.timeout(timeoutMs)
2953
3165
  });
2954
- if (res.status === 401 || res.status === 403) {
2955
- throw new CredentialsExpired(`Gmail rejected token (${res.status})`, inv.source.id);
2956
- }
2957
3166
  if (res.status === 404) {
2958
3167
  throw new Error(`gmail read_message: message ${id} not found`);
2959
3168
  }
2960
3169
  if (!res.ok) {
2961
- const text = await res.text().catch(() => "");
2962
- throw new Error(`gmail read_message ${res.status}: ${text.slice(0, 200)}`);
3170
+ throw await googleApiError(res, "gmail read_message", inv.source.id);
2963
3171
  }
2964
3172
  const full = await res.json();
2965
3173
  const headers = new Map((full.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
@@ -3035,12 +3243,8 @@ ${args.body}`;
3035
3243
  body: JSON.stringify({ raw: encodeBase64Url(raw) }),
3036
3244
  signal: AbortSignal.timeout(timeoutMs)
3037
3245
  });
3038
- if (sendRes.status === 401 || sendRes.status === 403) {
3039
- throw new CredentialsExpired(`Gmail rejected token (${sendRes.status})`, inv.source.id);
3040
- }
3041
3246
  if (!sendRes.ok) {
3042
- const text = await sendRes.text().catch(() => "");
3043
- throw new Error(`gmail send ${sendRes.status}: ${text.slice(0, 200)}`);
3247
+ throw await googleApiError(sendRes, "gmail send", inv.source.id);
3044
3248
  }
3045
3249
  const sent = await sendRes.json();
3046
3250
  return {
@@ -3056,12 +3260,8 @@ async function sendReply(inv, accessToken, timeoutMs) {
3056
3260
  headers: { authorization: `Bearer ${accessToken}` },
3057
3261
  signal: AbortSignal.timeout(timeoutMs)
3058
3262
  });
3059
- if (threadRes.status === 401 || threadRes.status === 403) {
3060
- throw new CredentialsExpired(`Gmail rejected token (${threadRes.status})`, inv.source.id);
3061
- }
3062
3263
  if (!threadRes.ok) {
3063
- const text = await threadRes.text().catch(() => "");
3064
- throw new Error(`gmail send_reply thread fetch ${threadRes.status}: ${text.slice(0, 200)}`);
3264
+ throw await googleApiError(threadRes, "gmail send_reply thread fetch", inv.source.id);
3065
3265
  }
3066
3266
  const thread = await threadRes.json();
3067
3267
  const last = thread.messages?.[thread.messages.length - 1];
@@ -3104,12 +3304,8 @@ ${body}`;
3104
3304
  body: JSON.stringify(sendBody),
3105
3305
  signal: AbortSignal.timeout(timeoutMs)
3106
3306
  });
3107
- if (sendRes.status === 401 || sendRes.status === 403) {
3108
- throw new CredentialsExpired(`Gmail rejected token (${sendRes.status})`, inv.source.id);
3109
- }
3110
3307
  if (!sendRes.ok) {
3111
- const text = await sendRes.text().catch(() => "");
3112
- throw new Error(`gmail send_reply ${sendRes.status}: ${text.slice(0, 200)}`);
3308
+ throw await googleApiError(sendRes, "gmail send_reply", inv.source.id);
3113
3309
  }
3114
3310
  const sent = await sendRes.json();
3115
3311
  return {
@@ -3135,12 +3331,8 @@ async function watchLabel(inv, accessToken, timeoutMs) {
3135
3331
  body: JSON.stringify(body),
3136
3332
  signal: AbortSignal.timeout(timeoutMs)
3137
3333
  });
3138
- if (res.status === 401 || res.status === 403) {
3139
- throw new CredentialsExpired(`Gmail rejected token (${res.status})`, inv.source.id);
3140
- }
3141
3334
  if (!res.ok) {
3142
- const text = await res.text().catch(() => "");
3143
- throw new Error(`gmail watch_label ${res.status}: ${text.slice(0, 200)}`);
3335
+ throw await googleApiError(res, "gmail watch_label", inv.source.id);
3144
3336
  }
3145
3337
  const json = await res.json();
3146
3338
  return {
@@ -4704,7 +4896,7 @@ async function uploadFile2(inv, accessToken, timeoutMs) {
4704
4896
  }
4705
4897
 
4706
4898
  // src/connectors/adapters/docuseal.ts
4707
- import { createHmac, timingSafeEqual } from "crypto";
4899
+ import { createHmac as createHmac2, timingSafeEqual } from "crypto";
4708
4900
  var DEFAULT_BASE = "https://api.docuseal.com";
4709
4901
  function readDocuSealCredentials(creds) {
4710
4902
  if (creds.kind === "api-key" && typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
@@ -4829,7 +5021,7 @@ function docuseal(opts = {}) {
4829
5021
  if (!creds?.webhookSecret) return { valid: false, reason: "missing_webhook_secret" };
4830
5022
  const sig = firstHeader(headers, "x-docuseal-signature");
4831
5023
  if (!sig) return { valid: false, reason: "missing_signature_header" };
4832
- const expected = createHmac("sha256", creds.webhookSecret).update(rawBody).digest("hex");
5024
+ const expected = createHmac2("sha256", creds.webhookSecret).update(rawBody).digest("hex");
4833
5025
  const a = Buffer.from(sig.toLowerCase(), "utf-8");
4834
5026
  const b = Buffer.from(expected, "utf-8");
4835
5027
  if (a.length !== b.length) return { valid: false, reason: "invalid_signature" };
@@ -7021,7 +7213,7 @@ function readApiKey(creds) {
7021
7213
  }
7022
7214
 
7023
7215
  // src/connectors/adapters/webhook.ts
7024
- import { createHmac as createHmac2 } from "crypto";
7216
+ import { createHmac as createHmac3 } from "crypto";
7025
7217
  var webhookConnector = {
7026
7218
  manifest: {
7027
7219
  kind: "webhook",
@@ -7135,7 +7327,7 @@ function signHeaders(creds, body, idempotencyKey) {
7135
7327
  "x-phony-idempotency-key": idempotencyKey
7136
7328
  };
7137
7329
  if (creds.kind === "hmac" && typeof creds.secret === "string" && creds.secret.length > 0) {
7138
- const sig = createHmac2("sha256", creds.secret).update(`${ts}.${body}`).digest("hex");
7330
+ const sig = createHmac3("sha256", creds.secret).update(`${ts}.${body}`).digest("hex");
7139
7331
  headers["x-phony-signature"] = `sha256=${sig}`;
7140
7332
  }
7141
7333
  return headers;
@@ -7297,6 +7489,83 @@ var githubConnector = declarativeRestConnector({
7297
7489
  },
7298
7490
  request: { method: "GET", path: "/search/issues", query: { q: "{q}", per_page: "{per_page}" } }
7299
7491
  },
7492
+ {
7493
+ name: "users.getAuthenticated",
7494
+ class: "read",
7495
+ description: "Resolve the authenticated user (the token owner). Quest verification anchors author:/owner: filters to this login, so it must be resolved first.",
7496
+ parameters: { type: "object", properties: {}, required: [] },
7497
+ request: { method: "GET", path: "/user" }
7498
+ },
7499
+ {
7500
+ name: "activity.checkStarred",
7501
+ class: "read",
7502
+ description: "Check whether the authenticated user has starred a repository. Returns { exists: true } when starred, { exists: false } when not.",
7503
+ parameters: repoParams,
7504
+ request: { method: "GET", path: "/user/starred/{owner}/{repo}", existenceCheck: true }
7505
+ },
7506
+ {
7507
+ name: "users.checkFollowing",
7508
+ class: "read",
7509
+ description: "Check whether the authenticated user follows another user. Returns { exists: true } when following, { exists: false } when not.",
7510
+ parameters: {
7511
+ type: "object",
7512
+ properties: { target: { type: "string", description: "Login of the user to check the follow against." } },
7513
+ required: ["target"]
7514
+ },
7515
+ request: { method: "GET", path: "/user/following/{target}", existenceCheck: true }
7516
+ },
7517
+ {
7518
+ name: "repos.listCommits",
7519
+ class: "read",
7520
+ description: "List commits on a repository, optionally filtered to a single author login/email. Used to verify a user contributed to the repo.",
7521
+ parameters: {
7522
+ type: "object",
7523
+ properties: {
7524
+ owner: { type: "string" },
7525
+ repo: { type: "string" },
7526
+ author: { type: "string", description: "Restrict to commits authored by this GitHub login or email." },
7527
+ per_page: { type: "integer", minimum: 1, maximum: 100 }
7528
+ },
7529
+ required: ["owner", "repo"]
7530
+ },
7531
+ request: {
7532
+ method: "GET",
7533
+ path: "/repos/{owner}/{repo}/commits",
7534
+ query: { author: "{author}", per_page: "{per_page}" }
7535
+ }
7536
+ },
7537
+ {
7538
+ name: "repos.getReadme",
7539
+ class: "read",
7540
+ description: "Read a repository's README (returns base64-encoded content). Used to verify README mentions.",
7541
+ parameters: repoParams,
7542
+ request: { method: "GET", path: "/repos/{owner}/{repo}/readme" }
7543
+ },
7544
+ {
7545
+ name: "search.code",
7546
+ class: "read",
7547
+ description: "Search code across GitHub. Used to verify code usage of a symbol or string.",
7548
+ parameters: {
7549
+ type: "object",
7550
+ properties: { q: { type: "string" }, per_page: { type: "integer", minimum: 1, maximum: 100 } },
7551
+ required: ["q"]
7552
+ },
7553
+ request: { method: "GET", path: "/search/code", query: { q: "{q}", per_page: "{per_page}" } }
7554
+ },
7555
+ {
7556
+ name: "orgs.checkMembership",
7557
+ class: "read",
7558
+ description: "Check whether a user is a member of an organization. Returns { exists: true } when a member, { exists: false } when not.",
7559
+ parameters: {
7560
+ type: "object",
7561
+ properties: {
7562
+ org: { type: "string" },
7563
+ user: { type: "string", description: "Login of the user to check membership for." }
7564
+ },
7565
+ required: ["org", "user"]
7566
+ },
7567
+ request: { method: "GET", path: "/orgs/{org}/members/{user}", existenceCheck: true }
7568
+ },
7300
7569
  {
7301
7570
  name: "issues.create",
7302
7571
  class: "mutation",
@@ -37162,7 +37431,7 @@ var smooveConnector = declarativeRestConnector({
37162
37431
  });
37163
37432
 
37164
37433
  // src/connectors/adapters/hellosign.ts
37165
- import { createHmac as createHmac3, timingSafeEqual as timingSafeEqual2 } from "crypto";
37434
+ import { createHmac as createHmac4, timingSafeEqual as timingSafeEqual2 } from "crypto";
37166
37435
  var SCOPES7 = [
37167
37436
  "basic_account_info",
37168
37437
  "request_signature",
@@ -37385,7 +37654,7 @@ function hellosign(opts) {
37385
37654
  if (!event || typeof event.event_time !== "string" || typeof event.event_type !== "string" || typeof event.event_hash !== "string") {
37386
37655
  return { valid: false, reason: "missing_event_fields" };
37387
37656
  }
37388
- const expected = createHmac3("sha256", source.credentials.accessToken).update(`${event.event_time}${event.event_type}`).digest("hex");
37657
+ const expected = createHmac4("sha256", source.credentials.accessToken).update(`${event.event_time}${event.event_type}`).digest("hex");
37389
37658
  const a = Buffer.from(event.event_hash.toLowerCase(), "utf-8");
37390
37659
  const b = Buffer.from(expected.toLowerCase(), "utf-8");
37391
37660
  if (a.length !== b.length) return { valid: false, reason: "invalid_signature" };
@@ -38378,10 +38647,10 @@ function googleDocs(opts) {
38378
38647
  headers: { authorization: `Bearer ${accessToken}` },
38379
38648
  signal: AbortSignal.timeout(8e3)
38380
38649
  });
38381
- if (res.status === 401 || res.status === 403) {
38382
- return { ok: false, reason: `Google rejected Docs token (${res.status}) \u2014 reconnect required` };
38650
+ if (!res.ok) {
38651
+ const body = await res.json().catch(() => void 0);
38652
+ return { ok: false, reason: googleTestFailureReason(res.status, body, "Google Docs") };
38383
38653
  }
38384
- if (!res.ok) return { ok: false, reason: `Google userinfo returned ${res.status}` };
38385
38654
  return { ok: true };
38386
38655
  } catch (err) {
38387
38656
  return { ok: false, reason: err instanceof Error ? err.message : String(err) };
@@ -38417,15 +38686,11 @@ async function getDocument2(inv, accessToken, timeoutMs) {
38417
38686
  headers: { authorization: `Bearer ${accessToken}` },
38418
38687
  signal: AbortSignal.timeout(timeoutMs)
38419
38688
  });
38420
- if (res.status === 401 || res.status === 403) {
38421
- throw new CredentialsExpired(`Google Docs rejected token (${res.status})`, inv.source.id);
38422
- }
38423
38689
  if (res.status === 404) {
38424
38690
  throw new Error(`google-docs get_document: document ${documentId} not found`);
38425
38691
  }
38426
38692
  if (!res.ok) {
38427
- const text = await res.text().catch(() => "");
38428
- throw new Error(`google-docs get_document ${res.status}: ${text.slice(0, 200)}`);
38693
+ throw await googleApiError(res, "google-docs get_document", inv.source.id);
38429
38694
  }
38430
38695
  const doc = await res.json();
38431
38696
  return {
@@ -38456,12 +38721,8 @@ async function createDocument2(inv, accessToken, timeoutMs) {
38456
38721
  body: JSON.stringify({ title }),
38457
38722
  signal: AbortSignal.timeout(timeoutMs)
38458
38723
  });
38459
- if (createRes.status === 401 || createRes.status === 403) {
38460
- throw new CredentialsExpired(`Google Docs rejected token (${createRes.status})`, inv.source.id);
38461
- }
38462
38724
  if (!createRes.ok) {
38463
- const text = await createRes.text().catch(() => "");
38464
- throw new Error(`google-docs create_document ${createRes.status}: ${text.slice(0, 200)}`);
38725
+ throw await googleApiError(createRes, "google-docs create_document", inv.source.id);
38465
38726
  }
38466
38727
  const created = await createRes.json();
38467
38728
  let revisionId = created.revisionId;
@@ -38487,12 +38748,8 @@ async function createDocument2(inv, accessToken, timeoutMs) {
38487
38748
  signal: AbortSignal.timeout(timeoutMs)
38488
38749
  }
38489
38750
  );
38490
- if (insertRes.status === 401 || insertRes.status === 403) {
38491
- throw new CredentialsExpired(`Google Docs rejected token (${insertRes.status})`, inv.source.id);
38492
- }
38493
38751
  if (!insertRes.ok) {
38494
- const text = await insertRes.text().catch(() => "");
38495
- throw new Error(`google-docs create_document insertText ${insertRes.status}: ${text.slice(0, 200)}`);
38752
+ throw await googleApiError(insertRes, "google-docs create_document insertText", inv.source.id);
38496
38753
  }
38497
38754
  const insertJson = await insertRes.json();
38498
38755
  revisionId = insertJson.writeControl?.requiredRevisionId ?? revisionId;
@@ -38514,15 +38771,11 @@ async function appendText(inv, accessToken, timeoutMs) {
38514
38771
  headers: { authorization: `Bearer ${accessToken}` },
38515
38772
  signal: AbortSignal.timeout(timeoutMs)
38516
38773
  });
38517
- if (docRes.status === 401 || docRes.status === 403) {
38518
- throw new CredentialsExpired(`Google Docs rejected token (${docRes.status})`, inv.source.id);
38519
- }
38520
38774
  if (docRes.status === 404) {
38521
38775
  throw new Error(`google-docs append_text: document ${documentId} not found`);
38522
38776
  }
38523
38777
  if (!docRes.ok) {
38524
- const errText = await docRes.text().catch(() => "");
38525
- throw new Error(`google-docs append_text fetch ${docRes.status}: ${errText.slice(0, 200)}`);
38778
+ throw await googleApiError(docRes, "google-docs append_text fetch", inv.source.id);
38526
38779
  }
38527
38780
  const doc = await docRes.json();
38528
38781
  const insertIndex = findAppendIndex(doc);
@@ -38551,9 +38804,6 @@ async function appendText(inv, accessToken, timeoutMs) {
38551
38804
  signal: AbortSignal.timeout(timeoutMs)
38552
38805
  }
38553
38806
  );
38554
- if (res.status === 401 || res.status === 403) {
38555
- throw new CredentialsExpired(`Google Docs rejected token (${res.status})`, inv.source.id);
38556
- }
38557
38807
  if (res.status === 400 && requiredRevisionId) {
38558
38808
  const errBody = await res.text().catch(() => "");
38559
38809
  if (/FAILED_PRECONDITION|requiredRevisionId/i.test(errBody)) {
@@ -38566,8 +38816,7 @@ async function appendText(inv, accessToken, timeoutMs) {
38566
38816
  throw new Error(`google-docs append_text ${res.status}: ${errBody.slice(0, 200)}`);
38567
38817
  }
38568
38818
  if (!res.ok) {
38569
- const errBody = await res.text().catch(() => "");
38570
- throw new Error(`google-docs append_text ${res.status}: ${errBody.slice(0, 200)}`);
38819
+ throw await googleApiError(res, "google-docs append_text", inv.source.id);
38571
38820
  }
38572
38821
  const json = await res.json();
38573
38822
  return {
@@ -38592,15 +38841,11 @@ async function deleteDocument(inv, accessToken, timeoutMs) {
38592
38841
  body: JSON.stringify({ trashed: true }),
38593
38842
  signal: AbortSignal.timeout(timeoutMs)
38594
38843
  });
38595
- if (res.status === 401 || res.status === 403) {
38596
- throw new CredentialsExpired(`Google Docs rejected token (${res.status})`, inv.source.id);
38597
- }
38598
38844
  if (res.status === 404) {
38599
38845
  throw new Error(`google-docs delete_document: document ${documentId} not found`);
38600
38846
  }
38601
38847
  if (!res.ok) {
38602
- const text = await res.text().catch(() => "");
38603
- throw new Error(`google-docs delete_document ${res.status}: ${text.slice(0, 200)}`);
38848
+ throw await googleApiError(res, "google-docs delete_document", inv.source.id);
38604
38849
  }
38605
38850
  const json = await res.json().catch(() => ({}));
38606
38851
  return {
@@ -38626,15 +38871,11 @@ async function exportDocument(inv, accessToken, timeoutMs) {
38626
38871
  headers: { authorization: `Bearer ${accessToken}` },
38627
38872
  signal: AbortSignal.timeout(timeoutMs)
38628
38873
  });
38629
- if (res.status === 401 || res.status === 403) {
38630
- throw new CredentialsExpired(`Google Docs rejected token (${res.status})`, inv.source.id);
38631
- }
38632
38874
  if (res.status === 404) {
38633
38875
  throw new Error(`google-docs export_document: document ${documentId} not found`);
38634
38876
  }
38635
38877
  if (!res.ok) {
38636
- const text = await res.text().catch(() => "");
38637
- throw new Error(`google-docs export_document ${res.status}: ${text.slice(0, 200)}`);
38878
+ throw await googleApiError(res, "google-docs export_document", inv.source.id);
38638
38879
  }
38639
38880
  const buf = new Uint8Array(await res.arrayBuffer());
38640
38881
  let binary = "";
@@ -38879,10 +39120,10 @@ function googleForms(opts) {
38879
39120
  headers: { authorization: `Bearer ${accessToken}` },
38880
39121
  signal: AbortSignal.timeout(8e3)
38881
39122
  });
38882
- if (res.status === 401 || res.status === 403) {
38883
- return { ok: false, reason: `Google rejected Forms token (${res.status}) \u2014 reconnect required` };
39123
+ if (!res.ok) {
39124
+ const body = await res.json().catch(() => void 0);
39125
+ return { ok: false, reason: googleTestFailureReason(res.status, body, "Google Forms") };
38884
39126
  }
38885
- if (!res.ok) return { ok: false, reason: `Google userinfo returned ${res.status}` };
38886
39127
  return { ok: true };
38887
39128
  } catch (err) {
38888
39129
  return { ok: false, reason: err instanceof Error ? err.message : String(err) };
@@ -38910,15 +39151,11 @@ async function getForm(inv, accessToken, timeoutMs) {
38910
39151
  headers: { authorization: `Bearer ${accessToken}` },
38911
39152
  signal: AbortSignal.timeout(timeoutMs)
38912
39153
  });
38913
- if (res.status === 401 || res.status === 403) {
38914
- throw new CredentialsExpired(`Google Forms rejected token (${res.status})`, inv.source.id);
38915
- }
38916
39154
  if (res.status === 404) {
38917
39155
  throw new Error(`google-forms get_form: form ${formId} not found`);
38918
39156
  }
38919
39157
  if (!res.ok) {
38920
- const text = await res.text().catch(() => "");
38921
- throw new Error(`google-forms get_form ${res.status}: ${text.slice(0, 200)}`);
39158
+ throw await googleApiError(res, "google-forms get_form", inv.source.id);
38922
39159
  }
38923
39160
  const form = await res.json();
38924
39161
  return {
@@ -38946,15 +39183,11 @@ async function listResponses(inv, accessToken, timeoutMs) {
38946
39183
  headers: { authorization: `Bearer ${accessToken}` },
38947
39184
  signal: AbortSignal.timeout(timeoutMs)
38948
39185
  });
38949
- if (res.status === 401 || res.status === 403) {
38950
- throw new CredentialsExpired(`Google Forms rejected token (${res.status})`, inv.source.id);
38951
- }
38952
39186
  if (res.status === 404) {
38953
39187
  throw new Error(`google-forms list_responses: form ${formId} not found`);
38954
39188
  }
38955
39189
  if (!res.ok) {
38956
- const text = await res.text().catch(() => "");
38957
- throw new Error(`google-forms list_responses ${res.status}: ${text.slice(0, 200)}`);
39190
+ throw await googleApiError(res, "google-forms list_responses", inv.source.id);
38958
39191
  }
38959
39192
  const envelope = await res.json();
38960
39193
  const responses = (envelope.responses ?? []).map((r) => ({
@@ -38984,15 +39217,11 @@ async function getResponse(inv, accessToken, timeoutMs) {
38984
39217
  signal: AbortSignal.timeout(timeoutMs)
38985
39218
  }
38986
39219
  );
38987
- if (res.status === 401 || res.status === 403) {
38988
- throw new CredentialsExpired(`Google Forms rejected token (${res.status})`, inv.source.id);
38989
- }
38990
39220
  if (res.status === 404) {
38991
39221
  throw new Error(`google-forms get_response: response ${responseId} not found`);
38992
39222
  }
38993
39223
  if (!res.ok) {
38994
- const text = await res.text().catch(() => "");
38995
- throw new Error(`google-forms get_response ${res.status}: ${text.slice(0, 200)}`);
39224
+ throw await googleApiError(res, "google-forms get_response", inv.source.id);
38996
39225
  }
38997
39226
  const r = await res.json();
38998
39227
  return {
@@ -39021,12 +39250,8 @@ async function createForm(inv, accessToken, timeoutMs) {
39021
39250
  body: JSON.stringify({ info }),
39022
39251
  signal: AbortSignal.timeout(timeoutMs)
39023
39252
  });
39024
- if (res.status === 401 || res.status === 403) {
39025
- throw new CredentialsExpired(`Google Forms rejected token (${res.status})`, inv.source.id);
39026
- }
39027
39253
  if (!res.ok) {
39028
- const text = await res.text().catch(() => "");
39029
- throw new Error(`google-forms create_form ${res.status}: ${text.slice(0, 200)}`);
39254
+ throw await googleApiError(res, "google-forms create_form", inv.source.id);
39030
39255
  }
39031
39256
  const form = await res.json();
39032
39257
  return {
@@ -39060,12 +39285,8 @@ async function batchUpdate(inv, accessToken, timeoutMs) {
39060
39285
  body: JSON.stringify(body),
39061
39286
  signal: AbortSignal.timeout(timeoutMs)
39062
39287
  });
39063
- if (res.status === 401 || res.status === 403) {
39064
- throw new CredentialsExpired(`Google Forms rejected token (${res.status})`, inv.source.id);
39065
- }
39066
39288
  if (!res.ok) {
39067
- const text = await res.text().catch(() => "");
39068
- throw new Error(`google-forms batch_update ${res.status}: ${text.slice(0, 200)}`);
39289
+ throw await googleApiError(res, "google-forms batch_update", inv.source.id);
39069
39290
  }
39070
39291
  const json = await res.json();
39071
39292
  return {
@@ -44804,16 +45025,17 @@ var amazonBedrockConnector = declarativeRestConnector({
44804
45025
  kind: "amazon-bedrock",
44805
45026
  displayName: "Amazon Bedrock",
44806
45027
  description: "Invoke AWS Bedrock foundation models for text generation, image analysis, image generation, and embeddings; list available foundation models.",
44807
- auth: { kind: "api-key", hint: "AWS access key id / secret access key pair (the connection should also carry metadata.region; requests are signed with AWS Signature V4 at the gateway)." },
45028
+ auth: { kind: "api-key", hint: 'AWS credentials as JSON: {"accessKeyId":"AKIA\u2026","secretAccessKey":"\u2026","region":"us-east-1"}. Optional "sessionToken" and "endpoint". Requests are signed with AWS Signature V4; the region selects the bedrock-runtime.<region>.amazonaws.com endpoint.' },
44808
45029
  category: "other",
44809
45030
  // Bedrock model invocations are non-idempotent generative calls; the manifest
44810
45031
  // surfaces are advisory (best-effort), not authoritative state.
44811
45032
  defaultConsistencyModel: "advisory",
44812
45033
  // bedrock-runtime is the workhorse endpoint for invoke/converse. Control-plane
44813
45034
  // listing operations override the host on a per-capability basis below.
44814
- baseUrl: { metadataKey: "runtimeEndpoint", fallback: "https://bedrock-runtime.us-east-1.amazonaws.com" },
45035
+ credentialPlacement: { kind: "aws-sigv4", service: "bedrock" },
45036
+ baseUrl: { metadataKey: "runtimeEndpoint", fallback: "https://bedrock-runtime.{region}.amazonaws.com" },
44815
45037
  defaultHeaders: { "content-type": "application/json", accept: "application/json" },
44816
- test: { method: "GET", path: "/foundation-models", headers: { host: "bedrock.us-east-1.amazonaws.com" } },
45038
+ test: { method: "GET", path: "/foundation-models", headers: { host: "bedrock.{region}.amazonaws.com" } },
44817
45039
  capabilities: [
44818
45040
  {
44819
45041
  name: "models.list",
@@ -44835,9 +45057,10 @@ var amazonBedrockConnector = declarativeRestConnector({
44835
45057
  byOutputModality: "{byOutputModality}",
44836
45058
  byInferenceType: "{byInferenceType}"
44837
45059
  },
44838
- // Control-plane host override; declarative-rest forwards the header to
44839
- // the gateway, which rewrites the upstream Host for SigV4.
44840
- headers: { host: "bedrock.us-east-1.amazonaws.com" }
45060
+ // Control-plane host override: the runtime folds this `host` header into
45061
+ // the request URL (region substituted) so SigV4 signs the bedrock.<region>
45062
+ // control-plane host, not the bedrock-runtime base host.
45063
+ headers: { host: "bedrock.{region}.amazonaws.com" }
44841
45064
  }
44842
45065
  },
44843
45066
  {
@@ -44854,7 +45077,7 @@ var amazonBedrockConnector = declarativeRestConnector({
44854
45077
  request: {
44855
45078
  method: "GET",
44856
45079
  path: "/foundation-models/{modelIdentifier}",
44857
- headers: { host: "bedrock.us-east-1.amazonaws.com" }
45080
+ headers: { host: "bedrock.{region}.amazonaws.com" }
44858
45081
  }
44859
45082
  },
44860
45083
  {
@@ -44967,13 +45190,14 @@ var amazonSecretsManagerConnector = declarativeRestConnector({
44967
45190
  description: "Create, read, update, find, and delete secrets in AWS Secrets Manager, or generate a random password.",
44968
45191
  auth: {
44969
45192
  kind: "api-key",
44970
- hint: "AWS access key id + secret access key + region (api-key field carries the SigV4 credential bundle)."
45193
+ hint: 'AWS credentials as JSON: {"accessKeyId":"AKIA\u2026","secretAccessKey":"\u2026","region":"us-east-1"}. Optional "sessionToken" and "endpoint". Requests are signed with AWS Signature V4; the region selects the secretsmanager.<region>.amazonaws.com endpoint.'
44971
45194
  },
44972
45195
  category: "other",
44973
45196
  defaultConsistencyModel: "authoritative",
45197
+ credentialPlacement: { kind: "aws-sigv4", service: "secretsmanager" },
44974
45198
  baseUrl: {
44975
45199
  metadataKey: "endpoint",
44976
- fallback: "https://secretsmanager.us-east-1.amazonaws.com"
45200
+ fallback: "https://secretsmanager.{region}.amazonaws.com"
44977
45201
  },
44978
45202
  defaultHeaders: {
44979
45203
  "Content-Type": "application/x-amz-json-1.1"
@@ -45871,11 +46095,15 @@ var amazonSesConnector = declarativeRestConnector({
45871
46095
  description: "Send transactional and templated email and manage SES email templates via the Amazon SES v2 REST API.",
45872
46096
  auth: {
45873
46097
  kind: "api-key",
45874
- hint: "AWS access key ID + secret access key with ses:SendEmail / ses:SendTemplatedEmail / template-management permissions. The region selects the SES endpoint (e.g. us-east-1)."
46098
+ hint: 'AWS credentials as JSON: {"accessKeyId":"AKIA\u2026","secretAccessKey":"\u2026","region":"us-east-1"} (the key needs ses:SendEmail / ses:SendTemplatedEmail / template-management permissions). Optional "sessionToken" and "endpoint". Requests are signed with AWS Signature V4; the region selects the email.<region>.amazonaws.com (SES v2) endpoint.'
45875
46099
  },
45876
46100
  category: "comms",
45877
46101
  defaultConsistencyModel: "authoritative",
45878
- baseUrl: { metadataKey: "endpoint" },
46102
+ // SES v2 REST API: the SigV4 signing name is `ses`, but the regional host is
46103
+ // the `email.` subdomain. metadata.endpoint still overrides when a tenant
46104
+ // pins a host.
46105
+ credentialPlacement: { kind: "aws-sigv4", service: "ses" },
46106
+ baseUrl: { metadataKey: "endpoint", fallback: "https://email.{region}.amazonaws.com" },
45879
46107
  test: { method: "GET", path: "/v2/email/identities" },
45880
46108
  capabilities: [
45881
46109
  {
@@ -46138,13 +46366,14 @@ var amazonSnsConnector = declarativeRestConnector({
46138
46366
  description: "Publish messages to Amazon Simple Notification Service topics, manage topics, and manage subscriptions.",
46139
46367
  auth: {
46140
46368
  kind: "api-key",
46141
- hint: "AWS access key id + secret access key + region (api-key field carries the SigV4 credential bundle)."
46369
+ hint: 'AWS credentials as JSON: {"accessKeyId":"AKIA\u2026","secretAccessKey":"\u2026","region":"us-east-1"}. Optional "sessionToken" and "endpoint". Requests are signed with AWS Signature V4; the region selects the sns.<region>.amazonaws.com endpoint.'
46142
46370
  },
46143
46371
  category: "comms",
46144
46372
  defaultConsistencyModel: "authoritative",
46373
+ credentialPlacement: { kind: "aws-sigv4", service: "sns" },
46145
46374
  baseUrl: {
46146
46375
  metadataKey: "endpoint",
46147
- fallback: "https://sns.us-east-1.amazonaws.com"
46376
+ fallback: "https://sns.{region}.amazonaws.com"
46148
46377
  },
46149
46378
  defaultHeaders: {
46150
46379
  "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
@@ -46413,16 +46642,17 @@ var amazonSqsConnector = declarativeRestConnector({
46413
46642
  description: "Send, receive, and delete messages on AWS SQS queues, and discover queue URLs and attributes.",
46414
46643
  auth: {
46415
46644
  kind: "api-key",
46416
- hint: "AWS access key id + secret access key + region (api-key field carries the SigV4 credential bundle; metadata.endpoint may override the regional host)."
46645
+ hint: 'AWS credentials as JSON: {"accessKeyId":"AKIA\u2026","secretAccessKey":"\u2026","region":"us-east-1"}. Optional "sessionToken" (STS temp creds) and "endpoint" (override host). Requests are signed with AWS Signature V4; the region selects the sqs.<region>.amazonaws.com endpoint.'
46417
46646
  },
46418
46647
  category: "other",
46419
46648
  // SQS is an at-least-once durable queue: sends, deletes, and queue-attribute
46420
46649
  // reads are authoritative against the service. Receive returns a transient
46421
46650
  // visibility-locked view, but the queue state itself is authoritative.
46422
46651
  defaultConsistencyModel: "authoritative",
46652
+ credentialPlacement: { kind: "aws-sigv4", service: "sqs" },
46423
46653
  baseUrl: {
46424
46654
  metadataKey: "endpoint",
46425
- fallback: "https://sqs.us-east-1.amazonaws.com"
46655
+ fallback: "https://sqs.{region}.amazonaws.com"
46426
46656
  },
46427
46657
  defaultHeaders: {
46428
46658
  "Content-Type": "application/x-amz-json-1.0"
@@ -46745,13 +46975,14 @@ var amazonTextractConnector = declarativeRestConnector({
46745
46975
  description: "Extract text, forms, tables, signatures, and structured data (expense, identity) from documents using AWS Textract.",
46746
46976
  auth: {
46747
46977
  kind: "api-key",
46748
- hint: "AWS access key id + secret access key + region (api-key field carries the SigV4 credential bundle; the gateway signs each request)."
46978
+ hint: 'AWS credentials as JSON: {"accessKeyId":"AKIA\u2026","secretAccessKey":"\u2026","region":"us-east-1"}. Optional "sessionToken" and "endpoint". Requests are signed with AWS Signature V4; the region selects the textract.<region>.amazonaws.com endpoint.'
46749
46979
  },
46750
46980
  category: "other",
46751
46981
  defaultConsistencyModel: "authoritative",
46982
+ credentialPlacement: { kind: "aws-sigv4", service: "textract" },
46752
46983
  baseUrl: {
46753
46984
  metadataKey: "endpoint",
46754
- fallback: "https://textract.us-east-1.amazonaws.com"
46985
+ fallback: "https://textract.{region}.amazonaws.com"
46755
46986
  },
46756
46987
  defaultHeaders: {
46757
46988
  "Content-Type": "application/x-amz-json-1.1"
@@ -52204,15 +52435,14 @@ var backblazeConnector = declarativeRestConnector({
52204
52435
  description: "Read and upload objects on a Backblaze B2 bucket via the S3-compatible REST API at s3.{region}.backblazeb2.com.",
52205
52436
  auth: {
52206
52437
  kind: "api-key",
52207
- hint: "Backblaze B2 application key pair (accessKeyId + secretAccessKey) scoped to the target bucket. Region selects the s3.{region}.backblazeb2.com endpoint."
52438
+ hint: 'Backblaze B2 application key pair as JSON: {"accessKeyId":"\u2026","secretAccessKey":"\u2026","region":"us-west-001"} scoped to the target bucket. Optional "endpoint" overrides the host. Requests are signed with AWS Signature V4 (S3-compatible); the region selects the s3.<region>.backblazeb2.com endpoint.'
52208
52439
  },
52209
52440
  category: "storage",
52210
52441
  defaultConsistencyModel: "authoritative",
52211
- // Endpoint is region-derived in the activepieces piece; the runtime
52212
- // resolves {region} from the credential bundle when constructing the
52213
- // request URL. The declarative-REST adapter takes a string baseUrl, so
52214
- // we keep the template here and rely on the runtime's URL resolver to
52215
- // substitute the region placeholder from the credential metadata.
52442
+ // Backblaze B2 speaks the S3 API, so it signs with AWS SigV4 under the `s3`
52443
+ // service. The runtime substitutes {region} from the credential bundle into
52444
+ // the host template below (or honors an explicit bundle `endpoint`).
52445
+ credentialPlacement: { kind: "aws-sigv4", service: "s3" },
52216
52446
  baseUrl: "https://s3.{region}.backblazeb2.com",
52217
52447
  test: { method: "GET", path: "/{bucket}?list-type=2&max-keys=1" },
52218
52448
  capabilities: [
@@ -113175,11 +113405,11 @@ var twinLabsConnector = declarativeRestConnector({
113175
113405
  // src/connectors/adapters/twitter.ts
113176
113406
  var AUTH_URL18 = "https://twitter.com/i/oauth2/authorize";
113177
113407
  var TOKEN_URL19 = "https://api.twitter.com/2/oauth2/token";
113178
- var SCOPES14 = ["tweet.read", "tweet.write", "users.read", "like.write", "offline.access"];
113408
+ var SCOPES14 = ["tweet.read", "tweet.write", "users.read", "follows.read", "like.write", "like.read", "offline.access"];
113179
113409
  var TWITTER_SPEC = {
113180
113410
  kind: "twitter",
113181
113411
  displayName: "Twitter",
113182
- description: "Post tweets and reply to tweets on Twitter.",
113412
+ description: "Read and verify Twitter/X engagement (follows, tweets, likes, retweets, mentions) and post or reply to tweets.",
113183
113413
  auth: {
113184
113414
  kind: "one_of",
113185
113415
  preferred: "oauth2",
@@ -113201,7 +113431,10 @@ var TWITTER_SPEC = {
113201
113431
  category: "comms",
113202
113432
  defaultConsistencyModel: "authoritative",
113203
113433
  baseUrl: "https://api.twitter.com/2",
113204
- test: { method: "GET", path: "/tweets/search/recent" },
113434
+ // `/users/me` is the cheapest read that proves the grant: it needs only
113435
+ // users.read, carries no required query param, and works on the free tier
113436
+ // (unlike `/tweets/search/recent`, which 400s without a `query`).
113437
+ test: { method: "GET", path: "/users/me" },
113205
113438
  capabilities: [
113206
113439
  {
113207
113440
  name: "tweets.create",
@@ -113299,6 +113532,123 @@ var TWITTER_SPEC = {
113299
113532
  },
113300
113533
  cas: "native-idempotency",
113301
113534
  externalEffect: true
113535
+ },
113536
+ // Read capabilities (official X API v2) — power quest verification in
113537
+ // blueprint-agent. `users.me` is the anchor: it resolves the connected
113538
+ // user id every other read needs. Optional `max_results` / `pagination_token`
113539
+ // are dropped from the query when the caller omits them, so the declarative
113540
+ // engine renders the bare endpoint by default. `following`, `liking_users`,
113541
+ // and `retweeted_by` are gated behind a PAID X API tier at runtime — the
113542
+ // capability is always present; availability is the registered app's tier.
113543
+ {
113544
+ name: "users.me",
113545
+ class: "read",
113546
+ description: 'Resolve the connected account (id, name, username) \u2014 anchors every other read. Pass user_fields (e.g. "public_metrics,created_at") to also fetch follower count and account age for anti-bot gating.',
113547
+ parameters: {
113548
+ type: "object",
113549
+ properties: {
113550
+ user_fields: {
113551
+ type: "string",
113552
+ description: 'Optional X `user.fields` selector, e.g. "public_metrics,created_at".'
113553
+ }
113554
+ }
113555
+ },
113556
+ request: { method: "GET", path: "/users/me", query: { "user.fields": "{user_fields}" } }
113557
+ },
113558
+ {
113559
+ name: "users.following",
113560
+ class: "read",
113561
+ description: "List the accounts a user follows, for verifying follows and followed_by. Cursor with pagination_token. Gated behind a paid X API tier.",
113562
+ parameters: {
113563
+ type: "object",
113564
+ properties: {
113565
+ id: { type: "string", description: "User id whose following list to read (from users.me)." },
113566
+ max_results: { type: "integer", description: "Page size (1\u20131000)." },
113567
+ pagination_token: { type: "string", description: "Cursor from a prior page (meta.next_token)." }
113568
+ },
113569
+ required: ["id"]
113570
+ },
113571
+ request: {
113572
+ method: "GET",
113573
+ path: "/users/{id}/following",
113574
+ query: { max_results: "{max_results}", pagination_token: "{pagination_token}" }
113575
+ }
113576
+ },
113577
+ {
113578
+ name: "users.tweets",
113579
+ class: "read",
113580
+ description: "List recent tweets authored by a user, for verifying tweeted. Hashtag filtering is applied by the caller. Cursor with pagination_token.",
113581
+ parameters: {
113582
+ type: "object",
113583
+ properties: {
113584
+ id: { type: "string", description: "Author user id (from users.me)." },
113585
+ max_results: { type: "integer", description: "Page size (5\u2013100)." },
113586
+ pagination_token: { type: "string", description: "Cursor from a prior page (meta.next_token)." }
113587
+ },
113588
+ required: ["id"]
113589
+ },
113590
+ request: {
113591
+ method: "GET",
113592
+ path: "/users/{id}/tweets",
113593
+ query: { max_results: "{max_results}", "tweet.fields": "text,created_at", pagination_token: "{pagination_token}" }
113594
+ }
113595
+ },
113596
+ {
113597
+ name: "tweets.retweetedBy",
113598
+ class: "read",
113599
+ description: "List the accounts that retweeted a tweet, for verifying retweeted. Cursor with pagination_token. Gated behind a paid X API tier.",
113600
+ parameters: {
113601
+ type: "object",
113602
+ properties: {
113603
+ id: { type: "string", description: "Tweet id to inspect." },
113604
+ max_results: { type: "integer", description: "Page size (1\u2013100)." },
113605
+ pagination_token: { type: "string", description: "Cursor from a prior page (meta.next_token)." }
113606
+ },
113607
+ required: ["id"]
113608
+ },
113609
+ request: {
113610
+ method: "GET",
113611
+ path: "/tweets/{id}/retweeted_by",
113612
+ query: { max_results: "{max_results}", pagination_token: "{pagination_token}" }
113613
+ }
113614
+ },
113615
+ {
113616
+ name: "tweets.likingUsers",
113617
+ class: "read",
113618
+ description: "List the accounts that liked a tweet, for verifying liked. Requires the like.read scope and a paid X API tier.",
113619
+ parameters: {
113620
+ type: "object",
113621
+ properties: {
113622
+ id: { type: "string", description: "Tweet id to inspect." },
113623
+ max_results: { type: "integer", description: "Page size (1\u2013100)." },
113624
+ pagination_token: { type: "string", description: "Cursor from a prior page (meta.next_token)." }
113625
+ },
113626
+ required: ["id"]
113627
+ },
113628
+ request: {
113629
+ method: "GET",
113630
+ path: "/tweets/{id}/liking_users",
113631
+ query: { max_results: "{max_results}", pagination_token: "{pagination_token}" }
113632
+ }
113633
+ },
113634
+ {
113635
+ name: "users.mentions",
113636
+ class: "read",
113637
+ description: "List tweets that mention a user, for verifying mentioned. Author filtering is applied by the caller. Cursor with pagination_token.",
113638
+ parameters: {
113639
+ type: "object",
113640
+ properties: {
113641
+ id: { type: "string", description: "Mentioned user id (from users.me)." },
113642
+ max_results: { type: "integer", description: "Page size (5\u2013100)." },
113643
+ pagination_token: { type: "string", description: "Cursor from a prior page (meta.next_token)." }
113644
+ },
113645
+ required: ["id"]
113646
+ },
113647
+ request: {
113648
+ method: "GET",
113649
+ path: "/users/{id}/mentions",
113650
+ query: { max_results: "{max_results}", "tweet.fields": "author_id,text", pagination_token: "{pagination_token}" }
113651
+ }
113302
113652
  }
113303
113653
  ]
113304
113654
  };
@@ -124598,6 +124948,11 @@ export {
124598
124948
  exchangeAuthorizationCode,
124599
124949
  refreshAccessToken,
124600
124950
  _resetPendingFlowsForTests,
124951
+ googleErrorReason,
124952
+ classifyGoogleReason,
124953
+ classifyGoogleError,
124954
+ googleApiError,
124955
+ googleTestFailureReason,
124601
124956
  googleCalendar,
124602
124957
  googleDrive,
124603
124958
  googleSheets,
@@ -125135,4 +125490,4 @@ export {
125135
125490
  demandbaseConnector,
125136
125491
  adapters_exports
125137
125492
  };
125138
- //# sourceMappingURL=chunk-ZBMMGAE2.js.map
125493
+ //# sourceMappingURL=chunk-F6MF4YNI.js.map