@farming-labs/docs 0.2.62 → 0.2.64

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.
Files changed (58) hide show
  1. package/dist/agent-CQTH7NFu.mjs +624 -0
  2. package/dist/agent-DKKptIgy.mjs +4365 -0
  3. package/dist/agent-evals-B7MIxuEX.mjs +2144 -0
  4. package/dist/agent-export-CBgWgPvH.mjs +910 -0
  5. package/dist/agent-scope-C_U--OZ7.mjs +283 -0
  6. package/dist/agent-skills-bundle.d.mts +13 -0
  7. package/dist/agent-skills-bundle.mjs +12 -0
  8. package/dist/agent-skills-server-CPja6Syt.d.mts +14 -0
  9. package/dist/agent-skills-server-DraIb6FV.mjs +415 -0
  10. package/dist/agent-skills-vite.d.mts +31 -0
  11. package/dist/agent-skills-vite.mjs +70 -0
  12. package/dist/agents-XWZBub6f.mjs +221 -0
  13. package/dist/analytics-Bx44lg6d.mjs +177 -0
  14. package/dist/cli/index.d.mts +15 -0
  15. package/dist/cli/index.mjs +452 -0
  16. package/dist/client/react.d.mts +45 -0
  17. package/dist/client/react.mjs +223 -0
  18. package/dist/cloud-analytics-CSyFE6SS.mjs +132 -0
  19. package/dist/cloud-ask-ai-sbpjOR2K.mjs +382 -0
  20. package/dist/cloud-ask-ai-zpwkdwnF.d.mts +23 -0
  21. package/dist/cloud-pdNC-tyj.mjs +1615 -0
  22. package/dist/code-blocks-DnNVNK2M.mjs +871 -0
  23. package/dist/codeblocks-CFuurVIH.mjs +250 -0
  24. package/dist/config-Wcdj-D0a.mjs +369 -0
  25. package/dist/dev-Cmy6DtdF.mjs +1333 -0
  26. package/dist/docs-cloud-server.d.mts +70 -0
  27. package/dist/docs-cloud-server.mjs +310 -0
  28. package/dist/doctor-DtGYZ41i.mjs +2036 -0
  29. package/dist/downgrade-w7e6Se0L.mjs +184 -0
  30. package/dist/errors-DbOhkE1h.mjs +20 -0
  31. package/dist/golden-evaluations-Dj-9Eo3v.mjs +1785 -0
  32. package/dist/i18n-CCaFUnAN.mjs +40 -0
  33. package/dist/index.d.mts +1150 -0
  34. package/dist/index.mjs +10 -0
  35. package/dist/init-CQY0Woe3.mjs +1264 -0
  36. package/dist/mcp-B9dcsivk.mjs +156 -0
  37. package/dist/mcp.d.mts +298 -0
  38. package/dist/mcp.mjs +4430 -0
  39. package/dist/metadata-DWExHQnx.mjs +237 -0
  40. package/dist/package-version-n5AFur8a.mjs +128 -0
  41. package/dist/reading-time-CYZ5VvKU.mjs +742 -0
  42. package/dist/review-CLoHTywU.mjs +673 -0
  43. package/dist/robots-BIpC4j4P.mjs +201 -0
  44. package/dist/robots-CUTahhoY.mjs +179 -0
  45. package/dist/search-B6V6qtiI.mjs +1826 -0
  46. package/dist/search-CaSyi6H6.d.mts +279 -0
  47. package/dist/search-DSjCeOk7.mjs +104 -0
  48. package/dist/server.d.mts +343 -0
  49. package/dist/server.mjs +14 -0
  50. package/dist/sitemap-Cykpe3Tz.mjs +249 -0
  51. package/dist/sitemap-server-C_6Wes83.mjs +1137 -0
  52. package/dist/standards-discovery-C4HUqMd2.d.mts +227 -0
  53. package/dist/standards-discovery-jkykaXq1.mjs +519 -0
  54. package/dist/templates-Bq_P7ctv.mjs +2465 -0
  55. package/dist/types-lMBIdZg0.d.mts +3315 -0
  56. package/dist/upgrade-oz-GChgt.mjs +56 -0
  57. package/dist/utils-DpiIioYb.mjs +225 -0
  58. package/package.json +1 -1
