mcp-scraper 0.40.3 → 0.41.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/README.md +2 -2
- package/dist/bin/api-server.cjs +748 -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-OOB35KFT.js +7 -0
- package/dist/chunk-OOB35KFT.js.map +1 -0
- package/dist/{chunk-6SZ52BQ5.js → chunk-SIE5LZ2V.js} +27 -7
- package/dist/chunk-SIE5LZ2V.js.map +1 -0
- package/dist/{server-W76RUYD3.js → server-2JJPCZH4.js} +707 -99
- package/dist/server-2JJPCZH4.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-SIE5LZ2V.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-OOB35KFT.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,375 @@ 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 result = await callSlackToolWithRetry(dependencies, {
|
|
30827
|
+
tool: "get-conversation-history",
|
|
30828
|
+
input: {
|
|
30829
|
+
channel_id: channelId,
|
|
30830
|
+
limit: pageSize,
|
|
30831
|
+
oldest: slackBoundaryTimestamp(input.from, -1),
|
|
30832
|
+
latest: slackBoundaryTimestamp(input.to, 0),
|
|
30833
|
+
inclusive: false,
|
|
30834
|
+
...state.historyCursor ? { cursor: state.historyCursor } : {}
|
|
30835
|
+
},
|
|
30836
|
+
requestId: `slack-export:${input.connectionId}:${channelId}:history:${state.historyCursor ?? "start"}`
|
|
30837
|
+
});
|
|
30838
|
+
const messages = messagesFromToolResult(result).slice(0, pageSize);
|
|
30839
|
+
const historyCursor = nextCursorFromToolResult(result);
|
|
30840
|
+
const records2 = messages.map((message) => normalizeSlackMessage(message, channelId, null));
|
|
30841
|
+
const threadIds = includeThreads ? [...new Set(messages.flatMap((message) => {
|
|
30842
|
+
const replyCount = typeof message.reply_count === "number" ? message.reply_count : 0;
|
|
30843
|
+
const timestamp = cleanString2(message.ts, 64);
|
|
30844
|
+
return replyCount > 0 && timestamp ? [timestamp] : [];
|
|
30845
|
+
}))].slice(0, MAX_PENDING_THREADS) : [];
|
|
30846
|
+
if (threadIds.length > 0) {
|
|
30847
|
+
return {
|
|
30848
|
+
providerConfigKey: "slack",
|
|
30849
|
+
dataset: "slack_channel_messages",
|
|
30850
|
+
records: records2,
|
|
30851
|
+
nextCursor: encodeCursor({
|
|
30852
|
+
version: 1,
|
|
30853
|
+
phase: "threads",
|
|
30854
|
+
channelId,
|
|
30855
|
+
includeThreads: true,
|
|
30856
|
+
historyCursor,
|
|
30857
|
+
historyMessageTs: messages.flatMap((message) => cleanString2(message.ts, 64) ?? []),
|
|
30858
|
+
threads: threadIds.map((threadTs) => ({ threadTs, cursor: null }))
|
|
30859
|
+
}),
|
|
30860
|
+
complete: false,
|
|
30861
|
+
counts: { listed: messages.length, exported: records2.length, failed: 0 },
|
|
30862
|
+
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."] : [],
|
|
30863
|
+
untrustedContent: true
|
|
30864
|
+
};
|
|
30865
|
+
}
|
|
30866
|
+
return {
|
|
30867
|
+
providerConfigKey: "slack",
|
|
30868
|
+
dataset: "slack_channel_messages",
|
|
30869
|
+
records: records2,
|
|
30870
|
+
nextCursor: historyCursor ? encodeCursor({ ...state, historyCursor }) : null,
|
|
30871
|
+
complete: historyCursor === null,
|
|
30872
|
+
counts: { listed: messages.length, exported: records2.length, failed: 0 },
|
|
30873
|
+
warnings: [],
|
|
30874
|
+
untrustedContent: true
|
|
30875
|
+
};
|
|
30876
|
+
}
|
|
30877
|
+
const batchSize = Math.min(THREAD_CONCURRENCY, state.threads.length, pageSize);
|
|
30878
|
+
const batch = state.threads.slice(0, batchSize);
|
|
30879
|
+
const perThreadLimit = Math.max(1, Math.min(100, Math.floor(pageSize / batchSize)));
|
|
30880
|
+
const results = await Promise.all(batch.map((thread) => callSlackToolWithRetry(dependencies, {
|
|
30881
|
+
tool: "get-thread-replies",
|
|
30882
|
+
input: {
|
|
30883
|
+
channel_id: channelId,
|
|
30884
|
+
thread_ts: thread.threadTs,
|
|
30885
|
+
limit: perThreadLimit,
|
|
30886
|
+
...thread.cursor ? { cursor: thread.cursor } : {}
|
|
30887
|
+
},
|
|
30888
|
+
requestId: `slack-export:${input.connectionId}:${channelId}:thread:${thread.threadTs}:${thread.cursor ?? "start"}`
|
|
30889
|
+
})));
|
|
30890
|
+
const records = [];
|
|
30891
|
+
const stillPending = [];
|
|
30892
|
+
let listed = 0;
|
|
30893
|
+
for (let index = 0; index < results.length; index += 1) {
|
|
30894
|
+
const thread = batch[index];
|
|
30895
|
+
const messages = messagesFromToolResult(results[index]);
|
|
30896
|
+
listed += messages.length;
|
|
30897
|
+
records.push(...messages.filter((message) => {
|
|
30898
|
+
const timestamp = cleanString2(message.ts, 64);
|
|
30899
|
+
return timestamp !== thread.threadTs && !state.historyMessageTs.includes(timestamp ?? "");
|
|
30900
|
+
}).map((message) => normalizeSlackMessage(message, channelId, thread.threadTs)));
|
|
30901
|
+
const cursor = nextCursorFromToolResult(results[index]);
|
|
30902
|
+
if (cursor) stillPending.push({ threadTs: thread.threadTs, cursor });
|
|
30903
|
+
}
|
|
30904
|
+
const nextState = {
|
|
30905
|
+
...state,
|
|
30906
|
+
threads: [...stillPending, ...state.threads.slice(batchSize)]
|
|
30907
|
+
};
|
|
30908
|
+
const next = nextAfterThreads(nextState);
|
|
30909
|
+
return {
|
|
30910
|
+
providerConfigKey: "slack",
|
|
30911
|
+
dataset: "slack_channel_messages",
|
|
30912
|
+
records,
|
|
30913
|
+
nextCursor: next.nextCursor,
|
|
30914
|
+
complete: next.complete,
|
|
30915
|
+
counts: { listed, exported: records.length, failed: 0 },
|
|
30916
|
+
warnings: [],
|
|
30917
|
+
untrustedContent: true
|
|
30918
|
+
};
|
|
30919
|
+
}
|
|
30920
|
+
|
|
30383
30921
|
// src/api/main-nango-transport.ts
|
|
30384
30922
|
import { createHash as createHash13, randomUUID as randomUUID16 } from "crypto";
|
|
30385
30923
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -30656,12 +31194,14 @@ var MainNangoTransportError = class extends Error {
|
|
|
30656
31194
|
status;
|
|
30657
31195
|
code;
|
|
30658
31196
|
retryable;
|
|
30659
|
-
|
|
31197
|
+
retryAfterMs;
|
|
31198
|
+
constructor(message, status, code, retryable = status === 429 || status >= 500, retryAfterMs = null) {
|
|
30660
31199
|
super(message);
|
|
30661
31200
|
this.name = "MainNangoTransportError";
|
|
30662
31201
|
this.status = status;
|
|
30663
31202
|
this.code = code;
|
|
30664
31203
|
this.retryable = retryable;
|
|
31204
|
+
this.retryAfterMs = retryAfterMs;
|
|
30665
31205
|
}
|
|
30666
31206
|
};
|
|
30667
31207
|
function nangoClientErrorText(error) {
|
|
@@ -30673,6 +31213,13 @@ function nangoClientErrorText(error) {
|
|
|
30673
31213
|
return "";
|
|
30674
31214
|
}
|
|
30675
31215
|
}
|
|
31216
|
+
function retryAfterMsFromText(value) {
|
|
31217
|
+
const milliseconds = value.match(/retry[_ -]?after[_ -]?ms[^0-9]{0,20}(\d{1,7})/i);
|
|
31218
|
+
if (milliseconds) return Math.min(Math.max(Number(milliseconds[1]), 1e3), 6e4);
|
|
31219
|
+
const seconds = value.match(/retry[_ -]?after[^0-9]{0,20}(\d{1,4})/i);
|
|
31220
|
+
if (seconds) return Math.min(Math.max(Number(seconds[1]), 1) * 1e3, 6e4);
|
|
31221
|
+
return null;
|
|
31222
|
+
}
|
|
30676
31223
|
function normalizeNangoClientError(error) {
|
|
30677
31224
|
if (error instanceof MainNangoTransportError) return error;
|
|
30678
31225
|
const detail = nangoClientErrorText(error);
|
|
@@ -30693,7 +31240,13 @@ function normalizeNangoClientError(error) {
|
|
|
30693
31240
|
);
|
|
30694
31241
|
}
|
|
30695
31242
|
if (/RESOURCE_EXHAUSTED|rate.?limit|status["']?\s*:\s*429/i.test(detail)) {
|
|
30696
|
-
return new MainNangoTransportError(
|
|
31243
|
+
return new MainNangoTransportError(
|
|
31244
|
+
"The connected provider is temporarily rate limited.",
|
|
31245
|
+
429,
|
|
31246
|
+
"upstream_rate_limited",
|
|
31247
|
+
true,
|
|
31248
|
+
retryAfterMsFromText(detail)
|
|
31249
|
+
);
|
|
30697
31250
|
}
|
|
30698
31251
|
if (/UNAUTHENTICATED|invalid_grant|status["']?\s*:\s*401/i.test(detail)) {
|
|
30699
31252
|
return new MainNangoTransportError("The service connection requires reconnection.", 409, "connection_inactive", false);
|
|
@@ -30758,7 +31311,9 @@ async function nangoRequest(path5, options = {}) {
|
|
|
30758
31311
|
throw new MainNangoTransportError(
|
|
30759
31312
|
status === 429 ? "The connected provider is temporarily rate limited." : "Nango request failed.",
|
|
30760
31313
|
status,
|
|
30761
|
-
status === 429 ? "upstream_rate_limited" : status === 404 ? "connection_not_found" : "connection_transport_unavailable"
|
|
31314
|
+
status === 429 ? "upstream_rate_limited" : status === 404 ? "connection_not_found" : "connection_transport_unavailable",
|
|
31315
|
+
status === 429 || status >= 500,
|
|
31316
|
+
status === 429 ? retryAfterMsFromText(`retry-after: ${response.headers.get("retry-after") ?? ""}`) : null
|
|
30762
31317
|
);
|
|
30763
31318
|
}
|
|
30764
31319
|
return payload;
|
|
@@ -31206,37 +31761,37 @@ function controlSecret() {
|
|
|
31206
31761
|
if (!secret2) throw new NangoControlError("Scheduled service connections are not configured.", 503);
|
|
31207
31762
|
return secret2;
|
|
31208
31763
|
}
|
|
31209
|
-
function
|
|
31764
|
+
function isRecord4(value) {
|
|
31210
31765
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
31211
31766
|
}
|
|
31212
31767
|
function unwrapData(value) {
|
|
31213
|
-
if (
|
|
31768
|
+
if (isRecord4(value) && isRecord4(value.data)) return value.data;
|
|
31214
31769
|
return value;
|
|
31215
31770
|
}
|
|
31216
|
-
function
|
|
31771
|
+
function cleanString3(value, max = 300) {
|
|
31217
31772
|
if (typeof value !== "string") return null;
|
|
31218
31773
|
const trimmed = value.trim();
|
|
31219
31774
|
return trimmed ? trimmed.slice(0, max) : null;
|
|
31220
31775
|
}
|
|
31221
31776
|
function firstString(record, keys, max = 300) {
|
|
31222
31777
|
for (const key of keys) {
|
|
31223
|
-
const value =
|
|
31778
|
+
const value = cleanString3(record[key], max);
|
|
31224
31779
|
if (value) return value;
|
|
31225
31780
|
}
|
|
31226
31781
|
return null;
|
|
31227
31782
|
}
|
|
31228
31783
|
function cleanTools(value) {
|
|
31229
31784
|
if (!Array.isArray(value)) return [];
|
|
31230
|
-
const tools = value.map((tool) =>
|
|
31785
|
+
const tools = value.map((tool) => cleanString3(tool, 200)).filter((tool) => !!tool);
|
|
31231
31786
|
return [...new Set(tools)].slice(0, 200);
|
|
31232
31787
|
}
|
|
31233
31788
|
function cleanStringArray(value, maxItems = 20, maxLength = 100) {
|
|
31234
31789
|
if (!Array.isArray(value)) return [];
|
|
31235
|
-
const items = value.map((item) =>
|
|
31790
|
+
const items = value.map((item) => cleanString3(item, maxLength)).filter((item) => !!item);
|
|
31236
31791
|
return [...new Set(items)].slice(0, maxItems);
|
|
31237
31792
|
}
|
|
31238
31793
|
function cleanHttpsUrl(value) {
|
|
31239
|
-
const candidate =
|
|
31794
|
+
const candidate = cleanString3(value, 2e3);
|
|
31240
31795
|
if (!candidate) return null;
|
|
31241
31796
|
try {
|
|
31242
31797
|
const url = new URL(candidate);
|
|
@@ -31248,7 +31803,7 @@ function cleanHttpsUrl(value) {
|
|
|
31248
31803
|
function arrayFromPayload(value, keys) {
|
|
31249
31804
|
const unwrapped = unwrapData(value);
|
|
31250
31805
|
if (Array.isArray(unwrapped)) return unwrapped;
|
|
31251
|
-
if (!
|
|
31806
|
+
if (!isRecord4(unwrapped)) return [];
|
|
31252
31807
|
for (const key of keys) {
|
|
31253
31808
|
if (Array.isArray(unwrapped[key])) return unwrapped[key];
|
|
31254
31809
|
}
|
|
@@ -31329,7 +31884,7 @@ function defaultControlErrorMessage(status) {
|
|
|
31329
31884
|
function safeControlErrorPayload(body, responseStatus) {
|
|
31330
31885
|
const status = controlErrorStatus(responseStatus);
|
|
31331
31886
|
const unwrapped = unwrapData(body);
|
|
31332
|
-
const record =
|
|
31887
|
+
const record = isRecord4(unwrapped) ? unwrapped : isRecord4(body) ? body : null;
|
|
31333
31888
|
const candidateCode = record ? firstString(record, ["code", "errorCode", "error_code"], 100) : null;
|
|
31334
31889
|
const normalizedCandidateCode = candidateCode ? CONTROL_ERROR_CODE_ALIASES.get(candidateCode) ?? candidateCode : null;
|
|
31335
31890
|
const code = normalizedCandidateCode && SAFE_CONTROL_ERROR_CODES.has(normalizedCandidateCode) ? normalizedCandidateCode : defaultControlErrorCode(status);
|
|
@@ -31378,7 +31933,7 @@ function sanitizeScheduleConnectionSelections(value) {
|
|
|
31378
31933
|
const selections = [];
|
|
31379
31934
|
const seen = /* @__PURE__ */ new Set();
|
|
31380
31935
|
for (const item of value) {
|
|
31381
|
-
if (!
|
|
31936
|
+
if (!isRecord4(item)) throw new ScheduleConnectionValidationError("Each service connection must be an object.");
|
|
31382
31937
|
const connectionId = firstString(item, ["connectionId", "connection_id"]);
|
|
31383
31938
|
const providerConfigKey = firstString(item, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id"]);
|
|
31384
31939
|
const allowedTools = cleanTools(item.allowedTools ?? item.allowed_tools ?? item.allowedCapabilities ?? item.allowed_capabilities);
|
|
@@ -31398,7 +31953,7 @@ async function getNangoCatalog() {
|
|
|
31398
31953
|
const rows = arrayFromPayload(body, ["providers", "catalog", "integrations", "services"]);
|
|
31399
31954
|
const result = [];
|
|
31400
31955
|
for (const row of rows) {
|
|
31401
|
-
if (!
|
|
31956
|
+
if (!isRecord4(row)) continue;
|
|
31402
31957
|
const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "id"]);
|
|
31403
31958
|
if (!providerConfigKey) continue;
|
|
31404
31959
|
const provider = firstString(row, ["provider"]);
|
|
@@ -31418,18 +31973,18 @@ async function getNangoCatalog() {
|
|
|
31418
31973
|
).filter((tool) => !disabledTools?.has(tool));
|
|
31419
31974
|
const requiredPermissionsByTool = {};
|
|
31420
31975
|
const rawRequiredPermissions = row.requiredPermissionsByTool ?? row.required_permissions_by_tool;
|
|
31421
|
-
if (
|
|
31976
|
+
if (isRecord4(rawRequiredPermissions)) {
|
|
31422
31977
|
for (const [tool, permissions] of Object.entries(rawRequiredPermissions).slice(0, 500)) {
|
|
31423
|
-
const cleanTool =
|
|
31978
|
+
const cleanTool = cleanString3(tool, 200);
|
|
31424
31979
|
if (!cleanTool || !safeDefaultAllowedTools.includes(cleanTool) && !actionTools.includes(cleanTool)) continue;
|
|
31425
31980
|
requiredPermissionsByTool[cleanTool] = cleanStringArray(permissions, 32, 200);
|
|
31426
31981
|
}
|
|
31427
31982
|
}
|
|
31428
31983
|
const requiredFeaturesByTool = {};
|
|
31429
31984
|
const rawRequiredFeatures = row.requiredFeaturesByTool ?? row.required_features_by_tool;
|
|
31430
|
-
if (
|
|
31985
|
+
if (isRecord4(rawRequiredFeatures)) {
|
|
31431
31986
|
for (const [tool, features] of Object.entries(rawRequiredFeatures).slice(0, 500)) {
|
|
31432
|
-
const cleanTool =
|
|
31987
|
+
const cleanTool = cleanString3(tool, 200);
|
|
31433
31988
|
if (!cleanTool) continue;
|
|
31434
31989
|
requiredFeaturesByTool[cleanTool] = cleanStringArray(features, 32, 200);
|
|
31435
31990
|
}
|
|
@@ -31554,7 +32109,7 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
31554
32109
|
const rows = arrayFromPayload(body, ["connections"]);
|
|
31555
32110
|
const result = [];
|
|
31556
32111
|
for (const row of rows) {
|
|
31557
|
-
if (!
|
|
32112
|
+
if (!isRecord4(row)) continue;
|
|
31558
32113
|
const connectionId = firstString(row, ["connectionId", "connection_id", "id"]);
|
|
31559
32114
|
const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
|
|
31560
32115
|
if (!connectionId || !providerConfigKey) continue;
|
|
@@ -31573,8 +32128,8 @@ async function getNangoConnections(identity, options = {}) {
|
|
|
31573
32128
|
const rawToolCapabilities = row.toolCapabilities ?? row.tool_capabilities;
|
|
31574
32129
|
if (Array.isArray(rawToolCapabilities)) {
|
|
31575
32130
|
for (const value of rawToolCapabilities.slice(0, 500)) {
|
|
31576
|
-
if (!
|
|
31577
|
-
const name =
|
|
32131
|
+
if (!isRecord4(value)) continue;
|
|
32132
|
+
const name = cleanString3(value.name, 200);
|
|
31578
32133
|
const classification = value.classification;
|
|
31579
32134
|
const blockedValue = Object.prototype.hasOwnProperty.call(value, "blockedReason") ? value.blockedReason : value.blocked_reason;
|
|
31580
32135
|
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 +32196,7 @@ async function deleteNangoConnection(identity, connectionId) {
|
|
|
31641
32196
|
body: JSON.stringify({ identity, connectionId })
|
|
31642
32197
|
});
|
|
31643
32198
|
const data = unwrapData(body);
|
|
31644
|
-
if (!
|
|
32199
|
+
if (!isRecord4(data) || data.ok !== true) {
|
|
31645
32200
|
throw new NangoControlError("The connection service returned an invalid deletion response.");
|
|
31646
32201
|
}
|
|
31647
32202
|
const remaining = Number(data.remainingConnections);
|
|
@@ -31649,7 +32204,7 @@ async function deleteNangoConnection(identity, connectionId) {
|
|
|
31649
32204
|
}
|
|
31650
32205
|
function sanitizeConnectSession(body) {
|
|
31651
32206
|
const data = unwrapData(body);
|
|
31652
|
-
if (!
|
|
32207
|
+
if (!isRecord4(data)) throw new NangoControlError("The connection service returned an invalid connect session.");
|
|
31653
32208
|
const connectLink = firstString(data, ["connectLink", "connect_link"], 2e3);
|
|
31654
32209
|
if (!connectLink) throw new NangoControlError("The connection service did not return a connect link.");
|
|
31655
32210
|
return {
|
|
@@ -31691,14 +32246,14 @@ async function createNangoReconnectSession(identity, connectionId) {
|
|
|
31691
32246
|
function sanitizeBindingRows(body, defaultScheduleActionId) {
|
|
31692
32247
|
const data = unwrapData(body);
|
|
31693
32248
|
const flattened = [];
|
|
31694
|
-
if (
|
|
32249
|
+
if (isRecord4(data) && isRecord4(data.bindings) && !Array.isArray(data.bindings)) {
|
|
31695
32250
|
for (const [scheduleActionId, value] of Object.entries(data.bindings)) {
|
|
31696
32251
|
if (Array.isArray(value)) value.forEach((row) => flattened.push({ row, scheduleActionId }));
|
|
31697
32252
|
}
|
|
31698
32253
|
} else {
|
|
31699
32254
|
const rows = arrayFromPayload(data, ["bindings", "connections"]);
|
|
31700
32255
|
for (const row of rows) {
|
|
31701
|
-
if (
|
|
32256
|
+
if (isRecord4(row) && Array.isArray(row.connections)) {
|
|
31702
32257
|
const scheduleActionId = firstString(row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || defaultScheduleActionId;
|
|
31703
32258
|
row.connections.forEach((connection) => flattened.push({ row: connection, scheduleActionId }));
|
|
31704
32259
|
} else {
|
|
@@ -31708,7 +32263,7 @@ function sanitizeBindingRows(body, defaultScheduleActionId) {
|
|
|
31708
32263
|
}
|
|
31709
32264
|
const result = [];
|
|
31710
32265
|
for (const item of flattened) {
|
|
31711
|
-
if (!
|
|
32266
|
+
if (!isRecord4(item.row)) continue;
|
|
31712
32267
|
const connectionId = firstString(item.row, ["connectionId", "connection_id"]);
|
|
31713
32268
|
const providerConfigKey = firstString(item.row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
|
|
31714
32269
|
if (!connectionId || !providerConfigKey) continue;
|
|
@@ -31776,7 +32331,7 @@ async function setScheduleConnectionActionsEnabled(identity, connectionId, enabl
|
|
|
31776
32331
|
body: JSON.stringify({ identity, connectionId, enabled })
|
|
31777
32332
|
});
|
|
31778
32333
|
const data = unwrapData(body);
|
|
31779
|
-
if (!
|
|
32334
|
+
if (!isRecord4(data) || !isRecord4(data.connection)) return enabled;
|
|
31780
32335
|
return data.connection.actionsEnabled === true;
|
|
31781
32336
|
}
|
|
31782
32337
|
async function callScheduleConnectionAction(identity, connectionId, input, tool, idempotencyKey2) {
|
|
@@ -31826,7 +32381,7 @@ async function callScheduleConnectionAction(identity, connectionId, input, tool,
|
|
|
31826
32381
|
body: JSON.stringify({ identity, connectionId, ...tool ? { tool } : {}, input })
|
|
31827
32382
|
});
|
|
31828
32383
|
const data = unwrapData(body);
|
|
31829
|
-
return
|
|
32384
|
+
return isRecord4(data) ? data.result ?? data : data;
|
|
31830
32385
|
}
|
|
31831
32386
|
async function callScheduleConnectionRead(identity, connectionId, tool, args, idempotencyKey2) {
|
|
31832
32387
|
if (mainOwnsIntegrations()) {
|
|
@@ -31849,7 +32404,7 @@ async function callScheduleConnectionRead(identity, connectionId, tool, args, id
|
|
|
31849
32404
|
body: JSON.stringify({ identity, connectionId, tool, args: args ?? {} })
|
|
31850
32405
|
});
|
|
31851
32406
|
const data = unwrapData(body);
|
|
31852
|
-
return
|
|
32407
|
+
return isRecord4(data) ? data.result ?? data : data;
|
|
31853
32408
|
}
|
|
31854
32409
|
async function testNangoConnection(identity, connectionId) {
|
|
31855
32410
|
if (!mainOwnsIntegrations()) {
|
|
@@ -31869,20 +32424,20 @@ async function testNangoConnection(identity, connectionId) {
|
|
|
31869
32424
|
}
|
|
31870
32425
|
}
|
|
31871
32426
|
function sanitizeToolSchema(value) {
|
|
31872
|
-
if (!
|
|
32427
|
+
if (!isRecord4(value) || value.type !== "object") return null;
|
|
31873
32428
|
try {
|
|
31874
32429
|
const serialized = JSON.stringify(value);
|
|
31875
32430
|
if (Buffer.byteLength(serialized, "utf8") > 256 * 1024) return null;
|
|
31876
32431
|
const cloned = JSON.parse(serialized);
|
|
31877
|
-
return
|
|
32432
|
+
return isRecord4(cloned) ? cloned : null;
|
|
31878
32433
|
} catch {
|
|
31879
32434
|
return null;
|
|
31880
32435
|
}
|
|
31881
32436
|
}
|
|
31882
32437
|
function sanitizeToolAnnotations(value) {
|
|
31883
|
-
if (!
|
|
32438
|
+
if (!isRecord4(value)) return void 0;
|
|
31884
32439
|
const annotations = {};
|
|
31885
|
-
const title =
|
|
32440
|
+
const title = cleanString3(value.title, 200);
|
|
31886
32441
|
if (title) annotations.title = title;
|
|
31887
32442
|
for (const key of ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"]) {
|
|
31888
32443
|
if (typeof value[key] === "boolean") annotations[key] = value[key];
|
|
@@ -31890,7 +32445,7 @@ function sanitizeToolAnnotations(value) {
|
|
|
31890
32445
|
return Object.keys(annotations).length > 0 ? annotations : void 0;
|
|
31891
32446
|
}
|
|
31892
32447
|
function sanitizeToolIconSource(value) {
|
|
31893
|
-
const candidate =
|
|
32448
|
+
const candidate = cleanString3(value, 1e4);
|
|
31894
32449
|
if (!candidate) return null;
|
|
31895
32450
|
if (/^data:image\/(?:png|jpeg|webp|gif|svg\+xml);base64,[a-z0-9+/=]+$/i.test(candidate)) return candidate;
|
|
31896
32451
|
return cleanHttpsUrl(candidate);
|
|
@@ -31899,10 +32454,10 @@ function sanitizeToolIcons(value) {
|
|
|
31899
32454
|
if (!Array.isArray(value)) return void 0;
|
|
31900
32455
|
const icons = [];
|
|
31901
32456
|
for (const item of value.slice(0, 16)) {
|
|
31902
|
-
if (!
|
|
32457
|
+
if (!isRecord4(item)) continue;
|
|
31903
32458
|
const src = sanitizeToolIconSource(item.src);
|
|
31904
32459
|
if (!src) continue;
|
|
31905
|
-
const mimeType =
|
|
32460
|
+
const mimeType = cleanString3(item.mimeType ?? item.mime_type, 100);
|
|
31906
32461
|
const sizes = cleanStringArray(item.sizes, 16, 32);
|
|
31907
32462
|
const theme = item.theme === "light" || item.theme === "dark" ? item.theme : void 0;
|
|
31908
32463
|
icons.push({
|
|
@@ -31916,7 +32471,7 @@ function sanitizeToolIcons(value) {
|
|
|
31916
32471
|
}
|
|
31917
32472
|
function canonicalJson2(value) {
|
|
31918
32473
|
if (Array.isArray(value)) return `[${value.map(canonicalJson2).join(",")}]`;
|
|
31919
|
-
if (
|
|
32474
|
+
if (isRecord4(value)) {
|
|
31920
32475
|
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(value[key])}`).join(",")}}`;
|
|
31921
32476
|
}
|
|
31922
32477
|
return JSON.stringify(value);
|
|
@@ -31947,8 +32502,8 @@ async function describeNangoTool(identity, connectionId, tool, fresh) {
|
|
|
31947
32502
|
}
|
|
31948
32503
|
function sanitizeNangoToolDescription(body, tool) {
|
|
31949
32504
|
const data = unwrapData(body);
|
|
31950
|
-
const rawTool =
|
|
31951
|
-
if (!
|
|
32505
|
+
const rawTool = isRecord4(data) && isRecord4(data.tool) ? data.tool : data;
|
|
32506
|
+
if (!isRecord4(rawTool)) {
|
|
31952
32507
|
throw new NangoControlError(
|
|
31953
32508
|
"The connection service returned an invalid live tool description.",
|
|
31954
32509
|
502,
|
|
@@ -31961,13 +32516,13 @@ function sanitizeNangoToolDescription(body, tool) {
|
|
|
31961
32516
|
serializedBytes = Buffer.byteLength(JSON.stringify(rawTool), "utf8");
|
|
31962
32517
|
} catch {
|
|
31963
32518
|
}
|
|
31964
|
-
const name =
|
|
31965
|
-
const classification =
|
|
31966
|
-
const transport =
|
|
31967
|
-
const providerConfigKey =
|
|
31968
|
-
const schemaSource =
|
|
31969
|
-
const upstreamSchemaHash =
|
|
31970
|
-
const fetchedAt =
|
|
32519
|
+
const name = cleanString3(rawTool.name, 200);
|
|
32520
|
+
const classification = cleanString3(rawTool.classification, 20);
|
|
32521
|
+
const transport = cleanString3(rawTool.transport, 20);
|
|
32522
|
+
const providerConfigKey = cleanString3(rawTool.providerConfigKey ?? rawTool.provider_config_key, 200);
|
|
32523
|
+
const schemaSource = cleanString3(rawTool.schemaSource ?? rawTool.schema_source, 50);
|
|
32524
|
+
const upstreamSchemaHash = cleanString3(rawTool.schemaHash ?? rawTool.schema_hash, 200);
|
|
32525
|
+
const fetchedAt = cleanString3(rawTool.fetchedAt ?? rawTool.fetched_at, 100);
|
|
31971
32526
|
const inputSchema = sanitizeToolSchema(rawTool.inputSchema ?? rawTool.input_schema);
|
|
31972
32527
|
const outputSchemaValue = rawTool.outputSchema ?? rawTool.output_schema;
|
|
31973
32528
|
const outputSchema = outputSchemaValue === void 0 ? void 0 : sanitizeToolSchema(outputSchemaValue);
|
|
@@ -31981,11 +32536,11 @@ function sanitizeNangoToolDescription(body, tool) {
|
|
|
31981
32536
|
true
|
|
31982
32537
|
);
|
|
31983
32538
|
}
|
|
31984
|
-
const protocolVersion = rawTool.protocolVersion === null || rawTool.protocol_version === null ? null :
|
|
32539
|
+
const protocolVersion = rawTool.protocolVersion === null || rawTool.protocol_version === null ? null : cleanString3(rawTool.protocolVersion ?? rawTool.protocol_version, 100);
|
|
31985
32540
|
const executionValue = rawTool.execution;
|
|
31986
|
-
const taskSupport =
|
|
31987
|
-
const title =
|
|
31988
|
-
const description =
|
|
32541
|
+
const taskSupport = isRecord4(executionValue) && (executionValue.taskSupport === "forbidden" || executionValue.taskSupport === "optional" || executionValue.taskSupport === "required") ? executionValue.taskSupport : null;
|
|
32542
|
+
const title = cleanString3(rawTool.title, 200);
|
|
32543
|
+
const description = cleanString3(rawTool.description, 12e3);
|
|
31989
32544
|
const annotations = sanitizeToolAnnotations(rawTool.annotations);
|
|
31990
32545
|
const icons = sanitizeToolIcons(rawTool.icons);
|
|
31991
32546
|
const execution = taskSupport ? { taskSupport } : void 0;
|
|
@@ -32028,10 +32583,13 @@ function sanitizeNangoToolDescription(body, tool) {
|
|
|
32028
32583
|
};
|
|
32029
32584
|
}
|
|
32030
32585
|
async function callScheduleConnectionExportPage(identity, input) {
|
|
32031
|
-
if (mainOwnsIntegrations() && input.dataset === "search_console_performance") {
|
|
32586
|
+
if (mainOwnsIntegrations() && (input.dataset === "search_console_performance" || input.dataset === "slack_channel_messages")) {
|
|
32032
32587
|
try {
|
|
32033
32588
|
return await callMainOwnedExportPage(identity, input);
|
|
32034
32589
|
} catch (error) {
|
|
32590
|
+
if (error instanceof Error && /^slack_export_(?:channel_required|cursor_invalid|range_invalid)$/.test(error.message)) {
|
|
32591
|
+
throw new NangoControlError("The Slack channel export request or continuation is invalid.", 400, "invalid_request", false);
|
|
32592
|
+
}
|
|
32035
32593
|
throw asNangoControlError(error);
|
|
32036
32594
|
}
|
|
32037
32595
|
}
|
|
@@ -32040,11 +32598,11 @@ async function callScheduleConnectionExportPage(identity, input) {
|
|
|
32040
32598
|
body: JSON.stringify({ identity, ...input })
|
|
32041
32599
|
}, 9e4);
|
|
32042
32600
|
const data = unwrapData(body);
|
|
32043
|
-
if (!
|
|
32601
|
+
if (!isRecord4(data) || data.ok !== true) {
|
|
32044
32602
|
throw new NangoControlError("Connected-data export returned an invalid page response.");
|
|
32045
32603
|
}
|
|
32046
|
-
const providerConfigKey =
|
|
32047
|
-
const dataset =
|
|
32604
|
+
const providerConfigKey = cleanString3(data.providerConfigKey, 128);
|
|
32605
|
+
const dataset = cleanString3(data.dataset, 64);
|
|
32048
32606
|
if (!providerConfigKey || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
|
|
32049
32607
|
throw new NangoControlError("Connected-data export returned invalid provider metadata.");
|
|
32050
32608
|
}
|
|
@@ -32054,7 +32612,7 @@ async function callScheduleConnectionExportPage(identity, input) {
|
|
|
32054
32612
|
if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
|
|
32055
32613
|
throw new NangoControlError("Connected-data export returned an invalid continuation cursor.");
|
|
32056
32614
|
}
|
|
32057
|
-
const rawCounts =
|
|
32615
|
+
const rawCounts = isRecord4(data.counts) ? data.counts : {};
|
|
32058
32616
|
const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
32059
32617
|
return {
|
|
32060
32618
|
providerConfigKey,
|
|
@@ -32072,11 +32630,11 @@ async function callScheduleConnectionExportPage(identity, input) {
|
|
|
32072
32630
|
};
|
|
32073
32631
|
}
|
|
32074
32632
|
function unwrapMcpToolJson(value) {
|
|
32075
|
-
if (!
|
|
32076
|
-
if (
|
|
32633
|
+
if (!isRecord4(value)) return value;
|
|
32634
|
+
if (isRecord4(value.structuredContent)) return value.structuredContent;
|
|
32077
32635
|
if (Array.isArray(value.content)) {
|
|
32078
|
-
const textPart = value.content.find((part) =>
|
|
32079
|
-
if (
|
|
32636
|
+
const textPart = value.content.find((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string");
|
|
32637
|
+
if (isRecord4(textPart) && typeof textPart.text === "string") {
|
|
32080
32638
|
try {
|
|
32081
32639
|
return JSON.parse(textPart.text);
|
|
32082
32640
|
} catch {
|
|
@@ -32089,13 +32647,26 @@ function unwrapMcpToolJson(value) {
|
|
|
32089
32647
|
function rowsFromToolResult(value, keys) {
|
|
32090
32648
|
const unwrapped = unwrapMcpToolJson(value);
|
|
32091
32649
|
if (Array.isArray(unwrapped)) return unwrapped;
|
|
32092
|
-
if (!
|
|
32650
|
+
if (!isRecord4(unwrapped)) return [];
|
|
32093
32651
|
for (const key of keys) if (Array.isArray(unwrapped[key])) return unwrapped[key];
|
|
32094
32652
|
return [];
|
|
32095
32653
|
}
|
|
32096
32654
|
async function callMainOwnedExportPage(identity, input) {
|
|
32097
32655
|
const connection = await getOwnedServiceConnection(identity, input.connectionId);
|
|
32098
32656
|
if (!connection) throw new NangoControlError("The service connection was not found.", 404, "connection_not_found", false);
|
|
32657
|
+
if (connection.providerConfigKey === "slack" && input.dataset === "slack_channel_messages") {
|
|
32658
|
+
return exportSlackChannelPage(input, {
|
|
32659
|
+
callTool: async (args) => callNangoToolDirect({
|
|
32660
|
+
identity,
|
|
32661
|
+
connectionId: input.connectionId,
|
|
32662
|
+
tool: args.tool,
|
|
32663
|
+
input: args.input,
|
|
32664
|
+
classification: "read",
|
|
32665
|
+
requestId: args.requestId,
|
|
32666
|
+
operationKind: "export"
|
|
32667
|
+
})
|
|
32668
|
+
});
|
|
32669
|
+
}
|
|
32099
32670
|
if (connection.providerConfigKey !== "google-search-console" || input.dataset !== "search_console_performance") {
|
|
32100
32671
|
throw new NangoControlError("This dataset has not moved to main-MCP export execution yet.", 400, "invalid_request", false);
|
|
32101
32672
|
}
|
|
@@ -32113,8 +32684,8 @@ async function callMainOwnedExportPage(identity, input) {
|
|
|
32113
32684
|
operationKind: "export"
|
|
32114
32685
|
});
|
|
32115
32686
|
const sites = rowsFromToolResult(listed, ["siteEntry", "sites", "items"]).flatMap((value) => {
|
|
32116
|
-
const row =
|
|
32117
|
-
const siteUrl2 = row ?
|
|
32687
|
+
const row = isRecord4(value) ? value : null;
|
|
32688
|
+
const siteUrl2 = row ? cleanString3(row.siteUrl ?? row.site_url, 2e3) : null;
|
|
32118
32689
|
return siteUrl2 ? [siteUrl2] : [];
|
|
32119
32690
|
}).sort().slice(0, 1e4);
|
|
32120
32691
|
if (siteIndex >= sites.length) {
|
|
@@ -32151,7 +32722,7 @@ async function callMainOwnedExportPage(identity, input) {
|
|
|
32151
32722
|
});
|
|
32152
32723
|
const rows = rowsFromToolResult(performance2, ["rows"]).slice(0, input.pageSize);
|
|
32153
32724
|
const records = rows.map((value, index) => {
|
|
32154
|
-
const row =
|
|
32725
|
+
const row = isRecord4(value) ? value : {};
|
|
32155
32726
|
const keys = Array.isArray(row.keys) ? row.keys.map((item) => String(item ?? "")) : [];
|
|
32156
32727
|
const date = keys[0] || null;
|
|
32157
32728
|
return {
|
|
@@ -32228,34 +32799,34 @@ function controlSecret2() {
|
|
|
32228
32799
|
if (!secret2) throw new ResendControlError("Resend connections are not configured.", 503);
|
|
32229
32800
|
return secret2;
|
|
32230
32801
|
}
|
|
32231
|
-
function
|
|
32802
|
+
function isRecord5(value) {
|
|
32232
32803
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
32233
32804
|
}
|
|
32234
32805
|
function unwrapData2(value) {
|
|
32235
|
-
return
|
|
32806
|
+
return isRecord5(value) && isRecord5(value.data) ? value.data : value;
|
|
32236
32807
|
}
|
|
32237
|
-
function
|
|
32808
|
+
function cleanString4(value, max = 300) {
|
|
32238
32809
|
if (typeof value !== "string") return null;
|
|
32239
32810
|
const trimmed = value.trim();
|
|
32240
32811
|
return trimmed ? trimmed.slice(0, max) : null;
|
|
32241
32812
|
}
|
|
32242
32813
|
function firstString2(record, keys, max = 300) {
|
|
32243
32814
|
for (const key of keys) {
|
|
32244
|
-
const value =
|
|
32815
|
+
const value = cleanString4(record[key], max);
|
|
32245
32816
|
if (value) return value;
|
|
32246
32817
|
}
|
|
32247
32818
|
return null;
|
|
32248
32819
|
}
|
|
32249
32820
|
function cleanTools2(value) {
|
|
32250
32821
|
if (!Array.isArray(value)) return [];
|
|
32251
|
-
return [...new Set(value.map((tool) =>
|
|
32822
|
+
return [...new Set(value.map((tool) => cleanString4(tool, 200)).filter((tool) => !!tool))].slice(0, 200);
|
|
32252
32823
|
}
|
|
32253
32824
|
function cleanStringArray2(value, maxItems = 50, maxLength = 500) {
|
|
32254
32825
|
if (!Array.isArray(value)) return [];
|
|
32255
|
-
return [...new Set(value.map((item) =>
|
|
32826
|
+
return [...new Set(value.map((item) => cleanString4(item, maxLength)).filter((item) => !!item))].slice(0, maxItems);
|
|
32256
32827
|
}
|
|
32257
32828
|
function cleanHttpsUrl2(value) {
|
|
32258
|
-
const candidate =
|
|
32829
|
+
const candidate = cleanString4(value, 2e3);
|
|
32259
32830
|
if (!candidate) return null;
|
|
32260
32831
|
try {
|
|
32261
32832
|
const url = new URL(candidate);
|
|
@@ -32273,7 +32844,7 @@ function cleanAuthorizationUrl(value) {
|
|
|
32273
32844
|
function arrayFromPayload2(value, keys) {
|
|
32274
32845
|
const unwrapped = unwrapData2(value);
|
|
32275
32846
|
if (Array.isArray(unwrapped)) return unwrapped;
|
|
32276
|
-
if (!
|
|
32847
|
+
if (!isRecord5(unwrapped)) return [];
|
|
32277
32848
|
for (const key of keys) {
|
|
32278
32849
|
if (Array.isArray(unwrapped[key])) return unwrapped[key];
|
|
32279
32850
|
}
|
|
@@ -32300,7 +32871,7 @@ async function controlRequest2(path5, init, timeoutMs = 2e4) {
|
|
|
32300
32871
|
body = {};
|
|
32301
32872
|
}
|
|
32302
32873
|
if (!response.ok) {
|
|
32303
|
-
const upstream =
|
|
32874
|
+
const upstream = isRecord5(body) ? cleanString4(body.error, 300) : null;
|
|
32304
32875
|
throw new ResendControlError(upstream || `Resend connection control failed (${response.status}).`);
|
|
32305
32876
|
}
|
|
32306
32877
|
return body;
|
|
@@ -32310,7 +32881,7 @@ async function getResendCatalog() {
|
|
|
32310
32881
|
const rows = arrayFromPayload2(body, ["providers", "catalog", "integrations", "services"]);
|
|
32311
32882
|
const result = [];
|
|
32312
32883
|
for (const row of rows) {
|
|
32313
|
-
if (!
|
|
32884
|
+
if (!isRecord5(row)) continue;
|
|
32314
32885
|
const id = firstString2(row, ["providerConfigKey", "provider_config_key", "id"]);
|
|
32315
32886
|
if (id !== RESEND_PROVIDER_CONFIG_KEY) continue;
|
|
32316
32887
|
const safeDefaultAllowedTools = cleanTools2(
|
|
@@ -32346,7 +32917,7 @@ async function getResendConnections(identity) {
|
|
|
32346
32917
|
const rows = arrayFromPayload2(body, ["connections"]);
|
|
32347
32918
|
const result = [];
|
|
32348
32919
|
for (const row of rows) {
|
|
32349
|
-
if (!
|
|
32920
|
+
if (!isRecord5(row)) continue;
|
|
32350
32921
|
const connectionId = firstString2(row, ["connectionId", "connection_id", "id"]);
|
|
32351
32922
|
const providerConfigKey = firstString2(row, ["providerConfigKey", "provider_config_key", "provider"]) || RESEND_PROVIDER_CONFIG_KEY;
|
|
32352
32923
|
if (!connectionId || providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) continue;
|
|
@@ -32390,7 +32961,7 @@ async function getResendConnections(identity) {
|
|
|
32390
32961
|
}
|
|
32391
32962
|
function sanitizeConnectSession2(body) {
|
|
32392
32963
|
const data = unwrapData2(body);
|
|
32393
|
-
if (!
|
|
32964
|
+
if (!isRecord5(data)) throw new ResendControlError("The Resend connection service returned an invalid connect session.");
|
|
32394
32965
|
const connectLink = cleanAuthorizationUrl(data.authorizationUrl ?? data.authorization_url ?? data.connectLink ?? data.connect_link);
|
|
32395
32966
|
if (!connectLink) throw new ResendControlError("The Resend connection service did not return a trusted authorization link.");
|
|
32396
32967
|
return {
|
|
@@ -32431,7 +33002,7 @@ async function setResendActionsEnabled(identity, connectionId, enabled) {
|
|
|
32431
33002
|
body: JSON.stringify({ identity, connectionId, enabled })
|
|
32432
33003
|
});
|
|
32433
33004
|
const data = unwrapData2(body);
|
|
32434
|
-
if (!
|
|
33005
|
+
if (!isRecord5(data) || !isRecord5(data.connection)) return enabled;
|
|
32435
33006
|
return data.connection.actionsEnabled === true || data.connection.actions_enabled === true;
|
|
32436
33007
|
}
|
|
32437
33008
|
async function callResendRead(identity, connectionId, tool, args) {
|
|
@@ -32440,7 +33011,7 @@ async function callResendRead(identity, connectionId, tool, args) {
|
|
|
32440
33011
|
body: JSON.stringify({ identity, connectionId, tool, input: args ?? {} })
|
|
32441
33012
|
});
|
|
32442
33013
|
const data = unwrapData2(body);
|
|
32443
|
-
return
|
|
33014
|
+
return isRecord5(data) ? data.result ?? data : data;
|
|
32444
33015
|
}
|
|
32445
33016
|
async function callResendAction(identity, connectionId, tool, input) {
|
|
32446
33017
|
const body = await controlRequest2("/api/internal/resend/actions/call", {
|
|
@@ -32448,7 +33019,7 @@ async function callResendAction(identity, connectionId, tool, input) {
|
|
|
32448
33019
|
body: JSON.stringify({ identity, connectionId, tool, input })
|
|
32449
33020
|
});
|
|
32450
33021
|
const data = unwrapData2(body);
|
|
32451
|
-
return
|
|
33022
|
+
return isRecord5(data) ? data.result ?? data : data;
|
|
32452
33023
|
}
|
|
32453
33024
|
async function describeResendTool(identity, connectionId, tool) {
|
|
32454
33025
|
const body = await controlRequest2("/api/internal/resend/describe", {
|
|
@@ -32456,12 +33027,12 @@ async function describeResendTool(identity, connectionId, tool) {
|
|
|
32456
33027
|
body: JSON.stringify({ identity, connectionId, tool })
|
|
32457
33028
|
});
|
|
32458
33029
|
const data = unwrapData2(body);
|
|
32459
|
-
const rawTool =
|
|
32460
|
-
if (!
|
|
33030
|
+
const rawTool = isRecord5(data) && isRecord5(data.tool) ? data.tool : data;
|
|
33031
|
+
if (!isRecord5(rawTool)) throw new ResendControlError("Resend returned an invalid tool description.");
|
|
32461
33032
|
const name = firstString2(rawTool, ["name"], 200);
|
|
32462
33033
|
const classification = firstString2(rawTool, ["classification", "kind"], 20);
|
|
32463
33034
|
const inputSchema = rawTool.inputSchema ?? rawTool.input_schema;
|
|
32464
|
-
if (!name || name !== tool || classification !== "read" && classification !== "action" || !
|
|
33035
|
+
if (!name || name !== tool || classification !== "read" && classification !== "action" || !isRecord5(inputSchema)) {
|
|
32465
33036
|
throw new ResendControlError("Resend returned an invalid tool description.");
|
|
32466
33037
|
}
|
|
32467
33038
|
return {
|
|
@@ -32478,10 +33049,10 @@ async function callResendExportPage(identity, input) {
|
|
|
32478
33049
|
body: JSON.stringify({ identity, ...input })
|
|
32479
33050
|
}, 9e4);
|
|
32480
33051
|
const data = unwrapData2(body);
|
|
32481
|
-
if (!
|
|
33052
|
+
if (!isRecord5(data) || data.ok !== true || data.providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) {
|
|
32482
33053
|
throw new ResendControlError("Resend export returned an invalid page response.");
|
|
32483
33054
|
}
|
|
32484
|
-
const dataset =
|
|
33055
|
+
const dataset = cleanString4(data.dataset, 64);
|
|
32485
33056
|
if (!dataset || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
|
|
32486
33057
|
throw new ResendControlError("Resend export returned invalid dataset metadata.");
|
|
32487
33058
|
}
|
|
@@ -32489,7 +33060,7 @@ async function callResendExportPage(identity, input) {
|
|
|
32489
33060
|
if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
|
|
32490
33061
|
throw new ResendControlError("Resend export returned an invalid continuation cursor.");
|
|
32491
33062
|
}
|
|
32492
|
-
const rawCounts =
|
|
33063
|
+
const rawCounts = isRecord5(data.counts) ? data.counts : {};
|
|
32493
33064
|
const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
32494
33065
|
return {
|
|
32495
33066
|
providerConfigKey: RESEND_PROVIDER_CONFIG_KEY,
|
|
@@ -33104,6 +33675,16 @@ app.get("/memory/note", auth2, async (c) => {
|
|
|
33104
33675
|
return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load note" }, 502);
|
|
33105
33676
|
}
|
|
33106
33677
|
});
|
|
33678
|
+
app.get("/memory/universe", auth2, async (c) => {
|
|
33679
|
+
const user = c.get("user");
|
|
33680
|
+
const includeConnections = c.req.query("includeConnections") === "1";
|
|
33681
|
+
try {
|
|
33682
|
+
const graph = await dbUniverse(resolveMemoryIdentity(user), includeConnections);
|
|
33683
|
+
return c.json({ ok: true, ...graph });
|
|
33684
|
+
} catch (err) {
|
|
33685
|
+
return c.json({ ok: false, error: err instanceof Error ? err.message : "could not build universe" }, 502);
|
|
33686
|
+
}
|
|
33687
|
+
});
|
|
33107
33688
|
app.put("/memory/note", auth2, async (c) => {
|
|
33108
33689
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
33109
33690
|
if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
|
|
@@ -33845,7 +34426,18 @@ app.post("/schedule-connections/actions/export", auth2, requireIntegrationsTier,
|
|
|
33845
34426
|
const body = await c.req.json().catch(() => ({}));
|
|
33846
34427
|
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
33847
34428
|
if (!connectionId) return c.json({ ok: false, error: "connectionId is required." }, 400);
|
|
33848
|
-
const
|
|
34429
|
+
const channelId = typeof body.channelId === "string" ? body.channelId.trim() : "";
|
|
34430
|
+
if (body.channelId !== void 0 && !channelId) {
|
|
34431
|
+
return c.json({ ok: false, error: "channelId must be a non-empty Slack conversation ID." }, 400);
|
|
34432
|
+
}
|
|
34433
|
+
if (body.includeThreads !== void 0 && typeof body.includeThreads !== "boolean") {
|
|
34434
|
+
return c.json({ ok: false, error: "includeThreads must be a boolean when provided." }, 400);
|
|
34435
|
+
}
|
|
34436
|
+
if (body.allTime !== void 0 && typeof body.allTime !== "boolean") {
|
|
34437
|
+
return c.json({ ok: false, error: "allTime must be a boolean when provided." }, 400);
|
|
34438
|
+
}
|
|
34439
|
+
const rawRequestedDataset = typeof body.dataset === "string" ? body.dataset.trim() : "auto";
|
|
34440
|
+
const requestedDataset = rawRequestedDataset === "auto" && channelId ? "slack_channel_messages" : rawRequestedDataset;
|
|
33849
34441
|
const datasets = [...CONNECTED_DATA_DATASETS];
|
|
33850
34442
|
if (!datasets.includes(requestedDataset)) {
|
|
33851
34443
|
return c.json({ ok: false, error: `dataset must be one of: ${datasets.join(", ")}.` }, 400);
|
|
@@ -33868,21 +34460,37 @@ app.post("/schedule-connections/actions/export", auth2, requireIntegrationsTier,
|
|
|
33868
34460
|
if (body.continuation !== void 0 && !continuation) {
|
|
33869
34461
|
return c.json({ ok: false, error: "continuation must be the complete object returned by a prior export." }, 400);
|
|
33870
34462
|
}
|
|
34463
|
+
if (body.allTime === true && requestedDataset !== "slack_channel_messages") {
|
|
34464
|
+
return c.json({ ok: false, error: "allTime is supported only for Slack channel exports." }, 400);
|
|
34465
|
+
}
|
|
34466
|
+
if (body.allTime === true && (body.from !== void 0 || body.lastDays !== void 0 || continuation)) {
|
|
34467
|
+
return c.json({ ok: false, error: "Do not pass allTime with from, lastDays, or continuation." }, 400);
|
|
34468
|
+
}
|
|
34469
|
+
if (requestedDataset === "slack_channel_messages" && !channelId && !continuation) {
|
|
34470
|
+
return c.json({ ok: false, error: "channelId is required for a Slack channel export." }, 400);
|
|
34471
|
+
}
|
|
33871
34472
|
try {
|
|
33872
34473
|
const useResend = await isResendConnection(user.email, connectionId);
|
|
33873
34474
|
const resolved = resolveConnectedDataExportRequest({
|
|
33874
34475
|
requestedDataset,
|
|
33875
|
-
from: typeof body.from === "string" ? body.from : void 0,
|
|
34476
|
+
from: body.allTime === true ? "1970-01-01T00:00:00.000Z" : typeof body.from === "string" ? body.from : void 0,
|
|
33876
34477
|
to: typeof body.to === "string" ? body.to : void 0,
|
|
33877
34478
|
lastDays,
|
|
33878
34479
|
cursor: typeof body.cursor === "string" ? body.cursor : void 0,
|
|
33879
|
-
continuation
|
|
34480
|
+
continuation,
|
|
34481
|
+
scope: channelId ? {
|
|
34482
|
+
slack: {
|
|
34483
|
+
channelId,
|
|
34484
|
+
includeThreads: body.includeThreads !== false
|
|
34485
|
+
}
|
|
34486
|
+
} : void 0
|
|
33880
34487
|
});
|
|
33881
34488
|
const result = await collectConnectedDataExport({
|
|
33882
34489
|
ownerId: sha256Hex(user.api_key).slice(0, 24),
|
|
33883
34490
|
connectionId,
|
|
33884
34491
|
dataset: resolved.dataset,
|
|
33885
34492
|
range: resolved.range,
|
|
34493
|
+
...resolved.scope ? { scope: resolved.scope } : {},
|
|
33886
34494
|
maxItems,
|
|
33887
34495
|
forceArtifact: body.delivery === "artifact",
|
|
33888
34496
|
...resolved.cursor ? { cursor: resolved.cursor } : {},
|
|
@@ -35713,4 +36321,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
35713
36321
|
export {
|
|
35714
36322
|
app
|
|
35715
36323
|
};
|
|
35716
|
-
//# sourceMappingURL=server-
|
|
36324
|
+
//# sourceMappingURL=server-2JJPCZH4.js.map
|