@farming-labs/docs 0.2.71 → 0.2.72

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 (51) hide show
  1. package/dist/{agent-bof3qzKZ.mjs → agent-DbDfIqhe.mjs} +3 -3
  2. package/dist/{agent-evals-Cr9TAkmB.mjs → agent-evals-CjYzCQMZ.mjs} +90 -11
  3. package/dist/{agent-export-7Ws4KqDE.mjs → agent-export-CpQhD8eA.mjs} +9 -9
  4. package/dist/{agent-FIyl4-Oa.mjs → agent-pS2nNQS3.mjs} +31 -4
  5. package/dist/agent-skills-bundle.d.mts +1 -1
  6. package/dist/{agent-skills-server-DTwblsLu.d.mts → agent-skills-server-DZnNB4_S.d.mts} +2 -2
  7. package/dist/agent-skills-vite.d.mts +3 -3
  8. package/dist/agent-skills-vite.mjs +1 -1
  9. package/dist/{agents-Be2ntaFr.mjs → agents-B-1h9Jlm.mjs} +8 -8
  10. package/dist/cli/index.mjs +29 -29
  11. package/dist/client/react.d.mts +1 -1
  12. package/dist/{cloud-pdNC-tyj.mjs → cloud-CyysfDny.mjs} +3 -3
  13. package/dist/{cloud-ask-ai-BfnX18rI.d.mts → cloud-ask-ai-D9gIN4Vr.d.mts} +1 -1
  14. package/dist/{codeblocks-CFuurVIH.mjs → codeblocks-DAj7EAwt.mjs} +2 -2
  15. package/dist/{dev-Cc1cFFj-.mjs → dev-qE7MYEh9.mjs} +2 -2
  16. package/dist/docs-cloud-server.d.mts +2 -2
  17. package/dist/docs-cloud-server.mjs +1 -1
  18. package/dist/{doctor-DoPAofVP.mjs → doctor-CBy8-tPu.mjs} +13 -13
  19. package/dist/{downgrade-w7e6Se0L.mjs → downgrade-DCxezPZI.mjs} +2 -2
  20. package/dist/{golden-evaluations-CPGGnXOV.mjs → golden-evaluations-DGg_eHIY.mjs} +23 -3
  21. package/dist/index.d.mts +25 -4
  22. package/dist/index.mjs +6 -6
  23. package/dist/{init-DAkI0Lkf.mjs → init-p1MHf06G.mjs} +3 -3
  24. package/dist/{mcp-BLynUzE7.mjs → mcp-BNK45dzD.mjs} +7 -7
  25. package/dist/mcp.d.mts +7 -2
  26. package/dist/mcp.mjs +122 -18
  27. package/dist/{metadata-bw8k-9x-.mjs → metadata-D6qlWDu-.mjs} +1 -1
  28. package/dist/{package-version-n5AFur8a.mjs → package-version-BXHcIm1T.mjs} +1 -1
  29. package/dist/{reading-time-DtNegNiq.mjs → reading-time-DvTuW2Iy.mjs} +2 -2
  30. package/dist/{review-NFnYbMRI.mjs → review-Cace7A2c.mjs} +8 -8
  31. package/dist/{robots-DOqBwQw8.mjs → robots-C2SindI5.mjs} +1 -1
  32. package/dist/{robots-B3e-aWPy.mjs → robots-DmE_wXvG.mjs} +4 -4
  33. package/dist/{search-CozHzW7v.d.mts → search-1aftosa5.d.mts} +24 -9
  34. package/dist/{search-x5O0-9X3.mjs → search-BbArHODP.mjs} +691 -26
  35. package/dist/{search-DOGUUZvs.mjs → search-DB3svkNQ.mjs} +7 -7
  36. package/dist/server.d.mts +13 -5
  37. package/dist/server.mjs +8 -8
  38. package/dist/{sitemap-BpMKdDUd.mjs → sitemap-DPTLh_26.mjs} +8 -8
  39. package/dist/{sitemap-server-D_hayOp6.mjs → sitemap-server-PUoOhplO.mjs} +1 -1
  40. package/dist/{standards-discovery-C0CIC4QI.d.mts → standards-discovery-DRJ10g7B.d.mts} +1 -1
  41. package/dist/{types-BQC05SQm.d.mts → types-B9DNNAdT.d.mts} +49 -1
  42. package/dist/{upgrade-oz-GChgt.mjs → upgrade-DDJQ5l6n.mjs} +2 -2
  43. package/package.json +1 -1
  44. package/dist/agent-scope-C_U--OZ7.mjs +0 -283
  45. /package/dist/{agent-skills-server-B4u3qy-1.mjs → agent-skills-server-BPVv4gMg.mjs} +0 -0
  46. /package/dist/{cloud-ask-ai-sbpjOR2K.mjs → cloud-ask-ai-hnJfj8-X.mjs} +0 -0
  47. /package/dist/{code-blocks-DnNVNK2M.mjs → code-blocks-qe0T8-xe.mjs} +0 -0
  48. /package/dist/{config-Wcdj-D0a.mjs → config-z7_R0f86.mjs} +0 -0
  49. /package/dist/{errors-DbOhkE1h.mjs → errors-Cc_qplZU.mjs} +0 -0
  50. /package/dist/{templates-C6avM0U3.mjs → templates-BJnf6B8l.mjs} +0 -0
  51. /package/dist/{utils-DpiIioYb.mjs → utils-C4RC8Zq_.mjs} +0 -0
@@ -1,4 +1,4 @@
1
- import { At as resolveDocsAudienceMdxContent, G as isDocsAgentDiscoveryRequest, Gt as findDocsMarkdownSection, K as isDocsAgentsRequest, Kt as parseDocsMarkdownSections, Q as isDocsSkillRequest, Qt as PAGE_AGENT_CONTRACT_START_MARKER, Yt as PAGE_AGENT_CONTRACT_END_MARKER, _t as resolveDocsMarkdownRequest, dt as resolveDocsAgentsFormat, lt as resolveDocsAgentFeedbackConfig, on as upsertPageAgentContractMarkdown, pt as resolveDocsLlmsTxtRequest, rn as renderPageAgentContractMarkdown, ut as resolveDocsAgentFeedbackRequest, xt as resolveDocsSkillFormat } from "./agent-FIyl4-Oa.mjs";
1
+ import { At as resolveDocsAudienceMdxContent, G as isDocsAgentDiscoveryRequest, Gt as findDocsMarkdownSection, K as isDocsAgentsRequest, Kt as parseDocsMarkdownSections, Q as isDocsSkillRequest, Qt as PAGE_AGENT_CONTRACT_START_MARKER, Yt as PAGE_AGENT_CONTRACT_END_MARKER, _t as resolveDocsMarkdownRequest, dt as resolveDocsAgentsFormat, lt as resolveDocsAgentFeedbackConfig, on as upsertPageAgentContractMarkdown, pt as resolveDocsLlmsTxtRequest, rn as renderPageAgentContractMarkdown, ut as resolveDocsAgentFeedbackRequest, xt as resolveDocsSkillFormat } from "./agent-pS2nNQS3.mjs";
2
2
 
3
3
  //#region src/telemetry.ts
4
4
  const DOCS_PACKAGE_NAME = "@farming-labs/docs";