@@ -0,0 +1,1826 @@
1
+ import { H as isDocsAgentDiscoveryRequest, J as isDocsSkillRequest, Qt as upsertPageAgentContractMarkdown, U as isDocsAgentsRequest, Vt as PAGE_AGENT_CONTRACT_END_MARKER, Wt as PAGE_AGENT_CONTRACT_START_MARKER, Yt as renderPageAgentContractMarkdown, at as resolveDocsAgentFeedbackConfig, gt as resolveDocsSkillFormat, lt as resolveDocsLlmsTxtRequest, ot as resolveDocsAgentFeedbackRequest, pt as resolveDocsMarkdownRequest, st as resolveDocsAgentsFormat, wt as resolveDocsAudienceMdxContent } from "./agent-DKKptIgy.mjs";
2
+
3
+ //#region src/telemetry.ts
4
+ const DOCS_PACKAGE_NAME = "@farming-labs/docs";
5
+ const DOCS_PACKAGE_VERSION = "0.2.25";
6
+ const DEFAULT_DOCS_TELEMETRY_ENDPOINT = "https://docs.farming-labs.dev/api/telemetry/events";
7
+ const PROJECT_TELEMETRY_CACHE_TTL_MS = 1440 * 60 * 1e3;
8
+ const PROJECT_TELEMETRY_CACHE_MAX_KEYS = 256;
9
+ function getRuntimeEnv() {
10
+ if (typeof process === "undefined" || !process.env) return;
11
+ return process.env;
12
+ }
13
+ function readRuntimeEnv(name) {
14
+ const value = getRuntimeEnv()?.[name]?.trim();
15
+ return value ? value : void 0;
16
+ }
17
+ function isTruthyEnv(value) {
18
+ return /^(1|true|yes|on)$/i.test(value ?? "");
19
+ }
20
+ function isFalsyEnv(value) {
21
+ return /^(0|false|no|off)$/i.test(value ?? "");
22
+ }
23
+ function isBrowserRuntime() {
24
+ return typeof window !== "undefined" && typeof document !== "undefined";
25
+ }
26
+ function isProductionTelemetryRuntime() {
27
+ if (isBrowserRuntime()) return false;
28
+ const env = getRuntimeEnv();
29
+ if (!env) return true;
30
+ if (env.NODE_ENV === "test") return false;
31
+ if (env.VERCEL_ENV === "production") return true;
32
+ if (env.CONTEXT === "production" && isTruthyEnv(env.NETLIFY)) return true;
33
+ if (isTruthyEnv(env.CF_PAGES)) return true;
34
+ if (env.RENDER_SERVICE_ID || env.FLY_APP_NAME || env.RAILWAY_ENVIRONMENT) return true;
35
+ return env.NODE_ENV === "production";
36
+ }
37
+ function resolveDocsTelemetryConfig(telemetry) {
38
+ const envToggle = readRuntimeEnv("DOCS_TELEMETRY");
39
+ const envDisabled = readRuntimeEnv("DOCS_TELEMETRY_DISABLED");
40
+ if (isFalsyEnv(envToggle) || isTruthyEnv(envDisabled) || telemetry === false) return { enabled: false };
41
+ const objectConfig = telemetry && typeof telemetry === "object" ? telemetry : void 0;
42
+ if (objectConfig?.enabled === false) return { enabled: false };
43
+ return {
44
+ enabled: telemetry === true || objectConfig?.enabled === true || isTruthyEnv(envToggle) || isProductionTelemetryRuntime(),
45
+ endpoint: objectConfig?.endpoint?.trim() || readRuntimeEnv("DOCS_TELEMETRY_ENDPOINT") || DEFAULT_DOCS_TELEMETRY_ENDPOINT
46
+ };
47
+ }
48
+ function readRequestOrigin(request) {
49
+ if (!request?.url) return void 0;
50
+ try {
51
+ return new URL(request.url).origin;
52
+ } catch {
53
+ return;
54
+ }
55
+ }
56
+ /** Normalize an HTTP(S) telemetry site value to its origin. */
57
+ function normalizeDocsTelemetryOrigin(candidate) {
58
+ if (typeof candidate !== "string") return void 0;
59
+ const trimmed = candidate.trim();
60
+ if (!trimmed) return void 0;
61
+ try {
62
+ const hasScheme = /^[a-z][a-z\d+.-]*:(?!\d)/i.test(trimmed);
63
+ const url = new URL(hasScheme ? trimmed : `https://${trimmed}`);
64
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
65
+ return url.origin;
66
+ } catch {
67
+ return;
68
+ }
69
+ }
70
+ /** Return whether a telemetry origin points at a local development server. */
71
+ function isLocalDocsTelemetryOrigin(candidate) {
72
+ const origin = normalizeDocsTelemetryOrigin(candidate);
73
+ if (!origin) return false;
74
+ const hostname = new URL(origin).hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.+$/, "");
75
+ if ([
76
+ "localhost",
77
+ "localhost.localdomain",
78
+ "localhost6",
79
+ "localhost6.localdomain6",
80
+ "ip6-localhost",
81
+ "ip6-loopback"
82
+ ].includes(hostname) || hostname.endsWith(".localhost")) return true;
83
+ if (hostname === "::" || hostname === "::1") return true;
84
+ const ipv4 = hostname.split(".").map((part) => Number(part));
85
+ if (ipv4.length === 4 && ipv4.every((part) => Number.isInteger(part) && part >= 0 && part <= 255)) return ipv4[0] === 127 || ipv4.every((part) => part === 0);
86
+ return /^::ffff:7f[0-9a-f]{2}:/.test(hostname) || hostname === "::ffff:0:0";
87
+ }
88
+ function isBlockedDocsTelemetryOrigin(candidate) {
89
+ if (candidate === void 0) return false;
90
+ return !normalizeDocsTelemetryOrigin(candidate) || isLocalDocsTelemetryOrigin(candidate);
91
+ }
92
+ function readDeploymentOrigin() {
93
+ const candidates = [
94
+ readRuntimeEnv("DOCS_SITE_URL"),
95
+ readRuntimeEnv("NEXT_PUBLIC_BASE_URL"),
96
+ readRuntimeEnv("NEXT_PUBLIC_SITE_URL"),
97
+ readRuntimeEnv("SITE_URL"),
98
+ readRuntimeEnv("URL"),
99
+ readRuntimeEnv("CF_PAGES_URL"),
100
+ readRuntimeEnv("VERCEL_PROJECT_PRODUCTION_URL"),
101
+ readRuntimeEnv("VERCEL_URL"),
102
+ readRuntimeEnv("DEPLOY_PRIME_URL")
103
+ ];
104
+ for (const candidate of candidates) {
105
+ const origin = normalizeDocsTelemetryOrigin(candidate);
106
+ if (origin) return origin;
107
+ }
108
+ }
109
+ function detectDeployment() {
110
+ const env = getRuntimeEnv();
111
+ if (!env) return void 0;
112
+ if (env.VERCEL || env.VERCEL_ENV) return {
113
+ provider: "vercel",
114
+ environment: env.VERCEL_ENV,
115
+ id: env.VERCEL_DEPLOYMENT_ID ?? env.VERCEL_GIT_COMMIT_SHA,
116
+ region: env.VERCEL_REGION
117
+ };
118
+ if (env.NETLIFY) return {
119
+ provider: "netlify",
120
+ environment: env.CONTEXT,
121
+ id: env.DEPLOY_ID ?? env.COMMIT_REF
122
+ };
123
+ if (env.CF_PAGES) return {
124
+ provider: "cloudflare-pages",
125
+ environment: env.CF_PAGES_BRANCH,
126
+ id: env.CF_PAGES_COMMIT_SHA
127
+ };
128
+ if (env.RENDER_SERVICE_ID) return {
129
+ provider: "render",
130
+ environment: env.RENDER_ENV,
131
+ id: env.RENDER_SERVICE_ID
132
+ };
133
+ if (env.FLY_APP_NAME) return {
134
+ provider: "fly",
135
+ environment: env.FLY_APP_NAME,
136
+ id: env.FLY_ALLOC_ID,
137
+ region: env.FLY_REGION
138
+ };
139
+ if (env.RAILWAY_ENVIRONMENT) return {
140
+ provider: "railway",
141
+ environment: env.RAILWAY_ENVIRONMENT,
142
+ id: env.RAILWAY_DEPLOYMENT_ID
143
+ };
144
+ return env.NODE_ENV === "production" ? { environment: "production" } : void 0;
145
+ }
146
+ function detectRuntime() {
147
+ if (typeof process !== "undefined" && process.versions?.node) return {
148
+ name: "node",
149
+ version: process.versions.node
150
+ };
151
+ if (typeof navigator !== "undefined" && navigator.userAgent) return { name: "web-standard" };
152
+ }
153
+ function isObjectConfigEnabled(value, defaultEnabled) {
154
+ if (value === false) return false;
155
+ if (value === true) return true;
156
+ if (value && typeof value === "object" && !Array.isArray(value)) {
157
+ const enabled = value.enabled;
158
+ return enabled === false ? false : defaultEnabled || enabled === true;
159
+ }
160
+ return defaultEnabled;
161
+ }
162
+ function hasObjectConfig(value) {
163
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
164
+ }
165
+ function getDocsTelemetryFeatures(config) {
166
+ const pageActions = config.pageActions;
167
+ const feedback = config.feedback;
168
+ return {
169
+ search: isObjectConfigEnabled(config.search, true),
170
+ ai: isObjectConfigEnabled(config.ai, false),
171
+ mcp: isObjectConfigEnabled(config.mcp, true),
172
+ llmsTxt: isObjectConfigEnabled(config.llmsTxt, true),
173
+ pageActions: hasObjectConfig(pageActions),
174
+ feedback: feedback === true || hasObjectConfig(feedback) && feedback.enabled !== false,
175
+ agentFeedback: feedback !== false && !(hasObjectConfig(feedback) && feedback.agent === false) && !(hasObjectConfig(feedback) && hasObjectConfig(feedback.agent) && feedback.agent.enabled === false),
176
+ sitemap: isObjectConfigEnabled(config.sitemap, true),
177
+ robots: isObjectConfigEnabled(config.robots, true),
178
+ apiReference: isObjectConfigEnabled(config.apiReference, false),
179
+ staticExport: config.staticExport === true,
180
+ changelog: isObjectConfigEnabled(config.changelog, false),
181
+ cloud: typeof config.cloud !== "undefined" && isObjectConfigEnabled(config.cloud, true),
182
+ review: isObjectConfigEnabled(config.review, true),
183
+ codeBlocksValidate: hasObjectConfig(config.codeBlocks) && Boolean(config.codeBlocks.validate)
184
+ };
185
+ }
186
+ function createDocsTelemetryEvent(config, input, context = {}) {
187
+ const telemetryConfig = config.telemetry && typeof config.telemetry === "object" ? config.telemetry : void 0;
188
+ const siteOriginCandidates = [
189
+ context.siteOrigin,
190
+ input.site?.origin,
191
+ telemetryConfig?.siteOrigin,
192
+ readRequestOrigin(context.request),
193
+ readDeploymentOrigin()
194
+ ];
195
+ if (siteOriginCandidates.some(isBlockedDocsTelemetryOrigin)) return void 0;
196
+ const siteOrigin = siteOriginCandidates.map(normalizeDocsTelemetryOrigin).find((origin) => Boolean(origin));
197
+ const deployment = input.deployment ?? detectDeployment();
198
+ const properties = context.properties || input.properties ? {
199
+ ...input.properties,
200
+ ...context.properties
201
+ } : void 0;
202
+ return {
203
+ ...input,
204
+ timestamp: input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
205
+ package: {
206
+ name: DOCS_PACKAGE_NAME,
207
+ version: DOCS_PACKAGE_VERSION,
208
+ ...input.package
209
+ },
210
+ framework: input.framework ?? context.framework,
211
+ runtime: input.runtime ?? detectRuntime(),
212
+ site: siteOrigin ? { origin: siteOrigin } : input.site,
213
+ deployment,
214
+ features: input.features ?? getDocsTelemetryFeatures(config),
215
+ properties
216
+ };
217
+ }
218
+ function projectEventKey(event) {
219
+ return [
220
+ event.package.name,
221
+ event.package.version,
222
+ event.framework ?? "",
223
+ event.site?.origin ?? "",
224
+ event.deployment?.provider ?? "",
225
+ event.deployment?.environment ?? "",
226
+ event.deployment?.id ?? ""
227
+ ].join("|");
228
+ }
229
+ function getSentProjectKeys() {
230
+ const globalValue = globalThis;
231
+ globalValue.__farmingLabsDocsTelemetryProjectKeys__ ??= /* @__PURE__ */ new Map();
232
+ return globalValue.__farmingLabsDocsTelemetryProjectKeys__;
233
+ }
234
+ function pruneSentProjectKeys(sent, now) {
235
+ for (const [key, expiresAt] of sent) if (expiresAt <= now) sent.delete(key);
236
+ while (sent.size >= PROJECT_TELEMETRY_CACHE_MAX_KEYS) {
237
+ const oldestKey = sent.keys().next().value;
238
+ if (!oldestKey) return;
239
+ sent.delete(oldestKey);
240
+ }
241
+ }
242
+ async function emitDocsTelemetryEvent(telemetry, event) {
243
+ const resolved = resolveDocsTelemetryConfig(telemetry);
244
+ const eventSiteOrigin = event.site?.origin;
245
+ const normalizedSiteOrigin = normalizeDocsTelemetryOrigin(eventSiteOrigin);
246
+ if (!resolved.enabled || !resolved.endpoint || typeof fetch !== "function" || eventSiteOrigin !== void 0 && (!normalizedSiteOrigin || isLocalDocsTelemetryOrigin(eventSiteOrigin))) return;
247
+ const eventToSend = normalizedSiteOrigin ? {
248
+ ...event,
249
+ site: { origin: normalizedSiteOrigin }
250
+ } : event;
251
+ try {
252
+ const ingestKey = readRuntimeEnv("DOCS_TELEMETRY_INGEST_KEY");
253
+ const headers = { "content-type": "application/json" };
254
+ if (ingestKey) headers["x-docs-telemetry-key"] = ingestKey;
255
+ await fetch(resolved.endpoint, {
256
+ method: "POST",
257
+ headers,
258
+ body: JSON.stringify({ event: eventToSend }),
259
+ keepalive: true
260
+ });
261
+ } catch {}
262
+ }
263
+ function emitDocsTelemetryProjectEvent(config, context = {}) {
264
+ const event = createDocsTelemetryEvent(config, { type: "project_detected" }, context);
265
+ if (!event) return;
266
+ const key = projectEventKey(event);
267
+ const sent = getSentProjectKeys();
268
+ const now = Date.now();
269
+ const expiresAt = sent.get(key);
270
+ if (expiresAt && expiresAt > now) return;
271
+ if (typeof expiresAt === "number") sent.delete(key);
272
+ pruneSentProjectKeys(sent, now);
273
+ sent.set(key, now + PROJECT_TELEMETRY_CACHE_TTL_MS);
274
+ emitDocsTelemetryEvent(config.telemetry, event);
275
+ }
276
+ function emitDocsTelemetryAgentSurfaceEvent(config, context) {
277
+ const event = createDocsTelemetryEvent(config, {
278
+ type: context.surface === "mcp" ? "mcp_request" : "agent_surface_used",
279
+ properties: { surface: context.surface }
280
+ }, context);
281
+ if (!event) return;
282
+ emitDocsTelemetryEvent(config.telemetry, event);
283
+ }
284
+ function emitDocsTelemetryMcpToolEvent(config, context) {
285
+ const event = createDocsTelemetryEvent(config, {
286
+ type: "mcp_tool_used",
287
+ properties: {
288
+ tool: context.tool,
289
+ locale: context.locale,
290
+ resultCount: context.resultCount
291
+ }
292
+ }, context);
293
+ if (!event) return;
294
+ emitDocsTelemetryEvent(config.telemetry, event);
295
+ }
296
+ function inferDocsTelemetryAgentSurface(request, options) {
297
+ const url = new URL(request.url);
298
+ const method = request.method.toUpperCase();
299
+ const feedbackRequest = resolveDocsAgentFeedbackRequest(url, resolveDocsAgentFeedbackConfig(options.feedback));
300
+ if ((method === "GET" || method === "HEAD") && isDocsAgentDiscoveryRequest(url)) return "agent_spec";
301
+ if ((method === "GET" || method === "HEAD") && feedbackRequest?.kind === "schema") return "agent_feedback_schema";
302
+ if (method === "POST" && feedbackRequest?.kind === "submit") return "agent_feedback_submit";
303
+ if (method === "GET" || method === "HEAD") {
304
+ if (isDocsAgentsRequest(url) || resolveDocsAgentsFormat(url) === "agents") return "agents";
305
+ if (isDocsSkillRequest(url) || resolveDocsSkillFormat(url) === "skill") return "skill";
306
+ if (resolveDocsMarkdownRequest(options.entry, url, request)) return "markdown";
307
+ if (resolveDocsLlmsTxtRequest(url, options.llmsTxt, options.entry)) return "llms";
308
+ }
309
+ if (method === "POST") return "ask_ai";
310
+ }
311
+
312
+ //#endregion
313
+ //#region src/sidebar.ts
314
+ function resolvePageSidebarFolderIndexBehavior(sidebar) {
315
+ if (!sidebar || typeof sidebar !== "object") return void 0;
316
+ const value = sidebar.folderIndexBehavior;
317
+ return value === "link" || value === "toggle" || value === "hidden" ? value : void 0;
318
+ }
319
+ function normalizeSidebarFolderBehaviorPath(path) {
320
+ if (!path) return void 0;
321
+ let value = path.trim();
322
+ if (!value) return void 0;
323
+ if (/^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(value)) try {
324
+ value = new URL(value).pathname;
325
+ } catch {
326
+ return;
327
+ }
328
+ else value = value.split("#", 1)[0]?.split("?", 1)[0] ?? value;
329
+ if (!value.startsWith("/")) value = `/${value}`;
330
+ return value.replace(/\/$/, "") || "/";
331
+ }
332
+ function resolveSidebarFolderIndexBehavior(sidebar, defaultBehavior = "link") {
333
+ if (sidebar === void 0 || sidebar === true || sidebar === false) return defaultBehavior;
334
+ if (sidebar.folderIndexBehavior === "toggle") return "toggle";
335
+ if (sidebar.folderIndexBehavior === "hidden") return "hidden";
336
+ if (sidebar.folderIndexBehavior === "link") return "link";
337
+ return defaultBehavior;
338
+ }
339
+ function resolveSidebarFolderIndexBehaviorForPath(sidebar, folderPath, defaultBehavior = "link") {
340
+ const fallback = resolveSidebarFolderIndexBehavior(sidebar, defaultBehavior);
341
+ if (!sidebar || typeof sidebar !== "object") return fallback;
342
+ const normalizedPath = normalizeSidebarFolderBehaviorPath(folderPath);
343
+ if (!normalizedPath) return fallback;
344
+ for (const [rawPath, override] of Object.entries(sidebar.folderIndexBehaviorOverrides ?? {})) if (normalizeSidebarFolderBehaviorPath(rawPath) === normalizedPath) return override === "link" || override === "toggle" || override === "hidden" ? override : fallback;
345
+ return fallback;
346
+ }
347
+ function applySidebarFolderIndexBehavior(tree, behaviorOrOptions) {
348
+ const resolveBehavior = typeof behaviorOrOptions === "string" ? () => behaviorOrOptions : (folderPath) => resolveSidebarFolderIndexBehaviorForPath(behaviorOrOptions.sidebar, folderPath, behaviorOrOptions.defaultBehavior);
349
+ function mapNode(node) {
350
+ if (!node || typeof node !== "object") return node;
351
+ const candidate = node;
352
+ if (candidate.type !== "folder" || !Array.isArray(candidate.children)) return node;
353
+ const children = candidate.children.map(mapNode);
354
+ const index = candidate.index ? mapNode(candidate.index) : void 0;
355
+ const folderPath = (typeof candidate.url === "string" ? candidate.url : void 0) || (candidate.index && typeof candidate.index === "object" && "url" in candidate.index && typeof candidate.index.url === "string" ? candidate.index.url ?? void 0 : void 0);
356
+ const behavior = (candidate.folderIndexBehavior === "link" || candidate.folderIndexBehavior === "toggle" || candidate.folderIndexBehavior === "hidden" ? candidate.folderIndexBehavior : void 0) ?? resolveBehavior(folderPath);
357
+ if (behavior === "link") return {
358
+ ...candidate,
359
+ folderIndexBehavior: void 0,
360
+ index,
361
+ children
362
+ };
363
+ if (behavior === "hidden") return {
364
+ ...candidate,
365
+ folderIndexBehavior: void 0,
366
+ index: void 0,
367
+ url: void 0,
368
+ children
369
+ };
370
+ return {
371
+ ...candidate,
372
+ folderIndexBehavior: void 0,
373
+ index: void 0,
374
+ url: void 0,
375
+ children: index ? [index, ...children] : children
376
+ };
377
+ }
378
+ return {
379
+ ...tree,
380
+ children: tree.children.map(mapNode)
381
+ };
382
+ }
383
+
384
+ //#endregion
385
+ //#region src/agent-provenance.ts
386
+ const GENERATED_AGENT_PROVENANCE_MARKER = "@farming-labs/docs:generated";
387
+ const GENERATED_AGENT_PROVENANCE_VERSION = 1;
388
+ function normalizeLineEndings(value) {
389
+ return value.replace(/\r\n?/g, "\n").replace(/^\uFEFF/, "");
390
+ }
391
+ function normalizeGeneratedAgentContent(value) {
392
+ return normalizeLineEndings(value).trimEnd();
393
+ }
394
+ function hashGeneratedAgentContent(value) {
395
+ const normalized = normalizeGeneratedAgentContent(value);
396
+ const bytes = new TextEncoder().encode(normalized);
397
+ let hash = 14695981039346656037n;
398
+ for (const byte of bytes) {
399
+ hash ^= BigInt(byte);
400
+ hash = BigInt.asUintN(64, hash * 1099511628211n);
401
+ }
402
+ return `fnv1a64:${hash.toString(16).padStart(16, "0")}`;
403
+ }
404
+ function parseProvenanceBlock(rawBlock) {
405
+ const entries = /* @__PURE__ */ new Map();
406
+ for (const line of rawBlock.split("\n")) {
407
+ const trimmed = line.trim();
408
+ if (!trimmed) continue;
409
+ const separatorIndex = trimmed.indexOf("=");
410
+ if (separatorIndex <= 0) continue;
411
+ const key = trimmed.slice(0, separatorIndex).trim();
412
+ const value = trimmed.slice(separatorIndex + 1).trim();
413
+ if (!key || !value) continue;
414
+ entries.set(key, value);
415
+ }
416
+ const version = Number.parseInt(entries.get("version") ?? "", 10);
417
+ const sourceKind = entries.get("sourceKind");
418
+ const sourceHash = entries.get("sourceHash");
419
+ const settingsHash = entries.get("settingsHash");
420
+ const outputHash = entries.get("outputHash");
421
+ const generatedAt = entries.get("generatedAt");
422
+ if (!Number.isFinite(version) || sourceKind !== "resolved-page" && sourceKind !== "agent-md" || !sourceHash || !settingsHash || !outputHash || !generatedAt) return;
423
+ return {
424
+ version,
425
+ sourceKind,
426
+ sourceHash,
427
+ settingsHash,
428
+ outputHash,
429
+ generatedAt
430
+ };
431
+ }
432
+ function parseGeneratedAgentDocument(raw) {
433
+ const normalized = normalizeLineEndings(raw);
434
+ const headerPattern = new RegExp(`^<!-- ${GENERATED_AGENT_PROVENANCE_MARKER}\\n([\\s\\S]*?)\\n-->\\n?`);
435
+ const match = normalized.match(headerPattern);
436
+ if (!match) return { content: normalized };
437
+ return {
438
+ provenance: parseProvenanceBlock(match[1]),
439
+ content: normalized.slice(match[0].length)
440
+ };
441
+ }
442
+ function stripGeneratedAgentProvenance(raw) {
443
+ return parseGeneratedAgentDocument(raw).content;
444
+ }
445
+ function serializeGeneratedAgentDocument(content, provenance) {
446
+ const normalizedContent = normalizeGeneratedAgentContent(content);
447
+ return `${[
448
+ `<!-- ${GENERATED_AGENT_PROVENANCE_MARKER}`,
449
+ `version=${provenance.version}`,
450
+ `sourceKind=${provenance.sourceKind}`,
451
+ `sourceHash=${provenance.sourceHash}`,
452
+ `settingsHash=${provenance.settingsHash}`,
453
+ `outputHash=${provenance.outputHash}`,
454
+ `generatedAt=${provenance.generatedAt}`,
455
+ "-->",
456
+ normalizedContent
457
+ ].join("\n")}\n`;
458
+ }
459
+
460
+ //#endregion
461
+ //#region src/markdown-sections.ts
462
+ const HTML_ENTITIES = {
463
+ amp: "&",
464
+ apos: "'",
465
+ gt: ">",
466
+ lt: "<",
467
+ nbsp: " ",
468
+ quot: "\""
469
+ };
470
+ /**
471
+ * Reduce inline Markdown in a heading to the visible label used by search and anchors.
472
+ * This intentionally covers the common inline constructs without adding a Markdown parser
473
+ * dependency to the runtime package.
474
+ */
475
+ function cleanDocsMarkdownHeadingLabel(value) {
476
+ return value.replace(/!\[([^\]]*)\]\([^)]*\)/gu, "$1").replace(/\[([^\]]+)\]\([^)]*\)/gu, "$1").replace(/!\[([^\]]*)\]\[[^\]]*\]/gu, "$1").replace(/\[([^\]]+)\]\[[^\]]*\]/gu, "$1").replace(/<((?:https?:\/\/|mailto:)[^>]+)>/giu, "$1").replace(/<[^>]+>/gu, "").replace(/`+([^`]*?)`+/gu, "$1").replace(/\\([!"#$%&'()*+,\-./:;<=>?@[\]^_`{|}~])/gu, "$1").replace(/[*_~]/gu, "").replace(/&(#(?:x[0-9a-f]+|\d+)|[a-z]+);/giu, (_match, entity) => {
477
+ if (entity.startsWith("#x") || entity.startsWith("#X")) {
478
+ const codePoint = Number.parseInt(entity.slice(2), 16);
479
+ return Number.isSafeInteger(codePoint) && codePoint >= 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : _match;
480
+ }
481
+ if (entity.startsWith("#")) {
482
+ const codePoint = Number.parseInt(entity.slice(1), 10);
483
+ return Number.isSafeInteger(codePoint) && codePoint >= 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : _match;
484
+ }
485
+ return HTML_ENTITIES[entity.toLowerCase()] ?? _match;
486
+ }).replace(/\s+/gu, " ").trim();
487
+ }
488
+ /** Keep anchors aligned with the framework's existing ASCII heading-slug behavior. */
489
+ function slugifyDocsMarkdownHeading(value) {
490
+ return cleanDocsMarkdownHeadingLabel(value).toLowerCase().replace(/[`'"‘’“”]/gu, "").replace(/&/gu, " and ").replace(/[^a-z0-9\s-]/gu, "").replace(/\s+/gu, "-").replace(/-+/gu, "-").replace(/^-|-$/gu, "");
491
+ }
492
+ function readOpeningFence(line) {
493
+ const match = line.match(/^ {0,3}(`{3,}|~{3,})/u);
494
+ if (!match) return void 0;
495
+ return {
496
+ marker: match[1][0],
497
+ length: match[1].length
498
+ };
499
+ }
500
+ function isClosingFence(line, fence) {
501
+ const marker = fence.marker === "`" ? "`" : "~";
502
+ const match = line.match(new RegExp(`^ {0,3}(${marker}{${fence.length},})[\\t ]*$`, "u"));
503
+ return Boolean(match);
504
+ }
505
+ function readAtxHeading(line) {
506
+ const match = line.match(/^ {0,3}(#{1,6})(?:[\t ]+|$)(.*)$/u);
507
+ if (!match) return void 0;
508
+ return {
509
+ title: cleanDocsMarkdownHeadingLabel(match[2].replace(/[\t ]+#+[\t ]*$/u, "").trim()),
510
+ level: match[1].length
511
+ };
512
+ }
513
+ function readSetextLevel(line) {
514
+ const match = line.match(/^ {0,3}(=+|-+)[\t ]*$/u);
515
+ if (!match) return void 0;
516
+ return match[1][0] === "=" ? 1 : 2;
517
+ }
518
+ function normalizeDocsSectionSelector(value) {
519
+ let selector = value.trim();
520
+ const hashIndex = selector.lastIndexOf("#");
521
+ if (hashIndex >= 0) selector = selector.slice(hashIndex + 1);
522
+ try {
523
+ selector = decodeURIComponent(selector);
524
+ } catch {}
525
+ return cleanDocsMarkdownHeadingLabel(selector.replace(/^#+/u, "")).toLowerCase();
526
+ }
527
+ /**
528
+ * Parse heading sections once for search, Ask AI hydration, and MCP tools.
529
+ * Supports CommonMark ATX indentation, Setext headings, fenced-code exclusion,
530
+ * visible inline labels, and stable duplicate anchors.
531
+ */
532
+ function parseDocsMarkdownSections(markdown) {
533
+ const lines = markdown.split("\n");
534
+ const headings = [];
535
+ const headingCounts = /* @__PURE__ */ new Map();
536
+ let openFence;
537
+ let setextCandidate;
538
+ const pushHeading = (title, level, start) => {
539
+ const baseAnchor = slugifyDocsMarkdownHeading(title) || `section-${headings.length}`;
540
+ const seen = headingCounts.get(baseAnchor) ?? 0;
541
+ headingCounts.set(baseAnchor, seen + 1);
542
+ headings.push({
543
+ title,
544
+ anchor: seen === 0 ? baseAnchor : `${baseAnchor}-${seen}`,
545
+ level,
546
+ start
547
+ });
548
+ };
549
+ for (let index = 0; index < lines.length; index += 1) {
550
+ const line = lines[index];
551
+ if (openFence) {
552
+ if (isClosingFence(line, openFence)) openFence = void 0;
553
+ setextCandidate = void 0;
554
+ continue;
555
+ }
556
+ const openingFence = readOpeningFence(line);
557
+ if (openingFence) {
558
+ openFence = openingFence;
559
+ setextCandidate = void 0;
560
+ continue;
561
+ }
562
+ const setextLevel = readSetextLevel(line);
563
+ if (setextLevel && setextCandidate?.index === index - 1) {
564
+ const title = cleanDocsMarkdownHeadingLabel(setextCandidate.value.trim());
565
+ if (title) pushHeading(title, setextLevel, setextCandidate.index);
566
+ setextCandidate = void 0;
567
+ continue;
568
+ }
569
+ const atxHeading = readAtxHeading(line);
570
+ if (atxHeading) {
571
+ pushHeading(atxHeading.title, atxHeading.level, index);
572
+ setextCandidate = void 0;
573
+ continue;
574
+ }
575
+ if (!line.trim() || /^ {4}|^\t/u.test(line)) {
576
+ setextCandidate = void 0;
577
+ continue;
578
+ }
579
+ setextCandidate = {
580
+ index,
581
+ value: line
582
+ };
583
+ }
584
+ return headings.map((heading, index) => {
585
+ const end = headings.slice(index + 1).find((candidate) => candidate.level <= heading.level)?.start ?? lines.length;
586
+ return {
587
+ title: heading.title,
588
+ anchor: heading.anchor,
589
+ level: heading.level,
590
+ content: lines.slice(heading.start, end).join("\n").trim()
591
+ };
592
+ });
593
+ }
594
+ function findDocsMarkdownSection(markdown, requestedSection) {
595
+ const selector = normalizeDocsSectionSelector(requestedSection);
596
+ if (!selector) return void 0;
597
+ return parseDocsMarkdownSections(markdown).find((section) => normalizeDocsSectionSelector(section.title) === selector || section.anchor === selector);
598
+ }
599
+
600
+ //#endregion
601
+ //#region src/search.ts
602
+ const DEFAULT_SEARCH_LIMIT = 10;
603
+ const MAX_SEARCH_SNIPPET_CHARS = 160;
604
+ const DEFAULT_MCP_PROTOCOL_VERSION = "2025-11-25";
605
+ const MCP_SESSION_CLEANUP_TIMEOUT_MS = 1e3;
606
+ const syncedIndexes = /* @__PURE__ */ new Set();
607
+ const ALGOLIA_MAX_RECORD_BYTES = 9500;
608
+ const DEFAULT_ASK_AI_CONTEXT_CHARS = 24e3;
609
+ const DEFAULT_ASK_AI_RESULT_CHARS = 6e3;
610
+ const SEARCH_STOP_WORDS = new Set([
611
+ "a",
612
+ "an",
613
+ "and",
614
+ "are",
615
+ "as",
616
+ "at",
617
+ "be",
618
+ "can",
619
+ "do",
620
+ "does",
621
+ "for",
622
+ "from",
623
+ "how",
624
+ "i",
625
+ "in",
626
+ "is",
627
+ "it",
628
+ "of",
629
+ "on",
630
+ "or",
631
+ "the",
632
+ "this",
633
+ "to",
634
+ "use",
635
+ "what",
636
+ "when",
637
+ "where",
638
+ "which",
639
+ "with"
640
+ ]);
641
+ function stripMarkdownText(content) {
642
+ return removeMdxModuleLinesOutsideFences(content).replace(/```[^\n]*\n([\s\S]*?)```/g, "$1").replace(/```([\s\S]*?)```/g, "$1").replace(/~~~[^\n]*\n([\s\S]*?)~~~/g, "$1").replace(/~~~([\s\S]*?)~~~/g, "$1").replace(/<[^>]+\/>/g, "").replace(/<\/?[A-Z][^>]*>/g, "").replace(/<\/?[a-z][^>]*>/g, "").replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/^\|?[\s:-]+(\|[\s:-]+)+\|?\s*$/gm, "").replace(/\|/g, " ").replace(/^[-*+]\s+/gm, "").replace(/(\*{1,3}|_{1,3})(.*?)\1/g, "$2").replace(/`{3,}[^\n]*$/gm, "").replace(/`([^`]+)`/g, "$1").replace(/`+/g, "").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/\n{3,}/g, "\n\n").replace(/\s{2,}/g, " ").trim();
643
+ }
644
+ function stripHtml(text) {
645
+ return text.replace(/<[^>]+>/g, "");
646
+ }
647
+ function normalizeMcpSsePayload(body) {
648
+ const payload = body.split("\n").filter((line) => line.startsWith("data: ")).map((line) => line.slice(6).trim()).filter(Boolean).at(-1);
649
+ return payload ? JSON.parse(payload) : null;
650
+ }
651
+ function normalizeWhitespace(value) {
652
+ return value.replace(/\s+/g, " ").trim();
653
+ }
654
+ function normalizeSearchPhrase(value) {
655
+ return normalizeWhitespace(value.toLowerCase().replace(/[?!.,;:]+$/g, ""));
656
+ }
657
+ function escapeRegExp(value) {
658
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
659
+ }
660
+ function literalMatchPriority(query, value) {
661
+ const q = normalizeSearchPhrase(query);
662
+ const text = normalizeSearchPhrase(value ?? "");
663
+ if (!q || !text) return 0;
664
+ if (text === q) return 2;
665
+ const boundary = "[^\\p{L}\\p{N}]";
666
+ return new RegExp(`(^|${boundary})${escapeRegExp(q)}(?=$|${boundary})`, "u").test(text) ? 1 : 0;
667
+ }
668
+ function isLiteralLookupQuery(query) {
669
+ const q = normalizeSearchPhrase(query);
670
+ const words = tokenizeSearchQuery(q);
671
+ return words.length > 0 && words.length <= 3 && words.join(" ") === q;
672
+ }
673
+ function tokenizeSearchQuery(query) {
674
+ 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))));
675
+ }
676
+ function normalizeUrlPathname(value) {
677
+ try {
678
+ return new URL(value, "https://docs.local").pathname.replace(/\/+$/, "") || "/";
679
+ } catch {
680
+ return value.split(/[?#]/)[0]?.replace(/\/+$/, "") || "/";
681
+ }
682
+ }
683
+ function safeDecodeUrlSegment(value) {
684
+ try {
685
+ return decodeURIComponent(value);
686
+ } catch {
687
+ return value;
688
+ }
689
+ }
690
+ function getUrlSearchSegments(value) {
691
+ let pathname = "";
692
+ try {
693
+ pathname = new URL(value, "https://docs.local").pathname;
694
+ } catch {
695
+ pathname = value.split(/[?#]/)[0] ?? "";
696
+ }
697
+ return Array.from(new Set(pathname.split("/").flatMap((segment) => {
698
+ const decoded = safeDecodeUrlSegment(segment);
699
+ return [decoded, decoded.replace(/[-_]+/g, " ")];
700
+ }).map(normalizeSearchPhrase).filter(Boolean)));
701
+ }
702
+ function resolveAskAIContextUrl(value, baseUrl) {
703
+ if (!baseUrl) return value;
704
+ try {
705
+ return new URL(value, baseUrl).toString();
706
+ } catch {
707
+ return value;
708
+ }
709
+ }
710
+ function getAskAIPageContent(page) {
711
+ return upsertPageAgentContractMarkdown(getPageAudienceRawContent(page, "agent"), page.agent).replace(PAGE_AGENT_CONTRACT_START_MARKER, "").replace(PAGE_AGENT_CONTRACT_END_MARKER, "").replace(/^\r?\n+/, "");
712
+ }
713
+ function getPageAgentContractSearchText(page) {
714
+ return stripMarkdownText(renderPageAgentContractMarkdown(page.agent).replace(PAGE_AGENT_CONTRACT_START_MARKER, "").replace(PAGE_AGENT_CONTRACT_END_MARKER, ""));
715
+ }
716
+ function removeMdxModuleLinesOutsideFences(content) {
717
+ let inFence = false;
718
+ return content.split("\n").filter((line) => {
719
+ const trimmed = line.trimStart();
720
+ if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
721
+ inFence = !inFence;
722
+ return true;
723
+ }
724
+ return inFence || !/^(import|export)\s/.test(trimmed);
725
+ }).join("\n");
726
+ }
727
+ function cleanAskAIContextMarkdown(content) {
728
+ return removeMdxModuleLinesOutsideFences(content).replace(/<[^>]+\/>/g, "").replace(/<\/?[A-Z][^>]*>/g, "").replace(/<\/?[a-z][^>]*>/g, "").replace(/\n{3,}/g, "\n\n").trim();
729
+ }
730
+ function packageRootFromSpecifier(specifier) {
731
+ if (!specifier || specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("@/") || specifier.startsWith("~/") || specifier.startsWith("#")) return null;
732
+ const parts = specifier.split("/").filter(Boolean);
733
+ if (parts.length === 0) return null;
734
+ if (parts[0]?.startsWith("@")) return parts.length > 1 ? `${parts[0]}/${parts[1]}` : null;
735
+ return parts[0];
736
+ }
737
+ function cleanPackageToken(token) {
738
+ const trimmed = token.trim().replace(/^["'`]+|["'`,;]+$/g, "").replace(/\\$/g, "");
739
+ if (!trimmed || trimmed.startsWith("-") || /^[A-Z_][A-Z0-9_]*=/.test(trimmed)) return null;
740
+ if (/^(npm|pnpm|yarn|bun|install|add|i|x|dlx|run|exec)$/.test(trimmed)) return null;
741
+ return packageRootFromSpecifier(trimmed.startsWith("@") ? trimmed.replace(/^(@[^/]+\/[^@]+)@.+$/, "$1") : trimmed.replace(/^([^@]+)@.+$/, "$1"));
742
+ }
743
+ function inferDocsAskAIPackageHints(content) {
744
+ const packages = /* @__PURE__ */ new Set();
745
+ const imports = /* @__PURE__ */ new Set();
746
+ const installCommands = /* @__PURE__ */ new Set();
747
+ for (const line of content.split("\n")) {
748
+ const trimmed = line.trim();
749
+ if (!trimmed) continue;
750
+ const importSpecifier = trimmed.match(/^(?:import|export)\s+(?:type\s+)?[\s\S]*?\s+from\s+["']([^"']+)["']/)?.[1];
751
+ const bareImportSpecifier = trimmed.match(/^import\s+["']([^"']+)["']/)?.[1];
752
+ const requireSpecifier = trimmed.match(/require\(["']([^"']+)["']\)/)?.[1];
753
+ const specifier = importSpecifier ?? bareImportSpecifier ?? requireSpecifier;
754
+ const packageName = specifier ? packageRootFromSpecifier(specifier) : null;
755
+ if (packageName) {
756
+ packages.add(packageName);
757
+ if (/^(?:import|export)\s/.test(trimmed)) imports.add(trimmed);
758
+ }
759
+ const installMatch = trimmed.match(/^(?:npm\s+(?:install|i)|pnpm\s+add|yarn\s+add|bun\s+add)\s+(.+)$/);
760
+ if (!installMatch) continue;
761
+ const commandPackages = installMatch[1].split(/\s+/).map(cleanPackageToken).filter((value) => Boolean(value));
762
+ if (commandPackages.length > 0) {
763
+ installCommands.add(trimmed);
764
+ for (const name of commandPackages) packages.add(name);
765
+ }
766
+ }
767
+ return {
768
+ packages: Array.from(packages).slice(0, 8),
769
+ imports: Array.from(imports).slice(0, 12),
770
+ installCommands: Array.from(installCommands).slice(0, 8)
771
+ };
772
+ }
773
+ function formatDocsAskAIPackageHints(hints, packageName) {
774
+ const packages = packageName ? Array.from(new Set([packageName, ...hints.packages])) : hints.packages;
775
+ if (packages.length === 0 && hints.imports.length === 0 && hints.installCommands.length === 0) return;
776
+ const lines = ["Package and import hints inferred from the retrieved documentation context:"];
777
+ if (packages.length > 0) lines.push(`- Package names found in install/import examples: ${packages.join(", ")}`);
778
+ if (hints.imports.length > 0) lines.push(`- Exact import lines found in context: ${hints.imports.map((line) => `\`${line}\``).join("; ")}`);
779
+ if (hints.installCommands.length > 0) lines.push(`- Exact install commands found in context: ${hints.installCommands.map((line) => `\`${line}\``).join("; ")}`);
780
+ lines.push("Use these exact package names, install commands, and import lines when relevant. Do not replace them with placeholders.");
781
+ return lines.join("\n");
782
+ }
783
+ function clampText(value, maxChars) {
784
+ if (maxChars <= 0) return "";
785
+ if (value.length <= maxChars) return value;
786
+ return `${value.slice(0, maxChars).trimEnd()}...`;
787
+ }
788
+ function findPageForSearchResult(pages, result, baseUrl, strictExternalOrigins = false) {
789
+ const rawUrl = result.url.trim();
790
+ const explicitlySchemed = /^[a-z][a-z\d+.-]*:/iu.test(rawUrl);
791
+ const explicitlyHosted = explicitlySchemed || /^[\\/]{2}/u.test(rawUrl);
792
+ if (explicitlySchemed && !/^https?:/iu.test(rawUrl)) return void 0;
793
+ if (explicitlyHosted && !baseUrl && strictExternalOrigins) return void 0;
794
+ if (explicitlyHosted && baseUrl) try {
795
+ if (new URL(result.url, baseUrl).origin !== new URL(baseUrl).origin) return void 0;
796
+ } catch {
797
+ return;
798
+ }
799
+ const resultPath = normalizeUrlPathname(result.url);
800
+ return pages.find((page) => normalizeUrlPathname(page.url) === resultPath);
801
+ }
802
+ function inferResultTitle(result, page) {
803
+ if (page) return page.title;
804
+ return stripHtml(result.content).trim().split("—")[0]?.trim() || result.url;
805
+ }
806
+ function formatAskAIContextResult(options) {
807
+ const { result, page, maxChars, baseUrl } = options;
808
+ const title = inferResultTitle(result, page);
809
+ const section = result.section;
810
+ const sectionSelector = getSearchResultAnchor(result.url) ?? section;
811
+ const contextContent = clampText(cleanAskAIContextMarkdown(page ? sectionSelector ? findDocsMarkdownSection(getAskAIPageContent(page), sectionSelector)?.content ?? "" : getAskAIPageContent(page) : [result.content, result.description].filter(Boolean).join("\n\n")), maxChars);
812
+ return {
813
+ ...result,
814
+ url: resolveAskAIContextUrl(result.url, baseUrl),
815
+ title,
816
+ contextContent
817
+ };
818
+ }
819
+ function getSearchResultKey(result) {
820
+ let hash = "";
821
+ try {
822
+ hash = new URL(result.url, "https://docs.local").hash.replace(/^#/, "");
823
+ } catch {
824
+ hash = result.url.split("#")[1]?.split(/[?&]/)[0] ?? "";
825
+ }
826
+ return `${normalizeUrlPathname(result.url)}#${normalizeWhitespace(hash || result.section || "").toLowerCase()}`;
827
+ }
828
+ function getSearchResultAnchor(value) {
829
+ let hash = "";
830
+ try {
831
+ hash = new URL(value, "https://docs.local").hash.replace(/^#/, "");
832
+ } catch {
833
+ hash = value.split("#")[1]?.split(/[?&]/)[0] ?? "";
834
+ }
835
+ if (!hash) return void 0;
836
+ try {
837
+ return decodeURIComponent(hash);
838
+ } catch {
839
+ return hash;
840
+ }
841
+ }
842
+ function getAskAIResultPageKey(value, baseUrl, strictExternalOrigins = false) {
843
+ const path = normalizeUrlPathname(value);
844
+ const rawValue = value.trim();
845
+ if (!(/^[a-z][a-z\d+.-]*:/iu.test(rawValue) || /^[\\/]{2}/u.test(rawValue)) || !baseUrl && !strictExternalOrigins) return path;
846
+ try {
847
+ const fallbackBase = baseUrl ?? "https://docs.local";
848
+ const parsed = new URL(value, fallbackBase);
849
+ const configuredBase = baseUrl ? new URL(baseUrl) : void 0;
850
+ if (configuredBase && parsed.origin === configuredBase.origin) return path;
851
+ return `${parsed.protocol}//${parsed.host}${path}`;
852
+ } catch {
853
+ return `${value.split("#", 1)[0]}::${path}`;
854
+ }
855
+ }
856
+ function getAskAIResultKey(result, baseUrl, strictExternalOrigins = false) {
857
+ return `${getAskAIResultPageKey(result.url, baseUrl, strictExternalOrigins)}#${normalizeWhitespace(getSearchResultAnchor(result.url) || result.section || "").toLowerCase()}`;
858
+ }
859
+ function mergeSearchResults(groups, getResultKey = getSearchResultKey) {
860
+ const seen = /* @__PURE__ */ new Set();
861
+ const results = [];
862
+ for (const group of groups) for (const result of group) {
863
+ const key = getResultKey(result);
864
+ if (seen.has(key)) continue;
865
+ seen.add(key);
866
+ results.push(result);
867
+ }
868
+ return results;
869
+ }
870
+ function buildAudienceProjectionSearchResults(documents, query) {
871
+ return documents.map((document) => {
872
+ const score = scoreDocument(query, document);
873
+ return {
874
+ id: document.id,
875
+ url: document.url,
876
+ content: document.section ? `${document.title} — ${document.section}` : document.title,
877
+ description: cleanSearchResultText(buildSnippet(document, query) ?? document.description),
878
+ type: document.type,
879
+ score,
880
+ section: document.section
881
+ };
882
+ });
883
+ }
884
+ function sanitizeExternalAudienceSearchResults(results, localAudienceResults, baseUrl, strictExternalOrigins = false, preserveUnmatched) {
885
+ const localByKey = new Map(localAudienceResults.map((result) => [getSearchResultKey(result), result]));
886
+ const localPageResults = /* @__PURE__ */ new Map();
887
+ for (const result of localAudienceResults) {
888
+ const pageUrl = normalizeUrlPathname(result.url);
889
+ if (!localPageResults.get(pageUrl) || result.type === "page") localPageResults.set(pageUrl, result);
890
+ }
891
+ return results.flatMap((result) => {
892
+ const rawUrl = result.url.trim();
893
+ const explicitlySchemed = /^[a-z][a-z\d+.-]*:/iu.test(rawUrl);
894
+ const externallyHosted = explicitlySchemed && /^https?:/iu.test(rawUrl) || /^[\\/]{2}/u.test(rawUrl);
895
+ const unsupportedScheme = explicitlySchemed && !/^https?:/iu.test(rawUrl);
896
+ let sameOriginOrUnknown = !externallyHosted || !baseUrl && !strictExternalOrigins;
897
+ if (unsupportedScheme) sameOriginOrUnknown = false;
898
+ if (externallyHosted && baseUrl) try {
899
+ sameOriginOrUnknown = new URL(result.url, baseUrl).origin === new URL(baseUrl).origin;
900
+ } catch {
901
+ sameOriginOrUnknown = false;
902
+ }
903
+ const hasSection = Boolean(getSearchResultAnchor(result.url) || result.section);
904
+ const local = sameOriginOrUnknown ? localByKey.get(getSearchResultKey(result)) ?? (hasSection ? void 0 : localPageResults.get(normalizeUrlPathname(result.url))) : void 0;
905
+ if (!local) return preserveUnmatched?.(result) ? [result] : [];
906
+ return [{
907
+ ...local,
908
+ id: result.id,
909
+ score: result.score ?? local.score
910
+ }];
911
+ });
912
+ }
913
+ function shouldPreserveUnmatchedExternalResult(options) {
914
+ const { result, localPagePaths, baseUrl } = options;
915
+ const path = normalizeUrlPathname(result.url);
916
+ const rawUrl = result.url.trim();
917
+ const explicitScheme = rawUrl.match(/^([a-z][a-z\d+.-]*):/iu)?.[1]?.toLowerCase();
918
+ if (explicitScheme && explicitScheme !== "http" && explicitScheme !== "https") return false;
919
+ const explicitlyHosted = /^[a-z][a-z\d+.-]*:/iu.test(rawUrl) || /^[\\/]{2}/u.test(rawUrl);
920
+ const knownLocalPath = localPagePaths.has(path);
921
+ if (!explicitlyHosted) return !knownLocalPath;
922
+ if (!baseUrl) return !knownLocalPath;
923
+ try {
924
+ if (new URL(result.url, baseUrl).origin !== new URL(baseUrl).origin) return true;
925
+ } catch {
926
+ return false;
927
+ }
928
+ return !knownLocalPath;
929
+ }
930
+ function getPageAudienceRawContent(page, audience) {
931
+ return resolveDocsAudienceMdxContent(audience === "agent" ? page.agentRawContent ?? page.agentFallbackRawContent ?? page.agentContent ?? page.agentFallbackContent ?? page.rawContent ?? page.content : page.rawContent ?? page.content, audience);
932
+ }
933
+ function getPageAudienceSearchText(page, audience) {
934
+ return stripMarkdownText(getPageAudienceRawContent(page, audience));
935
+ }
936
+ function isLocalProviderResult(result, baseUrl, strictExternalOrigins = false) {
937
+ const rawUrl = result.url.trim();
938
+ const explicitScheme = rawUrl.match(/^([a-z][a-z\d+.-]*):/iu)?.[1]?.toLowerCase();
939
+ if (explicitScheme && explicitScheme !== "http" && explicitScheme !== "https") return false;
940
+ if (!(/^[a-z][a-z\d+.-]*:/iu.test(rawUrl) || /^[\\/]{2}/u.test(rawUrl))) return true;
941
+ if (!baseUrl) return !strictExternalOrigins;
942
+ try {
943
+ return new URL(result.url, baseUrl).origin === new URL(baseUrl).origin;
944
+ } catch {
945
+ return false;
946
+ }
947
+ }
948
+ function hasOppositeAudienceEvidence(options) {
949
+ const { result, pages, query, audience, baseUrl, strictExternalOrigins } = options;
950
+ if (!isLocalProviderResult(result, baseUrl, strictExternalOrigins)) return false;
951
+ const pagePath = normalizeUrlPathname(result.url);
952
+ const page = pages.find((candidate) => normalizeUrlPathname(candidate.url) === pagePath);
953
+ if (!page) return false;
954
+ if (scoreDocument(query, pageToSearchDocument(page, audience)) > 0) return false;
955
+ const oppositeAudience = audience === "agent" ? "human" : "agent";
956
+ if (scoreDocument(query, pageToSearchDocument(page, oppositeAudience)) > 0) return true;
957
+ const evidence = cleanSearchResultText(result.description);
958
+ if (!evidence) return false;
959
+ const selectedTokens = new Set(tokenizeSearchQuery(getPageAudienceSearchText(page, audience)));
960
+ const oppositeTokens = new Set(tokenizeSearchQuery(getPageAudienceSearchText(page, oppositeAudience)));
961
+ const evidenceTokens = [...new Set(tokenizeSearchQuery(evidence))];
962
+ let selectedOnlyMatches = 0;
963
+ let oppositeOnlyMatches = 0;
964
+ for (const token of evidenceTokens) {
965
+ const inSelected = selectedTokens.has(token);
966
+ const inOpposite = oppositeTokens.has(token);
967
+ if (inSelected && !inOpposite) selectedOnlyMatches += 1;
968
+ if (inOpposite && !inSelected) oppositeOnlyMatches += 1;
969
+ }
970
+ return oppositeOnlyMatches > selectedOnlyMatches;
971
+ }
972
+ function pageToSearchDocument(page, audience = "human") {
973
+ return {
974
+ id: makeDocumentId(page.url, "page"),
975
+ url: page.url,
976
+ title: page.title,
977
+ content: normalizeWhitespace([getPageAudienceSearchText(page, audience), getPageAgentContractSearchText(page)].join(" ")),
978
+ description: page.description,
979
+ type: "page",
980
+ locale: page.locale,
981
+ framework: page.framework,
982
+ version: page.version,
983
+ tags: page.tags
984
+ };
985
+ }
986
+ function buildExactPageSearchResults(query, pages, audience = "human") {
987
+ const normalizedQuery = normalizeSearchPhrase(query);
988
+ if (!normalizedQuery) return [];
989
+ const results = [];
990
+ for (const page of pages) {
991
+ const document = pageToSearchDocument(page, audience);
992
+ const title = normalizeSearchPhrase(page.title);
993
+ const urlSegments = getUrlSearchSegments(page.url);
994
+ if (!(title === normalizedQuery || urlSegments.includes(normalizedQuery))) continue;
995
+ results.push({
996
+ id: document.id,
997
+ url: document.url,
998
+ content: cleanSearchResultText(document.title) ?? document.title,
999
+ description: cleanSearchResultText(buildSnippet(document, query) ?? document.description),
1000
+ type: "page",
1001
+ score: scoreDocument(query, document) + 2e3
1002
+ });
1003
+ }
1004
+ return results.sort((a, b) => (b.score ?? 0) - (a.score ?? 0) || a.url.localeCompare(b.url));
1005
+ }
1006
+ function hasDistinctResultSection(result) {
1007
+ if (result.type === "page") return false;
1008
+ const section = normalizeSearchPhrase(stripHtml(result.section ?? ""));
1009
+ if (!section) return true;
1010
+ return section !== normalizeSearchPhrase(normalizeSearchPhrase(stripHtml(result.content)).split(/\s+[—–]\s+/)[0] ?? "");
1011
+ }
1012
+ function insideLiteralResultPriority(query, result) {
1013
+ if (!hasDistinctResultSection(result) || !isLiteralLookupQuery(query)) return 0;
1014
+ return Math.max(literalMatchPriority(query, stripHtml(result.section ?? "")), literalMatchPriority(query, stripHtml(result.description ?? "")));
1015
+ }
1016
+ function prioritizeLiteralInsideResults(query, results) {
1017
+ if (!isLiteralLookupQuery(query)) return results;
1018
+ return [...results].sort((a, b) => {
1019
+ const literalDelta = insideLiteralResultPriority(query, b) - insideLiteralResultPriority(query, a);
1020
+ if (literalDelta) return literalDelta;
1021
+ return 0;
1022
+ });
1023
+ }
1024
+ function rankAskAIContextResult(query, result) {
1025
+ return scoreDocument(query, {
1026
+ id: result.id,
1027
+ url: result.url,
1028
+ title: result.title,
1029
+ content: result.contextContent,
1030
+ description: result.description,
1031
+ type: result.type,
1032
+ section: result.section
1033
+ });
1034
+ }
1035
+ function buildAskAIContextBlock(result) {
1036
+ const lines = [`## ${result.title}`, `URL: ${result.url}`];
1037
+ if (result.section) lines.push(`Section: ${result.section}`);
1038
+ if (result.description) lines.push(`Search snippet: ${result.description}`);
1039
+ lines.push("", result.contextContent);
1040
+ return lines.join("\n").trim();
1041
+ }
1042
+ function makeDocumentId(url, suffix) {
1043
+ return `${url}#${suffix}`;
1044
+ }
1045
+ function splitPageIntoSections(page, audience = "human") {
1046
+ return parseDocsMarkdownSections(getPageAudienceRawContent(page, audience)).flatMap((section, index) => {
1047
+ const content = normalizeWhitespace(stripMarkdownText(section.content));
1048
+ if (!content) return [];
1049
+ return [{
1050
+ id: makeDocumentId(page.url, `section-${index}`),
1051
+ url: `${page.url}#${section.anchor}`,
1052
+ title: page.title,
1053
+ section: section.title,
1054
+ content,
1055
+ description: page.description,
1056
+ type: "heading",
1057
+ locale: page.locale,
1058
+ framework: page.framework,
1059
+ version: page.version,
1060
+ tags: page.tags
1061
+ }];
1062
+ });
1063
+ }
1064
+ function buildDocsSearchDocuments(pages, chunking = {}, audience = "human") {
1065
+ const strategy = chunking.strategy ?? "section";
1066
+ return pages.flatMap((page) => {
1067
+ const base = pageToSearchDocument(page, audience);
1068
+ if (strategy === "page") return [base];
1069
+ const sections = splitPageIntoSections(page, audience);
1070
+ if (sections.length === 0) return [base];
1071
+ return [...base.content ? [base] : [], ...sections];
1072
+ });
1073
+ }
1074
+ function scoreDocument(query, document) {
1075
+ const q = normalizeSearchPhrase(query);
1076
+ if (!q) return 0;
1077
+ const words = tokenizeSearchQuery(q);
1078
+ const title = normalizeSearchPhrase(document.title);
1079
+ const section = document.section ? normalizeSearchPhrase(document.section) : "";
1080
+ const hasDistinctSection = Boolean(section && section !== title);
1081
+ const titleSection = section ? normalizeSearchPhrase(`${document.title} ${document.section}`) : "";
1082
+ const description = document.description ? normalizeSearchPhrase(document.description) : "";
1083
+ const content = normalizeSearchPhrase(document.content);
1084
+ const url = normalizeSearchPhrase(document.url);
1085
+ const urlSegments = getUrlSearchSegments(document.url);
1086
+ const titleTokens = tokenizeSearchQuery(title);
1087
+ const sectionTokens = tokenizeSearchQuery(section);
1088
+ let score = 0;
1089
+ const insideLiteralPriority = document.type !== "page" && hasDistinctSection && isLiteralLookupQuery(q) ? Math.max(literalMatchPriority(q, section), literalMatchPriority(q, description), literalMatchPriority(q, content)) : 0;
1090
+ if (insideLiteralPriority > 0) score += insideLiteralPriority * 2250;
1091
+ if (title === q) score += 1120;
1092
+ else if (title.startsWith(q)) score += 70;
1093
+ else if (title.includes(q)) score += 45;
1094
+ if (hasDistinctSection) {
1095
+ if (section === q) score += 1080;
1096
+ else if (section.startsWith(q)) score += 55;
1097
+ else if (section.includes(q)) score += 30;
1098
+ if (titleSection === q) score += 1e3;
1099
+ else if (titleSection.startsWith(q)) score += 50;
1100
+ else if (titleSection.includes(q)) score += 28;
1101
+ }
1102
+ if (urlSegments.includes(q)) score += 950;
1103
+ if (url.includes(q)) score += 12;
1104
+ if (description.includes(q)) score += 18;
1105
+ if (content.includes(q)) score += 12;
1106
+ let matchedWords = 0;
1107
+ for (const word of words) {
1108
+ let matched = false;
1109
+ if (title === word) {
1110
+ score += 28;
1111
+ matched = true;
1112
+ } else if (title.startsWith(word)) {
1113
+ score += 20;
1114
+ matched = true;
1115
+ } else if (title.includes(word)) {
1116
+ score += 12;
1117
+ matched = true;
1118
+ }
1119
+ if (hasDistinctSection) {
1120
+ if (section === word) {
1121
+ score += 22;
1122
+ matched = true;
1123
+ } else if (section.startsWith(word)) {
1124
+ score += 16;
1125
+ matched = true;
1126
+ } else if (section.includes(word)) {
1127
+ score += 10;
1128
+ matched = true;
1129
+ }
1130
+ }
1131
+ if (description.includes(word)) {
1132
+ score += 6;
1133
+ matched = true;
1134
+ }
1135
+ if (content.includes(word)) {
1136
+ score += 4;
1137
+ matched = true;
1138
+ }
1139
+ if (matched) matchedWords += 1;
1140
+ }
1141
+ if (words.length > 1) {
1142
+ if (hasDistinctSection && sectionTokens.length > 0 && words.every((word) => sectionTokens.includes(word))) score += 30;
1143
+ if (document.type === "page" && titleTokens.length > 0 && words.every((word) => titleTokens.includes(word))) score += 24;
1144
+ }
1145
+ if (matchedWords === words.length && words.length > 1) score += 20;
1146
+ if (score > 0 && document.type === "heading" && hasDistinctSection) score += 6;
1147
+ return score;
1148
+ }
1149
+ function buildSnippet(document, query) {
1150
+ const q = query.trim().toLowerCase();
1151
+ const sources = [normalizeWhitespace(stripMarkdownText(document.content)), normalizeWhitespace(stripMarkdownText(document.description ?? ""))].filter(Boolean);
1152
+ for (const source of sources) {
1153
+ if (!q) return clampSearchSnippet(source);
1154
+ const idx = source.toLowerCase().indexOf(q);
1155
+ if (idx === -1) continue;
1156
+ const start = Math.max(0, idx - 48);
1157
+ const end = Math.min(source.length, idx + q.length + 96);
1158
+ const prefix = start > 0 ? "..." : "";
1159
+ const suffix = end < source.length ? "..." : "";
1160
+ return clampSearchSnippet(`${prefix}${source.slice(start, end).trim()}${suffix}`);
1161
+ }
1162
+ return sources[0] ? clampSearchSnippet(sources[0]) : void 0;
1163
+ }
1164
+ function clampSearchSnippet(value) {
1165
+ if (value.length <= MAX_SEARCH_SNIPPET_CHARS) return value;
1166
+ return `${value.slice(0, MAX_SEARCH_SNIPPET_CHARS - 3).trimEnd()}...`;
1167
+ }
1168
+ function cleanSearchResultText(value) {
1169
+ if (!value) return void 0;
1170
+ return normalizeWhitespace(stripHtml(stripMarkdownText(value))) || void 0;
1171
+ }
1172
+ function trimTextToBytes(value, maxBytes) {
1173
+ if (maxBytes <= 0) return "";
1174
+ const encoder = new TextEncoder();
1175
+ if (encoder.encode(value).length <= maxBytes) return value;
1176
+ let low = 0;
1177
+ let high = value.length;
1178
+ let best = "";
1179
+ while (low <= high) {
1180
+ const mid = Math.floor((low + high) / 2);
1181
+ const next = `${value.slice(0, mid).trimEnd()}...`;
1182
+ if (encoder.encode(next).length <= maxBytes) {
1183
+ best = next;
1184
+ low = mid + 1;
1185
+ } else high = mid - 1;
1186
+ }
1187
+ return best;
1188
+ }
1189
+ function buildAlgoliaRecord(document) {
1190
+ const record = {
1191
+ objectID: document.id,
1192
+ id: document.id,
1193
+ url: document.url,
1194
+ title: document.title,
1195
+ section: document.section,
1196
+ content: document.content,
1197
+ description: document.description,
1198
+ type: document.type
1199
+ };
1200
+ const encoder = new TextEncoder();
1201
+ const sizeOf = (value) => encoder.encode(JSON.stringify(value)).length;
1202
+ if (sizeOf(record) <= ALGOLIA_MAX_RECORD_BYTES) return record;
1203
+ delete record.description;
1204
+ if (sizeOf(record) <= ALGOLIA_MAX_RECORD_BYTES) return record;
1205
+ const fixedBytes = sizeOf({
1206
+ ...record,
1207
+ content: ""
1208
+ });
1209
+ const remainingBytes = Math.max(ALGOLIA_MAX_RECORD_BYTES - fixedBytes - 32, 0);
1210
+ record.content = trimTextToBytes(document.content, remainingBytes);
1211
+ return record;
1212
+ }
1213
+ function createSimpleSearchAdapter() {
1214
+ return {
1215
+ name: "simple",
1216
+ async search(query, context) {
1217
+ const limit = query.limit ?? DEFAULT_SEARCH_LIMIT;
1218
+ return context.documents.map((document) => ({
1219
+ document,
1220
+ score: scoreDocument(query.query, document)
1221
+ })).filter((item) => item.score > 0).sort((a, b) => {
1222
+ if (b.score !== a.score) return b.score - a.score;
1223
+ return a.document.url.localeCompare(b.document.url);
1224
+ }).slice(0, limit).map(({ document, score }) => ({
1225
+ id: document.id,
1226
+ url: document.url,
1227
+ content: cleanSearchResultText(document.section ? `${document.title} — ${document.section}` : document.title) ?? (document.section ? `${document.title} — ${document.section}` : document.title),
1228
+ description: cleanSearchResultText(buildSnippet(document, query.query) ?? document.description),
1229
+ type: document.type,
1230
+ score,
1231
+ section: document.section
1232
+ }));
1233
+ }
1234
+ };
1235
+ }
1236
+ function normalizeDocsSearchConfig(search) {
1237
+ if (search === false) return {
1238
+ enabled: false,
1239
+ provider: "simple",
1240
+ maxResults: DEFAULT_SEARCH_LIMIT,
1241
+ chunking: { strategy: "section" }
1242
+ };
1243
+ if (!search || search === true) return {
1244
+ enabled: true,
1245
+ provider: "simple",
1246
+ maxResults: DEFAULT_SEARCH_LIMIT,
1247
+ chunking: { strategy: "section" },
1248
+ raw: typeof search === "object" ? search : void 0
1249
+ };
1250
+ const provider = search.provider ?? "simple";
1251
+ const maxResults = search.maxResults ?? DEFAULT_SEARCH_LIMIT;
1252
+ const chunking = search.chunking ?? { strategy: "section" };
1253
+ return {
1254
+ enabled: search.enabled ?? true,
1255
+ provider,
1256
+ maxResults,
1257
+ chunking,
1258
+ raw: search
1259
+ };
1260
+ }
1261
+ async function readResponseJson(response) {
1262
+ const text = await response.text();
1263
+ return text ? JSON.parse(text) : null;
1264
+ }
1265
+ async function readMcpResponsePayload(response) {
1266
+ const text = await response.text();
1267
+ if (!text) return null;
1268
+ if ((response.headers.get("content-type") ?? "").includes("application/json")) return JSON.parse(text);
1269
+ return normalizeMcpSsePayload(text);
1270
+ }
1271
+ function ensureOk(response, message) {
1272
+ if (response.ok) return;
1273
+ throw new Error(`${message} (${response.status} ${response.statusText})`);
1274
+ }
1275
+ function ensureJsonRpcOk(payload, message) {
1276
+ if (payload && typeof payload === "object" && "error" in payload && payload.error && typeof payload.error === "object" && "message" in payload.error) throw new Error(`${message}: ${String(payload.error.message)}`);
1277
+ }
1278
+ function resolveMcpEndpoint(endpoint) {
1279
+ if (/^https?:\/\//i.test(endpoint)) return endpoint;
1280
+ throw new Error("Relative MCP search endpoints must be resolved before creating the MCP adapter.");
1281
+ }
1282
+ function isDocsSearchResultType(value) {
1283
+ return value === "page" || value === "heading" || value === "text";
1284
+ }
1285
+ function mapMcpSearchResult(value) {
1286
+ if (!value || typeof value !== "object") return null;
1287
+ const item = value;
1288
+ const section = typeof item.section === "string" ? item.section : void 0;
1289
+ const title = typeof item.title === "string" ? item.title : void 0;
1290
+ const content = typeof item.content === "string" ? item.content : title ? section ? `${title} — ${section}` : title : void 0;
1291
+ const url = typeof item.url === "string" ? item.url : void 0;
1292
+ if (!content || !url) return null;
1293
+ return {
1294
+ id: typeof item.id === "string" ? item.id : typeof item.slug === "string" ? item.slug : url,
1295
+ url,
1296
+ content: cleanSearchResultText(content) ?? content,
1297
+ description: cleanSearchResultText(typeof item.description === "string" ? item.description : typeof item.excerpt === "string" ? item.excerpt : void 0) ?? void 0,
1298
+ type: isDocsSearchResultType(item.type) ? item.type : section ? "heading" : "page",
1299
+ score: typeof item.score === "number" ? item.score : void 0,
1300
+ section
1301
+ };
1302
+ }
1303
+ async function createOllamaEmbedding(text, config, signal) {
1304
+ const response = await fetch(`${(config.baseUrl ?? "http://127.0.0.1:11434").replace(/\/$/, "")}/api/embed`, {
1305
+ method: "POST",
1306
+ headers: { "Content-Type": "application/json" },
1307
+ body: JSON.stringify({
1308
+ model: config.model,
1309
+ input: text
1310
+ }),
1311
+ signal
1312
+ });
1313
+ ensureOk(response, "Failed to create Ollama embedding");
1314
+ const payload = await readResponseJson(response);
1315
+ if (Array.isArray(payload.embeddings?.[0])) return payload.embeddings[0];
1316
+ if (Array.isArray(payload.embedding)) return payload.embedding;
1317
+ throw new Error("Ollama embedding response did not include an embedding vector.");
1318
+ }
1319
+ function getTypesenseSearchBase(config) {
1320
+ return config.baseUrl.replace(/\/$/, "");
1321
+ }
1322
+ async function ensureTypesenseCollection(config, dimensions, signal) {
1323
+ const baseUrl = getTypesenseSearchBase(config);
1324
+ const headers = {
1325
+ "X-TYPESENSE-API-KEY": config.adminApiKey ?? config.apiKey,
1326
+ "Content-Type": "application/json"
1327
+ };
1328
+ const existing = await fetch(`${baseUrl}/collections/${encodeURIComponent(config.collection)}`, {
1329
+ headers,
1330
+ signal
1331
+ });
1332
+ if (existing.ok) return;
1333
+ if (existing.status !== 404) ensureOk(existing, "Failed to inspect Typesense collection");
1334
+ const fields = [
1335
+ {
1336
+ name: "id",
1337
+ type: "string"
1338
+ },
1339
+ {
1340
+ name: "url",
1341
+ type: "string"
1342
+ },
1343
+ {
1344
+ name: "title",
1345
+ type: "string"
1346
+ },
1347
+ {
1348
+ name: "section",
1349
+ type: "string",
1350
+ optional: true
1351
+ },
1352
+ {
1353
+ name: "content",
1354
+ type: "string"
1355
+ },
1356
+ {
1357
+ name: "description",
1358
+ type: "string",
1359
+ optional: true
1360
+ },
1361
+ {
1362
+ name: "type",
1363
+ type: "string"
1364
+ },
1365
+ {
1366
+ name: "locale",
1367
+ type: "string",
1368
+ optional: true
1369
+ },
1370
+ {
1371
+ name: "framework",
1372
+ type: "string",
1373
+ optional: true
1374
+ },
1375
+ {
1376
+ name: "version",
1377
+ type: "string",
1378
+ optional: true
1379
+ },
1380
+ {
1381
+ name: "tags",
1382
+ type: "string[]",
1383
+ optional: true
1384
+ }
1385
+ ];
1386
+ if (config.embeddings && dimensions) fields.push({
1387
+ name: "embedding",
1388
+ type: "float[]",
1389
+ num_dim: dimensions,
1390
+ optional: true
1391
+ });
1392
+ ensureOk(await fetch(`${baseUrl}/collections`, {
1393
+ method: "POST",
1394
+ headers,
1395
+ body: JSON.stringify({
1396
+ name: config.collection,
1397
+ fields
1398
+ }),
1399
+ signal
1400
+ }), "Failed to create Typesense collection");
1401
+ }
1402
+ function createTypesenseSearchAdapter(config) {
1403
+ return {
1404
+ name: "typesense",
1405
+ async index(context) {
1406
+ const adminApiKey = config.adminApiKey ?? config.apiKey;
1407
+ const docsForImport = await Promise.all(context.documents.map(async (document) => {
1408
+ const next = {
1409
+ id: document.id,
1410
+ url: document.url,
1411
+ title: document.title,
1412
+ section: document.section,
1413
+ content: document.content,
1414
+ description: document.description,
1415
+ type: document.type,
1416
+ locale: document.locale,
1417
+ framework: document.framework,
1418
+ version: document.version,
1419
+ tags: document.tags
1420
+ };
1421
+ if (config.mode === "hybrid" && config.embeddings) next.embedding = await createOllamaEmbedding(`${document.title}\n${document.section ?? ""}\n${document.content}`.trim(), config.embeddings, context.signal);
1422
+ return next;
1423
+ }));
1424
+ if (docsForImport.length === 0) return;
1425
+ await ensureTypesenseCollection(config, Array.isArray(docsForImport[0]?.embedding) ? docsForImport[0].embedding.length : void 0, context.signal);
1426
+ ensureOk(await fetch(`${getTypesenseSearchBase(config)}/collections/${encodeURIComponent(config.collection)}/documents/import?action=upsert`, {
1427
+ method: "POST",
1428
+ headers: {
1429
+ "X-TYPESENSE-API-KEY": adminApiKey,
1430
+ "Content-Type": "text/plain"
1431
+ },
1432
+ body: docsForImport.map((document) => JSON.stringify(document)).join("\n"),
1433
+ signal: context.signal
1434
+ }), "Failed to sync documents to Typesense");
1435
+ },
1436
+ async search(query, context) {
1437
+ const params = new URLSearchParams({
1438
+ q: query.query,
1439
+ query_by: (config.queryBy ?? [
1440
+ "title",
1441
+ "section",
1442
+ "content",
1443
+ "description"
1444
+ ]).join(","),
1445
+ per_page: String(query.limit ?? config.maxResults ?? DEFAULT_SEARCH_LIMIT),
1446
+ prioritize_exact_match: "true",
1447
+ num_typos: "2",
1448
+ highlight_fields: "content,title,section,description"
1449
+ });
1450
+ if (config.mode === "hybrid" && config.embeddings) {
1451
+ const vector = await createOllamaEmbedding(query.query, config.embeddings, context.signal);
1452
+ params.set("vector_query", `embedding:([${vector.join(",")}],k:${Math.max((query.limit ?? 10) * 4, 20)})`);
1453
+ }
1454
+ const response = await fetch(`${getTypesenseSearchBase(config)}/collections/${encodeURIComponent(config.collection)}/documents/search?${params.toString()}`, {
1455
+ headers: { "X-TYPESENSE-API-KEY": config.apiKey },
1456
+ signal: context.signal
1457
+ });
1458
+ ensureOk(response, "Typesense search failed");
1459
+ return ((await readResponseJson(response)).hits ?? []).map((hit) => {
1460
+ const document = hit.document ?? {};
1461
+ const section = typeof document.section === "string" ? document.section : void 0;
1462
+ const content = typeof document.title === "string" ? section ? `${document.title} — ${section}` : document.title : typeof document.content === "string" ? document.content : "Untitled result";
1463
+ 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);
1464
+ return {
1465
+ id: typeof document.id === "string" ? document.id : String(document.url ?? content),
1466
+ url: typeof document.url === "string" ? document.url : "/docs",
1467
+ content: cleanSearchResultText(content) ?? content,
1468
+ description: cleanSearchResultText(description),
1469
+ type: typeof document.type === "string" && [
1470
+ "page",
1471
+ "heading",
1472
+ "text"
1473
+ ].includes(document.type) ? document.type : section ? "heading" : "page",
1474
+ score: hit.text_match,
1475
+ section
1476
+ };
1477
+ });
1478
+ }
1479
+ };
1480
+ }
1481
+ function resolveSearchRequestConfig(search, requestUrl) {
1482
+ if (!search || search === true || typeof search !== "object" || search.provider !== "mcp") return search;
1483
+ if (!requestUrl) return search;
1484
+ const resolvedEndpoint = new URL(search.endpoint, requestUrl);
1485
+ const usesDefaultSearchTool = (search.toolName ?? "search_docs") === "search_docs";
1486
+ const isSameOrigin = resolvedEndpoint.origin === new URL(requestUrl).origin;
1487
+ return {
1488
+ ...search,
1489
+ endpoint: resolvedEndpoint.toString(),
1490
+ forwardAudience: search.forwardAudience ?? (usesDefaultSearchTool && isSameOrigin)
1491
+ };
1492
+ }
1493
+ /**
1494
+ * Resolve the public search audience without allowing malformed values to opt into agent content.
1495
+ * Human search remains the default for omitted, legacy, and unknown query values.
1496
+ */
1497
+ function resolveDocsSearchAudience(value) {
1498
+ return value === "agent" ? "agent" : "human";
1499
+ }
1500
+ function resolveAskAISearchRequestConfig(options) {
1501
+ if (!options.useMcp) return resolveSearchRequestConfig(options.search, options.requestUrl);
1502
+ if (typeof options.useMcp === "object") return resolveSearchRequestConfig({
1503
+ ...options.useMcp,
1504
+ provider: "mcp"
1505
+ }, options.requestUrl);
1506
+ if (options.mcpEnabled === false || options.mcpSearchEnabled === false || !options.mcpEndpoint) return resolveSearchRequestConfig(options.search, options.requestUrl);
1507
+ return resolveSearchRequestConfig({
1508
+ provider: "mcp",
1509
+ endpoint: options.mcpEndpoint
1510
+ }, options.requestUrl);
1511
+ }
1512
+ function createMcpSearchAdapter(config) {
1513
+ return {
1514
+ name: "mcp",
1515
+ async search(query, context) {
1516
+ const endpoint = resolveMcpEndpoint(config.endpoint);
1517
+ const protocolVersion = config.protocolVersion ?? DEFAULT_MCP_PROTOCOL_VERSION;
1518
+ const toolName = config.toolName ?? "search_docs";
1519
+ const forwardAudience = config.forwardAudience === true;
1520
+ const audience = resolveDocsSearchAudience(query.audience);
1521
+ const baseHeaders = config.headers ?? {};
1522
+ if (audience === "human" && !forwardAudience) throw new Error("MCP human-projection search requires forwardAudience: true on an audience-aware tool.");
1523
+ const initializeResponse = await fetch(endpoint, {
1524
+ method: "POST",
1525
+ headers: {
1526
+ ...baseHeaders,
1527
+ "Content-Type": "application/json",
1528
+ accept: "application/json, text/event-stream",
1529
+ "mcp-protocol-version": protocolVersion
1530
+ },
1531
+ body: JSON.stringify({
1532
+ jsonrpc: "2.0",
1533
+ id: 1,
1534
+ method: "initialize",
1535
+ params: {
1536
+ protocolVersion,
1537
+ capabilities: {},
1538
+ clientInfo: {
1539
+ name: "@farming-labs/docs-search",
1540
+ version: "0.1.2"
1541
+ }
1542
+ }
1543
+ }),
1544
+ signal: context.signal
1545
+ });
1546
+ const initializePayload = await readMcpResponsePayload(initializeResponse);
1547
+ ensureOk(initializeResponse, "MCP search initialization failed");
1548
+ ensureJsonRpcOk(initializePayload, "MCP search initialization failed");
1549
+ const sessionId = initializeResponse.headers.get("mcp-session-id") ?? void 0;
1550
+ try {
1551
+ const searchResponse = await fetch(endpoint, {
1552
+ method: "POST",
1553
+ headers: {
1554
+ ...baseHeaders,
1555
+ "Content-Type": "application/json",
1556
+ accept: "application/json, text/event-stream",
1557
+ "mcp-protocol-version": protocolVersion,
1558
+ ...sessionId ? { "mcp-session-id": sessionId } : {}
1559
+ },
1560
+ body: JSON.stringify({
1561
+ jsonrpc: "2.0",
1562
+ id: 2,
1563
+ method: "tools/call",
1564
+ params: {
1565
+ name: toolName,
1566
+ arguments: {
1567
+ query: query.query,
1568
+ limit: query.limit ?? config.maxResults ?? DEFAULT_SEARCH_LIMIT,
1569
+ locale: query.locale,
1570
+ ...forwardAudience ? { audience } : {}
1571
+ }
1572
+ }
1573
+ }),
1574
+ signal: context.signal
1575
+ });
1576
+ const payload = await readMcpResponsePayload(searchResponse);
1577
+ ensureOk(searchResponse, "MCP search request failed");
1578
+ ensureJsonRpcOk(payload, "MCP search request failed");
1579
+ const resultText = payload && typeof payload === "object" && "result" in payload && payload.result && typeof payload.result === "object" && "content" in payload.result && Array.isArray(payload.result.content) && typeof payload.result.content[0]?.text === "string" ? payload.result.content[0].text : null;
1580
+ if (!resultText) return [];
1581
+ const parsed = JSON.parse(resultText);
1582
+ return (Array.isArray(parsed) ? parsed : Array.isArray(parsed.results) ? parsed.results : Array.isArray(parsed.pages) ? parsed.pages : []).map(mapMcpSearchResult).filter((result) => Boolean(result));
1583
+ } finally {
1584
+ if (sessionId) {
1585
+ const cleanupController = new AbortController();
1586
+ const cleanupTimeout = setTimeout(() => cleanupController.abort(), MCP_SESSION_CLEANUP_TIMEOUT_MS);
1587
+ try {
1588
+ await fetch(endpoint, {
1589
+ method: "DELETE",
1590
+ headers: {
1591
+ ...baseHeaders,
1592
+ "mcp-protocol-version": protocolVersion,
1593
+ "mcp-session-id": sessionId
1594
+ },
1595
+ signal: cleanupController.signal
1596
+ });
1597
+ } catch {} finally {
1598
+ clearTimeout(cleanupTimeout);
1599
+ }
1600
+ }
1601
+ }
1602
+ }
1603
+ };
1604
+ }
1605
+ function getAlgoliaBase(config) {
1606
+ return `https://${config.appId}-dsn.algolia.net`;
1607
+ }
1608
+ function createAlgoliaSearchAdapter(config) {
1609
+ return {
1610
+ name: "algolia",
1611
+ async index(context) {
1612
+ if (!config.adminApiKey) return;
1613
+ ensureOk(await fetch(`${getAlgoliaBase(config)}/1/indexes/${encodeURIComponent(config.indexName)}/batch`, {
1614
+ method: "POST",
1615
+ headers: {
1616
+ "Content-Type": "application/json",
1617
+ "X-Algolia-Application-Id": config.appId,
1618
+ "X-Algolia-API-Key": config.adminApiKey
1619
+ },
1620
+ body: JSON.stringify({ requests: context.documents.map((document) => ({
1621
+ action: "addObject",
1622
+ body: buildAlgoliaRecord(document)
1623
+ })) }),
1624
+ signal: context.signal
1625
+ }), "Failed to sync documents to Algolia");
1626
+ },
1627
+ async search(query, context) {
1628
+ const response = await fetch(`${getAlgoliaBase(config)}/1/indexes/${encodeURIComponent(config.indexName)}/query`, {
1629
+ method: "POST",
1630
+ headers: {
1631
+ "Content-Type": "application/json",
1632
+ "X-Algolia-Application-Id": config.appId,
1633
+ "X-Algolia-API-Key": config.searchApiKey
1634
+ },
1635
+ body: JSON.stringify({
1636
+ query: query.query,
1637
+ hitsPerPage: query.limit ?? config.maxResults ?? DEFAULT_SEARCH_LIMIT,
1638
+ attributesToSnippet: ["content:20"]
1639
+ }),
1640
+ signal: context.signal
1641
+ });
1642
+ ensureOk(response, "Algolia search failed");
1643
+ return ((await readResponseJson(response)).hits ?? []).map((hit) => {
1644
+ const title = typeof hit.title === "string" ? hit.title : "Untitled result";
1645
+ const section = typeof hit.section === "string" ? hit.section : void 0;
1646
+ return {
1647
+ id: hit.objectID ?? String(hit.url ?? title),
1648
+ url: typeof hit.url === "string" ? hit.url : "/docs",
1649
+ content: cleanSearchResultText(section ? `${title} — ${section}` : title) ?? title,
1650
+ description: cleanSearchResultText(hit._snippetResult?.content?.value ?? hit._snippetResult?.description?.value ?? (typeof hit.description === "string" ? hit.description : void 0)),
1651
+ type: typeof hit.type === "string" && [
1652
+ "page",
1653
+ "heading",
1654
+ "text"
1655
+ ].includes(hit.type) ? hit.type : section ? "heading" : "page",
1656
+ score: hit._rankingInfo?.nbTypos != null ? 100 - hit._rankingInfo.nbTypos : void 0,
1657
+ section
1658
+ };
1659
+ });
1660
+ }
1661
+ };
1662
+ }
1663
+ async function resolveSearchAdapter(search, context) {
1664
+ const raw = search.raw;
1665
+ if (search.provider === "custom" && raw?.provider === "custom") return typeof raw.adapter === "function" ? await raw.adapter(context) : raw.adapter;
1666
+ if (search.provider === "typesense" && raw?.provider === "typesense") return createTypesenseSearchAdapter(raw);
1667
+ if (search.provider === "mcp" && raw?.provider === "mcp") return createMcpSearchAdapter(raw);
1668
+ if (search.provider === "algolia" && raw?.provider === "algolia") return createAlgoliaSearchAdapter(raw);
1669
+ return createSimpleSearchAdapter();
1670
+ }
1671
+ function shouldSyncOnSearch(search) {
1672
+ const raw = search.raw;
1673
+ if (search.provider === "algolia" && raw?.provider === "algolia") return (raw.syncOnSearch ?? Boolean(raw.adminApiKey)) && Boolean(raw.adminApiKey);
1674
+ if (search.provider === "typesense" && raw?.provider === "typesense") return (raw.syncOnSearch ?? Boolean(raw.adminApiKey)) && Boolean(raw.adminApiKey);
1675
+ return false;
1676
+ }
1677
+ function getSyncKey(search, context) {
1678
+ const raw = search.raw;
1679
+ if (search.provider === "algolia" && raw?.provider === "algolia") return `algolia:${raw.appId}:${raw.indexName}:${context.locale ?? "__default__"}`;
1680
+ if (search.provider === "typesense" && raw?.provider === "typesense") return `typesense:${raw.baseUrl}:${raw.collection}:${context.locale ?? "__default__"}`;
1681
+ if (search.provider === "mcp" && raw?.provider === "mcp") return `mcp:${raw.endpoint}:${context.locale ?? "__default__"}`;
1682
+ return `${search.provider}:${context.locale ?? "__default__"}`;
1683
+ }
1684
+ async function maybeSyncSearchIndex(adapter, search, context) {
1685
+ if (!shouldSyncOnSearch(search) || typeof adapter.index !== "function") return;
1686
+ const syncKey = getSyncKey(search, context);
1687
+ if (syncedIndexes.has(syncKey)) return;
1688
+ await adapter.index(context);
1689
+ syncedIndexes.add(syncKey);
1690
+ }
1691
+ async function performDocsSearch(options) {
1692
+ const search = normalizeDocsSearchConfig(options.search);
1693
+ if (!search.enabled) return [];
1694
+ const audience = resolveDocsSearchAudience(options.audience);
1695
+ const documents = buildDocsSearchDocuments(options.pages, search.chunking, audience);
1696
+ const context = {
1697
+ pages: options.pages,
1698
+ documents,
1699
+ audience,
1700
+ locale: options.locale,
1701
+ pathname: options.pathname,
1702
+ siteTitle: options.siteTitle,
1703
+ signal: options.signal
1704
+ };
1705
+ const query = {
1706
+ query: options.query,
1707
+ limit: options.limit ?? search.maxResults,
1708
+ locale: options.locale,
1709
+ pathname: options.pathname,
1710
+ audience
1711
+ };
1712
+ try {
1713
+ const adapter = await resolveSearchAdapter(search, context);
1714
+ await maybeSyncSearchIndex(adapter, search, audience === "agent" ? {
1715
+ ...context,
1716
+ documents: buildDocsSearchDocuments(options.pages, search.chunking, "human"),
1717
+ audience: "human"
1718
+ } : context);
1719
+ const results = await adapter.search(query, context);
1720
+ if (search.provider === "simple") return results;
1721
+ const localAudienceProjectionResults = buildAudienceProjectionSearchResults(documents, options.query);
1722
+ const localPagePaths = new Set(options.pages.map((page) => normalizeUrlPathname(page.url)));
1723
+ const preserveUnmatched = audience === "human" || search.provider === "mcp" ? (result) => shouldPreserveUnmatchedExternalResult({
1724
+ result,
1725
+ localPagePaths,
1726
+ baseUrl: options.baseUrl
1727
+ }) : void 0;
1728
+ const safeAdapterResults = sanitizeExternalAudienceSearchResults(results.filter((result) => !hasOppositeAudienceEvidence({
1729
+ result,
1730
+ pages: options.pages,
1731
+ query: options.query,
1732
+ audience,
1733
+ baseUrl: options.baseUrl,
1734
+ strictExternalOrigins: options.strictExternalOrigins
1735
+ })), localAudienceProjectionResults, options.baseUrl, options.strictExternalOrigins, preserveUnmatched);
1736
+ if (options.supplementExternalResults === false) return safeAdapterResults.slice(0, query.limit ?? search.maxResults ?? DEFAULT_SEARCH_LIMIT);
1737
+ const simpleAudienceResults = audience === "agent" || results.length === 0 || safeAdapterResults.length < results.length ? await createSimpleSearchAdapter().search(query, context) : [];
1738
+ const combinedResults = mergeSearchResults([
1739
+ buildExactPageSearchResults(options.query, options.pages, audience),
1740
+ safeAdapterResults,
1741
+ simpleAudienceResults
1742
+ ], audience === "agent" ? (result) => getAskAIResultKey(result, options.baseUrl, options.strictExternalOrigins) : void 0);
1743
+ return prioritizeLiteralInsideResults(options.query, combinedResults).slice(0, query.limit ?? search.maxResults ?? DEFAULT_SEARCH_LIMIT);
1744
+ } catch (error) {
1745
+ if (options.failureMode === "throw") throw error;
1746
+ return createSimpleSearchAdapter().search(query, context);
1747
+ }
1748
+ }
1749
+ async function buildDocsAskAIContext(options) {
1750
+ const limit = options.limit ?? 5;
1751
+ const searchLimit = Math.max(limit * 2, limit);
1752
+ const initialSearch = options.search === false ? true : options.search;
1753
+ const primarySearch = normalizeDocsSearchConfig(initialSearch).enabled ? initialSearch : true;
1754
+ const searchResults = await performDocsSearch({
1755
+ pages: options.pages,
1756
+ query: options.query,
1757
+ search: primarySearch,
1758
+ audience: "agent",
1759
+ locale: options.locale,
1760
+ pathname: options.pathname,
1761
+ siteTitle: options.siteTitle,
1762
+ baseUrl: options.baseUrl,
1763
+ limit: searchLimit,
1764
+ failureMode: options.searchFailureMode,
1765
+ strictExternalOrigins: options.strictExternalOrigins,
1766
+ signal: options.signal
1767
+ });
1768
+ const seen = /* @__PURE__ */ new Set();
1769
+ const maxResultChars = options.maxResultChars ?? DEFAULT_ASK_AI_RESULT_CHARS;
1770
+ const rankedResults = searchResults.map((result, index) => {
1771
+ const formatted = formatAskAIContextResult({
1772
+ result,
1773
+ page: findPageForSearchResult(options.pages, result, options.baseUrl, options.strictExternalOrigins),
1774
+ maxChars: maxResultChars,
1775
+ baseUrl: options.baseUrl
1776
+ });
1777
+ return {
1778
+ result,
1779
+ formatted,
1780
+ index,
1781
+ rank: rankAskAIContextResult(options.query, formatted)
1782
+ };
1783
+ }).sort((a, b) => b.rank - a.rank || a.index - b.index);
1784
+ const formattedResults = rankedResults.map((item) => item.formatted);
1785
+ const sectionResultPaths = new Set(formattedResults.filter((result) => result.section).map((result) => getAskAIResultPageKey(result.url, options.baseUrl, options.strictExternalOrigins)));
1786
+ const results = formattedResults.filter((result) => result.section || !sectionResultPaths.has(getAskAIResultPageKey(result.url, options.baseUrl, options.strictExternalOrigins))).filter((result) => {
1787
+ const key = getAskAIResultKey(result, options.baseUrl, options.strictExternalOrigins);
1788
+ if (seen.has(key)) return false;
1789
+ seen.add(key);
1790
+ return result.contextContent.length > 0;
1791
+ }).slice(0, limit);
1792
+ const maxContextChars = options.maxContextChars ?? DEFAULT_ASK_AI_CONTEXT_CHARS;
1793
+ const blocks = [];
1794
+ let usedChars = 0;
1795
+ for (const result of results) {
1796
+ const block = buildAskAIContextBlock(result);
1797
+ const separatorChars = blocks.length === 0 ? 0 : 7;
1798
+ if (usedChars + separatorChars + block.length > maxContextChars) {
1799
+ const remaining = maxContextChars - usedChars - separatorChars;
1800
+ if (remaining > 400) blocks.push(clampText(block, remaining));
1801
+ break;
1802
+ }
1803
+ blocks.push(block);
1804
+ usedChars += separatorChars + block.length;
1805
+ }
1806
+ const context = blocks.join("\n\n---\n\n");
1807
+ return {
1808
+ context,
1809
+ blocks: blocks.map((text, index) => ({
1810
+ text,
1811
+ result: results[index]
1812
+ })),
1813
+ results: results.slice(0, blocks.length),
1814
+ searchResults: rankedResults.map((item) => item.result),
1815
+ packageHints: inferDocsAskAIPackageHints(context)
1816
+ };
1817
+ }
1818
+ function createCustomSearchAdapter(adapter) {
1819
+ return {
1820
+ provider: "custom",
1821
+ adapter
1822
+ };
1823
+ }
1824
+
1825
+ //#endregion
1826
+ export { emitDocsTelemetryProjectEvent as A, applySidebarFolderIndexBehavior as C, emitDocsTelemetryAgentSurfaceEvent as D, resolveSidebarFolderIndexBehaviorForPath as E, resolveDocsTelemetryConfig as F, inferDocsTelemetryAgentSurface as M, isLocalDocsTelemetryOrigin as N, emitDocsTelemetryEvent as O, normalizeDocsTelemetryOrigin as P, stripGeneratedAgentProvenance as S, resolveSidebarFolderIndexBehavior as T, GENERATED_AGENT_PROVENANCE_VERSION as _, createMcpSearchAdapter as a, parseGeneratedAgentDocument as b, formatDocsAskAIPackageHints as c, resolveAskAISearchRequestConfig as d, resolveDocsSearchAudience as f, GENERATED_AGENT_PROVENANCE_MARKER as g, parseDocsMarkdownSections as h, createCustomSearchAdapter as i, getDocsTelemetryFeatures as j, emitDocsTelemetryMcpToolEvent as k, inferDocsAskAIPackageHints as l, findDocsMarkdownSection as m, buildDocsSearchDocuments as n, createSimpleSearchAdapter as o, resolveSearchRequestConfig as p, createAlgoliaSearchAdapter as r, createTypesenseSearchAdapter as s, buildDocsAskAIContext as t, performDocsSearch as u, hashGeneratedAgentContent as v, resolvePageSidebarFolderIndexBehavior as w, serializeGeneratedAgentDocument as x, normalizeGeneratedAgentContent as y };