mcp-scraper 0.40.3 → 0.41.1
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/README.md +2 -2
- package/dist/bin/api-server.cjs +750 -102
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +26 -6
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/chunk-R3EME5HH.js +7 -0
- package/dist/chunk-R3EME5HH.js.map +1 -0
- package/dist/{chunk-6SZ52BQ5.js → chunk-VE5T5UPM.js} +27 -7
- package/dist/chunk-VE5T5UPM.js.map +1 -0
- package/dist/{server-W76RUYD3.js → server-PKTDPCB2.js} +709 -99
- package/dist/server-PKTDPCB2.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-6SZ52BQ5.js.map +0 -1
- package/dist/chunk-XU4ZLIB2.js +0 -7
- package/dist/chunk-XU4ZLIB2.js.map +0 -1
- package/dist/server-W76RUYD3.js.map +0 -1
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
renewConnectedDataArtifactDownload,
|
|
68
68
|
renewDirectoryArtifactDownload,
|
|
69
69
|
transcribeMediaUrl
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-VE5T5UPM.js";
|
|
71
71
|
import {
|
|
72
72
|
auditImageUrls,
|
|
73
73
|
auditImages,
|
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
RawMapsOverviewSchema,
|
|
120
120
|
RawMapsReviewStatsSchema
|
|
121
121
|
} from "./chunk-CB5C3BPB.js";
|
|
122
|
-
import "./chunk-
|
|
122
|
+
import "./chunk-R3EME5HH.js";
|
|
123
123
|
import {
|
|
124
124
|
abandonExtractSettlement,
|
|
125
125
|
countSuccessfulPages,
|
|
@@ -29824,6 +29824,7 @@ var CONNECTED_DATA_DATASETS = [
|
|
|
29824
29824
|
"calendar_events",
|
|
29825
29825
|
"zoom_recordings",
|
|
29826
29826
|
"zoom_transcripts",
|
|
29827
|
+
"slack_channel_messages",
|
|
29827
29828
|
"meta_ads_insights",
|
|
29828
29829
|
"search_console_performance",
|
|
29829
29830
|
"resend_data",
|
|
@@ -29850,8 +29851,9 @@ function resolveConnectedDataRange(args) {
|
|
|
29850
29851
|
const from = args.from ? new Date(args.from) : new Date(to.getTime() - (args.lastDays ?? 7) * 864e5);
|
|
29851
29852
|
if (!Number.isFinite(from.getTime())) throw new ConnectedDataExportValidationError("from must be an RFC3339 timestamp.");
|
|
29852
29853
|
if (from.getTime() >= to.getTime()) throw new ConnectedDataExportValidationError("from must be earlier than to.");
|
|
29853
|
-
|
|
29854
|
-
|
|
29854
|
+
const maxRangeDays = args.maxRangeDays === void 0 ? 90 : args.maxRangeDays;
|
|
29855
|
+
if (maxRangeDays !== null && to.getTime() - from.getTime() > maxRangeDays * 864e5) {
|
|
29856
|
+
throw new ConnectedDataExportValidationError(`A connected-data export can cover at most ${maxRangeDays} days per request.`);
|
|
29855
29857
|
}
|
|
29856
29858
|
if (to.getTime() > now.getTime() + 5 * 6e4) {
|
|
29857
29859
|
throw new ConnectedDataExportValidationError("to cannot be in the future.");
|
|
@@ -29861,10 +29863,18 @@ function resolveConnectedDataRange(args) {
|
|
|
29861
29863
|
function resolveConnectedDataExportRequest(args) {
|
|
29862
29864
|
const continuation = args.continuation;
|
|
29863
29865
|
if (!continuation) {
|
|
29866
|
+
const scope2 = resolveConnectedDataExportScope(args.requestedDataset, args.scope);
|
|
29864
29867
|
return {
|
|
29865
29868
|
dataset: args.requestedDataset,
|
|
29866
|
-
range: resolveConnectedDataRange({
|
|
29867
|
-
|
|
29869
|
+
range: resolveConnectedDataRange({
|
|
29870
|
+
from: args.from,
|
|
29871
|
+
to: args.to,
|
|
29872
|
+
lastDays: args.lastDays,
|
|
29873
|
+
now: args.now,
|
|
29874
|
+
maxRangeDays: args.requestedDataset === "slack_channel_messages" ? null : 90
|
|
29875
|
+
}),
|
|
29876
|
+
...args.cursor ? { cursor: args.cursor } : {},
|
|
29877
|
+
...scope2 ? { scope: scope2 } : {}
|
|
29868
29878
|
};
|
|
29869
29879
|
}
|
|
29870
29880
|
const supported = CONNECTED_DATA_DATASETS.filter((dataset2) => dataset2 !== "auto");
|
|
@@ -29887,10 +29897,38 @@ function resolveConnectedDataExportRequest(args) {
|
|
|
29887
29897
|
throw new ConnectedDataExportValidationError("dataset conflicts with continuation.");
|
|
29888
29898
|
}
|
|
29889
29899
|
const dataset = continuation.dataset;
|
|
29900
|
+
const scope = resolveConnectedDataExportScope(dataset, continuation.scope);
|
|
29901
|
+
if (args.scope && JSON.stringify(resolveConnectedDataExportScope(dataset, args.scope)) !== JSON.stringify(scope)) {
|
|
29902
|
+
throw new ConnectedDataExportValidationError("scope conflicts with continuation.");
|
|
29903
|
+
}
|
|
29890
29904
|
return {
|
|
29891
29905
|
dataset,
|
|
29892
|
-
range: resolveConnectedDataRange({
|
|
29893
|
-
|
|
29906
|
+
range: resolveConnectedDataRange({
|
|
29907
|
+
from: continuation.from,
|
|
29908
|
+
to: continuation.to,
|
|
29909
|
+
now: args.now,
|
|
29910
|
+
maxRangeDays: dataset === "slack_channel_messages" ? null : 90
|
|
29911
|
+
}),
|
|
29912
|
+
cursor: continuation.cursor,
|
|
29913
|
+
...scope ? { scope } : {}
|
|
29914
|
+
};
|
|
29915
|
+
}
|
|
29916
|
+
function resolveConnectedDataExportScope(dataset, scope) {
|
|
29917
|
+
if (dataset !== "slack_channel_messages") {
|
|
29918
|
+
if (scope?.slack) {
|
|
29919
|
+
throw new ConnectedDataExportValidationError("Slack export scope requires dataset slack_channel_messages.");
|
|
29920
|
+
}
|
|
29921
|
+
return void 0;
|
|
29922
|
+
}
|
|
29923
|
+
const channelId = scope?.slack?.channelId?.trim();
|
|
29924
|
+
if (!channelId || !/^[A-Za-z0-9_-]{2,100}$/.test(channelId)) {
|
|
29925
|
+
throw new ConnectedDataExportValidationError("Slack channel export requires a valid channelId.");
|
|
29926
|
+
}
|
|
29927
|
+
return {
|
|
29928
|
+
slack: {
|
|
29929
|
+
channelId,
|
|
29930
|
+
includeThreads: scope?.slack?.includeThreads !== false
|
|
29931
|
+
}
|
|
29894
29932
|
};
|
|
29895
29933
|
}
|
|
29896
29934
|
function previewRecord(value) {
|
|
@@ -29919,7 +29957,14 @@ function previewRecord(value) {
|
|
|
29919
29957
|
"createdAt",
|
|
29920
29958
|
"updatedAt",
|
|
29921
29959
|
"hasTranscript",
|
|
29922
|
-
"contentTruncated"
|
|
29960
|
+
"contentTruncated",
|
|
29961
|
+
"channelId",
|
|
29962
|
+
"threadTs",
|
|
29963
|
+
"parentMessageTs",
|
|
29964
|
+
"occurredAt",
|
|
29965
|
+
"userId",
|
|
29966
|
+
"text",
|
|
29967
|
+
"replyCount"
|
|
29923
29968
|
]) {
|
|
29924
29969
|
if (record[key] === void 0) continue;
|
|
29925
29970
|
const item = record[key];
|
|
@@ -29962,7 +30007,8 @@ async function collectConnectedDataExport(args) {
|
|
|
29962
30007
|
dataset: args.dataset,
|
|
29963
30008
|
from: args.range.from,
|
|
29964
30009
|
to: args.range.to,
|
|
29965
|
-
pageSize: Math.min(25, maxItems - records.length),
|
|
30010
|
+
pageSize: Math.min(args.dataset === "slack_channel_messages" ? 200 : 25, maxItems - records.length),
|
|
30011
|
+
...args.scope ? { scope: args.scope } : {},
|
|
29966
30012
|
...cursor !== void 0 && cursor !== null ? { cursor } : {}
|
|
29967
30013
|
});
|
|
29968
30014
|
pages++;
|
|
@@ -30007,6 +30053,7 @@ async function collectConnectedDataExport(args) {
|
|
|
30007
30053
|
providerConfigKey,
|
|
30008
30054
|
dataset,
|
|
30009
30055
|
range: args.range,
|
|
30056
|
+
...args.scope ? { scope: args.scope } : {},
|
|
30010
30057
|
complete,
|
|
30011
30058
|
counts: { pages, listed, exported: records.length, failed },
|
|
30012
30059
|
untrustedContent: true,
|
|
@@ -30026,6 +30073,7 @@ ${lines.length ? `${lines.join("\n")}
|
|
|
30026
30073
|
providerConfigKey,
|
|
30027
30074
|
dataset,
|
|
30028
30075
|
range: args.range,
|
|
30076
|
+
...args.scope ? { scope: args.scope } : {},
|
|
30029
30077
|
counts: { pages, listed, exported: records.length, failed, bytes },
|
|
30030
30078
|
complete,
|
|
30031
30079
|
...!artifact ? { records } : {},
|
|
@@ -30035,7 +30083,8 @@ ${lines.length ? `${lines.join("\n")}
|
|
|
30035
30083
|
cursor: continuationCursor,
|
|
30036
30084
|
from: args.range.from,
|
|
30037
30085
|
to: args.range.to,
|
|
30038
|
-
dataset
|
|
30086
|
+
dataset,
|
|
30087
|
+
...args.scope ? { scope: args.scope } : {}
|
|
30039
30088
|
} : null,
|
|
30040
30089
|
warnings: [...warnings],
|
|
30041
30090
|
untrustedContent: true
|
|
@@ -30205,6 +30254,97 @@ async function getFreeCreditBreakdown(userId) {
|
|
|
30205
30254
|
|
|
30206
30255
|
// src/api/memory-db.ts
|
|
30207
30256
|
import { neon } from "@neondatabase/serverless";
|
|
30257
|
+
|
|
30258
|
+
// src/api/memory-universe.ts
|
|
30259
|
+
var SEP = "\n";
|
|
30260
|
+
var WIKILINK = /\[\[([^\][]+)\]\]/g;
|
|
30261
|
+
var HASH = /#[^]*$/;
|
|
30262
|
+
var CONNECTION_RE = /^Connection - (.+) - ([A-Za-z0-9_-]+)$/;
|
|
30263
|
+
function stripExt(value) {
|
|
30264
|
+
return value.replace(/\.(md|markdown|txt)$/i, "");
|
|
30265
|
+
}
|
|
30266
|
+
function vaultLabel(vault) {
|
|
30267
|
+
const provider = vault.match(CONNECTION_RE)?.[1];
|
|
30268
|
+
if (!provider) return vault;
|
|
30269
|
+
return provider.split(/[\s_-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
30270
|
+
}
|
|
30271
|
+
function noteTags(props) {
|
|
30272
|
+
const tags = props.tags;
|
|
30273
|
+
return Array.isArray(tags) ? tags.filter((tag) => typeof tag === "string") : [];
|
|
30274
|
+
}
|
|
30275
|
+
function wikiTargets(content) {
|
|
30276
|
+
const targets = [];
|
|
30277
|
+
for (const match of content.matchAll(WIKILINK)) {
|
|
30278
|
+
const target = match[1].split("|", 1)[0].replace(HASH, "").trim();
|
|
30279
|
+
if (target) targets.push(target);
|
|
30280
|
+
}
|
|
30281
|
+
return targets;
|
|
30282
|
+
}
|
|
30283
|
+
function buildResolver(notes) {
|
|
30284
|
+
const index = /* @__PURE__ */ new Map();
|
|
30285
|
+
const add = (key, note) => {
|
|
30286
|
+
const normalized = key.trim().toLowerCase();
|
|
30287
|
+
if (normalized && !index.has(normalized)) index.set(normalized, note);
|
|
30288
|
+
};
|
|
30289
|
+
for (const note of notes) add(`${note.vault}/${note.path}`, note);
|
|
30290
|
+
for (const note of notes) add(note.path, note);
|
|
30291
|
+
for (const note of notes) add(stripExt(note.path), note);
|
|
30292
|
+
for (const note of notes) if (note.title) add(note.title, note);
|
|
30293
|
+
for (const note of notes) {
|
|
30294
|
+
const base = note.path.split("/").pop() ?? note.path;
|
|
30295
|
+
add(base, note);
|
|
30296
|
+
add(stripExt(base), note);
|
|
30297
|
+
}
|
|
30298
|
+
return (target) => {
|
|
30299
|
+
const normalized = target.trim().toLowerCase();
|
|
30300
|
+
return index.get(normalized) ?? index.get(stripExt(normalized)) ?? null;
|
|
30301
|
+
};
|
|
30302
|
+
}
|
|
30303
|
+
function buildUniverseData(notes) {
|
|
30304
|
+
const resolver = buildResolver(notes);
|
|
30305
|
+
const nodes = [];
|
|
30306
|
+
const indexById = /* @__PURE__ */ new Map();
|
|
30307
|
+
const nodeFor = (id, label, group, ghost, extra) => {
|
|
30308
|
+
const existing = indexById.get(id);
|
|
30309
|
+
if (existing !== void 0) return existing;
|
|
30310
|
+
const index = nodes.length;
|
|
30311
|
+
indexById.set(id, index);
|
|
30312
|
+
nodes.push({ id, label, group, ghost, ...extra });
|
|
30313
|
+
return index;
|
|
30314
|
+
};
|
|
30315
|
+
for (const note of notes) {
|
|
30316
|
+
nodeFor(
|
|
30317
|
+
`${note.vault}${SEP}${note.path}`,
|
|
30318
|
+
note.title.trim() || note.path.split("/").pop() || note.path,
|
|
30319
|
+
vaultLabel(note.vault),
|
|
30320
|
+
false,
|
|
30321
|
+
{ sub: note.path, tags: noteTags(note.props), updatedAt: note.updatedAt }
|
|
30322
|
+
);
|
|
30323
|
+
}
|
|
30324
|
+
const links = [];
|
|
30325
|
+
const seenEdges = /* @__PURE__ */ new Set();
|
|
30326
|
+
for (const note of notes) {
|
|
30327
|
+
const from = indexById.get(`${note.vault}${SEP}${note.path}`);
|
|
30328
|
+
if (from === void 0) continue;
|
|
30329
|
+
for (const target of wikiTargets(note.content)) {
|
|
30330
|
+
const resolved = resolver(target);
|
|
30331
|
+
const to = resolved ? nodeFor(
|
|
30332
|
+
`${resolved.vault}${SEP}${resolved.path}`,
|
|
30333
|
+
resolved.title.trim() || resolved.path.split("/").pop() || resolved.path,
|
|
30334
|
+
vaultLabel(resolved.vault),
|
|
30335
|
+
false
|
|
30336
|
+
) : nodeFor(`ghost:${target.toLowerCase()}`, target, "", true);
|
|
30337
|
+
if (to === from) continue;
|
|
30338
|
+
const edgeKey = from < to ? `${from}:${to}` : `${to}:${from}`;
|
|
30339
|
+
if (seenEdges.has(edgeKey)) continue;
|
|
30340
|
+
seenEdges.add(edgeKey);
|
|
30341
|
+
links.push({ s: from, t: to });
|
|
30342
|
+
}
|
|
30343
|
+
}
|
|
30344
|
+
return { nodes, links, total: notes.length, built: notes.length, failed: 0 };
|
|
30345
|
+
}
|
|
30346
|
+
|
|
30347
|
+
// src/api/memory-db.ts
|
|
30208
30348
|
var _sql = null;
|
|
30209
30349
|
function sql() {
|
|
30210
30350
|
if (_sql) return _sql;
|
|
@@ -30349,6 +30489,35 @@ async function dbNote(identity, vault, path5) {
|
|
|
30349
30489
|
capturedAt: r.captured_at
|
|
30350
30490
|
};
|
|
30351
30491
|
}
|
|
30492
|
+
async function dbUniverse(identity, includeConnections = false) {
|
|
30493
|
+
const entitled = await entitledVaultRows(identity);
|
|
30494
|
+
const selected = entitled.filter(
|
|
30495
|
+
(entry) => includeConnections || !/^Connection - (.+) - ([A-Za-z0-9_-]+)$/.test(entry.vault)
|
|
30496
|
+
);
|
|
30497
|
+
if (selected.length === 0) return buildUniverseData([]);
|
|
30498
|
+
const logicalByPhysical = new Map(selected.map((entry) => [entry.physical, entry.vault]));
|
|
30499
|
+
const physicals = [...logicalByPhysical.keys()];
|
|
30500
|
+
const rows = await sql().query(
|
|
30501
|
+
`SELECT vault, path, title, content, updated_at, props
|
|
30502
|
+
FROM mem_notes
|
|
30503
|
+
WHERE vault = ANY($1)
|
|
30504
|
+
ORDER BY updated_at DESC`,
|
|
30505
|
+
[physicals]
|
|
30506
|
+
);
|
|
30507
|
+
const notes = rows.flatMap((row) => {
|
|
30508
|
+
const vault = logicalByPhysical.get(row.vault);
|
|
30509
|
+
if (!vault) return [];
|
|
30510
|
+
return [{
|
|
30511
|
+
vault,
|
|
30512
|
+
path: row.path,
|
|
30513
|
+
title: row.title,
|
|
30514
|
+
content: row.content,
|
|
30515
|
+
updatedAt: row.updated_at,
|
|
30516
|
+
props: row.props && typeof row.props === "object" ? row.props : {}
|
|
30517
|
+
}];
|
|
30518
|
+
});
|
|
30519
|
+
return buildUniverseData(notes);
|
|
30520
|
+
}
|
|
30352
30521
|
var FREE_COST_CAP_USD = 1;
|
|
30353
30522
|
async function dbUsage(identity, plan) {
|
|
30354
30523
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -30380,6 +30549,377 @@ async function dbUsage(identity, plan) {
|
|
|
30380
30549
|
// src/api/nango-control.ts
|
|
30381
30550
|
import { createHash as createHash14, randomUUID as randomUUID17 } from "crypto";
|
|
30382
30551
|
|
|
30552
|
+
// src/api/slack-archive-analysis.ts
|
|
30553
|
+
function isRecord2(value) {
|
|
30554
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
30555
|
+
}
|
|
30556
|
+
function cleanString(value) {
|
|
30557
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
30558
|
+
}
|
|
30559
|
+
function slackWeekStart(value) {
|
|
30560
|
+
if (!value) return null;
|
|
30561
|
+
const date = new Date(value);
|
|
30562
|
+
if (!Number.isFinite(date.getTime())) return null;
|
|
30563
|
+
const daysSinceMonday = (date.getUTCDay() + 6) % 7;
|
|
30564
|
+
date.setUTCDate(date.getUTCDate() - daysSinceMonday);
|
|
30565
|
+
return date.toISOString().slice(0, 10);
|
|
30566
|
+
}
|
|
30567
|
+
function normalizedUrl(value) {
|
|
30568
|
+
const candidate = cleanString(value)?.split("|", 1)[0]?.replace(/[),.;!?]+$/, "");
|
|
30569
|
+
if (!candidate || !/^https?:\/\//i.test(candidate)) return null;
|
|
30570
|
+
try {
|
|
30571
|
+
return new URL(candidate).toString();
|
|
30572
|
+
} catch {
|
|
30573
|
+
return null;
|
|
30574
|
+
}
|
|
30575
|
+
}
|
|
30576
|
+
function deriveSlackMessageIndexFields(args) {
|
|
30577
|
+
const fileNames = args.files.flatMap((file) => {
|
|
30578
|
+
if (!isRecord2(file)) return [];
|
|
30579
|
+
const name = cleanString(file.name) ?? cleanString(file.title) ?? cleanString(file.id);
|
|
30580
|
+
return name ? [name] : [];
|
|
30581
|
+
});
|
|
30582
|
+
const urls = /* @__PURE__ */ new Set();
|
|
30583
|
+
const text2 = args.text ?? "";
|
|
30584
|
+
for (const match of text2.matchAll(/<(https?:\/\/[^>|]+)(?:\|[^>]*)?>/gi)) {
|
|
30585
|
+
const url = normalizedUrl(match[1]);
|
|
30586
|
+
if (url) urls.add(url);
|
|
30587
|
+
}
|
|
30588
|
+
for (const match of text2.matchAll(/https?:\/\/[^\s<>()]+/gi)) {
|
|
30589
|
+
const url = normalizedUrl(match[0]);
|
|
30590
|
+
if (url) urls.add(url);
|
|
30591
|
+
}
|
|
30592
|
+
const attachments = Array.isArray(args.providerData.attachments) ? args.providerData.attachments : [];
|
|
30593
|
+
for (const attachment of attachments) {
|
|
30594
|
+
if (!isRecord2(attachment)) continue;
|
|
30595
|
+
for (const key of ["url", "title_link", "from_url", "service_url"]) {
|
|
30596
|
+
const url = normalizedUrl(attachment[key]);
|
|
30597
|
+
if (url) urls.add(url);
|
|
30598
|
+
}
|
|
30599
|
+
}
|
|
30600
|
+
const profile = isRecord2(args.providerData.user_profile) ? args.providerData.user_profile : isRecord2(args.providerData.profile) ? args.providerData.profile : null;
|
|
30601
|
+
const authorName = cleanString(profile?.display_name) ?? cleanString(profile?.real_name) ?? cleanString(args.providerData.username) ?? cleanString(args.providerData.user_name);
|
|
30602
|
+
return {
|
|
30603
|
+
weekStart: slackWeekStart(args.occurredAt),
|
|
30604
|
+
authorName,
|
|
30605
|
+
fileCount: args.files.length,
|
|
30606
|
+
hasFiles: args.files.length > 0,
|
|
30607
|
+
fileNames,
|
|
30608
|
+
linkCount: urls.size,
|
|
30609
|
+
links: [...urls]
|
|
30610
|
+
};
|
|
30611
|
+
}
|
|
30612
|
+
|
|
30613
|
+
// src/api/slack-connected-data-export.ts
|
|
30614
|
+
var CURSOR_PREFIX = "slack1.";
|
|
30615
|
+
var MAX_CURSOR_BYTES = 64 * 1024;
|
|
30616
|
+
var MAX_PENDING_THREADS = 200;
|
|
30617
|
+
var THREAD_CONCURRENCY = 3;
|
|
30618
|
+
var MAX_SLACK_RETRIES = 3;
|
|
30619
|
+
function isRecord3(value) {
|
|
30620
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
30621
|
+
}
|
|
30622
|
+
function cleanString2(value, maxLength = 1e4) {
|
|
30623
|
+
return typeof value === "string" && value.trim() ? value.trim().slice(0, maxLength) : null;
|
|
30624
|
+
}
|
|
30625
|
+
function unwrapToolResult(value) {
|
|
30626
|
+
if (!isRecord3(value)) return value;
|
|
30627
|
+
if (isRecord3(value.structuredContent)) return value.structuredContent;
|
|
30628
|
+
if (Array.isArray(value.content)) {
|
|
30629
|
+
const textPart = value.content.find((part) => isRecord3(part) && part.type === "text" && typeof part.text === "string");
|
|
30630
|
+
if (isRecord3(textPart) && typeof textPart.text === "string") {
|
|
30631
|
+
try {
|
|
30632
|
+
return unwrapToolResult(JSON.parse(textPart.text));
|
|
30633
|
+
} catch {
|
|
30634
|
+
return textPart.text;
|
|
30635
|
+
}
|
|
30636
|
+
}
|
|
30637
|
+
}
|
|
30638
|
+
if (isRecord3(value.result)) return unwrapToolResult(value.result);
|
|
30639
|
+
if (isRecord3(value.data)) return unwrapToolResult(value.data);
|
|
30640
|
+
return value;
|
|
30641
|
+
}
|
|
30642
|
+
function messagesFromToolResult(value) {
|
|
30643
|
+
const root = unwrapToolResult(value);
|
|
30644
|
+
if (Array.isArray(root)) return root.filter(isRecord3);
|
|
30645
|
+
if (!isRecord3(root)) return [];
|
|
30646
|
+
for (const key of ["messages", "replies", "items", "data"]) {
|
|
30647
|
+
if (Array.isArray(root[key])) return root[key].filter(isRecord3);
|
|
30648
|
+
}
|
|
30649
|
+
return [];
|
|
30650
|
+
}
|
|
30651
|
+
function nextCursorFromToolResult(value) {
|
|
30652
|
+
const root = unwrapToolResult(value);
|
|
30653
|
+
if (!isRecord3(root)) return null;
|
|
30654
|
+
const metadata = isRecord3(root.response_metadata) ? root.response_metadata : isRecord3(root.responseMetadata) ? root.responseMetadata : isRecord3(root.metadata) ? root.metadata : null;
|
|
30655
|
+
return cleanString2(
|
|
30656
|
+
root.next_cursor ?? root.nextCursor ?? metadata?.next_cursor ?? metadata?.nextCursor,
|
|
30657
|
+
1e4
|
|
30658
|
+
);
|
|
30659
|
+
}
|
|
30660
|
+
function slackTimestampToIso(value) {
|
|
30661
|
+
const timestamp = cleanString2(value, 64);
|
|
30662
|
+
if (!timestamp || !/^\d{1,20}(?:\.\d{1,9})?$/.test(timestamp)) return null;
|
|
30663
|
+
const milliseconds = Number(timestamp) * 1e3;
|
|
30664
|
+
if (!Number.isFinite(milliseconds)) return null;
|
|
30665
|
+
try {
|
|
30666
|
+
return new Date(milliseconds).toISOString();
|
|
30667
|
+
} catch {
|
|
30668
|
+
return null;
|
|
30669
|
+
}
|
|
30670
|
+
}
|
|
30671
|
+
function normalizeSlackMessage(row, channelId, parentMessageTs) {
|
|
30672
|
+
const timestamp = cleanString2(row.ts, 64);
|
|
30673
|
+
const replyCount = typeof row.reply_count === "number" && Number.isFinite(row.reply_count) ? Math.max(0, Math.floor(row.reply_count)) : 0;
|
|
30674
|
+
const rowThreadTs = cleanString2(row.thread_ts, 64);
|
|
30675
|
+
const inferredParentMessageTs = parentMessageTs ?? (rowThreadTs && rowThreadTs !== timestamp ? rowThreadTs : null);
|
|
30676
|
+
const threadTs = inferredParentMessageTs ?? rowThreadTs ?? (replyCount > 0 ? timestamp : null);
|
|
30677
|
+
const occurredAt = slackTimestampToIso(timestamp);
|
|
30678
|
+
const files = Array.isArray(row.files) ? row.files : [];
|
|
30679
|
+
const text2 = typeof row.text === "string" ? row.text : null;
|
|
30680
|
+
return {
|
|
30681
|
+
recordType: inferredParentMessageTs ? "slack_thread_reply" : "slack_message",
|
|
30682
|
+
dataset: "slack_channel_messages",
|
|
30683
|
+
id: timestamp ? `${channelId}:${timestamp}` : `${channelId}:unknown`,
|
|
30684
|
+
providerRecordId: timestamp,
|
|
30685
|
+
channelId,
|
|
30686
|
+
threadTs,
|
|
30687
|
+
parentMessageTs: inferredParentMessageTs,
|
|
30688
|
+
isThreadReply: inferredParentMessageTs !== null,
|
|
30689
|
+
occurredAt,
|
|
30690
|
+
userId: cleanString2(row.user, 100) ?? cleanString2(row.bot_id, 100),
|
|
30691
|
+
text: text2,
|
|
30692
|
+
subtype: cleanString2(row.subtype, 100),
|
|
30693
|
+
replyCount,
|
|
30694
|
+
files,
|
|
30695
|
+
reactions: Array.isArray(row.reactions) ? row.reactions : [],
|
|
30696
|
+
providerData: row,
|
|
30697
|
+
...deriveSlackMessageIndexFields({
|
|
30698
|
+
occurredAt,
|
|
30699
|
+
text: text2,
|
|
30700
|
+
files,
|
|
30701
|
+
providerData: row
|
|
30702
|
+
})
|
|
30703
|
+
};
|
|
30704
|
+
}
|
|
30705
|
+
function encodeCursor(state) {
|
|
30706
|
+
const encoded = `${CURSOR_PREFIX}${Buffer.from(JSON.stringify(state)).toString("base64url")}`;
|
|
30707
|
+
if (Buffer.byteLength(encoded, "utf8") > MAX_CURSOR_BYTES) {
|
|
30708
|
+
throw new Error("slack_export_cursor_too_large");
|
|
30709
|
+
}
|
|
30710
|
+
return encoded;
|
|
30711
|
+
}
|
|
30712
|
+
function validProviderCursor(value) {
|
|
30713
|
+
return value === null || typeof value === "string" && value.length > 0 && value.length <= 1e4;
|
|
30714
|
+
}
|
|
30715
|
+
function decodeCursor(cursor, channelId, includeThreads) {
|
|
30716
|
+
if (!cursor) {
|
|
30717
|
+
return { version: 1, phase: "history", channelId, includeThreads, historyCursor: null };
|
|
30718
|
+
}
|
|
30719
|
+
if (!cursor.startsWith(CURSOR_PREFIX) || Buffer.byteLength(cursor, "utf8") > MAX_CURSOR_BYTES) {
|
|
30720
|
+
throw new Error("slack_export_cursor_invalid");
|
|
30721
|
+
}
|
|
30722
|
+
let parsed;
|
|
30723
|
+
try {
|
|
30724
|
+
parsed = JSON.parse(Buffer.from(cursor.slice(CURSOR_PREFIX.length), "base64url").toString("utf8"));
|
|
30725
|
+
} catch {
|
|
30726
|
+
throw new Error("slack_export_cursor_invalid");
|
|
30727
|
+
}
|
|
30728
|
+
if (!isRecord3(parsed) || parsed.version !== 1 || parsed.channelId !== channelId || parsed.includeThreads !== includeThreads || !validProviderCursor(parsed.historyCursor)) {
|
|
30729
|
+
throw new Error("slack_export_cursor_invalid");
|
|
30730
|
+
}
|
|
30731
|
+
if (parsed.phase === "history") {
|
|
30732
|
+
return {
|
|
30733
|
+
version: 1,
|
|
30734
|
+
phase: "history",
|
|
30735
|
+
channelId,
|
|
30736
|
+
includeThreads,
|
|
30737
|
+
historyCursor: parsed.historyCursor
|
|
30738
|
+
};
|
|
30739
|
+
}
|
|
30740
|
+
if (parsed.phase !== "threads" || includeThreads !== true || !Array.isArray(parsed.historyMessageTs) || !Array.isArray(parsed.threads) || parsed.threads.length < 1 || parsed.threads.length > MAX_PENDING_THREADS) {
|
|
30741
|
+
throw new Error("slack_export_cursor_invalid");
|
|
30742
|
+
}
|
|
30743
|
+
const historyMessageTs = parsed.historyMessageTs.flatMap((value) => {
|
|
30744
|
+
const timestamp = cleanString2(value, 64);
|
|
30745
|
+
return timestamp && /^\d{1,20}(?:\.\d{1,9})?$/.test(timestamp) ? [timestamp] : [];
|
|
30746
|
+
});
|
|
30747
|
+
if (historyMessageTs.length !== parsed.historyMessageTs.length || historyMessageTs.length > MAX_PENDING_THREADS) {
|
|
30748
|
+
throw new Error("slack_export_cursor_invalid");
|
|
30749
|
+
}
|
|
30750
|
+
const threads = parsed.threads.flatMap((value) => {
|
|
30751
|
+
if (!isRecord3(value)) return [];
|
|
30752
|
+
const threadTs = cleanString2(value.threadTs, 64);
|
|
30753
|
+
if (!threadTs || !/^\d{1,20}(?:\.\d{1,9})?$/.test(threadTs) || !validProviderCursor(value.cursor)) return [];
|
|
30754
|
+
return [{ threadTs, cursor: value.cursor }];
|
|
30755
|
+
});
|
|
30756
|
+
if (threads.length !== parsed.threads.length) throw new Error("slack_export_cursor_invalid");
|
|
30757
|
+
return {
|
|
30758
|
+
version: 1,
|
|
30759
|
+
phase: "threads",
|
|
30760
|
+
channelId,
|
|
30761
|
+
includeThreads: true,
|
|
30762
|
+
historyCursor: parsed.historyCursor,
|
|
30763
|
+
historyMessageTs,
|
|
30764
|
+
threads
|
|
30765
|
+
};
|
|
30766
|
+
}
|
|
30767
|
+
function slackBoundaryTimestamp(iso, offsetMicroseconds) {
|
|
30768
|
+
const milliseconds = new Date(iso).getTime();
|
|
30769
|
+
if (!Number.isFinite(milliseconds)) throw new Error("slack_export_range_invalid");
|
|
30770
|
+
return Math.max(0, milliseconds / 1e3 + offsetMicroseconds / 1e6).toFixed(6);
|
|
30771
|
+
}
|
|
30772
|
+
function errorText(error) {
|
|
30773
|
+
if (error instanceof Error) return `${error.name} ${error.message}`;
|
|
30774
|
+
if (typeof error === "string") return error;
|
|
30775
|
+
try {
|
|
30776
|
+
return JSON.stringify(error);
|
|
30777
|
+
} catch {
|
|
30778
|
+
return "";
|
|
30779
|
+
}
|
|
30780
|
+
}
|
|
30781
|
+
function retryAfterMilliseconds(error, attempt) {
|
|
30782
|
+
const text2 = errorText(error);
|
|
30783
|
+
if (!/429|rate.?limit|resource_exhausted/i.test(text2)) return null;
|
|
30784
|
+
if (isRecord3(error) && typeof error.retryAfterMs === "number" && Number.isFinite(error.retryAfterMs)) {
|
|
30785
|
+
return Math.min(Math.max(error.retryAfterMs, 1e3), 6e4);
|
|
30786
|
+
}
|
|
30787
|
+
const explicit = text2.match(/retry[_ -]?after[^0-9]{0,20}(\d{1,4})/i);
|
|
30788
|
+
const seconds = explicit ? Number(explicit[1]) : attempt;
|
|
30789
|
+
return Math.min(Math.max(seconds, 1) * 1e3, 6e4);
|
|
30790
|
+
}
|
|
30791
|
+
async function callSlackToolWithRetry(dependencies, args) {
|
|
30792
|
+
for (let attempt = 1; attempt <= MAX_SLACK_RETRIES; attempt += 1) {
|
|
30793
|
+
try {
|
|
30794
|
+
return await dependencies.callTool(args);
|
|
30795
|
+
} catch (error) {
|
|
30796
|
+
const delay = retryAfterMilliseconds(error, attempt);
|
|
30797
|
+
if (delay === null || attempt === MAX_SLACK_RETRIES) throw error;
|
|
30798
|
+
await (dependencies.sleep ?? ((milliseconds) => new Promise((resolve2) => setTimeout(resolve2, milliseconds))))(delay);
|
|
30799
|
+
}
|
|
30800
|
+
}
|
|
30801
|
+
throw new Error("slack_export_retry_exhausted");
|
|
30802
|
+
}
|
|
30803
|
+
function nextAfterThreads(state) {
|
|
30804
|
+
if (state.threads.length > 0) return { nextCursor: encodeCursor(state), complete: false };
|
|
30805
|
+
if (state.historyCursor) {
|
|
30806
|
+
return {
|
|
30807
|
+
nextCursor: encodeCursor({
|
|
30808
|
+
version: 1,
|
|
30809
|
+
phase: "history",
|
|
30810
|
+
channelId: state.channelId,
|
|
30811
|
+
includeThreads: true,
|
|
30812
|
+
historyCursor: state.historyCursor
|
|
30813
|
+
}),
|
|
30814
|
+
complete: false
|
|
30815
|
+
};
|
|
30816
|
+
}
|
|
30817
|
+
return { nextCursor: null, complete: true };
|
|
30818
|
+
}
|
|
30819
|
+
async function exportSlackChannelPage(input, dependencies) {
|
|
30820
|
+
const channelId = input.scope?.slack?.channelId?.trim();
|
|
30821
|
+
if (!channelId) throw new Error("slack_export_channel_required");
|
|
30822
|
+
const includeThreads = input.scope?.slack?.includeThreads !== false;
|
|
30823
|
+
const state = decodeCursor(input.cursor, channelId, includeThreads);
|
|
30824
|
+
const pageSize = Math.max(1, Math.min(200, Math.floor(input.pageSize)));
|
|
30825
|
+
if (state.phase === "history") {
|
|
30826
|
+
const oldest = slackBoundaryTimestamp(input.from, -1);
|
|
30827
|
+
const latest = slackBoundaryTimestamp(input.to, 0);
|
|
30828
|
+
const result = await callSlackToolWithRetry(dependencies, {
|
|
30829
|
+
tool: "get-conversation-history",
|
|
30830
|
+
input: {
|
|
30831
|
+
channel_id: channelId,
|
|
30832
|
+
limit: pageSize,
|
|
30833
|
+
oldest,
|
|
30834
|
+
latest,
|
|
30835
|
+
inclusive: false,
|
|
30836
|
+
...state.historyCursor ? { cursor: state.historyCursor } : {}
|
|
30837
|
+
},
|
|
30838
|
+
requestId: `slack-export:${input.connectionId}:${channelId}:history:${oldest}:${latest}:${pageSize}:${state.historyCursor ?? "start"}`
|
|
30839
|
+
});
|
|
30840
|
+
const messages = messagesFromToolResult(result).slice(0, pageSize);
|
|
30841
|
+
const historyCursor = nextCursorFromToolResult(result);
|
|
30842
|
+
const records2 = messages.map((message) => normalizeSlackMessage(message, channelId, null));
|
|
30843
|
+
const threadIds = includeThreads ? [...new Set(messages.flatMap((message) => {
|
|
30844
|
+
const replyCount = typeof message.reply_count === "number" ? message.reply_count : 0;
|
|
30845
|
+
const timestamp = cleanString2(message.ts, 64);
|
|
30846
|
+
return replyCount > 0 && timestamp ? [timestamp] : [];
|
|
30847
|
+
}))].slice(0, MAX_PENDING_THREADS) : [];
|
|
30848
|
+
if (threadIds.length > 0) {
|
|
30849
|
+
return {
|
|
30850
|
+
providerConfigKey: "slack",
|
|
30851
|
+
dataset: "slack_channel_messages",
|
|
30852
|
+
records: records2,
|
|
30853
|
+
nextCursor: encodeCursor({
|
|
30854
|
+
version: 1,
|
|
30855
|
+
phase: "threads",
|
|
30856
|
+
channelId,
|
|
30857
|
+
includeThreads: true,
|
|
30858
|
+
historyCursor,
|
|
30859
|
+
historyMessageTs: messages.flatMap((message) => cleanString2(message.ts, 64) ?? []),
|
|
30860
|
+
threads: threadIds.map((threadTs) => ({ threadTs, cursor: null }))
|
|
30861
|
+
}),
|
|
30862
|
+
complete: false,
|
|
30863
|
+
counts: { listed: messages.length, exported: records2.length, failed: 0 },
|
|
30864
|
+
warnings: threadIds.length === MAX_PENDING_THREADS ? ["A history page reached the per-page threaded-parent safety limit; narrow the export range if threads are missing."] : [],
|
|
30865
|
+
untrustedContent: true
|
|
30866
|
+
};
|
|
30867
|
+
}
|
|
30868
|
+
return {
|
|
30869
|
+
providerConfigKey: "slack",
|
|
30870
|
+
dataset: "slack_channel_messages",
|
|
30871
|
+
records: records2,
|
|
30872
|
+
nextCursor: historyCursor ? encodeCursor({ ...state, historyCursor }) : null,
|
|
30873
|
+
complete: historyCursor === null,
|
|
30874
|
+
counts: { listed: messages.length, exported: records2.length, failed: 0 },
|
|
30875
|
+
warnings: [],
|
|
30876
|
+
untrustedContent: true
|
|
30877
|
+
};
|
|
30878
|
+
}
|
|
30879
|
+
const batchSize = Math.min(THREAD_CONCURRENCY, state.threads.length, pageSize);
|
|
30880
|
+
const batch = state.threads.slice(0, batchSize);
|
|
30881
|
+
const perThreadLimit = Math.max(1, Math.min(100, Math.floor(pageSize / batchSize)));
|
|
30882
|
+
const results = await Promise.all(batch.map((thread) => callSlackToolWithRetry(dependencies, {
|
|
30883
|
+
tool: "get-thread-replies",
|
|
30884
|
+
input: {
|
|
30885
|
+
channel_id: channelId,
|
|
30886
|
+
thread_ts: thread.threadTs,
|
|
30887
|
+
limit: perThreadLimit,
|
|
30888
|
+
...thread.cursor ? { cursor: thread.cursor } : {}
|
|
30889
|
+
},
|
|
30890
|
+
requestId: `slack-export:${input.connectionId}:${channelId}:thread:${thread.threadTs}:${perThreadLimit}:${thread.cursor ?? "start"}`
|
|
30891
|
+
})));
|
|
30892
|
+
const records = [];
|
|
30893
|
+
const stillPending = [];
|
|
30894
|
+
let listed = 0;
|
|
30895
|
+
for (let index = 0; index < results.length; index += 1) {
|
|
30896
|
+
const thread = batch[index];
|
|
30897
|
+
const messages = messagesFromToolResult(results[index]);
|
|
30898
|
+
listed += messages.length;
|
|
30899
|
+
records.push(...messages.filter((message) => {
|
|
30900
|
+
const timestamp = cleanString2(message.ts, 64);
|
|
30901
|
+
return timestamp !== thread.threadTs && !state.historyMessageTs.includes(timestamp ?? "");
|
|
30902
|
+
}).map((message) => normalizeSlackMessage(message, channelId, thread.threadTs)));
|
|
30903
|
+
const cursor = nextCursorFromToolResult(results[index]);
|
|
30904
|
+
if (cursor) stillPending.push({ threadTs: thread.threadTs, cursor });
|
|
30905
|
+
}
|
|
30906
|
+
const nextState = {
|
|
30907
|
+
...state,
|
|
30908
|
+
threads: [...stillPending, ...state.threads.slice(batchSize)]
|
|
30909
|
+
};
|
|
30910
|
+
const next = nextAfterThreads(nextState);
|
|
30911
|
+
return {
|
|
30912
|
+
providerConfigKey: "slack",
|
|
30913
|
+
dataset: "slack_channel_messages",
|
|
30914
|
+
records,
|
|
30915
|
+
nextCursor: next.nextCursor,
|
|
30916
|
+
complete: next.complete,
|
|
30917
|
+
counts: { listed, exported: records.length, failed: 0 },
|
|
30918
|
+
warnings: [],
|
|
30919
|
+
untrustedContent: true
|
|
30920
|
+
};
|
|
30921
|
+
}
|
|
30922
|
+
|
|
30383
30923
|
// src/api/main-nango-transport.ts
|
|
30384
30924
|
import { createHash as createHash13, randomUUID as randomUUID16 } from "crypto";
|
|
30385
30925
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -30656,12 +31196,14 @@ var MainNangoTransportError = class extends Error {
|
|
|
30656
31196
|
status;
|
|
30657
31197
|
code;
|
|
30658
31198
|
retryable;
|
|
30659
|
-
|
|
31199
|
+
retryAfterMs;
|
|
31200
|
+
constructor(message, status, code, retryable = status === 429 || status >= 500, retryAfterMs = null) {
|
|
30660
31201
|
super(message);
|
|
30661
31202
|
this.name = "MainNangoTransportError";
|
|
30662
31203
|
this.status = status;
|
|
30663
31204
|
this.code = code;
|
|
30664
31205
|
this.retryable = retryable;
|
|
31206
|
+
this.retryAfterMs = retryAfterMs;
|
|
30665
31207
|
}
|
|
30666
31208
|
};
|
|
30667
31209
|
function nangoClientErrorText(error) {
|
|
@@ -30673,6 +31215,13 @@ function nangoClientErrorText(error) {
|
|
|
30673
31215
|
return "";
|
|
30674
31216
|
}
|
|
30675
31217
|
}
|
|
31218
|
+
function retryAfterMsFromText(value) {
|
|
31219
|
+
const milliseconds = value.match(/retry[_ -]?after[_ -]?ms[^0-9]{0,20}(\d{1,7})/i);
|
|
31220
|
+
if (milliseconds) return Math.min(Math.max(Number(milliseconds[1]), 1e3), 6e4);
|
|
31221
|
+
const seconds = value.match(/retry[_ -]?after[^0-9]{0,20}(\d{1,4})/i);
|
|
31222
|
+
if (seconds) return Math.min(Math.max(Number(seconds[1]), 1) * 1e3, 6e4);
|
|
31223
|
+
return null;
|
|
31224
|
+
}
|
|
30676
31225
|
function normalizeNangoClientError(error) {
|
|
30677
31226
|
if (error instanceof MainNangoTransportError) return error;
|
|
30678
31227
|
const detail = nangoClientErrorText(error);
|
|
@@ -30693,7 +31242,13 @@ function normalizeNangoClientError(error) {
|
|
|
30693
31242
|
);
|
|
30694
31243
|
}
|
|
30695
31244
|
if (/RESOURCE_EXHAUSTED|rate.?limit|status["']?\s*:\s*429/i.test(detail)) {
|
|
30696
|
-
return new MainNangoTransportError(
|
|
31245
|
+
return new MainNangoTransportError(
|
|
31246
|
+
"The connected provider is temporarily rate limited.",
|
|
31247
|
+
429,
|
|
31248
|
+
"upstream_rate_limited",
|
|
31249
|
+
true,
|
|
31250
|
+
retryAfterMsFromText(detail)
|
|
31251
|
+
);
|
|
30697
31252
|
}
|
|
30698
31253
|
if (/UNAUTHENTICATED|invalid_grant|status["']?\s*:\s*401/i.test(detail)) {
|
|
30699
31254
|
return new MainNangoTransportError("The service connection requires reconnection.", 409, "connection_inactive", false);
|
|
@@ -30758,7 +31313,9 @@ async function nangoRequest(path5, options = {}) {
|
|
|
30758
31313
|
throw new MainNangoTransportError(
|
|
30759
31314
|
status === 429 ? "The connected provider is temporarily rate limited." : "Nango request failed.",
|
|
30760
31315
|
status,
|
|
30761
|
-
status === 429 ? "upstream_rate_limited" : status === 404 ? "connection_not_found" : "connection_transport_unavailable"
|
|
31316
|
+
status === 429 ? "upstream_rate_limited" : status === 404 ? "connection_not_found" : "connection_transport_unavailable",
|
|
31317
|
+
status === 429 || status >= 500,
|
|
31318
|
+
status === 429 ? retryAfterMsFromText(`retry-after: ${response.headers.get("retry-after") ?? ""}`) : null
|
|
30762
31319
|
);
|
|
30763
31320
|
}
|
|
30764
31321
|
return payload;
|
|
@@ -31206,37 +31763,37 @@ function controlSecret() {
|
|
|
31206
31763
|
if (!secret2) throw new NangoControlError("Scheduled service connections are not configured.", 503);
|
|
31207
31764
|
return secret2;
|
|
31208
31765
|
}
|
|
31209
|
-
function
|
|
31766
|
+
function isRecord4(value) {
|
|
31210
31767
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
31211
31768
|
}
|
|
31212
31769
|
function unwrapData(value) {
|
|
31213
|
-
if (
|
|
31770
|
+
if (isRecord4(value) && isRecord4(value.data)) return value.data;
|
|
31214
31771
|
return value;
|
|
31215
31772
|
}
|
|
31216
|
-
function
|
|
31773
|
+
function cleanString3(value, max = 300) {
|
|
31217
31774
|
if (typeof value !== "string") return null;
|
|
31218
31775
|
const trimmed = value.trim();
|
|
31219
31776
|
return trimmed ? trimmed.slice(0, max) : null;
|
|
31220
31777
|
}
|
|
31221
31778
|
function firstString(record, keys, max = 300) {
|
|
31222
31779
|
for (const key of keys) {
|
|
31223
|
-
const value =
|
|
31780
|
+
const value = cleanString3(record[key], max);
|
|
31224
31781
|
if (value) return value;
|
|
31225
31782
|
}
|
|
31226
31783
|
return null;
|
|
31227
31784
|
}
|
|
31228
31785
|
function cleanTools(value) {
|
|
31229
31786
|
if (!Array.isArray(value)) return [];
|
|
31230
|
-
const tools = value.map((tool) =>
|
|
31787
|
+
const tools = value.map((tool) => cleanString3(tool, 200)).filter((tool) => !!tool);
|
|
31231
31788
|
return [...new Set(tools)].slice(0, 200);
|
|
31232
31789
|
}
|
|
31233
31790
|
function cleanStringArray(value, maxItems = 20, maxLength = 100) {
|
|
31234
31791
|
if (!Array.isArray(value)) return [];
|
|
31235
|
-
const items = value.map((item) =>
|
|
31792
|
+
const items = value.map((item) => cleanString3(item, maxLength)).filter((item) => !!item);
|
|
31236
31793
|
return [...new Set(items)].slice(0, maxItems);
|
|
31237
31794
|
}
|
|
31238
31795
|
function cleanHttpsUrl(value) {
|
|
31239
|
-
const candidate =
|
|
31796
|
+
const candidate = cleanString3(value, 2e3);
|
|
31240
31797
|
if (!candidate) return null;
|
|
31241
31798
|
try {
|
|
31242
31799
|
const url = new URL(candidate);
|
|
@@ -31248,7 +31805,7 @@ function cleanHttpsUrl(value) {
|
|
|
31248
31805
|
function arrayFromPayload(value, keys) {
|
|
31249
31806
|
const unwrapped = unwrapData(value);
|
|
31250
31807
|
if (Array.isArray(unwrapped)) return unwrapped;
|
|
31251
|
-
if (!
|
|
31808
|
+
if (!isRecord4(unwrapped)) return [];
|
|
31252
31809
|
for (const key of keys) {
|
|
31253
31810
|
if (Array.isArray(unwrapped[key])) return unwrapped[key];
|
|
31254
31811
|
}
|
|
@@ -31329,7 +31886,7 @@ function defaultControlErrorMessage(status) {
|
|
|
31329
31886
|
function safeControlErrorPayload(body, responseStatus) {
|
|
31330
31887
|
const status = controlErrorStatus(responseStatus);
|
|
31331
31888
|
const unwrapped = unwrapData(body);
|
|
31332
|
-
const record =
|
|
31889
|
+
const record = isRecord4(unwrapped) ? unwrapped : isRecord4(body) ? body : null;
|
|
31333
31890
|
const candidateCode = record ? firstString(record, ["code", "errorCode", "error_code"], 100) : null;
|
|
31334
31891
|
const normalizedCandidateCode = candidateCode ? CONTROL_ERROR_CODE_ALIASES.get(candidateCode) ?? candidateCode : null;
|
|
31335
31892
|
const code = normalizedCandidateCode && SAFE_CONTROL_ERROR_CODES.has(normalizedCandidateCode) ? normalizedCandidateCode : defaultControlErrorCode(status);
|
|
@@ -31378,7 +31935,7 @@ function sanitizeScheduleConnectionSelections(value) {
|
|
|
31378
31935
|
const selections = [];
|
|
31379
31936
|
const seen = /* @__PURE__ */ new Set();
|
|
31380
31937
|
for (const item of value) {
|
|
31381
|
-
if (!
|
|
31938
|
+
if (!isRecord4(item)) throw new ScheduleConnectionValidationError("Each service connection must be an object.");
|
|
31382
31939
|
const connectionId = firstString(item, ["connectionId", "connection_id"]);
|
|
31383
31940
|
const providerConfigKey = firstString(item, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id"]);
|
|
31384
31941
|
const allowedTools = cleanTools(item.allowedTools ?? item.allowed_tools ?? item.allowedCapabilities ?? item.allowed_capabilities);
|
|
@@ -31398,7 +31955,7 @@ async function getNangoCatalog() {
|
|
|
31398
31955
|
const rows = arrayFromPayload(body, ["providers", "catalog", "integrations", "services"]);
|
|
31399
31956
|
const result = [];
|
|
31400
31957
|
for (const row of rows) {
|
|
31401
|
-
if (!
|
|
31958
|
+
if (!isRecord4(row)) continue;
|
|
31402
31959
|
const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "id"]);
|
|
31403
31960
|
if (!providerConfigKey) continue;
|
|
31404
31961
|
const provider = firstString(row, ["provider"]);
|
|
@@ -31418,18 +31975,18 @@ async function getNangoCatalog() {
|
|
|
31418
31975
|
).filter((tool) => !disabledTools?.has(tool));
|
|
31419
31976
|
const requiredPermissionsByTool = {};
|
|
31420
31977
|
const rawRequiredPermissions = row.requiredPermissionsByTool ?? row.required_permissions_by_tool;
|
|
31421
|
-
if (
|
|
31978
|
+
if (isRecord4(rawRequiredPermissions)) {
|
|
31422
31979
|
for (const [tool, permissions] of Object.entries(rawRequiredPermissions).slice(0, 500)) {
|
|
31423
|
-
const cleanTool =
|
|
31980
|
+
const cleanTool = cleanString3(tool, 200);
|
|
31424
31981
|
if (!cleanTool || !safeDefaultAllowedTools.includes(cleanTool) && !actionTools.includes(cleanTool)) continue;
|
|
31425
31982
|
requiredPermissionsByTool[cleanTool] = cleanStringArray(permissions, 32, 200);
|
|
31426
31983
|
}
|
|
31427
31984
|
}
|
|
31428
31985
|
const requiredFeaturesByTool = {};
|
|
31429
31986
|
const rawRequiredFeatures = row.requiredFeaturesByTool ?? row.required_features_by_tool;
|
|
31430
|
-
if (
|
|
31987
|
+
if (isRecord4(rawRequiredFeatures)) {
|
|
31431
31988
|
for (const [tool, features] of Object.entries(rawRequiredFeatures).slice(0, 500)) {
|
|
31432
|
-
const cleanTool =
|
|
31989
|
+
const cleanTool = cleanString3(tool, 200);
|
|
31433
31990
|
if (!cleanTool) continue;
|
|
31434
31991
|
requiredFeaturesByTool[cleanTool] = cleanStringArray(features, 32, 200);
|
|
31435
31992
|
}
|
|
@@ -31554,7 +32111,7 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
31554
32111
|
const rows = arrayFromPayload(body, ["connections"]);
|
|
31555
32112
|
const result = [];
|
|
31556
32113
|
for (const row of rows) {
|
|
31557
|
-
if (!
|
|
32114
|
+
if (!isRecord4(row)) continue;
|
|
31558
32115
|
const connectionId = firstString(row, ["connectionId", "connection_id", "id"]);
|
|
31559
32116
|
const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
|
|
31560
32117
|
if (!connectionId || !providerConfigKey) continue;
|
|
@@ -31573,8 +32130,8 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
31573
32130
|
const rawToolCapabilities = row.toolCapabilities ?? row.tool_capabilities;
|
|
31574
32131
|
if (Array.isArray(rawToolCapabilities)) {
|
|
31575
32132
|
for (const value of rawToolCapabilities.slice(0, 500)) {
|
|
31576
|
-
if (!
|
|
31577
|
-
const name =
|
|
32133
|
+
if (!isRecord4(value)) continue;
|
|
32134
|
+
const name = cleanString3(value.name, 200);
|
|
31578
32135
|
const classification = value.classification;
|
|
31579
32136
|
const blockedValue = Object.prototype.hasOwnProperty.call(value, "blockedReason") ? value.blockedReason : value.blocked_reason;
|
|
31580
32137
|
const blockedReason = blockedValue === null ? null : blockedValue === "missing_permission" || blockedValue === "permission_policy_missing" || blockedValue === "permission_verification_unavailable" || blockedValue === "missing_app_feature" ? blockedValue : void 0;
|
|
@@ -31641,7 +32198,7 @@ async function deleteNangoConnection(identity, connectionId) {
|
|
|
31641
32198
|
body: JSON.stringify({ identity, connectionId })
|
|
31642
32199
|
});
|
|
31643
32200
|
const data = unwrapData(body);
|
|
31644
|
-
if (!
|
|
32201
|
+
if (!isRecord4(data) || data.ok !== true) {
|
|
31645
32202
|
throw new NangoControlError("The connection service returned an invalid deletion response.");
|
|
31646
32203
|
}
|
|
31647
32204
|
const remaining = Number(data.remainingConnections);
|
|
@@ -31649,7 +32206,7 @@ async function deleteNangoConnection(identity, connectionId) {
|
|
|
31649
32206
|
}
|
|
31650
32207
|
function sanitizeConnectSession(body) {
|
|
31651
32208
|
const data = unwrapData(body);
|
|
31652
|
-
if (!
|
|
32209
|
+
if (!isRecord4(data)) throw new NangoControlError("The connection service returned an invalid connect session.");
|
|
31653
32210
|
const connectLink = firstString(data, ["connectLink", "connect_link"], 2e3);
|
|
31654
32211
|
if (!connectLink) throw new NangoControlError("The connection service did not return a connect link.");
|
|
31655
32212
|
return {
|
|
@@ -31691,14 +32248,14 @@ async function createNangoReconnectSession(identity, connectionId) {
|
|
|
31691
32248
|
function sanitizeBindingRows(body, defaultScheduleActionId) {
|
|
31692
32249
|
const data = unwrapData(body);
|
|
31693
32250
|
const flattened = [];
|
|
31694
|
-
if (
|
|
32251
|
+
if (isRecord4(data) && isRecord4(data.bindings) && !Array.isArray(data.bindings)) {
|
|
31695
32252
|
for (const [scheduleActionId, value] of Object.entries(data.bindings)) {
|
|
31696
32253
|
if (Array.isArray(value)) value.forEach((row) => flattened.push({ row, scheduleActionId }));
|
|
31697
32254
|
}
|
|
31698
32255
|
} else {
|
|
31699
32256
|
const rows = arrayFromPayload(data, ["bindings", "connections"]);
|
|
31700
32257
|
for (const row of rows) {
|
|
31701
|
-
if (
|
|
32258
|
+
if (isRecord4(row) && Array.isArray(row.connections)) {
|
|
31702
32259
|
const scheduleActionId = firstString(row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || defaultScheduleActionId;
|
|
31703
32260
|
row.connections.forEach((connection) => flattened.push({ row: connection, scheduleActionId }));
|
|
31704
32261
|
} else {
|
|
@@ -31708,7 +32265,7 @@ function sanitizeBindingRows(body, defaultScheduleActionId) {
|
|
|
31708
32265
|
}
|
|
31709
32266
|
const result = [];
|
|
31710
32267
|
for (const item of flattened) {
|
|
31711
|
-
if (!
|
|
32268
|
+
if (!isRecord4(item.row)) continue;
|
|
31712
32269
|
const connectionId = firstString(item.row, ["connectionId", "connection_id"]);
|
|
31713
32270
|
const providerConfigKey = firstString(item.row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
|
|
31714
32271
|
if (!connectionId || !providerConfigKey) continue;
|
|
@@ -31776,7 +32333,7 @@ async function setScheduleConnectionActionsEnabled(identity, connectionId, enabl
|
|
|
31776
32333
|
body: JSON.stringify({ identity, connectionId, enabled })
|
|
31777
32334
|
});
|
|
31778
32335
|
const data = unwrapData(body);
|
|
31779
|
-
if (!
|
|
32336
|
+
if (!isRecord4(data) || !isRecord4(data.connection)) return enabled;
|
|
31780
32337
|
return data.connection.actionsEnabled === true;
|
|
31781
32338
|
}
|
|
31782
32339
|
async function callScheduleConnectionAction(identity, connectionId, input, tool, idempotencyKey2) {
|
|
@@ -31826,7 +32383,7 @@ async function callScheduleConnectionAction(identity, connectionId, input, tool,
|
|
|
31826
32383
|
body: JSON.stringify({ identity, connectionId, ...tool ? { tool } : {}, input })
|
|
31827
32384
|
});
|
|
31828
32385
|
const data = unwrapData(body);
|
|
31829
|
-
return
|
|
32386
|
+
return isRecord4(data) ? data.result ?? data : data;
|
|
31830
32387
|
}
|
|
31831
32388
|
async function callScheduleConnectionRead(identity, connectionId, tool, args, idempotencyKey2) {
|
|
31832
32389
|
if (mainOwnsIntegrations()) {
|
|
@@ -31849,7 +32406,7 @@ async function callScheduleConnectionRead(identity, connectionId, tool, args, id
|
|
|
31849
32406
|
body: JSON.stringify({ identity, connectionId, tool, args: args ?? {} })
|
|
31850
32407
|
});
|
|
31851
32408
|
const data = unwrapData(body);
|
|
31852
|
-
return
|
|
32409
|
+
return isRecord4(data) ? data.result ?? data : data;
|
|
31853
32410
|
}
|
|
31854
32411
|
async function testNangoConnection(identity, connectionId) {
|
|
31855
32412
|
if (!mainOwnsIntegrations()) {
|
|
@@ -31869,20 +32426,20 @@ async function testNangoConnection(identity, connectionId) {
|
|
|
31869
32426
|
}
|
|
31870
32427
|
}
|
|
31871
32428
|
function sanitizeToolSchema(value) {
|
|
31872
|
-
if (!
|
|
32429
|
+
if (!isRecord4(value) || value.type !== "object") return null;
|
|
31873
32430
|
try {
|
|
31874
32431
|
const serialized = JSON.stringify(value);
|
|
31875
32432
|
if (Buffer.byteLength(serialized, "utf8") > 256 * 1024) return null;
|
|
31876
32433
|
const cloned = JSON.parse(serialized);
|
|
31877
|
-
return
|
|
32434
|
+
return isRecord4(cloned) ? cloned : null;
|
|
31878
32435
|
} catch {
|
|
31879
32436
|
return null;
|
|
31880
32437
|
}
|
|
31881
32438
|
}
|
|
31882
32439
|
function sanitizeToolAnnotations(value) {
|
|
31883
|
-
if (!
|
|
32440
|
+
if (!isRecord4(value)) return void 0;
|
|
31884
32441
|
const annotations = {};
|
|
31885
|
-
const title =
|
|
32442
|
+
const title = cleanString3(value.title, 200);
|
|
31886
32443
|
if (title) annotations.title = title;
|
|
31887
32444
|
for (const key of ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"]) {
|
|
31888
32445
|
if (typeof value[key] === "boolean") annotations[key] = value[key];
|
|
@@ -31890,7 +32447,7 @@ function sanitizeToolAnnotations(value) {
|
|
|
31890
32447
|
return Object.keys(annotations).length > 0 ? annotations : void 0;
|
|
31891
32448
|
}
|
|
31892
32449
|
function sanitizeToolIconSource(value) {
|
|
31893
|
-
const candidate =
|
|
32450
|
+
const candidate = cleanString3(value, 1e4);
|
|
31894
32451
|
if (!candidate) return null;
|
|
31895
32452
|
if (/^data:image\/(?:png|jpeg|webp|gif|svg\+xml);base64,[a-z0-9+/=]+$/i.test(candidate)) return candidate;
|
|
31896
32453
|
return cleanHttpsUrl(candidate);
|
|
@@ -31899,10 +32456,10 @@ function sanitizeToolIcons(value) {
|
|
|
31899
32456
|
if (!Array.isArray(value)) return void 0;
|
|
31900
32457
|
const icons = [];
|
|
31901
32458
|
for (const item of value.slice(0, 16)) {
|
|
31902
|
-
if (!
|
|
32459
|
+
if (!isRecord4(item)) continue;
|
|
31903
32460
|
const src = sanitizeToolIconSource(item.src);
|
|
31904
32461
|
if (!src) continue;
|
|
31905
|
-
const mimeType =
|
|
32462
|
+
const mimeType = cleanString3(item.mimeType ?? item.mime_type, 100);
|
|
31906
32463
|
const sizes = cleanStringArray(item.sizes, 16, 32);
|
|
31907
32464
|
const theme = item.theme === "light" || item.theme === "dark" ? item.theme : void 0;
|
|
31908
32465
|
icons.push({
|
|
@@ -31916,7 +32473,7 @@ function sanitizeToolIcons(value) {
|
|
|
31916
32473
|
}
|
|
31917
32474
|
function canonicalJson2(value) {
|
|
31918
32475
|
if (Array.isArray(value)) return `[${value.map(canonicalJson2).join(",")}]`;
|
|
31919
|
-
if (
|
|
32476
|
+
if (isRecord4(value)) {
|
|
31920
32477
|
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(value[key])}`).join(",")}}`;
|
|
31921
32478
|
}
|
|
31922
32479
|
return JSON.stringify(value);
|
|
@@ -31947,8 +32504,8 @@ async function describeNangoTool(identity, connectionId, tool, fresh) {
|
|
|
31947
32504
|
}
|
|
31948
32505
|
function sanitizeNangoToolDescription(body, tool) {
|
|
31949
32506
|
const data = unwrapData(body);
|
|
31950
|
-
const rawTool =
|
|
31951
|
-
if (!
|
|
32507
|
+
const rawTool = isRecord4(data) && isRecord4(data.tool) ? data.tool : data;
|
|
32508
|
+
if (!isRecord4(rawTool)) {
|
|
31952
32509
|
throw new NangoControlError(
|
|
31953
32510
|
"The connection service returned an invalid live tool description.",
|
|
31954
32511
|
502,
|
|
@@ -31961,13 +32518,13 @@ function sanitizeNangoToolDescription(body, tool) {
|
|
|
31961
32518
|
serializedBytes = Buffer.byteLength(JSON.stringify(rawTool), "utf8");
|
|
31962
32519
|
} catch {
|
|
31963
32520
|
}
|
|
31964
|
-
const name =
|
|
31965
|
-
const classification =
|
|
31966
|
-
const transport =
|
|
31967
|
-
const providerConfigKey =
|
|
31968
|
-
const schemaSource =
|
|
31969
|
-
const upstreamSchemaHash =
|
|
31970
|
-
const fetchedAt =
|
|
32521
|
+
const name = cleanString3(rawTool.name, 200);
|
|
32522
|
+
const classification = cleanString3(rawTool.classification, 20);
|
|
32523
|
+
const transport = cleanString3(rawTool.transport, 20);
|
|
32524
|
+
const providerConfigKey = cleanString3(rawTool.providerConfigKey ?? rawTool.provider_config_key, 200);
|
|
32525
|
+
const schemaSource = cleanString3(rawTool.schemaSource ?? rawTool.schema_source, 50);
|
|
32526
|
+
const upstreamSchemaHash = cleanString3(rawTool.schemaHash ?? rawTool.schema_hash, 200);
|
|
32527
|
+
const fetchedAt = cleanString3(rawTool.fetchedAt ?? rawTool.fetched_at, 100);
|
|
31971
32528
|
const inputSchema = sanitizeToolSchema(rawTool.inputSchema ?? rawTool.input_schema);
|
|
31972
32529
|
const outputSchemaValue = rawTool.outputSchema ?? rawTool.output_schema;
|
|
31973
32530
|
const outputSchema = outputSchemaValue === void 0 ? void 0 : sanitizeToolSchema(outputSchemaValue);
|
|
@@ -31981,11 +32538,11 @@ function sanitizeNangoToolDescription(body, tool) {
|
|
|
31981
32538
|
true
|
|
31982
32539
|
);
|
|
31983
32540
|
}
|
|
31984
|
-
const protocolVersion = rawTool.protocolVersion === null || rawTool.protocol_version === null ? null :
|
|
32541
|
+
const protocolVersion = rawTool.protocolVersion === null || rawTool.protocol_version === null ? null : cleanString3(rawTool.protocolVersion ?? rawTool.protocol_version, 100);
|
|
31985
32542
|
const executionValue = rawTool.execution;
|
|
31986
|
-
const taskSupport =
|
|
31987
|
-
const title =
|
|
31988
|
-
const description =
|
|
32543
|
+
const taskSupport = isRecord4(executionValue) && (executionValue.taskSupport === "forbidden" || executionValue.taskSupport === "optional" || executionValue.taskSupport === "required") ? executionValue.taskSupport : null;
|
|
32544
|
+
const title = cleanString3(rawTool.title, 200);
|
|
32545
|
+
const description = cleanString3(rawTool.description, 12e3);
|
|
31989
32546
|
const annotations = sanitizeToolAnnotations(rawTool.annotations);
|
|
31990
32547
|
const icons = sanitizeToolIcons(rawTool.icons);
|
|
31991
32548
|
const execution = taskSupport ? { taskSupport } : void 0;
|
|
@@ -32028,10 +32585,13 @@ function sanitizeNangoToolDescription(body, tool) {
|
|
|
32028
32585
|
};
|
|
32029
32586
|
}
|
|
32030
32587
|
async function callScheduleConnectionExportPage(identity, input) {
|
|
32031
|
-
if (mainOwnsIntegrations() && input.dataset === "search_console_performance") {
|
|
32588
|
+
if (mainOwnsIntegrations() && (input.dataset === "search_console_performance" || input.dataset === "slack_channel_messages")) {
|
|
32032
32589
|
try {
|
|
32033
32590
|
return await callMainOwnedExportPage(identity, input);
|
|
32034
32591
|
} catch (error) {
|
|
32592
|
+
if (error instanceof Error && /^slack_export_(?:channel_required|cursor_invalid|range_invalid)$/.test(error.message)) {
|
|
32593
|
+
throw new NangoControlError("The Slack channel export request or continuation is invalid.", 400, "invalid_request", false);
|
|
32594
|
+
}
|
|
32035
32595
|
throw asNangoControlError(error);
|
|
32036
32596
|
}
|
|
32037
32597
|
}
|
|
@@ -32040,11 +32600,11 @@ async function callScheduleConnectionExportPage(identity, input) {
|
|
|
32040
32600
|
body: JSON.stringify({ identity, ...input })
|
|
32041
32601
|
}, 9e4);
|
|
32042
32602
|
const data = unwrapData(body);
|
|
32043
|
-
if (!
|
|
32603
|
+
if (!isRecord4(data) || data.ok !== true) {
|
|
32044
32604
|
throw new NangoControlError("Connected-data export returned an invalid page response.");
|
|
32045
32605
|
}
|
|
32046
|
-
const providerConfigKey =
|
|
32047
|
-
const dataset =
|
|
32606
|
+
const providerConfigKey = cleanString3(data.providerConfigKey, 128);
|
|
32607
|
+
const dataset = cleanString3(data.dataset, 64);
|
|
32048
32608
|
if (!providerConfigKey || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
|
|
32049
32609
|
throw new NangoControlError("Connected-data export returned invalid provider metadata.");
|
|
32050
32610
|
}
|
|
@@ -32054,7 +32614,7 @@ async function callScheduleConnectionExportPage(identity, input) {
|
|
|
32054
32614
|
if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
|
|
32055
32615
|
throw new NangoControlError("Connected-data export returned an invalid continuation cursor.");
|
|
32056
32616
|
}
|
|
32057
|
-
const rawCounts =
|
|
32617
|
+
const rawCounts = isRecord4(data.counts) ? data.counts : {};
|
|
32058
32618
|
const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
32059
32619
|
return {
|
|
32060
32620
|
providerConfigKey,
|
|
@@ -32072,11 +32632,11 @@ async function callScheduleConnectionExportPage(identity, input) {
|
|
|
32072
32632
|
};
|
|
32073
32633
|
}
|
|
32074
32634
|
function unwrapMcpToolJson(value) {
|
|
32075
|
-
if (!
|
|
32076
|
-
if (
|
|
32635
|
+
if (!isRecord4(value)) return value;
|
|
32636
|
+
if (isRecord4(value.structuredContent)) return value.structuredContent;
|
|
32077
32637
|
if (Array.isArray(value.content)) {
|
|
32078
|
-
const textPart = value.content.find((part) =>
|
|
32079
|
-
if (
|
|
32638
|
+
const textPart = value.content.find((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string");
|
|
32639
|
+
if (isRecord4(textPart) && typeof textPart.text === "string") {
|
|
32080
32640
|
try {
|
|
32081
32641
|
return JSON.parse(textPart.text);
|
|
32082
32642
|
} catch {
|
|
@@ -32089,13 +32649,26 @@ function unwrapMcpToolJson(value) {
|
|
|
32089
32649
|
function rowsFromToolResult(value, keys) {
|
|
32090
32650
|
const unwrapped = unwrapMcpToolJson(value);
|
|
32091
32651
|
if (Array.isArray(unwrapped)) return unwrapped;
|
|
32092
|
-
if (!
|
|
32652
|
+
if (!isRecord4(unwrapped)) return [];
|
|
32093
32653
|
for (const key of keys) if (Array.isArray(unwrapped[key])) return unwrapped[key];
|
|
32094
32654
|
return [];
|
|
32095
32655
|
}
|
|
32096
32656
|
async function callMainOwnedExportPage(identity, input) {
|
|
32097
32657
|
const connection = await getOwnedServiceConnection(identity, input.connectionId);
|
|
32098
32658
|
if (!connection) throw new NangoControlError("The service connection was not found.", 404, "connection_not_found", false);
|
|
32659
|
+
if (connection.providerConfigKey === "slack" && input.dataset === "slack_channel_messages") {
|
|
32660
|
+
return exportSlackChannelPage(input, {
|
|
32661
|
+
callTool: async (args) => callNangoToolDirect({
|
|
32662
|
+
identity,
|
|
32663
|
+
connectionId: input.connectionId,
|
|
32664
|
+
tool: args.tool,
|
|
32665
|
+
input: args.input,
|
|
32666
|
+
classification: "read",
|
|
32667
|
+
requestId: args.requestId,
|
|
32668
|
+
operationKind: "export"
|
|
32669
|
+
})
|
|
32670
|
+
});
|
|
32671
|
+
}
|
|
32099
32672
|
if (connection.providerConfigKey !== "google-search-console" || input.dataset !== "search_console_performance") {
|
|
32100
32673
|
throw new NangoControlError("This dataset has not moved to main-MCP export execution yet.", 400, "invalid_request", false);
|
|
32101
32674
|
}
|
|
@@ -32113,8 +32686,8 @@ async function callMainOwnedExportPage(identity, input) {
|
|
|
32113
32686
|
operationKind: "export"
|
|
32114
32687
|
});
|
|
32115
32688
|
const sites = rowsFromToolResult(listed, ["siteEntry", "sites", "items"]).flatMap((value) => {
|
|
32116
|
-
const row =
|
|
32117
|
-
const siteUrl2 = row ?
|
|
32689
|
+
const row = isRecord4(value) ? value : null;
|
|
32690
|
+
const siteUrl2 = row ? cleanString3(row.siteUrl ?? row.site_url, 2e3) : null;
|
|
32118
32691
|
return siteUrl2 ? [siteUrl2] : [];
|
|
32119
32692
|
}).sort().slice(0, 1e4);
|
|
32120
32693
|
if (siteIndex >= sites.length) {
|
|
@@ -32151,7 +32724,7 @@ async function callMainOwnedExportPage(identity, input) {
|
|
|
32151
32724
|
});
|
|
32152
32725
|
const rows = rowsFromToolResult(performance2, ["rows"]).slice(0, input.pageSize);
|
|
32153
32726
|
const records = rows.map((value, index) => {
|
|
32154
|
-
const row =
|
|
32727
|
+
const row = isRecord4(value) ? value : {};
|
|
32155
32728
|
const keys = Array.isArray(row.keys) ? row.keys.map((item) => String(item ?? "")) : [];
|
|
32156
32729
|
const date = keys[0] || null;
|
|
32157
32730
|
return {
|
|
@@ -32228,34 +32801,34 @@ function controlSecret2() {
|
|
|
32228
32801
|
if (!secret2) throw new ResendControlError("Resend connections are not configured.", 503);
|
|
32229
32802
|
return secret2;
|
|
32230
32803
|
}
|
|
32231
|
-
function
|
|
32804
|
+
function isRecord5(value) {
|
|
32232
32805
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
32233
32806
|
}
|
|
32234
32807
|
function unwrapData2(value) {
|
|
32235
|
-
return
|
|
32808
|
+
return isRecord5(value) && isRecord5(value.data) ? value.data : value;
|
|
32236
32809
|
}
|
|
32237
|
-
function
|
|
32810
|
+
function cleanString4(value, max = 300) {
|
|
32238
32811
|
if (typeof value !== "string") return null;
|
|
32239
32812
|
const trimmed = value.trim();
|
|
32240
32813
|
return trimmed ? trimmed.slice(0, max) : null;
|
|
32241
32814
|
}
|
|
32242
32815
|
function firstString2(record, keys, max = 300) {
|
|
32243
32816
|
for (const key of keys) {
|
|
32244
|
-
const value =
|
|
32817
|
+
const value = cleanString4(record[key], max);
|
|
32245
32818
|
if (value) return value;
|
|
32246
32819
|
}
|
|
32247
32820
|
return null;
|
|
32248
32821
|
}
|
|
32249
32822
|
function cleanTools2(value) {
|
|
32250
32823
|
if (!Array.isArray(value)) return [];
|
|
32251
|
-
return [...new Set(value.map((tool) =>
|
|
32824
|
+
return [...new Set(value.map((tool) => cleanString4(tool, 200)).filter((tool) => !!tool))].slice(0, 200);
|
|
32252
32825
|
}
|
|
32253
32826
|
function cleanStringArray2(value, maxItems = 50, maxLength = 500) {
|
|
32254
32827
|
if (!Array.isArray(value)) return [];
|
|
32255
|
-
return [...new Set(value.map((item) =>
|
|
32828
|
+
return [...new Set(value.map((item) => cleanString4(item, maxLength)).filter((item) => !!item))].slice(0, maxItems);
|
|
32256
32829
|
}
|
|
32257
32830
|
function cleanHttpsUrl2(value) {
|
|
32258
|
-
const candidate =
|
|
32831
|
+
const candidate = cleanString4(value, 2e3);
|
|
32259
32832
|
if (!candidate) return null;
|
|
32260
32833
|
try {
|
|
32261
32834
|
const url = new URL(candidate);
|
|
@@ -32273,7 +32846,7 @@ function cleanAuthorizationUrl(value) {
|
|
|
32273
32846
|
function arrayFromPayload2(value, keys) {
|
|
32274
32847
|
const unwrapped = unwrapData2(value);
|
|
32275
32848
|
if (Array.isArray(unwrapped)) return unwrapped;
|
|
32276
|
-
if (!
|
|
32849
|
+
if (!isRecord5(unwrapped)) return [];
|
|
32277
32850
|
for (const key of keys) {
|
|
32278
32851
|
if (Array.isArray(unwrapped[key])) return unwrapped[key];
|
|
32279
32852
|
}
|
|
@@ -32300,7 +32873,7 @@ async function controlRequest2(path5, init, timeoutMs = 2e4) {
|
|
|
32300
32873
|
body = {};
|
|
32301
32874
|
}
|
|
32302
32875
|
if (!response.ok) {
|
|
32303
|
-
const upstream =
|
|
32876
|
+
const upstream = isRecord5(body) ? cleanString4(body.error, 300) : null;
|
|
32304
32877
|
throw new ResendControlError(upstream || `Resend connection control failed (${response.status}).`);
|
|
32305
32878
|
}
|
|
32306
32879
|
return body;
|
|
@@ -32310,7 +32883,7 @@ async function getResendCatalog() {
|
|
|
32310
32883
|
const rows = arrayFromPayload2(body, ["providers", "catalog", "integrations", "services"]);
|
|
32311
32884
|
const result = [];
|
|
32312
32885
|
for (const row of rows) {
|
|
32313
|
-
if (!
|
|
32886
|
+
if (!isRecord5(row)) continue;
|
|
32314
32887
|
const id = firstString2(row, ["providerConfigKey", "provider_config_key", "id"]);
|
|
32315
32888
|
if (id !== RESEND_PROVIDER_CONFIG_KEY) continue;
|
|
32316
32889
|
const safeDefaultAllowedTools = cleanTools2(
|
|
@@ -32346,7 +32919,7 @@ async function getResendConnections(identity) {
|
|
|
32346
32919
|
const rows = arrayFromPayload2(body, ["connections"]);
|
|
32347
32920
|
const result = [];
|
|
32348
32921
|
for (const row of rows) {
|
|
32349
|
-
if (!
|
|
32922
|
+
if (!isRecord5(row)) continue;
|
|
32350
32923
|
const connectionId = firstString2(row, ["connectionId", "connection_id", "id"]);
|
|
32351
32924
|
const providerConfigKey = firstString2(row, ["providerConfigKey", "provider_config_key", "provider"]) || RESEND_PROVIDER_CONFIG_KEY;
|
|
32352
32925
|
if (!connectionId || providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) continue;
|
|
@@ -32390,7 +32963,7 @@ async function getResendConnections(identity) {
|
|
|
32390
32963
|
}
|
|
32391
32964
|
function sanitizeConnectSession2(body) {
|
|
32392
32965
|
const data = unwrapData2(body);
|
|
32393
|
-
if (!
|
|
32966
|
+
if (!isRecord5(data)) throw new ResendControlError("The Resend connection service returned an invalid connect session.");
|
|
32394
32967
|
const connectLink = cleanAuthorizationUrl(data.authorizationUrl ?? data.authorization_url ?? data.connectLink ?? data.connect_link);
|
|
32395
32968
|
if (!connectLink) throw new ResendControlError("The Resend connection service did not return a trusted authorization link.");
|
|
32396
32969
|
return {
|
|
@@ -32431,7 +33004,7 @@ async function setResendActionsEnabled(identity, connectionId, enabled) {
|
|
|
32431
33004
|
body: JSON.stringify({ identity, connectionId, enabled })
|
|
32432
33005
|
});
|
|
32433
33006
|
const data = unwrapData2(body);
|
|
32434
|
-
if (!
|
|
33007
|
+
if (!isRecord5(data) || !isRecord5(data.connection)) return enabled;
|
|
32435
33008
|
return data.connection.actionsEnabled === true || data.connection.actions_enabled === true;
|
|
32436
33009
|
}
|
|
32437
33010
|
async function callResendRead(identity, connectionId, tool, args) {
|
|
@@ -32440,7 +33013,7 @@ async function callResendRead(identity, connectionId, tool, args) {
|
|
|
32440
33013
|
body: JSON.stringify({ identity, connectionId, tool, input: args ?? {} })
|
|
32441
33014
|
});
|
|
32442
33015
|
const data = unwrapData2(body);
|
|
32443
|
-
return
|
|
33016
|
+
return isRecord5(data) ? data.result ?? data : data;
|
|
32444
33017
|
}
|
|
32445
33018
|
async function callResendAction(identity, connectionId, tool, input) {
|
|
32446
33019
|
const body = await controlRequest2("/api/internal/resend/actions/call", {
|
|
@@ -32448,7 +33021,7 @@ async function callResendAction(identity, connectionId, tool, input) {
|
|
|
32448
33021
|
body: JSON.stringify({ identity, connectionId, tool, input })
|
|
32449
33022
|
});
|
|
32450
33023
|
const data = unwrapData2(body);
|
|
32451
|
-
return
|
|
33024
|
+
return isRecord5(data) ? data.result ?? data : data;
|
|
32452
33025
|
}
|
|
32453
33026
|
async function describeResendTool(identity, connectionId, tool) {
|
|
32454
33027
|
const body = await controlRequest2("/api/internal/resend/describe", {
|
|
@@ -32456,12 +33029,12 @@ async function describeResendTool(identity, connectionId, tool) {
|
|
|
32456
33029
|
body: JSON.stringify({ identity, connectionId, tool })
|
|
32457
33030
|
});
|
|
32458
33031
|
const data = unwrapData2(body);
|
|
32459
|
-
const rawTool =
|
|
32460
|
-
if (!
|
|
33032
|
+
const rawTool = isRecord5(data) && isRecord5(data.tool) ? data.tool : data;
|
|
33033
|
+
if (!isRecord5(rawTool)) throw new ResendControlError("Resend returned an invalid tool description.");
|
|
32461
33034
|
const name = firstString2(rawTool, ["name"], 200);
|
|
32462
33035
|
const classification = firstString2(rawTool, ["classification", "kind"], 20);
|
|
32463
33036
|
const inputSchema = rawTool.inputSchema ?? rawTool.input_schema;
|
|
32464
|
-
if (!name || name !== tool || classification !== "read" && classification !== "action" || !
|
|
33037
|
+
if (!name || name !== tool || classification !== "read" && classification !== "action" || !isRecord5(inputSchema)) {
|
|
32465
33038
|
throw new ResendControlError("Resend returned an invalid tool description.");
|
|
32466
33039
|
}
|
|
32467
33040
|
return {
|
|
@@ -32478,10 +33051,10 @@ async function callResendExportPage(identity, input) {
|
|
|
32478
33051
|
body: JSON.stringify({ identity, ...input })
|
|
32479
33052
|
}, 9e4);
|
|
32480
33053
|
const data = unwrapData2(body);
|
|
32481
|
-
if (!
|
|
33054
|
+
if (!isRecord5(data) || data.ok !== true || data.providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) {
|
|
32482
33055
|
throw new ResendControlError("Resend export returned an invalid page response.");
|
|
32483
33056
|
}
|
|
32484
|
-
const dataset =
|
|
33057
|
+
const dataset = cleanString4(data.dataset, 64);
|
|
32485
33058
|
if (!dataset || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
|
|
32486
33059
|
throw new ResendControlError("Resend export returned invalid dataset metadata.");
|
|
32487
33060
|
}
|
|
@@ -32489,7 +33062,7 @@ async function callResendExportPage(identity, input) {
|
|
|
32489
33062
|
if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
|
|
32490
33063
|
throw new ResendControlError("Resend export returned an invalid continuation cursor.");
|
|
32491
33064
|
}
|
|
32492
|
-
const rawCounts =
|
|
33065
|
+
const rawCounts = isRecord5(data.counts) ? data.counts : {};
|
|
32493
33066
|
const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
32494
33067
|
return {
|
|
32495
33068
|
providerConfigKey: RESEND_PROVIDER_CONFIG_KEY,
|
|
@@ -33104,6 +33677,16 @@ app.get("/memory/note", auth2, async (c) => {
|
|
|
33104
33677
|
return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load note" }, 502);
|
|
33105
33678
|
}
|
|
33106
33679
|
});
|
|
33680
|
+
app.get("/memory/universe", auth2, async (c) => {
|
|
33681
|
+
const user = c.get("user");
|
|
33682
|
+
const includeConnections = c.req.query("includeConnections") === "1";
|
|
33683
|
+
try {
|
|
33684
|
+
const graph = await dbUniverse(resolveMemoryIdentity(user), includeConnections);
|
|
33685
|
+
return c.json({ ok: true, ...graph });
|
|
33686
|
+
} catch (err) {
|
|
33687
|
+
return c.json({ ok: false, error: err instanceof Error ? err.message : "could not build universe" }, 502);
|
|
33688
|
+
}
|
|
33689
|
+
});
|
|
33107
33690
|
app.put("/memory/note", auth2, async (c) => {
|
|
33108
33691
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
33109
33692
|
if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
|
|
@@ -33845,7 +34428,18 @@ app.post("/schedule-connections/actions/export", auth2, requireIntegrationsTier,
|
|
|
33845
34428
|
const body = await c.req.json().catch(() => ({}));
|
|
33846
34429
|
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
33847
34430
|
if (!connectionId) return c.json({ ok: false, error: "connectionId is required." }, 400);
|
|
33848
|
-
const
|
|
34431
|
+
const channelId = typeof body.channelId === "string" ? body.channelId.trim() : "";
|
|
34432
|
+
if (body.channelId !== void 0 && !channelId) {
|
|
34433
|
+
return c.json({ ok: false, error: "channelId must be a non-empty Slack conversation ID." }, 400);
|
|
34434
|
+
}
|
|
34435
|
+
if (body.includeThreads !== void 0 && typeof body.includeThreads !== "boolean") {
|
|
34436
|
+
return c.json({ ok: false, error: "includeThreads must be a boolean when provided." }, 400);
|
|
34437
|
+
}
|
|
34438
|
+
if (body.allTime !== void 0 && typeof body.allTime !== "boolean") {
|
|
34439
|
+
return c.json({ ok: false, error: "allTime must be a boolean when provided." }, 400);
|
|
34440
|
+
}
|
|
34441
|
+
const rawRequestedDataset = typeof body.dataset === "string" ? body.dataset.trim() : "auto";
|
|
34442
|
+
const requestedDataset = rawRequestedDataset === "auto" && channelId ? "slack_channel_messages" : rawRequestedDataset;
|
|
33849
34443
|
const datasets = [...CONNECTED_DATA_DATASETS];
|
|
33850
34444
|
if (!datasets.includes(requestedDataset)) {
|
|
33851
34445
|
return c.json({ ok: false, error: `dataset must be one of: ${datasets.join(", ")}.` }, 400);
|
|
@@ -33868,21 +34462,37 @@ app.post("/schedule-connections/actions/export", auth2, requireIntegrationsTier,
|
|
|
33868
34462
|
if (body.continuation !== void 0 && !continuation) {
|
|
33869
34463
|
return c.json({ ok: false, error: "continuation must be the complete object returned by a prior export." }, 400);
|
|
33870
34464
|
}
|
|
34465
|
+
if (body.allTime === true && requestedDataset !== "slack_channel_messages") {
|
|
34466
|
+
return c.json({ ok: false, error: "allTime is supported only for Slack channel exports." }, 400);
|
|
34467
|
+
}
|
|
34468
|
+
if (body.allTime === true && (body.from !== void 0 || body.lastDays !== void 0 || continuation)) {
|
|
34469
|
+
return c.json({ ok: false, error: "Do not pass allTime with from, lastDays, or continuation." }, 400);
|
|
34470
|
+
}
|
|
34471
|
+
if (requestedDataset === "slack_channel_messages" && !channelId && !continuation) {
|
|
34472
|
+
return c.json({ ok: false, error: "channelId is required for a Slack channel export." }, 400);
|
|
34473
|
+
}
|
|
33871
34474
|
try {
|
|
33872
34475
|
const useResend = await isResendConnection(user.email, connectionId);
|
|
33873
34476
|
const resolved = resolveConnectedDataExportRequest({
|
|
33874
34477
|
requestedDataset,
|
|
33875
|
-
from: typeof body.from === "string" ? body.from : void 0,
|
|
34478
|
+
from: body.allTime === true ? "1970-01-01T00:00:00.000Z" : typeof body.from === "string" ? body.from : void 0,
|
|
33876
34479
|
to: typeof body.to === "string" ? body.to : void 0,
|
|
33877
34480
|
lastDays,
|
|
33878
34481
|
cursor: typeof body.cursor === "string" ? body.cursor : void 0,
|
|
33879
|
-
continuation
|
|
34482
|
+
continuation,
|
|
34483
|
+
scope: channelId ? {
|
|
34484
|
+
slack: {
|
|
34485
|
+
channelId,
|
|
34486
|
+
includeThreads: body.includeThreads !== false
|
|
34487
|
+
}
|
|
34488
|
+
} : void 0
|
|
33880
34489
|
});
|
|
33881
34490
|
const result = await collectConnectedDataExport({
|
|
33882
34491
|
ownerId: sha256Hex(user.api_key).slice(0, 24),
|
|
33883
34492
|
connectionId,
|
|
33884
34493
|
dataset: resolved.dataset,
|
|
33885
34494
|
range: resolved.range,
|
|
34495
|
+
...resolved.scope ? { scope: resolved.scope } : {},
|
|
33886
34496
|
maxItems,
|
|
33887
34497
|
forceArtifact: body.delivery === "artifact",
|
|
33888
34498
|
...resolved.cursor ? { cursor: resolved.cursor } : {},
|
|
@@ -35713,4 +36323,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
35713
36323
|
export {
|
|
35714
36324
|
app
|
|
35715
36325
|
};
|
|
35716
|
-
//# sourceMappingURL=server-
|
|
36326
|
+
//# sourceMappingURL=server-PKTDPCB2.js.map
|