@@ -457,6 +457,326 @@ function serializeGeneratedAgentDocument(content, provenance) {
457
457
  ].join("\n")}\n`;
458
458
  }
459
459
 
460
+ //#endregion
461
+ //#region src/agent-scope.ts
462
+ /** Normalize framework aliases used by page metadata, agent contracts, MCP, and evaluations. */
463
+ function normalizeAgentFramework(value) {
464
+ const normalized = value.toLowerCase().replace(/[^a-z0-9]/gu, "");
465
+ if ([
466
+ "next",
467
+ "nextjs",
468
+ "nextjsapp",
469
+ "reactnext"
470
+ ].includes(normalized)) return "nextjs";
471
+ if ([
472
+ "tanstack",
473
+ "tanstackstart",
474
+ "start"
475
+ ].includes(normalized)) return "tanstackstart";
476
+ if (["svelte", "sveltekit"].includes(normalized)) return "sveltekit";
477
+ if (["nuxt", "nuxtjs"].includes(normalized)) return "nuxt";
478
+ return normalized;
479
+ }
480
+ function normalizeAgentLocale(value) {
481
+ return value.trim().toLowerCase().replace(/_/gu, "-");
482
+ }
483
+ function normalizeAgentVersion(value) {
484
+ return value.trim().toLowerCase().replace(/^v(?=\d)/u, "");
485
+ }
486
+ function normalizeAgentScopeValues(value) {
487
+ const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
488
+ return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean)));
489
+ }
490
+ function compareVersions(left, right) {
491
+ for (let index = 0; index < 3; index += 1) if (left[index] !== right[index]) return left[index] > right[index] ? 1 : -1;
492
+ return 0;
493
+ }
494
+ function parseExactVersion(value) {
495
+ const match = normalizeAgentVersion(value).match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-[0-9a-z.-]+)?(?:\+[0-9a-z.-]+)?$/iu);
496
+ if (!match) return void 0;
497
+ return [
498
+ Number(match[1]),
499
+ Number(match[2] ?? 0),
500
+ Number(match[3] ?? 0)
501
+ ];
502
+ }
503
+ function parseVersionOperand(value) {
504
+ const normalized = normalizeAgentVersion(value);
505
+ if (normalized === "*" || normalized === "x") return {
506
+ version: [
507
+ 0,
508
+ 0,
509
+ 0
510
+ ],
511
+ components: 0,
512
+ wildcard: true
513
+ };
514
+ const match = normalized.match(/^(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?$/iu);
515
+ if (!match) return void 0;
516
+ const raw = [
517
+ match[1],
518
+ match[2],
519
+ match[3]
520
+ ];
521
+ const wildcardIndex = raw.findIndex((part, index) => index > 0 && (part?.toLowerCase() === "x" || part === "*"));
522
+ if (wildcardIndex >= 0 && raw.slice(wildcardIndex + 1).some((part) => part && part.toLowerCase() !== "x" && part !== "*")) return;
523
+ const components = wildcardIndex >= 0 ? wildcardIndex : raw.filter(Boolean).length;
524
+ return {
525
+ version: [
526
+ Number(raw[0]),
527
+ Number(raw[1] ?? 0) || 0,
528
+ Number(raw[2] ?? 0) || 0
529
+ ],
530
+ components,
531
+ wildcard: wildcardIndex >= 0
532
+ };
533
+ }
534
+ function nextVersionBoundary(operand) {
535
+ const [major, minor] = operand.version;
536
+ if (operand.components <= 0) return void 0;
537
+ if (operand.components === 1) return [
538
+ major + 1,
539
+ 0,
540
+ 0
541
+ ];
542
+ return [
543
+ major,
544
+ minor + 1,
545
+ 0
546
+ ];
547
+ }
548
+ function operandRange(operand) {
549
+ if (operand.components === 0) return {
550
+ minimumInclusive: true,
551
+ maximumInclusive: false
552
+ };
553
+ if (operand.components < 3 || operand.wildcard) return {
554
+ minimum: operand.version,
555
+ minimumInclusive: true,
556
+ maximum: nextVersionBoundary(operand),
557
+ maximumInclusive: false
558
+ };
559
+ return {
560
+ minimum: operand.version,
561
+ minimumInclusive: true,
562
+ maximum: operand.version,
563
+ maximumInclusive: true
564
+ };
565
+ }
566
+ function caretRange(operand) {
567
+ const [major, minor, patch] = operand.version;
568
+ let maximum;
569
+ if (major > 0) maximum = [
570
+ major + 1,
571
+ 0,
572
+ 0
573
+ ];
574
+ else if (operand.components <= 1) maximum = [
575
+ 1,
576
+ 0,
577
+ 0
578
+ ];
579
+ else if (minor > 0 || operand.components === 2) maximum = [
580
+ 0,
581
+ minor + 1,
582
+ 0
583
+ ];
584
+ else maximum = [
585
+ 0,
586
+ 0,
587
+ patch + 1
588
+ ];
589
+ return {
590
+ minimum: operand.version,
591
+ minimumInclusive: true,
592
+ maximum,
593
+ maximumInclusive: false
594
+ };
595
+ }
596
+ function tildeRange(operand) {
597
+ const [major, minor] = operand.version;
598
+ const maximum = operand.components <= 1 ? [
599
+ major + 1,
600
+ 0,
601
+ 0
602
+ ] : [
603
+ major,
604
+ minor + 1,
605
+ 0
606
+ ];
607
+ return {
608
+ minimum: operand.version,
609
+ minimumInclusive: true,
610
+ maximum,
611
+ maximumInclusive: false
612
+ };
613
+ }
614
+ function intersectRanges(left, right) {
615
+ let minimum = left.minimum;
616
+ let minimumInclusive = left.minimumInclusive;
617
+ if (!minimum || right.minimum && compareVersions(right.minimum, minimum) > 0) {
618
+ minimum = right.minimum;
619
+ minimumInclusive = right.minimumInclusive;
620
+ } else if (right.minimum && compareVersions(right.minimum, minimum) === 0) minimumInclusive = minimumInclusive && right.minimumInclusive;
621
+ let maximum = left.maximum;
622
+ let maximumInclusive = left.maximumInclusive;
623
+ if (!maximum || right.maximum && compareVersions(right.maximum, maximum) < 0) {
624
+ maximum = right.maximum;
625
+ maximumInclusive = right.maximumInclusive;
626
+ } else if (right.maximum && compareVersions(right.maximum, maximum) === 0) maximumInclusive = maximumInclusive && right.maximumInclusive;
627
+ if (minimum && maximum) {
628
+ const compared = compareVersions(minimum, maximum);
629
+ if (compared > 0 || compared === 0 && !(minimumInclusive && maximumInclusive)) return void 0;
630
+ }
631
+ return {
632
+ minimum,
633
+ minimumInclusive,
634
+ maximum,
635
+ maximumInclusive
636
+ };
637
+ }
638
+ function comparatorRange(operator, operand) {
639
+ if (!operator || operator === "=") return operandRange(operand);
640
+ if (operator === ">=") return {
641
+ minimum: operand.version,
642
+ minimumInclusive: true,
643
+ maximumInclusive: false
644
+ };
645
+ if (operator === ">") {
646
+ const partialBoundary = operand.components < 3 ? nextVersionBoundary(operand) : void 0;
647
+ return {
648
+ minimum: partialBoundary ?? operand.version,
649
+ minimumInclusive: Boolean(partialBoundary),
650
+ maximumInclusive: false
651
+ };
652
+ }
653
+ if (operator === "<") return {
654
+ minimumInclusive: true,
655
+ maximum: operand.version,
656
+ maximumInclusive: false
657
+ };
658
+ const partialBoundary = operand.components < 3 ? nextVersionBoundary(operand) : void 0;
659
+ return {
660
+ minimumInclusive: true,
661
+ maximum: partialBoundary ?? operand.version,
662
+ maximumInclusive: !partialBoundary
663
+ };
664
+ }
665
+ function parseVersionRangeBranch(value) {
666
+ const branch = normalizeAgentVersion(value).trim();
667
+ if (!branch) return void 0;
668
+ const hyphen = branch.match(/^(.+?)\s+-\s+(.+)$/u);
669
+ if (hyphen) {
670
+ const minimumOperand = parseVersionOperand(hyphen[1]);
671
+ const maximumOperand = parseVersionOperand(hyphen[2]);
672
+ if (!minimumOperand || !maximumOperand) return void 0;
673
+ const maximumBoundary = maximumOperand.components < 3 ? nextVersionBoundary(maximumOperand) : maximumOperand.version;
674
+ return intersectRanges({
675
+ minimum: minimumOperand.version,
676
+ minimumInclusive: true,
677
+ maximumInclusive: false
678
+ }, {
679
+ minimumInclusive: true,
680
+ maximum: maximumBoundary,
681
+ maximumInclusive: maximumOperand.components === 3
682
+ });
683
+ }
684
+ const special = branch.match(/^(\^|~)\s*(.+)$/u);
685
+ if (special) {
686
+ const operand = parseVersionOperand(special[2]);
687
+ if (!operand || operand.components === 0) return void 0;
688
+ return special[1] === "^" ? caretRange(operand) : tildeRange(operand);
689
+ }
690
+ const comparators = Array.from(branch.matchAll(/(>=|<=|>|<|=)\s*([^\s]+)/gu));
691
+ if (comparators.length > 0) {
692
+ if (comparators.map((match) => match[0]).join(" ").replace(/\s+/gu, " ") !== branch.replace(/\s+/gu, " ")) return void 0;
693
+ let range = {
694
+ minimumInclusive: true,
695
+ maximumInclusive: false
696
+ };
697
+ for (const comparator of comparators) {
698
+ const operand = parseVersionOperand(comparator[2]);
699
+ if (!operand) return void 0;
700
+ const next = intersectRanges(range, comparatorRange(comparator[1], operand));
701
+ if (!next) return void 0;
702
+ range = next;
703
+ }
704
+ return range;
705
+ }
706
+ const operand = parseVersionOperand(branch);
707
+ return operand ? operandRange(operand) : void 0;
708
+ }
709
+ function parseVersionRanges(value) {
710
+ return normalizeAgentVersion(value).split("||").map((branch) => parseVersionRangeBranch(branch)).filter((range) => Boolean(range));
711
+ }
712
+ function parseVersionConstraintBranches(value) {
713
+ const ranges = parseVersionRanges(value);
714
+ if (ranges.length > 0) return ranges.map((range) => ({
715
+ kind: "range",
716
+ range
717
+ }));
718
+ const normalized = normalizeAgentVersion(value);
719
+ return normalized ? [{
720
+ kind: "opaque",
721
+ value: normalized
722
+ }] : [];
723
+ }
724
+ function intersectVersionConstraintBranches(left, right) {
725
+ if (left.kind === "opaque" || right.kind === "opaque") return left.kind === "opaque" && right.kind === "opaque" && left.value === right.value ? left : void 0;
726
+ const range = intersectRanges(left.range, right.range);
727
+ return range ? {
728
+ kind: "range",
729
+ range
730
+ } : void 0;
731
+ }
732
+ /**
733
+ * Return true when choosing one alternative from every group can select one shared version.
734
+ *
735
+ * Each inner group is an OR list while the groups themselves are intersected. Computing the
736
+ * accumulated range prevents pairwise matches from accepting three mutually incompatible scopes.
737
+ */
738
+ function agentVersionConstraintGroupsOverlap(groups) {
739
+ if (groups.length === 0) return false;
740
+ let intersections = groups[0].flatMap(parseVersionConstraintBranches);
741
+ if (intersections.length === 0) return false;
742
+ for (const group of groups.slice(1)) {
743
+ const branches = group.flatMap(parseVersionConstraintBranches);
744
+ if (branches.length === 0) return false;
745
+ intersections = intersections.flatMap((intersection) => branches.map((branch) => intersectVersionConstraintBranches(intersection, branch)).filter((value) => Boolean(value)));
746
+ if (intersections.length === 0) return false;
747
+ }
748
+ return true;
749
+ }
750
+ function rangeContains(range, version) {
751
+ if (range.minimum) {
752
+ const compared = compareVersions(version, range.minimum);
753
+ if (compared < 0 || compared === 0 && !range.minimumInclusive) return false;
754
+ }
755
+ if (range.maximum) {
756
+ const compared = compareVersions(version, range.maximum);
757
+ if (compared > 0 || compared === 0 && !range.maximumInclusive) return false;
758
+ }
759
+ return true;
760
+ }
761
+ function rangesOverlap(left, right) {
762
+ return Boolean(intersectRanges(left, right));
763
+ }
764
+ /** Match an exact requested version against an exact or range-like documented constraint. */
765
+ function agentVersionConstraintMatches(requested, constraint) {
766
+ if (normalizeAgentVersion(requested) === normalizeAgentVersion(constraint)) return Boolean(normalizeAgentVersion(requested));
767
+ const wanted = parseExactVersion(requested);
768
+ if (!wanted) return false;
769
+ return parseVersionRanges(constraint).some((range) => rangeContains(range, wanted));
770
+ }
771
+ /** Return true when two exact or range-like documented version constraints can select one version. */
772
+ function agentVersionConstraintsOverlap(left, right) {
773
+ const normalizedLeft = normalizeAgentVersion(left);
774
+ if (normalizedLeft === normalizeAgentVersion(right)) return Boolean(normalizedLeft);
775
+ const leftRanges = parseVersionRanges(left);
776
+ const rightRanges = parseVersionRanges(right);
777
+ return leftRanges.some((leftRange) => rightRanges.some((rightRange) => rangesOverlap(leftRange, rightRange)));
778
+ }
779
+
460
780
  //#endregion
461
781
  //#region src/search.ts
462
782
  const DEFAULT_SEARCH_LIMIT = 10;
@@ -467,6 +787,26 @@ const syncedIndexes = /* @__PURE__ */ new Set();
467
787
  const ALGOLIA_MAX_RECORD_BYTES = 9500;
468
788
  const DEFAULT_ASK_AI_CONTEXT_CHARS = 24e3;
469
789
  const DEFAULT_ASK_AI_RESULT_CHARS = 6e3;
790
+ const MAX_SEARCH_FILTER_VALUES = 16;
791
+ const MAX_SEARCH_FILTER_VALUE_CHARS = 128;
792
+ const MAX_SEARCH_FILTER_RAW_CHARS = 4096;
793
+ const MAX_SEARCH_FILTER_SEGMENTS = 64;
794
+ const MAX_PROVIDER_SCOPE_FILTER_IDS = 1e3;
795
+ const MAX_PROVIDER_SCOPE_FILTER_CHARS = 16e3;
796
+ const MAX_SEARCH_WARNINGS = 16;
797
+ const MAX_SEARCH_WARNING_VALUES = 16;
798
+ const MAX_SEARCH_WARNING_PAGE_URLS = 8;
799
+ const SEARCH_FILTER_FIELDS = [
800
+ "framework",
801
+ "version",
802
+ "package",
803
+ "tags"
804
+ ];
805
+ const SEARCH_AMBIGUITY_FIELDS = [
806
+ "framework",
807
+ "version",
808
+ "package"
809
+ ];
470
810
  const SEARCH_STOP_WORDS = new Set([
471
811
  "a",
472
812
  "an",
@@ -498,6 +838,143 @@ const SEARCH_STOP_WORDS = new Set([
498
838
  "which",
499
839
  "with"
500
840
  ]);
841
+ function normalizeDocsSearchFilterValue(field, value) {
842
+ const bounded = value.trim().slice(0, MAX_SEARCH_FILTER_VALUE_CHARS).trim();
843
+ if (!bounded) return "";
844
+ if (field === "framework") return normalizeAgentFramework(bounded);
845
+ if (field === "version") return normalizeAgentVersion(bounded);
846
+ return bounded.toLowerCase();
847
+ }
848
+ function normalizeDocsSearchFilterValues(field, input) {
849
+ const values = typeof input === "string" ? [input] : input ?? [];
850
+ const normalized = [];
851
+ const seen = /* @__PURE__ */ new Set();
852
+ let remainingChars = MAX_SEARCH_FILTER_RAW_CHARS;
853
+ let remainingSegments = MAX_SEARCH_FILTER_SEGMENTS;
854
+ for (const item of values) {
855
+ if (remainingChars <= 0 || remainingSegments <= 0) break;
856
+ const boundedItem = item.slice(0, remainingChars);
857
+ remainingChars -= boundedItem.length;
858
+ for (const part of boundedItem.split(",")) {
859
+ if (remainingSegments <= 0) break;
860
+ remainingSegments -= 1;
861
+ const value = normalizeDocsSearchFilterValue(field, part);
862
+ if (!value || seen.has(value)) continue;
863
+ seen.add(value);
864
+ normalized.push(value);
865
+ if (normalized.length >= MAX_SEARCH_FILTER_VALUES) return normalized;
866
+ }
867
+ }
868
+ return normalized;
869
+ }
870
+ /** Normalize scalar or array-valued scope filters for programmatic, HTTP, and MCP callers. */
871
+ function normalizeDocsSearchFilters(input = {}) {
872
+ const filters = {};
873
+ for (const field of SEARCH_FILTER_FIELDS) {
874
+ const values = normalizeDocsSearchFilterValues(field, input[field]);
875
+ if (values.length > 0) filters[field] = values;
876
+ }
877
+ return filters;
878
+ }
879
+ /** Parse the public search scope parameters from a URL query string. */
880
+ function resolveDocsSearchFilters(searchParams) {
881
+ return normalizeDocsSearchFilters({
882
+ framework: searchParams.getAll("framework"),
883
+ version: searchParams.getAll("version"),
884
+ package: searchParams.getAll("package"),
885
+ tags: searchParams.getAll("tags")
886
+ });
887
+ }
888
+ /** Resolve search filters and the backwards-compatible structured response opt-in. */
889
+ function resolveDocsSearchRequest(searchParams) {
890
+ return {
891
+ filters: resolveDocsSearchFilters(searchParams),
892
+ structured: searchParams.get("response") === "structured"
893
+ };
894
+ }
895
+ function hasDocsSearchFilters(filters) {
896
+ return SEARCH_FILTER_FIELDS.some((field) => (filters[field]?.length ?? 0) > 0);
897
+ }
898
+ function resolveProviderScopeDocumentIds(query, context) {
899
+ if (!hasDocsSearchFilters(query.filters ?? {})) return void 0;
900
+ const ids = Array.from(new Set(context.documents.map((document) => document.id)));
901
+ return ids.length <= MAX_PROVIDER_SCOPE_FILTER_IDS ? ids : [];
902
+ }
903
+ function docsSearchFiltersMatch(expected, actual) {
904
+ return SEARCH_FILTER_FIELDS.every((field) => {
905
+ const expectedValues = expected[field] ?? [];
906
+ const actualValues = actual[field] ?? [];
907
+ return expectedValues.length === actualValues.length && expectedValues.every((value) => actualValues.includes(value));
908
+ });
909
+ }
910
+ function parseVerifiedMcpSearchFilters(value) {
911
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
912
+ const record = value;
913
+ const filterFields = SEARCH_FILTER_FIELDS;
914
+ if (Object.keys(record).some((field) => !filterFields.includes(field))) return;
915
+ const input = {};
916
+ for (const field of SEARCH_FILTER_FIELDS) {
917
+ const values = record[field];
918
+ if (values === void 0) continue;
919
+ if (!Array.isArray(values) || values.length > MAX_SEARCH_FILTER_VALUES || values.some((item) => typeof item !== "string" || item.length > MAX_SEARCH_FILTER_VALUE_CHARS)) return;
920
+ input[field] = values;
921
+ }
922
+ const normalized = normalizeDocsSearchFilters(input);
923
+ for (const field of SEARCH_FILTER_FIELDS) {
924
+ const values = record[field];
925
+ if (values === void 0) continue;
926
+ if (!Array.isArray(values) || values.length !== (normalized[field]?.length ?? 0) || values.some((value, index) => value !== normalized[field]?.[index])) return;
927
+ }
928
+ return normalized;
929
+ }
930
+ function normalizeDocsSearchMetadataValues(field, value) {
931
+ const values = normalizeAgentScopeValues(value);
932
+ return Array.from(new Set(values.map((item) => normalizeDocsSearchFilterValue(field, item)).filter((item) => Boolean(item))));
933
+ }
934
+ function valuesOverlap(left, right, matches) {
935
+ return left.some((leftValue) => right.some((rightValue) => matches(leftValue, rightValue)));
936
+ }
937
+ function resolveDocsSearchPageScope(page) {
938
+ const topFramework = normalizeDocsSearchMetadataValues("framework", page.framework);
939
+ const contractFramework = normalizeDocsSearchMetadataValues("framework", page.agent?.appliesTo?.framework);
940
+ const topVersion = normalizeDocsSearchMetadataValues("version", page.version);
941
+ const contractVersion = normalizeDocsSearchMetadataValues("version", page.agent?.appliesTo?.version);
942
+ const packageValues = normalizeDocsSearchMetadataValues("package", page.agent?.appliesTo?.package);
943
+ const tags = normalizeDocsSearchMetadataValues("tags", page.tags);
944
+ const conflicts = [];
945
+ if (topFramework.length > 0 && contractFramework.length > 0 && !valuesOverlap(topFramework, contractFramework, (left, right) => left === right)) conflicts.push("framework");
946
+ if (topVersion.length > 0 && contractVersion.length > 0 && !valuesOverlap(topVersion, contractVersion, agentVersionConstraintsOverlap)) conflicts.push("version");
947
+ return {
948
+ framework: topFramework.length > 0 && contractFramework.length > 0 ? topFramework.filter((value) => contractFramework.includes(value)) : Array.from(new Set([...topFramework, ...contractFramework])),
949
+ version: Array.from(new Set([...topVersion, ...contractVersion])),
950
+ package: packageValues,
951
+ tags,
952
+ declarations: {
953
+ framework: [topFramework, contractFramework].filter((values) => values.length > 0),
954
+ version: [topVersion, contractVersion].filter((values) => values.length > 0),
955
+ package: packageValues.length > 0 ? [packageValues] : [],
956
+ tags: tags.length > 0 ? [tags] : []
957
+ },
958
+ conflicts
959
+ };
960
+ }
961
+ function docsSearchScopeValueMatches(field, requested, candidate) {
962
+ return field === "version" ? agentVersionConstraintsOverlap(requested, candidate) : requested === candidate;
963
+ }
964
+ function docsSearchPageMatchesFilters(scope, filters) {
965
+ if (scope.conflicts.length > 0) return false;
966
+ return SEARCH_FILTER_FIELDS.every((field) => {
967
+ const requested = filters[field];
968
+ if (!requested || requested.length === 0) return true;
969
+ return docsSearchScopeFieldMatches(scope, field, requested);
970
+ });
971
+ }
972
+ function docsSearchScopeFieldMatches(scope, field, requested) {
973
+ const declarations = scope.declarations[field];
974
+ if (declarations.length === 0) return false;
975
+ if (field === "version") return agentVersionConstraintGroupsOverlap([requested, ...declarations]);
976
+ return requested.some((value) => declarations.every((candidates) => candidates.some((candidate) => docsSearchScopeValueMatches(field, value, candidate))));
977
+ }
501
978
  function stripMarkdownText(content) {
502
979
  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();
503
980
  }
@@ -830,6 +1307,7 @@ function hasOppositeAudienceEvidence(options) {
830
1307
  return oppositeOnlyMatches > selectedOnlyMatches;
831
1308
  }
832
1309
  function pageToSearchDocument(page, audience = "human") {
1310
+ const scope = resolveDocsSearchPageScope(page);
833
1311
  return {
834
1312
  id: makeDocumentId(page.url, "page"),
835
1313
  url: page.url,
@@ -838,9 +1316,10 @@ function pageToSearchDocument(page, audience = "human") {
838
1316
  description: page.description,
839
1317
  type: "page",
840
1318
  locale: page.locale,
841
- framework: page.framework,
842
- version: page.version,
843
- tags: page.tags
1319
+ framework: scope.framework[0],
1320
+ version: scope.version[0],
1321
+ package: scope.package.length > 0 ? scope.package : void 0,
1322
+ tags: scope.tags.length > 0 ? scope.tags : void 0
844
1323
  };
845
1324
  }
846
1325
  function buildExactPageSearchResults(query, pages, audience = "human") {
@@ -903,7 +1382,9 @@ function makeDocumentId(url, suffix) {
903
1382
  return `${url}#${suffix}`;
