@hraness/kb 0.17.1

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 (200) hide show
  1. package/DISCLOSURE +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +545 -0
  4. package/dist/agent-context.js +35 -0
  5. package/dist/agent-guide-audit.js +18 -0
  6. package/dist/attachments.js +19 -0
  7. package/dist/authoring.js +33 -0
  8. package/dist/benchmark.js +19 -0
  9. package/dist/capture.js +49 -0
  10. package/dist/cli.js +3646 -0
  11. package/dist/clip/acquire.js +57 -0
  12. package/dist/clip/args.js +21 -0
  13. package/dist/clip/bounded-byte-buffer.js +9 -0
  14. package/dist/clip/browser-profiles.js +17 -0
  15. package/dist/clip/bundle-reader.js +11 -0
  16. package/dist/clip/cli.js +27 -0
  17. package/dist/clip/cookies.js +21 -0
  18. package/dist/clip/defuddle-worker.js +84 -0
  19. package/dist/clip/doctor.js +32 -0
  20. package/dist/clip/jobs.js +784 -0
  21. package/dist/clip/network-proxy.js +9 -0
  22. package/dist/clip/network.js +28 -0
  23. package/dist/clip/persist.js +30 -0
  24. package/dist/clip/refresh.js +11 -0
  25. package/dist/clip/terminal.js +11 -0
  26. package/dist/evaluation-builder.js +12238 -0
  27. package/dist/evaluation-kb.js +26 -0
  28. package/dist/evaluation.js +35 -0
  29. package/dist/git.js +39 -0
  30. package/dist/graph.js +39 -0
  31. package/dist/index-01jj6rbv.js +1103 -0
  32. package/dist/index-06c9ctr6.js +738 -0
  33. package/dist/index-0kavxzqj.js +2480 -0
  34. package/dist/index-1gwbassd.js +743 -0
  35. package/dist/index-1n418kb9.js +827 -0
  36. package/dist/index-1vrd1rmn.js +56 -0
  37. package/dist/index-1xxnjn0d.js +171 -0
  38. package/dist/index-3rm7cz6h.js +373 -0
  39. package/dist/index-3v2z4f0q.js +328 -0
  40. package/dist/index-48pz4jpc.js +392 -0
  41. package/dist/index-4j3tt0c3.js +234 -0
  42. package/dist/index-5n05se68.js +307 -0
  43. package/dist/index-5vwpzb5a.js +764 -0
  44. package/dist/index-6g2pv9d2.js +370 -0
  45. package/dist/index-7fzc32gf.js +712 -0
  46. package/dist/index-84x0vjjp.js +432 -0
  47. package/dist/index-adx6khj5.js +488 -0
  48. package/dist/index-b88v3vtm.js +805 -0
  49. package/dist/index-cv6fh7z5.js +462 -0
  50. package/dist/index-cxfrakt7.js +1067 -0
  51. package/dist/index-d13v9ckt.js +150 -0
  52. package/dist/index-dycjbhw2.js +1943 -0
  53. package/dist/index-dyqwejk5.js +531 -0
  54. package/dist/index-e5fbsywq.js +658 -0
  55. package/dist/index-f984hw45.js +927 -0
  56. package/dist/index-gh719d91.js +63 -0
  57. package/dist/index-hgve9rh2.js +2046 -0
  58. package/dist/index-hya40gb2.js +474 -0
  59. package/dist/index-j4zgmzjr.js +152 -0
  60. package/dist/index-jsmvyyvf.js +1166 -0
  61. package/dist/index-kh0pwvdd.js +1974 -0
  62. package/dist/index-mqx4nd6v.js +119 -0
  63. package/dist/index-mxxxytys.js +1254 -0
  64. package/dist/index-n5dd7r0v.js +711 -0
  65. package/dist/index-npg9z1a4.js +431 -0
  66. package/dist/index-pj501bh1.js +140 -0
  67. package/dist/index-qry4vhxk.js +1041 -0
  68. package/dist/index-s2gw5aw9.js +199 -0
  69. package/dist/index-vvdj6kca.js +70 -0
  70. package/dist/index-w2zc0vwa.js +571 -0
  71. package/dist/index-x3fthpsc.js +585 -0
  72. package/dist/index-zr53sf63.js +44 -0
  73. package/dist/index-zxdy5pby.js +2728 -0
  74. package/dist/index-zzhgcwyt.js +966 -0
  75. package/dist/index.js +453 -0
  76. package/dist/navigation.js +13 -0
  77. package/dist/pdf.js +85 -0
  78. package/dist/percolate.js +24 -0
  79. package/dist/portfolio.js +77 -0
  80. package/dist/query.js +29 -0
  81. package/dist/repository-memory.js +60 -0
  82. package/dist/sdk.js +36 -0
  83. package/dist/search-rules.js +34 -0
  84. package/dist/search.js +22 -0
  85. package/dist/semantic.js +39 -0
  86. package/dist/source-inbox.js +17 -0
  87. package/dist/untrusted-content.js +32 -0
  88. package/dist/url-intelligence.js +74 -0
  89. package/dist/workflow.js +23 -0
  90. package/dist/workflows/decision-context.js +20 -0
  91. package/dist/workflows/explain-change.js +8 -0
  92. package/dist/workflows/index.js +28 -0
  93. package/dist/workflows/plan-radar.js +10 -0
  94. package/package.json +409 -0
  95. package/skills/kb/AGENTS.md +19 -0
  96. package/skills/kb/SKILL.md +82 -0
  97. package/skills/kb/agents/openai.yaml +4 -0
  98. package/skills/kb/references/pdf-review.md +51 -0
  99. package/skills/kb/references/percolate.md +144 -0
  100. package/skills/kb/references/plan-structure.md +105 -0
  101. package/skills/kb/references/plan.md +115 -0
  102. package/skills/kb/references/query.md +231 -0
  103. package/skills/kb/references/refresh.md +133 -0
  104. package/skills/kb/references/save-pdf.md +125 -0
  105. package/skills/kb/references/save-url.md +178 -0
  106. package/skills/kb/references/url-authentication.md +75 -0
  107. package/skills/kb/references/url-platforms.md +25 -0
  108. package/src/agent-context.ts +1241 -0
  109. package/src/agent-guide-audit.ts +750 -0
  110. package/src/attachments.ts +723 -0
  111. package/src/authoring.ts +1725 -0
  112. package/src/benchmark.ts +362 -0
  113. package/src/capture.ts +68 -0
  114. package/src/cli.ts +3660 -0
  115. package/src/clip/acquire.ts +1273 -0
  116. package/src/clip/archive-today.ts +402 -0
  117. package/src/clip/args.ts +423 -0
  118. package/src/clip/assets.ts +262 -0
  119. package/src/clip/bounded-byte-buffer.ts +75 -0
  120. package/src/clip/browser-profiles.ts +372 -0
  121. package/src/clip/bundle-reader.ts +608 -0
  122. package/src/clip/capture.ts +1054 -0
  123. package/src/clip/cli.ts +181 -0
  124. package/src/clip/cookies.ts +512 -0
  125. package/src/clip/doctor.ts +1110 -0
  126. package/src/clip/extract.ts +868 -0
  127. package/src/clip/jobs.ts +959 -0
  128. package/src/clip/lib.ts +861 -0
  129. package/src/clip/media.ts +1474 -0
  130. package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
  131. package/src/clip/metadata-search-tool/Cargo.toml +18 -0
  132. package/src/clip/metadata-search-tool/runner.ts +685 -0
  133. package/src/clip/metadata-search-tool/src/main.rs +389 -0
  134. package/src/clip/metadata-search.ts +687 -0
  135. package/src/clip/network-proxy.ts +627 -0
  136. package/src/clip/network.ts +950 -0
  137. package/src/clip/package-root.ts +54 -0
  138. package/src/clip/persist.ts +1472 -0
  139. package/src/clip/platforms.ts +1824 -0
  140. package/src/clip/refresh.ts +197 -0
  141. package/src/clip/structured.ts +432 -0
  142. package/src/clip/terminal.ts +197 -0
  143. package/src/clip/url-intelligence.ts +581 -0
  144. package/src/clip/url-metadata-backfill.ts +616 -0
  145. package/src/clip/url-metadata-cli.ts +251 -0
  146. package/src/clip/url-metadata.ts +1397 -0
  147. package/src/evaluation-analysis-v2.ts +1440 -0
  148. package/src/evaluation-builder.ts +2539 -0
  149. package/src/evaluation-corpus-authoring.ts +2052 -0
  150. package/src/evaluation-evidence.ts +1147 -0
  151. package/src/evaluation-execution-v2.ts +426 -0
  152. package/src/evaluation-implementation.ts +131 -0
  153. package/src/evaluation-kb-closure.ts +1430 -0
  154. package/src/evaluation-kb-runner-v2.ts +735 -0
  155. package/src/evaluation-kb-v2.ts +2567 -0
  156. package/src/evaluation-kb.ts +1018 -0
  157. package/src/evaluation-measurement-v2.ts +1462 -0
  158. package/src/evaluation-packing-v2.ts +452 -0
  159. package/src/evaluation-redaction.ts +28 -0
  160. package/src/evaluation-v2.ts +3857 -0
  161. package/src/evaluation.ts +1220 -0
  162. package/src/git.ts +1276 -0
  163. package/src/graph.ts +1487 -0
  164. package/src/index.ts +20 -0
  165. package/src/init.ts +123 -0
  166. package/src/navigation.ts +277 -0
  167. package/src/note-lock.ts +529 -0
  168. package/src/pdf/args.ts +202 -0
  169. package/src/pdf/capture.ts +298 -0
  170. package/src/pdf/cli.ts +159 -0
  171. package/src/pdf/extract.ts +303 -0
  172. package/src/pdf/layout.ts +618 -0
  173. package/src/pdf/markdown.ts +154 -0
  174. package/src/pdf/model.ts +234 -0
  175. package/src/pdf/ocr.ts +190 -0
  176. package/src/pdf/persist.ts +314 -0
  177. package/src/pdf/source.ts +135 -0
  178. package/src/pdf/tools.ts +146 -0
  179. package/src/pdf.ts +92 -0
  180. package/src/percolate.ts +877 -0
  181. package/src/portfolio-audit.ts +574 -0
  182. package/src/portfolio-identity.ts +162 -0
  183. package/src/portfolio-registry.ts +473 -0
  184. package/src/portfolio.ts +765 -0
  185. package/src/query.ts +646 -0
  186. package/src/repository-memory.ts +1150 -0
  187. package/src/sdk.ts +1452 -0
  188. package/src/search-rules.ts +712 -0
  189. package/src/search.ts +710 -0
  190. package/src/semantic-runtime.ts +1139 -0
  191. package/src/semantic.ts +2480 -0
  192. package/src/source-inbox.ts +232 -0
  193. package/src/untrusted-content.ts +401 -0
  194. package/src/url-intelligence.ts +104 -0
  195. package/src/vault.ts +636 -0
  196. package/src/workflow.ts +653 -0
  197. package/src/workflows/decision-context.ts +81 -0
  198. package/src/workflows/explain-change.ts +73 -0
  199. package/src/workflows/index.ts +3 -0
  200. package/src/workflows/plan-radar.ts +101 -0
