@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.
- package/dist/catalog.d.ts +2 -2
- package/dist/catalog.js +6 -6
- package/dist/{chunk-YA52626Y.js → chunk-CPZUNSKB.js} +2 -2
- package/dist/{chunk-ZBMMGAE2.js → chunk-F6MF4YNI.js} +603 -248
- package/dist/{chunk-ZBMMGAE2.js.map → chunk-F6MF4YNI.js.map} +1 -1
- package/dist/{chunk-U4W37I5T.js → chunk-LA76XB7M.js} +2 -2
- package/dist/{chunk-ITA67K5M.js → chunk-S2BUASSV.js} +2 -2
- package/dist/{chunk-BDSGHMY6.js → chunk-UWF33ESC.js} +38 -2
- package/dist/chunk-UWF33ESC.js.map +1 -0
- package/dist/{chunk-HNHR2MKW.js → chunk-VLVSP7SP.js} +2 -2
- package/dist/connect/index.d.ts +2 -2
- package/dist/connect/index.js +2 -2
- package/dist/connectors/adapters/index.d.ts +79 -7
- package/dist/connectors/adapters/index.js +12 -2
- package/dist/connectors/index.d.ts +3 -3
- package/dist/connectors/index.js +16 -2
- package/dist/{consumer-rANg4Qhz.d.ts → consumer-CTMB1xcc.d.ts} +1 -1
- package/dist/consumer.d.ts +3 -3
- package/dist/consumer.js +2 -2
- package/dist/{core-types-ZABxT4_F.d.ts → core-types-BZLzY8Oa.d.ts} +1 -1
- package/dist/coverage-catalog.d.ts +2 -2
- package/dist/{importers-CWmyogMo.d.ts → importers-6Beu0SQ9.d.ts} +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +20 -6
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +6 -6
- package/dist/middleware/index.d.ts +2 -2
- package/dist/middleware/index.js +2 -2
- package/dist/registry.d.ts +2 -2
- package/dist/registry.js +6 -6
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +6 -6
- package/dist/specs.d.ts +2 -2
- package/dist/{tangle-id-DE1QrpAY.d.ts → tangle-id-_uZ3ywCF.d.ts} +1 -1
- package/dist/{types-DzPmyjGl.d.ts → types-rBPdMcpb.d.ts} +52 -5
- package/package.json +1 -1
- package/dist/chunk-BDSGHMY6.js.map +0 -1
- /package/dist/{chunk-YA52626Y.js.map → chunk-CPZUNSKB.js.map} +0 -0
- /package/dist/{chunk-U4W37I5T.js.map → chunk-LA76XB7M.js.map} +0 -0
- /package/dist/{chunk-ITA67K5M.js.map → chunk-S2BUASSV.js.map} +0 -0
- /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-
|
|
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
|
-
|
|
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.
|
|
1005
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1484
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2034
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
2583
|
+
...renderHeaders(renderableHeaders, inv.args)
|
|
2419
2584
|
};
|
|
2420
|
-
|
|
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"] =
|
|
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:
|
|
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.
|
|
2886
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
38382
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
38883
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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
|
|
44839
|
-
// the
|
|
44840
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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
|
-
//
|
|
52212
|
-
//
|
|
52213
|
-
//
|
|
52214
|
-
|
|
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: "
|
|
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
|
-
|
|
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-
|
|
125493
|
+
//# sourceMappingURL=chunk-F6MF4YNI.js.map
|