mcp-scraper 0.64.0 → 0.65.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/CHANGELOG.md +16 -0
- package/README.md +2 -2
- package/dist/bin/api-server.cjs +1028 -497
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- 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 +2 -2
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +49 -9
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +4 -4
- package/dist/{chunk-TRSZEK2D.js → chunk-2XUXVKT4.js} +3 -3
- package/dist/{chunk-TRSZEK2D.js.map → chunk-2XUXVKT4.js.map} +1 -1
- package/dist/chunk-5BODYBIP.js +7 -0
- package/dist/chunk-5BODYBIP.js.map +1 -0
- package/dist/{chunk-BRKCPONM.js → chunk-5PIO7QBG.js} +2 -2
- package/dist/{chunk-5QYSY5KI.js → chunk-AMLFKPLL.js} +139 -3
- package/dist/chunk-AMLFKPLL.js.map +1 -0
- package/dist/{chunk-3NXLGTC6.js → chunk-DCWXVAQT.js} +2 -2
- package/dist/{chunk-3NXLGTC6.js.map → chunk-DCWXVAQT.js.map} +1 -1
- package/dist/{chunk-EEW3DMI5.js → chunk-PSKRQDGN.js} +50 -10
- package/dist/chunk-PSKRQDGN.js.map +1 -0
- package/dist/{extract-bundle-FW23CEMG.js → extract-bundle-UWKJT4MU.js} +371 -14
- package/dist/extract-bundle-UWKJT4MU.js.map +1 -0
- package/dist/{server-TMAWQYZE.js → server-AFEA235I.js} +170 -163
- package/dist/server-AFEA235I.js.map +1 -0
- package/dist/{site-extract-repository-UXSFD2QM.js → site-extract-repository-J3RH3MOS.js} +3 -3
- package/dist/{worker-DD243CON.js → worker-YKRIK5LS.js} +2 -2
- package/package.json +3 -1
- package/dist/chunk-5QYSY5KI.js.map +0 -1
- package/dist/chunk-DYSXI6QU.js +0 -7
- package/dist/chunk-DYSXI6QU.js.map +0 -1
- package/dist/chunk-EEW3DMI5.js.map +0 -1
- package/dist/extract-bundle-FW23CEMG.js.map +0 -1
- package/dist/server-TMAWQYZE.js.map +0 -1
- /package/dist/{chunk-BRKCPONM.js.map → chunk-5PIO7QBG.js.map} +0 -0
- /package/dist/{site-extract-repository-UXSFD2QM.js.map → site-extract-repository-J3RH3MOS.js.map} +0 -0
- /package/dist/{worker-DD243CON.js.map → worker-YKRIK5LS.js.map} +0 -0
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
classifyExtractionProblem,
|
|
3
|
+
commonsSemanticSearchConfigured,
|
|
3
4
|
discoverStaticMedia,
|
|
5
|
+
embedQueuedCommonsDocuments,
|
|
4
6
|
extractionProblemResponse,
|
|
5
7
|
extractionWireStatus,
|
|
6
8
|
harvestPageMedia,
|
|
7
9
|
publicizeExtractionFailure,
|
|
10
|
+
queuedCommonsDocumentCount,
|
|
11
|
+
semanticCommonsEntityScores,
|
|
8
12
|
typeFromMime
|
|
9
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-AMLFKPLL.js";
|
|
10
14
|
import {
|
|
11
15
|
REQUIRED_LOCATION_STATE_CODES,
|
|
12
16
|
importHostedCensusPlacesCsv,
|
|
@@ -199,7 +203,7 @@ import {
|
|
|
199
203
|
resolveDeploymentProfile,
|
|
200
204
|
resolveLocalSourcebookSchemaType,
|
|
201
205
|
transcribeMediaUrl
|
|
202
|
-
} from "./chunk-
|
|
206
|
+
} from "./chunk-PSKRQDGN.js";
|
|
203
207
|
import {
|
|
204
208
|
auditImageUrls,
|
|
205
209
|
auditImages,
|
|
@@ -261,7 +265,7 @@ import {
|
|
|
261
265
|
} from "./chunk-UVFB24RZ.js";
|
|
262
266
|
import {
|
|
263
267
|
PACKAGE_VERSION
|
|
264
|
-
} from "./chunk-
|
|
268
|
+
} from "./chunk-5BODYBIP.js";
|
|
265
269
|
import {
|
|
266
270
|
SITE_EXTRACT_ARTIFACT_PREFIX,
|
|
267
271
|
abandonExtractSettlement,
|
|
@@ -292,7 +296,7 @@ import {
|
|
|
292
296
|
setExtractJobTotal,
|
|
293
297
|
settleExtractJob,
|
|
294
298
|
terminalExtractJobStatus
|
|
295
|
-
} from "./chunk-
|
|
299
|
+
} from "./chunk-5PIO7QBG.js";
|
|
296
300
|
import {
|
|
297
301
|
createPrivateArtifact,
|
|
298
302
|
privateArtifactOwnerId,
|
|
@@ -352,7 +356,7 @@ import {
|
|
|
352
356
|
tierChangeLeavesPrivateOffer,
|
|
353
357
|
tierCreditsPerInterval,
|
|
354
358
|
vendorCostUsdToMc
|
|
355
|
-
} from "./chunk-
|
|
359
|
+
} from "./chunk-2XUXVKT4.js";
|
|
356
360
|
import {
|
|
357
361
|
CaptchaError,
|
|
358
362
|
MapsSearchIncompletePageError,
|
|
@@ -5291,8 +5295,9 @@ async function fetchText(url, auditedHost, timeoutMs = 1e4) {
|
|
|
5291
5295
|
}
|
|
5292
5296
|
function extractSitemapLocs(xml) {
|
|
5293
5297
|
const locs = [];
|
|
5294
|
-
for (const m of xml.matchAll(/<loc
|
|
5295
|
-
const candidate = m[1].trim();
|
|
5298
|
+
for (const m of xml.matchAll(/<loc\b[^>]*>\s*(?:<!\[CDATA\[([\s\S]*?)\]\]>|([^<]+))\s*<\/loc>/gi)) {
|
|
5299
|
+
const candidate = (m[1] ?? m[2] ?? "").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'|'/gi, "'").trim();
|
|
5300
|
+
if (!/^https?:\/\//i.test(candidate)) continue;
|
|
5296
5301
|
if (boundedDiscoveredUrl(candidate)) locs.push(candidate);
|
|
5297
5302
|
}
|
|
5298
5303
|
return locs;
|
|
@@ -5519,6 +5524,7 @@ import { chromium as chromium3 } from "playwright";
|
|
|
5519
5524
|
var DEFAULT_CONCURRENCY = 1;
|
|
5520
5525
|
var DEFAULT_URLS_PER_BROWSER = 10;
|
|
5521
5526
|
var DEFAULT_HEADLESS = true;
|
|
5527
|
+
var MAX_RENDERED_DOM_BYTES = 256 * 1024;
|
|
5522
5528
|
async function discoverSitemapUrls2(startUrl, maxUrls) {
|
|
5523
5529
|
const domain = new URL(startUrl).hostname.replace(/^www\./, "");
|
|
5524
5530
|
return discoverSitemapUrls(startUrl, domain, maxUrls);
|
|
@@ -5538,7 +5544,52 @@ async function createBrowserWithRetry(client2, headless) {
|
|
|
5538
5544
|
throw new Error("unreachable");
|
|
5539
5545
|
}
|
|
5540
5546
|
var PAGE_HARD_TIMEOUT_MS = 35e3;
|
|
5541
|
-
async function
|
|
5547
|
+
async function settleRenderedPage(page) {
|
|
5548
|
+
await page.waitForLoadState("load", { timeout: 8e3 }).catch(() => void 0);
|
|
5549
|
+
await page.waitForLoadState("networkidle", { timeout: 5e3 }).catch(() => void 0);
|
|
5550
|
+
await page.evaluate(async () => {
|
|
5551
|
+
const wait = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
5552
|
+
const height = Math.max(document.documentElement?.scrollHeight ?? 0, document.body?.scrollHeight ?? 0);
|
|
5553
|
+
const viewport = Math.max(1, window.innerHeight);
|
|
5554
|
+
const steps = Math.min(12, Math.max(1, Math.ceil(height / viewport)));
|
|
5555
|
+
for (let index = 1; index <= steps; index++) {
|
|
5556
|
+
window.scrollTo(0, Math.min(height, index * viewport));
|
|
5557
|
+
await wait(120);
|
|
5558
|
+
}
|
|
5559
|
+
window.scrollTo(0, 0);
|
|
5560
|
+
await wait(250);
|
|
5561
|
+
}).catch(() => void 0);
|
|
5562
|
+
}
|
|
5563
|
+
async function captureInertRenderedDom(page) {
|
|
5564
|
+
return page.evaluate((maxBytes) => {
|
|
5565
|
+
const root = document.documentElement?.cloneNode(true);
|
|
5566
|
+
if (!root) return { renderedDom: "", renderedDomTruncated: false, renderedDomSanitized: true };
|
|
5567
|
+
root.querySelectorAll('script,noscript,template,iframe,object,embed,meta[http-equiv="refresh"]').forEach((node) => node.remove());
|
|
5568
|
+
root.querySelectorAll("*").forEach((node) => {
|
|
5569
|
+
for (const attribute of [...node.attributes]) {
|
|
5570
|
+
const name = attribute.name.toLowerCase();
|
|
5571
|
+
const value = attribute.value.trim().toLowerCase();
|
|
5572
|
+
if (name.startsWith("on") || (name === "href" || name === "src" || name === "action") && value.startsWith("javascript:")) {
|
|
5573
|
+
node.removeAttribute(attribute.name);
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
});
|
|
5577
|
+
const html = "<!doctype html>\n" + root.outerHTML;
|
|
5578
|
+
const encoder = new TextEncoder();
|
|
5579
|
+
if (encoder.encode(html).byteLength <= maxBytes) {
|
|
5580
|
+
return { renderedDom: html, renderedDomTruncated: false, renderedDomSanitized: true };
|
|
5581
|
+
}
|
|
5582
|
+
let low = 0;
|
|
5583
|
+
let high = html.length;
|
|
5584
|
+
while (low < high) {
|
|
5585
|
+
const middle = Math.ceil((low + high) / 2);
|
|
5586
|
+
if (encoder.encode(html.slice(0, middle)).byteLength <= maxBytes) low = middle;
|
|
5587
|
+
else high = middle - 1;
|
|
5588
|
+
}
|
|
5589
|
+
return { renderedDom: html.slice(0, low), renderedDomTruncated: true, renderedDomSanitized: true };
|
|
5590
|
+
}, MAX_RENDERED_DOM_BYTES);
|
|
5591
|
+
}
|
|
5592
|
+
async function crawlPage(context, url, headless, telemetry, options) {
|
|
5542
5593
|
const startedAt = Date.now();
|
|
5543
5594
|
let page = null;
|
|
5544
5595
|
let guardState = null;
|
|
@@ -5547,6 +5598,7 @@ async function crawlPage(context, url, headless, telemetry) {
|
|
|
5547
5598
|
guardState = await installBrowserRequestGuard(page, url);
|
|
5548
5599
|
const work = (async (p) => {
|
|
5549
5600
|
const resp = await p.goto(url, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
5601
|
+
if (options.renderJavaScript || options.captureRenderedDom) await settleRenderedPage(p);
|
|
5550
5602
|
let html = await p.evaluate((maxBytes) => {
|
|
5551
5603
|
const value = document.documentElement?.outerHTML ?? "";
|
|
5552
5604
|
if (new TextEncoder().encode(value).byteLength > maxBytes) throw new Error(`Browser HTML exceeds ${maxBytes} byte limit`);
|
|
@@ -5582,6 +5634,7 @@ async function crawlPage(context, url, headless, telemetry) {
|
|
|
5582
5634
|
};
|
|
5583
5635
|
}
|
|
5584
5636
|
const isHttpError = resp != null && (resp.status() < 200 || resp.status() >= 300);
|
|
5637
|
+
const rendered = options.captureRenderedDom ? await captureInertRenderedDom(p) : null;
|
|
5585
5638
|
return {
|
|
5586
5639
|
url,
|
|
5587
5640
|
html,
|
|
@@ -5593,10 +5646,12 @@ async function crawlPage(context, url, headless, telemetry) {
|
|
|
5593
5646
|
...isHttpError ? { errorCode: `browser_http_${resp.status()}`, error: `Browser fetch returned HTTP ${resp.status()}` } : {},
|
|
5594
5647
|
browserMode: headless ? "stealth_headless" : "stealth_headful",
|
|
5595
5648
|
challengeDetected: false,
|
|
5596
|
-
stopReason: isHttpError ? "http_error" : "usable_page"
|
|
5649
|
+
stopReason: isHttpError ? "http_error" : "usable_page",
|
|
5650
|
+
...rendered ?? {}
|
|
5597
5651
|
};
|
|
5598
5652
|
})(page);
|
|
5599
|
-
const
|
|
5653
|
+
const hardTimeoutMs = options.renderJavaScript || options.captureRenderedDom ? 55e3 : PAGE_HARD_TIMEOUT_MS;
|
|
5654
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("page-hard-timeout")), hardTimeoutMs));
|
|
5600
5655
|
return await Promise.race([work, timeout]);
|
|
5601
5656
|
} catch (error) {
|
|
5602
5657
|
const message = guardState?.blockedReason ?? (error instanceof Error ? error.message : String(error));
|
|
@@ -5668,7 +5723,7 @@ async function crawlWithRotation(urls, opts) {
|
|
|
5668
5723
|
}
|
|
5669
5724
|
const context = await browser.newContext(withRuntimeUserAgent({ serviceWorkers: "block" }, runtimeUserAgent));
|
|
5670
5725
|
try {
|
|
5671
|
-
r = await crawlPage(context, url, passHeadless, { client: client2, sessionId: kb.session_id });
|
|
5726
|
+
r = await crawlPage(context, url, passHeadless, { client: client2, sessionId: kb.session_id }, opts);
|
|
5672
5727
|
} finally {
|
|
5673
5728
|
await context.close().catch(() => {
|
|
5674
5729
|
});
|
|
@@ -6826,17 +6881,29 @@ async function mapWithConcurrency(items, concurrency, fn) {
|
|
|
6826
6881
|
}
|
|
6827
6882
|
async function extractPagesRotating(urls, opts) {
|
|
6828
6883
|
const uniqueUrls = [...new Set(urls)];
|
|
6829
|
-
const
|
|
6884
|
+
const initialPages = await mapWithConcurrency(uniqueUrls, opts.concurrency ?? EXTRACT_CONCURRENCY, async (url) => {
|
|
6830
6885
|
const checked = await validatePublicHttpUrl(url, { field: "page URL" });
|
|
6831
|
-
if (checked.error || !checked.parsed)
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6886
|
+
if (checked.error || !checked.parsed) {
|
|
6887
|
+
if (!opts.forceBrowserRender) return fetchPagePlain(url);
|
|
6888
|
+
const attempt2 = failedAttempt("browser", null, 0, "unsafe_url", checked.error ?? "Page URL was rejected.");
|
|
6889
|
+
return emptyPageData(url, null, "browser", { code: attempt2.errorCode, message: attempt2.error, attempts: [attempt2] });
|
|
6890
|
+
}
|
|
6891
|
+
if (normalizeUrl(checked.parsed.href, checked.parsed.href)) {
|
|
6892
|
+
if (!opts.forceBrowserRender) return fetchPagePlain(url);
|
|
6893
|
+
return emptyPageData(url, null, "browser", {
|
|
6894
|
+
code: "browser_pending",
|
|
6895
|
+
message: "Awaiting forced JavaScript rendering.",
|
|
6896
|
+
attempts: []
|
|
6897
|
+
});
|
|
6898
|
+
}
|
|
6899
|
+
const method = opts.forceBrowserRender ? "browser" : "fetch";
|
|
6900
|
+
const attempt = failedAttempt(method, null, 0, "non_page_url", "The URL points to an asset, feed, API, account route, or other non-page path excluded from text-page extraction.");
|
|
6901
|
+
const page = emptyPageData(url, null, method, { code: attempt.errorCode, message: attempt.error, attempts: [attempt] });
|
|
6835
6902
|
page.contentKind = "excluded_url";
|
|
6836
6903
|
return page;
|
|
6837
6904
|
});
|
|
6838
|
-
const byUrl = new Map(
|
|
6839
|
-
const browserTargets =
|
|
6905
|
+
const byUrl = new Map(initialPages.map((page) => [page.url, page]));
|
|
6906
|
+
const browserTargets = opts.forceBrowserRender ? initialPages.filter((page) => page.failureCode === "browser_pending").map((page) => page.url) : initialPages.filter((page) => !isPageExtractionSuccessful(page) && ![
|
|
6840
6907
|
"unsafe_url",
|
|
6841
6908
|
"unsafe_redirect",
|
|
6842
6909
|
"cross_site_redirect",
|
|
@@ -6848,7 +6915,9 @@ async function extractPagesRotating(urls, opts) {
|
|
|
6848
6915
|
const fetched = await crawlWithRotation(browserTargets, {
|
|
6849
6916
|
apiKey: opts.kernelApiKey,
|
|
6850
6917
|
concurrency: opts.concurrency,
|
|
6851
|
-
urlsPerBrowser: opts.urlsPerBrowser
|
|
6918
|
+
urlsPerBrowser: opts.urlsPerBrowser,
|
|
6919
|
+
renderJavaScript: opts.forceBrowserRender,
|
|
6920
|
+
captureRenderedDom: opts.captureRenderedDom
|
|
6852
6921
|
});
|
|
6853
6922
|
for (const result of fetched) {
|
|
6854
6923
|
const plainPage = byUrl.get(result.url);
|
|
@@ -6865,12 +6934,17 @@ async function extractPagesRotating(urls, opts) {
|
|
|
6865
6934
|
const browserOk = !redirectRejected && Boolean(result.html) && result.status >= 200 && result.status < 300;
|
|
6866
6935
|
if (browserOk) {
|
|
6867
6936
|
const replay = parseWaybackReplayUrl(result.url);
|
|
6868
|
-
|
|
6937
|
+
const page = parsePageData(result.url, result.renderedDom ?? result.html, result.status, "browser", {
|
|
6869
6938
|
headers: result.headers,
|
|
6870
6939
|
responseTimeMs,
|
|
6871
6940
|
redirectUrl: result.redirectUrl,
|
|
6872
6941
|
documentUrl: replay?.originalUrl ?? result.redirectUrl ?? result.url
|
|
6873
|
-
}, [...priorAttempts, ...browserAttemptEntries(result, true)])
|
|
6942
|
+
}, [...priorAttempts, ...browserAttemptEntries(result, true)]);
|
|
6943
|
+
if (result.renderedDom != null) {
|
|
6944
|
+
page.renderedDomTruncated = result.renderedDomTruncated === true;
|
|
6945
|
+
page.renderedDomSanitized = result.renderedDomSanitized === true;
|
|
6946
|
+
}
|
|
6947
|
+
byUrl.set(result.url, page);
|
|
6874
6948
|
continue;
|
|
6875
6949
|
}
|
|
6876
6950
|
const code = redirectRejected ? "unsafe_browser_redirect" : result.errorCode ?? (result.status > 0 ? `browser_http_${result.status}` : result.html ? "browser_invalid_status" : "browser_empty_response");
|
|
@@ -6884,7 +6958,16 @@ async function extractPagesRotating(urls, opts) {
|
|
|
6884
6958
|
}));
|
|
6885
6959
|
}
|
|
6886
6960
|
}
|
|
6887
|
-
|
|
6961
|
+
if (opts.forceBrowserRender && !opts.kernelApiKey) {
|
|
6962
|
+
for (const url of browserTargets) {
|
|
6963
|
+
byUrl.set(url, emptyPageData(url, null, "browser", {
|
|
6964
|
+
code: "browser_service_unconfigured",
|
|
6965
|
+
message: "Forced JavaScript rendering requires the hosted browser service.",
|
|
6966
|
+
attempts: [failedAttempt("browser", null, 0, "browser_service_unconfigured", "Forced JavaScript rendering requires the hosted browser service.")]
|
|
6967
|
+
}));
|
|
6968
|
+
}
|
|
6969
|
+
}
|
|
6970
|
+
return urls.map((url) => byUrl.get(url) ?? emptyPageData(url, null, opts.forceBrowserRender ? "browser" : "fetch", {
|
|
6888
6971
|
code: "result_missing",
|
|
6889
6972
|
message: "No extraction result was recorded."
|
|
6890
6973
|
}));
|
|
@@ -9326,6 +9409,8 @@ var siteExtractFn = inngest.createFunction(
|
|
|
9326
9409
|
const maxPages = Number(job.options.maxPages ?? 1e4);
|
|
9327
9410
|
const concurrency = Number(job.options.concurrency ?? 1);
|
|
9328
9411
|
const urlsPerBrowser = Number(job.options.urlsPerBrowser ?? job.options.rotateProxyEvery ?? 10);
|
|
9412
|
+
const captureRenderedDom = job.options.captureRenderedDom === true;
|
|
9413
|
+
const forceBrowserRender = job.options.renderJavaScript === true || job.options.semanticSimilarity === true || captureRenderedDom;
|
|
9329
9414
|
const batchSize = siteExtractBatchSize(concurrency, urlsPerBrowser);
|
|
9330
9415
|
const waybackReplay = parseWaybackReplayUrl(job.startUrl);
|
|
9331
9416
|
const waybackTimeline = job.options.waybackTimeline;
|
|
@@ -9408,7 +9493,13 @@ var siteExtractFn = inngest.createFunction(
|
|
|
9408
9493
|
if (!batch.length) break;
|
|
9409
9494
|
const frontierCapacity = Math.max(0, maxPages - seen.size);
|
|
9410
9495
|
const newLinks = await step.run(`crawl-batch-${i}`, async () => {
|
|
9411
|
-
const pages = await runWithCostContext(costContext, () => extractPagesRotating(batch, {
|
|
9496
|
+
const pages = await runWithCostContext(costContext, () => extractPagesRotating(batch, {
|
|
9497
|
+
kernelApiKey: key,
|
|
9498
|
+
concurrency,
|
|
9499
|
+
urlsPerBrowser,
|
|
9500
|
+
forceBrowserRender,
|
|
9501
|
+
captureRenderedDom
|
|
9502
|
+
}));
|
|
9412
9503
|
const storedPages = pages.map((p) => withWaybackProvenance({
|
|
9413
9504
|
...p,
|
|
9414
9505
|
// parsePageData deliberately keeps large bodies non-enumerable so
|
|
@@ -9462,7 +9553,7 @@ var siteExtractFn = inngest.createFunction(
|
|
|
9462
9553
|
});
|
|
9463
9554
|
}) : null;
|
|
9464
9555
|
const finalized = await step.run("finalize", async () => {
|
|
9465
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
9556
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-UWKJT4MU.js");
|
|
9466
9557
|
const current = await getExtractJob(jobId2);
|
|
9467
9558
|
if (!current) throw new Error("extract job disappeared before finalization");
|
|
9468
9559
|
const stored = await assembleExtractArtifacts(current, { branding, imageAudit });
|
|
@@ -16187,6 +16278,11 @@ async function prepareSiteExtractStart(input) {
|
|
|
16187
16278
|
urlsPerBrowser: input.urlsPerBrowser,
|
|
16188
16279
|
formats: input.formats,
|
|
16189
16280
|
downloadImages: input.downloadImages,
|
|
16281
|
+
renderJavaScript: input.renderJavaScript === true,
|
|
16282
|
+
captureRenderedDom: input.captureRenderedDom === true,
|
|
16283
|
+
semanticSimilarity: input.semanticSimilarity === true,
|
|
16284
|
+
similarityThreshold: input.similarityThreshold,
|
|
16285
|
+
similarityMaxPairs: input.similarityMaxPairs,
|
|
16190
16286
|
...input.waybackReplay ? {
|
|
16191
16287
|
waybackReplay: input.waybackReplay,
|
|
16192
16288
|
disableLinkDiscovery: true
|
|
@@ -19383,7 +19479,27 @@ var ExtractSiteBodySchema = z17.object({
|
|
|
19383
19479
|
downloadImages: z17.boolean().optional(),
|
|
19384
19480
|
preserveMedia: z17.boolean().optional(),
|
|
19385
19481
|
delivery: z17.enum(["auto", "artifact"]).optional(),
|
|
19386
|
-
formats: z17.array(z17.enum(["markdown", "html", "links", "json", "images", "branding", "issues"])).optional()
|
|
19482
|
+
formats: z17.array(z17.enum(["markdown", "html", "links", "json", "images", "branding", "issues"])).optional(),
|
|
19483
|
+
renderJavaScript: z17.boolean().optional(),
|
|
19484
|
+
captureRenderedDom: z17.boolean().optional(),
|
|
19485
|
+
semanticSimilarity: z17.boolean().optional(),
|
|
19486
|
+
similarityThreshold: z17.number().min(0).max(1).optional(),
|
|
19487
|
+
similarityMaxPairs: z17.number().int().min(1).max(5e4).optional()
|
|
19488
|
+
}).superRefine((value, ctx) => {
|
|
19489
|
+
if (value.semanticSimilarity && (value.maxPages ?? 100) > 500) {
|
|
19490
|
+
ctx.addIssue({
|
|
19491
|
+
code: z17.ZodIssueCode.custom,
|
|
19492
|
+
path: ["maxPages"],
|
|
19493
|
+
message: "semanticSimilarity supports at most 500 pages per analysis."
|
|
19494
|
+
});
|
|
19495
|
+
}
|
|
19496
|
+
if (value.semanticSimilarity && value.wayback) {
|
|
19497
|
+
ctx.addIssue({
|
|
19498
|
+
code: z17.ZodIssueCode.custom,
|
|
19499
|
+
path: ["wayback"],
|
|
19500
|
+
message: "semanticSimilarity analyzes one live rendered corpus and cannot be combined with a Wayback timeline."
|
|
19501
|
+
});
|
|
19502
|
+
}
|
|
19387
19503
|
});
|
|
19388
19504
|
var SiteExportReadBodySchema = z17.object({
|
|
19389
19505
|
jobId: z17.string().trim().min(1),
|
|
@@ -36327,134 +36443,6 @@ async function hostEntityImages(input) {
|
|
|
36327
36443
|
return rewrite;
|
|
36328
36444
|
}
|
|
36329
36445
|
|
|
36330
|
-
// src/api/commons-embeddings.ts
|
|
36331
|
-
import { neon } from "@neondatabase/serverless";
|
|
36332
|
-
var COMMONS_EMBED_PROVIDER = "jina";
|
|
36333
|
-
var _vectorSql = null;
|
|
36334
|
-
var vectorSchemaReady = false;
|
|
36335
|
-
function commonsEmbedModel() {
|
|
36336
|
-
return (process.env.JINA_EMBED_MODEL ?? "jina-embeddings-v5-omni-small").trim();
|
|
36337
|
-
}
|
|
36338
|
-
function commonsEmbedDim() {
|
|
36339
|
-
return Number((process.env.JINA_EMBED_DIM ?? "1024").trim());
|
|
36340
|
-
}
|
|
36341
|
-
function commonsSemanticSearchConfigured() {
|
|
36342
|
-
return Boolean(process.env.JINA_API_KEY?.trim() && process.env.MEMORY_DATABASE_URL?.trim());
|
|
36343
|
-
}
|
|
36344
|
-
function vectorSql() {
|
|
36345
|
-
if (_vectorSql) return _vectorSql;
|
|
36346
|
-
const url = process.env.MEMORY_DATABASE_URL?.trim();
|
|
36347
|
-
if (!url) throw new Error("MEMORY_DATABASE_URL is not set; Commons semantic search needs the shared Postgres.");
|
|
36348
|
-
_vectorSql = neon(url);
|
|
36349
|
-
return _vectorSql;
|
|
36350
|
-
}
|
|
36351
|
-
async function ensureCommonsVectorSchema() {
|
|
36352
|
-
if (vectorSchemaReady) return;
|
|
36353
|
-
const dimension = commonsEmbedDim();
|
|
36354
|
-
await vectorSql().query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
36355
|
-
await vectorSql().query(`
|
|
36356
|
-
CREATE TABLE IF NOT EXISTS commons_index_vectors (
|
|
36357
|
-
document_id TEXT PRIMARY KEY,
|
|
36358
|
-
entity_id TEXT NOT NULL,
|
|
36359
|
-
document_type TEXT NOT NULL,
|
|
36360
|
-
title TEXT NOT NULL,
|
|
36361
|
-
embedding vector(${dimension}) NOT NULL,
|
|
36362
|
-
model TEXT NOT NULL,
|
|
36363
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
36364
|
-
)
|
|
36365
|
-
`);
|
|
36366
|
-
await vectorSql().query("CREATE INDEX IF NOT EXISTS commons_index_vectors_entity ON commons_index_vectors(entity_id)");
|
|
36367
|
-
vectorSchemaReady = true;
|
|
36368
|
-
}
|
|
36369
|
-
async function embedCommonsTexts(texts) {
|
|
36370
|
-
const apiKey = process.env.JINA_API_KEY?.trim();
|
|
36371
|
-
if (!apiKey) throw new Error("JINA_API_KEY is not set; Commons semantic search cannot embed.");
|
|
36372
|
-
if (!texts.length) return [];
|
|
36373
|
-
const response = await fetch("https://api.jina.ai/v1/embeddings", {
|
|
36374
|
-
method: "POST",
|
|
36375
|
-
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
36376
|
-
body: JSON.stringify({
|
|
36377
|
-
model: commonsEmbedModel(),
|
|
36378
|
-
dimensions: commonsEmbedDim(),
|
|
36379
|
-
input: texts.map((text2) => ({ text: text2.slice(0, 8e3) }))
|
|
36380
|
-
}),
|
|
36381
|
-
signal: AbortSignal.timeout(6e4)
|
|
36382
|
-
});
|
|
36383
|
-
if (!response.ok) {
|
|
36384
|
-
throw new Error(`Jina embedding request failed with HTTP ${response.status}: ${(await response.text()).slice(0, 200)}`);
|
|
36385
|
-
}
|
|
36386
|
-
const payload = await response.json();
|
|
36387
|
-
const vectors = (payload.data ?? []).map((item) => item.embedding);
|
|
36388
|
-
if (vectors.length !== texts.length) {
|
|
36389
|
-
throw new Error(`Jina returned ${vectors.length} embeddings for ${texts.length} inputs.`);
|
|
36390
|
-
}
|
|
36391
|
-
return vectors;
|
|
36392
|
-
}
|
|
36393
|
-
async function embedQueuedCommonsDocuments(limit = 50) {
|
|
36394
|
-
if (!commonsSemanticSearchConfigured()) return { claimed: 0, embedded: 0, failed: 0, remaining: 0 };
|
|
36395
|
-
await ensureCommonsVectorSchema();
|
|
36396
|
-
const bounded2 = Math.max(1, Math.min(200, Math.floor(limit)));
|
|
36397
|
-
const queued = await getDb().execute({
|
|
36398
|
-
sql: `SELECT id, entity_id, document_type, title, text FROM commons_index_documents
|
|
36399
|
-
WHERE embedding_status IN ('queued', 'failed') ORDER BY updated_at ASC LIMIT ?`,
|
|
36400
|
-
args: [bounded2]
|
|
36401
|
-
});
|
|
36402
|
-
const rows = queued.rows;
|
|
36403
|
-
if (!rows.length) return { claimed: 0, embedded: 0, failed: 0, remaining: await queuedCommonsDocumentCount() };
|
|
36404
|
-
let embedded = 0;
|
|
36405
|
-
let failed = 0;
|
|
36406
|
-
const model = commonsEmbedModel();
|
|
36407
|
-
try {
|
|
36408
|
-
const vectors = await embedCommonsTexts(rows.map((row) => `${row.title}
|
|
36409
|
-
|
|
36410
|
-
${row.text}`));
|
|
36411
|
-
for (const [index, row] of rows.entries()) {
|
|
36412
|
-
const literal = `[${vectors[index].join(",")}]`;
|
|
36413
|
-
await vectorSql().query(
|
|
36414
|
-
`INSERT INTO commons_index_vectors (document_id, entity_id, document_type, title, embedding, model, updated_at)
|
|
36415
|
-
VALUES ($1, $2, $3, $4, $5::vector, $6, now())
|
|
36416
|
-
ON CONFLICT (document_id) DO UPDATE SET entity_id = EXCLUDED.entity_id, document_type = EXCLUDED.document_type,
|
|
36417
|
-
title = EXCLUDED.title, embedding = EXCLUDED.embedding, model = EXCLUDED.model, updated_at = now()`,
|
|
36418
|
-
[row.id, row.entity_id, row.document_type, row.title, literal, model]
|
|
36419
|
-
);
|
|
36420
|
-
await getDb().execute({
|
|
36421
|
-
sql: `UPDATE commons_index_documents SET embedding_status = 'indexed', embedding_provider = ?, embedding_model = ?,
|
|
36422
|
-
vector_ref = ?, indexed_at = ?, error = NULL WHERE id = ?`,
|
|
36423
|
-
args: [COMMONS_EMBED_PROVIDER, model, row.id, (/* @__PURE__ */ new Date()).toISOString(), row.id]
|
|
36424
|
-
});
|
|
36425
|
-
embedded += 1;
|
|
36426
|
-
}
|
|
36427
|
-
} catch (error) {
|
|
36428
|
-
failed = rows.length - embedded;
|
|
36429
|
-
const message = (error instanceof Error ? error.message : String(error)).slice(0, 500);
|
|
36430
|
-
for (const row of rows.slice(embedded)) {
|
|
36431
|
-
await getDb().execute({
|
|
36432
|
-
sql: `UPDATE commons_index_documents SET embedding_status = 'failed', error = ? WHERE id = ?`,
|
|
36433
|
-
args: [message, row.id]
|
|
36434
|
-
}).catch(() => void 0);
|
|
36435
|
-
}
|
|
36436
|
-
}
|
|
36437
|
-
return { claimed: rows.length, embedded, failed, remaining: await queuedCommonsDocumentCount() };
|
|
36438
|
-
}
|
|
36439
|
-
async function queuedCommonsDocumentCount() {
|
|
36440
|
-
const result = await getDb().execute(`SELECT COUNT(*) AS n FROM commons_index_documents WHERE embedding_status IN ('queued', 'failed')`);
|
|
36441
|
-
return Number(result.rows[0]?.n ?? 0);
|
|
36442
|
-
}
|
|
36443
|
-
async function semanticCommonsEntityScores(query, limit = 40) {
|
|
36444
|
-
const scores = /* @__PURE__ */ new Map();
|
|
36445
|
-
if (!commonsSemanticSearchConfigured() || !query.trim()) return scores;
|
|
36446
|
-
await ensureCommonsVectorSchema();
|
|
36447
|
-
const [vector] = await embedCommonsTexts([query]);
|
|
36448
|
-
if (!vector) return scores;
|
|
36449
|
-
const rows = await vectorSql().query(
|
|
36450
|
-
`SELECT entity_id, MAX(1 - (embedding <=> $1::vector)) AS score
|
|
36451
|
-
FROM commons_index_vectors GROUP BY entity_id ORDER BY score DESC LIMIT $2`,
|
|
36452
|
-
[`[${vector.join(",")}]`, Math.max(1, Math.min(100, limit))]
|
|
36453
|
-
);
|
|
36454
|
-
for (const row of rows) scores.set(String(row.entity_id), Number(row.score));
|
|
36455
|
-
return scores;
|
|
36456
|
-
}
|
|
36457
|
-
|
|
36458
36446
|
// src/api/schema-presence.ts
|
|
36459
36447
|
var cache = null;
|
|
36460
36448
|
async function loadSchemaObjects() {
|
|
@@ -43194,7 +43182,7 @@ function requestedSiteMaxPages(maxPages) {
|
|
|
43194
43182
|
return Math.min(ABSOLUTE_SITE_MAX_PAGES, Math.max(1, maxPages ?? DEFAULT_SITE_MAX_PAGES));
|
|
43195
43183
|
}
|
|
43196
43184
|
function shouldRunSiteExtractInBackground(input) {
|
|
43197
|
-
return input.background === true || input.downloadImages === true || input.preserveMedia === true || requestedSiteMaxPages(input.maxPages) > MAX_SYNCHRONOUS_SITE_PAGES;
|
|
43185
|
+
return input.background === true || input.downloadImages === true || input.preserveMedia === true || input.renderJavaScript === true || input.captureRenderedDom === true || input.semanticSimilarity === true || requestedSiteMaxPages(input.maxPages) > MAX_SYNCHRONOUS_SITE_PAGES;
|
|
43198
43186
|
}
|
|
43199
43187
|
|
|
43200
43188
|
// src/api/page-media-artifacts.ts
|
|
@@ -45013,7 +45001,7 @@ async function getFreeCreditBreakdown(userId) {
|
|
|
45013
45001
|
}
|
|
45014
45002
|
|
|
45015
45003
|
// src/api/memory-db.ts
|
|
45016
|
-
import { neon
|
|
45004
|
+
import { neon } from "@neondatabase/serverless";
|
|
45017
45005
|
|
|
45018
45006
|
// src/api/memory-universe.ts
|
|
45019
45007
|
var SEP = "\n";
|
|
@@ -45110,7 +45098,7 @@ function sql() {
|
|
|
45110
45098
|
if (_sql) return _sql;
|
|
45111
45099
|
const url = process.env.MEMORY_DATABASE_URL?.trim();
|
|
45112
45100
|
if (!url) throw new Error("MEMORY_DATABASE_URL is not set (paa-crawler needs mcp-memory Neon URL for reads)");
|
|
45113
|
-
_sql =
|
|
45101
|
+
_sql = neon(url);
|
|
45114
45102
|
return _sql;
|
|
45115
45103
|
}
|
|
45116
45104
|
function physicalVault(owner, vault) {
|
|
@@ -50323,6 +50311,13 @@ app.post("/extract-site", auth4, async (c) => {
|
|
|
50323
50311
|
const bodyResult = ExtractSiteBodySchema.safeParse(raw);
|
|
50324
50312
|
if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
50325
50313
|
const body = bodyResult.data;
|
|
50314
|
+
if (body.semanticSimilarity && !process.env.JINA_API_KEY?.trim()) {
|
|
50315
|
+
return c.json({
|
|
50316
|
+
error: "Semantic site similarity is not configured on this deployment.",
|
|
50317
|
+
errorCode: "semantic_similarity_unconfigured",
|
|
50318
|
+
retryable: false
|
|
50319
|
+
}, 503);
|
|
50320
|
+
}
|
|
50326
50321
|
if (body.preserveMedia !== void 0 && body.downloadImages !== void 0 && body.preserveMedia !== body.downloadImages) {
|
|
50327
50322
|
return c.json({ error: "preserveMedia conflicts with deprecated downloadImages." }, 400);
|
|
50328
50323
|
}
|
|
@@ -50376,7 +50371,12 @@ app.post("/extract-site", auth4, async (c) => {
|
|
|
50376
50371
|
maxPages: requestedMaxPages,
|
|
50377
50372
|
rotateProxyEvery: body.rotateProxyEvery ?? 10,
|
|
50378
50373
|
formats: [...body.formats ?? []].sort(),
|
|
50379
|
-
downloadImages
|
|
50374
|
+
downloadImages,
|
|
50375
|
+
renderJavaScript: body.renderJavaScript === true,
|
|
50376
|
+
captureRenderedDom: body.captureRenderedDom === true,
|
|
50377
|
+
semanticSimilarity: body.semanticSimilarity === true,
|
|
50378
|
+
similarityThreshold: body.similarityThreshold ?? null,
|
|
50379
|
+
similarityMaxPairs: body.similarityMaxPairs ?? null
|
|
50380
50380
|
}));
|
|
50381
50381
|
const prepared = await prepareSiteExtractStart({
|
|
50382
50382
|
jobId: jobId2,
|
|
@@ -50390,6 +50390,11 @@ app.post("/extract-site", auth4, async (c) => {
|
|
|
50390
50390
|
urlsPerBrowser: body.rotateProxyEvery ?? 10,
|
|
50391
50391
|
formats: body.formats,
|
|
50392
50392
|
downloadImages,
|
|
50393
|
+
renderJavaScript: body.renderJavaScript === true,
|
|
50394
|
+
captureRenderedDom: body.captureRenderedDom === true,
|
|
50395
|
+
semanticSimilarity: body.semanticSimilarity === true,
|
|
50396
|
+
similarityThreshold: body.similarityThreshold,
|
|
50397
|
+
similarityMaxPairs: body.similarityMaxPairs,
|
|
50393
50398
|
debitKey: `site-extract:${user.id}:${jobId2}:hold`,
|
|
50394
50399
|
waybackReplay: waybackReplay && !body.wayback ? {
|
|
50395
50400
|
timestamp: waybackReplay.timestamp,
|
|
@@ -50483,8 +50488,10 @@ app.post("/extract-site", auth4, async (c) => {
|
|
|
50483
50488
|
startUrl: crawlStartUrl,
|
|
50484
50489
|
maxPages: siteMaxPages,
|
|
50485
50490
|
seedUrls: waybackCaptures?.map((capture) => capture.rawReplayUrl),
|
|
50486
|
-
kernelApiKey: rotateProxies || wantsBranding || body.browserFallback || body.kernelFallback ? browserServiceApiKey() : void 0,
|
|
50491
|
+
kernelApiKey: rotateProxies || wantsBranding || body.browserFallback || body.kernelFallback || body.renderJavaScript || body.captureRenderedDom || body.semanticSimilarity ? browserServiceApiKey() : void 0,
|
|
50487
50492
|
formats: body.formats,
|
|
50493
|
+
forceBrowserRender: body.renderJavaScript || body.captureRenderedDom || body.semanticSimilarity,
|
|
50494
|
+
captureRenderedDom: body.captureRenderedDom,
|
|
50488
50495
|
...rotateProxies ? {
|
|
50489
50496
|
rotateProxyEvery: body.rotateProxyEvery ?? 30,
|
|
50490
50497
|
parallelism: concurrencyLimitForUser(user)
|
|
@@ -51006,7 +51013,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
51006
51013
|
return c.json({ error: "Unauthorized" }, 401);
|
|
51007
51014
|
}
|
|
51008
51015
|
const startedAt = Date.now();
|
|
51009
|
-
const { drainQueue } = await import("./worker-
|
|
51016
|
+
const { drainQueue } = await import("./worker-YKRIK5LS.js");
|
|
51010
51017
|
const budget = { maxJobs: 10, deadlineMs: startedAt + CRON_TICK_DRAIN_BUDGET_MS };
|
|
51011
51018
|
const origin = `${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`;
|
|
51012
51019
|
const unfinished = [];
|
|
@@ -51086,8 +51093,8 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
51086
51093
|
finishExtractJob: finishExtractJob2,
|
|
51087
51094
|
terminalExtractJobStatus: terminalExtractJobStatus2,
|
|
51088
51095
|
extractJobLimitInfo: extractJobLimitInfo2
|
|
51089
|
-
} = await import("./site-extract-repository-
|
|
51090
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
51096
|
+
} = await import("./site-extract-repository-J3RH3MOS.js");
|
|
51097
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-UWKJT4MU.js");
|
|
51091
51098
|
const existing = await getExtractJob2(jobId2);
|
|
51092
51099
|
if (!existing) return c.json({ error: "job not found" }, 404);
|
|
51093
51100
|
if (existing.status !== "failed") {
|
|
@@ -51114,7 +51121,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
51114
51121
|
if (!updated) return c.json({ error: "job state changed during re-finalization" }, 409);
|
|
51115
51122
|
let settlement = "already_settled_or_refunded";
|
|
51116
51123
|
if (job.billedMc == null && job.userId != null) {
|
|
51117
|
-
const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-
|
|
51124
|
+
const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-J3RH3MOS.js");
|
|
51118
51125
|
const heldMc = Number(job.options.heldMc ?? 0);
|
|
51119
51126
|
const successful = await countSuccessfulPages2(jobId2);
|
|
51120
51127
|
const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
|
|
@@ -51301,4 +51308,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
51301
51308
|
export {
|
|
51302
51309
|
app
|
|
51303
51310
|
};
|
|
51304
|
-
//# sourceMappingURL=server-
|
|
51311
|
+
//# sourceMappingURL=server-AFEA235I.js.map
|