@tangle-network/agent-integrations 0.43.0 → 0.45.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 (42) hide show
  1. package/dist/catalog.d.ts +2 -2
  2. package/dist/catalog.js +6 -6
  3. package/dist/{chunk-6OOMR4SL.js → chunk-E6BBOBYT.js} +25 -3
  4. package/dist/chunk-E6BBOBYT.js.map +1 -0
  5. package/dist/{chunk-DQADH6DL.js → chunk-LA76XB7M.js} +2 -2
  6. package/dist/{chunk-RFBI7NUC.js → chunk-S2BUASSV.js} +2 -2
  7. package/dist/{chunk-EQB46PIG.js → chunk-UWF33ESC.js} +38 -2
  8. package/dist/chunk-UWF33ESC.js.map +1 -0
  9. package/dist/{chunk-GA7RAMMB.js → chunk-VLVSP7SP.js} +2 -2
  10. package/dist/{chunk-HRPCFEGL.js → chunk-WTMOFI3U.js} +398 -214
  11. package/dist/{chunk-HRPCFEGL.js.map → chunk-WTMOFI3U.js.map} +1 -1
  12. package/dist/connect/index.d.ts +2 -2
  13. package/dist/connect/index.js +2 -2
  14. package/dist/connectors/adapters/index.d.ts +73 -11
  15. package/dist/connectors/adapters/index.js +12 -2
  16. package/dist/connectors/index.d.ts +3 -3
  17. package/dist/connectors/index.js +16 -2
  18. package/dist/{consumer-DQiAGxQ2.d.ts → consumer-CTMB1xcc.d.ts} +1 -1
  19. package/dist/consumer.d.ts +3 -3
  20. package/dist/consumer.js +2 -2
  21. package/dist/{core-types-17jYpwtm.d.ts → core-types-BZLzY8Oa.d.ts} +1 -1
  22. package/dist/coverage-catalog.d.ts +2 -2
  23. package/dist/{importers-C_mQqIJP.d.ts → importers-6Beu0SQ9.d.ts} +1 -1
  24. package/dist/index.d.ts +9 -9
  25. package/dist/index.js +20 -6
  26. package/dist/mcp.d.ts +3 -3
  27. package/dist/mcp.js +6 -6
  28. package/dist/middleware/index.d.ts +2 -2
  29. package/dist/middleware/index.js +2 -2
  30. package/dist/registry.d.ts +2 -2
  31. package/dist/registry.js +6 -6
  32. package/dist/runtime.d.ts +2 -2
  33. package/dist/runtime.js +6 -6
  34. package/dist/specs.d.ts +2 -2
  35. package/dist/{tangle-id-CpdEOacl.d.ts → tangle-id-_uZ3ywCF.d.ts} +1 -1
  36. package/dist/{types-BLOYLnKZ.d.ts → types-rBPdMcpb.d.ts} +72 -5
  37. package/package.json +1 -1
  38. package/dist/chunk-6OOMR4SL.js.map +0 -1
  39. package/dist/chunk-EQB46PIG.js.map +0 -1
  40. /package/dist/{chunk-DQADH6DL.js.map → chunk-LA76XB7M.js.map} +0 -0
  41. /package/dist/{chunk-RFBI7NUC.js.map → chunk-S2BUASSV.js.map} +0 -0
  42. /package/dist/{chunk-GA7RAMMB.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-EQB46PIG.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 {
@@ -2450,6 +2495,9 @@ async function executeRestRequest(spec, request, inv, requiredArgs) {
2450
2495
  }
2451
2496
  };
2452
2497
  }
2498
+ if (request.existenceCheck && (res.status === 204 || res.status === 404)) {
2499
+ return { data: { exists: res.status === 204 } };
2500
+ }
2453
2501
  if (!res.ok) {
2454
2502
  throw new Error(`${spec.kind} ${request.method} ${url.pathname} HTTP ${res.status}: ${(await safeErrorText(res)).slice(0, 300)}`);
2455
2503
  }
@@ -2882,10 +2930,10 @@ function gmail(opts) {
2882
2930
  headers: { authorization: `Bearer ${accessToken}` },
2883
2931
  signal: AbortSignal.timeout(8e3)
2884
2932
  });
