@juspay/neurolink 10.3.0 → 10.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser/neurolink.min.js +430 -417
  3. package/dist/cli/loop/optionsSchema.js +4 -0
  4. package/dist/constants/contextWindows.d.ts +24 -0
  5. package/dist/constants/contextWindows.js +50 -0
  6. package/dist/context/errorDetection.d.ts +6 -0
  7. package/dist/context/errorDetection.js +18 -0
  8. package/dist/context/stepBudgetGuard.d.ts +17 -3
  9. package/dist/context/stepBudgetGuard.js +53 -14
  10. package/dist/core/baseProvider.d.ts +12 -0
  11. package/dist/core/baseProvider.js +18 -0
  12. package/dist/core/modules/GenerationHandler.js +208 -42
  13. package/dist/core/modules/ToolsManager.d.ts +17 -0
  14. package/dist/core/modules/ToolsManager.js +99 -1
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +3 -0
  17. package/dist/knowledge/context.d.ts +18 -0
  18. package/dist/knowledge/context.js +91 -0
  19. package/dist/knowledge/defaults.d.ts +24 -0
  20. package/dist/knowledge/defaults.js +29 -0
  21. package/dist/knowledge/engine.d.ts +35 -0
  22. package/dist/knowledge/engine.js +179 -0
  23. package/dist/knowledge/index.d.ts +15 -0
  24. package/dist/knowledge/index.js +15 -0
  25. package/dist/knowledge/indexCache.d.ts +19 -0
  26. package/dist/knowledge/indexCache.js +109 -0
  27. package/dist/knowledge/knowledgeIndex.d.ts +41 -0
  28. package/dist/knowledge/knowledgeIndex.js +204 -0
  29. package/dist/knowledge/normalize.d.ts +32 -0
  30. package/dist/knowledge/normalize.js +74 -0
  31. package/dist/knowledge/resolve.d.ts +18 -0
  32. package/dist/knowledge/resolve.js +156 -0
  33. package/dist/knowledge/retrieval.d.ts +16 -0
  34. package/dist/knowledge/retrieval.js +221 -0
  35. package/dist/lib/constants/contextWindows.d.ts +24 -0
  36. package/dist/lib/constants/contextWindows.js +50 -0
  37. package/dist/lib/context/errorDetection.d.ts +6 -0
  38. package/dist/lib/context/errorDetection.js +18 -0
  39. package/dist/lib/context/stepBudgetGuard.d.ts +17 -3
  40. package/dist/lib/context/stepBudgetGuard.js +53 -14
  41. package/dist/lib/core/baseProvider.d.ts +12 -0
  42. package/dist/lib/core/baseProvider.js +18 -0
  43. package/dist/lib/core/modules/GenerationHandler.js +208 -42
  44. package/dist/lib/core/modules/ToolsManager.d.ts +17 -0
  45. package/dist/lib/core/modules/ToolsManager.js +99 -1
  46. package/dist/lib/files/fileTools.d.ts +1 -1
  47. package/dist/lib/index.d.ts +1 -0
  48. package/dist/lib/index.js +3 -0
  49. package/dist/lib/knowledge/context.d.ts +18 -0
  50. package/dist/lib/knowledge/context.js +92 -0
  51. package/dist/lib/knowledge/defaults.d.ts +24 -0
  52. package/dist/lib/knowledge/defaults.js +30 -0
  53. package/dist/lib/knowledge/engine.d.ts +35 -0
  54. package/dist/lib/knowledge/engine.js +180 -0
  55. package/dist/lib/knowledge/index.d.ts +15 -0
  56. package/dist/lib/knowledge/index.js +16 -0
  57. package/dist/lib/knowledge/indexCache.d.ts +19 -0
  58. package/dist/lib/knowledge/indexCache.js +110 -0
  59. package/dist/lib/knowledge/knowledgeIndex.d.ts +41 -0
  60. package/dist/lib/knowledge/knowledgeIndex.js +205 -0
  61. package/dist/lib/knowledge/normalize.d.ts +32 -0
  62. package/dist/lib/knowledge/normalize.js +75 -0
  63. package/dist/lib/knowledge/resolve.d.ts +18 -0
  64. package/dist/lib/knowledge/resolve.js +157 -0
  65. package/dist/lib/knowledge/retrieval.d.ts +16 -0
  66. package/dist/lib/knowledge/retrieval.js +222 -0
  67. package/dist/lib/mcp/toolDiscoveryService.js +59 -20
  68. package/dist/lib/neurolink.d.ts +14 -1
  69. package/dist/lib/neurolink.js +142 -12
  70. package/dist/lib/providers/litellm.d.ts +27 -19
  71. package/dist/lib/providers/litellm.js +171 -92
  72. package/dist/lib/providers/openaiChatCompletionsBase.d.ts +37 -1
  73. package/dist/lib/providers/openaiChatCompletionsBase.js +201 -33
  74. package/dist/lib/providers/openaiChatCompletionsClient.d.ts +23 -5
  75. package/dist/lib/providers/openaiChatCompletionsClient.js +94 -14
  76. package/dist/lib/proxy/proxyFetch.d.ts +17 -0
  77. package/dist/lib/proxy/proxyFetch.js +42 -4
  78. package/dist/lib/types/config.d.ts +12 -0
  79. package/dist/lib/types/conversation.d.ts +1 -1
  80. package/dist/lib/types/dynamic.d.ts +12 -0
  81. package/dist/lib/types/generate.d.ts +32 -2
  82. package/dist/lib/types/index.d.ts +1 -0
  83. package/dist/lib/types/index.js +1 -0
  84. package/dist/lib/types/knowledge.d.ts +342 -0
  85. package/dist/lib/types/knowledge.js +30 -0
  86. package/dist/lib/types/openaiCompatible.d.ts +2 -0
  87. package/dist/lib/types/providers.d.ts +9 -0
  88. package/dist/lib/types/stream.d.ts +14 -0
  89. package/dist/lib/utils/errorHandling.js +8 -2
  90. package/dist/lib/utils/schemaConversion.d.ts +16 -0
  91. package/dist/lib/utils/schemaConversion.js +165 -8
  92. package/dist/lib/utils/timeout.d.ts +22 -0
  93. package/dist/lib/utils/timeout.js +72 -12
  94. package/dist/lib/utils/tokenLimits.js +22 -0
  95. package/dist/lib/utils/toolCallRepair.d.ts +8 -0
  96. package/dist/lib/utils/toolCallRepair.js +4 -1
  97. package/dist/mcp/toolDiscoveryService.js +59 -20
  98. package/dist/neurolink.d.ts +14 -1
  99. package/dist/neurolink.js +142 -12
  100. package/dist/providers/litellm.d.ts +27 -19
  101. package/dist/providers/litellm.js +171 -92
  102. package/dist/providers/openaiChatCompletionsBase.d.ts +37 -1
  103. package/dist/providers/openaiChatCompletionsBase.js +201 -33
  104. package/dist/providers/openaiChatCompletionsClient.d.ts +23 -5
  105. package/dist/providers/openaiChatCompletionsClient.js +94 -14
  106. package/dist/proxy/proxyFetch.d.ts +17 -0
  107. package/dist/proxy/proxyFetch.js +42 -4
  108. package/dist/types/config.d.ts +12 -0
  109. package/dist/types/conversation.d.ts +1 -1
  110. package/dist/types/dynamic.d.ts +12 -0
  111. package/dist/types/generate.d.ts +32 -2
  112. package/dist/types/index.d.ts +1 -0
  113. package/dist/types/index.js +1 -0
  114. package/dist/types/knowledge.d.ts +342 -0
  115. package/dist/types/knowledge.js +29 -0
  116. package/dist/types/openaiCompatible.d.ts +2 -0
  117. package/dist/types/providers.d.ts +9 -0
  118. package/dist/types/stream.d.ts +14 -0
  119. package/dist/utils/errorHandling.js +8 -2
  120. package/dist/utils/schemaConversion.d.ts +16 -0
  121. package/dist/utils/schemaConversion.js +165 -8
  122. package/dist/utils/timeout.d.ts +22 -0
  123. package/dist/utils/timeout.js +72 -12
  124. package/dist/utils/tokenLimits.js +22 -0
  125. package/dist/utils/toolCallRepair.d.ts +8 -0
  126. package/dist/utils/toolCallRepair.js +4 -1
  127. package/package.json +4 -1
