@farming-labs/docs 0.2.73 → 0.2.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-o9OTadrf.mjs → agent-BJ0KXN-A.mjs} +7 -0
- package/dist/{agent-ClRpctMG.mjs → agent-D0LEY2Vy.mjs} +2 -2
- package/dist/{agent-evals-BFHGypqh.mjs → agent-evals-LIScgQVm.mjs} +56 -30
- package/dist/{agent-export-B9bah0AV.mjs → agent-export-B92pNe6g.mjs} +5 -5
- package/dist/agent-skills-bundle.d.mts +1 -1
- package/dist/{agent-skills-server-DCbAx5WU.d.mts → agent-skills-server-BqvIGQb1.d.mts} +2 -2
- package/dist/agent-skills-vite.d.mts +3 -3
- package/dist/{agents-CR-Uasbc.mjs → agents-C228QGbp.mjs} +4 -4
- package/dist/cli/index.mjs +19 -15
- package/dist/client/react.d.mts +1 -1
- package/dist/{cloud-ask-ai-yR9vZmSG.d.mts → cloud-ask-ai-6X9pVjIY.d.mts} +1 -1
- package/dist/docs-cloud-server.d.mts +2 -2
- package/dist/{doctor-BeyN3Ljh.mjs → doctor-CgrUZ3tY.mjs} +8 -8
- package/dist/{golden-evaluations-CloP0pAe.mjs → golden-evaluations-DTEPZeb0.mjs} +2 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +5 -5
- package/dist/{mcp-BLLXvEXB.mjs → mcp-YD37HlPY.mjs} +7 -5
- package/dist/mcp.d.mts +13 -2
- package/dist/mcp.mjs +114 -54
- package/dist/{prompt-references-Cz35rlOa.mjs → prompt-references-BIS1eXt4.mjs} +2 -2
- package/dist/{search-D1n8Sf25.d.mts → retrieval-digest-9Kp3uSaZ.d.mts} +48 -5
- package/dist/{review-DaJLdPLh.mjs → review-Dbzkdl6K.mjs} +4 -4
- package/dist/{robots-DI-9MZz1.mjs → robots-DUO0r4UI.mjs} +2 -2
- package/dist/{robots-BcuxnZqZ.mjs → robots-RbgmSvPH.mjs} +1 -1
- package/dist/{search-RfPcG1if.mjs → search-DIyIl-tE.mjs} +1240 -136
- package/dist/{search-DeOJiMAy.mjs → search-hcqaEZVN.mjs} +62 -24
- package/dist/server.d.mts +5 -5
- package/dist/server.mjs +5 -5
- package/dist/{sitemap-BGGbX_KE.mjs → sitemap-jhkS_jYx.mjs} +4 -4
- package/dist/{sitemap-server-VefDnzEB.mjs → sitemap-server-BfyBM7_V.mjs} +1 -1
- package/dist/{standards-discovery-83lp1lew.d.mts → standards-discovery-CdrOE-05.d.mts} +1 -1
- package/dist/{types-_HCPD4wc.d.mts → types-clg5J1R4.d.mts} +60 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as isDocsAgentDiscoveryRequest, K as isDocsAgentsRequest, Q as isDocsSkillRequest, _t as resolveDocsMarkdownRequest, dt as resolveDocsAgentsFormat, jt as resolveDocsAudienceMdxContent, lt as resolveDocsAgentFeedbackConfig, pt as resolveDocsLlmsTxtRequest, ut as resolveDocsAgentFeedbackRequest, xt as resolveDocsSkillFormat } from "./agent-
|
|
1
|
+
import { G as isDocsAgentDiscoveryRequest, K as isDocsAgentsRequest, Q as isDocsSkillRequest, _t as resolveDocsMarkdownRequest, dt as resolveDocsAgentsFormat, jt as resolveDocsAudienceMdxContent, lt as resolveDocsAgentFeedbackConfig, pt as resolveDocsLlmsTxtRequest, ut as resolveDocsAgentFeedbackRequest, xt as resolveDocsSkillFormat } from "./agent-BJ0KXN-A.mjs";
|
|
2
2
|
import { S as upsertPageAgentContractMarkdown, a as findDocsGeneratedAgentContractRanges, d as PAGE_AGENT_CONTRACT_END_MARKER, l as stripDocsGeneratedAgentContractMarkers, m as PAGE_AGENT_CONTRACT_START_MARKER, o as findDocsMarkdownSection, s as parseDocsMarkdownSections, y as renderPageAgentContractMarkdown } from "./markdown-sections-7OoA7ylx.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/telemetry.ts
|
|
@@ -458,6 +458,172 @@ function serializeGeneratedAgentDocument(content, provenance) {
|
|
|
458
458
|
].join("\n")}\n`;
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
+
//#endregion
|
|
462
|
+
//#region src/retrieval-digest.ts
|
|
463
|
+
const SHA256_INITIAL_STATE = [
|
|
464
|
+
1779033703,
|
|
465
|
+
3144134277,
|
|
466
|
+
1013904242,
|
|
467
|
+
2773480762,
|
|
468
|
+
1359893119,
|
|
469
|
+
2600822924,
|
|
470
|
+
528734635,
|
|
471
|
+
1541459225
|
|
472
|
+
];
|
|
473
|
+
const SHA256_ROUND_CONSTANTS = [
|
|
474
|
+
1116352408,
|
|
475
|
+
1899447441,
|
|
476
|
+
3049323471,
|
|
477
|
+
3921009573,
|
|
478
|
+
961987163,
|
|
479
|
+
1508970993,
|
|
480
|
+
2453635748,
|
|
481
|
+
2870763221,
|
|
482
|
+
3624381080,
|
|
483
|
+
310598401,
|
|
484
|
+
607225278,
|
|
485
|
+
1426881987,
|
|
486
|
+
1925078388,
|
|
487
|
+
2162078206,
|
|
488
|
+
2614888103,
|
|
489
|
+
3248222580,
|
|
490
|
+
3835390401,
|
|
491
|
+
4022224774,
|
|
492
|
+
264347078,
|
|
493
|
+
604807628,
|
|
494
|
+
770255983,
|
|
495
|
+
1249150122,
|
|
496
|
+
1555081692,
|
|
497
|
+
1996064986,
|
|
498
|
+
2554220882,
|
|
499
|
+
2821834349,
|
|
500
|
+
2952996808,
|
|
501
|
+
3210313671,
|
|
502
|
+
3336571891,
|
|
503
|
+
3584528711,
|
|
504
|
+
113926993,
|
|
505
|
+
338241895,
|
|
506
|
+
666307205,
|
|
507
|
+
773529912,
|
|
508
|
+
1294757372,
|
|
509
|
+
1396182291,
|
|
510
|
+
1695183700,
|
|
511
|
+
1986661051,
|
|
512
|
+
2177026350,
|
|
513
|
+
2456956037,
|
|
514
|
+
2730485921,
|
|
515
|
+
2820302411,
|
|
516
|
+
3259730800,
|
|
517
|
+
3345764771,
|
|
518
|
+
3516065817,
|
|
519
|
+
3600352804,
|
|
520
|
+
4094571909,
|
|
521
|
+
275423344,
|
|
522
|
+
430227734,
|
|
523
|
+
506948616,
|
|
524
|
+
659060556,
|
|
525
|
+
883997877,
|
|
526
|
+
958139571,
|
|
527
|
+
1322822218,
|
|
528
|
+
1537002063,
|
|
529
|
+
1747873779,
|
|
530
|
+
1955562222,
|
|
531
|
+
2024104815,
|
|
532
|
+
2227730452,
|
|
533
|
+
2361852424,
|
|
534
|
+
2428436474,
|
|
535
|
+
2756734187,
|
|
536
|
+
3204031479,
|
|
537
|
+
3329325298
|
|
538
|
+
];
|
|
539
|
+
function rotateRight(value, bits) {
|
|
540
|
+
return value >>> bits | value << 32 - bits;
|
|
541
|
+
}
|
|
542
|
+
function normalizeDigestContent(value) {
|
|
543
|
+
return value.replace(/\r\n?/gu, "\n").replace(/^\uFEFF/u, "").trimEnd();
|
|
544
|
+
}
|
|
545
|
+
/** Validate the bounded HTTP(S) URL or root-relative URI reference used by provenance. */
|
|
546
|
+
function isDocsRetrievalCanonicalUrl(value) {
|
|
547
|
+
const hasUnsafeCharacter = value.includes("\\") || Array.from(value).some((character) => {
|
|
548
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
549
|
+
return codePoint <= 31 || codePoint === 127;
|
|
550
|
+
});
|
|
551
|
+
if (!value || value !== value.trim() || value.length > 4096 || hasUnsafeCharacter) return false;
|
|
552
|
+
if (/^\/(?!\/)/u.test(value)) try {
|
|
553
|
+
new URL(value, "https://docs.local");
|
|
554
|
+
return true;
|
|
555
|
+
} catch {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
const url = new URL(value);
|
|
560
|
+
return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password;
|
|
561
|
+
} catch {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Hash a retrieval source projection with portable SHA-256.
|
|
567
|
+
*
|
|
568
|
+
* The projection is normalized by removing one leading BOM, converting CRLF/CR
|
|
569
|
+
* line endings to LF, and trimming trailing whitespace. The helper is exported so
|
|
570
|
+
* agents can independently verify provenance in Node, edge, and browser runtimes.
|
|
571
|
+
*/
|
|
572
|
+
function digestDocsRetrievalContent(value) {
|
|
573
|
+
const input = new TextEncoder().encode(normalizeDigestContent(value));
|
|
574
|
+
const paddedLength = Math.ceil((input.length + 9) / 64) * 64;
|
|
575
|
+
const padded = new Uint8Array(paddedLength);
|
|
576
|
+
padded.set(input);
|
|
577
|
+
padded[input.length] = 128;
|
|
578
|
+
const bitLength = input.length * 8;
|
|
579
|
+
const dataView = new DataView(padded.buffer);
|
|
580
|
+
dataView.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296), false);
|
|
581
|
+
dataView.setUint32(paddedLength - 4, bitLength >>> 0, false);
|
|
582
|
+
const state = Uint32Array.from(SHA256_INITIAL_STATE);
|
|
583
|
+
const words = new Uint32Array(64);
|
|
584
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
585
|
+
for (let index = 0; index < 16; index += 1) words[index] = dataView.getUint32(offset + index * 4, false);
|
|
586
|
+
for (let index = 16; index < 64; index += 1) {
|
|
587
|
+
const previous15 = words[index - 15];
|
|
588
|
+
const previous2 = words[index - 2];
|
|
589
|
+
const sigma0 = rotateRight(previous15, 7) ^ rotateRight(previous15, 18) ^ previous15 >>> 3;
|
|
590
|
+
const sigma1 = rotateRight(previous2, 17) ^ rotateRight(previous2, 19) ^ previous2 >>> 10;
|
|
591
|
+
words[index] = words[index - 16] + sigma0 + words[index - 7] + sigma1 >>> 0;
|
|
592
|
+
}
|
|
593
|
+
let a = state[0];
|
|
594
|
+
let b = state[1];
|
|
595
|
+
let c = state[2];
|
|
596
|
+
let d = state[3];
|
|
597
|
+
let e = state[4];
|
|
598
|
+
let f = state[5];
|
|
599
|
+
let g = state[6];
|
|
600
|
+
let h = state[7];
|
|
601
|
+
for (let index = 0; index < 64; index += 1) {
|
|
602
|
+
const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
603
|
+
const choice = e & f ^ ~e & g;
|
|
604
|
+
const temporary1 = h + sum1 + choice + SHA256_ROUND_CONSTANTS[index] + words[index] >>> 0;
|
|
605
|
+
const temporary2 = (rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
606
|
+
h = g;
|
|
607
|
+
g = f;
|
|
608
|
+
f = e;
|
|
609
|
+
e = d + temporary1 >>> 0;
|
|
610
|
+
d = c;
|
|
611
|
+
c = b;
|
|
612
|
+
b = a;
|
|
613
|
+
a = temporary1 + temporary2 >>> 0;
|
|
614
|
+
}
|
|
615
|
+
state[0] = state[0] + a >>> 0;
|
|
616
|
+
state[1] = state[1] + b >>> 0;
|
|
617
|
+
state[2] = state[2] + c >>> 0;
|
|
618
|
+
state[3] = state[3] + d >>> 0;
|
|
619
|
+
state[4] = state[4] + e >>> 0;
|
|
620
|
+
state[5] = state[5] + f >>> 0;
|
|
621
|
+
state[6] = state[6] + g >>> 0;
|
|
622
|
+
state[7] = state[7] + h >>> 0;
|
|
623
|
+
}
|
|
624
|
+
return `sha256:${Array.from(state, (word) => word.toString(16).padStart(8, "0")).join("")}`;
|
|
625
|
+
}
|
|
626
|
+
|
|
461
627
|
//#endregion
|
|
462
628
|
//#region src/agent-scope.ts
|
|
463
629
|
/** Normalize framework aliases used by page metadata, agent contracts, MCP, and evaluations. */
|
|
@@ -784,7 +950,8 @@ const DEFAULT_SEARCH_LIMIT = 10;
|
|
|
784
950
|
const MAX_SEARCH_SNIPPET_CHARS = 160;
|
|
785
951
|
const DEFAULT_MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
786
952
|
const MCP_SESSION_CLEANUP_TIMEOUT_MS = 1e3;
|
|
787
|
-
const syncedIndexes = /* @__PURE__ */ new
|
|
953
|
+
const syncedIndexes = /* @__PURE__ */ new Map();
|
|
954
|
+
const syncingIndexes = /* @__PURE__ */ new Map();
|
|
788
955
|
const ALGOLIA_MAX_RECORD_BYTES = 9500;
|
|
789
956
|
const DEFAULT_ASK_AI_CONTEXT_CHARS = 24e3;
|
|
790
957
|
const DEFAULT_ASK_AI_RESULT_CHARS = 6e3;
|
|
@@ -794,9 +961,13 @@ const MAX_SEARCH_FILTER_RAW_CHARS = 4096;
|
|
|
794
961
|
const MAX_SEARCH_FILTER_SEGMENTS = 64;
|
|
795
962
|
const MAX_PROVIDER_SCOPE_FILTER_IDS = 1e3;
|
|
796
963
|
const MAX_PROVIDER_SCOPE_FILTER_CHARS = 16e3;
|
|
964
|
+
const ALGOLIA_BATCH_OPERATIONS = 1e3;
|
|
797
965
|
const MAX_SEARCH_WARNINGS = 16;
|
|
798
966
|
const MAX_SEARCH_WARNING_VALUES = 16;
|
|
799
967
|
const MAX_SEARCH_WARNING_PAGE_URLS = 8;
|
|
968
|
+
const RETRIEVAL_INDEX_FORMAT = "docs-retrieval-index.v1";
|
|
969
|
+
const MAX_RETRIEVAL_SOURCE_URL_CHARS = 4096;
|
|
970
|
+
const MAX_RETRIEVAL_SOURCE_VALUE_CHARS = 256;
|
|
800
971
|
const SEARCH_FILTER_FIELDS = [
|
|
801
972
|
"framework",
|
|
802
973
|
"version",
|
|
@@ -896,9 +1067,10 @@ function resolveDocsSearchRequest(searchParams) {
|
|
|
896
1067
|
function hasDocsSearchFilters(filters) {
|
|
897
1068
|
return SEARCH_FILTER_FIELDS.some((field) => (filters[field]?.length ?? 0) > 0);
|
|
898
1069
|
}
|
|
899
|
-
function resolveProviderScopeDocumentIds(query, context) {
|
|
1070
|
+
function resolveProviderScopeDocumentIds(query, context, corpusId) {
|
|
900
1071
|
if (!hasDocsSearchFilters(query.filters ?? {})) return void 0;
|
|
901
|
-
const
|
|
1072
|
+
const documents = corpusId ? buildDocsSearchDocuments(context.pages, context.chunking ?? { strategy: "section" }, "human") : context.documents;
|
|
1073
|
+
const ids = Array.from(new Set(documents.map((document) => corpusId ? makeHostedProviderDocumentId(corpusId, document.id) : document.id)));
|
|
902
1074
|
return ids.length <= MAX_PROVIDER_SCOPE_FILTER_IDS ? ids : [];
|
|
903
1075
|
}
|
|
904
1076
|
function docsSearchFiltersMatch(expected, actual) {
|
|
@@ -1011,13 +1183,49 @@ function isLiteralLookupQuery(query) {
|
|
|
1011
1183
|
function tokenizeSearchQuery(query) {
|
|
1012
1184
|
return Array.from(new Set(query.toLowerCase().replace(/[^\p{L}\p{N}@/_:.-]+/gu, " ").split(/\s+/).map((word) => word.replace(/^[^\p{L}\p{N}@]+|[^\p{L}\p{N}]+$/gu, "")).filter((word) => word.length > 1 && !SEARCH_STOP_WORDS.has(word))));
|
|
1013
1185
|
}
|
|
1014
|
-
function
|
|
1186
|
+
function normalizeUrlRouteKey(value) {
|
|
1015
1187
|
try {
|
|
1016
|
-
|
|
1188
|
+
const url = new URL(value, "https://docs.local");
|
|
1189
|
+
return `${url.pathname || "/"}${url.search}`;
|
|
1017
1190
|
} catch {
|
|
1018
|
-
return value.split(
|
|
1191
|
+
return value.split("#", 1)[0] || "/";
|
|
1019
1192
|
}
|
|
1020
1193
|
}
|
|
1194
|
+
function normalizeAuthoredUrlIdentity(value) {
|
|
1195
|
+
const trimmed = value.trim();
|
|
1196
|
+
const explicitScheme = trimmed.match(/^([a-z][a-z\d+.-]*):/iu)?.[1]?.toLowerCase();
|
|
1197
|
+
if (explicitScheme && explicitScheme !== "http" && explicitScheme !== "https") return normalizeUrlRouteKey("/");
|
|
1198
|
+
try {
|
|
1199
|
+
const url = new URL(trimmed, "https://docs.local");
|
|
1200
|
+
const route = `${url.pathname || "/"}${url.search}`;
|
|
1201
|
+
if (explicitScheme) return `${url.origin}${route}`;
|
|
1202
|
+
if (trimmed.startsWith("//")) return `//${url.host}${route}`;
|
|
1203
|
+
return route;
|
|
1204
|
+
} catch {
|
|
1205
|
+
return normalizeUrlRouteKey(trimmed);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
function appendDocsLocaleQuery(value, locale) {
|
|
1209
|
+
const hashIndex = value.indexOf("#");
|
|
1210
|
+
const withoutHash = hashIndex >= 0 ? value.slice(0, hashIndex) : value;
|
|
1211
|
+
const hash = hashIndex >= 0 ? value.slice(hashIndex) : "";
|
|
1212
|
+
const queryIndex = withoutHash.indexOf("?");
|
|
1213
|
+
const rawQuery = queryIndex >= 0 ? withoutHash.slice(queryIndex + 1) : "";
|
|
1214
|
+
if (new URLSearchParams(rawQuery).has("lang")) return value;
|
|
1215
|
+
return `${withoutHash}${queryIndex >= 0 ? "&" : "?"}lang=${encodeURIComponent(locale)}${hash}`;
|
|
1216
|
+
}
|
|
1217
|
+
function localizeDocsSearchPage(page, localeFallback) {
|
|
1218
|
+
const rawLocale = (page.locale ?? localeFallback)?.trim();
|
|
1219
|
+
if (!rawLocale) return page;
|
|
1220
|
+
const locale = rawLocale;
|
|
1221
|
+
const url = appendDocsLocaleQuery(page.url, locale);
|
|
1222
|
+
if (url === page.url && page.locale === locale) return page;
|
|
1223
|
+
return {
|
|
1224
|
+
...page,
|
|
1225
|
+
url,
|
|
1226
|
+
locale
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1021
1229
|
function safeDecodeUrlSegment(value) {
|
|
1022
1230
|
try {
|
|
1023
1231
|
return decodeURIComponent(value);
|
|
@@ -1129,19 +1337,19 @@ function clampText(value, maxChars) {
|
|
|
1129
1337
|
if (value.length <= maxChars) return value;
|
|
1130
1338
|
return `${value.slice(0, maxChars).trimEnd()}...`;
|
|
1131
1339
|
}
|
|
1132
|
-
function findPageForSearchResult(pages, result, baseUrl,
|
|
1340
|
+
function findPageForSearchResult(pages, result, baseUrl, pagesByRoute) {
|
|
1133
1341
|
const rawUrl = result.url.trim();
|
|
1134
1342
|
const explicitlySchemed = /^[a-z][a-z\d+.-]*:/iu.test(rawUrl);
|
|
1135
1343
|
const explicitlyHosted = explicitlySchemed || /^[\\/]{2}/u.test(rawUrl);
|
|
1136
1344
|
if (explicitlySchemed && !/^https?:/iu.test(rawUrl)) return void 0;
|
|
1137
|
-
if (explicitlyHosted && !baseUrl
|
|
1345
|
+
if (explicitlyHosted && !baseUrl) return void 0;
|
|
1138
1346
|
if (explicitlyHosted && baseUrl) try {
|
|
1139
1347
|
if (new URL(result.url, baseUrl).origin !== new URL(baseUrl).origin) return void 0;
|
|
1140
1348
|
} catch {
|
|
1141
1349
|
return;
|
|
1142
1350
|
}
|
|
1143
|
-
const resultPath =
|
|
1144
|
-
return pages.find((page) =>
|
|
1351
|
+
const resultPath = normalizeUrlRouteKey(result.url);
|
|
1352
|
+
return pagesByRoute?.get(resultPath) ?? pages.find((page) => normalizeUrlRouteKey(page.url) === resultPath);
|
|
1145
1353
|
}
|
|
1146
1354
|
function inferResultTitle(result, page) {
|
|
1147
1355
|
if (page) return page.title;
|
|
@@ -1163,7 +1371,7 @@ function formatAskAIContextResult(options) {
|
|
|
1163
1371
|
function getSearchResultKey(result) {
|
|
1164
1372
|
const anchor = getSearchResultAnchor(result.url);
|
|
1165
1373
|
const sectionFallback = normalizeWhitespace(result.section ?? "").toLowerCase();
|
|
1166
|
-
return `${
|
|
1374
|
+
return `${normalizeUrlRouteKey(result.url)}#${anchor ?? sectionFallback}`;
|
|
1167
1375
|
}
|
|
1168
1376
|
function getSearchResultAnchor(value) {
|
|
1169
1377
|
let hash = "";
|
|
@@ -1181,7 +1389,7 @@ function getSearchResultAnchor(value) {
|
|
|
1181
1389
|
}
|
|
1182
1390
|
}
|
|
1183
1391
|
function getAskAIResultPageKey(value, baseUrl, strictExternalOrigins = false) {
|
|
1184
|
-
const path =
|
|
1392
|
+
const path = normalizeUrlRouteKey(value);
|
|
1185
1393
|
const rawValue = value.trim();
|
|
1186
1394
|
if (!(/^[a-z][a-z\d+.-]*:/iu.test(rawValue) || /^[\\/]{2}/u.test(rawValue)) || !baseUrl && !strictExternalOrigins) return path;
|
|
1187
1395
|
try {
|
|
@@ -1224,11 +1432,11 @@ function buildAudienceProjectionSearchResults(documents, query) {
|
|
|
1224
1432
|
};
|
|
1225
1433
|
});
|
|
1226
1434
|
}
|
|
1227
|
-
function sanitizeExternalAudienceSearchResults(results, localAudienceResults, baseUrl,
|
|
1435
|
+
function sanitizeExternalAudienceSearchResults(results, localAudienceResults, baseUrl, preserveUnmatched, fallbackSectionToLocalPage = false) {
|
|
1228
1436
|
const localByKey = new Map(localAudienceResults.map((result) => [getSearchResultKey(result), result]));
|
|
1229
1437
|
const localPageResults = /* @__PURE__ */ new Map();
|
|
1230
1438
|
for (const result of localAudienceResults) {
|
|
1231
|
-
const pageUrl =
|
|
1439
|
+
const pageUrl = normalizeUrlRouteKey(result.url);
|
|
1232
1440
|
if (!localPageResults.get(pageUrl) || result.type === "page") localPageResults.set(pageUrl, result);
|
|
1233
1441
|
}
|
|
1234
1442
|
return results.flatMap((result) => {
|
|
@@ -1236,7 +1444,7 @@ function sanitizeExternalAudienceSearchResults(results, localAudienceResults, ba
|
|
|
1236
1444
|
const explicitlySchemed = /^[a-z][a-z\d+.-]*:/iu.test(rawUrl);
|
|
1237
1445
|
const externallyHosted = explicitlySchemed && /^https?:/iu.test(rawUrl) || /^[\\/]{2}/u.test(rawUrl);
|
|
1238
1446
|
const unsupportedScheme = explicitlySchemed && !/^https?:/iu.test(rawUrl);
|
|
1239
|
-
let sameOriginOrUnknown = !externallyHosted
|
|
1447
|
+
let sameOriginOrUnknown = !externallyHosted;
|
|
1240
1448
|
if (unsupportedScheme) sameOriginOrUnknown = false;
|
|
1241
1449
|
if (externallyHosted && baseUrl) try {
|
|
1242
1450
|
sameOriginOrUnknown = new URL(result.url, baseUrl).origin === new URL(baseUrl).origin;
|
|
@@ -1244,7 +1452,7 @@ function sanitizeExternalAudienceSearchResults(results, localAudienceResults, ba
|
|
|
1244
1452
|
sameOriginOrUnknown = false;
|
|
1245
1453
|
}
|
|
1246
1454
|
const hasSection = Boolean(getSearchResultAnchor(result.url) || result.section);
|
|
1247
|
-
const local = sameOriginOrUnknown ? localByKey.get(getSearchResultKey(result)) ?? (hasSection ? void 0 : localPageResults.get(
|
|
1455
|
+
const local = sameOriginOrUnknown ? localByKey.get(getSearchResultKey(result)) ?? (hasSection && !fallbackSectionToLocalPage ? void 0 : localPageResults.get(normalizeUrlRouteKey(result.url))) : void 0;
|
|
1248
1456
|
if (!local) return preserveUnmatched?.(result) ? [result] : [];
|
|
1249
1457
|
return [{
|
|
1250
1458
|
...local,
|
|
@@ -1255,14 +1463,14 @@ function sanitizeExternalAudienceSearchResults(results, localAudienceResults, ba
|
|
|
1255
1463
|
}
|
|
1256
1464
|
function shouldPreserveUnmatchedExternalResult(options) {
|
|
1257
1465
|
const { result, localPagePaths, baseUrl } = options;
|
|
1258
|
-
const path =
|
|
1466
|
+
const path = normalizeUrlRouteKey(result.url);
|
|
1259
1467
|
const rawUrl = result.url.trim();
|
|
1260
1468
|
const explicitScheme = rawUrl.match(/^([a-z][a-z\d+.-]*):/iu)?.[1]?.toLowerCase();
|
|
1261
1469
|
if (explicitScheme && explicitScheme !== "http" && explicitScheme !== "https") return false;
|
|
1262
1470
|
const explicitlyHosted = /^[a-z][a-z\d+.-]*:/iu.test(rawUrl) || /^[\\/]{2}/u.test(rawUrl);
|
|
1263
1471
|
const knownLocalPath = localPagePaths.has(path);
|
|
1264
1472
|
if (!explicitlyHosted) return !knownLocalPath;
|
|
1265
|
-
if (!baseUrl) return
|
|
1473
|
+
if (!baseUrl) return true;
|
|
1266
1474
|
try {
|
|
1267
1475
|
if (new URL(result.url, baseUrl).origin !== new URL(baseUrl).origin) return true;
|
|
1268
1476
|
} catch {
|
|
@@ -1270,22 +1478,526 @@ function shouldPreserveUnmatchedExternalResult(options) {
|
|
|
1270
1478
|
}
|
|
1271
1479
|
return !knownLocalPath;
|
|
1272
1480
|
}
|
|
1481
|
+
function getPageAudienceSource(page, audience) {
|
|
1482
|
+
return audience === "agent" ? page.agentRawContent ?? page.agentFallbackRawContent ?? page.agentContent ?? page.agentFallbackContent ?? page.rawContent ?? page.content : page.rawContent ?? page.content;
|
|
1483
|
+
}
|
|
1273
1484
|
function getPageAudienceRawContent(page, audience) {
|
|
1274
|
-
return resolveDocsAudienceMdxContent(
|
|
1485
|
+
return resolveDocsAudienceMdxContent(getPageAudienceSource(page, audience), audience);
|
|
1275
1486
|
}
|
|
1276
1487
|
function getPageAudienceSectionContent(page, audience) {
|
|
1277
1488
|
const content = getPageAudienceRawContent(page, audience);
|
|
1278
1489
|
return audience === "agent" ? upsertPageAgentContractMarkdown(content, page.agent) : content;
|
|
1279
1490
|
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Build the exact normalized-input projection represented by a retrieval source digest.
|
|
1493
|
+
* This is public so consumers can independently reproduce `source.digest`.
|
|
1494
|
+
*/
|
|
1495
|
+
function buildDocsRetrievalDigestProjection(page, audience = "human") {
|
|
1496
|
+
const audienceContent = getPageAudienceRawContent(page, audience);
|
|
1497
|
+
return audience === "agent" ? upsertPageAgentContractMarkdown(audienceContent, page.agent) : [audienceContent, renderPageAgentContractMarkdown(page.agent)].filter(Boolean).join("\n\n");
|
|
1498
|
+
}
|
|
1280
1499
|
function getPageAudienceSearchText(page, audience) {
|
|
1281
1500
|
return stripMarkdownText(getPageAudienceRawContent(page, audience));
|
|
1282
1501
|
}
|
|
1283
|
-
function
|
|
1502
|
+
function getPageAudienceIndexContent(page, audience) {
|
|
1503
|
+
return normalizeWhitespace([getPageAudienceSearchText(page, audience), getPageAgentContractSearchText(page)].join(" "));
|
|
1504
|
+
}
|
|
1505
|
+
function sortRetrievalSourceValues(values) {
|
|
1506
|
+
const sorted = Array.from(new Set(values)).sort(compareSearchMetadataValues).slice(0, MAX_SEARCH_FILTER_VALUES);
|
|
1507
|
+
return sorted.length > 0 ? sorted : void 0;
|
|
1508
|
+
}
|
|
1509
|
+
function buildDocsRetrievalSourceScope(page, audience, localeFallback) {
|
|
1510
|
+
const resolved = resolveDocsSearchPageScope(page);
|
|
1511
|
+
const rawLocale = (page.locale ?? localeFallback)?.trim();
|
|
1512
|
+
const locale = rawLocale ? normalizeAgentLocale(rawLocale).slice(0, MAX_SEARCH_FILTER_VALUE_CHARS) : void 0;
|
|
1513
|
+
const framework = sortRetrievalSourceValues(resolved.framework);
|
|
1514
|
+
const version = sortRetrievalSourceValues(resolved.version);
|
|
1515
|
+
const versionGroups = resolved.declarations.version.map((group) => sortRetrievalSourceValues(group)).filter((group) => Boolean(group)).slice(0, MAX_SEARCH_FILTER_VALUES);
|
|
1516
|
+
const packageNames = sortRetrievalSourceValues(resolved.package);
|
|
1517
|
+
const tags = sortRetrievalSourceValues(resolved.tags);
|
|
1518
|
+
const truncated = SEARCH_FILTER_FIELDS.filter((field) => {
|
|
1519
|
+
if (field === "version") return resolved.version.length > MAX_SEARCH_FILTER_VALUES || resolved.declarations.version.length > MAX_SEARCH_FILTER_VALUES || resolved.declarations.version.some((group) => group.length > MAX_SEARCH_FILTER_VALUES);
|
|
1520
|
+
return resolved[field].length > MAX_SEARCH_FILTER_VALUES;
|
|
1521
|
+
});
|
|
1522
|
+
return {
|
|
1523
|
+
audience,
|
|
1524
|
+
...locale ? { locale: [locale] } : {},
|
|
1525
|
+
...framework ? { framework } : {},
|
|
1526
|
+
...version ? { version } : {},
|
|
1527
|
+
...versionGroups.length > 1 ? { versionGroups } : {},
|
|
1528
|
+
...packageNames ? { package: packageNames } : {},
|
|
1529
|
+
...tags ? { tags } : {},
|
|
1530
|
+
...truncated.length > 0 ? { truncated } : {},
|
|
1531
|
+
...resolved.conflicts.length > 0 ? { conflicts: [...resolved.conflicts].sort((left, right) => SEARCH_FILTER_FIELDS.indexOf(left) - SEARCH_FILTER_FIELDS.indexOf(right)) } : {}
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
function buildDocsRetrievalScopeIdentity(page, audience, localeFallback) {
|
|
1535
|
+
const resolved = resolveDocsSearchPageScope(page);
|
|
1536
|
+
const sortValues = (values) => Array.from(new Set(values)).sort(compareSearchMetadataValues);
|
|
1537
|
+
const declarations = Object.fromEntries(SEARCH_FILTER_FIELDS.map((field) => [field, resolved.declarations[field].map(sortValues).sort((left, right) => compareSearchMetadataValues(JSON.stringify(left), JSON.stringify(right)))]));
|
|
1538
|
+
return hashDocsRetrievalValue(JSON.stringify({
|
|
1539
|
+
audience,
|
|
1540
|
+
locale: (page.locale ?? localeFallback)?.trim(),
|
|
1541
|
+
declarations,
|
|
1542
|
+
conflicts: [...resolved.conflicts].sort((left, right) => SEARCH_FILTER_FIELDS.indexOf(left) - SEARCH_FILTER_FIELDS.indexOf(right))
|
|
1543
|
+
}));
|
|
1544
|
+
}
|
|
1545
|
+
function getUrlHash(value) {
|
|
1546
|
+
const hashIndex = value.indexOf("#");
|
|
1547
|
+
return hashIndex >= 0 ? value.slice(hashIndex) : "";
|
|
1548
|
+
}
|
|
1549
|
+
function resolveDocsRetrievalCanonicalUrl(page, resultUrl, baseUrl) {
|
|
1550
|
+
const requestedCanonical = page.canonicalUrl?.trim();
|
|
1551
|
+
const requested = requestedCanonical && requestedCanonical.length <= MAX_RETRIEVAL_SOURCE_URL_CHARS ? requestedCanonical : page.url;
|
|
1552
|
+
const explicitScheme = requested.match(/^([a-z][a-z\d+.-]*):/iu)?.[1]?.toLowerCase();
|
|
1553
|
+
const pageScheme = page.url.match(/^([a-z][a-z\d+.-]*):/iu)?.[1]?.toLowerCase();
|
|
1554
|
+
const safePageUrl = pageScheme && pageScheme !== "http" && pageScheme !== "https" ? "/" : page.url;
|
|
1555
|
+
const configured = explicitScheme && explicitScheme !== "http" && explicitScheme !== "https" ? safePageUrl : requested;
|
|
1556
|
+
let canonical = configured;
|
|
1557
|
+
if (baseUrl) try {
|
|
1558
|
+
canonical = new URL(configured, baseUrl).toString();
|
|
1559
|
+
} catch {
|
|
1560
|
+
canonical = configured;
|
|
1561
|
+
}
|
|
1562
|
+
else if (configured.startsWith("//")) canonical = `https:${configured}`;
|
|
1563
|
+
const withSection = `${canonical.split("#", 1)[0]}${getUrlHash(resultUrl)}`;
|
|
1564
|
+
if (withSection.length <= MAX_RETRIEVAL_SOURCE_URL_CHARS && isDocsRetrievalCanonicalUrl(withSection)) return withSection;
|
|
1565
|
+
const fallback = (baseUrl ? (() => {
|
|
1566
|
+
try {
|
|
1567
|
+
return new URL(safePageUrl, baseUrl).toString();
|
|
1568
|
+
} catch {
|
|
1569
|
+
return safePageUrl;
|
|
1570
|
+
}
|
|
1571
|
+
})() : safePageUrl).split("#", 1)[0];
|
|
1572
|
+
if (fallback.length <= MAX_RETRIEVAL_SOURCE_URL_CHARS && isDocsRetrievalCanonicalUrl(fallback)) return fallback;
|
|
1573
|
+
if (baseUrl) try {
|
|
1574
|
+
const root = new URL("/", baseUrl).toString();
|
|
1575
|
+
if (root.length <= MAX_RETRIEVAL_SOURCE_URL_CHARS && isDocsRetrievalCanonicalUrl(root)) return root;
|
|
1576
|
+
} catch {}
|
|
1577
|
+
return "/";
|
|
1578
|
+
}
|
|
1579
|
+
function hashDocsRetrievalValue(value) {
|
|
1580
|
+
return digestDocsRetrievalContent(value);
|
|
1581
|
+
}
|
|
1582
|
+
const docsRetrievalDigestMemo = /* @__PURE__ */ new WeakMap();
|
|
1583
|
+
function getDocsRetrievalSourceDigest(page, audience, cache) {
|
|
1584
|
+
const cached = cache?.get(page);
|
|
1585
|
+
if (cached) return cached;
|
|
1586
|
+
const source = getPageAudienceSource(page, audience);
|
|
1587
|
+
let agentContractKey = "";
|
|
1588
|
+
let cacheable = true;
|
|
1589
|
+
try {
|
|
1590
|
+
agentContractKey = JSON.stringify(page.agent ?? null);
|
|
1591
|
+
} catch {
|
|
1592
|
+
cacheable = false;
|
|
1593
|
+
}
|
|
1594
|
+
const memo = docsRetrievalDigestMemo.get(page)?.[audience];
|
|
1595
|
+
if (cacheable && memo && memo.source === source && memo.agentContractKey === agentContractKey) {
|
|
1596
|
+
cache?.set(page, memo.digest);
|
|
1597
|
+
return memo.digest;
|
|
1598
|
+
}
|
|
1599
|
+
const digest = hashDocsRetrievalValue(buildDocsRetrievalDigestProjection(page, audience));
|
|
1600
|
+
if (cacheable) {
|
|
1601
|
+
const pageMemo = docsRetrievalDigestMemo.get(page) ?? {};
|
|
1602
|
+
pageMemo[audience] = {
|
|
1603
|
+
source,
|
|
1604
|
+
agentContractKey,
|
|
1605
|
+
digest
|
|
1606
|
+
};
|
|
1607
|
+
docsRetrievalDigestMemo.set(page, pageMemo);
|
|
1608
|
+
}
|
|
1609
|
+
cache?.set(page, digest);
|
|
1610
|
+
return digest;
|
|
1611
|
+
}
|
|
1612
|
+
function resolveDocsRetrievalLastModified(page, audience) {
|
|
1613
|
+
const parseCandidate = (value) => {
|
|
1614
|
+
const normalized = value?.trim();
|
|
1615
|
+
if (!normalized) return void 0;
|
|
1616
|
+
const timestamp = Date.parse(normalized);
|
|
1617
|
+
return Number.isFinite(timestamp) ? {
|
|
1618
|
+
value: normalized,
|
|
1619
|
+
timestamp
|
|
1620
|
+
} : void 0;
|
|
1621
|
+
};
|
|
1622
|
+
const pageModified = parseCandidate(page.lastmod) ?? parseCandidate(page.lastModified);
|
|
1623
|
+
if (!(audience === "agent" && (page.agentRawContent !== void 0 || page.agentContent !== void 0))) return pageModified?.value;
|
|
1624
|
+
const agentModified = parseCandidate(page.agentLastModified);
|
|
1625
|
+
if (!renderPageAgentContractMarkdown(page.agent)) return agentModified?.value ?? pageModified?.value;
|
|
1626
|
+
if (!agentModified) return pageModified?.value;
|
|
1627
|
+
if (!pageModified) return agentModified.value;
|
|
1628
|
+
return agentModified.timestamp >= pageModified.timestamp ? agentModified.value : pageModified.value;
|
|
1629
|
+
}
|
|
1630
|
+
async function buildDocsSearchIndexGeneration(pages, options) {
|
|
1631
|
+
const sources = (await Promise.all(pages.map(async (rawPage) => {
|
|
1632
|
+
const page = localizeDocsSearchPage(rawPage, options.locale);
|
|
1633
|
+
const authoredLastModified = page.lastmod?.trim();
|
|
1634
|
+
return {
|
|
1635
|
+
canonicalIdentity: normalizeAuthoredUrlIdentity(page.canonicalUrl?.trim() || page.url),
|
|
1636
|
+
url: normalizeAuthoredUrlIdentity(page.url),
|
|
1637
|
+
indexedUrl: page.url,
|
|
1638
|
+
title: page.title,
|
|
1639
|
+
description: page.description,
|
|
1640
|
+
type: page.type,
|
|
1641
|
+
scope: buildDocsRetrievalSourceScope(page, options.audience, options.locale),
|
|
1642
|
+
scopeIdentity: buildDocsRetrievalScopeIdentity(page, options.audience, options.locale),
|
|
1643
|
+
lastModified: authoredLastModified && Number.isFinite(Date.parse(authoredLastModified)) ? authoredLastModified : void 0,
|
|
1644
|
+
digest: getDocsRetrievalSourceDigest(rawPage, options.audience, options.digestCache),
|
|
1645
|
+
agentContract: getPageAgentContractSearchText(rawPage)
|
|
1646
|
+
};
|
|
1647
|
+
}))).sort((left, right) => {
|
|
1648
|
+
const canonical = compareSearchMetadataValues(left.canonicalIdentity, right.canonicalIdentity);
|
|
1649
|
+
if (canonical !== 0) return canonical;
|
|
1650
|
+
const url = compareSearchMetadataValues(left.url, right.url);
|
|
1651
|
+
if (url !== 0) return url;
|
|
1652
|
+
const digest = compareSearchMetadataValues(left.digest, right.digest);
|
|
1653
|
+
if (digest !== 0) return digest;
|
|
1654
|
+
const title = compareSearchMetadataValues(left.title, right.title);
|
|
1655
|
+
return title !== 0 ? title : compareSearchMetadataValues(JSON.stringify(left), JSON.stringify(right));
|
|
1656
|
+
});
|
|
1657
|
+
return hashDocsRetrievalValue(JSON.stringify({
|
|
1658
|
+
format: RETRIEVAL_INDEX_FORMAT,
|
|
1659
|
+
audience: options.audience,
|
|
1660
|
+
chunking: options.chunking.strategy ?? "section",
|
|
1661
|
+
sources
|
|
1662
|
+
}));
|
|
1663
|
+
}
|
|
1664
|
+
async function buildDocsRetrievalSource(rawPage, resultUrl, options) {
|
|
1665
|
+
const page = localizeDocsSearchPage(rawPage, options.locale);
|
|
1666
|
+
const lastModified = resolveDocsRetrievalLastModified(rawPage, options.audience);
|
|
1667
|
+
return {
|
|
1668
|
+
canonicalUrl: resolveDocsRetrievalCanonicalUrl(page, resultUrl, options.baseUrl),
|
|
1669
|
+
scope: buildDocsRetrievalSourceScope(page, options.audience, options.locale),
|
|
1670
|
+
...lastModified ? { lastModified } : {},
|
|
1671
|
+
digest: getDocsRetrievalSourceDigest(rawPage, options.audience, options.digestCache),
|
|
1672
|
+
indexGeneration: options.indexGeneration
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
function parseRetrievalSourceString(value, maxChars = MAX_RETRIEVAL_SOURCE_VALUE_CHARS) {
|
|
1676
|
+
if (typeof value !== "string") return void 0;
|
|
1677
|
+
const normalized = value.trim();
|
|
1678
|
+
if (!normalized || normalized.length > maxChars) return void 0;
|
|
1679
|
+
if (Array.from(normalized).some((character) => {
|
|
1680
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
1681
|
+
return codePoint <= 31 || codePoint === 127;
|
|
1682
|
+
})) return void 0;
|
|
1683
|
+
return normalized;
|
|
1684
|
+
}
|
|
1685
|
+
function parseRetrievalSourceValues(field, value) {
|
|
1686
|
+
if (!Array.isArray(value) || value.length > MAX_SEARCH_FILTER_VALUES) return { valid: false };
|
|
1687
|
+
const parsed = [];
|
|
1688
|
+
for (const item of value) {
|
|
1689
|
+
const stringValue = parseRetrievalSourceString(item, MAX_SEARCH_FILTER_VALUE_CHARS);
|
|
1690
|
+
if (!stringValue) return { valid: false };
|
|
1691
|
+
if (field === "locale") {
|
|
1692
|
+
const normalizedLocale = normalizeAgentLocale(stringValue);
|
|
1693
|
+
if (!normalizedLocale) return { valid: false };
|
|
1694
|
+
parsed.push(normalizedLocale);
|
|
1695
|
+
continue;
|
|
1696
|
+
}
|
|
1697
|
+
const normalized = normalizeDocsSearchFilterValue(field, stringValue);
|
|
1698
|
+
if (!normalized) return { valid: false };
|
|
1699
|
+
parsed.push(normalized);
|
|
1700
|
+
}
|
|
1701
|
+
return {
|
|
1702
|
+
valid: true,
|
|
1703
|
+
values: sortRetrievalSourceValues(parsed)
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
function parseRetrievalSourceVersionGroups(value) {
|
|
1707
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_SEARCH_FILTER_VALUES) return { valid: false };
|
|
1708
|
+
const groups = value.map((group) => parseRetrievalSourceValues("version", group));
|
|
1709
|
+
if (groups.some((group) => !group.valid || !group.values || group.values.length === 0)) return { valid: false };
|
|
1710
|
+
return {
|
|
1711
|
+
valid: true,
|
|
1712
|
+
groups: groups.map((group) => group.values)
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
function parseDocsRetrievalSource(value, options = {}) {
|
|
1716
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1717
|
+
const record = value;
|
|
1718
|
+
const canonicalUrl = parseRetrievalSourceString(record.canonicalUrl, MAX_RETRIEVAL_SOURCE_URL_CHARS);
|
|
1719
|
+
const digest = parseRetrievalSourceString(record.digest);
|
|
1720
|
+
const indexGeneration = parseRetrievalSourceString(record.indexGeneration);
|
|
1721
|
+
if (!canonicalUrl || !digest || !indexGeneration) return void 0;
|
|
1722
|
+
const digestPattern = /^sha256:[a-f\d]{64}$/iu;
|
|
1723
|
+
if (!digestPattern.test(digest) || !digestPattern.test(indexGeneration)) return void 0;
|
|
1724
|
+
if (!isDocsRetrievalCanonicalUrl(canonicalUrl) || /^\/(?!\/)/u.test(canonicalUrl) && !options.allowRootRelativeCanonical) return;
|
|
1725
|
+
if (!record.scope || typeof record.scope !== "object" || Array.isArray(record.scope)) return;
|
|
1726
|
+
const rawScope = record.scope;
|
|
1727
|
+
const audience = rawScope.audience === "human" || rawScope.audience === "agent" ? rawScope.audience : void 0;
|
|
1728
|
+
if (!audience) return void 0;
|
|
1729
|
+
const parseScopeFields = (value) => {
|
|
1730
|
+
if (!Array.isArray(value) || value.length > SEARCH_FILTER_FIELDS.length || value.some((field) => typeof field !== "string" || !SEARCH_FILTER_FIELDS.includes(field))) return { valid: false };
|
|
1731
|
+
return {
|
|
1732
|
+
valid: true,
|
|
1733
|
+
values: Array.from(new Set(value)).sort((left, right) => SEARCH_FILTER_FIELDS.indexOf(left) - SEARCH_FILTER_FIELDS.indexOf(right))
|
|
1734
|
+
};
|
|
1735
|
+
};
|
|
1736
|
+
const parseOptionalValues = (field, value) => value === void 0 ? { valid: true } : parseRetrievalSourceValues(field, value);
|
|
1737
|
+
const parseOptionalScopeFields = (value) => value === void 0 ? { valid: true } : parseScopeFields(value);
|
|
1738
|
+
const conflicts = parseOptionalScopeFields(rawScope.conflicts);
|
|
1739
|
+
const truncated = parseOptionalScopeFields(rawScope.truncated);
|
|
1740
|
+
const locale = parseOptionalValues("locale", rawScope.locale);
|
|
1741
|
+
const framework = parseOptionalValues("framework", rawScope.framework);
|
|
1742
|
+
const version = parseOptionalValues("version", rawScope.version);
|
|
1743
|
+
const versionGroups = rawScope.versionGroups === void 0 ? {
|
|
1744
|
+
valid: true,
|
|
1745
|
+
groups: void 0
|
|
1746
|
+
} : parseRetrievalSourceVersionGroups(rawScope.versionGroups);
|
|
1747
|
+
const packageNames = parseOptionalValues("package", rawScope.package);
|
|
1748
|
+
const tags = parseOptionalValues("tags", rawScope.tags);
|
|
1749
|
+
if (!conflicts.valid || !truncated.valid || !locale.valid || !framework.valid || !version.valid || !versionGroups.valid || !packageNames.valid || !tags.valid) return;
|
|
1750
|
+
const scope = {
|
|
1751
|
+
audience,
|
|
1752
|
+
...locale.values ? { locale: locale.values } : {},
|
|
1753
|
+
...framework.values ? { framework: framework.values } : {},
|
|
1754
|
+
...version.values ? { version: version.values } : {},
|
|
1755
|
+
...versionGroups.groups ? { versionGroups: versionGroups.groups } : {},
|
|
1756
|
+
...packageNames.values ? { package: packageNames.values } : {},
|
|
1757
|
+
...tags.values ? { tags: tags.values } : {},
|
|
1758
|
+
...truncated.values && truncated.values.length > 0 ? { truncated: truncated.values } : {},
|
|
1759
|
+
...conflicts.values && conflicts.values.length > 0 ? { conflicts: conflicts.values } : {}
|
|
1760
|
+
};
|
|
1761
|
+
const rawLastModified = parseRetrievalSourceString(record.lastModified);
|
|
1762
|
+
if (record.lastModified !== void 0 && (!rawLastModified || !Number.isFinite(Date.parse(rawLastModified)))) return;
|
|
1763
|
+
const lastModified = rawLastModified && Number.isFinite(Date.parse(rawLastModified)) ? rawLastModified : void 0;
|
|
1764
|
+
return {
|
|
1765
|
+
canonicalUrl,
|
|
1766
|
+
scope,
|
|
1767
|
+
...lastModified ? { lastModified } : {},
|
|
1768
|
+
digest,
|
|
1769
|
+
indexGeneration
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
function docsRetrievalSourceMatchesRequest(source, audience, filters, locale) {
|
|
1773
|
+
if (source.scope.audience !== audience) return false;
|
|
1774
|
+
for (const field of SEARCH_FILTER_FIELDS) {
|
|
1775
|
+
const requested = filters?.[field];
|
|
1776
|
+
if (!requested?.length) continue;
|
|
1777
|
+
if (source.scope.conflicts?.includes(field)) return false;
|
|
1778
|
+
if (source.scope.truncated?.includes(field)) return false;
|
|
1779
|
+
if (field === "version" && source.scope.versionGroups?.length) {
|
|
1780
|
+
if (!agentVersionConstraintGroupsOverlap([requested, ...source.scope.versionGroups])) return false;
|
|
1781
|
+
continue;
|
|
1782
|
+
}
|
|
1783
|
+
const candidates = source.scope[field];
|
|
1784
|
+
if (!candidates?.length) return false;
|
|
1785
|
+
if (!requested.some((requestedValue) => candidates.some((candidate) => docsSearchScopeValueMatches(field, requestedValue, candidate)))) return false;
|
|
1786
|
+
}
|
|
1787
|
+
const requestedLocale = locale ? normalizeAgentLocale(locale) : void 0;
|
|
1788
|
+
if (requestedLocale && (!source.scope.locale?.length || !source.scope.locale.some((candidate) => normalizeAgentLocale(candidate) === requestedLocale))) return false;
|
|
1789
|
+
return true;
|
|
1790
|
+
}
|
|
1791
|
+
const HOSTED_RETRIEVAL_SOURCE_FIELDS = [
|
|
1792
|
+
"source_canonical_url",
|
|
1793
|
+
"source_scope_audience",
|
|
1794
|
+
"source_scope_locale",
|
|
1795
|
+
"source_scope_framework",
|
|
1796
|
+
"source_scope_version",
|
|
1797
|
+
"source_scope_version_groups",
|
|
1798
|
+
"source_scope_package",
|
|
1799
|
+
"source_scope_tags",
|
|
1800
|
+
"source_scope_truncated",
|
|
1801
|
+
"source_scope_conflicts",
|
|
1802
|
+
"source_last_modified",
|
|
1803
|
+
"source_digest",
|
|
1804
|
+
"source_index_generation"
|
|
1805
|
+
];
|
|
1806
|
+
function readFlattenedHostedRetrievalSource(record) {
|
|
1807
|
+
return parseDocsRetrievalSource({
|
|
1808
|
+
canonicalUrl: record.source_canonical_url,
|
|
1809
|
+
scope: {
|
|
1810
|
+
audience: record.source_scope_audience,
|
|
1811
|
+
locale: record.source_scope_locale,
|
|
1812
|
+
framework: record.source_scope_framework,
|
|
1813
|
+
version: record.source_scope_version,
|
|
1814
|
+
versionGroups: (() => {
|
|
1815
|
+
const value = record.source_scope_version_groups;
|
|
1816
|
+
if (typeof value !== "string") return value;
|
|
1817
|
+
try {
|
|
1818
|
+
return JSON.parse(value);
|
|
1819
|
+
} catch {
|
|
1820
|
+
return null;
|
|
1821
|
+
}
|
|
1822
|
+
})(),
|
|
1823
|
+
package: record.source_scope_package,
|
|
1824
|
+
tags: record.source_scope_tags,
|
|
1825
|
+
truncated: record.source_scope_truncated,
|
|
1826
|
+
conflicts: record.source_scope_conflicts
|
|
1827
|
+
},
|
|
1828
|
+
lastModified: record.source_last_modified,
|
|
1829
|
+
digest: record.source_digest,
|
|
1830
|
+
indexGeneration: record.source_index_generation
|
|
1831
|
+
}, { allowRootRelativeCanonical: true });
|
|
1832
|
+
}
|
|
1833
|
+
function readHostedRetrievalSource(record) {
|
|
1834
|
+
const hasNested = record.source !== void 0;
|
|
1835
|
+
const hasFlattened = HOSTED_RETRIEVAL_SOURCE_FIELDS.some((key) => key in record);
|
|
1836
|
+
if (hasNested) {
|
|
1837
|
+
const nested = parseDocsRetrievalSource(record.source, { allowRootRelativeCanonical: true });
|
|
1838
|
+
if (!nested) return void 0;
|
|
1839
|
+
if (!hasFlattened) return nested;
|
|
1840
|
+
const flattened = readFlattenedHostedRetrievalSource(record);
|
|
1841
|
+
return flattened && JSON.stringify(flattened) === JSON.stringify(nested) ? nested : void 0;
|
|
1842
|
+
}
|
|
1843
|
+
return readFlattenedHostedRetrievalSource(record);
|
|
1844
|
+
}
|
|
1845
|
+
function hasHostedRetrievalSource(record) {
|
|
1846
|
+
return record.source !== void 0 || [
|
|
1847
|
+
"source_corpus_id",
|
|
1848
|
+
"source_document_id",
|
|
1849
|
+
...HOSTED_RETRIEVAL_SOURCE_FIELDS
|
|
1850
|
+
].some((key) => key in record);
|
|
1851
|
+
}
|
|
1852
|
+
function serializeHostedRetrievalSource(source, corpusId, sourceDocumentId) {
|
|
1853
|
+
if (!source) return {};
|
|
1854
|
+
return {
|
|
1855
|
+
source_corpus_id: corpusId,
|
|
1856
|
+
source_document_id: sourceDocumentId,
|
|
1857
|
+
source_canonical_url: source.canonicalUrl,
|
|
1858
|
+
source_scope_audience: source.scope.audience,
|
|
1859
|
+
source_scope_locale: source.scope.locale,
|
|
1860
|
+
source_scope_framework: source.scope.framework,
|
|
1861
|
+
source_scope_version: source.scope.version,
|
|
1862
|
+
source_scope_version_groups: source.scope.versionGroups ? JSON.stringify(source.scope.versionGroups) : void 0,
|
|
1863
|
+
source_scope_package: source.scope.package,
|
|
1864
|
+
source_scope_tags: source.scope.tags,
|
|
1865
|
+
source_scope_truncated: source.scope.truncated,
|
|
1866
|
+
source_scope_conflicts: source.scope.conflicts,
|
|
1867
|
+
source_last_modified: source.lastModified,
|
|
1868
|
+
source_digest: source.digest,
|
|
1869
|
+
source_index_generation: source.indexGeneration
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
function resolveHostedCorpusId(syncNamespace, context) {
|
|
1873
|
+
const explicitNamespace = syncNamespace?.trim();
|
|
1874
|
+
if (explicitNamespace && explicitNamespace.length > 1024) throw new Error("Search syncNamespace must be 1024 characters or fewer.");
|
|
1875
|
+
let canonicalIdentity;
|
|
1876
|
+
const indexBaseUrl = context.indexBaseUrl;
|
|
1877
|
+
if (!explicitNamespace && indexBaseUrl) try {
|
|
1878
|
+
const url = new URL(indexBaseUrl);
|
|
1879
|
+
if ((url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password) canonicalIdentity = `${url.origin}${url.pathname.replace(/\/+$/u, "") || "/"}`;
|
|
1880
|
+
} catch {}
|
|
1881
|
+
const identity = explicitNamespace ? `namespace:${explicitNamespace}` : canonicalIdentity ? `canonical:${canonicalIdentity}` : void 0;
|
|
1882
|
+
if (!identity) return void 0;
|
|
1883
|
+
return hashDocsRetrievalValue(JSON.stringify({
|
|
1884
|
+
format: "docs-hosted-corpus.v1",
|
|
1885
|
+
identity,
|
|
1886
|
+
audience: resolveDocsSearchAudience(context.audience),
|
|
1887
|
+
locale: context.locale ? normalizeAgentLocale(context.locale) : "__all__"
|
|
1888
|
+
}));
|
|
1889
|
+
}
|
|
1890
|
+
function resolveHostedHumanCorpusId(syncNamespace, context) {
|
|
1891
|
+
return resolveHostedCorpusId(syncNamespace, {
|
|
1892
|
+
audience: "human",
|
|
1893
|
+
locale: context.locale,
|
|
1894
|
+
baseUrl: context.baseUrl,
|
|
1895
|
+
indexBaseUrl: context.indexBaseUrl
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
function makeHostedProviderDocumentId(corpusId, sourceDocumentId) {
|
|
1899
|
+
return `docs_${hashDocsRetrievalValue(JSON.stringify({
|
|
1900
|
+
format: "docs-hosted-document.v1",
|
|
1901
|
+
corpusId,
|
|
1902
|
+
sourceDocumentId
|
|
1903
|
+
})).slice(7)}`;
|
|
1904
|
+
}
|
|
1905
|
+
function readHostedSourceDocumentId(record) {
|
|
1906
|
+
return parseRetrievalSourceString(record.source_document_id, MAX_RETRIEVAL_SOURCE_URL_CHARS);
|
|
1907
|
+
}
|
|
1908
|
+
function hostedRecordMatchesCorpus(record, corpusId) {
|
|
1909
|
+
if (!corpusId) return true;
|
|
1910
|
+
if (record.source_corpus_id === corpusId) return true;
|
|
1911
|
+
return record.source_corpus_id === void 0 && !hasHostedRetrievalSource(record);
|
|
1912
|
+
}
|
|
1913
|
+
async function enrichDocsSearchResultsWithSources(options) {
|
|
1914
|
+
const digestCache = /* @__PURE__ */ new Map();
|
|
1915
|
+
const localizedPages = options.pages.map((page) => localizeDocsSearchPage(page, options.locale));
|
|
1916
|
+
const pagesByRoute = /* @__PURE__ */ new Map();
|
|
1917
|
+
for (const page of localizedPages) {
|
|
1918
|
+
const route = normalizeUrlRouteKey(page.url);
|
|
1919
|
+
if (!pagesByRoute.has(route)) pagesByRoute.set(route, page);
|
|
1920
|
+
}
|
|
1921
|
+
let resolvedIndexGeneration;
|
|
1922
|
+
const getIndexGeneration = () => {
|
|
1923
|
+
resolvedIndexGeneration ??= options.indexGeneration ? Promise.resolve(options.indexGeneration) : buildDocsSearchIndexGeneration(options.generationPages ?? options.pages, {
|
|
1924
|
+
audience: options.audience,
|
|
1925
|
+
chunking: options.chunking,
|
|
1926
|
+
locale: options.locale,
|
|
1927
|
+
baseUrl: options.baseUrl,
|
|
1928
|
+
digestCache
|
|
1929
|
+
});
|
|
1930
|
+
return resolvedIndexGeneration;
|
|
1931
|
+
};
|
|
1932
|
+
return (await Promise.all(options.results.map(async (result) => {
|
|
1933
|
+
const { source: rawSource, ...resultWithoutSource } = result;
|
|
1934
|
+
const page = findPageForSearchResult(localizedPages, result, options.baseUrl, pagesByRoute);
|
|
1935
|
+
if (page) return {
|
|
1936
|
+
...resultWithoutSource,
|
|
1937
|
+
source: await buildDocsRetrievalSource(page, result.url, {
|
|
1938
|
+
audience: options.audience,
|
|
1939
|
+
chunking: options.chunking,
|
|
1940
|
+
locale: options.locale,
|
|
1941
|
+
baseUrl: options.baseUrl,
|
|
1942
|
+
indexGeneration: await getIndexGeneration(),
|
|
1943
|
+
digestCache
|
|
1944
|
+
})
|
|
1945
|
+
};
|
|
1946
|
+
if (rawSource === void 0) return resultWithoutSource;
|
|
1947
|
+
const source = parseDocsRetrievalSource(rawSource, { allowRootRelativeCanonical: options.requireCurrentIndexGeneration });
|
|
1948
|
+
if (!source) return null;
|
|
1949
|
+
if (!docsRetrievalSourceMatchesRequest(source, options.audience, options.filters, options.locale)) return null;
|
|
1950
|
+
if (options.requireCurrentIndexGeneration && source.indexGeneration !== await getIndexGeneration()) return null;
|
|
1951
|
+
return {
|
|
1952
|
+
...resultWithoutSource,
|
|
1953
|
+
source
|
|
1954
|
+
};
|
|
1955
|
+
}))).filter((result) => Boolean(result));
|
|
1956
|
+
}
|
|
1957
|
+
async function enrichDocsSearchDocumentsWithSources(options) {
|
|
1958
|
+
const results = await enrichDocsSearchResultsWithSources({
|
|
1959
|
+
results: options.documents.map((document) => ({
|
|
1960
|
+
id: document.id,
|
|
1961
|
+
url: document.url,
|
|
1962
|
+
content: document.title,
|
|
1963
|
+
description: document.description,
|
|
1964
|
+
type: document.type,
|
|
1965
|
+
section: document.section,
|
|
1966
|
+
source: document.source
|
|
1967
|
+
})),
|
|
1968
|
+
pages: options.pages,
|
|
1969
|
+
audience: options.audience,
|
|
1970
|
+
chunking: options.chunking,
|
|
1971
|
+
locale: options.locale,
|
|
1972
|
+
baseUrl: options.baseUrl,
|
|
1973
|
+
indexGeneration: options.indexGeneration
|
|
1974
|
+
});
|
|
1975
|
+
const sources = new Map(results.map((result) => [result.id, result.source]));
|
|
1976
|
+
return options.documents.map((document) => {
|
|
1977
|
+
const source = sources.get(document.id);
|
|
1978
|
+
return source ? {
|
|
1979
|
+
...document,
|
|
1980
|
+
source
|
|
1981
|
+
} : document;
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Attach the same canonical provenance used by built-in search providers.
|
|
1986
|
+
* Custom adapters can call this before persisting their own hosted records.
|
|
1987
|
+
*/
|
|
1988
|
+
function enrichDocsSearchDocumentsWithProvenance(options) {
|
|
1989
|
+
return enrichDocsSearchDocumentsWithSources({
|
|
1990
|
+
...options,
|
|
1991
|
+
audience: resolveDocsSearchAudience(options.audience),
|
|
1992
|
+
chunking: options.chunking ?? { strategy: "section" }
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
function isLocalProviderResult(result, baseUrl) {
|
|
1284
1996
|
const rawUrl = result.url.trim();
|
|
1285
1997
|
const explicitScheme = rawUrl.match(/^([a-z][a-z\d+.-]*):/iu)?.[1]?.toLowerCase();
|
|
1286
1998
|
if (explicitScheme && explicitScheme !== "http" && explicitScheme !== "https") return false;
|
|
1287
1999
|
if (!(/^[a-z][a-z\d+.-]*:/iu.test(rawUrl) || /^[\\/]{2}/u.test(rawUrl))) return true;
|
|
1288
|
-
if (!baseUrl) return
|
|
2000
|
+
if (!baseUrl) return false;
|
|
1289
2001
|
try {
|
|
1290
2002
|
return new URL(result.url, baseUrl).origin === new URL(baseUrl).origin;
|
|
1291
2003
|
} catch {
|
|
@@ -1293,10 +2005,10 @@ function isLocalProviderResult(result, baseUrl, strictExternalOrigins = false) {
|
|
|
1293
2005
|
}
|
|
1294
2006
|
}
|
|
1295
2007
|
function hasOppositeAudienceEvidence(options) {
|
|
1296
|
-
const { result, pages, query, audience, baseUrl
|
|
1297
|
-
if (!isLocalProviderResult(result, baseUrl
|
|
1298
|
-
const pagePath =
|
|
1299
|
-
const page = pages.find((candidate) =>
|
|
2008
|
+
const { result, pages, query, audience, baseUrl } = options;
|
|
2009
|
+
if (!isLocalProviderResult(result, baseUrl)) return false;
|
|
2010
|
+
const pagePath = normalizeUrlRouteKey(result.url);
|
|
2011
|
+
const page = pages.find((candidate) => normalizeUrlRouteKey(candidate.url) === pagePath);
|
|
1300
2012
|
if (!page) return false;
|
|
1301
2013
|
if (scoreDocument(query, pageToSearchDocument(page, audience)) > 0) return false;
|
|
1302
2014
|
const oppositeAudience = audience === "agent" ? "human" : "agent";
|
|
@@ -1316,13 +2028,14 @@ function hasOppositeAudienceEvidence(options) {
|
|
|
1316
2028
|
}
|
|
1317
2029
|
return oppositeOnlyMatches > selectedOnlyMatches;
|
|
1318
2030
|
}
|
|
1319
|
-
function pageToSearchDocument(
|
|
2031
|
+
function pageToSearchDocument(rawPage, audience = "human") {
|
|
2032
|
+
const page = localizeDocsSearchPage(rawPage, rawPage.locale);
|
|
1320
2033
|
const scope = resolveDocsSearchPageScope(page);
|
|
1321
2034
|
return {
|
|
1322
2035
|
id: makeDocumentId(page.url, "page"),
|
|
1323
2036
|
url: page.url,
|
|
1324
2037
|
title: page.title,
|
|
1325
|
-
content:
|
|
2038
|
+
content: getPageAudienceIndexContent(page, audience),
|
|
1326
2039
|
description: page.description,
|
|
1327
2040
|
type: "page",
|
|
1328
2041
|
locale: page.locale,
|
|
@@ -1385,6 +2098,23 @@ function buildAskAIContextBlock(result) {
|
|
|
1385
2098
|
const lines = [`## ${result.title}`, `URL: ${result.url}`];
|
|
1386
2099
|
if (result.section) lines.push(`Section: ${result.section}`);
|
|
1387
2100
|
if (result.description) lines.push(`Search snippet: ${result.description}`);
|
|
2101
|
+
if (result.source) {
|
|
2102
|
+
const scope = result.source.scope;
|
|
2103
|
+
const scopeEntries = [
|
|
2104
|
+
`audience=${scope.audience}`,
|
|
2105
|
+
scope.locale?.length ? `locale=${scope.locale.join(",")}` : void 0,
|
|
2106
|
+
scope.framework?.length ? `framework=${scope.framework.join(",")}` : void 0,
|
|
2107
|
+
scope.version?.length ? `version=${scope.version.join(",")}` : void 0,
|
|
2108
|
+
scope.package?.length ? `package=${scope.package.join(",")}` : void 0,
|
|
2109
|
+
scope.tags?.length ? `tags=${scope.tags.join(",")}` : void 0,
|
|
2110
|
+
scope.conflicts?.length ? `conflicts=${scope.conflicts.join(",")}` : void 0
|
|
2111
|
+
].filter((value) => Boolean(value));
|
|
2112
|
+
lines.push(`Canonical URL: ${result.source.canonicalUrl}`);
|
|
2113
|
+
lines.push(`Source scope: ${scopeEntries.join("; ")}`);
|
|
2114
|
+
if (result.source.lastModified) lines.push(`Source modified: ${result.source.lastModified}`);
|
|
2115
|
+
lines.push(`Source digest: ${result.source.digest}`);
|
|
2116
|
+
lines.push(`Index generation: ${result.source.indexGeneration}`);
|
|
2117
|
+
}
|
|
1388
2118
|
lines.push("", result.contextContent);
|
|
1389
2119
|
return lines.join("\n").trim();
|
|
1390
2120
|
}
|
|
@@ -1420,7 +2150,8 @@ function splitPageIntoSections(page, audience = "human") {
|
|
|
1420
2150
|
}
|
|
1421
2151
|
function buildDocsSearchDocuments(pages, chunking = {}, audience = "human") {
|
|
1422
2152
|
const strategy = chunking.strategy ?? "section";
|
|
1423
|
-
return pages.flatMap((
|
|
2153
|
+
return pages.flatMap((rawPage) => {
|
|
2154
|
+
const page = localizeDocsSearchPage(rawPage, rawPage.locale);
|
|
1424
2155
|
const base = pageToSearchDocument(page, audience);
|
|
1425
2156
|
if (strategy === "page") return [base];
|
|
1426
2157
|
const sections = splitPageIntoSections(page, audience);
|
|
@@ -1526,50 +2257,60 @@ function cleanSearchResultText(value) {
|
|
|
1526
2257
|
if (!value) return void 0;
|
|
1527
2258
|
return normalizeWhitespace(stripHtml(stripMarkdownText(value))) || void 0;
|
|
1528
2259
|
}
|
|
1529
|
-
function
|
|
1530
|
-
|
|
1531
|
-
const encoder = new TextEncoder();
|
|
1532
|
-
if (encoder.encode(value).length <= maxBytes) return value;
|
|
1533
|
-
let low = 0;
|
|
1534
|
-
let high = value.length;
|
|
1535
|
-
let best = "";
|
|
1536
|
-
while (low <= high) {
|
|
1537
|
-
const mid = Math.floor((low + high) / 2);
|
|
1538
|
-
const next = `${value.slice(0, mid).trimEnd()}...`;
|
|
1539
|
-
if (encoder.encode(next).length <= maxBytes) {
|
|
1540
|
-
best = next;
|
|
1541
|
-
low = mid + 1;
|
|
1542
|
-
} else high = mid - 1;
|
|
1543
|
-
}
|
|
1544
|
-
return best;
|
|
1545
|
-
}
|
|
1546
|
-
function buildAlgoliaRecord(document) {
|
|
2260
|
+
function buildAlgoliaRecord(document, corpusId) {
|
|
2261
|
+
const providerDocumentId = corpusId ? makeHostedProviderDocumentId(corpusId, document.id) : document.id;
|
|
1547
2262
|
const record = {
|
|
1548
|
-
objectID:
|
|
1549
|
-
id:
|
|
2263
|
+
objectID: providerDocumentId,
|
|
2264
|
+
id: providerDocumentId,
|
|
1550
2265
|
url: document.url,
|
|
1551
2266
|
title: document.title,
|
|
1552
2267
|
section: document.section,
|
|
1553
2268
|
content: document.content,
|
|
1554
2269
|
description: document.description,
|
|
1555
2270
|
type: document.type,
|
|
1556
|
-
locale: document.locale,
|
|
2271
|
+
locale: document.locale ?? document.source?.scope.locale?.[0],
|
|
1557
2272
|
framework: document.framework,
|
|
1558
2273
|
version: document.version,
|
|
1559
2274
|
package: document.package,
|
|
1560
|
-
tags: document.tags
|
|
2275
|
+
tags: document.tags,
|
|
2276
|
+
_tags: corpusId ? [corpusId] : void 0,
|
|
2277
|
+
...serializeHostedRetrievalSource(document.source, corpusId, document.id)
|
|
1561
2278
|
};
|
|
1562
2279
|
const encoder = new TextEncoder();
|
|
1563
2280
|
const sizeOf = (value) => encoder.encode(JSON.stringify(value)).length;
|
|
1564
2281
|
if (sizeOf(record) <= ALGOLIA_MAX_RECORD_BYTES) return record;
|
|
1565
2282
|
delete record.description;
|
|
1566
2283
|
if (sizeOf(record) <= ALGOLIA_MAX_RECORD_BYTES) return record;
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
2284
|
+
record.content = "";
|
|
2285
|
+
for (const field of [
|
|
2286
|
+
"package",
|
|
2287
|
+
"tags",
|
|
2288
|
+
"framework",
|
|
2289
|
+
"version"
|
|
2290
|
+
]) {
|
|
2291
|
+
if (sizeOf(record) <= ALGOLIA_MAX_RECORD_BYTES) break;
|
|
2292
|
+
delete record[field];
|
|
2293
|
+
}
|
|
2294
|
+
const trimFieldToFit = (field, value) => {
|
|
2295
|
+
let low = 0;
|
|
2296
|
+
let high = value.length;
|
|
2297
|
+
let best = "";
|
|
2298
|
+
while (low <= high) {
|
|
2299
|
+
const middle = Math.floor((low + high) / 2);
|
|
2300
|
+
const candidate = middle < value.length ? `${value.slice(0, middle).trimEnd()}...` : value;
|
|
2301
|
+
record[field] = candidate;
|
|
2302
|
+
if (sizeOf(record) <= ALGOLIA_MAX_RECORD_BYTES) {
|
|
2303
|
+
best = candidate;
|
|
2304
|
+
low = middle + 1;
|
|
2305
|
+
} else high = middle - 1;
|
|
2306
|
+
}
|
|
2307
|
+
record[field] = best;
|
|
2308
|
+
};
|
|
2309
|
+
if (sizeOf(record) > ALGOLIA_MAX_RECORD_BYTES && typeof record.section === "string") trimFieldToFit("section", record.section);
|
|
2310
|
+
if (sizeOf(record) > ALGOLIA_MAX_RECORD_BYTES && typeof record.title === "string") trimFieldToFit("title", record.title);
|
|
2311
|
+
if (sizeOf(record) > ALGOLIA_MAX_RECORD_BYTES) throw new Error(`Algolia record ${document.id} exceeds the record limit with required provenance.`);
|
|
2312
|
+
trimFieldToFit("content", document.content);
|
|
2313
|
+
if (sizeOf(record) > ALGOLIA_MAX_RECORD_BYTES) throw new Error(`Algolia record ${document.id} still exceeds the record limit after trimming.`);
|
|
1573
2314
|
return record;
|
|
1574
2315
|
}
|
|
1575
2316
|
function createSimpleSearchAdapter() {
|
|
@@ -1577,7 +2318,7 @@ function createSimpleSearchAdapter() {
|
|
|
1577
2318
|
name: "simple",
|
|
1578
2319
|
async search(query, context) {
|
|
1579
2320
|
const limit = query.limit ?? DEFAULT_SEARCH_LIMIT;
|
|
1580
|
-
|
|
2321
|
+
const results = context.documents.map((document) => ({
|
|
1581
2322
|
document,
|
|
1582
2323
|
score: scoreDocument(query.query, document)
|
|
1583
2324
|
})).filter((item) => item.score > 0).sort((a, b) => {
|
|
@@ -1590,8 +2331,20 @@ function createSimpleSearchAdapter() {
|
|
|
1590
2331
|
description: cleanSearchResultText(buildSnippet(document, query.query) ?? document.description),
|
|
1591
2332
|
type: document.type,
|
|
1592
2333
|
score,
|
|
1593
|
-
section: document.section
|
|
2334
|
+
section: document.section,
|
|
2335
|
+
source: document.source
|
|
1594
2336
|
}));
|
|
2337
|
+
if (context.deferSourceProvenance) return results;
|
|
2338
|
+
return enrichDocsSearchResultsWithSources({
|
|
2339
|
+
results,
|
|
2340
|
+
pages: context.pages,
|
|
2341
|
+
audience: resolveDocsSearchAudience(context.audience),
|
|
2342
|
+
chunking: context.chunking ?? { strategy: "section" },
|
|
2343
|
+
locale: query.locale ?? context.locale,
|
|
2344
|
+
baseUrl: context.baseUrl,
|
|
2345
|
+
indexGeneration: context.indexGeneration,
|
|
2346
|
+
filters: query.filters
|
|
2347
|
+
});
|
|
1595
2348
|
}
|
|
1596
2349
|
};
|
|
1597
2350
|
}
|
|
@@ -1644,7 +2397,7 @@ function resolveMcpEndpoint(endpoint) {
|
|
|
1644
2397
|
function isDocsSearchResultType(value) {
|
|
1645
2398
|
return value === "page" || value === "heading" || value === "text";
|
|
1646
2399
|
}
|
|
1647
|
-
function mapMcpSearchResult(value) {
|
|
2400
|
+
function mapMcpSearchResult(value, sourceBaseUrl) {
|
|
1648
2401
|
if (!value || typeof value !== "object") return null;
|
|
1649
2402
|
const item = value;
|
|
1650
2403
|
const section = typeof item.section === "string" ? item.section : void 0;
|
|
@@ -1652,6 +2405,19 @@ function mapMcpSearchResult(value) {
|
|
|
1652
2405
|
const content = typeof item.content === "string" ? item.content : title ? section ? `${title} — ${section}` : title : void 0;
|
|
1653
2406
|
const url = typeof item.url === "string" ? item.url : void 0;
|
|
1654
2407
|
if (!content || !url) return null;
|
|
2408
|
+
const hasSource = item.source !== void 0;
|
|
2409
|
+
let sourceValue = item.source;
|
|
2410
|
+
if (hasSource && sourceBaseUrl && sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue)) {
|
|
2411
|
+
const sourceRecord = sourceValue;
|
|
2412
|
+
if (typeof sourceRecord.canonicalUrl === "string" && /^\/(?!\/)/u.test(sourceRecord.canonicalUrl)) try {
|
|
2413
|
+
sourceValue = {
|
|
2414
|
+
...sourceRecord,
|
|
2415
|
+
canonicalUrl: new URL(sourceRecord.canonicalUrl, sourceBaseUrl).toString()
|
|
2416
|
+
};
|
|
2417
|
+
} catch {}
|
|
2418
|
+
}
|
|
2419
|
+
const source = hasSource ? parseDocsRetrievalSource(sourceValue) : void 0;
|
|
2420
|
+
if (hasSource && !source) return null;
|
|
1655
2421
|
return {
|
|
1656
2422
|
id: typeof item.id === "string" ? item.id : typeof item.slug === "string" ? item.slug : url,
|
|
1657
2423
|
url,
|
|
@@ -1659,7 +2425,8 @@ function mapMcpSearchResult(value) {
|
|
|
1659
2425
|
description: cleanSearchResultText(typeof item.description === "string" ? item.description : typeof item.excerpt === "string" ? item.excerpt : void 0) ?? void 0,
|
|
1660
2426
|
type: isDocsSearchResultType(item.type) ? item.type : section ? "heading" : "page",
|
|
1661
2427
|
score: typeof item.score === "number" ? item.score : void 0,
|
|
1662
|
-
section
|
|
2428
|
+
section,
|
|
2429
|
+
...source ? { source } : {}
|
|
1663
2430
|
};
|
|
1664
2431
|
}
|
|
1665
2432
|
function readMcpSearchToolPayload(payload) {
|
|
@@ -1692,7 +2459,84 @@ function getTypesenseSearchBase(config) {
|
|
|
1692
2459
|
function quoteTypesenseFilterValue(value) {
|
|
1693
2460
|
return `\`${value.replace(/\\/gu, "\\\\").replace(/`/gu, "\\`")}\``;
|
|
1694
2461
|
}
|
|
1695
|
-
|
|
2462
|
+
const TYPESENSE_RETRIEVAL_SOURCE_FIELDS = [
|
|
2463
|
+
{
|
|
2464
|
+
name: "source_corpus_id",
|
|
2465
|
+
type: "string",
|
|
2466
|
+
optional: true
|
|
2467
|
+
},
|
|
2468
|
+
{
|
|
2469
|
+
name: "source_document_id",
|
|
2470
|
+
type: "string",
|
|
2471
|
+
optional: true
|
|
2472
|
+
},
|
|
2473
|
+
{
|
|
2474
|
+
name: "source_canonical_url",
|
|
2475
|
+
type: "string",
|
|
2476
|
+
optional: true
|
|
2477
|
+
},
|
|
2478
|
+
{
|
|
2479
|
+
name: "source_scope_audience",
|
|
2480
|
+
type: "string",
|
|
2481
|
+
optional: true
|
|
2482
|
+
},
|
|
2483
|
+
{
|
|
2484
|
+
name: "source_scope_locale",
|
|
2485
|
+
type: "string[]",
|
|
2486
|
+
optional: true
|
|
2487
|
+
},
|
|
2488
|
+
{
|
|
2489
|
+
name: "source_scope_framework",
|
|
2490
|
+
type: "string[]",
|
|
2491
|
+
optional: true
|
|
2492
|
+
},
|
|
2493
|
+
{
|
|
2494
|
+
name: "source_scope_version",
|
|
2495
|
+
type: "string[]",
|
|
2496
|
+
optional: true
|
|
2497
|
+
},
|
|
2498
|
+
{
|
|
2499
|
+
name: "source_scope_version_groups",
|
|
2500
|
+
type: "string",
|
|
2501
|
+
optional: true
|
|
2502
|
+
},
|
|
2503
|
+
{
|
|
2504
|
+
name: "source_scope_package",
|
|
2505
|
+
type: "string[]",
|
|
2506
|
+
optional: true
|
|
2507
|
+
},
|
|
2508
|
+
{
|
|
2509
|
+
name: "source_scope_tags",
|
|
2510
|
+
type: "string[]",
|
|
2511
|
+
optional: true
|
|
2512
|
+
},
|
|
2513
|
+
{
|
|
2514
|
+
name: "source_scope_truncated",
|
|
2515
|
+
type: "string[]",
|
|
2516
|
+
optional: true
|
|
2517
|
+
},
|
|
2518
|
+
{
|
|
2519
|
+
name: "source_scope_conflicts",
|
|
2520
|
+
type: "string[]",
|
|
2521
|
+
optional: true
|
|
2522
|
+
},
|
|
2523
|
+
{
|
|
2524
|
+
name: "source_last_modified",
|
|
2525
|
+
type: "string",
|
|
2526
|
+
optional: true
|
|
2527
|
+
},
|
|
2528
|
+
{
|
|
2529
|
+
name: "source_digest",
|
|
2530
|
+
type: "string",
|
|
2531
|
+
optional: true
|
|
2532
|
+
},
|
|
2533
|
+
{
|
|
2534
|
+
name: "source_index_generation",
|
|
2535
|
+
type: "string",
|
|
2536
|
+
optional: true
|
|
2537
|
+
}
|
|
2538
|
+
];
|
|
2539
|
+
async function ensureTypesenseCollection(config, dimensions, signal, retryAfterCreateConflict = true, retryAfterAlterFailure = true) {
|
|
1696
2540
|
const baseUrl = getTypesenseSearchBase(config);
|
|
1697
2541
|
const headers = {
|
|
1698
2542
|
"X-TYPESENSE-API-KEY": config.adminApiKey ?? config.apiKey,
|
|
@@ -1702,7 +2546,50 @@ async function ensureTypesenseCollection(config, dimensions, signal) {
|
|
|
1702
2546
|
headers,
|
|
1703
2547
|
signal
|
|
1704
2548
|
});
|
|
1705
|
-
if (existing.ok)
|
|
2549
|
+
if (existing.ok) {
|
|
2550
|
+
const existingPayload = await readResponseJson(existing);
|
|
2551
|
+
if (!Array.isArray(existingPayload?.fields)) throw new Error("Typesense collection response did not include a fields array.");
|
|
2552
|
+
const existingFields = new Map(existingPayload.fields.flatMap((field) => typeof field.name === "string" ? [[field.name, field]] : []));
|
|
2553
|
+
const incompatibleFields = TYPESENSE_RETRIEVAL_SOURCE_FIELDS.flatMap((expected) => {
|
|
2554
|
+
const name = typeof expected.name === "string" ? expected.name : void 0;
|
|
2555
|
+
const existingField = name ? existingFields.get(name) : void 0;
|
|
2556
|
+
if (!name || !existingField) return [];
|
|
2557
|
+
return existingField.type === expected.type && existingField.optional === true ? [] : [name];
|
|
2558
|
+
});
|
|
2559
|
+
if (incompatibleFields.length > 0) throw new Error(`Typesense collection has incompatible provenance fields: ${incompatibleFields.join(", ")}.`);
|
|
2560
|
+
const missingFields = TYPESENSE_RETRIEVAL_SOURCE_FIELDS.filter((field) => typeof field.name === "string" && !existingFields.has(field.name));
|
|
2561
|
+
const embeddingField = existingPayload.fields.find((field) => field.name === "embedding");
|
|
2562
|
+
const expectedEmbeddingField = config.embeddings && dimensions ? {
|
|
2563
|
+
name: "embedding",
|
|
2564
|
+
type: "float[]",
|
|
2565
|
+
num_dim: dimensions,
|
|
2566
|
+
optional: true
|
|
2567
|
+
} : void 0;
|
|
2568
|
+
const replaceEmbedding = Boolean(expectedEmbeddingField && embeddingField && (embeddingField.type !== "float[]" || embeddingField.num_dim !== dimensions));
|
|
2569
|
+
if (replaceEmbedding) ensureOk(await fetch(`${baseUrl}/collections/${encodeURIComponent(config.collection)}`, {
|
|
2570
|
+
method: "PATCH",
|
|
2571
|
+
headers,
|
|
2572
|
+
body: JSON.stringify({ fields: [{
|
|
2573
|
+
name: "embedding",
|
|
2574
|
+
drop: true
|
|
2575
|
+
}] }),
|
|
2576
|
+
signal
|
|
2577
|
+
}), "Failed to replace an incompatible Typesense embedding field");
|
|
2578
|
+
const fieldsToAdd = [...missingFields, ...expectedEmbeddingField && (!embeddingField || replaceEmbedding) ? [expectedEmbeddingField] : []];
|
|
2579
|
+
if (fieldsToAdd.length === 0) return;
|
|
2580
|
+
const altered = await fetch(`${baseUrl}/collections/${encodeURIComponent(config.collection)}`, {
|
|
2581
|
+
method: "PATCH",
|
|
2582
|
+
headers,
|
|
2583
|
+
body: JSON.stringify({ fields: fieldsToAdd }),
|
|
2584
|
+
signal
|
|
2585
|
+
});
|
|
2586
|
+
if (!altered.ok && retryAfterAlterFailure) {
|
|
2587
|
+
await ensureTypesenseCollection(config, dimensions, signal, retryAfterCreateConflict, false);
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
ensureOk(altered, "Failed to update the Typesense search schema");
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
1706
2593
|
if (existing.status !== 404) ensureOk(existing, "Failed to inspect Typesense collection");
|
|
1707
2594
|
const fields = [
|
|
1708
2595
|
{
|
|
@@ -1759,7 +2646,8 @@ async function ensureTypesenseCollection(config, dimensions, signal) {
|
|
|
1759
2646
|
name: "tags",
|
|
1760
2647
|
type: "string[]",
|
|
1761
2648
|
optional: true
|
|
1762
|
-
}
|
|
2649
|
+
},
|
|
2650
|
+
...TYPESENSE_RETRIEVAL_SOURCE_FIELDS
|
|
1763
2651
|
];
|
|
1764
2652
|
if (config.embeddings && dimensions) fields.push({
|
|
1765
2653
|
name: "embedding",
|
|
@@ -1767,7 +2655,7 @@ async function ensureTypesenseCollection(config, dimensions, signal) {
|
|
|
1767
2655
|
num_dim: dimensions,
|
|
1768
2656
|
optional: true
|
|
1769
2657
|
});
|
|
1770
|
-
|
|
2658
|
+
const response = await fetch(`${baseUrl}/collections`, {
|
|
1771
2659
|
method: "POST",
|
|
1772
2660
|
headers,
|
|
1773
2661
|
body: JSON.stringify({
|
|
@@ -1775,34 +2663,66 @@ async function ensureTypesenseCollection(config, dimensions, signal) {
|
|
|
1775
2663
|
fields
|
|
1776
2664
|
}),
|
|
1777
2665
|
signal
|
|
1778
|
-
})
|
|
2666
|
+
});
|
|
2667
|
+
if (response.status === 409 && retryAfterCreateConflict) {
|
|
2668
|
+
await ensureTypesenseCollection(config, dimensions, signal, false, retryAfterAlterFailure);
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
ensureOk(response, "Failed to create Typesense collection");
|
|
2672
|
+
}
|
|
2673
|
+
async function ensureTypesenseImportSucceeded(response, expectedRecords) {
|
|
2674
|
+
ensureOk(response, "Failed to sync documents to Typesense");
|
|
2675
|
+
const lines = (await response.text()).split(/\r?\n/u).filter((line) => line.trim());
|
|
2676
|
+
if (lines.length !== expectedRecords) throw new Error(`Typesense acknowledged ${lines.length} of ${expectedRecords} imported records.`);
|
|
2677
|
+
for (const [index, line] of lines.entries()) {
|
|
2678
|
+
let result;
|
|
2679
|
+
try {
|
|
2680
|
+
result = JSON.parse(line);
|
|
2681
|
+
} catch {
|
|
2682
|
+
throw new Error(`Typesense import returned invalid JSON on record ${index + 1}.`);
|
|
2683
|
+
}
|
|
2684
|
+
if (result.success !== true) {
|
|
2685
|
+
const detail = typeof result.error === "string" ? `: ${result.error}` : "";
|
|
2686
|
+
throw new Error(`Typesense failed to import record ${index + 1}${detail}`);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
1779
2689
|
}
|
|
1780
2690
|
function createTypesenseSearchAdapter(config) {
|
|
1781
2691
|
return {
|
|
1782
2692
|
name: "typesense",
|
|
1783
2693
|
async index(context) {
|
|
1784
2694
|
const adminApiKey = config.adminApiKey ?? config.apiKey;
|
|
1785
|
-
const
|
|
2695
|
+
const corpusId = resolveHostedHumanCorpusId(config.syncNamespace, context);
|
|
2696
|
+
const documents = await enrichDocsSearchDocumentsWithSources({
|
|
2697
|
+
documents: context.documents,
|
|
2698
|
+
pages: context.pages,
|
|
2699
|
+
audience: resolveDocsSearchAudience(context.audience),
|
|
2700
|
+
chunking: context.chunking ?? { strategy: "section" },
|
|
2701
|
+
locale: context.locale,
|
|
2702
|
+
baseUrl: context.baseUrl,
|
|
2703
|
+
indexGeneration: context.indexGeneration
|
|
2704
|
+
});
|
|
2705
|
+
const docsForImport = await Promise.all(documents.map(async (document) => {
|
|
1786
2706
|
const next = {
|
|
1787
|
-
id: document.id,
|
|
2707
|
+
id: corpusId ? makeHostedProviderDocumentId(corpusId, document.id) : document.id,
|
|
1788
2708
|
url: document.url,
|
|
1789
2709
|
title: document.title,
|
|
1790
2710
|
section: document.section,
|
|
1791
2711
|
content: document.content,
|
|
1792
2712
|
description: document.description,
|
|
1793
2713
|
type: document.type,
|
|
1794
|
-
locale: document.locale,
|
|
2714
|
+
locale: document.locale ?? document.source?.scope.locale?.[0],
|
|
1795
2715
|
framework: document.framework,
|
|
1796
2716
|
version: document.version,
|
|
1797
2717
|
package: document.package,
|
|
1798
|
-
tags: document.tags
|
|
2718
|
+
tags: document.tags,
|
|
2719
|
+
...serializeHostedRetrievalSource(document.source, corpusId, document.id)
|
|
1799
2720
|
};
|
|
1800
2721
|
if (config.mode === "hybrid" && config.embeddings) next.embedding = await createOllamaEmbedding(`${document.title}\n${document.section ?? ""}\n${document.content}`.trim(), config.embeddings, context.signal);
|
|
1801
2722
|
return next;
|
|
1802
2723
|
}));
|
|
1803
|
-
if (docsForImport.length === 0) return;
|
|
1804
2724
|
await ensureTypesenseCollection(config, Array.isArray(docsForImport[0]?.embedding) ? docsForImport[0].embedding.length : void 0, context.signal);
|
|
1805
|
-
|
|
2725
|
+
if (docsForImport.length > 0) await ensureTypesenseImportSucceeded(await fetch(`${getTypesenseSearchBase(config)}/collections/${encodeURIComponent(config.collection)}/documents/import?action=upsert`, {
|
|
1806
2726
|
method: "POST",
|
|
1807
2727
|
headers: {
|
|
1808
2728
|
"X-TYPESENSE-API-KEY": adminApiKey,
|
|
@@ -1810,10 +2730,11 @@ function createTypesenseSearchAdapter(config) {
|
|
|
1810
2730
|
},
|
|
1811
2731
|
body: docsForImport.map((document) => JSON.stringify(document)).join("\n"),
|
|
1812
2732
|
signal: context.signal
|
|
1813
|
-
}),
|
|
2733
|
+
}), docsForImport.length);
|
|
1814
2734
|
},
|
|
1815
2735
|
async search(query, context) {
|
|
1816
|
-
const
|
|
2736
|
+
const corpusId = resolveHostedHumanCorpusId(config.syncNamespace, context);
|
|
2737
|
+
const scopedDocumentIds = resolveProviderScopeDocumentIds(query, context, corpusId);
|
|
1817
2738
|
if (scopedDocumentIds?.length === 0) return [];
|
|
1818
2739
|
const params = new URLSearchParams({
|
|
1819
2740
|
q: query.query,
|
|
@@ -1832,7 +2753,8 @@ function createTypesenseSearchAdapter(config) {
|
|
|
1832
2753
|
const vector = await createOllamaEmbedding(query.query, config.embeddings, context.signal);
|
|
1833
2754
|
params.set("vector_query", `embedding:([${vector.join(",")}],k:${Math.max((query.limit ?? 10) * 4, 20)})`);
|
|
1834
2755
|
}
|
|
1835
|
-
const
|
|
2756
|
+
const filterClauses = [corpusId ? `source_corpus_id:=${quoteTypesenseFilterValue(corpusId)}` : void 0, scopedDocumentIds ? `id:=[${scopedDocumentIds.map(quoteTypesenseFilterValue).join(",")}]` : void 0].filter((value) => Boolean(value));
|
|
2757
|
+
const filterBy = filterClauses.length > 0 ? filterClauses.join(" && ") : void 0;
|
|
1836
2758
|
if (filterBy && filterBy.length > MAX_PROVIDER_SCOPE_FILTER_CHARS) return [];
|
|
1837
2759
|
const response = filterBy ? await fetch(`${getTypesenseSearchBase(config)}/multi_search`, {
|
|
1838
2760
|
method: "POST",
|
|
@@ -1852,13 +2774,16 @@ function createTypesenseSearchAdapter(config) {
|
|
|
1852
2774
|
});
|
|
1853
2775
|
ensureOk(response, "Typesense search failed");
|
|
1854
2776
|
const rawPayload = await readResponseJson(response);
|
|
1855
|
-
return ((filterBy ? rawPayload.results?.[0] ?? {} : rawPayload).hits ?? []).
|
|
2777
|
+
return ((filterBy ? rawPayload.results?.[0] ?? {} : rawPayload).hits ?? []).flatMap((hit) => {
|
|
1856
2778
|
const document = hit.document ?? {};
|
|
2779
|
+
if (!hostedRecordMatchesCorpus(document, corpusId)) return [];
|
|
1857
2780
|
const section = typeof document.section === "string" ? document.section : void 0;
|
|
1858
2781
|
const content = typeof document.title === "string" ? section ? `${document.title} — ${section}` : document.title : typeof document.content === "string" ? document.content : "Untitled result";
|
|
1859
2782
|
const description = hit.highlights?.find((item) => item.field === "content")?.snippet ?? hit.highlights?.find((item) => item.field === "description")?.snippet ?? (typeof document.description === "string" ? document.description : void 0);
|
|
1860
|
-
|
|
1861
|
-
|
|
2783
|
+
const source = readHostedRetrievalSource(document);
|
|
2784
|
+
if (hasHostedRetrievalSource(document) && !source) return [];
|
|
2785
|
+
return [{
|
|
2786
|
+
id: readHostedSourceDocumentId(document) ?? (typeof document.id === "string" ? document.id : String(document.url ?? content)),
|
|
1862
2787
|
url: typeof document.url === "string" ? document.url : "/docs",
|
|
1863
2788
|
content: cleanSearchResultText(content) ?? content,
|
|
1864
2789
|
description: cleanSearchResultText(description),
|
|
@@ -1868,8 +2793,9 @@ function createTypesenseSearchAdapter(config) {
|
|
|
1868
2793
|
"text"
|
|
1869
2794
|
].includes(document.type) ? document.type : section ? "heading" : "page",
|
|
1870
2795
|
score: hit.text_match,
|
|
1871
|
-
section
|
|
1872
|
-
|
|
2796
|
+
section,
|
|
2797
|
+
...source ? { source } : {}
|
|
2798
|
+
}];
|
|
1873
2799
|
});
|
|
1874
2800
|
}
|
|
1875
2801
|
};
|
|
@@ -1983,7 +2909,7 @@ function createMcpSearchAdapter(config) {
|
|
|
1983
2909
|
const echoedFilters = parseVerifiedMcpSearchFilters(parsed.filters);
|
|
1984
2910
|
if (!echoedFilters || !docsSearchFiltersMatch(query.filters ?? {}, echoedFilters)) return [];
|
|
1985
2911
|
}
|
|
1986
|
-
return (Array.isArray(parsed) ? parsed : Array.isArray(parsed.results) ? parsed.results : Array.isArray(parsed.pages) ? parsed.pages : []).map(mapMcpSearchResult).filter((result) => Boolean(result));
|
|
2912
|
+
return (Array.isArray(parsed) ? parsed : Array.isArray(parsed.results) ? parsed.results : Array.isArray(parsed.pages) ? parsed.pages : []).map((result) => mapMcpSearchResult(result, endpoint)).filter((result) => Boolean(result));
|
|
1987
2913
|
} finally {
|
|
1988
2914
|
if (sessionId) {
|
|
1989
2915
|
const cleanupController = new AbortController();
|
|
@@ -2006,9 +2932,62 @@ function createMcpSearchAdapter(config) {
|
|
|
2006
2932
|
}
|
|
2007
2933
|
};
|
|
2008
2934
|
}
|
|
2009
|
-
function
|
|
2935
|
+
function getAlgoliaSearchBase(config) {
|
|
2010
2936
|
return `https://${config.appId}-dsn.algolia.net`;
|
|
2011
2937
|
}
|
|
2938
|
+
function getAlgoliaAdminBase(config) {
|
|
2939
|
+
return `https://${config.appId}.algolia.net`;
|
|
2940
|
+
}
|
|
2941
|
+
function getAlgoliaAdminHeaders(config) {
|
|
2942
|
+
return {
|
|
2943
|
+
"Content-Type": "application/json",
|
|
2944
|
+
"X-Algolia-Application-Id": config.appId,
|
|
2945
|
+
"X-Algolia-API-Key": config.adminApiKey ?? config.searchApiKey
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
async function waitForAlgoliaTask(config, taskId, signal) {
|
|
2949
|
+
const deadline = Date.now() + 6e4;
|
|
2950
|
+
for (let attempt = 0; Date.now() < deadline; attempt += 1) {
|
|
2951
|
+
const response = await fetch(`${getAlgoliaAdminBase(config)}/1/indexes/${encodeURIComponent(config.indexName)}/task/${encodeURIComponent(String(taskId))}`, {
|
|
2952
|
+
headers: getAlgoliaAdminHeaders(config),
|
|
2953
|
+
signal
|
|
2954
|
+
});
|
|
2955
|
+
ensureOk(response, "Failed to inspect Algolia sync task");
|
|
2956
|
+
const payload = await readResponseJson(response);
|
|
2957
|
+
if (payload?.status === "published") return;
|
|
2958
|
+
if (payload?.status !== "notPublished") throw new Error("Algolia sync task returned an unknown status.");
|
|
2959
|
+
const delay = Math.min(100 * 2 ** Math.min(attempt, 4), 2e3);
|
|
2960
|
+
await new Promise((resolve, reject) => {
|
|
2961
|
+
let timer;
|
|
2962
|
+
const onAbort = () => {
|
|
2963
|
+
clearTimeout(timer);
|
|
2964
|
+
reject(signal?.reason ?? /* @__PURE__ */ new Error("Algolia sync was aborted."));
|
|
2965
|
+
};
|
|
2966
|
+
timer = setTimeout(() => {
|
|
2967
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2968
|
+
resolve();
|
|
2969
|
+
}, Math.min(delay, Math.max(1, deadline - Date.now())));
|
|
2970
|
+
if (signal?.aborted) onAbort();
|
|
2971
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
throw new Error("Timed out waiting for Algolia to publish the synced index.");
|
|
2975
|
+
}
|
|
2976
|
+
async function executeAlgoliaBatch(config, requests, message, signal) {
|
|
2977
|
+
for (let offset = 0; offset < requests.length; offset += ALGOLIA_BATCH_OPERATIONS) {
|
|
2978
|
+
const batch = requests.slice(offset, offset + ALGOLIA_BATCH_OPERATIONS);
|
|
2979
|
+
const response = await fetch(`${getAlgoliaAdminBase(config)}/1/indexes/${encodeURIComponent(config.indexName)}/batch`, {
|
|
2980
|
+
method: "POST",
|
|
2981
|
+
headers: getAlgoliaAdminHeaders(config),
|
|
2982
|
+
body: JSON.stringify({ requests: batch }),
|
|
2983
|
+
signal
|
|
2984
|
+
});
|
|
2985
|
+
ensureOk(response, message);
|
|
2986
|
+
const payload = await readResponseJson(response);
|
|
2987
|
+
if (typeof payload?.taskID !== "string" && typeof payload?.taskID !== "number") throw new Error("Algolia sync response did not include a task ID.");
|
|
2988
|
+
await waitForAlgoliaTask(config, payload.taskID, signal);
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2012
2991
|
function quoteAlgoliaFilterValue(value) {
|
|
2013
2992
|
return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, "\\\"")}"`;
|
|
2014
2993
|
}
|
|
@@ -2017,26 +2996,29 @@ function createAlgoliaSearchAdapter(config) {
|
|
|
2017
2996
|
name: "algolia",
|
|
2018
2997
|
async index(context) {
|
|
2019
2998
|
if (!config.adminApiKey) return;
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
}), "Failed to sync documents to Algolia");
|
|
2999
|
+
const corpusId = resolveHostedHumanCorpusId(config.syncNamespace, context);
|
|
3000
|
+
await executeAlgoliaBatch(config, (await enrichDocsSearchDocumentsWithSources({
|
|
3001
|
+
documents: context.documents,
|
|
3002
|
+
pages: context.pages,
|
|
3003
|
+
audience: resolveDocsSearchAudience(context.audience),
|
|
3004
|
+
chunking: context.chunking ?? { strategy: "section" },
|
|
3005
|
+
locale: context.locale,
|
|
3006
|
+
baseUrl: context.baseUrl,
|
|
3007
|
+
indexGeneration: context.indexGeneration
|
|
3008
|
+
})).map((document) => ({
|
|
3009
|
+
action: "addObject",
|
|
3010
|
+
body: buildAlgoliaRecord(document, corpusId)
|
|
3011
|
+
})), "Failed to sync documents to Algolia", context.signal);
|
|
2033
3012
|
},
|
|
2034
3013
|
async search(query, context) {
|
|
2035
|
-
const
|
|
3014
|
+
const corpusId = resolveHostedHumanCorpusId(config.syncNamespace, context);
|
|
3015
|
+
const scopedDocumentIds = resolveProviderScopeDocumentIds(query, context, corpusId);
|
|
2036
3016
|
if (scopedDocumentIds?.length === 0) return [];
|
|
2037
|
-
const
|
|
3017
|
+
const scopedIdsFilter = scopedDocumentIds?.map((id) => `objectID:${quoteAlgoliaFilterValue(id)}`).join(" OR ");
|
|
3018
|
+
const filterClauses = [corpusId ? `_tags:${quoteAlgoliaFilterValue(corpusId)}` : void 0, scopedIdsFilter ? corpusId ? `(${scopedIdsFilter})` : scopedIdsFilter : void 0].filter((value) => Boolean(value));
|
|
3019
|
+
const filters = filterClauses.length > 0 ? filterClauses.join(" AND ") : void 0;
|
|
2038
3020
|
if (filters && filters.length > MAX_PROVIDER_SCOPE_FILTER_CHARS) return [];
|
|
2039
|
-
const response = await fetch(`${
|
|
3021
|
+
const response = await fetch(`${getAlgoliaSearchBase(config)}/1/indexes/${encodeURIComponent(config.indexName)}/query`, {
|
|
2040
3022
|
method: "POST",
|
|
2041
3023
|
headers: {
|
|
2042
3024
|
"Content-Type": "application/json",
|
|
@@ -2046,17 +3028,26 @@ function createAlgoliaSearchAdapter(config) {
|
|
|
2046
3028
|
body: JSON.stringify({
|
|
2047
3029
|
query: query.query,
|
|
2048
3030
|
hitsPerPage: query.limit ?? config.maxResults ?? DEFAULT_SEARCH_LIMIT,
|
|
3031
|
+
restrictSearchableAttributes: [
|
|
3032
|
+
"title",
|
|
3033
|
+
"section",
|
|
3034
|
+
"content",
|
|
3035
|
+
"description"
|
|
3036
|
+
],
|
|
2049
3037
|
attributesToSnippet: ["content:20"],
|
|
2050
3038
|
...filters ? { filters } : {}
|
|
2051
3039
|
}),
|
|
2052
3040
|
signal: context.signal
|
|
2053
3041
|
});
|
|
2054
3042
|
ensureOk(response, "Algolia search failed");
|
|
2055
|
-
return ((await readResponseJson(response)).hits ?? []).
|
|
3043
|
+
return ((await readResponseJson(response)).hits ?? []).flatMap((hit) => {
|
|
3044
|
+
if (!hostedRecordMatchesCorpus(hit, corpusId)) return [];
|
|
2056
3045
|
const title = typeof hit.title === "string" ? hit.title : "Untitled result";
|
|
2057
3046
|
const section = typeof hit.section === "string" ? hit.section : void 0;
|
|
2058
|
-
|
|
2059
|
-
|
|
3047
|
+
const source = readHostedRetrievalSource(hit);
|
|
3048
|
+
if (hasHostedRetrievalSource(hit) && !source) return [];
|
|
3049
|
+
return [{
|
|
3050
|
+
id: readHostedSourceDocumentId(hit) ?? hit.objectID ?? String(hit.url ?? title),
|
|
2060
3051
|
url: typeof hit.url === "string" ? hit.url : "/docs",
|
|
2061
3052
|
content: cleanSearchResultText(section ? `${title} — ${section}` : title) ?? title,
|
|
2062
3053
|
description: cleanSearchResultText(hit._snippetResult?.content?.value ?? hit._snippetResult?.description?.value ?? (typeof hit.description === "string" ? hit.description : void 0)),
|
|
@@ -2066,8 +3057,9 @@ function createAlgoliaSearchAdapter(config) {
|
|
|
2066
3057
|
"text"
|
|
2067
3058
|
].includes(hit.type) ? hit.type : section ? "heading" : "page",
|
|
2068
3059
|
score: hit._rankingInfo?.nbTypos != null ? 100 - hit._rankingInfo.nbTypos : void 0,
|
|
2069
|
-
section
|
|
2070
|
-
|
|
3060
|
+
section,
|
|
3061
|
+
...source ? { source } : {}
|
|
3062
|
+
}];
|
|
2071
3063
|
});
|
|
2072
3064
|
}
|
|
2073
3065
|
};
|
|
@@ -2088,17 +3080,71 @@ function shouldSyncOnSearch(search) {
|
|
|
2088
3080
|
}
|
|
2089
3081
|
function getSyncKey(search, context) {
|
|
2090
3082
|
const raw = search.raw;
|
|
2091
|
-
if (search.provider === "algolia" && raw?.provider === "algolia")
|
|
2092
|
-
|
|
3083
|
+
if (search.provider === "algolia" && raw?.provider === "algolia") {
|
|
3084
|
+
const corpusId = resolveHostedHumanCorpusId(raw.syncNamespace, context) ?? "__unowned__";
|
|
3085
|
+
return `algolia:${raw.appId}:${raw.indexName}:${corpusId}:${context.locale ?? "__default__"}`;
|
|
3086
|
+
}
|
|
3087
|
+
if (search.provider === "typesense" && raw?.provider === "typesense") {
|
|
3088
|
+
const corpusId = resolveHostedHumanCorpusId(raw.syncNamespace, context) ?? "__unowned__";
|
|
3089
|
+
return `typesense:${raw.baseUrl}:${raw.collection}:${corpusId}:${context.locale ?? "__default__"}`;
|
|
3090
|
+
}
|
|
2093
3091
|
if (search.provider === "mcp" && raw?.provider === "mcp") return `mcp:${raw.endpoint}:${context.locale ?? "__default__"}`;
|
|
2094
3092
|
return `${search.provider}:${context.locale ?? "__default__"}`;
|
|
2095
3093
|
}
|
|
3094
|
+
function getSearchSyncFingerprint(search, context, indexGeneration) {
|
|
3095
|
+
const raw = search.raw;
|
|
3096
|
+
const providerConfig = search.provider === "typesense" && raw?.provider === "typesense" ? {
|
|
3097
|
+
syncNamespace: raw.syncNamespace?.trim() || void 0,
|
|
3098
|
+
mode: raw.mode ?? "keyword",
|
|
3099
|
+
embeddings: raw.embeddings ? {
|
|
3100
|
+
provider: raw.embeddings.provider,
|
|
3101
|
+
model: raw.embeddings.model,
|
|
3102
|
+
baseUrl: raw.embeddings.baseUrl
|
|
3103
|
+
} : void 0
|
|
3104
|
+
} : search.provider === "algolia" && raw?.provider === "algolia" ? { syncNamespace: raw.syncNamespace?.trim() || void 0 } : void 0;
|
|
3105
|
+
return hashDocsRetrievalValue(JSON.stringify({
|
|
3106
|
+
format: "docs-search-sync.v1",
|
|
3107
|
+
indexGeneration,
|
|
3108
|
+
canonicalBaseUrl: context.baseUrl,
|
|
3109
|
+
provider: search.provider,
|
|
3110
|
+
providerConfig
|
|
3111
|
+
}));
|
|
3112
|
+
}
|
|
2096
3113
|
async function maybeSyncSearchIndex(adapter, search, context) {
|
|
2097
3114
|
if (!shouldSyncOnSearch(search) || typeof adapter.index !== "function") return;
|
|
2098
3115
|
const syncKey = getSyncKey(search, context);
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
3116
|
+
const indexGeneration = context.indexGeneration ?? await buildDocsSearchIndexGeneration(context.pages, {
|
|
3117
|
+
audience: resolveDocsSearchAudience(context.audience),
|
|
3118
|
+
chunking: context.chunking ?? search.chunking,
|
|
3119
|
+
locale: context.locale,
|
|
3120
|
+
baseUrl: context.baseUrl
|
|
3121
|
+
});
|
|
3122
|
+
context.indexGeneration = indexGeneration;
|
|
3123
|
+
const syncFingerprint = getSearchSyncFingerprint(search, context, indexGeneration);
|
|
3124
|
+
while (true) {
|
|
3125
|
+
const inFlight = syncingIndexes.get(syncKey);
|
|
3126
|
+
if (inFlight) {
|
|
3127
|
+
try {
|
|
3128
|
+
await inFlight.promise;
|
|
3129
|
+
} catch (error) {
|
|
3130
|
+
if (inFlight.fingerprint === syncFingerprint) throw error;
|
|
3131
|
+
}
|
|
3132
|
+
continue;
|
|
3133
|
+
}
|
|
3134
|
+
if (syncedIndexes.get(syncKey) === syncFingerprint) return;
|
|
3135
|
+
const sync = adapter.index(context).then(() => {
|
|
3136
|
+
syncedIndexes.set(syncKey, syncFingerprint);
|
|
3137
|
+
});
|
|
3138
|
+
syncingIndexes.set(syncKey, {
|
|
3139
|
+
fingerprint: syncFingerprint,
|
|
3140
|
+
promise: sync
|
|
3141
|
+
});
|
|
3142
|
+
try {
|
|
3143
|
+
await sync;
|
|
3144
|
+
} finally {
|
|
3145
|
+
if (syncingIndexes.get(syncKey)?.promise === sync) syncingIndexes.delete(syncKey);
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
2102
3148
|
}
|
|
2103
3149
|
async function performDocsSearch(options) {
|
|
2104
3150
|
const search = normalizeDocsSearchConfig(options.search);
|
|
@@ -2106,7 +3152,9 @@ async function performDocsSearch(options) {
|
|
|
2106
3152
|
const audience = resolveDocsSearchAudience(options.audience);
|
|
2107
3153
|
const filters = normalizeDocsSearchFilters(options.filters);
|
|
2108
3154
|
const hasFilters = hasDocsSearchFilters(filters);
|
|
2109
|
-
const
|
|
3155
|
+
const corpusPages = options.pages.map((page) => localizeDocsSearchPage(page, options.locale));
|
|
3156
|
+
const indexBaseUrl = options.syncBaseUrl === null ? void 0 : options.syncBaseUrl ?? options.baseUrl;
|
|
3157
|
+
const scopedPages = hasFilters ? corpusPages.filter((page) => docsSearchPageMatchesFilters(resolveDocsSearchPageScope(page), filters)) : corpusPages;
|
|
2110
3158
|
let resolvedDocuments;
|
|
2111
3159
|
const getDocuments = () => {
|
|
2112
3160
|
if (!resolvedDocuments) resolvedDocuments = buildDocsSearchDocuments(scopedPages, search.chunking, audience);
|
|
@@ -2124,6 +3172,10 @@ async function performDocsSearch(options) {
|
|
|
2124
3172
|
locale: options.locale,
|
|
2125
3173
|
pathname: options.pathname,
|
|
2126
3174
|
siteTitle: options.siteTitle,
|
|
3175
|
+
baseUrl: options.baseUrl,
|
|
3176
|
+
indexBaseUrl,
|
|
3177
|
+
chunking: search.chunking,
|
|
3178
|
+
deferSourceProvenance: true,
|
|
2127
3179
|
signal: options.signal
|
|
2128
3180
|
};
|
|
2129
3181
|
const query = {
|
|
@@ -2134,17 +3186,47 @@ async function performDocsSearch(options) {
|
|
|
2134
3186
|
audience,
|
|
2135
3187
|
...hasFilters ? { filters } : {}
|
|
2136
3188
|
};
|
|
3189
|
+
let currentIndexGeneration;
|
|
3190
|
+
const getCurrentIndexGeneration = () => {
|
|
3191
|
+
currentIndexGeneration ??= options.indexGeneration ? Promise.resolve(options.indexGeneration) : buildDocsSearchIndexGeneration(options.generationPages ?? options.pages, {
|
|
3192
|
+
audience,
|
|
3193
|
+
chunking: search.chunking,
|
|
3194
|
+
locale: options.locale,
|
|
3195
|
+
baseUrl: options.baseUrl
|
|
3196
|
+
});
|
|
3197
|
+
return currentIndexGeneration;
|
|
3198
|
+
};
|
|
3199
|
+
const requireCurrentIndexGeneration = search.provider === "algolia" || search.provider === "typesense";
|
|
3200
|
+
const finalizeResults = async (results) => enrichDocsSearchResultsWithSources({
|
|
3201
|
+
results,
|
|
3202
|
+
pages: scopedPages,
|
|
3203
|
+
generationPages: options.generationPages ?? options.pages,
|
|
3204
|
+
audience,
|
|
3205
|
+
chunking: search.chunking,
|
|
3206
|
+
locale: options.locale,
|
|
3207
|
+
baseUrl: options.baseUrl,
|
|
3208
|
+
indexGeneration: options.indexGeneration,
|
|
3209
|
+
strictExternalOrigins: options.strictExternalOrigins,
|
|
3210
|
+
filters,
|
|
3211
|
+
requireCurrentIndexGeneration
|
|
3212
|
+
});
|
|
2137
3213
|
try {
|
|
2138
3214
|
const adapter = await resolveSearchAdapter(search, context);
|
|
2139
|
-
if (shouldSyncOnSearch(search) && typeof adapter.index === "function")
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
3215
|
+
if (shouldSyncOnSearch(search) && typeof adapter.index === "function") {
|
|
3216
|
+
const syncPages = audience === "agent" || hasFilters ? corpusPages : scopedPages;
|
|
3217
|
+
await maybeSyncSearchIndex(adapter, search, {
|
|
3218
|
+
pages: syncPages,
|
|
3219
|
+
documents: buildDocsSearchDocuments(syncPages, search.chunking, "human"),
|
|
3220
|
+
audience: "human",
|
|
3221
|
+
locale: options.locale,
|
|
3222
|
+
pathname: options.pathname,
|
|
3223
|
+
siteTitle: options.siteTitle,
|
|
3224
|
+
baseUrl: indexBaseUrl,
|
|
3225
|
+
indexBaseUrl,
|
|
3226
|
+
chunking: search.chunking,
|
|
3227
|
+
signal: options.signal
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
2148
3230
|
const adapterSearch = adapter.search(query, context);
|
|
2149
3231
|
let documents;
|
|
2150
3232
|
try {
|
|
@@ -2154,34 +3236,46 @@ async function performDocsSearch(options) {
|
|
|
2154
3236
|
throw error;
|
|
2155
3237
|
}
|
|
2156
3238
|
const results = await adapterSearch;
|
|
2157
|
-
if (search.provider === "simple") return results;
|
|
3239
|
+
if (search.provider === "simple") return finalizeResults(results);
|
|
2158
3240
|
const localAudienceProjectionResults = buildAudienceProjectionSearchResults(documents, options.query);
|
|
2159
|
-
const localPagePaths = new Set(scopedPages.map((page) =>
|
|
2160
|
-
const allLocalPagePaths = hasFilters && search.provider === "mcp" ? new Set(options.pages.map((page) =>
|
|
3241
|
+
const localPagePaths = new Set(scopedPages.map((page) => normalizeUrlRouteKey(page.url)));
|
|
3242
|
+
const allLocalPagePaths = hasFilters && search.provider === "mcp" ? new Set(options.pages.map((page) => normalizeUrlRouteKey(page.url))) : localPagePaths;
|
|
2161
3243
|
const preserveUnmatched = !hasFilters && audience === "human" || search.provider === "mcp" ? (result) => shouldPreserveUnmatchedExternalResult({
|
|
2162
3244
|
result,
|
|
2163
3245
|
localPagePaths: allLocalPagePaths,
|
|
2164
3246
|
baseUrl: options.baseUrl
|
|
2165
3247
|
}) : void 0;
|
|
2166
|
-
const
|
|
3248
|
+
const isKnownLocalProviderResult = (result) => isLocalProviderResult(result, options.baseUrl) && localPagePaths.has(normalizeUrlRouteKey(result.url));
|
|
3249
|
+
const expectedProviderGeneration = requireCurrentIndexGeneration ? audience === "human" ? await getCurrentIndexGeneration() : await buildDocsSearchIndexGeneration(options.generationPages ?? options.pages, {
|
|
3250
|
+
audience: "human",
|
|
3251
|
+
chunking: search.chunking,
|
|
3252
|
+
locale: options.locale,
|
|
3253
|
+
baseUrl: indexBaseUrl
|
|
3254
|
+
}) : void 0;
|
|
3255
|
+
const providerAudience = requireCurrentIndexGeneration ? "human" : audience;
|
|
3256
|
+
const safeAdapterResults = sanitizeExternalAudienceSearchResults(results.filter((result) => {
|
|
3257
|
+
if (result.source === void 0) return !requireCurrentIndexGeneration || isKnownLocalProviderResult(result);
|
|
3258
|
+
const source = parseDocsRetrievalSource(result.source, { allowRootRelativeCanonical: requireCurrentIndexGeneration || isKnownLocalProviderResult(result) });
|
|
3259
|
+
const knownLocal = isKnownLocalProviderResult(result);
|
|
3260
|
+
return Boolean(source && docsRetrievalSourceMatchesRequest(source, providerAudience, knownLocal ? void 0 : filters, options.locale) && (!expectedProviderGeneration || source.indexGeneration === expectedProviderGeneration));
|
|
3261
|
+
}).filter((result) => requireCurrentIndexGeneration && isKnownLocalProviderResult(result) || !hasOppositeAudienceEvidence({
|
|
2167
3262
|
result,
|
|
2168
3263
|
pages: scopedPages,
|
|
2169
3264
|
query: options.query,
|
|
2170
3265
|
audience,
|
|
2171
|
-
baseUrl: options.baseUrl
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
if (options.supplementExternalResults === false) return safeAdapterResults.slice(0, query.limit ?? search.maxResults ?? DEFAULT_SEARCH_LIMIT);
|
|
3266
|
+
baseUrl: options.baseUrl
|
|
3267
|
+
})), localAudienceProjectionResults, options.baseUrl, preserveUnmatched, requireCurrentIndexGeneration && audience === "agent");
|
|
3268
|
+
if (options.supplementExternalResults === false) return finalizeResults(safeAdapterResults.slice(0, query.limit ?? search.maxResults ?? DEFAULT_SEARCH_LIMIT));
|
|
2175
3269
|
const simpleAudienceResults = audience === "agent" || results.length === 0 || safeAdapterResults.length < results.length ? await createSimpleSearchAdapter().search(query, context) : [];
|
|
2176
3270
|
const combinedResults = mergeSearchResults([
|
|
2177
3271
|
buildExactPageSearchResults(options.query, scopedPages, audience),
|
|
2178
3272
|
safeAdapterResults,
|
|
2179
3273
|
simpleAudienceResults
|
|
2180
3274
|
], audience === "agent" ? (result) => getAskAIResultKey(result, options.baseUrl, options.strictExternalOrigins) : void 0);
|
|
2181
|
-
return prioritizeLiteralInsideResults(options.query, combinedResults).slice(0, query.limit ?? search.maxResults ?? DEFAULT_SEARCH_LIMIT);
|
|
3275
|
+
return finalizeResults(prioritizeLiteralInsideResults(options.query, combinedResults).slice(0, query.limit ?? search.maxResults ?? DEFAULT_SEARCH_LIMIT));
|
|
2182
3276
|
} catch (error) {
|
|
2183
3277
|
if (options.failureMode === "throw") throw error;
|
|
2184
|
-
return createSimpleSearchAdapter().search(query, context);
|
|
3278
|
+
return finalizeResults(await createSimpleSearchAdapter().search(query, context));
|
|
2185
3279
|
}
|
|
2186
3280
|
}
|
|
2187
3281
|
function compareSearchMetadataValues(left, right) {
|
|
@@ -2197,11 +3291,11 @@ function boundedSearchWarningPageUrls(pages) {
|
|
|
2197
3291
|
return bounded.length > 0 ? bounded : void 0;
|
|
2198
3292
|
}
|
|
2199
3293
|
function findSearchResultPages(pages, results, baseUrl) {
|
|
2200
|
-
const pagesByPath = new Map(pages.map((page) => [
|
|
3294
|
+
const pagesByPath = new Map(pages.map((page) => [normalizeUrlRouteKey(page.url), page]));
|
|
2201
3295
|
const found = /* @__PURE__ */ new Map();
|
|
2202
3296
|
for (const result of results) {
|
|
2203
|
-
if (!isLocalProviderResult(result, baseUrl
|
|
2204
|
-
const page = pagesByPath.get(
|
|
3297
|
+
if (!isLocalProviderResult(result, baseUrl)) continue;
|
|
3298
|
+
const page = pagesByPath.get(normalizeUrlRouteKey(result.url));
|
|
2205
3299
|
if (page) found.set(page.url, page);
|
|
2206
3300
|
}
|
|
2207
3301
|
return [...found.values()].sort((left, right) => compareSearchMetadataValues(left.url, right.url));
|
|
@@ -2283,16 +3377,25 @@ function buildDocsSearchWarnings(options) {
|
|
|
2283
3377
|
async function performDocsSearchWithMetadata(options) {
|
|
2284
3378
|
const audience = resolveDocsSearchAudience(options.audience);
|
|
2285
3379
|
const filters = normalizeDocsSearchFilters(options.filters);
|
|
2286
|
-
const
|
|
2287
|
-
|
|
3380
|
+
const search = normalizeDocsSearchConfig(options.search);
|
|
3381
|
+
const indexGeneration = options.indexGeneration ?? await buildDocsSearchIndexGeneration(options.generationPages ?? options.pages, {
|
|
2288
3382
|
audience,
|
|
2289
|
-
|
|
3383
|
+
chunking: search.chunking,
|
|
3384
|
+
locale: options.locale,
|
|
3385
|
+
baseUrl: options.baseUrl
|
|
2290
3386
|
});
|
|
3387
|
+
const results = options.query.trim() ? await performDocsSearch({
|
|
3388
|
+
...options,
|
|
3389
|
+
audience,
|
|
3390
|
+
filters,
|
|
3391
|
+
indexGeneration
|
|
3392
|
+
}) : [];
|
|
2291
3393
|
return {
|
|
2292
3394
|
format: "docs-search.v1",
|
|
2293
3395
|
query: options.query,
|
|
2294
3396
|
audience,
|
|
2295
3397
|
filters,
|
|
3398
|
+
indexGeneration,
|
|
2296
3399
|
resultCount: results.length,
|
|
2297
3400
|
results,
|
|
2298
3401
|
warnings: buildDocsSearchWarnings({
|
|
@@ -2319,6 +3422,7 @@ async function buildDocsAskAIContext(options) {
|
|
|
2319
3422
|
pathname: options.pathname,
|
|
2320
3423
|
siteTitle: options.siteTitle,
|
|
2321
3424
|
baseUrl: options.baseUrl,
|
|
3425
|
+
syncBaseUrl: options.syncBaseUrl,
|
|
2322
3426
|
limit: searchLimit,
|
|
2323
3427
|
filters: options.filters,
|
|
2324
3428
|
failureMode: options.searchFailureMode,
|
|
@@ -2330,7 +3434,7 @@ async function buildDocsAskAIContext(options) {
|
|
|
2330
3434
|
const rankedResults = searchResults.map((result, index) => {
|
|
2331
3435
|
const formatted = formatAskAIContextResult({
|
|
2332
3436
|
result,
|
|
2333
|
-
page: findPageForSearchResult(options.pages, result, options.baseUrl
|
|
3437
|
+
page: findPageForSearchResult(options.pages, result, options.baseUrl),
|
|
2334
3438
|
maxChars: maxResultChars,
|
|
2335
3439
|
baseUrl: options.baseUrl
|
|
2336
3440
|
});
|
|
@@ -2383,4 +3487,4 @@ function createCustomSearchAdapter(adapter) {
|
|
|
2383
3487
|
}
|
|
2384
3488
|
|
|
2385
3489
|
//#endregion
|
|
2386
|
-
export {
|
|
3490
|
+
export { GENERATED_AGENT_PROVENANCE_MARKER as A, resolveSidebarFolderIndexBehaviorForPath as B, agentVersionConstraintsOverlap as C, normalizeAgentVersion as D, normalizeAgentScopeValues as E, serializeGeneratedAgentDocument as F, getDocsTelemetryFeatures as G, emitDocsTelemetryEvent as H, stripGeneratedAgentProvenance as I, normalizeDocsTelemetryOrigin as J, inferDocsTelemetryAgentSurface as K, applySidebarFolderIndexBehavior as L, hashGeneratedAgentContent as M, normalizeGeneratedAgentContent as N, digestDocsRetrievalContent as O, parseGeneratedAgentDocument as P, resolvePageSidebarFolderIndexBehavior as R, agentVersionConstraintMatches as S, normalizeAgentLocale as T, emitDocsTelemetryMcpToolEvent as U, emitDocsTelemetryAgentSurfaceEvent as V, emitDocsTelemetryProjectEvent as W, resolveDocsTelemetryConfig as Y, resolveDocsSearchAudience as _, createCustomSearchAdapter as a, resolveSearchRequestConfig as b, createTypesenseSearchAdapter as c, inferDocsAskAIPackageHints as d, normalizeDocsSearchFilters as f, resolveDocsRetrievalLastModified as g, resolveAskAISearchRequestConfig as h, createAlgoliaSearchAdapter as i, GENERATED_AGENT_PROVENANCE_VERSION as j, isDocsRetrievalCanonicalUrl as k, enrichDocsSearchDocumentsWithProvenance as l, performDocsSearchWithMetadata as m, buildDocsRetrievalDigestProjection as n, createMcpSearchAdapter as o, performDocsSearch as p, isLocalDocsTelemetryOrigin as q, buildDocsSearchDocuments as r, createSimpleSearchAdapter as s, buildDocsAskAIContext as t, formatDocsAskAIPackageHints as u, resolveDocsSearchFilters as v, normalizeAgentFramework as w, agentVersionConstraintGroupsOverlap as x, resolveDocsSearchRequest as y, resolveSidebarFolderIndexBehavior as z };
|