2885
- if (res.status === 401 || res.status === 403) {
2886
- return { ok: false, reason: `Google rejected Gmail token (${res.status}) \u2014 reconnect required` };
2933
+ if (!res.ok) {
2934
+ const body = await res.json().catch(() => void 0);
2935
+ return { ok: false, reason: googleTestFailureReason(res.status, body, "Gmail") };
2887
2936
  }
2888
- if (!res.ok) return { ok: false, reason: `Gmail returned ${res.status}` };
2889
2937
  return { ok: true };
2890
2938
  } catch (err) {
2891
2939
  return { ok: false, reason: err instanceof Error ? err.message : String(err) };
@@ -2906,12 +2954,8 @@ async function listMessages(inv, accessToken, timeoutMs) {
2906
2954
  headers: { authorization: `Bearer ${accessToken}` },
2907
2955
  signal: AbortSignal.timeout(timeoutMs)
2908
2956
  });
2909
- if (listRes.status === 401 || listRes.status === 403) {
2910
- throw new CredentialsExpired(`Gmail rejected token (${listRes.status})`, inv.source.id);
2911
- }
2912
2957
  if (!listRes.ok) {
2913
- const text = await listRes.text().catch(() => "");
2914
- throw new Error(`gmail list_messages ${listRes.status}: ${text.slice(0, 200)}`);
2958
+ throw await googleApiError(listRes, "gmail list_messages", inv.source.id);
2915
2959
  }
2916
2960
  const listJson = await listRes.json();
2917
2961
  const ids = listJson.messages ?? [];
@@ -2951,15 +2995,11 @@ async function readMessage(inv, accessToken, timeoutMs) {
2951
2995
  headers: { authorization: `Bearer ${accessToken}` },
2952
2996
  signal: AbortSignal.timeout(timeoutMs)
2953
2997
  });
2954
- if (res.status === 401 || res.status === 403) {
2955
- throw new CredentialsExpired(`Gmail rejected token (${res.status})`, inv.source.id);
2956
- }
2957
2998
  if (res.status === 404) {
2958
2999
  throw new Error(`gmail read_message: message ${id} not found`);
2959
3000
  }
2960
3001
  if (!res.ok) {
2961
- const text = await res.text().catch(() => "");
2962
- throw new Error(`gmail read_message ${res.status}: ${text.slice(0, 200)}`);
3002
+ throw await googleApiError(res, "gmail read_message", inv.source.id);
2963
3003
  }
2964
3004
  const full = await res.json();
2965
3005
  const headers = new Map((full.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
@@ -3035,12 +3075,8 @@ ${args.body}`;
3035
3075
  body: JSON.stringify({ raw: encodeBase64Url(raw) }),
3036
3076
  signal: AbortSignal.timeout(timeoutMs)
3037
3077
  });
3038
- if (sendRes.status === 401 || sendRes.status === 403) {
3039
- throw new CredentialsExpired(`Gmail rejected token (${sendRes.status})`, inv.source.id);
3040
- }
3041
3078
  if (!sendRes.ok) {
3042
- const text = await sendRes.text().catch(() => "");
3043
- throw new Error(`gmail send ${sendRes.status}: ${text.slice(0, 200)}`);
3079
+ throw await googleApiError(sendRes, "gmail send", inv.source.id);
3044
3080
  }
3045
3081
  const sent = await sendRes.json();
3046
3082
  return {
@@ -3056,12 +3092,8 @@ async function sendReply(inv, accessToken, timeoutMs) {
3056
3092
  headers: { authorization: `Bearer ${accessToken}` },
3057
3093
  signal: AbortSignal.timeout(timeoutMs)
3058
3094
  });
3059
- if (threadRes.status === 401 || threadRes.status === 403) {
3060
- throw new CredentialsExpired(`Gmail rejected token (${threadRes.status})`, inv.source.id);
3061
- }
3062
3095
  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)}`);
3096
+ throw await googleApiError(threadRes, "gmail send_reply thread fetch", inv.source.id);
3065
3097
  }
3066
3098
  const thread = await threadRes.json();
3067
3099
  const last = thread.messages?.[thread.messages.length - 1];
