@farming-labs/docs 0.2.74 → 0.2.77
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-D0LEY2Vy.mjs → agent-D4hNGatV.mjs} +2 -2
- package/dist/agent-DRKj42ht.d.mts +938 -0
- package/dist/{agent-BJ0KXN-A.mjs → agent-SRIJ-oF8.mjs} +28 -6
- package/dist/{agent-evals-LIScgQVm.mjs → agent-evals-DmFoKy_y.mjs} +1 -1
- package/dist/{agent-export-B92pNe6g.mjs → agent-export-CM-p9_YD.mjs} +7 -7
- package/dist/agent-skills-bundle.d.mts +1 -1
- package/dist/{agent-skills-server-noFn1l_p.mjs → agent-skills-server-B2EXn2QD.mjs} +1 -1
- package/dist/{agent-skills-server-BqvIGQb1.d.mts → agent-skills-server-CJ4D0G9P.d.mts} +2 -2
- package/dist/agent-skills-vite.d.mts +3 -3
- package/dist/agent-skills-vite.mjs +2 -2
- package/dist/{agents-C228QGbp.mjs → agents-BeLDNCnE.mjs} +7 -16
- package/dist/cli/index.mjs +15 -15
- package/dist/client/react.d.mts +1 -1
- package/dist/{cloud-ask-ai-6X9pVjIY.d.mts → cloud-ask-ai-BCgHPxew.d.mts} +1 -1
- package/dist/{dev-Cc1cFFj-.mjs → dev-kjaxOOla.mjs} +8 -2
- package/dist/docs-cloud-server.d.mts +2 -2
- package/dist/{doctor-CgrUZ3tY.mjs → doctor-DhMVKHjp.mjs} +57 -24
- package/dist/{golden-evaluations-DTEPZeb0.mjs → golden-evaluations-VzE6juQl.mjs} +7 -3
- package/dist/index.d.mts +5 -876
- package/dist/index.mjs +6 -6
- package/dist/{mcp-YD37HlPY.mjs → mcp-CZkQJXtA.mjs} +7 -6
- package/dist/mcp.d.mts +30 -5
- package/dist/mcp.mjs +594 -69
- package/dist/{prompt-references-BIS1eXt4.mjs → prompt-references-B6L3AD98.mjs} +3 -3
- package/dist/{retrieval-digest-9Kp3uSaZ.d.mts → retrieval-digest-rponR4jM.d.mts} +24 -50
- package/dist/{review-Dbzkdl6K.mjs → review-nxoV8jIm.mjs} +5 -5
- package/dist/{robots-RbgmSvPH.mjs → robots-CgH6Stqn.mjs} +2 -2
- package/dist/{robots-DUO0r4UI.mjs → robots-CiQT8mX2.mjs} +3 -3
- package/dist/{search-hcqaEZVN.mjs → search-BTUIkhnT.mjs} +6 -6
- package/dist/{search-DIyIl-tE.mjs → search-Da2AKaFJ.mjs} +624 -122
- package/dist/server.d.mts +9 -6
- package/dist/server.mjs +7 -7
- package/dist/{sitemap-jhkS_jYx.mjs → sitemap-e0_GeCWJ.mjs} +6 -6
- package/dist/{sitemap-server-BfyBM7_V.mjs → sitemap-server-DHaxyPAd.mjs} +12 -3
- package/dist/{standards-discovery-BwL5ffGx.mjs → standards-discovery-BKlEnK_H.mjs} +32 -4
- package/dist/{standards-discovery-CdrOE-05.d.mts → standards-discovery-BbslY4id.d.mts} +34 -7
- package/dist/{types-clg5J1R4.d.mts → types-B-5Kzmkn.d.mts} +73 -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-SRIJ-oF8.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
|
|
@@ -624,6 +624,103 @@ function digestDocsRetrievalContent(value) {
|
|
|
624
624
|
return `sha256:${Array.from(state, (word) => word.toString(16).padStart(8, "0")).join("")}`;
|
|
625
625
|
}
|
|
626
626
|
|
|
627
|
+
//#endregion
|
|
628
|
+
//#region src/pagination.ts
|
|
629
|
+
const DOCS_PAGINATION_CURSOR_VERSION = 1;
|
|
630
|
+
const MAX_DOCS_PAGINATION_CURSOR_CHARS = 4096;
|
|
631
|
+
const MAX_DOCS_PAGINATION_STATE_CHARS = 1024;
|
|
632
|
+
const MAX_DOCS_PAGINATION_OFFSET = 1e7;
|
|
633
|
+
const DOCS_PAGINATION_DIGEST_PATTERN = /^sha256:[a-f\d]{64}$/u;
|
|
634
|
+
var DocsPaginationCursorError = class extends Error {
|
|
635
|
+
code = "invalid_cursor";
|
|
636
|
+
constructor() {
|
|
637
|
+
super("Invalid or stale pagination cursor.");
|
|
638
|
+
this.name = "DocsPaginationCursorError";
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
function paginationDigest(label, value) {
|
|
642
|
+
return digestDocsRetrievalContent(`${label}\0${value}`);
|
|
643
|
+
}
|
|
644
|
+
function cursorChecksum(payload) {
|
|
645
|
+
return paginationDigest("docs-pagination-checksum.v1", JSON.stringify(payload));
|
|
646
|
+
}
|
|
647
|
+
function encodeBase64Url(value) {
|
|
648
|
+
const bytes = new TextEncoder().encode(value);
|
|
649
|
+
let binary = "";
|
|
650
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
651
|
+
return btoa(binary).replace(/\+/gu, "-").replace(/\//gu, "_").replace(/=+$/u, "");
|
|
652
|
+
}
|
|
653
|
+
function decodeBase64Url(value) {
|
|
654
|
+
const padded = `${value.replace(/-/gu, "+").replace(/_/gu, "/")}${"=".repeat((4 - value.length % 4) % 4)}`;
|
|
655
|
+
const binary = atob(padded);
|
|
656
|
+
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
657
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
658
|
+
}
|
|
659
|
+
function isDigest(value) {
|
|
660
|
+
return typeof value === "string" && DOCS_PAGINATION_DIGEST_PATTERN.test(value);
|
|
661
|
+
}
|
|
662
|
+
function assertCursorOptions(options) {
|
|
663
|
+
if (!/^[a-z][a-z\d._/-]{0,127}$/u.test(options.kind) || typeof options.scope !== "string" || typeof options.snapshot !== "string") throw new TypeError("Pagination cursor options must contain a valid kind, scope, and snapshot.");
|
|
664
|
+
}
|
|
665
|
+
function createDocsPaginationCursor(offset, options) {
|
|
666
|
+
assertCursorOptions(options);
|
|
667
|
+
if (!Number.isSafeInteger(offset) || offset <= 0 || offset > MAX_DOCS_PAGINATION_OFFSET) throw new TypeError("Pagination cursor offsets must be positive safe integers.");
|
|
668
|
+
if (options.state !== void 0 && (options.state.length === 0 || options.state.length > MAX_DOCS_PAGINATION_STATE_CHARS)) throw new TypeError("Pagination provider state must be a bounded, non-empty string.");
|
|
669
|
+
const unsigned = [
|
|
670
|
+
DOCS_PAGINATION_CURSOR_VERSION,
|
|
671
|
+
options.kind,
|
|
672
|
+
offset,
|
|
673
|
+
paginationDigest("docs-pagination-scope.v1", options.scope),
|
|
674
|
+
paginationDigest("docs-pagination-snapshot.v1", options.snapshot),
|
|
675
|
+
options.state ?? null
|
|
676
|
+
];
|
|
677
|
+
return encodeBase64Url(JSON.stringify([...unsigned, cursorChecksum(unsigned)]));
|
|
678
|
+
}
|
|
679
|
+
function resolveDocsPaginationCursor(cursor, options) {
|
|
680
|
+
assertCursorOptions(options);
|
|
681
|
+
if (cursor === void 0) return { offset: 0 };
|
|
682
|
+
if (cursor.length === 0 || cursor.length > MAX_DOCS_PAGINATION_CURSOR_CHARS || !/^[A-Za-z\d_-]+$/u.test(cursor)) throw new DocsPaginationCursorError();
|
|
683
|
+
let parsed;
|
|
684
|
+
try {
|
|
685
|
+
parsed = JSON.parse(decodeBase64Url(cursor));
|
|
686
|
+
} catch {
|
|
687
|
+
throw new DocsPaginationCursorError();
|
|
688
|
+
}
|
|
689
|
+
if (!Array.isArray(parsed) || parsed.length !== 7 || encodeBase64Url(JSON.stringify(parsed)) !== cursor) throw new DocsPaginationCursorError();
|
|
690
|
+
const [version, kind, offset, scopeDigest, snapshotDigest, state, checksum] = parsed;
|
|
691
|
+
const unsigned = [
|
|
692
|
+
version,
|
|
693
|
+
kind,
|
|
694
|
+
offset,
|
|
695
|
+
scopeDigest,
|
|
696
|
+
snapshotDigest,
|
|
697
|
+
state
|
|
698
|
+
];
|
|
699
|
+
if (version !== DOCS_PAGINATION_CURSOR_VERSION || kind !== options.kind || !Number.isSafeInteger(offset) || offset <= 0 || offset > MAX_DOCS_PAGINATION_OFFSET || !isDigest(scopeDigest) || !isDigest(snapshotDigest) || !(state === null || typeof state === "string" && state.length > 0 && state.length <= MAX_DOCS_PAGINATION_STATE_CHARS) || !isDigest(checksum) || scopeDigest !== paginationDigest("docs-pagination-scope.v1", options.scope) || snapshotDigest !== paginationDigest("docs-pagination-snapshot.v1", options.snapshot) || checksum !== cursorChecksum(unsigned)) throw new DocsPaginationCursorError();
|
|
700
|
+
return {
|
|
701
|
+
offset,
|
|
702
|
+
...typeof state === "string" ? { state } : {}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function resolveDocsPaginationOffset(cursor, options) {
|
|
706
|
+
return resolveDocsPaginationCursor(cursor, options).offset;
|
|
707
|
+
}
|
|
708
|
+
function paginateDocsItems(items, options) {
|
|
709
|
+
if (!Number.isSafeInteger(options.pageSize) || options.pageSize <= 0) throw new TypeError("Pagination page sizes must be positive safe integers.");
|
|
710
|
+
const offset = resolveDocsPaginationOffset(options.cursor, options);
|
|
711
|
+
if (offset >= items.length && options.cursor !== void 0) throw new DocsPaginationCursorError();
|
|
712
|
+
const pageItems = items.slice(offset, offset + options.pageSize);
|
|
713
|
+
const nextOffset = offset + pageItems.length;
|
|
714
|
+
const hasMore = nextOffset < items.length;
|
|
715
|
+
return {
|
|
716
|
+
items: pageItems,
|
|
717
|
+
resultCount: pageItems.length,
|
|
718
|
+
total: items.length,
|
|
719
|
+
hasMore,
|
|
720
|
+
...hasMore ? { nextCursor: createDocsPaginationCursor(nextOffset, options) } : {}
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
627
724
|
//#endregion
|
|
628
725
|
//#region src/agent-scope.ts
|
|
629
726
|
/** Normalize framework aliases used by page metadata, agent contracts, MCP, and evaluations. */
|
|
@@ -947,6 +1044,7 @@ function agentVersionConstraintsOverlap(left, right) {
|
|
|
947
1044
|
//#endregion
|
|
948
1045
|
//#region src/search.ts
|
|
949
1046
|
const DEFAULT_SEARCH_LIMIT = 10;
|
|
1047
|
+
const MAX_STRUCTURED_SEARCH_LIMIT = 25;
|
|
950
1048
|
const MAX_SEARCH_SNIPPET_CHARS = 160;
|
|
951
1049
|
const DEFAULT_MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
952
1050
|
const MCP_SESSION_CLEANUP_TIMEOUT_MS = 1e3;
|
|
@@ -959,8 +1057,14 @@ const MAX_SEARCH_FILTER_VALUES = 16;
|
|
|
959
1057
|
const MAX_SEARCH_FILTER_VALUE_CHARS = 128;
|
|
960
1058
|
const MAX_SEARCH_FILTER_RAW_CHARS = 4096;
|
|
961
1059
|
const MAX_SEARCH_FILTER_SEGMENTS = 64;
|
|
1060
|
+
const MAX_SEARCH_PROVIDER_CURSOR_STATE_CHARS = 1024;
|
|
962
1061
|
const MAX_PROVIDER_SCOPE_FILTER_IDS = 1e3;
|
|
963
1062
|
const MAX_PROVIDER_SCOPE_FILTER_CHARS = 16e3;
|
|
1063
|
+
const MAX_HOSTED_PROVIDER_CURSOR_RESULTS = 1e3;
|
|
1064
|
+
const MAX_MCP_SEARCH_CURSOR_REPLAY_PAGES = 64;
|
|
1065
|
+
const MAX_MCP_SEARCH_CURSOR_RESULTS = MAX_MCP_SEARCH_CURSOR_REPLAY_PAGES;
|
|
1066
|
+
const DOCS_UNPAGINATED_PROVIDER_QUERY = Symbol("docs-unpaginated-provider-query");
|
|
1067
|
+
const DOCS_PROVIDER_SCOPE_FILTER_OVERFLOW = Symbol("docs-provider-scope-filter-overflow");
|
|
964
1068
|
const ALGOLIA_BATCH_OPERATIONS = 1e3;
|
|
965
1069
|
const MAX_SEARCH_WARNINGS = 16;
|
|
966
1070
|
const MAX_SEARCH_WARNING_VALUES = 16;
|
|
@@ -1057,11 +1161,41 @@ function resolveDocsSearchFilters(searchParams) {
|
|
|
1057
1161
|
tags: searchParams.getAll("tags")
|
|
1058
1162
|
});
|
|
1059
1163
|
}
|
|
1164
|
+
var DocsSearchRequestError = class extends Error {
|
|
1165
|
+
code = "invalid_search_request";
|
|
1166
|
+
constructor(message) {
|
|
1167
|
+
super(message);
|
|
1168
|
+
this.name = "DocsSearchRequestError";
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
function resolveDocsSearchError(error) {
|
|
1172
|
+
if (error instanceof DocsPaginationCursorError) return {
|
|
1173
|
+
code: error.code,
|
|
1174
|
+
message: error.message
|
|
1175
|
+
};
|
|
1176
|
+
if (error instanceof DocsSearchRequestError) return {
|
|
1177
|
+
code: error.code,
|
|
1178
|
+
message: error.message
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1060
1181
|
/** Resolve search filters and the backwards-compatible structured response opt-in. */
|
|
1061
1182
|
function resolveDocsSearchRequest(searchParams) {
|
|
1183
|
+
const structured = searchParams.get("response") === "structured";
|
|
1184
|
+
const cursor = searchParams.get("cursor") ?? void 0;
|
|
1185
|
+
if (cursor !== void 0 && !structured) throw new DocsSearchRequestError("Search cursors require the structured response format (`response=structured`).");
|
|
1186
|
+
if (cursor !== void 0 && (cursor.length === 0 || cursor.length > 4096 || !/^[A-Za-z\d_-]+$/u.test(cursor))) throw new DocsPaginationCursorError();
|
|
1187
|
+
const rawLimit = searchParams.get("limit");
|
|
1188
|
+
let limit;
|
|
1189
|
+
if (rawLimit !== null && structured) {
|
|
1190
|
+
if (!/^\d+$/u.test(rawLimit)) throw new DocsSearchRequestError("Search limit must be an integer between 1 and 25.");
|
|
1191
|
+
limit = Number(rawLimit);
|
|
1192
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_STRUCTURED_SEARCH_LIMIT) throw new DocsSearchRequestError("Search limit must be an integer between 1 and 25.");
|
|
1193
|
+
}
|
|
1062
1194
|
return {
|
|
1063
1195
|
filters: resolveDocsSearchFilters(searchParams),
|
|
1064
|
-
structured
|
|
1196
|
+
structured,
|
|
1197
|
+
...cursor !== void 0 ? { cursor } : {},
|
|
1198
|
+
...limit !== void 0 ? { limit } : {}
|
|
1065
1199
|
};
|
|
1066
1200
|
}
|
|
1067
1201
|
function hasDocsSearchFilters(filters) {
|
|
@@ -1071,7 +1205,7 @@ function resolveProviderScopeDocumentIds(query, context, corpusId) {
|
|
|
1071
1205
|
if (!hasDocsSearchFilters(query.filters ?? {})) return void 0;
|
|
1072
1206
|
const documents = corpusId ? buildDocsSearchDocuments(context.pages, context.chunking ?? { strategy: "section" }, "human") : context.documents;
|
|
1073
1207
|
const ids = Array.from(new Set(documents.map((document) => corpusId ? makeHostedProviderDocumentId(corpusId, document.id) : document.id)));
|
|
1074
|
-
return ids.length <= MAX_PROVIDER_SCOPE_FILTER_IDS ? ids :
|
|
1208
|
+
return ids.length <= MAX_PROVIDER_SCOPE_FILTER_IDS ? ids : DOCS_PROVIDER_SCOPE_FILTER_OVERFLOW;
|
|
1075
1209
|
}
|
|
1076
1210
|
function docsSearchFiltersMatch(expected, actual) {
|
|
1077
1211
|
return SEARCH_FILTER_FIELDS.every((field) => {
|
|
@@ -2257,8 +2391,12 @@ function cleanSearchResultText(value) {
|
|
|
2257
2391
|
if (!value) return void 0;
|
|
2258
2392
|
return normalizeWhitespace(stripHtml(stripMarkdownText(value))) || void 0;
|
|
2259
2393
|
}
|
|
2394
|
+
function getHostedIndexGenerationTag(indexGeneration) {
|
|
2395
|
+
return `docs-generation:${indexGeneration}`;
|
|
2396
|
+
}
|
|
2260
2397
|
function buildAlgoliaRecord(document, corpusId) {
|
|
2261
2398
|
const providerDocumentId = corpusId ? makeHostedProviderDocumentId(corpusId, document.id) : document.id;
|
|
2399
|
+
const generationTag = document.source?.indexGeneration ? getHostedIndexGenerationTag(document.source.indexGeneration) : void 0;
|
|
2262
2400
|
const record = {
|
|
2263
2401
|
objectID: providerDocumentId,
|
|
2264
2402
|
id: providerDocumentId,
|
|
@@ -2273,7 +2411,7 @@ function buildAlgoliaRecord(document, corpusId) {
|
|
|
2273
2411
|
version: document.version,
|
|
2274
2412
|
package: document.package,
|
|
2275
2413
|
tags: document.tags,
|
|
2276
|
-
_tags: corpusId ? [corpusId] : void 0,
|
|
2414
|
+
_tags: corpusId || generationTag ? [corpusId, generationTag].filter((value) => Boolean(value)) : void 0,
|
|
2277
2415
|
...serializeHostedRetrievalSource(document.source, corpusId, document.id)
|
|
2278
2416
|
};
|
|
2279
2417
|
const encoder = new TextEncoder();
|
|
@@ -2313,39 +2451,50 @@ function buildAlgoliaRecord(document, corpusId) {
|
|
|
2313
2451
|
if (sizeOf(record) > ALGOLIA_MAX_RECORD_BYTES) throw new Error(`Algolia record ${document.id} still exceeds the record limit after trimming.`);
|
|
2314
2452
|
return record;
|
|
2315
2453
|
}
|
|
2454
|
+
async function searchSimpleDocsPage(query, context) {
|
|
2455
|
+
const limit = query.limit ?? DEFAULT_SEARCH_LIMIT;
|
|
2456
|
+
const offset = query.offset ?? 0;
|
|
2457
|
+
const matches = context.documents.map((document) => ({
|
|
2458
|
+
document,
|
|
2459
|
+
score: scoreDocument(query.query, document)
|
|
2460
|
+
})).filter((item) => item.score > 0).sort((a, b) => {
|
|
2461
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
2462
|
+
return a.document.url.localeCompare(b.document.url);
|
|
2463
|
+
});
|
|
2464
|
+
const results = matches.slice(offset, offset + limit).map(({ document, score }) => ({
|
|
2465
|
+
id: document.id,
|
|
2466
|
+
url: document.url,
|
|
2467
|
+
content: cleanSearchResultText(document.section ? `${document.title} — ${document.section}` : document.title) ?? (document.section ? `${document.title} — ${document.section}` : document.title),
|
|
2468
|
+
description: cleanSearchResultText(buildSnippet(document, query.query) ?? document.description),
|
|
2469
|
+
type: document.type,
|
|
2470
|
+
score,
|
|
2471
|
+
section: document.section,
|
|
2472
|
+
source: document.source
|
|
2473
|
+
}));
|
|
2474
|
+
return {
|
|
2475
|
+
results: context.deferSourceProvenance ? results : await enrichDocsSearchResultsWithSources({
|
|
2476
|
+
results,
|
|
2477
|
+
pages: context.pages,
|
|
2478
|
+
audience: resolveDocsSearchAudience(context.audience),
|
|
2479
|
+
chunking: context.chunking ?? { strategy: "section" },
|
|
2480
|
+
locale: query.locale ?? context.locale,
|
|
2481
|
+
baseUrl: context.baseUrl,
|
|
2482
|
+
indexGeneration: context.indexGeneration,
|
|
2483
|
+
filters: query.filters
|
|
2484
|
+
}),
|
|
2485
|
+
total: matches.length
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2316
2488
|
function createSimpleSearchAdapter() {
|
|
2317
2489
|
return {
|
|
2318
2490
|
name: "simple",
|
|
2319
2491
|
async search(query, context) {
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
return a.document.url.localeCompare(b.document.url);
|
|
2327
|
-
}).slice(0, limit).map(({ document, score }) => ({
|
|
2328
|
-
id: document.id,
|
|
2329
|
-
url: document.url,
|
|
2330
|
-
content: cleanSearchResultText(document.section ? `${document.title} — ${document.section}` : document.title) ?? (document.section ? `${document.title} — ${document.section}` : document.title),
|
|
2331
|
-
description: cleanSearchResultText(buildSnippet(document, query.query) ?? document.description),
|
|
2332
|
-
type: document.type,
|
|
2333
|
-
score,
|
|
2334
|
-
section: document.section,
|
|
2335
|
-
source: document.source
|
|
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
|
-
});
|
|
2348
|
-
}
|
|
2492
|
+
return (await searchSimpleDocsPage({
|
|
2493
|
+
...query,
|
|
2494
|
+
offset: 0
|
|
2495
|
+
}, context)).results;
|
|
2496
|
+
},
|
|
2497
|
+
searchPage: searchSimpleDocsPage
|
|
2349
2498
|
};
|
|
2350
2499
|
}
|
|
2351
2500
|
function normalizeDocsSearchConfig(search) {
|
|
@@ -2373,6 +2522,46 @@ function normalizeDocsSearchConfig(search) {
|
|
|
2373
2522
|
raw: search
|
|
2374
2523
|
};
|
|
2375
2524
|
}
|
|
2525
|
+
function getDocsSearchPaginationProviderIdentity(search) {
|
|
2526
|
+
const raw = search.raw;
|
|
2527
|
+
if (raw?.provider === "algolia") return {
|
|
2528
|
+
provider: "algolia",
|
|
2529
|
+
appId: raw.appId,
|
|
2530
|
+
indexName: raw.indexName,
|
|
2531
|
+
syncNamespace: raw.syncNamespace?.trim() || void 0
|
|
2532
|
+
};
|
|
2533
|
+
if (raw?.provider === "typesense") return {
|
|
2534
|
+
provider: "typesense",
|
|
2535
|
+
baseUrl: raw.baseUrl,
|
|
2536
|
+
collection: raw.collection,
|
|
2537
|
+
mode: raw.mode ?? "keyword",
|
|
2538
|
+
queryBy: raw.queryBy ?? [
|
|
2539
|
+
"title",
|
|
2540
|
+
"section",
|
|
2541
|
+
"content",
|
|
2542
|
+
"description"
|
|
2543
|
+
],
|
|
2544
|
+
embeddings: raw.embeddings ? {
|
|
2545
|
+
provider: raw.embeddings.provider,
|
|
2546
|
+
model: raw.embeddings.model,
|
|
2547
|
+
baseUrl: raw.embeddings.baseUrl ?? "http://127.0.0.1:11434"
|
|
2548
|
+
} : void 0,
|
|
2549
|
+
syncNamespace: raw.syncNamespace?.trim() || void 0
|
|
2550
|
+
};
|
|
2551
|
+
if (raw?.provider === "mcp") return {
|
|
2552
|
+
provider: "mcp",
|
|
2553
|
+
endpoint: raw.endpoint,
|
|
2554
|
+
toolName: raw.toolName ?? "search_docs",
|
|
2555
|
+
protocolVersion: raw.protocolVersion ?? DEFAULT_MCP_PROTOCOL_VERSION,
|
|
2556
|
+
forwardAudience: raw.forwardAudience === true
|
|
2557
|
+
};
|
|
2558
|
+
if (raw?.provider === "custom") return {
|
|
2559
|
+
provider: "custom",
|
|
2560
|
+
adapterName: typeof raw.adapter === "object" && raw.adapter ? raw.adapter.name : void 0,
|
|
2561
|
+
paginationRevision: raw.paginationRevision?.trim() || void 0
|
|
2562
|
+
};
|
|
2563
|
+
return { provider: search.provider };
|
|
2564
|
+
}
|
|
2376
2565
|
async function readResponseJson(response) {
|
|
2377
2566
|
const text = await response.text();
|
|
2378
2567
|
return text ? JSON.parse(text) : null;
|
|
@@ -2688,7 +2877,7 @@ async function ensureTypesenseImportSucceeded(response, expectedRecords) {
|
|
|
2688
2877
|
}
|
|
2689
2878
|
}
|
|
2690
2879
|
function createTypesenseSearchAdapter(config) {
|
|
2691
|
-
|
|
2880
|
+
const adapter = {
|
|
2692
2881
|
name: "typesense",
|
|
2693
2882
|
async index(context) {
|
|
2694
2883
|
const adminApiKey = config.adminApiKey ?? config.apiKey;
|
|
@@ -2733,9 +2922,23 @@ function createTypesenseSearchAdapter(config) {
|
|
|
2733
2922
|
}), docsForImport.length);
|
|
2734
2923
|
},
|
|
2735
2924
|
async search(query, context) {
|
|
2925
|
+
return (await adapter.searchPage({
|
|
2926
|
+
...query,
|
|
2927
|
+
offset: 0,
|
|
2928
|
+
[DOCS_UNPAGINATED_PROVIDER_QUERY]: true
|
|
2929
|
+
}, context)).results;
|
|
2930
|
+
},
|
|
2931
|
+
async searchPage(query, context) {
|
|
2932
|
+
const exactPagination = !query[DOCS_UNPAGINATED_PROVIDER_QUERY];
|
|
2736
2933
|
const corpusId = resolveHostedHumanCorpusId(config.syncNamespace, context);
|
|
2737
2934
|
const scopedDocumentIds = resolveProviderScopeDocumentIds(query, context, corpusId);
|
|
2738
|
-
if (scopedDocumentIds
|
|
2935
|
+
if (scopedDocumentIds === DOCS_PROVIDER_SCOPE_FILTER_OVERFLOW) throw new Error("Typesense scope filters exceed the provider pagination limit.");
|
|
2936
|
+
if (scopedDocumentIds?.length === 0) return {
|
|
2937
|
+
results: [],
|
|
2938
|
+
total: 0
|
|
2939
|
+
};
|
|
2940
|
+
const pageSize = query.limit ?? config.maxResults ?? DEFAULT_SEARCH_LIMIT;
|
|
2941
|
+
if ((query.offset ?? 0) % pageSize !== 0) throw new Error("Typesense search offset does not match its page boundary.");
|
|
2739
2942
|
const params = new URLSearchParams({
|
|
2740
2943
|
q: query.query,
|
|
2741
2944
|
query_by: (config.queryBy ?? [
|
|
@@ -2744,18 +2947,23 @@ function createTypesenseSearchAdapter(config) {
|
|
|
2744
2947
|
"content",
|
|
2745
2948
|
"description"
|
|
2746
2949
|
]).join(","),
|
|
2747
|
-
per_page: String(
|
|
2950
|
+
per_page: String(pageSize),
|
|
2748
2951
|
prioritize_exact_match: "true",
|
|
2749
2952
|
num_typos: "2",
|
|
2750
|
-
highlight_fields: "content,title,section,description"
|
|
2953
|
+
highlight_fields: "content,title,section,description",
|
|
2954
|
+
page: String(Math.floor((query.offset ?? 0) / pageSize) + 1)
|
|
2751
2955
|
});
|
|
2752
2956
|
if (config.mode === "hybrid" && config.embeddings) {
|
|
2753
2957
|
const vector = await createOllamaEmbedding(query.query, config.embeddings, context.signal);
|
|
2754
2958
|
params.set("vector_query", `embedding:([${vector.join(",")}],k:${Math.max((query.limit ?? 10) * 4, 20)})`);
|
|
2755
2959
|
}
|
|
2756
|
-
const filterClauses = [
|
|
2960
|
+
const filterClauses = [
|
|
2961
|
+
corpusId ? `source_corpus_id:=${quoteTypesenseFilterValue(corpusId)}` : void 0,
|
|
2962
|
+
context.indexGeneration ? `source_index_generation:=${quoteTypesenseFilterValue(context.indexGeneration)}` : void 0,
|
|
2963
|
+
scopedDocumentIds ? `id:=[${scopedDocumentIds.map(quoteTypesenseFilterValue).join(",")}]` : void 0
|
|
2964
|
+
].filter((value) => Boolean(value));
|
|
2757
2965
|
const filterBy = filterClauses.length > 0 ? filterClauses.join(" && ") : void 0;
|
|
2758
|
-
if (filterBy && filterBy.length > MAX_PROVIDER_SCOPE_FILTER_CHARS)
|
|
2966
|
+
if (filterBy && filterBy.length > MAX_PROVIDER_SCOPE_FILTER_CHARS) throw new Error("Typesense scope filters exceed the provider pagination limit.");
|
|
2759
2967
|
const response = filterBy ? await fetch(`${getTypesenseSearchBase(config)}/multi_search`, {
|
|
2760
2968
|
method: "POST",
|
|
2761
2969
|
headers: {
|
|
@@ -2774,7 +2982,9 @@ function createTypesenseSearchAdapter(config) {
|
|
|
2774
2982
|
});
|
|
2775
2983
|
ensureOk(response, "Typesense search failed");
|
|
2776
2984
|
const rawPayload = await readResponseJson(response);
|
|
2777
|
-
|
|
2985
|
+
const payload = filterBy ? rawPayload.results?.[0] ?? {} : rawPayload;
|
|
2986
|
+
if (exactPagination && payload.search_cutoff !== void 0 && payload.search_cutoff !== false) throw new Error("Typesense search was cut off before exact pagination completed.");
|
|
2987
|
+
const results = (payload.hits ?? []).flatMap((hit) => {
|
|
2778
2988
|
const document = hit.document ?? {};
|
|
2779
2989
|
if (!hostedRecordMatchesCorpus(document, corpusId)) return [];
|
|
2780
2990
|
const section = typeof document.section === "string" ? document.section : void 0;
|
|
@@ -2797,8 +3007,18 @@ function createTypesenseSearchAdapter(config) {
|
|
|
2797
3007
|
...source ? { source } : {}
|
|
2798
3008
|
}];
|
|
2799
3009
|
});
|
|
3010
|
+
const exactTotal = typeof payload.found === "number" && Number.isSafeInteger(payload.found) && payload.found >= 0 ? payload.found : void 0;
|
|
3011
|
+
if (exactPagination && exactTotal === void 0) throw new Error("Typesense search did not return an exact result total.");
|
|
3012
|
+
const total = exactTotal ?? results.length;
|
|
3013
|
+
if (exactPagination && total > MAX_HOSTED_PROVIDER_CURSOR_RESULTS) throw new Error("Typesense search exceeds the exact cursor pagination window.");
|
|
3014
|
+
if (exactPagination && (query.offset ?? 0) + results.length < total && results.length !== pageSize) throw new Error("Typesense search returned an incomplete non-final page.");
|
|
3015
|
+
return {
|
|
3016
|
+
results,
|
|
3017
|
+
total
|
|
3018
|
+
};
|
|
2800
3019
|
}
|
|
2801
3020
|
};
|
|
3021
|
+
return adapter;
|
|
2802
3022
|
}
|
|
2803
3023
|
function resolveSearchRequestConfig(search, requestUrl) {
|
|
2804
3024
|
if (!search || search === true || typeof search !== "object" || search.provider !== "mcp") return search;
|
|
@@ -2832,44 +3052,60 @@ function resolveAskAISearchRequestConfig(options) {
|
|
|
2832
3052
|
}, options.requestUrl);
|
|
2833
3053
|
}
|
|
2834
3054
|
function createMcpSearchAdapter(config) {
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
3055
|
+
async function runMcpSearch(query, context, paginated) {
|
|
3056
|
+
const endpoint = resolveMcpEndpoint(config.endpoint);
|
|
3057
|
+
const protocolVersion = config.protocolVersion ?? DEFAULT_MCP_PROTOCOL_VERSION;
|
|
3058
|
+
const toolName = config.toolName ?? "search_docs";
|
|
3059
|
+
const forwardAudience = config.forwardAudience === true;
|
|
3060
|
+
const audience = resolveDocsSearchAudience(query.audience);
|
|
3061
|
+
const baseHeaders = config.headers ?? {};
|
|
3062
|
+
if (audience === "human" && !forwardAudience) throw new Error("MCP human-projection search requires forwardAudience: true on an audience-aware tool.");
|
|
3063
|
+
const initializeResponse = await fetch(endpoint, {
|
|
3064
|
+
method: "POST",
|
|
3065
|
+
headers: {
|
|
3066
|
+
...baseHeaders,
|
|
3067
|
+
"Content-Type": "application/json",
|
|
3068
|
+
accept: "application/json, text/event-stream",
|
|
3069
|
+
"mcp-protocol-version": protocolVersion
|
|
3070
|
+
},
|
|
3071
|
+
body: JSON.stringify({
|
|
3072
|
+
jsonrpc: "2.0",
|
|
3073
|
+
id: 1,
|
|
3074
|
+
method: "initialize",
|
|
3075
|
+
params: {
|
|
3076
|
+
protocolVersion,
|
|
3077
|
+
capabilities: {},
|
|
3078
|
+
clientInfo: {
|
|
3079
|
+
name: "@farming-labs/docs-search",
|
|
3080
|
+
version: "0.1.2"
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
}),
|
|
3084
|
+
signal: context.signal
|
|
3085
|
+
});
|
|
3086
|
+
const initializePayload = await readMcpResponsePayload(initializeResponse);
|
|
3087
|
+
ensureOk(initializeResponse, "MCP search initialization failed");
|
|
3088
|
+
ensureJsonRpcOk(initializePayload, "MCP search initialization failed");
|
|
3089
|
+
const sessionId = initializeResponse.headers.get("mcp-session-id") ?? void 0;
|
|
3090
|
+
try {
|
|
3091
|
+
ensureOk(await fetch(endpoint, {
|
|
2846
3092
|
method: "POST",
|
|
2847
3093
|
headers: {
|
|
2848
3094
|
...baseHeaders,
|
|
2849
3095
|
"Content-Type": "application/json",
|
|
2850
3096
|
accept: "application/json, text/event-stream",
|
|
2851
|
-
"mcp-protocol-version": protocolVersion
|
|
3097
|
+
"mcp-protocol-version": protocolVersion,
|
|
3098
|
+
...sessionId ? { "mcp-session-id": sessionId } : {}
|
|
2852
3099
|
},
|
|
2853
3100
|
body: JSON.stringify({
|
|
2854
3101
|
jsonrpc: "2.0",
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
params: {
|
|
2858
|
-
protocolVersion,
|
|
2859
|
-
capabilities: {},
|
|
2860
|
-
clientInfo: {
|
|
2861
|
-
name: "@farming-labs/docs-search",
|
|
2862
|
-
version: "0.1.2"
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
3102
|
+
method: "notifications/initialized",
|
|
3103
|
+
params: {}
|
|
2865
3104
|
}),
|
|
2866
3105
|
signal: context.signal
|
|
2867
|
-
});
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2870
|
-
ensureJsonRpcOk(initializePayload, "MCP search initialization failed");
|
|
2871
|
-
const sessionId = initializeResponse.headers.get("mcp-session-id") ?? void 0;
|
|
2872
|
-
try {
|
|
3106
|
+
}), "MCP initialized notification failed");
|
|
3107
|
+
const pageSize = query.limit ?? config.maxResults ?? DEFAULT_SEARCH_LIMIT;
|
|
3108
|
+
const callRemoteSearchPage = async (providerCursor, id) => {
|
|
2873
3109
|
const searchResponse = await fetch(endpoint, {
|
|
2874
3110
|
method: "POST",
|
|
2875
3111
|
headers: {
|
|
@@ -2881,14 +3117,15 @@ function createMcpSearchAdapter(config) {
|
|
|
2881
3117
|
},
|
|
2882
3118
|
body: JSON.stringify({
|
|
2883
3119
|
jsonrpc: "2.0",
|
|
2884
|
-
id
|
|
3120
|
+
id,
|
|
2885
3121
|
method: "tools/call",
|
|
2886
3122
|
params: {
|
|
2887
3123
|
name: toolName,
|
|
2888
3124
|
arguments: {
|
|
2889
3125
|
query: query.query,
|
|
2890
|
-
limit:
|
|
3126
|
+
limit: pageSize,
|
|
2891
3127
|
locale: query.locale,
|
|
3128
|
+
...providerCursor !== void 0 ? { cursor: providerCursor } : {},
|
|
2892
3129
|
...forwardAudience ? { audience } : {},
|
|
2893
3130
|
...query.filters?.framework ? { framework: query.filters.framework } : {},
|
|
2894
3131
|
...query.filters?.version ? { version: query.filters.version } : {},
|
|
@@ -2903,32 +3140,107 @@ function createMcpSearchAdapter(config) {
|
|
|
2903
3140
|
ensureOk(searchResponse, "MCP search request failed");
|
|
2904
3141
|
ensureJsonRpcOk(payload, "MCP search request failed");
|
|
2905
3142
|
const parsed = readMcpSearchToolPayload(payload);
|
|
2906
|
-
if (!parsed) return
|
|
3143
|
+
if (!parsed) return {
|
|
3144
|
+
parsed,
|
|
3145
|
+
rawResults: [],
|
|
3146
|
+
results: []
|
|
3147
|
+
};
|
|
2907
3148
|
if (hasDocsSearchFilters(query.filters ?? {})) {
|
|
2908
|
-
if (Array.isArray(parsed) || parsed.format !== "docs-search.v1")
|
|
3149
|
+
if (Array.isArray(parsed) || parsed.format !== "docs-search.v1") {
|
|
3150
|
+
if (paginated) throw new Error("MCP search returned unverifiable pagination data.");
|
|
3151
|
+
return {
|
|
3152
|
+
parsed,
|
|
3153
|
+
rawResults: [],
|
|
3154
|
+
results: []
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
2909
3157
|
const echoedFilters = parseVerifiedMcpSearchFilters(parsed.filters);
|
|
2910
|
-
if (!echoedFilters || !docsSearchFiltersMatch(query.filters ?? {}, echoedFilters))
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
try {
|
|
2918
|
-
await fetch(endpoint, {
|
|
2919
|
-
method: "DELETE",
|
|
2920
|
-
headers: {
|
|
2921
|
-
...baseHeaders,
|
|
2922
|
-
"mcp-protocol-version": protocolVersion,
|
|
2923
|
-
"mcp-session-id": sessionId
|
|
2924
|
-
},
|
|
2925
|
-
signal: cleanupController.signal
|
|
2926
|
-
});
|
|
2927
|
-
} catch {} finally {
|
|
2928
|
-
clearTimeout(cleanupTimeout);
|
|
3158
|
+
if (!echoedFilters || !docsSearchFiltersMatch(query.filters ?? {}, echoedFilters)) {
|
|
3159
|
+
if (paginated) throw new Error("MCP search returned mismatched scope filters.");
|
|
3160
|
+
return {
|
|
3161
|
+
parsed,
|
|
3162
|
+
rawResults: [],
|
|
3163
|
+
results: []
|
|
3164
|
+
};
|
|
2929
3165
|
}
|
|
2930
3166
|
}
|
|
3167
|
+
const rawResults = Array.isArray(parsed) ? parsed : Array.isArray(parsed.results) ? parsed.results : Array.isArray(parsed.pages) ? parsed.pages : [];
|
|
3168
|
+
return {
|
|
3169
|
+
parsed,
|
|
3170
|
+
rawResults,
|
|
3171
|
+
results: rawResults.map((result) => mapMcpSearchResult(result, endpoint)).filter((result) => Boolean(result))
|
|
3172
|
+
};
|
|
3173
|
+
};
|
|
3174
|
+
if (!paginated) return (await callRemoteSearchPage(void 0, 2)).results;
|
|
3175
|
+
const targetOffset = query.offset ?? 0;
|
|
3176
|
+
if (targetOffset === 0 && query.cursor !== void 0 || targetOffset > 0 && query.cursor === void 0) throw new Error("MCP search continuation is missing or misplaced its provider cursor.");
|
|
3177
|
+
let consumed = 0;
|
|
3178
|
+
let remoteCursor;
|
|
3179
|
+
let expectedTotal;
|
|
3180
|
+
const replayResultDigests = [];
|
|
3181
|
+
for (let pageIndex = 0; pageIndex < MAX_MCP_SEARCH_CURSOR_REPLAY_PAGES; pageIndex += 1) {
|
|
3182
|
+
const { parsed, rawResults, results } = await callRemoteSearchPage(remoteCursor, pageIndex + 2);
|
|
3183
|
+
if (!parsed) {
|
|
3184
|
+
if (targetOffset === 0) return {
|
|
3185
|
+
results: [],
|
|
3186
|
+
total: 0
|
|
3187
|
+
};
|
|
3188
|
+
throw new Error("MCP search continuation ended before the requested page.");
|
|
3189
|
+
}
|
|
3190
|
+
if (Array.isArray(parsed) || parsed.format !== "docs-search.v1" || results.length !== rawResults.length || parsed.resultCount !== void 0 && parsed.resultCount !== results.length || typeof parsed.total !== "number" || !Number.isSafeInteger(parsed.total) || parsed.total < consumed + results.length || typeof parsed.hasMore !== "boolean" || parsed.nextCursor !== void 0 && (typeof parsed.nextCursor !== "string" || parsed.nextCursor.length > 768)) throw new Error("MCP search returned malformed paginated results.");
|
|
3191
|
+
const total = parsed.total;
|
|
3192
|
+
const nextCursor = typeof parsed.nextCursor === "string" ? parsed.nextCursor : void 0;
|
|
3193
|
+
const hasMore = consumed + results.length < total;
|
|
3194
|
+
if (expectedTotal !== void 0 && total !== expectedTotal || parsed.hasMore !== hasMore || nextCursor !== void 0 !== hasMore || hasMore && results.length === 0) throw new Error("MCP search returned inconsistent pagination metadata.");
|
|
3195
|
+
expectedTotal ??= total;
|
|
3196
|
+
if (total > MAX_MCP_SEARCH_CURSOR_RESULTS) throw new Error("MCP search exceeds its bounded exact pagination window.");
|
|
3197
|
+
replayResultDigests.push(...results.map((result) => hashDocsRetrievalValue(JSON.stringify(result))));
|
|
3198
|
+
const nextOffset = consumed + results.length;
|
|
3199
|
+
const nextCheckpoint = hasMore ? hashDocsRetrievalValue(JSON.stringify({
|
|
3200
|
+
format: "docs-mcp-search-replay.v1",
|
|
3201
|
+
total,
|
|
3202
|
+
nextOffset,
|
|
3203
|
+
resultDigests: replayResultDigests
|
|
3204
|
+
})) : void 0;
|
|
3205
|
+
if (nextOffset === targetOffset && nextCheckpoint !== query.cursor) throw new Error("MCP search continuation cursor is stale.");
|
|
3206
|
+
if (consumed === targetOffset) return {
|
|
3207
|
+
results,
|
|
3208
|
+
total,
|
|
3209
|
+
...nextCheckpoint !== void 0 ? { nextCursor: nextCheckpoint } : {}
|
|
3210
|
+
};
|
|
3211
|
+
if (!hasMore || nextCursor === void 0) throw new Error("MCP search continuation ended before the requested page.");
|
|
3212
|
+
consumed = nextOffset;
|
|
3213
|
+
if (consumed > targetOffset) throw new Error("MCP search continuation offset does not match a page boundary.");
|
|
3214
|
+
remoteCursor = nextCursor;
|
|
2931
3215
|
}
|
|
3216
|
+
throw new Error("MCP search exceeds its bounded cursor replay limit.");
|
|
3217
|
+
} finally {
|
|
3218
|
+
if (sessionId) {
|
|
3219
|
+
const cleanupController = new AbortController();
|
|
3220
|
+
const cleanupTimeout = setTimeout(() => cleanupController.abort(), MCP_SESSION_CLEANUP_TIMEOUT_MS);
|
|
3221
|
+
try {
|
|
3222
|
+
await fetch(endpoint, {
|
|
3223
|
+
method: "DELETE",
|
|
3224
|
+
headers: {
|
|
3225
|
+
...baseHeaders,
|
|
3226
|
+
"mcp-protocol-version": protocolVersion,
|
|
3227
|
+
"mcp-session-id": sessionId
|
|
3228
|
+
},
|
|
3229
|
+
signal: cleanupController.signal
|
|
3230
|
+
});
|
|
3231
|
+
} catch {} finally {
|
|
3232
|
+
clearTimeout(cleanupTimeout);
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
return {
|
|
3238
|
+
name: "mcp",
|
|
3239
|
+
async search(query, context) {
|
|
3240
|
+
return await runMcpSearch(query, context, false);
|
|
3241
|
+
},
|
|
3242
|
+
async searchPage(query, context) {
|
|
3243
|
+
return await runMcpSearch(query, context, true);
|
|
2932
3244
|
}
|
|
2933
3245
|
};
|
|
2934
3246
|
}
|
|
@@ -2992,7 +3304,7 @@ function quoteAlgoliaFilterValue(value) {
|
|
|
2992
3304
|
return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, "\\\"")}"`;
|
|
2993
3305
|
}
|
|
2994
3306
|
function createAlgoliaSearchAdapter(config) {
|
|
2995
|
-
|
|
3307
|
+
const adapter = {
|
|
2996
3308
|
name: "algolia",
|
|
2997
3309
|
async index(context) {
|
|
2998
3310
|
if (!config.adminApiKey) return;
|
|
@@ -3011,13 +3323,31 @@ function createAlgoliaSearchAdapter(config) {
|
|
|
3011
3323
|
})), "Failed to sync documents to Algolia", context.signal);
|
|
3012
3324
|
},
|
|
3013
3325
|
async search(query, context) {
|
|
3326
|
+
return (await adapter.searchPage({
|
|
3327
|
+
...query,
|
|
3328
|
+
offset: 0,
|
|
3329
|
+
[DOCS_UNPAGINATED_PROVIDER_QUERY]: true
|
|
3330
|
+
}, context)).results;
|
|
3331
|
+
},
|
|
3332
|
+
async searchPage(query, context) {
|
|
3333
|
+
const exactPagination = !query[DOCS_UNPAGINATED_PROVIDER_QUERY];
|
|
3014
3334
|
const corpusId = resolveHostedHumanCorpusId(config.syncNamespace, context);
|
|
3015
3335
|
const scopedDocumentIds = resolveProviderScopeDocumentIds(query, context, corpusId);
|
|
3016
|
-
if (scopedDocumentIds
|
|
3336
|
+
if (scopedDocumentIds === DOCS_PROVIDER_SCOPE_FILTER_OVERFLOW) throw new Error("Algolia scope filters exceed the provider pagination limit.");
|
|
3337
|
+
if (scopedDocumentIds?.length === 0) return {
|
|
3338
|
+
results: [],
|
|
3339
|
+
total: 0
|
|
3340
|
+
};
|
|
3017
3341
|
const scopedIdsFilter = scopedDocumentIds?.map((id) => `objectID:${quoteAlgoliaFilterValue(id)}`).join(" OR ");
|
|
3018
|
-
const filterClauses = [
|
|
3342
|
+
const filterClauses = [
|
|
3343
|
+
corpusId ? `_tags:${quoteAlgoliaFilterValue(corpusId)}` : void 0,
|
|
3344
|
+
context.indexGeneration ? `_tags:${quoteAlgoliaFilterValue(getHostedIndexGenerationTag(context.indexGeneration))}` : void 0,
|
|
3345
|
+
scopedIdsFilter ? corpusId || context.indexGeneration ? `(${scopedIdsFilter})` : scopedIdsFilter : void 0
|
|
3346
|
+
].filter((value) => Boolean(value));
|
|
3019
3347
|
const filters = filterClauses.length > 0 ? filterClauses.join(" AND ") : void 0;
|
|
3020
|
-
if (filters && filters.length > MAX_PROVIDER_SCOPE_FILTER_CHARS)
|
|
3348
|
+
if (filters && filters.length > MAX_PROVIDER_SCOPE_FILTER_CHARS) throw new Error("Algolia scope filters exceed the provider pagination limit.");
|
|
3349
|
+
const pageSize = query.limit ?? config.maxResults ?? DEFAULT_SEARCH_LIMIT;
|
|
3350
|
+
if ((query.offset ?? 0) % pageSize !== 0) throw new Error("Algolia search offset does not match its page boundary.");
|
|
3021
3351
|
const response = await fetch(`${getAlgoliaSearchBase(config)}/1/indexes/${encodeURIComponent(config.indexName)}/query`, {
|
|
3022
3352
|
method: "POST",
|
|
3023
3353
|
headers: {
|
|
@@ -3027,7 +3357,8 @@ function createAlgoliaSearchAdapter(config) {
|
|
|
3027
3357
|
},
|
|
3028
3358
|
body: JSON.stringify({
|
|
3029
3359
|
query: query.query,
|
|
3030
|
-
hitsPerPage:
|
|
3360
|
+
hitsPerPage: pageSize,
|
|
3361
|
+
page: Math.floor((query.offset ?? 0) / pageSize),
|
|
3031
3362
|
restrictSearchableAttributes: [
|
|
3032
3363
|
"title",
|
|
3033
3364
|
"section",
|
|
@@ -3040,7 +3371,8 @@ function createAlgoliaSearchAdapter(config) {
|
|
|
3040
3371
|
signal: context.signal
|
|
3041
3372
|
});
|
|
3042
3373
|
ensureOk(response, "Algolia search failed");
|
|
3043
|
-
|
|
3374
|
+
const payload = await readResponseJson(response);
|
|
3375
|
+
const results = (payload.hits ?? []).flatMap((hit) => {
|
|
3044
3376
|
if (!hostedRecordMatchesCorpus(hit, corpusId)) return [];
|
|
3045
3377
|
const title = typeof hit.title === "string" ? hit.title : "Untitled result";
|
|
3046
3378
|
const section = typeof hit.section === "string" ? hit.section : void 0;
|
|
@@ -3061,8 +3393,21 @@ function createAlgoliaSearchAdapter(config) {
|
|
|
3061
3393
|
...source ? { source } : {}
|
|
3062
3394
|
}];
|
|
3063
3395
|
});
|
|
3396
|
+
const exactTotal = typeof payload.nbHits === "number" && Number.isSafeInteger(payload.nbHits) && payload.nbHits >= 0 ? payload.nbHits : void 0;
|
|
3397
|
+
const exactPageCount = typeof payload.nbPages === "number" && Number.isSafeInteger(payload.nbPages) && payload.nbPages >= 0 ? payload.nbPages : void 0;
|
|
3398
|
+
if (exactPagination && (exactTotal === void 0 || exactPageCount === void 0)) throw new Error("Algolia search did not return exact reachable pagination metadata.");
|
|
3399
|
+
const total = exactTotal ?? results.length;
|
|
3400
|
+
if (exactPagination && total > MAX_HOSTED_PROVIDER_CURSOR_RESULTS) throw new Error("Algolia search exceeds the exact cursor pagination window.");
|
|
3401
|
+
if (exactPagination && payload.exhaustiveNbHits === false) throw new Error("Algolia search returned an approximate result total.");
|
|
3402
|
+
if (exactPagination && exactPageCount !== void 0 && total > exactPageCount * pageSize) throw new Error("Algolia search total exceeds its reachable pagination window.");
|
|
3403
|
+
if (exactPagination && (query.offset ?? 0) + results.length < total && results.length !== pageSize) throw new Error("Algolia search returned an incomplete non-final page.");
|
|
3404
|
+
return {
|
|
3405
|
+
results,
|
|
3406
|
+
total
|
|
3407
|
+
};
|
|
3064
3408
|
}
|
|
3065
3409
|
};
|
|
3410
|
+
return adapter;
|
|
3066
3411
|
}
|
|
3067
3412
|
async function resolveSearchAdapter(search, context) {
|
|
3068
3413
|
const raw = search.raw;
|
|
@@ -3146,9 +3491,29 @@ async function maybeSyncSearchIndex(adapter, search, context) {
|
|
|
3146
3491
|
}
|
|
3147
3492
|
}
|
|
3148
3493
|
}
|
|
3149
|
-
|
|
3494
|
+
function normalizeDocsSearchAdapterPage(value) {
|
|
3495
|
+
if (Array.isArray(value)) return {
|
|
3496
|
+
results: value,
|
|
3497
|
+
total: value.length
|
|
3498
|
+
};
|
|
3499
|
+
if (!value || !Array.isArray(value.results) || !Number.isSafeInteger(value.total) || value.total < 0 || value.results.length > value.total || value.nextCursor !== void 0 && (typeof value.nextCursor !== "string" || value.nextCursor.length === 0 || JSON.stringify([
|
|
3500
|
+
1,
|
|
3501
|
+
"provider",
|
|
3502
|
+
value.total,
|
|
3503
|
+
value.nextCursor
|
|
3504
|
+
]).length > MAX_SEARCH_PROVIDER_CURSOR_STATE_CHARS)) throw new Error("Search adapter returned invalid pagination metadata.");
|
|
3505
|
+
return value;
|
|
3506
|
+
}
|
|
3507
|
+
async function performDocsSearchInternal(options) {
|
|
3150
3508
|
const search = normalizeDocsSearchConfig(options.search);
|
|
3151
|
-
if (!search.enabled)
|
|
3509
|
+
if (!search.enabled) {
|
|
3510
|
+
if (options.paginationMode === "provider" || options.expectedTotal !== void 0 && options.expectedTotal !== 0) throw new DocsPaginationCursorError();
|
|
3511
|
+
return {
|
|
3512
|
+
results: [],
|
|
3513
|
+
total: 0,
|
|
3514
|
+
paginationMode: "local"
|
|
3515
|
+
};
|
|
3516
|
+
}
|
|
3152
3517
|
const audience = resolveDocsSearchAudience(options.audience);
|
|
3153
3518
|
const filters = normalizeDocsSearchFilters(options.filters);
|
|
3154
3519
|
const hasFilters = hasDocsSearchFilters(filters);
|
|
@@ -3181,6 +3546,8 @@ async function performDocsSearch(options) {
|
|
|
3181
3546
|
const query = {
|
|
3182
3547
|
query: options.query,
|
|
3183
3548
|
limit: options.limit ?? search.maxResults,
|
|
3549
|
+
...options.paginated ? { offset: options.offset ?? 0 } : {},
|
|
3550
|
+
...options.providerCursor !== void 0 ? { cursor: options.providerCursor } : {},
|
|
3184
3551
|
locale: options.locale,
|
|
3185
3552
|
pathname: options.pathname,
|
|
3186
3553
|
audience,
|
|
@@ -3197,6 +3564,16 @@ async function performDocsSearch(options) {
|
|
|
3197
3564
|
return currentIndexGeneration;
|
|
3198
3565
|
};
|
|
3199
3566
|
const requireCurrentIndexGeneration = search.provider === "algolia" || search.provider === "typesense";
|
|
3567
|
+
let hostedIndexGeneration;
|
|
3568
|
+
const getHostedIndexGeneration = () => {
|
|
3569
|
+
hostedIndexGeneration ??= audience === "human" ? getCurrentIndexGeneration() : buildDocsSearchIndexGeneration(options.generationPages ?? options.pages, {
|
|
3570
|
+
audience: "human",
|
|
3571
|
+
chunking: search.chunking,
|
|
3572
|
+
locale: options.locale,
|
|
3573
|
+
baseUrl: indexBaseUrl
|
|
3574
|
+
});
|
|
3575
|
+
return hostedIndexGeneration;
|
|
3576
|
+
};
|
|
3200
3577
|
const finalizeResults = async (results) => enrichDocsSearchResultsWithSources({
|
|
3201
3578
|
results,
|
|
3202
3579
|
pages: scopedPages,
|
|
@@ -3210,6 +3587,20 @@ async function performDocsSearch(options) {
|
|
|
3210
3587
|
filters,
|
|
3211
3588
|
requireCurrentIndexGeneration
|
|
3212
3589
|
});
|
|
3590
|
+
const createLocalPage = async () => {
|
|
3591
|
+
const fallback = options.paginated ? await createSimpleSearchAdapter().searchPage(query, context) : normalizeDocsSearchAdapterPage(await createSimpleSearchAdapter().search(query, context));
|
|
3592
|
+
if (options.expectedTotal !== void 0 && fallback.total !== options.expectedTotal) throw new DocsPaginationCursorError();
|
|
3593
|
+
return {
|
|
3594
|
+
results: await finalizeResults(fallback.results),
|
|
3595
|
+
total: options.paginated ? fallback.total : fallback.results.length,
|
|
3596
|
+
paginationMode: "local"
|
|
3597
|
+
};
|
|
3598
|
+
};
|
|
3599
|
+
const fallBackFromProvider = () => {
|
|
3600
|
+
if (options.paginationMode === "provider") throw new DocsPaginationCursorError();
|
|
3601
|
+
return createLocalPage();
|
|
3602
|
+
};
|
|
3603
|
+
if (options.paginated && options.paginationMode === "local") return createLocalPage();
|
|
3213
3604
|
try {
|
|
3214
3605
|
const adapter = await resolveSearchAdapter(search, context);
|
|
3215
3606
|
if (shouldSyncOnSearch(search) && typeof adapter.index === "function") {
|
|
@@ -3223,11 +3614,17 @@ async function performDocsSearch(options) {
|
|
|
3223
3614
|
siteTitle: options.siteTitle,
|
|
3224
3615
|
baseUrl: indexBaseUrl,
|
|
3225
3616
|
indexBaseUrl,
|
|
3617
|
+
...options.paginated && requireCurrentIndexGeneration ? { indexGeneration: await getHostedIndexGeneration() } : {},
|
|
3226
3618
|
chunking: search.chunking,
|
|
3227
3619
|
signal: options.signal
|
|
3228
3620
|
});
|
|
3229
3621
|
}
|
|
3230
|
-
|
|
3622
|
+
if (options.paginated && requireCurrentIndexGeneration) context.indexGeneration = await getHostedIndexGeneration();
|
|
3623
|
+
if (options.paginated && search.provider === "custom" && (!adapter.searchPage || search.raw?.provider !== "custom" || !search.raw.paginationRevision?.trim())) {
|
|
3624
|
+
if (options.paginationMode === "provider") throw new DocsPaginationCursorError();
|
|
3625
|
+
return createLocalPage();
|
|
3626
|
+
}
|
|
3627
|
+
const adapterSearch = options.paginated && adapter.searchPage ? adapter.searchPage(query, context) : adapter.search(query, context);
|
|
3231
3628
|
let documents;
|
|
3232
3629
|
try {
|
|
3233
3630
|
documents = getDocuments();
|
|
@@ -3235,8 +3632,20 @@ async function performDocsSearch(options) {
|
|
|
3235
3632
|
adapterSearch.catch(() => void 0);
|
|
3236
3633
|
throw error;
|
|
3237
3634
|
}
|
|
3238
|
-
const
|
|
3239
|
-
|
|
3635
|
+
const rawAdapterResult = await adapterSearch;
|
|
3636
|
+
const adapterPage = options.paginated && !adapter.searchPage && Array.isArray(rawAdapterResult) ? {
|
|
3637
|
+
results: rawAdapterResult.slice(options.offset ?? 0, (options.offset ?? 0) + (query.limit ?? search.maxResults)),
|
|
3638
|
+
total: rawAdapterResult.length
|
|
3639
|
+
} : normalizeDocsSearchAdapterPage(rawAdapterResult);
|
|
3640
|
+
const results = adapterPage.results;
|
|
3641
|
+
if (search.provider === "simple") {
|
|
3642
|
+
if (options.expectedTotal !== void 0 && adapterPage.total !== options.expectedTotal) throw new DocsPaginationCursorError();
|
|
3643
|
+
return {
|
|
3644
|
+
results: await finalizeResults(results),
|
|
3645
|
+
total: options.paginated ? adapterPage.total : results.length,
|
|
3646
|
+
paginationMode: "local"
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3240
3649
|
const localAudienceProjectionResults = buildAudienceProjectionSearchResults(documents, options.query);
|
|
3241
3650
|
const localPagePaths = new Set(scopedPages.map((page) => normalizeUrlRouteKey(page.url)));
|
|
3242
3651
|
const allLocalPagePaths = hasFilters && search.provider === "mcp" ? new Set(options.pages.map((page) => normalizeUrlRouteKey(page.url))) : localPagePaths;
|
|
@@ -3246,12 +3655,7 @@ async function performDocsSearch(options) {
|
|
|
3246
3655
|
baseUrl: options.baseUrl
|
|
3247
3656
|
}) : void 0;
|
|
3248
3657
|
const isKnownLocalProviderResult = (result) => isLocalProviderResult(result, options.baseUrl) && localPagePaths.has(normalizeUrlRouteKey(result.url));
|
|
3249
|
-
const expectedProviderGeneration = requireCurrentIndexGeneration ?
|
|
3250
|
-
audience: "human",
|
|
3251
|
-
chunking: search.chunking,
|
|
3252
|
-
locale: options.locale,
|
|
3253
|
-
baseUrl: indexBaseUrl
|
|
3254
|
-
}) : void 0;
|
|
3658
|
+
const expectedProviderGeneration = requireCurrentIndexGeneration ? await getHostedIndexGeneration() : void 0;
|
|
3255
3659
|
const providerAudience = requireCurrentIndexGeneration ? "human" : audience;
|
|
3256
3660
|
const safeAdapterResults = sanitizeExternalAudienceSearchResults(results.filter((result) => {
|
|
3257
3661
|
if (result.source === void 0) return !requireCurrentIndexGeneration || isKnownLocalProviderResult(result);
|
|
@@ -3265,19 +3669,45 @@ async function performDocsSearch(options) {
|
|
|
3265
3669
|
audience,
|
|
3266
3670
|
baseUrl: options.baseUrl
|
|
3267
3671
|
})), localAudienceProjectionResults, options.baseUrl, preserveUnmatched, requireCurrentIndexGeneration && audience === "agent");
|
|
3268
|
-
if (options.
|
|
3672
|
+
if (options.paginated) {
|
|
3673
|
+
const endOffset = (query.offset ?? 0) + safeAdapterResults.length;
|
|
3674
|
+
if (safeAdapterResults.length !== results.length || safeAdapterResults.length > (query.limit ?? search.maxResults) || endOffset > adapterPage.total || requireCurrentIndexGeneration && adapterPage.total === 0 && localAudienceProjectionResults.length > 0 || endOffset < adapterPage.total && safeAdapterResults.length === 0 || adapterPage.nextCursor !== void 0 && options.providerCursor !== void 0 && adapterPage.nextCursor === options.providerCursor || adapterPage.nextCursor !== void 0 && endOffset >= adapterPage.total || options.expectedTotal !== void 0 && adapterPage.total !== options.expectedTotal) return fallBackFromProvider();
|
|
3675
|
+
return {
|
|
3676
|
+
results: await finalizeResults(safeAdapterResults),
|
|
3677
|
+
total: adapterPage.total,
|
|
3678
|
+
...adapterPage.nextCursor !== void 0 ? { nextCursor: adapterPage.nextCursor } : {},
|
|
3679
|
+
paginationMode: "provider"
|
|
3680
|
+
};
|
|
3681
|
+
}
|
|
3682
|
+
if (options.supplementExternalResults === false) {
|
|
3683
|
+
const bounded = safeAdapterResults.slice(0, query.limit ?? search.maxResults ?? DEFAULT_SEARCH_LIMIT);
|
|
3684
|
+
return {
|
|
3685
|
+
results: await finalizeResults(bounded),
|
|
3686
|
+
total: bounded.length,
|
|
3687
|
+
paginationMode: "provider"
|
|
3688
|
+
};
|
|
3689
|
+
}
|
|
3269
3690
|
const simpleAudienceResults = audience === "agent" || results.length === 0 || safeAdapterResults.length < results.length ? await createSimpleSearchAdapter().search(query, context) : [];
|
|
3270
3691
|
const combinedResults = mergeSearchResults([
|
|
3271
3692
|
buildExactPageSearchResults(options.query, scopedPages, audience),
|
|
3272
3693
|
safeAdapterResults,
|
|
3273
3694
|
simpleAudienceResults
|
|
3274
3695
|
], audience === "agent" ? (result) => getAskAIResultKey(result, options.baseUrl, options.strictExternalOrigins) : void 0);
|
|
3275
|
-
|
|
3696
|
+
const bounded = prioritizeLiteralInsideResults(options.query, combinedResults).slice(0, query.limit ?? search.maxResults ?? DEFAULT_SEARCH_LIMIT);
|
|
3697
|
+
return {
|
|
3698
|
+
results: await finalizeResults(bounded),
|
|
3699
|
+
total: bounded.length,
|
|
3700
|
+
paginationMode: "provider"
|
|
3701
|
+
};
|
|
3276
3702
|
} catch (error) {
|
|
3703
|
+
if (error instanceof DocsPaginationCursorError) throw error;
|
|
3277
3704
|
if (options.failureMode === "throw") throw error;
|
|
3278
|
-
return
|
|
3705
|
+
return fallBackFromProvider();
|
|
3279
3706
|
}
|
|
3280
3707
|
}
|
|
3708
|
+
async function performDocsSearch(options) {
|
|
3709
|
+
return (await performDocsSearchInternal(options)).results;
|
|
3710
|
+
}
|
|
3281
3711
|
function compareSearchMetadataValues(left, right) {
|
|
3282
3712
|
if (left === right) return 0;
|
|
3283
3713
|
return left < right ? -1 : 1;
|
|
@@ -3340,7 +3770,7 @@ function buildDocsSearchWarnings(options) {
|
|
|
3340
3770
|
count: unknown.length
|
|
3341
3771
|
});
|
|
3342
3772
|
}
|
|
3343
|
-
const resultPages = findSearchResultPages(options.pages, options.results, options.baseUrl);
|
|
3773
|
+
const resultPages = options.completeResultSet ? relevantPageScopes.filter(({ scope }) => docsSearchPageMatchesFilters(scope, options.filters)).map(({ page }) => page).sort((left, right) => compareSearchMetadataValues(left.url, right.url)) : findSearchResultPages(options.pages, options.results, options.baseUrl);
|
|
3344
3774
|
for (const field of SEARCH_AMBIGUITY_FIELDS) {
|
|
3345
3775
|
if ((options.filters[field]?.length ?? 0) > 0) continue;
|
|
3346
3776
|
const values = resultPages.flatMap((page) => {
|
|
@@ -3374,37 +3804,108 @@ function buildDocsSearchWarnings(options) {
|
|
|
3374
3804
|
return SEARCH_FILTER_FIELDS.indexOf(left.field) - SEARCH_FILTER_FIELDS.indexOf(right.field);
|
|
3375
3805
|
}).slice(0, MAX_SEARCH_WARNINGS);
|
|
3376
3806
|
}
|
|
3807
|
+
function parseDocsSearchCursorState(state, offset) {
|
|
3808
|
+
if (offset === 0 && state === void 0) return void 0;
|
|
3809
|
+
if (state === void 0) throw new DocsPaginationCursorError();
|
|
3810
|
+
let parsed;
|
|
3811
|
+
try {
|
|
3812
|
+
parsed = JSON.parse(state);
|
|
3813
|
+
} catch {
|
|
3814
|
+
throw new DocsPaginationCursorError();
|
|
3815
|
+
}
|
|
3816
|
+
if (!Array.isArray(parsed) || parsed.length !== 4 || JSON.stringify(parsed) !== state) throw new DocsPaginationCursorError();
|
|
3817
|
+
const [version, mode, total, providerCursor] = parsed;
|
|
3818
|
+
if (version !== 1 || mode !== "local" && mode !== "provider" || !Number.isSafeInteger(total) || total < offset || !(providerCursor === null || typeof providerCursor === "string" && providerCursor.length <= 768) || mode === "local" && providerCursor !== null) throw new DocsPaginationCursorError();
|
|
3819
|
+
return {
|
|
3820
|
+
mode,
|
|
3821
|
+
total,
|
|
3822
|
+
...typeof providerCursor === "string" ? { providerCursor } : {}
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3825
|
+
function createDocsSearchCursorState(page) {
|
|
3826
|
+
return JSON.stringify([
|
|
3827
|
+
1,
|
|
3828
|
+
page.paginationMode,
|
|
3829
|
+
page.total,
|
|
3830
|
+
page.paginationMode === "provider" ? page.nextCursor ?? null : null
|
|
3831
|
+
]);
|
|
3832
|
+
}
|
|
3377
3833
|
async function performDocsSearchWithMetadata(options) {
|
|
3378
3834
|
const audience = resolveDocsSearchAudience(options.audience);
|
|
3379
3835
|
const filters = normalizeDocsSearchFilters(options.filters);
|
|
3380
3836
|
const search = normalizeDocsSearchConfig(options.search);
|
|
3837
|
+
const requestedLimit = options.limit ?? search.maxResults ?? DEFAULT_SEARCH_LIMIT;
|
|
3838
|
+
const limit = Number.isFinite(requestedLimit) && requestedLimit > 0 ? Math.max(1, Math.floor(requestedLimit)) : DEFAULT_SEARCH_LIMIT;
|
|
3381
3839
|
const indexGeneration = options.indexGeneration ?? await buildDocsSearchIndexGeneration(options.generationPages ?? options.pages, {
|
|
3382
3840
|
audience,
|
|
3383
3841
|
chunking: search.chunking,
|
|
3384
3842
|
locale: options.locale,
|
|
3385
3843
|
baseUrl: options.baseUrl
|
|
3386
3844
|
});
|
|
3387
|
-
const
|
|
3845
|
+
const normalizedFilters = Object.fromEntries(SEARCH_FILTER_FIELDS.flatMap((field) => {
|
|
3846
|
+
const values = filters[field];
|
|
3847
|
+
return values?.length ? [[field, [...values].sort(compareSearchMetadataValues)]] : [];
|
|
3848
|
+
}));
|
|
3849
|
+
const provider = getDocsSearchPaginationProviderIdentity(search);
|
|
3850
|
+
const cursorOptions = {
|
|
3851
|
+
kind: "docs-search",
|
|
3852
|
+
scope: JSON.stringify({
|
|
3853
|
+
format: "docs-search-pagination.v2",
|
|
3854
|
+
query: options.query,
|
|
3855
|
+
audience,
|
|
3856
|
+
filters: normalizedFilters,
|
|
3857
|
+
locale: options.locale ? normalizeAgentLocale(options.locale) : void 0,
|
|
3858
|
+
pathname: options.pathname?.trim() || void 0,
|
|
3859
|
+
provider,
|
|
3860
|
+
limit
|
|
3861
|
+
}),
|
|
3862
|
+
snapshot: indexGeneration
|
|
3863
|
+
};
|
|
3864
|
+
const cursorPosition = resolveDocsPaginationCursor(options.cursor, cursorOptions);
|
|
3865
|
+
const offset = cursorPosition.offset;
|
|
3866
|
+
const cursorState = parseDocsSearchCursorState(cursorPosition.state, offset);
|
|
3867
|
+
const page = options.query.trim() ? await performDocsSearchInternal({
|
|
3388
3868
|
...options,
|
|
3389
3869
|
audience,
|
|
3390
3870
|
filters,
|
|
3391
|
-
indexGeneration
|
|
3392
|
-
|
|
3871
|
+
indexGeneration,
|
|
3872
|
+
limit,
|
|
3873
|
+
offset,
|
|
3874
|
+
paginated: true,
|
|
3875
|
+
providerCursor: cursorState?.providerCursor,
|
|
3876
|
+
paginationMode: cursorState?.mode,
|
|
3877
|
+
expectedTotal: cursorState?.total
|
|
3878
|
+
}) : {
|
|
3879
|
+
results: [],
|
|
3880
|
+
total: 0,
|
|
3881
|
+
paginationMode: "local"
|
|
3882
|
+
};
|
|
3883
|
+
if (options.cursor !== void 0 && offset >= page.total || page.results.length > Math.min(limit, Math.max(0, page.total - offset)) || offset < page.total && page.results.length === 0) throw new DocsPaginationCursorError();
|
|
3884
|
+
const nextOffset = Math.min(page.total, offset + page.results.length);
|
|
3885
|
+
const hasMore = nextOffset < page.total;
|
|
3886
|
+
const nextCursor = hasMore ? createDocsPaginationCursor(nextOffset, {
|
|
3887
|
+
...cursorOptions,
|
|
3888
|
+
state: createDocsSearchCursorState(page)
|
|
3889
|
+
}) : void 0;
|
|
3393
3890
|
return {
|
|
3394
3891
|
format: "docs-search.v1",
|
|
3395
3892
|
query: options.query,
|
|
3396
3893
|
audience,
|
|
3397
3894
|
filters,
|
|
3398
3895
|
indexGeneration,
|
|
3399
|
-
resultCount: results.length,
|
|
3400
|
-
|
|
3896
|
+
resultCount: page.results.length,
|
|
3897
|
+
total: page.total,
|
|
3898
|
+
hasMore,
|
|
3899
|
+
...nextCursor ? { nextCursor } : {},
|
|
3900
|
+
results: page.results,
|
|
3401
3901
|
warnings: buildDocsSearchWarnings({
|
|
3402
3902
|
pages: options.pages,
|
|
3403
|
-
results,
|
|
3903
|
+
results: page.results,
|
|
3404
3904
|
filters,
|
|
3405
3905
|
query: options.query,
|
|
3406
3906
|
audience,
|
|
3407
|
-
baseUrl: options.baseUrl
|
|
3907
|
+
baseUrl: options.baseUrl,
|
|
3908
|
+
completeResultSet: true
|
|
3408
3909
|
})
|
|
3409
3910
|
};
|
|
3410
3911
|
}
|
|
@@ -3479,12 +3980,13 @@ async function buildDocsAskAIContext(options) {
|
|
|
3479
3980
|
packageHints: inferDocsAskAIPackageHints(context)
|
|
3480
3981
|
};
|
|
3481
3982
|
}
|
|
3482
|
-
function createCustomSearchAdapter(adapter) {
|
|
3983
|
+
function createCustomSearchAdapter(adapter, options = {}) {
|
|
3483
3984
|
return {
|
|
3484
3985
|
provider: "custom",
|
|
3485
|
-
adapter
|
|
3986
|
+
adapter,
|
|
3987
|
+
...options.paginationRevision ? { paginationRevision: options.paginationRevision } : {}
|
|
3486
3988
|
};
|
|
3487
3989
|
}
|
|
3488
3990
|
|
|
3489
3991
|
//#endregion
|
|
3490
|
-
export {
|
|
3992
|
+
export { resolveDocsTelemetryConfig as $, DocsPaginationCursorError as A, stripGeneratedAgentProvenance as B, agentVersionConstraintGroupsOverlap as C, normalizeAgentLocale as D, normalizeAgentFramework as E, GENERATED_AGENT_PROVENANCE_VERSION as F, emitDocsTelemetryAgentSurfaceEvent as G, resolvePageSidebarFolderIndexBehavior as H, hashGeneratedAgentContent as I, emitDocsTelemetryProjectEvent as J, emitDocsTelemetryEvent as K, normalizeGeneratedAgentContent as L, digestDocsRetrievalContent as M, isDocsRetrievalCanonicalUrl as N, normalizeAgentScopeValues as O, GENERATED_AGENT_PROVENANCE_MARKER as P, normalizeDocsTelemetryOrigin as Q, parseGeneratedAgentDocument as R, resolveSearchRequestConfig as S, agentVersionConstraintsOverlap as T, resolveSidebarFolderIndexBehavior as U, applySidebarFolderIndexBehavior as V, resolveSidebarFolderIndexBehaviorForPath as W, inferDocsTelemetryAgentSurface as X, getDocsTelemetryFeatures as Y, isLocalDocsTelemetryOrigin as Z, resolveDocsRetrievalLastModified as _, createAlgoliaSearchAdapter as a, resolveDocsSearchFilters as b, createSimpleSearchAdapter as c, formatDocsAskAIPackageHints as d, inferDocsAskAIPackageHints as f, resolveAskAISearchRequestConfig as g, performDocsSearchWithMetadata as h, buildDocsSearchDocuments as i, paginateDocsItems as j, normalizeAgentVersion as k, createTypesenseSearchAdapter as l, performDocsSearch as m, buildDocsAskAIContext as n, createCustomSearchAdapter as o, normalizeDocsSearchFilters as p, emitDocsTelemetryMcpToolEvent as q, buildDocsRetrievalDigestProjection as r, createMcpSearchAdapter as s, DocsSearchRequestError as t, enrichDocsSearchDocumentsWithProvenance as u, resolveDocsSearchAudience as v, agentVersionConstraintMatches as w, resolveDocsSearchRequest as x, resolveDocsSearchError as y, serializeGeneratedAgentDocument as z };
|