@@ -0,0 +1,205 @@
1
+ /**
2
+ * In-memory knowledge index construction.
3
+ *
4
+ * Index construction is an application-process lifecycle operation, not a
5
+ * per-session one. A complete `KnowledgeIndexSnapshot` is built once from the
6
+ * normalized entries and then queried by every turn.
7
+ *
8
+ * The lexical scorer is a compact field-aware BM25 (BM25F-style): each of the
9
+ * five text fields is scored independently and combined with per-field
10
+ * weights, retaining the per-field breakdown so a retrieval decision is
11
+ * explainable in traces. No embedding model or vector store is involved.
12
+ */
13
+ import { normalizePhrases, normalizeText, tokenize } from "./normalize.js";
14
+ const FIELD_NAMES = [
15
+ "title",
16
+ "aliases",
17
+ "keywords",
18
+ "summary",
19
+ "body",
20
+ ];
21
+ /** Okapi BM25 saturation and length-normalization parameters. */
22
+ const BM25_K1 = 1.5;
23
+ const BM25_B = 0.75;
24
+ /**
25
+ * Field-aware BM25 over the document set. Query text is scored per field and
26
+ * the weighted per-field scores are summed. Satisfies the structural
27
+ * `KnowledgeLexicalSearcher` type held by a snapshot.
28
+ */
29
+ export class KnowledgeLexicalIndex {
30
+ weights;
31
+ // field -> (term -> (docId -> term frequency))
32
+ postings = new Map();
33
+ // field -> (docId -> field length in tokens)
34
+ fieldLengths = new Map();
35
+ // field -> total tokens across all docs (for average length)
36
+ fieldTokenTotals = new Map();
37
+ // field -> average field length (computed in finalize)
38
+ avgFieldLength = new Map();
39
+ docCount = 0;
40
+ constructor(weights) {
41
+ this.weights = weights;
42
+ for (const field of FIELD_NAMES) {
43
+ this.postings.set(field, new Map());
44
+ this.fieldLengths.set(field, new Map());
45
+ this.fieldTokenTotals.set(field, 0);
46
+ }
47
+ }
48
+ add(document) {
49
+ this.docCount += 1;
50
+ for (const field of FIELD_NAMES) {
51
+ const tokens = document.fields[field];
52
+ const postingsForField = this.postings.get(field);
53
+ const lengthsForField = this.fieldLengths.get(field);
54
+ if (!postingsForField || !lengthsForField) {
55
+ continue;
56
+ }
57
+ lengthsForField.set(document.id, tokens.length);
58
+ this.fieldTokenTotals.set(field, (this.fieldTokenTotals.get(field) ?? 0) + tokens.length);
59
+ const termFrequency = new Map();
60
+ for (const token of tokens) {
61
+ termFrequency.set(token, (termFrequency.get(token) ?? 0) + 1);
62
+ }
63
+ for (const [term, frequency] of termFrequency) {
64
+ const docMap = postingsForField.get(term);
65
+ if (docMap) {
66
+ docMap.set(document.id, frequency);
67
+ }
68
+ else {
69
+ postingsForField.set(term, new Map([[document.id, frequency]]));
70
+ }
71
+ }
72
+ }
73
+ }
74
+ finalize() {
75
+ for (const field of FIELD_NAMES) {
76
+ const total = this.fieldTokenTotals.get(field) ?? 0;
77
+ this.avgFieldLength.set(field, this.docCount > 0 ? total / this.docCount : 0);
78
+ }
79
+ }
80
+ search(queryTokens, topK, eligibleEntryIds) {
81
+ if (this.docCount === 0 || queryTokens.length === 0) {
82
+ return [];
83
+ }
84
+ const uniqueTerms = Array.from(new Set(queryTokens));
85
+ const totals = new Map();
86
+ const breakdowns = new Map();
87
+ for (const field of FIELD_NAMES) {
88
+ const weight = this.weights[field];
89
+ const avgdl = this.avgFieldLength.get(field) ?? 0;
90
+ const postingsForField = this.postings.get(field);
91
+ const lengthsForField = this.fieldLengths.get(field);
92
+ if (weight <= 0 || avgdl <= 0 || !postingsForField || !lengthsForField) {
93
+ continue;
94
+ }
95
+ for (const term of uniqueTerms) {
96
+ const docMap = postingsForField.get(term);
97
+ if (!docMap) {
98
+ continue;
99
+ }
100
+ const documentFrequency = docMap.size;
101
+ const idf = Math.log(1 +
102
+ (this.docCount - documentFrequency + 0.5) /
103
+ (documentFrequency + 0.5));
104
+ for (const [docId, frequency] of docMap) {
105
+ if (eligibleEntryIds && !eligibleEntryIds.has(docId)) {
106
+ continue;
107
+ }
108
+ const length = lengthsForField.get(docId) ?? 0;
109
+ const denominator = frequency + BM25_K1 * (1 - BM25_B + BM25_B * (length / avgdl));
110
+ const contribution = ((idf * (frequency * (BM25_K1 + 1))) / denominator) * weight;
111
+ totals.set(docId, (totals.get(docId) ?? 0) + contribution);
112
+ const breakdown = breakdowns.get(docId) ?? {};
113
+ breakdown[field] = (breakdown[field] ?? 0) + contribution;
114
+ breakdowns.set(docId, breakdown);
115
+ }
116
+ }
117
+ }
118
+ const matches = [];
119
+ for (const [id, score] of totals) {
120
+ matches.push({ id, score, fieldScores: breakdowns.get(id) ?? {} });
121
+ }
122
+ matches.sort((left, right) => right.score - left.score || compareIds(left.id, right.id));
123
+ return matches.slice(0, topK);
124
+ }
125
+ }
126
+ /** Deterministic tiebreak so equal scores produce a stable order. */
127
+ const compareIds = (left, right) => {
128
+ if (left < right) {
129
+ return -1;
130
+ }
131
+ if (left > right) {
132
+ return 1;
133
+ }
134
+ return 0;
135
+ };
136
+ const addToIndex = (index, key, id) => {
137
+ const bucket = index.get(key);
138
+ if (bucket) {
139
+ bucket.add(id);
140
+ }
141
+ else {
142
+ index.set(key, new Set([id]));
143
+ }
144
+ };
145
+ /**
146
+ * Build the internal search document from a normalized entry. `exactKeys`
147
+ * carry whole-phrase identifiers (entry id and title); each
148
+ * text field is tokenized for the field-aware scorer.
149
+ */
150
+ export const buildDocument = (entry) => {
151
+ const exactKeys = normalizePhrases([entry.id, entry.title]);
152
+ return {
153
+ id: entry.id,
154
+ exactKeys,
155
+ fields: {
156
+ title: tokenize(entry.title),
157
+ aliases: entry.aliases.flatMap((alias) => tokenize(alias)),
158
+ keywords: entry.keywords.flatMap((keyword) => tokenize(keyword)),
159
+ summary: tokenize(entry.summary),
160
+ body: tokenize(entry.body),
161
+ },
162
+ };
163
+ };
164
+ /**
165
+ * Build the complete immutable snapshot: entry map, exact/alias/relation
166
+ * indexes, and the field-aware lexical index.
167
+ */
168
+ export const buildIndexSnapshot = (entries, weights) => {
169
+ const entriesById = new Map();
170
+ const exactIndex = new Map();
171
+ const aliasIndex = new Map();
172
+ const relationIndex = new Map();
173
+ const lexical = new KnowledgeLexicalIndex(weights);
174
+ for (const entry of entries) {
175
+ entriesById.set(entry.id, entry);
176
+ const document = buildDocument(entry);
177
+ for (const key of document.exactKeys) {
178
+ addToIndex(exactIndex, key, entry.id);
179
+ }
180
+ for (const alias of entry.aliases) {
181
+ const key = normalizeText(alias);
182
+ if (key) {
183
+ addToIndex(aliasIndex, key, entry.id);
184
+ }
185
+ }
186
+ const relations = [...entry.relatedEntryIds];
187
+ if (entry.parentEntryId) {
188
+ relations.push(entry.parentEntryId);
189
+ }
190
+ if (relations.length > 0) {
191
+ relationIndex.set(entry.id, relations);
192
+ }
193
+ lexical.add(document);
194
+ }
195
+ lexical.finalize();
196
+ return {
197
+ entriesById,
198
+ exactIndex,
199
+ aliasIndex,
200
+ relationIndex,
201
+ lexical,
202
+ entryCount: entries.length,
203
+ };
204
+ };
205
+ //# sourceMappingURL=knowledgeIndex.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Deterministic text normalization for knowledge grounding.
3
+ *
4
+ * The SAME normalizer runs at index-build time and query time so that a
5
+ * configuration key, a technical identifier, and a user's phrasing all
6
+ * collapse to the same comparable token stream:
7
+ *
8
+ * "Multi-Step Flow" -> "multi step flow"
9
+ * "enableMultiStepFlow" -> "enable multi step flow"
10
+ * "ACCOUNT_STATUS" -> "account status"
11
+ * " Access Policy " -> "access policy"
12
+ *
13
+ * The pipeline is intentionally conservative: it splits identifiers and
14
+ * separators and folds case/diacritics, but it NEVER invents synonyms.
15
+ * Reviewed acronym and spelling expansions belong in an entry's `aliases`,
16
+ * not in this function.
17
+ */
18
+ /**
19
+ * Normalize an arbitrary string into a single space-delimited lowercase
20
+ * phrase. Idempotent: `normalizeText(normalizeText(x)) === normalizeText(x)`.
21
+ */
22
+ export declare const normalizeText: (input: string) => string;
23
+ /**
24
+ * Normalize then split into tokens. Empty input yields an empty array.
25
+ * Numbers and alphanumeric identifiers are preserved as their own tokens.
26
+ */
27
+ export declare const tokenize: (input: string) => string[];
28
+ /**
29
+ * Normalize each candidate phrase and drop blanks/duplicates while preserving
30
+ * first-seen order. Used to build the exact-key and alias key sets.
31
+ */
32
+ export declare const normalizePhrases: (phrases: string[]) => string[];
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Deterministic text normalization for knowledge grounding.
3
+ *
4
+ * The SAME normalizer runs at index-build time and query time so that a
5
+ * configuration key, a technical identifier, and a user's phrasing all
6
+ * collapse to the same comparable token stream:
7
+ *
8
+ * "Multi-Step Flow" -> "multi step flow"
9
+ * "enableMultiStepFlow" -> "enable multi step flow"
10
+ * "ACCOUNT_STATUS" -> "account status"
11
+ * " Access Policy " -> "access policy"
12
+ *
13
+ * The pipeline is intentionally conservative: it splits identifiers and
14
+ * separators and folds case/diacritics, but it NEVER invents synonyms.
15
+ * Reviewed acronym and spelling expansions belong in an entry's `aliases`,
16
+ * not in this function.
17
+ */
18
+ /**
19
+ * Insert spaces at camelCase / PascalCase / acronym boundaries. Must run
20
+ * BEFORE lowercasing because it relies on case information.
21
+ *
22
+ * "enableURLReading" -> "enable URL Reading"
23
+ * "customAccessOptionsV2" -> "custom Access Options V2"
24
+ */
25
+ const splitIdentifierBoundaries = (input) => input
26
+ // lower/number followed by an uppercase: "enableTwo" -> "enable Two"
27
+ .replace(/([\p{Ll}\p{N}])(\p{Lu})/gu, "$1 $2")
28
+ // acronym run followed by a Word: "URLReading" -> "URL Reading"
29
+ .replace(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, "$1 $2");
30
+ /**
31
+ * Normalize an arbitrary string into a single space-delimited lowercase
32
+ * phrase. Idempotent: `normalizeText(normalizeText(x)) === normalizeText(x)`.
33
+ */
34
+ export const normalizeText = (input) => {
35
+ if (!input) {
36
+ return "";
37
+ }
38
+ const boundaries = splitIdentifierBoundaries(
39
+ // NFKC folds compatibility forms; NFD + mark-strip removes diacritics.
40
+ input
41
+ .normalize("NFKC")
42
+ .normalize("NFD")
43
+ .replace(/\p{M}+/gu, ""));
44
+ return (boundaries
45
+ // any run of non-letter/non-number (snake_case, kebab-case, dots, slashes,
46
+ // punctuation, and existing whitespace) collapses to a single space.
47
+ .replace(/[^\p{L}\p{N}]+/gu, " ")
48
+ .trim()
49
+ .toLowerCase());
50
+ };
51
+ /**
52
+ * Normalize then split into tokens. Empty input yields an empty array.
53
+ * Numbers and alphanumeric identifiers are preserved as their own tokens.
54
+ */
55
+ export const tokenize = (input) => {
56
+ const normalized = normalizeText(input);
57
+ return normalized ? normalized.split(" ") : [];
58
+ };
59
+ /**
60
+ * Normalize each candidate phrase and drop blanks/duplicates while preserving
61
+ * first-seen order. Used to build the exact-key and alias key sets.
62
+ */
63
+ export const normalizePhrases = (phrases) => {
64
+ const seen = new Set();
65
+ const out = [];
66
+ for (const phrase of phrases) {
67
+ const normalized = normalizeText(phrase);
68
+ if (normalized && !seen.has(normalized)) {
69
+ seen.add(normalized);
70
+ out.push(normalized);
71
+ }
72
+ }
73
+ return out;
74
+ };
75
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1,18 @@
1
+ import type { KnowledgeEntryInput, KnowledgeManifest, KnowledgeNormalizeOptions, KnowledgeNormalizeResult, KnowledgeSource, NormalizedKnowledgeEntry } from "../types/index.js";
2
+ /**
3
+ * Resolve one authored entry into a complete `NormalizedKnowledgeEntry`,
4
+ * materializing every omitted optional (optional arrays to `[]`, `body` to "",
5
+ * `kind` to "text", `status` to "active") so downstream code never re-checks
6
+ * it. Authored arrays are cloned so the normalized snapshot is isolated from
7
+ * caller-owned references.
8
+ */
9
+ export declare const resolveEntry: (input: KnowledgeEntryInput, version: string) => NormalizedKnowledgeEntry;
10
+ /** Expand a build manifest into sources — one per catalog. */
11
+ export declare const manifestToSources: (manifest: KnowledgeManifest) => KnowledgeSource[];
12
+ /**
13
+ * Resolve and validate every source into normalized entries. `validation.ok`
14
+ * is false when any error-level issue was found; the caller (index builder)
15
+ * must not swap in an index built from an invalid set. Async so the public
16
+ * contract is stable if a future source kind needs asynchronous loading.
17
+ */
18
+ export declare const normalizeAndValidate: (sources: KnowledgeSource[], options: KnowledgeNormalizeOptions) => Promise<KnowledgeNormalizeResult>;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Source loading, default resolution, and generic validation.
3
+ *
4
+ * Required fields (id/title/summary/domain/integrations) come straight
5
+ * from the authored entry; omitted optionals fall back to SDK defaults. This
6
+ * covers only provider-neutral invariants (ids, required fields, enum values,
7
+ * relationship integrity). Host-specific rules — configuration-key coverage,
8
+ * taxonomy conflicts — are enforced by the host build BEFORE the manifest
9
+ * reaches NeuroLink. Errors block indexing; warnings are surfaced but do not.
10
+ */
11
+ import { decodeArray, decodeString } from "type-decoder/dist/index.js";
12
+ import { normalizeText } from "./normalize.js";
13
+ const SDK_DEFAULT_KIND = "text";
14
+ const SDK_DEFAULT_STATUS = "active";
15
+ const STATUSES = new Set(["draft", "active", "deprecated"]);
16
+ const KINDS = new Set([
17
+ "concept",
18
+ "text",
19
+ "configuration",
20
+ "tool",
21
+ "procedure",
22
+ "policy",
23
+ "troubleshooting",
24
+ ]);
25
+ /**
26
+ * Resolve one authored entry into a complete `NormalizedKnowledgeEntry`,
27
+ * materializing every omitted optional (optional arrays to `[]`, `body` to "",
28
+ * `kind` to "text", `status` to "active") so downstream code never re-checks
29
+ * it. Authored arrays are cloned so the normalized snapshot is isolated from
30
+ * caller-owned references.
31
+ */
32
+ export const resolveEntry = (input, version) => ({
33
+ id: input.id,
34
+ title: input.title,
35
+ summary: input.summary,
36
+ domain: input.domain,
37
+ integrations: decodeArray(input.integrations, decodeString) ?? [],
38
+ kind: input.kind ?? SDK_DEFAULT_KIND,
39
+ status: input.status ?? SDK_DEFAULT_STATUS,
40
+ body: input.body ?? "",
41
+ aliases: [...(input.aliases ?? [])],
42
+ keywords: [...(input.keywords ?? [])],
43
+ relatedEntryIds: [...(input.relatedEntryIds ?? [])],
44
+ parentEntryId: input.parentEntryId,
45
+ version,
46
+ });
47
+ /** Expand a build manifest into sources — one per catalog. */
48
+ export const manifestToSources = (manifest) => manifest.catalogs.map((catalog) => ({
49
+ id: catalog.id,
50
+ version: manifest.contentVersion,
51
+ entries: catalog.entries,
52
+ }));
53
+ /** Resolve a source's entries and its effective version. */
54
+ const loadSourceEntries = (source, manifestVersion) => ({
55
+ id: source.id,
56
+ version: source.version ?? manifestVersion,
57
+ entries: source.entries,
58
+ });
59
+ const error = (code, message, extra) => ({ level: "error", code, message, ...extra });
60
+ const warn = (code, message, extra) => ({ level: "warning", code, message, ...extra });
61
+ /**
62
+ * Resolve and validate every source into normalized entries. `validation.ok`
63
+ * is false when any error-level issue was found; the caller (index builder)
64
+ * must not swap in an index built from an invalid set. Async so the public
65
+ * contract is stable if a future source kind needs asynchronous loading.
66
+ */
67
+ export const normalizeAndValidate = async (sources, options) => {
68
+ const issues = [];
69
+ const entries = [];
70
+ const seenIds = new Set();
71
+ const aliasOwner = new Map();
72
+ for (const source of sources) {
73
+ const loaded = loadSourceEntries(source, options.manifestVersion);
74
+ if (!loaded.id) {
75
+ issues.push(error("missing-source-id", "Source has no id"));
76
+ }
77
+ for (const input of loaded.entries) {
78
+ if (!input.id) {
79
+ issues.push(error("missing-entry-id", "Entry has no id", { sourceId: loaded.id }));
80
+ continue;
81
+ }
82
+ if (seenIds.has(input.id)) {
83
+ issues.push(error("duplicate-entry-id", `Duplicate entry id "${input.id}"`, {
84
+ entryId: input.id,
85
+ }));
86
+ continue;
87
+ }
88
+ seenIds.add(input.id);
89
+ if (!input.title || !input.title.trim()) {
90
+ issues.push(error("missing-title", "Entry is missing a title", {
91
+ entryId: input.id,
92
+ }));
93
+ }
94
+ if (!input.summary || !input.summary.trim()) {
95
+ issues.push(error("missing-summary", "Entry is missing a summary", {
96
+ entryId: input.id,
97
+ }));
98
+ }
99
+ if (!input.domain || !input.domain.trim()) {
100
+ issues.push(error("missing-domain", "Entry is missing a domain", {
101
+ entryId: input.id,
102
+ }));
103
+ }
104
+ if (input.integrations === undefined) {
105
+ issues.push(error("missing-integrations", "Entry is missing integrations", {
106
+ entryId: input.id,
107
+ }));
108
+ }
109
+ else if (decodeArray(input.integrations, decodeString) === null) {
110
+ issues.push(error("bad-integrations", "Entry integrations must be an array of strings", { entryId: input.id }));
111
+ }
112
+ const normalized = resolveEntry(input, loaded.version);
113
+ if (!KINDS.has(normalized.kind)) {
114
+ issues.push(error("bad-kind", `Unsupported kind "${normalized.kind}"`, {
115
+ entryId: normalized.id,
116
+ }));
117
+ }
118
+ if (!STATUSES.has(normalized.status)) {
119
+ issues.push(error("bad-status", `Unsupported status "${normalized.status}"`, {
120
+ entryId: normalized.id,
121
+ }));
122
+ }
123
+ for (const alias of normalized.aliases) {
124
+ const key = normalizeText(alias);
125
+ if (!key) {
126
+ issues.push(warn("empty-alias", `Alias "${alias}" is empty after normalization`, { entryId: normalized.id }));
127
+ continue;
128
+ }
129
+ const owner = aliasOwner.get(key);
130
+ if (owner && owner !== normalized.id) {
131
+ issues.push(warn("duplicate-alias", `Alias "${alias}" also used by "${owner}"`, { entryId: normalized.id }));
132
+ }
133
+ else {
134
+ aliasOwner.set(key, normalized.id);
135
+ }
136
+ }
137
+ entries.push(normalized);
138
+ }
139
+ }
140
+ // Relationship integrity — resolved after all entries are collected.
141
+ const idSet = new Set(entries.map((entry) => entry.id));
142
+ for (const entry of entries) {
143
+ for (const related of entry.relatedEntryIds) {
144
+ if (!idSet.has(related)) {
145
+ issues.push(warn("broken-relation", `relatedEntryId "${related}" not found`, {
146
+ entryId: entry.id,
147
+ }));
148
+ }
149
+ }
150
+ if (entry.parentEntryId && !idSet.has(entry.parentEntryId)) {
151
+ issues.push(warn("broken-parent", `parentEntryId "${entry.parentEntryId}" not found`, { entryId: entry.id }));
152
+ }
153
+ }
154
+ const ok = !issues.some((issue) => issue.level === "error");
155
+ return { entries, validation: { ok, issues } };
156
+ };
157
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Lexical-first retrieval pipeline.
3
+ *
4
+ * For one turn: authorize/filter BEFORE ranking, resolve exact identifiers and
5
+ * reviewed aliases via query n-gram lookup, add field-aware BM25 over a bounded
6
+ * query+history window, combine the deterministic signals, take the top
7
+ * candidates, expand a bounded set of directly-related entries, and classify
8
+ * confidence. No LLM, no embeddings, no vector store.
9
+ */
10
+ import type { KnowledgeIndexSnapshot, KnowledgeResolvedRetrieval, KnowledgeRetrievalRequest, KnowledgeSelection } from "../types/index.js";
11
+ /**
12
+ * Run retrieval against a ready snapshot. Returns the primary selection, a
13
+ * bounded relationship expansion, the scored candidate list (diagnostics), and
14
+ * a confidence class. Context assembly is a separate, later step.
15
+ */
16
+ export declare const retrieve: (snapshot: KnowledgeIndexSnapshot, request: KnowledgeRetrievalRequest, config: KnowledgeResolvedRetrieval, blockedDomains?: string[]) => KnowledgeSelection;