@@ -3104,12 +3136,8 @@ ${body}`;
3104
3136
  body: JSON.stringify(sendBody),
3105
3137
  signal: AbortSignal.timeout(timeoutMs)
3106
3138
  });
3107
- if (sendRes.status === 401 || sendRes.status === 403) {
3108
- throw new CredentialsExpired(`Gmail rejected token (${sendRes.status})`, inv.source.id);
3109
- }
3110
3139
  if (!sendRes.ok) {
3111
- const text = await sendRes.text().catch(() => "");
3112
- throw new Error(`gmail send_reply ${sendRes.status}: ${text.slice(0, 200)}`);
3140
+ throw await googleApiError(sendRes, "gmail send_reply", inv.source.id);
3113
3141
  }
3114
3142
  const sent = await sendRes.json();
3115
3143
  return {
@@ -3135,12 +3163,8 @@ async function watchLabel(inv, accessToken, timeoutMs) {
3135
3163
  body: JSON.stringify(body),
3136
3164
  signal: AbortSignal.timeout(timeoutMs)
3137
3165
  });
3138
- if (res.status === 401 || res.status === 403) {
3139
- throw new CredentialsExpired(`Gmail rejected token (${res.status})`, inv.source.id);
3140
- }
3141
3166
  if (!res.ok) {
3142
- const text = await res.text().catch(() => "");
3143
- throw new Error(`gmail watch_label ${res.status}: ${text.slice(0, 200)}`);
3167
+ throw await googleApiError(res, "gmail watch_label", inv.source.id);
3144
3168
  }
3145
3169
  const json = await res.json();
3146
3170
  return {
@@ -7297,6 +7321,83 @@ var githubConnector = declarativeRestConnector({
7297
7321
  },
7298
7322
  request: { method: "GET", path: "/search/issues", query: { q: "{q}", per_page: "{per_page}" } }
7299
7323
  },
7324
+ {
7325
+ name: "users.getAuthenticated",
7326
+ class: "read",
7327
+ description: "Resolve the authenticated user (the token owner). Quest verification anchors author:/owner: filters to this login, so it must be resolved first.",
7328
+ parameters: { type: "object", properties: {}, required: [] },
7329
+ request: { method: "GET", path: "/user" }
7330
+ },
7331
+ {
7332
+ name: "activity.checkStarred",
7333
+ class: "read",
7334
+ description: "Check whether the authenticated user has starred a repository. Returns { exists: true } when starred, { exists: false } when not.",
7335
+ parameters: repoParams,
7336
+ request: { method: "GET", path: "/user/starred/{owner}/{repo}", existenceCheck: true }
7337
+ },
7338
+ {
7339
+ name: "users.checkFollowing",
7340
+ class: "read",
7341
+ description: "Check whether the authenticated user follows another user. Returns { exists: true } when following, { exists: false } when not.",
7342
+ parameters: {
7343
+ type: "object",
7344
+ properties: { target: { type: "string", description: "Login of the user to check the follow against." } },
7345
+ required: ["target"]
7346
+ },
7347
+ request: { method: "GET", path: "/user/following/{target}", existenceCheck: true }
7348
+ },
7349
+ {
7350
+ name: "repos.listCommits",
7351
+ class: "read",
7352
+ description: "List commits on a repository, optionally filtered to a single author login/email. Used to verify a user contributed to the repo.",
7353
+ parameters: {
7354
+ type: "object",
7355
+ properties: {
7356
+ owner: { type: "string" },
7357
+ repo: { type: "string" },
7358
+ author: { type: "string", description: "Restrict to commits authored by this GitHub login or email." },
7359
+ per_page: { type: "integer", minimum: 1, maximum: 100 }
7360
+ },
7361
+ required: ["owner", "repo"]
7362
+ },
7363
+ request: {
7364
+ method: "GET",
7365
+ path: "/repos/{owner}/{repo}/commits",
7366
+ query: { author: "{author}", per_page: "{per_page}" }
7367
+ }
7368
+ },
7369
+ {
7370
+ name: "repos.getReadme",
7371
+ class: "read",
7372
+ description: "Read a repository's README (returns base64-encoded content). Used to verify README mentions.",
7373
+ parameters: repoParams,
7374
+ request: { method: "GET", path: "/repos/{owner}/{repo}/readme" }
7375
+ },
7376
+ {
7377
+ name: "search.code",
7378
+ class: "read",
7379
+ description: "Search code across GitHub. Used to verify code usage of a symbol or string.",
7380
+ parameters: {
7381
+ type: "object",
7382
+ properties: { q: { type: "string" }, per_page: { type: "integer", minimum: 1, maximum: 100 } },
7383
+ required: ["q"]
7384
+ },
7385
+ request: { method: "GET", path: "/search/code", query: { q: "{q}", per_page: "{per_page}" } }
7386
+ },
7387
+ {
7388
+ name: "orgs.checkMembership",
7389
+ class: "read",
7390
+ description: "Check whether a user is a member of an organization. Returns { exists: true } when a member, { exists: false } when not.",
7391
+ parameters: {
7392
+ type: "object",
7393
+ properties: {
7394
+ org: { type: "string" },
7395
+ user: { type: "string", description: "Login of the user to check membership for." }
7396
+ },
7397
+ required: ["org", "user"]
7398
+ },
7399
+ request: { method: "GET", path: "/orgs/{org}/members/{user}", existenceCheck: true }
7400
+ },
7300
7401
  {
7301
7402
  name: "issues.create",
7302
7403
  class: "mutation",
@@ -38378,10 +38479,10 @@ function googleDocs(opts) {
38378
38479
  headers: { authorization: `Bearer ${accessToken}` },
38379
38480
  signal: AbortSignal.timeout(8e3)
38380
38481
  });
38381
- if (res.status === 401 || res.status === 403) {
38382
- return { ok: false, reason: `Google rejected Docs token (${res.status}) \u2014 reconnect required` };
38482
+ if (!res.ok) {
38483
+ const body = await res.json().catch(() => void 0);
38484
+ return { ok: false, reason: googleTestFailureReason(res.status, body, "Google Docs") };
38383
38485
  }
38384
- if (!res.ok) return { ok: false, reason: `Google userinfo returned ${res.status}` };
38385
38486
  return { ok: true };
38386
38487
  } catch (err) {
38387
38488
  return { ok: false, reason: err instanceof Error ? err.message : String(err) };
@@ -38417,15 +38518,11 @@ async function getDocument2(inv, accessToken, timeoutMs) {
38417
38518
  headers: { authorization: `Bearer ${accessToken}` },
38418
38519
  signal: AbortSignal.timeout(timeoutMs)
38419
38520
  });
38420
- if (res.status === 401 || res.status === 403) {
38421
- throw new CredentialsExpired(`Google Docs rejected token (${res.status})`, inv.source.id);
38422
- }
38423
38521
  if (res.status === 404) {
38424
38522
  throw new Error(`google-docs get_document: document ${documentId} not found`);
38425
38523
  }
38426
38524
  if (!res.ok) {
38427
- const text = await res.text().catch(() => "");
38428
- throw new Error(`google-docs get_document ${res.status}: ${text.slice(0, 200)}`);
38525
+ throw await googleApiError(res, "google-docs get_document", inv.source.id);
38429
38526
  }
38430
38527
  const doc = await res.json();
38431
38528
  return {
@@ -38456,12 +38553,8 @@ async function createDocument2(inv, accessToken, timeoutMs) {
38456
38553
  body: JSON.stringify({ title }),
38457
38554
  signal: AbortSignal.timeout(timeoutMs)
38458
38555
  });
38459
- if (createRes.status === 401 || createRes.status === 403) {
38460
- throw new CredentialsExpired(`Google Docs rejected token (${createRes.status})`, inv.source.id);
38461
- }
38462
38556
  if (!createRes.ok) {
38463
- const text = await createRes.text().catch(() => "");
38464
- throw new Error(`google-docs create_document ${createRes.status}: ${text.slice(0, 200)}`);
38557
+ throw await googleApiError(createRes, "google-docs create_document", inv.source.id);
38465
38558
  }
38466
38559
  const created = await createRes.json();
38467
38560
  let revisionId = created.revisionId;
@@ -38487,12 +38580,8 @@ async function createDocument2(inv, accessToken, timeoutMs) {
38487
38580
  signal: AbortSignal.timeout(timeoutMs)
38488
38581
  }
38489
38582
  );
38490
- if (insertRes.status === 401 || insertRes.status === 403) {
38491
- throw new CredentialsExpired(`Google Docs rejected token (${insertRes.status})`, inv.source.id);
38492
- }
38493
38583
  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)}`);