904
1383
  }
905
1384
  function splitPageIntoSections(page, audience = "human") {
906
- return parseDocsMarkdownSections(getPageAudienceRawContent(page, audience)).flatMap((section, index) => {
1385
+ const raw = getPageAudienceRawContent(page, audience);
1386
+ const scope = resolveDocsSearchPageScope(page);
1387
+ return parseDocsMarkdownSections(raw).flatMap((section, index) => {
907
1388
  const content = normalizeWhitespace(stripMarkdownText(section.content));
908
1389
  if (!content) return [];
909
1390
  return [{
@@ -915,9 +1396,10 @@ function splitPageIntoSections(page, audience = "human") {
915
1396
  description: page.description,
916
1397
  type: "heading",
917
1398
  locale: page.locale,
918
- framework: page.framework,
919
- version: page.version,
920
- tags: page.tags
1399
+ framework: scope.framework[0],
1400
+ version: scope.version[0],
1401
+ package: scope.package.length > 0 ? scope.package : void 0,
1402
+ tags: scope.tags.length > 0 ? scope.tags : void 0
921
1403
  }];
922
1404
  });
923
1405
  }
@@ -1055,7 +1537,12 @@ function buildAlgoliaRecord(document) {
1055
1537
  section: document.section,
1056
1538
  content: document.content,
1057
1539
  description: document.description,
1058
- type: document.type
1540
+ type: document.type,
1541
+ locale: document.locale,
1542
+ framework: document.framework,
1543
+ version: document.version,
1544
+ package: document.package,
1545
+ tags: document.tags
1059
1546
  };
1060
1547
  const encoder = new TextEncoder();
1061
1548
  const sizeOf = (value) => encoder.encode(JSON.stringify(value)).length;
@@ -1160,6 +1647,14 @@ function mapMcpSearchResult(value) {
1160
1647
  section
1161
1648
  };
1162
1649
  }
1650
+ function readMcpSearchToolPayload(payload) {
1651
+ if (!payload || typeof payload !== "object" || !("result" in payload) || !payload.result || typeof payload.result !== "object") return null;
1652
+ const result = payload.result;
1653
+ if (result.structuredContent && typeof result.structuredContent === "object" && !Array.isArray(result.structuredContent)) return result.structuredContent;
1654
+ const content = Array.isArray(result.content) ? result.content : [];
1655
+ const resultText = content.length > 0 && content[0] && typeof content[0] === "object" && "text" in content[0] && typeof content[0].text === "string" ? content[0].text : null;
1656
+ return resultText ? JSON.parse(resultText) : null;
1657
+ }
1163
1658
  async function createOllamaEmbedding(text, config, signal) {
1164
1659
  const response = await fetch(`${(config.baseUrl ?? "http://127.0.0.1:11434").replace(/\/$/, "")}/api/embed`, {
1165
1660
  method: "POST",
@@ -1179,6 +1674,9 @@ async function createOllamaEmbedding(text, config, signal) {
1179
1674
  function getTypesenseSearchBase(config) {
1180
1675
  return config.baseUrl.replace(/\/$/, "");
1181
1676
  }
1677
+ function quoteTypesenseFilterValue(value) {
1678
+ return `\`${value.replace(/\\/gu, "\\\\").replace(/`/gu, "\\`")}\``;
1679
+ }
1182
1680
  async function ensureTypesenseCollection(config, dimensions, signal) {
1183
1681
  const baseUrl = getTypesenseSearchBase(config);
1184
1682
  const headers = {
@@ -1237,6 +1735,11 @@ async function ensureTypesenseCollection(config, dimensions, signal) {
1237
1735
  type: "string",
1238
1736
  optional: true
1239
1737
  },
1738
+ {
1739
+ name: "package",
1740
+ type: "string[]",
1741
+ optional: true
1742
+ },
1240
1743
  {
1241
1744
  name: "tags",
1242
1745
  type: "string[]",
@@ -1276,6 +1779,7 @@ function createTypesenseSearchAdapter(config) {
1276
1779
  locale: document.locale,
1277
1780
  framework: document.framework,
1278
1781
  version: document.version,
1782
+ package: document.package,
1279
1783
  tags: document.tags
1280
1784
  };
1281
1785
  if (config.mode === "hybrid" && config.embeddings) next.embedding = await createOllamaEmbedding(`${document.title}\n${document.section ?? ""}\n${document.content}`.trim(), config.embeddings, context.signal);
@@ -1294,6 +1798,8 @@ function createTypesenseSearchAdapter(config) {
1294
1798
  }), "Failed to sync documents to Typesense");
1295
1799
  },
1296
1800
  async search(query, context) {
1801
+ const scopedDocumentIds = resolveProviderScopeDocumentIds(query, context);
1802
+ if (scopedDocumentIds?.length === 0) return [];
1297
1803
  const params = new URLSearchParams({
1298
1804
  q: query.query,
1299
1805
  query_by: (config.queryBy ?? [
@@ -1311,12 +1817,27 @@ function createTypesenseSearchAdapter(config) {
1311
1817
  const vector = await createOllamaEmbedding(query.query, config.embeddings, context.signal);
1312
1818
  params.set("vector_query", `embedding:([${vector.join(",")}],k:${Math.max((query.limit ?? 10) * 4, 20)})`);
1313
1819
  }
1314
- const response = await fetch(`${getTypesenseSearchBase(config)}/collections/${encodeURIComponent(config.collection)}/documents/search?${params.toString()}`, {
1820
+ const filterBy = scopedDocumentIds ? `id:=[${scopedDocumentIds.map(quoteTypesenseFilterValue).join(",")}]` : void 0;
1821
+ if (filterBy && filterBy.length > MAX_PROVIDER_SCOPE_FILTER_CHARS) return [];
1822
+ const response = filterBy ? await fetch(`${getTypesenseSearchBase(config)}/multi_search`, {
1823
+ method: "POST",
1824
+ headers: {
1825
+ "Content-Type": "application/json",
1826
+ "X-TYPESENSE-API-KEY": config.apiKey
1827
+ },
1828
+ body: JSON.stringify({ searches: [{
1829
+ collection: config.collection,
1830
+ ...Object.fromEntries(params),
1831
+ filter_by: filterBy
1832
+ }] }),
1833
+ signal: context.signal
1834
+ }) : await fetch(`${getTypesenseSearchBase(config)}/collections/${encodeURIComponent(config.collection)}/documents/search?${params.toString()}`, {
1315
1835
  headers: { "X-TYPESENSE-API-KEY": config.apiKey },
1316
1836
  signal: context.signal
1317
1837
  });
1318
1838
  ensureOk(response, "Typesense search failed");
1319
- return ((await readResponseJson(response)).hits ?? []).map((hit) => {
1839
+ const rawPayload = await readResponseJson(response);
1840
+ return ((filterBy ? rawPayload.results?.[0] ?? {} : rawPayload).hits ?? []).map((hit) => {
1320
1841
  const document = hit.document ?? {};
1321
1842
  const section = typeof document.section === "string" ? document.section : void 0;
1322
1843
  const content = typeof document.title === "string" ? section ? `${document.title} — ${section}` : document.title : typeof document.content === "string" ? document.content : "Untitled result";
@@ -1427,7 +1948,11 @@ function createMcpSearchAdapter(config) {
1427
1948
  query: query.query,
1428
1949
  limit: query.limit ?? config.maxResults ?? DEFAULT_SEARCH_LIMIT,
1429
1950
  locale: query.locale,
1430
- ...forwardAudience ? { audience } : {}
1951
+ ...forwardAudience ? { audience } : {},
1952
+ ...query.filters?.framework ? { framework: query.filters.framework } : {},
1953
+ ...query.filters?.version ? { version: query.filters.version } : {},
1954
+ ...query.filters?.package ? { package: query.filters.package } : {},
1955
+ ...query.filters?.tags ? { tags: query.filters.tags } : {}
1431
1956
  }
1432
1957
  }
1433
1958
  }),
@@ -1436,9 +1961,13 @@ function createMcpSearchAdapter(config) {
1436
1961
  const payload = await readMcpResponsePayload(searchResponse);
1437
1962
  ensureOk(searchResponse, "MCP search request failed");
1438
1963
  ensureJsonRpcOk(payload, "MCP search request failed");
1439
- 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;
1440
- if (!resultText) return [];
1441
- const parsed = JSON.parse(resultText);
1964
+ const parsed = readMcpSearchToolPayload(payload);
1965
+ if (!parsed) return [];
1966
+ if (hasDocsSearchFilters(query.filters ?? {})) {
1967
+ if (Array.isArray(parsed) || parsed.format !== "docs-search.v1") return [];
1968
+ const echoedFilters = parseVerifiedMcpSearchFilters(parsed.filters);
1969
+ if (!echoedFilters || !docsSearchFiltersMatch(query.filters ?? {}, echoedFilters)) return [];
1970
+ }
1442
1971
  return (Array.isArray(parsed) ? parsed : Array.isArray(parsed.results) ? parsed.results : Array.isArray(parsed.pages) ? parsed.pages : []).map(mapMcpSearchResult).filter((result) => Boolean(result));
1443
1972
  } finally {
1444
1973
  if (sessionId) {
@@ -1465,6 +1994,9 @@ function createMcpSearchAdapter(config) {
1465
1994
  function getAlgoliaBase(config) {
1466
1995
  return `https://${config.appId}-dsn.algolia.net`;
1467
1996
  }
1997
+ function quoteAlgoliaFilterValue(value) {
1998
+ return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, "\\\"")}"`;
1999
+ }
1468
2000
  function createAlgoliaSearchAdapter(config) {
1469
2001
  return {
1470
2002
  name: "algolia",
@@ -1485,6 +2017,10 @@ function createAlgoliaSearchAdapter(config) {
1485
2017
  }), "Failed to sync documents to Algolia");
1486
2018
  },
1487
2019
  async search(query, context) {
2020
+ const scopedDocumentIds = resolveProviderScopeDocumentIds(query, context);
2021
+ if (scopedDocumentIds?.length === 0) return [];
2022
+ const filters = scopedDocumentIds?.map((id) => `objectID:${quoteAlgoliaFilterValue(id)}`).join(" OR ");
2023
+ if (filters && filters.length > MAX_PROVIDER_SCOPE_FILTER_CHARS) return [];
1488
2024
  const response = await fetch(`${getAlgoliaBase(config)}/1/indexes/${encodeURIComponent(config.indexName)}/query`, {
1489
2025
  method: "POST",
1490
2026
  headers: {
@@ -1495,7 +2031,8 @@ function createAlgoliaSearchAdapter(config) {
1495
2031
  body: JSON.stringify({
1496
2032
  query: query.query,
1497
2033
  hitsPerPage: query.limit ?? config.maxResults ?? DEFAULT_SEARCH_LIMIT,
1498
- attributesToSnippet: ["content:20"]
2034
+ attributesToSnippet: ["content:20"],
2035
+ ...filters ? { filters } : {}
1499
2036
  }),
1500
2037
  signal: context.signal
1501
2038
  });
@@ -1552,9 +2089,12 @@ async function performDocsSearch(options) {
1552
2089
  const search = normalizeDocsSearchConfig(options.search);
1553
2090
  if (!search.enabled) return [];
1554
2091
  const audience = resolveDocsSearchAudience(options.audience);
1555
- const documents = buildDocsSearchDocuments(options.pages, search.chunking, audience);
2092
+ const filters = normalizeDocsSearchFilters(options.filters);
2093
+ const hasFilters = hasDocsSearchFilters(filters);
2094
+ const scopedPages = hasFilters ? options.pages.filter((page) => docsSearchPageMatchesFilters(resolveDocsSearchPageScope(page), filters)) : options.pages;
2095
+ const documents = buildDocsSearchDocuments(scopedPages, search.chunking, audience);
1556
2096
  const context = {
1557
- pages: options.pages,
2097
+ pages: scopedPages,
1558
2098
  documents,
1559
2099
  audience,
1560
2100
  locale: options.locale,
@@ -1567,27 +2107,30 @@ async function performDocsSearch(options) {
1567
2107
  limit: options.limit ?? search.maxResults,
1568
2108
  locale: options.locale,
1569
2109
  pathname: options.pathname,
1570
- audience
2110
+ audience,
2111
+ ...hasFilters ? { filters } : {}
1571
2112
  };
1572
2113
  try {
1573
2114
  const adapter = await resolveSearchAdapter(search, context);
1574
- await maybeSyncSearchIndex(adapter, search, audience === "agent" ? {
2115
+ await maybeSyncSearchIndex(adapter, search, audience === "agent" || hasFilters ? {
1575
2116
  ...context,
2117
+ pages: options.pages,
1576
2118
  documents: buildDocsSearchDocuments(options.pages, search.chunking, "human"),
1577
2119
  audience: "human"
1578
2120
  } : context);
1579
2121
  const results = await adapter.search(query, context);
1580
2122
  if (search.provider === "simple") return results;
1581
2123
  const localAudienceProjectionResults = buildAudienceProjectionSearchResults(documents, options.query);
1582
- const localPagePaths = new Set(options.pages.map((page) => normalizeUrlPathname(page.url)));
1583
- const preserveUnmatched = audience === "human" || search.provider === "mcp" ? (result) => shouldPreserveUnmatchedExternalResult({
2124
+ const localPagePaths = new Set(scopedPages.map((page) => normalizeUrlPathname(page.url)));
2125
+ const allLocalPagePaths = hasFilters && search.provider === "mcp" ? new Set(options.pages.map((page) => normalizeUrlPathname(page.url))) : localPagePaths;
2126
+ const preserveUnmatched = !hasFilters && audience === "human" || search.provider === "mcp" ? (result) => shouldPreserveUnmatchedExternalResult({
1584
2127
  result,
1585
- localPagePaths,
2128
+ localPagePaths: allLocalPagePaths,
1586
2129
  baseUrl: options.baseUrl
1587
2130
  }) : void 0;
1588
2131
  const safeAdapterResults = sanitizeExternalAudienceSearchResults(results.filter((result) => !hasOppositeAudienceEvidence({
1589
2132
  result,
1590
- pages: options.pages,
2133
+ pages: scopedPages,
1591
2134
  query: options.query,
1592
2135
  audience,
1593
2136
  baseUrl: options.baseUrl,
@@ -1596,7 +2139,7 @@ async function performDocsSearch(options) {
1596
2139
  if (options.supplementExternalResults === false) return safeAdapterResults.slice(0, query.limit ?? search.maxResults ?? DEFAULT_SEARCH_LIMIT);
1597
2140
  const simpleAudienceResults = audience === "agent" || results.length === 0 || safeAdapterResults.length < results.length ? await createSimpleSearchAdapter().search(query, context) : [];
1598
2141
  const combinedResults = mergeSearchResults([
1599
- buildExactPageSearchResults(options.query, options.pages, audience),
2142
+ buildExactPageSearchResults(options.query, scopedPages, audience),
1600
2143
  safeAdapterResults,
1601
2144
  simpleAudienceResults
1602
2145
  ], audience === "agent" ? (result) => getAskAIResultKey(result, options.baseUrl, options.strictExternalOrigins) : void 0);
@@ -1606,6 +2149,127 @@ async function performDocsSearch(options) {
1606
2149
  return createSimpleSearchAdapter().search(query, context);
1607
2150
  }
1608
2151
  }
2152
+ function compareSearchMetadataValues(left, right) {
2153
+ if (left === right) return 0;
2154
+ return left < right ? -1 : 1;
2155
+ }
2156
+ function boundedSearchWarningValues(values) {
2157
+ const bounded = Array.from(new Set(values)).sort(compareSearchMetadataValues).slice(0, MAX_SEARCH_WARNING_VALUES);
2158
+ return bounded.length > 0 ? bounded : void 0;
2159
+ }
2160
+ function boundedSearchWarningPageUrls(pages) {
2161
+ const bounded = Array.from(new Set(pages.map((page) => page.url))).sort(compareSearchMetadataValues).slice(0, MAX_SEARCH_WARNING_PAGE_URLS);
2162
+ return bounded.length > 0 ? bounded : void 0;
2163
+ }
2164
+ function findSearchResultPages(pages, results, baseUrl) {
2165
+ const pagesByPath = new Map(pages.map((page) => [normalizeUrlPathname(page.url), page]));
2166
+ const found = /* @__PURE__ */ new Map();
2167
+ for (const result of results) {
2168
+ if (!isLocalProviderResult(result, baseUrl, true)) continue;
2169
+ const page = pagesByPath.get(normalizeUrlPathname(result.url));
2170
+ if (page) found.set(page.url, page);
2171
+ }
2172
+ return [...found.values()].sort((left, right) => compareSearchMetadataValues(left.url, right.url));
2173
+ }
2174
+ function searchScopeIsAmbiguous(field, values) {
2175
+ if (values.length < 2) return false;
2176
+ if (field !== "version") return true;
2177
+ return values.some((left, index) => values.slice(index + 1).some((right) => !agentVersionConstraintsOverlap(left, right)));
2178
+ }
2179
+ function buildDocsSearchWarnings(options) {
2180
+ const pageScopes = options.pages.map((page) => ({
2181
+ page,
2182
+ scope: resolveDocsSearchPageScope(page)
2183
+ }));
2184
+ const relevantPageScopes = pageScopes.filter(({ page }) => scoreDocument(options.query, pageToSearchDocument(page, options.audience)) > 0);
2185
+ const warnings = [];
2186
+ for (const field of SEARCH_FILTER_FIELDS) {
2187
+ const conflicting = relevantPageScopes.filter(({ scope }) => scope.conflicts.includes(field));
2188
+ if (conflicting.length > 0) warnings.push({
2189
+ code: "conflicting_scope_metadata",
2190
+ field,
2191
+ message: `${conflicting.length} page${conflicting.length === 1 ? "" : "s"} declare conflicting ${field} metadata and cannot be selected safely by scoped search.`,
2192
+ pageUrls: boundedSearchWarningPageUrls(conflicting.map(({ page }) => page)),
2193
+ count: conflicting.length
2194
+ });
2195
+ const requested = options.filters[field];
2196
+ if (!requested || requested.length === 0) continue;
2197
+ const missing = relevantPageScopes.filter(({ scope }) => scope.declarations[field].length === 0);
2198
+ if (missing.length > 0) warnings.push({
2199
+ code: "missing_scope_metadata",
2200
+ field,
2201
+ message: `${missing.length} page${missing.length === 1 ? "" : "s"} lack ${field} metadata and were excluded by the strict scope filter.`,
2202
+ pageUrls: boundedSearchWarningPageUrls(missing.map(({ page }) => page)),
2203
+ count: missing.length
2204
+ });
2205
+ const unknown = requested.filter((value) => !pageScopes.some(({ scope }) => !scope.conflicts.includes(field) && docsSearchScopeFieldMatches(scope, field, [value])));
2206
+ if (unknown.length > 0) warnings.push({
2207
+ code: "unknown_filter_value",
2208
+ field,
2209
+ message: `No page metadata matches ${field} filter value${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}.`,
2210
+ values: boundedSearchWarningValues(unknown),
2211
+ count: unknown.length
2212
+ });
2213
+ }
2214
+ const resultPages = findSearchResultPages(options.pages, options.results, options.baseUrl);
2215
+ for (const field of SEARCH_AMBIGUITY_FIELDS) {
2216
+ if ((options.filters[field]?.length ?? 0) > 0) continue;
2217
+ const values = resultPages.flatMap((page) => {
2218
+ const scope = resolveDocsSearchPageScope(page);
2219
+ return scope.conflicts.includes(field) ? [] : scope[field];
2220
+ });
2221
+ const unique = Array.from(new Set(values)).sort(compareSearchMetadataValues);
2222
+ if (!searchScopeIsAmbiguous(field, unique)) continue;
2223
+ const contributingPages = resultPages.filter((page) => {
2224
+ const scope = resolveDocsSearchPageScope(page);
2225
+ return !scope.conflicts.includes(field) && scope[field].length > 0;
2226
+ });
2227
+ warnings.push({
2228
+ code: "ambiguous_scope",
2229
+ field,
2230
+ message: `Search results span multiple ${field} scopes; add a ${field} filter before acting on scope-specific guidance.`,
2231
+ values: boundedSearchWarningValues(unique),
2232
+ pageUrls: boundedSearchWarningPageUrls(contributingPages),
2233
+ count: unique.length
2234
+ });
2235
+ }
2236
+ const codeOrder = {
2237
+ ambiguous_scope: 0,
2238
+ unknown_filter_value: 1,
2239
+ missing_scope_metadata: 2,
2240
+ conflicting_scope_metadata: 3
2241
+ };
2242
+ return warnings.sort((left, right) => {
2243
+ const codeDelta = codeOrder[left.code] - codeOrder[right.code];
2244
+ if (codeDelta !== 0) return codeDelta;
2245
+ return SEARCH_FILTER_FIELDS.indexOf(left.field) - SEARCH_FILTER_FIELDS.indexOf(right.field);
2246
+ }).slice(0, MAX_SEARCH_WARNINGS);
2247
+ }
2248
+ async function performDocsSearchWithMetadata(options) {
2249
+ const audience = resolveDocsSearchAudience(options.audience);
2250
+ const filters = normalizeDocsSearchFilters(options.filters);
2251
+ const results = await performDocsSearch({
2252
+ ...options,
2253
+ audience,
2254
+ filters
2255
+ });
2256
+ return {
2257
+ format: "docs-search.v1",
2258
+ query: options.query,
2259
+ audience,
2260
+ filters,
2261
+ resultCount: results.length,
2262
+ results,
2263
+ warnings: buildDocsSearchWarnings({
2264
+ pages: options.pages,
2265
+ results,
2266
+ filters,
2267
+ query: options.query,
2268
+ audience,
2269
+ baseUrl: options.baseUrl
2270
+ })
2271
+ };
2272
+ }
1609
2273
  async function buildDocsAskAIContext(options) {
1610
2274
  const limit = options.limit ?? 5;
1611
2275
  const searchLimit = Math.max(limit * 2, limit);
@@ -1621,6 +2285,7 @@ async function buildDocsAskAIContext(options) {
1621
2285
  siteTitle: options.siteTitle,
1622
2286
  baseUrl: options.baseUrl,
1623
2287
  limit: searchLimit,
2288
+ filters: options.filters,
1624
2289
  failureMode: options.searchFailureMode,
1625
2290
  strictExternalOrigins: options.strictExternalOrigins,
1626
2291
  signal: options.signal
@@ -1683,4 +2348,4 @@ function createCustomSearchAdapter(adapter) {
1683
2348
  }
1684
2349
 
1685
2350
  //#endregion
1686
- export { inferDocsTelemetryAgentSurface as A, resolveSidebarFolderIndexBehavior as C, emitDocsTelemetryMcpToolEvent as D, emitDocsTelemetryEvent as E, normalizeDocsTelemetryOrigin as M, resolveDocsTelemetryConfig as N, emitDocsTelemetryProjectEvent as O, resolvePageSidebarFolderIndexBehavior as S, emitDocsTelemetryAgentSurfaceEvent as T, normalizeGeneratedAgentContent as _, createMcpSearchAdapter as a, stripGeneratedAgentProvenance as b, formatDocsAskAIPackageHints as c, resolveAskAISearchRequestConfig as d, resolveDocsSearchAudience as f, hashGeneratedAgentContent as g, GENERATED_AGENT_PROVENANCE_VERSION as h, createCustomSearchAdapter as i, isLocalDocsTelemetryOrigin as j, getDocsTelemetryFeatures as k, inferDocsAskAIPackageHints as l, GENERATED_AGENT_PROVENANCE_MARKER as m, buildDocsSearchDocuments as n, createSimpleSearchAdapter as o, resolveSearchRequestConfig as p, createAlgoliaSearchAdapter as r, createTypesenseSearchAdapter as s, buildDocsAskAIContext as t, performDocsSearch as u, parseGeneratedAgentDocument as v, resolveSidebarFolderIndexBehaviorForPath as w, applySidebarFolderIndexBehavior as x, serializeGeneratedAgentDocument as y };
2351
+ export { stripGeneratedAgentProvenance as A, inferDocsTelemetryAgentSurface as B, normalizeAgentVersion as C, normalizeGeneratedAgentContent as D, hashGeneratedAgentContent as E, emitDocsTelemetryAgentSurfaceEvent as F, normalizeDocsTelemetryOrigin as H, emitDocsTelemetryEvent as I, emitDocsTelemetryMcpToolEvent as L, resolvePageSidebarFolderIndexBehavior as M, resolveSidebarFolderIndexBehavior as N, parseGeneratedAgentDocument as O, resolveSidebarFolderIndexBehaviorForPath as P, emitDocsTelemetryProjectEvent as R, normalizeAgentScopeValues as S, GENERATED_AGENT_PROVENANCE_VERSION as T, resolveDocsTelemetryConfig as U, isLocalDocsTelemetryOrigin as V, resolveSearchRequestConfig as _, createMcpSearchAdapter as a, normalizeAgentFramework as b, formatDocsAskAIPackageHints as c, performDocsSearch as d, performDocsSearchWithMetadata as f, resolveDocsSearchRequest as g, resolveDocsSearchFilters as h, createCustomSearchAdapter as i, applySidebarFolderIndexBehavior as j, serializeGeneratedAgentDocument as k, inferDocsAskAIPackageHints as l, resolveDocsSearchAudience as m, buildDocsSearchDocuments as n, createSimpleSearchAdapter as o, resolveAskAISearchRequestConfig as p, createAlgoliaSearchAdapter as r, createTypesenseSearchAdapter as s, buildDocsAskAIContext as t, normalizeDocsSearchFilters as u, agentVersionConstraintMatches as v, GENERATED_AGENT_PROVENANCE_MARKER as w, normalizeAgentLocale as x, agentVersionConstraintsOverlap as y, getDocsTelemetryFeatures as z };