package/src/sdk.ts ADDED
@@ -0,0 +1,1452 @@
1
+ import { resolve } from "node:path";
2
+
3
+ import type { MetadataObject, Note } from "./graph.js";
4
+ import { lookupNote } from "./graph.js";
5
+ import {
6
+ GitHistoryError,
7
+ gitHistoryForNotes,
8
+ indexGitHistory,
9
+ searchGitHistory,
10
+ validateGitHistoryForNotesOptions,
11
+ validateGitHistoryForNotesRequest,
12
+ validateSearchGitHistoryOptions,
13
+ type GitHistoryDependencies,
14
+ type GitHistoryForNotesOptions,
15
+ type GitHistoryForNotesResult,
16
+ type GitHistoryIndexResult,
17
+ type GitHistorySearchResult,
18
+ type SearchGitHistoryOptions,
19
+ } from "./git.js";
20
+ import {
21
+ navigateLinks,
22
+ NavigationBudgetError,
23
+ type LinkNeighborhood,
24
+ type NavigateLinksOptions,
25
+ } from "./navigation.js";
26
+ import {
27
+ queryVault,
28
+ type QueryOptions,
29
+ type QueryRow,
30
+ } from "./query.js";
31
+ import {
32
+ buildGraphContext,
33
+ fuseRankedCandidates,
34
+ searchExactVault,
35
+ validateSearchQuery,
36
+ type ExactMatchEvidence,
37
+ type ExactSearchHit,
38
+ type GraphContext,
39
+ type GraphContextOptions,
40
+ type FusionContribution,
41
+ } from "./search.js";
42
+ import {
43
+ expandSearchRequest,
44
+ parseSearchRules,
45
+ prioritizeSearchHits,
46
+ type SearchPriorityTrace,
47
+ type SearchRuleRequest,
48
+ type SearchRulesV1,
49
+ } from "./search-rules.js";
50
+ import {
51
+ openSemanticSearchSession,
52
+ recommendedEmbeddingModel,
53
+ type SemanticDependencies,
54
+ type SemanticQueryEmbeddingAccounting,
55
+ type SemanticSearchHit,
56
+ type SemanticSearchMode,
57
+ type SemanticSearchSession,
58
+ type VerifiedEmbeddingModelLease,
59
+ } from "./semantic.js";
60
+ import {
61
+ scanVault,
62
+ type ScanVaultOptions,
63
+ type VaultSnapshot,
64
+ } from "./vault.js";
65
+ import {
66
+ createUntrustedToolResult,
67
+ UntrustedContentBudgetError,
68
+ type UntrustedStructuredContent,
69
+ } from "./untrusted-content.js";
70
+
71
+ export const MAX_SEARCH_RESULTS = 100;
72
+ export const MAX_SEARCH_CANDIDATES = 500;
73
+ export const DEFAULT_SEARCH_RESULTS = 10;
74
+ const MAX_READ_BYTES = 64 * 1_024;
75
+ const DEFAULT_CONTEXT_BYTES = 24 * 1_024;
76
+ const MAX_CONTEXT_BYTES = 64 * 1_024;
77
+ export const MIN_UNTRUSTED_CONTEXT_BYTES = 512;
78
+ const DEFAULT_SEARCH_HISTORY_NOTES = 5;
79
+ const MAX_SEARCH_HISTORY_NOTES = 20;
80
+ export const MAX_SEARCH_RELATED_SEEDS = 5;
81
+ export const MAX_SEARCH_NOTE_REFERENCE_BYTES = 16 * 1_024;
82
+
83
+ export type KnowledgeBaseSearchMode = "exact" | SemanticSearchMode;
84
+ export type KnowledgeBaseSearchOrdering = "relevance" | "priority-then-relevance";
85
+
86
+ export type KnowledgeBaseGraphOptions = Omit<
87
+ GraphContextOptions,
88
+ "seeds" | "primaryIds"
89
+ > & {
90
+ /** Explicit note identities to seed before the strongest text results. */
91
+ readonly related?: readonly string[];
92
+ };
93
+
94
+ export type KnowledgeBaseHistoryOptions = GitHistoryForNotesOptions & {
95
+ /** Number of top primary results enriched with Git provenance. */
96
+ readonly noteLimit?: number;
97
+ /** Reject the search when Git provenance cannot be produced. */
98
+ readonly policy?: "auto" | "required";
99
+ };
100
+
101
+ export type KnowledgeBaseSearchOptions = {
102
+ readonly query: string;
103
+ readonly mode?: KnowledgeBaseSearchMode;
104
+ /** Preserve relevance by default; configured priority rules remain explicitly opt-in. */
105
+ readonly ordering?: KnowledgeBaseSearchOrdering;
106
+ readonly filters?: QueryOptions["filters"];
107
+ readonly tags?: readonly string[];
108
+ /** Match any exact, case-sensitive canonical authored repository scope. */
109
+ readonly repositoryScopes?: readonly string[];
110
+ readonly limit?: number;
111
+ readonly candidateLimit?: number;
112
+ /** QMD-local score cutoff for hybrid, keyword, and semantic modes. */
113
+ readonly minScore?: number;
114
+ /** Explicit graph context is separate from the primary relevance rank. */
115
+ readonly graph?: false | KnowledgeBaseGraphOptions;
116
+ /** Git provenance is separate from the primary relevance rank. */
117
+ readonly history?: false | "auto" | "required" | KnowledgeBaseHistoryOptions;
118
+ };
119
+
120
+ export type KnowledgeBaseExactEvidence = {
121
+ readonly kind: "exact";
122
+ readonly rank: number;
123
+ readonly identity: boolean;
124
+ readonly matches: readonly ExactMatchEvidence[];
125
+ };
126
+
127
+ export type KnowledgeBaseQmdEvidence = {
128
+ readonly kind: "qmd";
129
+ readonly rank: number;
130
+ /** Exact live-vault path returned by the QMD row. */
131
+ readonly path?: string;
132
+ /** One-based source line for the QMD snippet when the backend exposes it. */
133
+ readonly line?: number;
134
+ readonly source: SemanticSearchHit["source"];
135
+ /** Backend-local score. It is not comparable across search modes. */
136
+ readonly score: number;
137
+ readonly signals?: SemanticSearchHit["signals"];
138
+ };
139
+
140
+ export type KnowledgeBaseSearchEvidence =
141
+ | KnowledgeBaseExactEvidence
142
+ | KnowledgeBaseQmdEvidence;
143
+
144
+ export type KnowledgeBaseSearchHit = {
145
+ readonly id: string;
146
+ readonly path: string;
147
+ readonly title: string;
148
+ readonly rank: number;
149
+ /** Weighted reciprocal-rank score. It is not a probability. */
150
+ readonly score: number;
151
+ readonly identity: boolean;
152
+ readonly line?: number;
153
+ readonly snippet: string;
154
+ readonly tags: readonly string[];
155
+ readonly metadata: MetadataObject;
156
+ readonly evidence: readonly KnowledgeBaseSearchEvidence[];
157
+ readonly contributions: readonly FusionContribution[];
158
+ };
159
+
160
+ export type KnowledgeBaseSearchDiagnostic = {
161
+ readonly lane: "exact" | "git" | "graph" | "qmd";
162
+ readonly status: "degraded" | "ready" | "unavailable";
163
+ readonly results: number;
164
+ readonly message?: string;
165
+ };
166
+
167
+ export type KnowledgeBaseSearchPriorityApplication = {
168
+ readonly ordering: "priority-then-relevance";
169
+ readonly trace: readonly SearchPriorityTrace[];
170
+ };
171
+
172
+ export type KnowledgeBaseSearchRuleApplication =
173
+ | {
174
+ /** Configured leading alias consumed from the caller query. */
175
+ readonly alias: string;
176
+ /** Retrieval query after the alias's query and constraints were expanded. */
177
+ readonly effectiveQuery: string;
178
+ readonly priority?: KnowledgeBaseSearchPriorityApplication;
179
+ }
180
+ | {
181
+ readonly alias?: never;
182
+ readonly effectiveQuery?: never;
183
+ readonly priority: KnowledgeBaseSearchPriorityApplication;
184
+ };
185
+
186
+ export type KnowledgeBaseSearchResult = {
187
+ readonly query: string;
188
+ readonly mode: KnowledgeBaseSearchMode;
189
+ readonly results: readonly KnowledgeBaseSearchHit[];
190
+ readonly graph: GraphContext | null;
191
+ readonly history: GitHistoryForNotesResult | null;
192
+ readonly partial: boolean;
193
+ /** Present only when an alias or at least one priority rule was applied. */
194
+ readonly rules?: KnowledgeBaseSearchRuleApplication;
195
+ readonly diagnostics: {
196
+ readonly notes: number;
197
+ readonly model: string | null;
198
+ readonly elapsedMs: number;
199
+ /** Exact query-vector work; null only when an optional backend fallback hid it. */
200
+ readonly queryEmbedding?: SemanticQueryEmbeddingAccounting | null;
201
+ readonly lanes: readonly KnowledgeBaseSearchDiagnostic[];
202
+ };
203
+ };
204
+
205
+ export type KnowledgeBaseReadResult = {
206
+ readonly id: string;
207
+ readonly path: string;
208
+ readonly title: string;
209
+ readonly content: string;
210
+ readonly truncated: boolean;
211
+ };
212
+
213
+ export type OpenKnowledgeBaseOptions = {
214
+ readonly root: string;
215
+ /** Strict v1 aliases and advisory priority rules for this session. */
216
+ readonly searchRules?: SearchRulesV1;
217
+ /** Stable logical vault context used only by search rules with a vaultId predicate. */
218
+ readonly vaultId?: string;
219
+ /** Repository root enables bounded Git history and provenance. */
220
+ readonly repository?: string;
221
+ readonly database?: string;
222
+ /** Verified local bytes for the pinned semantic model. */
223
+ readonly embeddingModelFile?: string;
224
+ /** Shared opaque lease for verified local bytes. Mutually exclusive with embeddingModelFile. */
225
+ readonly embeddingModelLease?: VerifiedEmbeddingModelLease;
226
+ /** Require QMD's measurable store-local query-vector boundary. */
227
+ readonly requireStoreLocalVectorBoundary?: boolean;
228
+ readonly scan?: Omit<ScanVaultOptions, "mentionScope">;
229
+ };
230
+
231
+ export type KnowledgeBaseDependencies = {
232
+ readonly scanVault?: typeof scanVault;
233
+ readonly semantic?: SemanticDependencies;
234
+ readonly openSemanticSearchSession?: typeof openSemanticSearchSession;
235
+ /** Already-open query session whose ownership transfers after identity validation. */
236
+ readonly semanticSession?: SemanticSearchSession;
237
+ readonly git?: GitHistoryDependencies;
238
+ readonly indexGitHistory?: typeof indexGitHistory;
239
+ };
240
+
241
+ export type KnowledgeBaseSession = {
242
+ readonly root: string;
243
+ readonly repository?: string;
244
+ readonly noteCount: number;
245
+ readonly grep: (options: Parameters<typeof searchExactVault>[2]) => readonly ExactSearchHit[];
246
+ readonly list: (options?: QueryOptions) => readonly QueryRow[];
247
+ readonly read: (note: string, options?: { readonly maxBytes?: number }) => KnowledgeBaseReadResult;
248
+ readonly links: (note: string, options?: NavigateLinksOptions) => LinkNeighborhood;
249
+ readonly backlinks: (
250
+ note: string,
251
+ options?: Omit<NavigateLinksOptions, "direction">,
252
+ ) => LinkNeighborhood;
253
+ readonly search: (options: KnowledgeBaseSearchOptions) => Promise<KnowledgeBaseSearchResult>;
254
+ readonly history: (
255
+ noteIds: readonly string[],
256
+ options?: GitHistoryForNotesOptions,
257
+ ) => Promise<GitHistoryForNotesResult>;
258
+ readonly searchHistory: (options: SearchGitHistoryOptions) => Promise<GitHistorySearchResult>;
259
+ readonly close: () => Promise<void>;
260
+ };
261
+
262
+ function checkedLimit(
263
+ value: number | undefined,
264
+ fallback: number,
265
+ maximum: number,
266
+ label: string,
267
+ ): number {
268
+ const limit = value ?? fallback;
269
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > maximum) {
270
+ throw new RangeError(`${label} must be an integer from 1 through ${maximum}.`);
271
+ }
272
+ return limit;
273
+ }
274
+
275
+ function noteOrThrow(notes: readonly Note[], query: string): Note {
276
+ const result = lookupNote(notes, query);
277
+ if (result.kind === "found") return result.note;
278
+ if (result.kind === "ambiguous") {
279
+ throw new Error(
280
+ `Knowledge-base note ${JSON.stringify(query)} is ambiguous: `
281
+ + result.candidates.map(({ path }) => path).join(", "),
282
+ );
283
+ }
284
+ throw new Error(`Knowledge-base note ${JSON.stringify(query)} was not found.`);
285
+ }
286
+
287
+ function errorMessage(error: unknown): string {
288
+ return error instanceof Error ? error.message : String(error);
289
+ }
290
+
291
+ function utf8Prefix(value: string, maximumBytes: number): {
292
+ readonly value: string;
293
+ readonly truncated: boolean;
294
+ readonly bytes: number;
295
+ } {
296
+ let bytes = 0;
297
+ let end = 0;
298
+ for (const character of value) {
299
+ const width = Buffer.byteLength(character, "utf8");
300
+ if (bytes + width > maximumBytes) {
301
+ return { value: value.slice(0, end), truncated: true, bytes };
302
+ }
303
+ bytes += width;
304
+ end += character.length;
305
+ }
306
+ return { value, truncated: false, bytes };
307
+ }
308
+
309
+ function unavailableHistory(
310
+ root: string,
311
+ reason: string,
312
+ ): GitHistoryIndexResult {
313
+ return {
314
+ status: "unavailable",
315
+ repository: "",
316
+ root,
317
+ vaultPrefix: "",
318
+ reason,
319
+ };
320
+ }
321
+
322
+ function limitedHistoryMessage(
323
+ commits: readonly { readonly pathLimit: number }[],
324
+ ): string {
325
+ const count = commits.length;
326
+ const limit = commits[0]?.pathLimit ?? 0;
327
+ return `${count} Git commit${count === 1 ? "" : "s"} exceeded the ${limit.toLocaleString("en-US")} changed-path detail limit; co-change evidence is incomplete.`;
328
+ }
329
+
330
+ function qmdMode(mode: KnowledgeBaseSearchMode): SemanticSearchMode | null {
331
+ return mode === "exact" ? null : mode;
332
+ }
333
+
334
+ function checkedSearchMode(value: unknown): KnowledgeBaseSearchMode {
335
+ const mode = value ?? "hybrid";
336
+ if (
337
+ mode !== "exact"
338
+ && mode !== "hybrid"
339
+ && mode !== "keyword"
340
+ && mode !== "semantic"
341
+ ) {
342
+ throw new Error(
343
+ 'Knowledge-base search mode must be "exact", "hybrid", "keyword", or "semantic".',
344
+ );
345
+ }
346
+ return mode;
347
+ }
348
+
349
+ function checkedSearchOrdering(value: unknown): KnowledgeBaseSearchOrdering {
350
+ const ordering = value ?? "relevance";
351
+ if (ordering !== "relevance" && ordering !== "priority-then-relevance") {
352
+ throw new TypeError(
353
+ 'Knowledge-base search ordering must be "relevance" or "priority-then-relevance".',
354
+ );
355
+ }
356
+ return ordering;
357
+ }
358
+
359
+ type CheckedGraphOptions = {
360
+ readonly related: readonly string[];
361
+ readonly depth: number;
362
+ readonly neighborsPerSeed: number;
363
+ readonly limit: number;
364
+ };
365
+
366
+ function checkedGraphOptions(value: unknown): CheckedGraphOptions | null {
367
+ if (value === false) return null;
368
+ if (value !== undefined && (typeof value !== "object" || value === null || Array.isArray(value))) {
369
+ throw new TypeError("Search graph options must be false or an options object.");
370
+ }
371
+ const options = (value ?? {}) as KnowledgeBaseGraphOptions;
372
+ const related: unknown = options.related ?? [];
373
+ if (!Array.isArray(related)) {
374
+ throw new TypeError("Search related-note seeds must be an array.");
375
+ }
376
+ if (related.length > MAX_SEARCH_RELATED_SEEDS) {
377
+ throw new RangeError(
378
+ `Hybrid search accepts at most ${MAX_SEARCH_RELATED_SEEDS} explicit related-note seeds.`,
379
+ );
380
+ }
381
+ const checkedRelated: string[] = [];
382
+ for (const [index, seed] of (related as readonly unknown[]).entries()) {
383
+ if (typeof seed !== "string") {
384
+ throw new TypeError(`Search related-note seed ${index + 1} must be a string.`);
385
+ }
386
+ if (seed.trim() === "") {
387
+ throw new TypeError(`Search related-note seed ${index + 1} must not be empty.`);
388
+ }
389
+ if (Buffer.byteLength(seed, "utf8") > MAX_SEARCH_NOTE_REFERENCE_BYTES) {
390
+ throw new RangeError(
391
+ `Search related-note seed ${index + 1} must be at most `
392
+ + `${MAX_SEARCH_NOTE_REFERENCE_BYTES.toLocaleString("en-US")} UTF-8 bytes.`,
393
+ );
394
+ }
395
+ checkedRelated.push(seed);
396
+ }
397
+ return Object.freeze({
398
+ related: Object.freeze(checkedRelated),
399
+ depth: checkedLimit(options.depth, 1, 2, "Graph context depth"),
400
+ neighborsPerSeed: checkedLimit(
401
+ options.neighborsPerSeed,
402
+ 3,
403
+ 20,
404
+ "Graph neighbors per seed",
405
+ ),
406
+ limit: checkedLimit(options.limit, 20, 100, "Graph context limit"),
407
+ });
408
+ }
409
+
410
+ function resolvedGraphSeeds(notes: readonly Note[], seeds: readonly string[]): readonly string[] {
411
+ return seeds.map((seed) => {
412
+ const lookup = lookupNote(notes, seed);
413
+ if (lookup.kind === "missing") {
414
+ throw new Error(`Graph context seed ${JSON.stringify(seed)} was not found.`);
415
+ }
416
+ if (lookup.kind === "ambiguous") {
417
+ throw new Error(
418
+ `Graph context seed ${JSON.stringify(seed)} is ambiguous: `
419
+ + lookup.candidates.map(({ path }) => path).join(", "),
420
+ );
421
+ }
422
+ return lookup.note.id;
423
+ });
424
+ }
425
+
426
+ type CheckedHistoryRequest =
427
+ | { readonly enabled: false }
428
+ | {
429
+ readonly enabled: true;
430
+ readonly required: boolean;
431
+ readonly noteLimit: number;
432
+ readonly options: Required<GitHistoryForNotesOptions>;
433
+ };
434
+
435
+ function checkedHistoryRequest(value: unknown): CheckedHistoryRequest {
436
+ if (value === undefined || value === false) return { enabled: false };
437
+ let options: KnowledgeBaseHistoryOptions;
438
+ let required: boolean;
439
+ if (value === "auto") {
440
+ options = {};
441
+ required = false;
442
+ } else if (value === "required") {
443
+ options = {};
444
+ required = true;
445
+ } else if (typeof value !== "object" || value === null || Array.isArray(value)) {
446
+ throw new Error(
447
+ 'Search history must be false, "auto", "required", or an options object.',
448
+ );
449
+ } else {
450
+ options = value;
451
+ if (
452
+ options.policy !== undefined
453
+ && options.policy !== "auto"
454
+ && options.policy !== "required"
455
+ ) {
456
+ throw new Error('Search history policy must be "auto" or "required".');
457
+ }
458
+ required = options.policy === "required";
459
+ }
460
+ return {
461
+ enabled: true,
462
+ required,
463
+ noteLimit: checkedLimit(
464
+ options.noteLimit,
465
+ DEFAULT_SEARCH_HISTORY_NOTES,
466
+ MAX_SEARCH_HISTORY_NOTES,
467
+ "Git history note limit",
468
+ ),
469
+ options: validateGitHistoryForNotesOptions(options),
470
+ };
471
+ }
472
+
473
+ /** Validate the search-history request before any optional backend work begins. */
474
+ export function validateKnowledgeBaseSearchHistory(value: unknown): void {
475
+ checkedHistoryRequest(value);
476
+ }
477
+
478
+ /** Open a read-only session that shares one live Markdown scan across retrieval tools. */
479
+ export async function openKnowledgeBase(
480
+ options: OpenKnowledgeBaseOptions,
481
+ dependencies: KnowledgeBaseDependencies = {},
482
+ ): Promise<KnowledgeBaseSession> {
483
+ const searchRules = options.searchRules === undefined
484
+ ? null
485
+ : parseSearchRules(options.searchRules);
486
+ if (options.embeddingModelFile !== undefined && options.embeddingModelLease !== undefined) {
487
+ throw new TypeError(
488
+ "embeddingModelFile and embeddingModelLease are mutually exclusive.",
489
+ );
490
+ }
491
+ if (
492
+ dependencies.semanticSession !== undefined
493
+ && dependencies.openSemanticSearchSession !== undefined
494
+ ) {
495
+ throw new TypeError(
496
+ "semanticSession and openSemanticSearchSession dependencies are mutually exclusive.",
497
+ );
498
+ }
499
+ const snapshot: VaultSnapshot = await (dependencies.scanVault ?? scanVault)(
500
+ options.root,
501
+ { ...(options.scan ?? {}), mentionScope: false },
502
+ );
503
+ const notesById = new Map(snapshot.notes.map((note) => [note.id, note]));
504
+ const notesByPath = new Map(snapshot.notes.map((note) => [note.path, note]));
505
+ const injectedSemantic = dependencies.semanticSession;
506
+ if (
507
+ injectedSemantic !== undefined
508
+ && (
509
+ resolve(injectedSemantic.root) !== resolve(snapshot.root)
510
+ || (
511
+ options.database !== undefined
512
+ && resolve(injectedSemantic.database) !== resolve(options.database)
513
+ )
514
+ || injectedSemantic.model !== recommendedEmbeddingModel
515
+ )
516
+ ) {
517
+ throw new TypeError(
518
+ "The preopened semantic session does not match the knowledge-base snapshot, database, and model.",
519
+ );
520
+ }
521
+ let closeRequested = false;
522
+ let closePromise: Promise<void> | undefined;
523
+ let semanticPromise: Promise<SemanticSearchSession> | undefined = injectedSemantic === undefined
524
+ ? undefined
525
+ : Promise.resolve(injectedSemantic);
526
+ let gitPromise: Promise<GitHistoryIndexResult> | undefined;
527
+
528
+ const assertOpen = (): void => {
529
+ if (closeRequested) throw new Error("Knowledge-base session is closed.");
530
+ };
531
+ const semantic = (): Promise<SemanticSearchSession> => {
532
+ assertOpen();
533
+ semanticPromise ??= (dependencies.openSemanticSearchSession
534
+ ?? openSemanticSearchSession)(
535
+ {
536
+ root: snapshot.root,
537
+ ...(options.database === undefined ? {} : { database: options.database }),
538
+ ...(options.embeddingModelFile === undefined
539
+ ? {}
540
+ : { embeddingModelFile: options.embeddingModelFile }),
541
+ ...(options.embeddingModelLease === undefined
542
+ ? {}
543
+ : { embeddingModelLease: options.embeddingModelLease }),
544
+ ...(options.requireStoreLocalVectorBoundary === undefined
545
+ ? {}
546
+ : {
547
+ requireStoreLocalVectorBoundary:
548
+ options.requireStoreLocalVectorBoundary,
549
+ }),
550
+ },
551
+ {
552
+ ...(dependencies.semantic ?? {}),
553
+ scanVault: () => Promise.resolve(snapshot),
554
+ },
555
+ );
556
+ return semanticPromise;
557
+ };
558
+ const gitIndex = (): Promise<GitHistoryIndexResult> => {
559
+ assertOpen();
560
+ if (options.repository === undefined) {
561
+ return Promise.resolve(unavailableHistory(
562
+ snapshot.root,
563
+ "No repository root was configured for this knowledge-base session.",
564
+ ));
565
+ }
566
+ gitPromise ??= (dependencies.indexGitHistory ?? indexGitHistory)(
567
+ {
568
+ repository: options.repository,
569
+ root: snapshot.root,
570
+ notes: snapshot.notes,
571
+ },
572
+ dependencies.git,
573
+ );
574
+ return gitPromise;
575
+ };
576
+
577
+ const grep = (
578
+ grepOptions: Parameters<typeof searchExactVault>[2],
579
+ ): readonly ExactSearchHit[] => {
580
+ assertOpen();
581
+ return searchExactVault(snapshot.notes, snapshot.analysis, grepOptions);
582
+ };
583
+ const list = (queryOptions: QueryOptions = {}): readonly QueryRow[] => {
584
+ assertOpen();
585
+ return queryVault(snapshot.notes, snapshot.analysis, queryOptions);
586
+ };
587
+ const read = (
588
+ query: string,
589
+ readOptions: { readonly maxBytes?: number } = {},
590
+ ): KnowledgeBaseReadResult => {
591
+ assertOpen();
592
+ const maximumBytes = checkedLimit(
593
+ readOptions.maxBytes,
594
+ MAX_READ_BYTES,
595
+ MAX_READ_BYTES,
596
+ "Read byte limit",
597
+ );
598
+ const note = noteOrThrow(snapshot.notes, query);
599
+ const content = utf8Prefix(note.content, maximumBytes);
600
+ return {
601
+ id: note.id,
602
+ path: note.path,
603
+ title: note.title,
604
+ content: content.value,
605
+ truncated: content.truncated,
606
+ };
607
+ };
608
+ const links = (
609
+ query: string,
610
+ linkOptions: NavigateLinksOptions = {},
611
+ ): LinkNeighborhood => {
612
+ assertOpen();
613
+ return navigateLinks(
614
+ snapshot.notes,
615
+ snapshot.analysis,
616
+ noteOrThrow(snapshot.notes, query),
617
+ linkOptions,
618
+ );
619
+ };
620
+
621
+ const search = async (
622
+ searchOptions: KnowledgeBaseSearchOptions,
623
+ ): Promise<KnowledgeBaseSearchResult> => {
624
+ assertOpen();
625
+ const startedAt = performance.now();
626
+ const { query: requestedQuery } = validateSearchQuery(searchOptions.query);
627
+ const expansion = searchRules === null
628
+ ? { request: searchOptions, alias: null }
629
+ : expandSearchRequest(
630
+ searchOptions as KnowledgeBaseSearchOptions & SearchRuleRequest,
631
+ searchRules,
632
+ );
633
+ const effectiveOptions = expansion.request as KnowledgeBaseSearchOptions;
634
+ const { query: effectiveQuery } = validateSearchQuery(effectiveOptions.query);
635
+ const mode = checkedSearchMode(effectiveOptions.mode);
636
+ const ordering = checkedSearchOrdering(effectiveOptions.ordering);
637
+ const minScore = effectiveOptions.minScore;
638
+ if (
639
+ minScore !== undefined
640
+ && (
641
+ !Number.isFinite(minScore)
642
+ || minScore < 0
643
+ || minScore > 1
644
+ )
645
+ ) {
646
+ throw new RangeError("Search minimum score must be a number from 0 through 1.");
647
+ }
648
+ if (mode === "exact" && minScore !== undefined) {
649
+ throw new Error("Search minimum score applies only to hybrid, keyword, or semantic mode.");
650
+ }
651
+ const limit = checkedLimit(
652
+ effectiveOptions.limit,
653
+ DEFAULT_SEARCH_RESULTS,
654
+ MAX_SEARCH_RESULTS,
655
+ "Search limit",
656
+ );
657
+ const filters = effectiveOptions.filters ?? [];
658
+ const tags = effectiveOptions.tags ?? [];
659
+ const repositoryScopes = effectiveOptions.repositoryScopes ?? [];
660
+ const filtered = filters.length > 0 || tags.length > 0 || repositoryScopes.length > 0;
661
+ const candidateLimit = checkedLimit(
662
+ effectiveOptions.candidateLimit,
663
+ filtered ? MAX_SEARCH_CANDIDATES : Math.max(40, limit * 4),
664
+ MAX_SEARCH_CANDIDATES,
665
+ "Search candidate limit",
666
+ );
667
+ if (candidateLimit < limit) {
668
+ throw new RangeError("Search candidate limit must be at least the result limit.");
669
+ }
670
+ const historyRequest = checkedHistoryRequest(effectiveOptions.history);
671
+ const graphOptions = checkedGraphOptions(effectiveOptions.graph);
672
+ const explicitSeeds = graphOptions === null
673
+ ? []
674
+ : resolvedGraphSeeds(snapshot.notes, graphOptions.related);
675
+ const historyNoteLimit = historyRequest.enabled ? historyRequest.noteLimit : null;
676
+ const allowedIds = new Set(queryVault(snapshot.notes, snapshot.analysis, {
677
+ filters,
678
+ tags,
679
+ repositoryScopes,
680
+ }).map(({ id }) => id));
681
+ const includeExact = mode === "hybrid" || mode === "exact";
682
+ const exact = includeExact
683
+ ? searchExactVault(snapshot.notes, snapshot.analysis, {
684
+ query: effectiveQuery,
685
+ filters,
686
+ tags,
687
+ repositoryScopes,
688
+ limit: Math.min(MAX_SEARCH_CANDIDATES, candidateLimit),
689
+ })
690
+ : [];
691
+ const exactById = new Map(exact.map((hit, index) => [hit.id, { hit, rank: index + 1 }]));
692
+ const semanticById = new Map<string, { readonly hit: SemanticSearchHit; readonly rank: number }>();
693
+ const diagnostics: KnowledgeBaseSearchDiagnostic[] = includeExact
694
+ ? [{ lane: "exact", status: "ready", results: exact.length }]
695
+ : [];
696
+ let model: string | null = null;
697
+ let queryEmbedding: SemanticQueryEmbeddingAccounting | null = Object.freeze({
698
+ calls: 0,
699
+ inputTokens: 0,
700
+ durationMs: 0,
701
+ });
702
+ const selectedQmdMode = qmdMode(mode);
703
+ if (selectedQmdMode !== null) {
704
+ if (allowedIds.size === 0) {
705
+ diagnostics.push({ lane: "qmd", status: "ready", results: 0 });
706
+ } else {
707
+ if (selectedQmdMode !== "keyword") queryEmbedding = null;
708
+ try {
709
+ const session = await semantic();
710
+ model = session.model;
711
+ const semanticLimit = candidateLimit;
712
+ const result = await session.search({
713
+ query: effectiveQuery,
714
+ mode: selectedQmdMode,
715
+ limit: semanticLimit,
716
+ candidateLimit,
717
+ ...(minScore === undefined
718
+ ? {}
719
+ : { minScore }),
720
+ });
721
+ queryEmbedding = result.queryEmbedding
722
+ ?? (selectedQmdMode === "keyword" ? queryEmbedding : null);
723
+ let acceptedRank = 0;
724
+ let discardedCandidates = result.rawWindow?.discarded ?? 0;
725
+ for (const hit of result.results) {
726
+ const note = notesByPath.get(hit.path);
727
+ if (note === undefined || semanticById.has(note.id)) {
728
+ discardedCandidates += 1;
729
+ continue;
730
+ }
731
+ if (!allowedIds.has(note.id)) {
732
+ discardedCandidates += 1;
733
+ continue;
734
+ }
735
+ acceptedRank += 1;
736
+ semanticById.set(note.id, { hit, rank: acceptedRank });
737
+ }
738
+ const embeddingFailures = result.embedding?.failures?.length ?? 0;
739
+ const embeddingErrors = result.embedding?.errors ?? 0;
740
+ const embeddingDegraded = embeddingErrors > 0 || embeddingFailures > 0;
741
+ const requestedEligible = Math.min(limit, allowedIds.size);
742
+ const incompleteCandidateWindow = discardedCandidates > 0
743
+ && semanticById.size < requestedEligible;
744
+ const messages: string[] = [];
745
+ if (embeddingDegraded) {
746
+ messages.push(
747
+ `QMD embedding reported ${embeddingErrors} error(s)`
748
+ + ` and ${embeddingFailures} retained failure record(s).`,
749
+ );
750
+ }
751
+ if (incompleteCandidateWindow) {
752
+ messages.push(
753
+ `QMD's bounded ${semanticLimit}-candidate retrieval discarded `
754
+ + `${discardedCandidates} row(s) during live reconciliation or metadata filtering `
755
+ + `and accepted ${semanticById.size} of ${requestedEligible} eligible requested `
756
+ + "result(s); this bounded reconciliation cannot certify a complete eligible result set.",
757
+ );
758
+ }
759
+ diagnostics.push({
760
+ lane: "qmd",
761
+ status: embeddingDegraded || incompleteCandidateWindow ? "degraded" : "ready",
762
+ results: semanticById.size,
763
+ ...(messages.length === 0 ? {} : { message: messages.join(" ") }),
764
+ });
765
+ } catch (error: unknown) {
766
+ diagnostics.push({
767
+ lane: "qmd",
768
+ status: "unavailable",
769
+ results: 0,
770
+ message: errorMessage(error),
771
+ });
772
+ }
773
+ }
774
+ }
775
+ const lanes = [
776
+ ...(includeExact
777
+ ? [{ name: "exact", weight: 1, ids: exact.map(({ id }) => id) }]
778
+ : []),
779
+ ...(selectedQmdMode === null
780
+ ? []
781
+ : [{ name: "qmd", weight: 1, ids: [...semanticById.keys()] }]),
782
+ ];
783
+ const fused = fuseRankedCandidates(lanes);
784
+ const ordered = fused.toSorted((left, right) =>
785
+ Number(exactById.get(right.id)?.hit.identity ?? false)
786
+ - Number(exactById.get(left.id)?.hit.identity ?? false)
787
+ || left.rank - right.rank
788
+ || left.id.localeCompare(right.id));
789
+ const shouldApplyPriority = ordering === "priority-then-relevance"
790
+ && searchRules !== null
791
+ && searchRules.priorityRules.length > 0;
792
+ const relevanceResults = (shouldApplyPriority ? ordered : ordered.slice(0, limit))
793
+ .map((candidate, index): KnowledgeBaseSearchHit => {
794
+ const note = notesById.get(candidate.id);
795
+ if (note === undefined) {
796
+ throw new Error(`Fused retrieval returned unknown note ${JSON.stringify(candidate.id)}.`);
797
+ }
798
+ const exactMatch = exactById.get(candidate.id);
799
+ const semanticMatch = semanticById.get(candidate.id);
800
+ const evidence: KnowledgeBaseSearchEvidence[] = [];
801
+ if (exactMatch !== undefined) {
802
+ evidence.push({
803
+ kind: "exact",
804
+ rank: exactMatch.rank,
805
+ identity: exactMatch.hit.identity,
806
+ matches: exactMatch.hit.matches,
807
+ });
808
+ }
809
+ if (semanticMatch !== undefined) {
810
+ evidence.push({
811
+ kind: "qmd",
812
+ rank: semanticMatch.rank,
813
+ path: semanticMatch.hit.path,
814
+ ...(semanticMatch.hit.line === undefined ? {} : { line: semanticMatch.hit.line }),
815
+ source: semanticMatch.hit.source,
816
+ score: semanticMatch.hit.score,
817
+ ...(semanticMatch.hit.signals === undefined
818
+ ? {}
819
+ : { signals: semanticMatch.hit.signals }),
820
+ });
821
+ }
822
+ const snippetSource = exactMatch?.hit.identity === true || semanticMatch === undefined
823
+ ? exactMatch?.hit
824
+ : semanticMatch.hit;
825
+ return {
826
+ id: note.id,
827
+ path: note.path,
828
+ title: note.title,
829
+ rank: index + 1,
830
+ score: candidate.score,
831
+ identity: exactMatch?.hit.identity ?? false,
832
+ ...(snippetSource?.line === undefined ? {} : { line: snippetSource.line }),
833
+ snippet: snippetSource?.snippet ?? note.summary,
834
+ tags: note.tags,
835
+ metadata: note.metadata,
836
+ evidence,
837
+ contributions: candidate.contributions,
838
+ };
839
+ });
840
+ let priorityTrace: readonly SearchPriorityTrace[] | null = null;
841
+ let results: readonly KnowledgeBaseSearchHit[] = relevanceResults.slice(0, limit);
842
+ if (shouldApplyPriority && searchRules !== null) {
843
+ const prioritized = prioritizeSearchHits(relevanceResults, searchRules, {
844
+ ...(options.vaultId === undefined ? {} : { vaultId: options.vaultId }),
845
+ });
846
+ const selectedHits = prioritized.hits.slice(0, limit);
847
+ const selectedTrace = prioritized.trace.slice(0, limit);
848
+ if (selectedTrace.some(({ matchedRuleIds }) => matchedRuleIds.length > 0)) {
849
+ priorityTrace = Object.freeze(selectedTrace);
850
+ results = Object.freeze(selectedHits.map((hit, index) =>
851
+ hit.rank === index + 1 ? hit : { ...hit, rank: index + 1 }));
852
+ }
853
+ }
854
+ let graph: GraphContext | null = null;
855
+ if (graphOptions !== null) {
856
+ try {
857
+ graph = buildGraphContext(snapshot.notes, snapshot.analysis, {
858
+ seeds: [...explicitSeeds, ...results.slice(0, 5).map(({ id }) => id)],
859
+ primaryIds: results.map(({ id }) => id),
860
+ depth: graphOptions.depth,
861
+ neighborsPerSeed: graphOptions.neighborsPerSeed,
862
+ limit: graphOptions.limit,
863
+ });
864
+ diagnostics.push({
865
+ lane: "graph",
866
+ status: "ready",
867
+ results: graph.related.length,
868
+ });
869
+ } catch (error: unknown) {
870
+ if (!(error instanceof NavigationBudgetError)) throw error;
871
+ diagnostics.push({
872
+ lane: "graph",
873
+ status: "unavailable",
874
+ results: 0,
875
+ message: error.message,
876
+ });
877
+ }
878
+ }
879
+ let history: GitHistoryForNotesResult | null = null;
880
+ if (
881
+ historyRequest.enabled
882
+ && historyNoteLimit !== null
883
+ && results.length > 0
884
+ ) {
885
+ const outcome = await gitIndex().then(
886
+ (index) => ({ status: "indexed", index } as const),
887
+ (error: unknown) => ({ status: "failed", error } as const),
888
+ );
889
+ let index: GitHistoryIndexResult;
890
+ if (outcome.status === "failed") {
891
+ if (historyRequest.required) throw outcome.error;
892
+ index = unavailableHistory(snapshot.root, errorMessage(outcome.error));
893
+ } else {
894
+ index = outcome.index;
895
+ }
896
+ if (historyRequest.required && index.status === "unavailable") {
897
+ throw new GitHistoryError(
898
+ "unavailable",
899
+ `Required Git history is unavailable: ${index.reason}`,
900
+ );
901
+ }
902
+ history = gitHistoryForNotes(
903
+ index,
904
+ results.slice(0, historyNoteLimit).map(({ id }) => id),
905
+ historyRequest.options,
906
+ );
907
+ const limitedCommits = history.status === "ready"
908
+ ? history.limitedCommits ?? []
909
+ : [];
910
+ if (historyRequest.required && limitedCommits.length > 0) {
911
+ throw new GitHistoryError(
912
+ "budget",
913
+ `Required Git history is incomplete: ${limitedHistoryMessage(limitedCommits)}`,
914
+ );
915
+ }
916
+ diagnostics.push(history.status === "ready" && limitedCommits.length === 0
917
+ ? {
918
+ lane: "git",
919
+ status: "ready",
920
+ results: history.notes.length,
921
+ }
922
+ : history.status === "ready"
923
+ ? {
924
+ lane: "git",
925
+ status: "degraded",
926
+ results: history.notes.length,
927
+ message: limitedHistoryMessage(limitedCommits),
928
+ }
929
+ : {
930
+ lane: "git",
931
+ status: "unavailable",
932
+ results: 0,
933
+ message: history.reason,
934
+ });
935
+ }
936
+ const priorityApplication: KnowledgeBaseSearchPriorityApplication | undefined =
937
+ priorityTrace === null
938
+ ? undefined
939
+ : Object.freeze({
940
+ ordering: "priority-then-relevance",
941
+ trace: priorityTrace,
942
+ });
943
+ const ruleApplication: KnowledgeBaseSearchRuleApplication | undefined =
944
+ expansion.alias === null
945
+ ? priorityApplication === undefined
946
+ ? undefined
947
+ : Object.freeze({ priority: priorityApplication })
948
+ : Object.freeze({
949
+ alias: expansion.alias,
950
+ effectiveQuery,
951
+ ...(priorityApplication === undefined ? {} : { priority: priorityApplication }),
952
+ });
953
+ return {
954
+ query: expansion.alias === null ? effectiveQuery : requestedQuery,
955
+ mode,
956
+ results,
957
+ graph,
958
+ history,
959
+ partial: diagnostics.some(({ status }) => status !== "ready"),
960
+ ...(ruleApplication === undefined ? {} : { rules: ruleApplication }),
961
+ diagnostics: {
962
+ notes: snapshot.notes.length,
963
+ model: selectedQmdMode === null ? null : model ?? recommendedEmbeddingModel,
964
+ elapsedMs: performance.now() - startedAt,
965
+ queryEmbedding,
966
+ lanes: diagnostics,
967
+ },
968
+ };
969
+ };
970
+
971
+ return {
972
+ root: snapshot.root,
973
+ ...(options.repository === undefined ? {} : { repository: options.repository }),
974
+ noteCount: snapshot.notes.length,
975
+ grep,
976
+ list,
977
+ read,
978
+ links,
979
+ backlinks: (query, linkOptions = {}) => links(query, {
980
+ ...linkOptions,
981
+ direction: "in",
982
+ }),
983
+ search,
984
+ history: async (noteIds, historyOptions = {}) => {
985
+ assertOpen();
986
+ const request = validateGitHistoryForNotesRequest(noteIds, historyOptions);
987
+ return gitHistoryForNotes(await gitIndex(), request.noteIds, request.options);
988
+ },
989
+ searchHistory: async (historyOptions) => {
990
+ assertOpen();
991
+ const request = validateSearchGitHistoryOptions(historyOptions);
992
+ return searchGitHistory(await gitIndex(), {
993
+ query: request.query,
994
+ ...(request.allowedNoteIds === null ? {} : { allowedNoteIds: request.allowedNoteIds }),
995
+ limit: request.limit,
996
+ commitsPerHit: request.commitsPerHit,
997
+ cochangedPathsPerCommit: request.cochangedPathsPerCommit,
998
+ });
999
+ },
1000
+ close: () => {
1001
+ if (closePromise !== undefined) return closePromise;
1002
+ closeRequested = true;
1003
+ closePromise = semanticPromise === undefined
1004
+ ? Promise.resolve()
1005
+ : semanticPromise.then(
1006
+ (session) => session.close(),
1007
+ () => undefined,
1008
+ );
1009
+ return closePromise;
1010
+ },
1011
+ };
1012
+ }
1013
+
1014
+ type Utf8ContextWriter = {
1015
+ readonly append: (value: string, final?: boolean) => boolean;
1016
+ readonly result: () => { readonly content: string; readonly truncated: boolean };
1017
+ };
1018
+
1019
+ function utf8ContextWriter(maximumBytes: number): Utf8ContextWriter {
1020
+ const chunks: string[] = [];
1021
+ let bytes = 0;
1022
+ let truncated = false;
1023
+ return {
1024
+ append: (value, final = false) => {
1025
+ if (truncated) return false;
1026
+ const remaining = maximumBytes - bytes;
1027
+ const prefix = utf8Prefix(value, remaining);
1028
+ if (prefix.value !== "") chunks.push(prefix.value);
1029
+ bytes += prefix.bytes;
1030
+ if (prefix.truncated || (!final && bytes === maximumBytes)) {
1031
+ // At least the final newline remains at an exact non-final boundary,
1032
+ // so no later field is needed to prove that the result is partial.
1033
+ truncated = true;
1034
+ return false;
1035
+ }
1036
+ return true;
1037
+ },
1038
+ result: () => ({ content: chunks.join(""), truncated }),
1039
+ };
1040
+ }
1041
+
1042
+ /** Render a bounded Markdown handoff without evaluating fields past its byte budget. */
1043
+ export function packSearchContext(
1044
+ result: KnowledgeBaseSearchResult,
1045
+ options: { readonly maxBytes?: number } = {},
1046
+ ): { readonly content: string; readonly truncated: boolean } {
1047
+ const maximumBytes = checkedLimit(
1048
+ options.maxBytes,
1049
+ DEFAULT_CONTEXT_BYTES,
1050
+ MAX_CONTEXT_BYTES,
1051
+ "Context byte limit",
1052
+ );
1053
+ const writer = utf8ContextWriter(maximumBytes);
1054
+ const append = (value: string): boolean => writer.append(value);
1055
+ if (!append("# Knowledge-base context\n\nQuery: ")) return writer.result();
1056
+ if (!append(result.query)) return writer.result();
1057
+ if (!append("\nMode: ")) return writer.result();
1058
+ if (!append(result.mode)) return writer.result();
1059
+ if (!append("\n")) return writer.result();
1060
+
1061
+ for (const hit of result.results) {
1062
+ if (!append("\n\n## ")) return writer.result();
1063
+ if (!append(String(hit.rank))) return writer.result();
1064
+ if (!append(". ")) return writer.result();
1065
+ if (!append(hit.title)) return writer.result();
1066
+ if (!append("\n\nPath: ")) return writer.result();
1067
+ if (!append(hit.path)) return writer.result();
1068
+ if (hit.line !== undefined) {
1069
+ if (!append(":")) return writer.result();
1070
+ if (!append(String(hit.line))) return writer.result();
1071
+ }
1072
+ if (!append("\n\nEvidence: ")) return writer.result();
1073
+ for (const [index, item] of hit.evidence.entries()) {
1074
+ if (index > 0 && !append(", ")) return writer.result();
1075
+ if (!append(item.kind)) return writer.result();
1076
+ if (!append("#")) return writer.result();
1077
+ if (!append(String(item.rank))) return writer.result();
1078
+ }
1079
+ if (!append("\n\n")) return writer.result();
1080
+ if (!append(hit.snippet)) return writer.result();
1081
+ }
1082
+
1083
+ const graph = result.graph;
1084
+ if (graph !== null && graph.related.length > 0) {
1085
+ if (!append("\n\n## Related graph context")) return writer.result();
1086
+ for (const [hitIndex, hit] of graph.related.entries()) {
1087
+ if (!append(hitIndex === 0 ? "\n\n- " : "\n- ")) return writer.result();
1088
+ if (!append(hit.path)) return writer.result();
1089
+ if (!append(" (")) return writer.result();
1090
+ for (const [index, evidence] of hit.evidence.entries()) {
1091
+ if (index > 0 && !append(", ")) return writer.result();
1092
+ if (!append(evidence.kind)) return writer.result();
1093
+ }
1094
+ if (!append(")")) return writer.result();
1095
+ }
1096
+ }
1097
+
1098
+ const history = result.history;
1099
+ if (history?.status === "ready") {
1100
+ if (!append("\n\n## Git provenance\n\n")) return writer.result();
1101
+ const limitedCommits = history.limitedCommits ?? [];
1102
+ if (limitedCommits.length > 0) {
1103
+ if (!append("> Partial: ")) return writer.result();
1104
+ if (!append(limitedHistoryMessage(limitedCommits))) return writer.result();
1105
+ if (!append("\n\n")) return writer.result();
1106
+ }
1107
+ let commitIndex = 0;
1108
+ for (const note of history.notes) {
1109
+ for (const commit of note.commits) {
1110
+ if (commitIndex > 0 && !append("\n")) return writer.result();
1111
+ if (!append("- ")) return writer.result();
1112
+ if (!append(note.path)) return writer.result();
1113
+ if (!append(": ")) return writer.result();
1114
+ if (!append(commit.committedAt)) return writer.result();
1115
+ if (!append(" ")) return writer.result();
1116
+ if (!append(commit.hash.slice(0, 12))) return writer.result();
1117
+ if (!append(" ")) return writer.result();
1118
+ if (!append(commit.subject)) return writer.result();
1119
+ commitIndex += 1;
1120
+ }
1121
+ }
1122
+ }
1123
+ writer.append("\n", true);
1124
+ return writer.result();
1125
+ }
1126
+
1127
+ export type PackedUntrustedSearchContext = Readonly<{
1128
+ /** Trusted notice followed by canonical JSON for the structured content. */
1129
+ content: string;
1130
+ truncated: boolean;
1131
+ structuredContent: UntrustedStructuredContent;
1132
+ }>;
1133
+
1134
+ type InspectedDataRecord = Readonly<Record<string, unknown>>;
1135
+
1136
+ function inspectedDataRecord(value: unknown, label: string): InspectedDataRecord {
1137
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1138
+ throw new TypeError(`${label} must be a plain data object.`);
1139
+ }
1140
+ const prototype = Object.getPrototypeOf(value);
1141
+ if (prototype !== Object.prototype && prototype !== null) {
1142
+ throw new TypeError(`${label} must be a plain data object.`);
1143
+ }
1144
+ const descriptors = Object.getOwnPropertyDescriptors(value);
1145
+ const output = Object.create(null) as Record<string, unknown>;
1146
+ for (const key of Reflect.ownKeys(descriptors)) {
1147
+ if (typeof key === "symbol") throw new TypeError(`${label} contains a symbol property.`);
1148
+ const descriptor = descriptors[key];
1149
+ if (descriptor === undefined || !("value" in descriptor)) {
1150
+ throw new TypeError(`${label} contains an accessor property.`);
1151
+ }
1152
+ if (!descriptor.enumerable) {
1153
+ throw new TypeError(`${label} contains a non-enumerable property.`);
1154
+ }
1155
+ Object.defineProperty(output, key, {
1156
+ configurable: false,
1157
+ enumerable: true,
1158
+ value: descriptor.value,
1159
+ writable: false,
1160
+ });
1161
+ }
1162
+ return Object.freeze(output);
1163
+ }
1164
+
1165
+ function inspectedDataArray(
1166
+ value: unknown,
1167
+ label: string,
1168
+ maximum: number,
1169
+ ): readonly unknown[] {
1170
+ if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
1171
+ throw new TypeError(`${label} must be an ordinary array.`);
1172
+ }
1173
+ if (value.length > maximum) {
1174
+ throw new RangeError(`${label} exceeds its ${maximum.toLocaleString("en-US")}-item limit.`);
1175
+ }
1176
+ const descriptors = Object.getOwnPropertyDescriptors(value);
1177
+ for (const key of Reflect.ownKeys(descriptors)) {
1178
+ if (typeof key === "symbol") throw new TypeError(`${label} contains a symbol property.`);
1179
+ if (key === "length") continue;
1180
+ const index = Number(key);
1181
+ if (!Number.isSafeInteger(index) || index < 0 || index >= value.length || String(index) !== key) {
1182
+ throw new TypeError(`${label} contains a non-index property.`);
1183
+ }
1184
+ }
1185
+ const output: unknown[] = [];
1186
+ for (let index = 0; index < value.length; index += 1) {
1187
+ const descriptor = descriptors[String(index)];
1188
+ if (descriptor === undefined || !("value" in descriptor) || !descriptor.enumerable) {
1189
+ throw new TypeError(`${label} must be a dense array of enumerable data properties.`);
1190
+ }
1191
+ output.push(descriptor.value);
1192
+ }
1193
+ return Object.freeze(output);
1194
+ }
1195
+
1196
+ function requiredData(record: InspectedDataRecord, key: string, label: string): unknown {
1197
+ if (!Object.hasOwn(record, key)) throw new TypeError(`${label}.${key} is required.`);
1198
+ return record[key];
1199
+ }
1200
+
1201
+ function dataString(value: unknown, label: string): string {
1202
+ if (typeof value !== "string") throw new TypeError(`${label} must be a string.`);
1203
+ return value;
1204
+ }
1205
+
1206
+ function dataNumber(value: unknown, label: string): number {
1207
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1208
+ throw new TypeError(`${label} must be a finite number.`);
1209
+ }
1210
+ return value;
1211
+ }
1212
+
1213
+ function dataBoolean(value: unknown, label: string): boolean {
1214
+ if (typeof value !== "boolean") throw new TypeError(`${label} must be a boolean.`);
1215
+ return value;
1216
+ }
1217
+
1218
+ function optionalDataNumber(
1219
+ record: InspectedDataRecord,
1220
+ key: string,
1221
+ label: string,
1222
+ ): number | undefined {
1223
+ if (!Object.hasOwn(record, key)) return undefined;
1224
+ return dataNumber(record[key], `${label}.${key}`);
1225
+ }
1226
+
1227
+ function searchEvidenceSummary(value: unknown, label: string): Readonly<Record<string, unknown>> {
1228
+ const evidence = inspectedDataRecord(value, label);
1229
+ return Object.freeze({
1230
+ kind: dataString(requiredData(evidence, "kind", label), `${label}.kind`),
1231
+ rank: dataNumber(requiredData(evidence, "rank", label), `${label}.rank`),
1232
+ });
1233
+ }
1234
+
1235
+ function searchHitContextRecord(value: unknown, index: number): Readonly<Record<string, unknown>> {
1236
+ const label = `Knowledge-base search result ${index + 1}`;
1237
+ const hit = inspectedDataRecord(value, label);
1238
+ const evidence = inspectedDataArray(
1239
+ requiredData(hit, "evidence", label),
1240
+ `${label}.evidence`,
1241
+ 32,
1242
+ ).map((entry, evidenceIndex) =>
1243
+ searchEvidenceSummary(entry, `${label}.evidence[${evidenceIndex}]`));
1244
+ const line = optionalDataNumber(hit, "line", label);
1245
+ return Object.freeze({
1246
+ kind: "search-result",
1247
+ id: dataString(requiredData(hit, "id", label), `${label}.id`),
1248
+ path: dataString(requiredData(hit, "path", label), `${label}.path`),
1249
+ title: dataString(requiredData(hit, "title", label), `${label}.title`),
1250
+ rank: dataNumber(requiredData(hit, "rank", label), `${label}.rank`),
1251
+ score: dataNumber(requiredData(hit, "score", label), `${label}.score`),
1252
+ identity: dataBoolean(requiredData(hit, "identity", label), `${label}.identity`),
1253
+ ...(line === undefined ? {} : { line }),
1254
+ evidence: Object.freeze(evidence),
1255
+ snippet: dataString(requiredData(hit, "snippet", label), `${label}.snippet`),
1256
+ });
1257
+ }
1258
+
1259
+ function graphContextRecords(value: unknown): readonly Readonly<Record<string, unknown>>[] {
1260
+ if (value === null) return [];
1261
+ const graph = inspectedDataRecord(value, "Knowledge-base graph context");
1262
+ const related = inspectedDataArray(
1263
+ requiredData(graph, "related", "Knowledge-base graph context"),
1264
+ "Knowledge-base graph context.related",
1265
+ 100,
1266
+ );
1267
+ return Object.freeze(related.map((entry, index) => {
1268
+ const label = `Knowledge-base graph result ${index + 1}`;
1269
+ const hit = inspectedDataRecord(entry, label);
1270
+ const evidence = inspectedDataArray(
1271
+ requiredData(hit, "evidence", label),
1272
+ `${label}.evidence`,
1273
+ 100,
1274
+ ).map((item, evidenceIndex) => {
1275
+ const evidenceLabel = `${label}.evidence[${evidenceIndex}]`;
1276
+ const inspected = inspectedDataRecord(item, evidenceLabel);
1277
+ return Object.freeze({
1278
+ kind: dataString(requiredData(inspected, "kind", evidenceLabel), `${evidenceLabel}.kind`),
1279
+ });
1280
+ });
1281
+ return Object.freeze({
1282
+ kind: "graph-related",
1283
+ id: dataString(requiredData(hit, "id", label), `${label}.id`),
1284
+ path: dataString(requiredData(hit, "path", label), `${label}.path`),
1285
+ title: dataString(requiredData(hit, "title", label), `${label}.title`),
1286
+ distance: dataNumber(requiredData(hit, "distance", label), `${label}.distance`),
1287
+ evidence: Object.freeze(evidence),
1288
+ });
1289
+ }));
1290
+ }
1291
+
1292
+ function gitContextRecords(value: unknown): readonly Readonly<Record<string, unknown>>[] {
1293
+ if (value === null) return [];
1294
+ const history = inspectedDataRecord(value, "Knowledge-base Git provenance");
1295
+ const status = dataString(
1296
+ requiredData(history, "status", "Knowledge-base Git provenance"),
1297
+ "Knowledge-base Git provenance.status",
1298
+ );
1299
+ if (status === "unavailable") {
1300
+ return Object.freeze([Object.freeze({
1301
+ kind: "git-status",
1302
+ status,
1303
+ reason: dataString(
1304
+ requiredData(history, "reason", "Knowledge-base Git provenance"),
1305
+ "Knowledge-base Git provenance.reason",
1306
+ ),
1307
+ })]);
1308
+ }
1309
+ if (status !== "ready") throw new TypeError("Knowledge-base Git provenance.status is invalid.");
1310
+ const output: Readonly<Record<string, unknown>>[] = [Object.freeze({
1311
+ kind: "git-status",
1312
+ status,
1313
+ head: dataString(
1314
+ requiredData(history, "head", "Knowledge-base Git provenance"),
1315
+ "Knowledge-base Git provenance.head",
1316
+ ),
1317
+ })];
1318
+ const notes = inspectedDataArray(
1319
+ requiredData(history, "notes", "Knowledge-base Git provenance"),
1320
+ "Knowledge-base Git provenance.notes",
1321
+ MAX_SEARCH_HISTORY_NOTES,
1322
+ );
1323
+ for (const [noteIndex, entry] of notes.entries()) {
1324
+ const noteLabel = `Knowledge-base Git provenance note ${noteIndex + 1}`;
1325
+ const note = inspectedDataRecord(entry, noteLabel);
1326
+ const noteId = dataString(requiredData(note, "id", noteLabel), `${noteLabel}.id`);
1327
+ const path = dataString(requiredData(note, "path", noteLabel), `${noteLabel}.path`);
1328
+ const commits = inspectedDataArray(
1329
+ requiredData(note, "commits", noteLabel),
1330
+ `${noteLabel}.commits`,
1331
+ 100,
1332
+ );
1333
+ for (const [commitIndex, commitValue] of commits.entries()) {
1334
+ const commitLabel = `${noteLabel}.commits[${commitIndex}]`;
1335
+ const commit = inspectedDataRecord(commitValue, commitLabel);
1336
+ output.push(Object.freeze({
1337
+ kind: "git-provenance",
1338
+ noteId,
1339
+ path,
1340
+ hash: dataString(requiredData(commit, "hash", commitLabel), `${commitLabel}.hash`),
1341
+ committedAt: dataString(
1342
+ requiredData(commit, "committedAt", commitLabel),
1343
+ `${commitLabel}.committedAt`,
1344
+ ),
1345
+ subject: dataString(
1346
+ requiredData(commit, "subject", commitLabel),
1347
+ `${commitLabel}.subject`,
1348
+ ),
1349
+ }));
1350
+ }
1351
+ }
1352
+ if (Object.hasOwn(history, "limitedCommits")) {
1353
+ const limited = inspectedDataArray(
1354
+ history.limitedCommits,
1355
+ "Knowledge-base Git provenance.limitedCommits",
1356
+ 100,
1357
+ );
1358
+ if (limited.length > 0) {
1359
+ const first = inspectedDataRecord(limited[0], "Knowledge-base Git limited commit");
1360
+ output.push(Object.freeze({
1361
+ kind: "git-coverage",
1362
+ limitedCommits: limited.length,
1363
+ pathLimit: dataNumber(
1364
+ requiredData(first, "pathLimit", "Knowledge-base Git limited commit"),
1365
+ "Knowledge-base Git limited commit.pathLimit",
1366
+ ),
1367
+ }));
1368
+ }
1369
+ }
1370
+ return Object.freeze(output);
1371
+ }
1372
+
1373
+ function untrustedSearchRecords(
1374
+ result: KnowledgeBaseSearchResult,
1375
+ ): readonly Readonly<Record<string, unknown>>[] {
1376
+ const root = inspectedDataRecord(result, "Knowledge-base search result");
1377
+ const records: Readonly<Record<string, unknown>>[] = [Object.freeze({
1378
+ kind: "knowledge-base-search",
1379
+ query: dataString(
1380
+ requiredData(root, "query", "Knowledge-base search result"),
1381
+ "Knowledge-base search result.query",
1382
+ ),
1383
+ mode: dataString(
1384
+ requiredData(root, "mode", "Knowledge-base search result"),
1385
+ "Knowledge-base search result.mode",
1386
+ ),
1387
+ partial: dataBoolean(
1388
+ requiredData(root, "partial", "Knowledge-base search result"),
1389
+ "Knowledge-base search result.partial",
1390
+ ),
1391
+ })];
1392
+ const results = inspectedDataArray(
1393
+ requiredData(root, "results", "Knowledge-base search result"),
1394
+ "Knowledge-base search result.results",
1395
+ MAX_SEARCH_RESULTS,
1396
+ );
1397
+ records.push(...results.map(searchHitContextRecord));
1398
+ records.push(...graphContextRecords(requiredData(root, "graph", "Knowledge-base search result")));
1399
+ records.push(...gitContextRecords(requiredData(root, "history", "Knowledge-base search result")));
1400
+ return Object.freeze(records);
1401
+ }
1402
+
1403
+ /**
1404
+ * Pack source-controlled search, graph, and Git fields as canonical JSON under
1405
+ * an explicit execution-untrusted boundary. Epistemic trust labels, if added by
1406
+ * a consumer, remain data and never change this execution classification.
1407
+ */
1408
+ export function packUntrustedSearchContext(
1409
+ result: KnowledgeBaseSearchResult,
1410
+ options: { readonly maxBytes?: number } = {},
1411
+ ): PackedUntrustedSearchContext {
1412
+ const maximumBytes = checkedLimit(
1413
+ options.maxBytes,
1414
+ DEFAULT_CONTEXT_BYTES,
1415
+ MAX_CONTEXT_BYTES,
1416
+ "Untrusted context byte limit",
1417
+ );
1418
+ if (maximumBytes < MIN_UNTRUSTED_CONTEXT_BYTES) {
1419
+ throw new RangeError(
1420
+ `Untrusted context byte limit must be at least ${MIN_UNTRUSTED_CONTEXT_BYTES}.`,
1421
+ );
1422
+ }
1423
+ const accepted: Readonly<Record<string, unknown>>[] = [];
1424
+ let truncated = false;
1425
+ for (const record of untrustedSearchRecords(result)) {
1426
+ try {
1427
+ createUntrustedToolResult([...accepted, record], {
1428
+ maxBytes: maximumBytes,
1429
+ truncated: false,
1430
+ });
1431
+ accepted.push(record);
1432
+ } catch (error) {
1433
+ if (!(error instanceof UntrustedContentBudgetError)) throw error;
1434
+ truncated = true;
1435
+ break;
1436
+ }
1437
+ }
1438
+ if (accepted.length === 0) {
1439
+ throw new RangeError(
1440
+ `Untrusted context byte limit ${maximumBytes} cannot hold the required search header.`,
1441
+ );
1442
+ }
1443
+ const toolResult = createUntrustedToolResult(accepted, {
1444
+ maxBytes: maximumBytes,
1445
+ truncated,
1446
+ });
1447
+ return Object.freeze({
1448
+ content: toolResult.content[0].text,
1449
+ truncated,
1450
+ structuredContent: toolResult.structuredContent,
1451
+ });
1452
+ }