38584
+ throw await googleApiError(insertRes, "google-docs create_document insertText", inv.source.id);
38496
38585
  }
38497
38586
  const insertJson = await insertRes.json();
38498
38587
  revisionId = insertJson.writeControl?.requiredRevisionId ?? revisionId;
@@ -38514,15 +38603,11 @@ async function appendText(inv, accessToken, timeoutMs) {
38514
38603
  headers: { authorization: `Bearer ${accessToken}` },
38515
38604
  signal: AbortSignal.timeout(timeoutMs)
38516
38605
  });
38517
- if (docRes.status === 401 || docRes.status === 403) {
38518
- throw new CredentialsExpired(`Google Docs rejected token (${docRes.status})`, inv.source.id);
38519
- }
38520
38606
  if (docRes.status === 404) {
38521
38607
  throw new Error(`google-docs append_text: document ${documentId} not found`);
38522
38608
  }
38523
38609
  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)}`);
38610
+ throw await googleApiError(docRes, "google-docs append_text fetch", inv.source.id);
38526
38611
  }
38527
38612
  const doc = await docRes.json();
38528
38613
  const insertIndex = findAppendIndex(doc);
@@ -38551,9 +38636,6 @@ async function appendText(inv, accessToken, timeoutMs) {
38551
38636
  signal: AbortSignal.timeout(timeoutMs)
38552
38637
  }
38553
38638
  );
38554
- if (res.status === 401 || res.status === 403) {
38555
- throw new CredentialsExpired(`Google Docs rejected token (${res.status})`, inv.source.id);
38556
- }
38557
38639
  if (res.status === 400 && requiredRevisionId) {
38558
38640
  const errBody = await res.text().catch(() => "");
38559
38641
  if (/FAILED_PRECONDITION|requiredRevisionId/i.test(errBody)) {
@@ -38566,8 +38648,7 @@ async function appendText(inv, accessToken, timeoutMs) {
38566
38648
  throw new Error(`google-docs append_text ${res.status}: ${errBody.slice(0, 200)}`);
38567
38649
  }
38568
38650
  if (!res.ok) {
38569
- const errBody = await res.text().catch(() => "");
38570
- throw new Error(`google-docs append_text ${res.status}: ${errBody.slice(0, 200)}`);
38651
+ throw await googleApiError(res, "google-docs append_text", inv.source.id);
38571
38652
  }
38572
38653
  const json = await res.json();
38573
38654
  return {
@@ -38592,15 +38673,11 @@ async function deleteDocument(inv, accessToken, timeoutMs) {
38592
38673
  body: JSON.stringify({ trashed: true }),
38593
38674
  signal: AbortSignal.timeout(timeoutMs)
38594
38675
  });
38595
- if (res.status === 401 || res.status === 403) {
38596
- throw new CredentialsExpired(`Google Docs rejected token (${res.status})`, inv.source.id);
38597
- }
38598
38676
  if (res.status === 404) {
38599
38677
  throw new Error(`google-docs delete_document: document ${documentId} not found`);
38600
38678
  }
38601
38679
  if (!res.ok) {
38602
- const text = await res.text().catch(() => "");
38603
- throw new Error(`google-docs delete_document ${res.status}: ${text.slice(0, 200)}`);
38680
+ throw await googleApiError(res, "google-docs delete_document", inv.source.id);
38604
38681
  }
38605
38682
  const json = await res.json().catch(() => ({}));
38606
38683
  return {
@@ -38626,15 +38703,11 @@ async function exportDocument(inv, accessToken, timeoutMs) {
38626
38703
  headers: { authorization: `Bearer ${accessToken}` },
38627
38704
  signal: AbortSignal.timeout(timeoutMs)
38628
38705
  });
38629
- if (res.status === 401 || res.status === 403) {
38630
- throw new CredentialsExpired(`Google Docs rejected token (${res.status})`, inv.source.id);
38631
- }
38632
38706
  if (res.status === 404) {
38633
38707
  throw new Error(`google-docs export_document: document ${documentId} not found`);
38634
38708
  }
38635
38709
  if (!res.ok) {
38636
- const text = await res.text().catch(() => "");
38637
- throw new Error(`google-docs export_document ${res.status}: ${text.slice(0, 200)}`);
38710
+ throw await googleApiError(res, "google-docs export_document", inv.source.id);
38638
38711
  }
38639
38712
  const buf = new Uint8Array(await res.arrayBuffer());
38640
38713
  let binary = "";
@@ -38879,10 +38952,10 @@ function googleForms(opts) {
38879
38952
  headers: { authorization: `Bearer ${accessToken}` },
38880
38953
  signal: AbortSignal.timeout(8e3)
38881
38954
  });
38882
- if (res.status === 401 || res.status === 403) {
38883
- return { ok: false, reason: `Google rejected Forms token (${res.status}) \u2014 reconnect required` };
38955
+ if (!res.ok) {
38956
+ const body = await res.json().catch(() => void 0);
38957
+ return { ok: false, reason: googleTestFailureReason(res.status, body, "Google Forms") };
38884
38958
  }
38885
- if (!res.ok) return { ok: false, reason: `Google userinfo returned ${res.status}` };
38886
38959
  return { ok: true };
38887
38960
  } catch (err) {
38888
38961
  return { ok: false, reason: err instanceof Error ? err.message : String(err) };
@@ -38910,15 +38983,11 @@ async function getForm(inv, accessToken, timeoutMs) {
38910
38983
  headers: { authorization: `Bearer ${accessToken}` },
38911
38984
  signal: AbortSignal.timeout(timeoutMs)
38912
38985
  });
38913
- if (res.status === 401 || res.status === 403) {
38914
- throw new CredentialsExpired(`Google Forms rejected token (${res.status})`, inv.source.id);
38915
- }
38916
38986
  if (res.status === 404) {
38917
38987
  throw new Error(`google-forms get_form: form ${formId} not found`);
38918
38988
  }
38919
38989
  if (!res.ok) {
38920
- const text = await res.text().catch(() => "");
38921
- throw new Error(`google-forms get_form ${res.status}: ${text.slice(0, 200)}`);
38990
+ throw await googleApiError(res, "google-forms get_form", inv.source.id);
38922
38991
  }
38923
38992
  const form = await res.json();
38924
38993
  return {
@@ -38946,15 +39015,11 @@ async function listResponses(inv, accessToken, timeoutMs) {
38946
39015
  headers: { authorization: `Bearer ${accessToken}` },
38947
39016
  signal: AbortSignal.timeout(timeoutMs)
38948
39017
  });
38949
- if (res.status === 401 || res.status === 403) {
38950
- throw new CredentialsExpired(`Google Forms rejected token (${res.status})`, inv.source.id);
38951
- }
38952
39018
  if (res.status === 404) {
38953
39019
  throw new Error(`google-forms list_responses: form ${formId} not found`);
38954
39020
  }
38955
39021
  if (!res.ok) {
38956
- const text = await res.text().catch(() => "");
38957
- throw new Error(`google-forms list_responses ${res.status}: ${text.slice(0, 200)}`);
39022
+ throw await googleApiError(res, "google-forms list_responses", inv.source.id);
38958
39023
  }
38959
39024
  const envelope = await res.json();
38960
39025
  const responses = (envelope.responses ?? []).map((r) => ({
@@ -38984,15 +39049,11 @@ async function getResponse(inv, accessToken, timeoutMs) {
38984
39049
  signal: AbortSignal.timeout(timeoutMs)
38985
39050
  }
38986
39051
  );
38987
- if (res.status === 401 || res.status === 403) {
38988
- throw new CredentialsExpired(`Google Forms rejected token (${res.status})`, inv.source.id);
38989
- }
38990
39052
  if (res.status === 404) {
38991
39053
  throw new Error(`google-forms get_response: response ${responseId} not found`);
38992
39054
  }
38993
39055
  if (!res.ok) {
38994
- const text = await res.text().catch(() => "");
38995
- throw new Error(`google-forms get_response ${res.status}: ${text.slice(0, 200)}`);
39056
+ throw await googleApiError(res, "google-forms get_response", inv.source.id);
38996
39057
  }
38997
39058
  const r = await res.json();
38998
39059
  return {
@@ -39021,12 +39082,8 @@ async function createForm(inv, accessToken, timeoutMs) {
39021
39082
  body: JSON.stringify({ info }),
39022
39083
  signal: AbortSignal.timeout(timeoutMs)
39023
39084
  });
39024
- if (res.status === 401 || res.status === 403) {
39025
- throw new CredentialsExpired(`Google Forms rejected token (${res.status})`, inv.source.id);
39026
- }
39027
39085
  if (!res.ok) {
39028
- const text = await res.text().catch(() => "");
39029
- throw new Error(`google-forms create_form ${res.status}: ${text.slice(0, 200)}`);
39086
+ throw await googleApiError(res, "google-forms create_form", inv.source.id);
39030
39087
  }
39031
39088
  const form = await res.json();
39032
39089
  return {
@@ -39060,12 +39117,8 @@ async function batchUpdate(inv, accessToken, timeoutMs) {
39060
39117
  body: JSON.stringify(body),
39061
39118
  signal: AbortSignal.timeout(timeoutMs)
39062
39119
  });
39063
- if (res.status === 401 || res.status === 403) {
39064
- throw new CredentialsExpired(`Google Forms rejected token (${res.status})`, inv.source.id);
39065
- }
39066
39120
  if (!res.ok) {
39067
- const text = await res.text().catch(() => "");
39068
- throw new Error(`google-forms batch_update ${res.status}: ${text.slice(0, 200)}`);
39121
+ throw await googleApiError(res, "google-forms batch_update", inv.source.id);
39069
39122
  }
39070
39123
  const json = await res.json();
39071
39124
  return {
@@ -113175,11 +113228,11 @@ var twinLabsConnector = declarativeRestConnector({
113175
113228
  // src/connectors/adapters/twitter.ts
113176
113229
  var AUTH_URL18 = "https://twitter.com/i/oauth2/authorize";
113177
113230
  var TOKEN_URL19 = "https://api.twitter.com/2/oauth2/token";
113178
- var SCOPES14 = ["tweet.read", "tweet.write", "users.read", "like.write", "offline.access"];
113231
+ var SCOPES14 = ["tweet.read", "tweet.write", "users.read", "follows.read", "like.write", "like.read", "offline.access"];
113179
113232
  var TWITTER_SPEC = {
113180
113233
  kind: "twitter",
113181
113234
  displayName: "Twitter",
113182
- description: "Post tweets and reply to tweets on Twitter.",
113235
+ description: "Read and verify Twitter/X engagement (follows, tweets, likes, retweets, mentions) and post or reply to tweets.",
113183
113236
  auth: {
113184
113237
  kind: "one_of",
113185
113238
  preferred: "oauth2",
@@ -113201,7 +113254,10 @@ var TWITTER_SPEC = {
113201
113254
  category: "comms",
113202
113255
  defaultConsistencyModel: "authoritative",
113203
113256
  baseUrl: "https://api.twitter.com/2",
113204
- test: { method: "GET", path: "/tweets/search/recent" },
113257
+ // `/users/me` is the cheapest read that proves the grant: it needs only
113258
+ // users.read, carries no required query param, and works on the free tier
113259
+ // (unlike `/tweets/search/recent`, which 400s without a `query`).
113260
+ test: { method: "GET", path: "/users/me" },
113205
113261
  capabilities: [
113206
113262
  {
113207
113263
  name: "tweets.create",
@@ -113299,6 +113355,123 @@ var TWITTER_SPEC = {
113299
113355
  },
113300
113356
  cas: "native-idempotency",
113301
113357
  externalEffect: true
113358
+ },
113359
+ // Read capabilities (official X API v2) — power quest verification in
113360
+ // blueprint-agent. `users.me` is the anchor: it resolves the connected
113361
+ // user id every other read needs. Optional `max_results` / `pagination_token`
113362
+ // are dropped from the query when the caller omits them, so the declarative
113363
+ // engine renders the bare endpoint by default. `following`, `liking_users`,
113364
+ // and `retweeted_by` are gated behind a PAID X API tier at runtime — the
113365
+ // capability is always present; availability is the registered app's tier.
113366
+ {
113367
+ name: "users.me",
113368
+ class: "read",
113369
+ 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.',
113370
+ parameters: {
113371
+ type: "object",
113372
+ properties: {
113373
+ user_fields: {
113374
+ type: "string",
113375
+ description: 'Optional X `user.fields` selector, e.g. "public_metrics,created_at".'
113376
+ }
113377
+ }
113378
+ },
113379
+ request: { method: "GET", path: "/users/me", query: { "user.fields": "{user_fields}" } }
113380
+ },
113381
+ {
113382
+ name: "users.following",
113383
+ class: "read",
113384
+ description: "List the accounts a user follows, for verifying follows and followed_by. Cursor with pagination_token. Gated behind a paid X API tier.",
113385
+ parameters: {
113386
+ type: "object",
113387
+ properties: {
113388
+ id: { type: "string", description: "User id whose following list to read (from users.me)." },
113389
+ max_results: { type: "integer", description: "Page size (1\u20131000)." },
113390
+ pagination_token: { type: "string", description: "Cursor from a prior page (meta.next_token)." }
113391
+ },
113392
+ required: ["id"]
113393
+ },
113394
+ request: {
113395
+ method: "GET",
113396
+ path: "/users/{id}/following",
113397
+ query: { max_results: "{max_results}", pagination_token: "{pagination_token}" }
113398
+ }
113399
+ },
113400
+ {
113401
+ name: "users.tweets",
113402
+ class: "read",
113403
+ description: "List recent tweets authored by a user, for verifying tweeted. Hashtag filtering is applied by the caller. Cursor with pagination_token.",
113404
+ parameters: {
113405
+ type: "object",
113406
+ properties: {
113407
+ id: { type: "string", description: "Author user id (from users.me)." },
113408
+ max_results: { type: "integer", description: "Page size (5\u2013100)." },
113409
+ pagination_token: { type: "string", description: "Cursor from a prior page (meta.next_token)." }
113410
+ },
113411
+ required: ["id"]
113412
+ },
113413
+ request: {
113414
+ method: "GET",
113415
+ path: "/users/{id}/tweets",
113416
+ query: { max_results: "{max_results}", "tweet.fields": "text,created_at", pagination_token: "{pagination_token}" }
113417
+ }
113418
+ },
113419
+ {
113420
+ name: "tweets.retweetedBy",
113421
+ class: "read",
113422
+ description: "List the accounts that retweeted a tweet, for verifying retweeted. Cursor with pagination_token. Gated behind a paid X API tier.",
113423
+ parameters: {
113424
+ type: "object",
113425
+ properties: {
113426
+ id: { type: "string", description: "Tweet id to inspect." },
113427
+ max_results: { type: "integer", description: "Page size (1\u2013100)." },
113428
+ pagination_token: { type: "string", description: "Cursor from a prior page (meta.next_token)." }
113429
+ },
113430
+ required: ["id"]
113431
+ },
113432
+ request: {
113433
+ method: "GET",
113434
+ path: "/tweets/{id}/retweeted_by",
113435
+ query: { max_results: "{max_results}", pagination_token: "{pagination_token}" }
113436
+ }
113437
+ },
113438
+ {
113439
+ name: "tweets.likingUsers",
113440
+ class: "read",
113441
+ description: "List the accounts that liked a tweet, for verifying liked. Requires the like.read scope and a paid X API tier.",
113442
+ parameters: {
113443
+ type: "object",
113444
+ properties: {
113445
+ id: { type: "string", description: "Tweet id to inspect." },
113446
+ max_results: { type: "integer", description: "Page size (1\u2013100)." },
113447
+ pagination_token: { type: "string", description: "Cursor from a prior page (meta.next_token)." }
113448
+ },
113449
+ required: ["id"]
113450
+ },
113451
+ request: {
113452
+ method: "GET",
113453
+ path: "/tweets/{id}/liking_users",
113454
+ query: { max_results: "{max_results}", pagination_token: "{pagination_token}" }
113455
+ }
113456
+ },
113457
+ {
113458
+ name: "users.mentions",
113459
+ class: "read",
113460
+ description: "List tweets that mention a user, for verifying mentioned. Author filtering is applied by the caller. Cursor with pagination_token.",
113461
+ parameters: {
113462
+ type: "object",
113463
+ properties: {
113464
+ id: { type: "string", description: "Mentioned user id (from users.me)." },
113465
+ max_results: { type: "integer", description: "Page size (5\u2013100)." },
113466
+ pagination_token: { type: "string", description: "Cursor from a prior page (meta.next_token)." }
113467
+ },
113468
+ required: ["id"]
113469
+ },
113470
+ request: {
113471
+ method: "GET",
113472
+ path: "/users/{id}/mentions",
113473
+ query: { max_results: "{max_results}", "tweet.fields": "author_id,text", pagination_token: "{pagination_token}" }
113474
+ }
113302
113475
  }
113303
113476
  ]
113304
113477
  };
@@ -124080,7 +124253,13 @@ var gongConnector = declarativeRestConnector({
124080
124253
  "api:flows:write"
124081
124254
  ],
124082
124255
  clientIdEnv: "GONG_OAUTH_CLIENT_ID",
124083
- clientSecretEnv: "GONG_OAUTH_CLIENT_SECRET"
124256
+ clientSecretEnv: "GONG_OAUTH_CLIENT_SECRET",
124257
+ // Gong returns the per-customer API host on the token response; capture it
124258
+ // into metadata.apiBaseUrlForCustomer so `baseUrl` resolution below finds
124259
+ // it. Required: the generic api.gong.io host is invalid for OAuth apps, so
124260
+ // a missing value fails the exchange loud rather than minting a connection
124261
+ // that looks active while every call 404s.
124262
+ tokenMetadata: { apiBaseUrlForCustomer: { field: "api_base_url_for_customer", required: true } }
124084
124263
  },
124085
124264
  category: "comms",
124086
124265
  defaultConsistencyModel: "authoritative",
@@ -124592,6 +124771,11 @@ export {
124592
124771
  exchangeAuthorizationCode,
124593
124772
  refreshAccessToken,
124594
124773
  _resetPendingFlowsForTests,
124774
+ googleErrorReason,
124775
+ classifyGoogleReason,
124776
+ classifyGoogleError,
124777
+ googleApiError,
124778
+ googleTestFailureReason,
124595
124779
  googleCalendar,
124596
124780
  googleDrive,
124597
124781
  googleSheets,
@@ -125129,4 +125313,4 @@ export {
125129
125313
  demandbaseConnector,
125130
125314
  adapters_exports
125131
125315
  };
125132
- //# sourceMappingURL=chunk-HRPCFEGL.js.map
125316
+ //# sourceMappingURL=chunk-WTMOFI3U.js.map