@brainpilot/runtime 0.0.9 → 0.0.10

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 (60) hide show
  1. package/dist/events.d.ts +18 -1
  2. package/dist/events.d.ts.map +1 -1
  3. package/dist/events.js +16 -0
  4. package/dist/events.js.map +1 -1
  5. package/dist/extensions/agent-status.d.ts +13 -11
  6. package/dist/extensions/agent-status.d.ts.map +1 -1
  7. package/dist/extensions/agent-status.js +37 -21
  8. package/dist/extensions/agent-status.js.map +1 -1
  9. package/dist/extensions/trace-reminder.d.ts +7 -19
  10. package/dist/extensions/trace-reminder.d.ts.map +1 -1
  11. package/dist/extensions/trace-reminder.js +57 -33
  12. package/dist/extensions/trace-reminder.js.map +1 -1
  13. package/dist/mas-agent.d.ts +2 -0
  14. package/dist/mas-agent.d.ts.map +1 -1
  15. package/dist/mas-agent.js +54 -3
  16. package/dist/mas-agent.js.map +1 -1
  17. package/dist/personas.d.ts.map +1 -1
  18. package/dist/personas.js +66 -14
  19. package/dist/personas.js.map +1 -1
  20. package/dist/server.d.ts.map +1 -1
  21. package/dist/server.js +22 -9
  22. package/dist/server.js.map +1 -1
  23. package/dist/session-manager.d.ts +40 -1
  24. package/dist/session-manager.d.ts.map +1 -1
  25. package/dist/session-manager.js +116 -28
  26. package/dist/session-manager.js.map +1 -1
  27. package/dist/tools/kb/paths.d.ts +24 -0
  28. package/dist/tools/kb/paths.d.ts.map +1 -0
  29. package/dist/tools/kb/paths.js +69 -0
  30. package/dist/tools/kb/paths.js.map +1 -0
  31. package/dist/tools/kb/python.d.ts +2 -0
  32. package/dist/tools/kb/python.d.ts.map +1 -0
  33. package/dist/tools/kb/python.js +42 -0
  34. package/dist/tools/kb/python.js.map +1 -0
  35. package/dist/tools/kb/retrieve.d.ts +20 -0
  36. package/dist/tools/kb/retrieve.d.ts.map +1 -0
  37. package/dist/tools/kb/retrieve.js +122 -0
  38. package/dist/tools/kb/retrieve.js.map +1 -0
  39. package/dist/tools/kb/search-papers.d.ts +39 -0
  40. package/dist/tools/kb/search-papers.d.ts.map +1 -0
  41. package/dist/tools/kb/search-papers.js +188 -0
  42. package/dist/tools/kb/search-papers.js.map +1 -0
  43. package/dist/tools/kb/sidecar.d.ts +11 -0
  44. package/dist/tools/kb/sidecar.d.ts.map +1 -0
  45. package/dist/tools/kb/sidecar.js +161 -0
  46. package/dist/tools/kb/sidecar.js.map +1 -0
  47. package/dist/tools/kb/store.d.ts +45 -0
  48. package/dist/tools/kb/store.d.ts.map +1 -0
  49. package/dist/tools/kb/store.js +157 -0
  50. package/dist/tools/kb/store.js.map +1 -0
  51. package/dist/tools/kb/tools.d.ts +24 -0
  52. package/dist/tools/kb/tools.d.ts.map +1 -0
  53. package/dist/tools/kb/tools.js +192 -0
  54. package/dist/tools/kb/tools.js.map +1 -0
  55. package/dist/tools/system-tools.d.ts.map +1 -1
  56. package/dist/tools/system-tools.js +41 -6
  57. package/dist/tools/system-tools.js.map +1 -1
  58. package/dist/types.d.ts +16 -0
  59. package/dist/types.d.ts.map +1 -1
  60. package/package.json +3 -3
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Multi-criteria paper search over the local ``source/KB_source.json``
3
+ * library written by ``scripts/extract_meta.py``.
4
+ *
5
+ * Mirrors `tools.py:search_papers` (the function that powers the v3 KB on
6
+ * the legacy MCP server):
7
+ *
8
+ * - Filters: title (exact), authors (any-overlap exact), journal (exact),
9
+ * published_year (year prefix). Each filter is OPTIONAL.
10
+ * - Ranking: count whole-word keyword matches against title + abstract,
11
+ * and optionally the full .mmd body in full-paper mode. Ties break by
12
+ * publication date desc.
13
+ * - Output: "meta-data" returns metadata + keyword_hits. "full-paper"
14
+ * additionally returns a `mmd_content` segment + `segment_info` so
15
+ * long papers can be paged through.
16
+ *
17
+ * Internal-only fields (``mmd_path``, ``extraction_status``) are stripped
18
+ * from every returned record so they never leak to an agent.
19
+ */
20
+ import { readFile } from "node:fs/promises";
21
+ import { resolveKbPaths } from "./paths.js";
22
+ const SEGMENT_CHARS = 20_000;
23
+ const INTERNAL_FIELDS = new Set(["mmd_path", "extraction_status"]);
24
+ function stripInternal(d) {
25
+ const out = {};
26
+ for (const [k, v] of Object.entries(d)) {
27
+ if (!INTERNAL_FIELDS.has(k))
28
+ out[k] = v;
29
+ }
30
+ return out;
31
+ }
32
+ function normalizeStrList(value) {
33
+ if (value === undefined || value === null)
34
+ return null;
35
+ let items;
36
+ if (typeof value === "string") {
37
+ items = value.split(",").map((s) => s.trim());
38
+ }
39
+ else {
40
+ items = value.map((s) => String(s).trim());
41
+ }
42
+ items = items.filter(Boolean);
43
+ return items.length ? items : null;
44
+ }
45
+ function escapeRegex(s) {
46
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
47
+ }
48
+ function countKeywordHits(text, patterns) {
49
+ if (!text)
50
+ return 0;
51
+ let n = 0;
52
+ for (const p of patterns) {
53
+ const m = text.match(p);
54
+ if (m)
55
+ n += m.length;
56
+ }
57
+ return n;
58
+ }
59
+ async function readMmd(path) {
60
+ if (!path)
61
+ return "";
62
+ try {
63
+ return await readFile(path, "utf8");
64
+ }
65
+ catch {
66
+ return "";
67
+ }
68
+ }
69
+ let SOURCE_CACHE = null;
70
+ async function loadSource(kbSourceJson) {
71
+ // Cheap freshness check via mtime — extract_meta.py writes atomically, so
72
+ // a changed mtime always reflects a new generation we should pick up.
73
+ const { stat } = await import("node:fs/promises");
74
+ let mtimeMs = 0;
75
+ try {
76
+ mtimeMs = (await stat(kbSourceJson)).mtimeMs;
77
+ }
78
+ catch {
79
+ /* file may not exist yet — surface a clean error below */
80
+ }
81
+ if (SOURCE_CACHE && SOURCE_CACHE.path === kbSourceJson && SOURCE_CACHE.mtimeMs === mtimeMs) {
82
+ return SOURCE_CACHE.papers;
83
+ }
84
+ let raw;
85
+ try {
86
+ raw = await readFile(kbSourceJson, "utf8");
87
+ }
88
+ catch (err) {
89
+ throw new Error(`KB_source.json not found at ${kbSourceJson}: ${err.message}`);
90
+ }
91
+ const data = JSON.parse(raw);
92
+ const papers = Array.isArray(data.papers) ? data.papers : [];
93
+ SOURCE_CACHE = { path: kbSourceJson, mtimeMs, papers };
94
+ return papers;
95
+ }
96
+ export async function searchPapers(args) {
97
+ const mode = args.mode ?? "meta-data";
98
+ if (mode !== "meta-data" && mode !== "full-paper") {
99
+ throw new Error(`mode must be 'meta-data' or 'full-paper', got '${mode}'`);
100
+ }
101
+ const topk = Math.max(1, Math.floor(args.topk ?? 5));
102
+ const segment = Math.max(1, Math.floor(args.segment ?? 1));
103
+ const authors = normalizeStrList(args.authors);
104
+ const keywords = normalizeStrList(args.keywords);
105
+ const kb = resolveKbPaths(args.kbRoot);
106
+ const papers = await loadSource(kb.kbSourceJson);
107
+ // Filter
108
+ const filtered = [];
109
+ for (const paper of papers) {
110
+ if (typeof paper !== "object" || paper === null)
111
+ continue;
112
+ if (args.title !== undefined && paper.title !== args.title)
113
+ continue;
114
+ if (authors !== null) {
115
+ const pa = Array.isArray(paper.authors) ? paper.authors : [];
116
+ if (!authors.some((a) => pa.includes(a)))
117
+ continue;
118
+ }
119
+ if (args.journal !== undefined && paper.journal !== args.journal)
120
+ continue;
121
+ if (args.published_year !== undefined) {
122
+ const pd = typeof paper.published_date === "string" ? paper.published_date : "";
123
+ if (!pd.startsWith(String(args.published_year)))
124
+ continue;
125
+ }
126
+ filtered.push(paper);
127
+ }
128
+ // Rank
129
+ let ranked = [];
130
+ if (keywords) {
131
+ const patterns = [];
132
+ for (const kw of keywords) {
133
+ try {
134
+ patterns.push(new RegExp(`\\b${escapeRegex(kw)}\\b`, "gi"));
135
+ }
136
+ catch {
137
+ /* skip un-compilable */
138
+ }
139
+ }
140
+ for (const p of filtered) {
141
+ const mmd = await readMmd(p.mmd_path);
142
+ const blob = `${p.title ?? ""} ${p.abstract ?? ""} ${mmd}`;
143
+ const hits = countKeywordHits(blob, patterns);
144
+ ranked.push({ paper: p, hits, mmd });
145
+ }
146
+ ranked.sort((a, b) => {
147
+ if (b.hits !== a.hits)
148
+ return b.hits - a.hits;
149
+ const yearA = Number((a.paper.published_date ?? "").slice(0, 4)) || 0;
150
+ const yearB = Number((b.paper.published_date ?? "").slice(0, 4)) || 0;
151
+ return yearB - yearA;
152
+ });
153
+ }
154
+ else {
155
+ ranked = filtered.map((p) => ({ paper: p, hits: 0, mmd: "" }));
156
+ }
157
+ const top = ranked.slice(0, topk);
158
+ if (mode === "meta-data") {
159
+ return top.map(({ paper, hits }) => ({
160
+ ...stripInternal(paper),
161
+ keyword_hits: hits,
162
+ }));
163
+ }
164
+ // full-paper
165
+ const out = [];
166
+ for (const { paper, hits, mmd } of top) {
167
+ const content = mmd || (await readMmd(paper.mmd_path));
168
+ const totalChars = content.length;
169
+ const totalSegments = totalChars > 0
170
+ ? Math.ceil(totalChars / SEGMENT_CHARS)
171
+ : 1;
172
+ const seg = Math.max(1, Math.min(segment, totalSegments));
173
+ const start = (seg - 1) * SEGMENT_CHARS;
174
+ const end = Math.min(start + SEGMENT_CHARS, totalChars);
175
+ out.push({
176
+ metadata: { ...stripInternal(paper), keyword_hits: hits },
177
+ mmd_content: content.slice(start, end),
178
+ segment_info: {
179
+ segment: seg,
180
+ total_segments: totalSegments,
181
+ total_chars: totalChars,
182
+ has_more: seg < totalSegments,
183
+ },
184
+ });
185
+ }
186
+ return out;
187
+ }
188
+ //# sourceMappingURL=search-papers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-papers.js","sourceRoot":"","sources":["../../../src/tools/kb/search-papers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /** Return the sidecar URL, starting the child if needed. Concurrent callers
2
+ * share a single startup promise. */
3
+ export declare function ensureSidecar(rootOverride?: string): Promise<string>;
4
+ /** Best-effort shutdown — called from server.ts on signal. */
5
+ export declare function stopSidecar(): void;
6
+ /** Read-only inspection helper for the health UI. */
7
+ export declare function sidecarStatus(): {
8
+ running: boolean;
9
+ url?: string;
10
+ };
11
+ //# sourceMappingURL=sidecar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../../../src/tools/kb/sidecar.ts"],"names":[],"mappings":"AA6JA;sCACsC;AACtC,wBAAsB,aAAa,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc1E;AAED,8DAA8D;AAC9D,wBAAgB,WAAW,IAAI,IAAI,CAKlC;AAED,qDAAqD;AACrD,wBAAgB,aAAa,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAElE"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Lazy bge model sidecar.
3
+ *
4
+ * bge-m3 and bge-reranker-v2-m3 take 5–20 s to load and ~2.5 GB of RAM —
5
+ * way too much for a per-query cost. The runtime keeps ONE shared Python
6
+ * sub-process running for the lifetime of the BrainPilot session and
7
+ * routes every embed/rerank call to it over loopback HTTP.
8
+ *
9
+ * Lifecycle:
10
+ * - cold: no child, no port, no readiness — first caller triggers start()
11
+ * - starting: child spawned with --port 0, port file polled until readable
12
+ * then `/health` polled until `embedder_loaded === true`
13
+ * - ready: every caller resolves to the same { url } record
14
+ * - dead: if the process exits, the next call retries from cold
15
+ *
16
+ * The user can bypass the sidecar entirely by setting `BP_KB_SERVER_URL`
17
+ * (point at a pre-existing model server). In that case we skip the spawn
18
+ * and just probe that URL.
19
+ */
20
+ import { spawn } from "node:child_process";
21
+ import { mkdtemp, readFile, unlink } from "node:fs/promises";
22
+ import { existsSync } from "node:fs";
23
+ import { tmpdir } from "node:os";
24
+ import { join } from "node:path";
25
+ import { resolveKbPaths } from "./paths.js";
26
+ import { resolveKbPython } from "./python.js";
27
+ const HEALTH_TIMEOUT_MS = 60_000; // first PyTorch load can be slow on CPU
28
+ const POLL_INTERVAL_MS = 500;
29
+ let STATE = null;
30
+ async function pollPortFile(path, deadlineMs) {
31
+ while (Date.now() < deadlineMs) {
32
+ if (existsSync(path)) {
33
+ try {
34
+ const text = (await readFile(path, "utf8")).trim();
35
+ const port = Number(text);
36
+ if (Number.isInteger(port) && port > 0)
37
+ return port;
38
+ }
39
+ catch {
40
+ /* race: writer mid-write, keep polling */
41
+ }
42
+ }
43
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
44
+ }
45
+ throw new Error(`sidecar did not publish its port within ${HEALTH_TIMEOUT_MS}ms`);
46
+ }
47
+ async function pollHealth(url, deadlineMs) {
48
+ let lastErr = null;
49
+ while (Date.now() < deadlineMs) {
50
+ try {
51
+ const r = await fetch(url + "/health");
52
+ if (r.ok) {
53
+ const h = (await r.json());
54
+ if (h.embedder_loaded && h.reranker_loaded)
55
+ return;
56
+ }
57
+ }
58
+ catch (err) {
59
+ lastErr = err;
60
+ }
61
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
62
+ }
63
+ throw new Error(`sidecar at ${url} not ready in ${HEALTH_TIMEOUT_MS}ms${lastErr ? `: ${lastErr.message}` : ""}`);
64
+ }
65
+ async function startSidecar(rootOverride) {
66
+ // Honour an out-of-process server set by the operator. Useful for: shared
67
+ // GPU box, dev iteration without restarting BrainPilot, integration tests.
68
+ const overrideUrl = process.env.BP_KB_SERVER_URL?.trim();
69
+ if (overrideUrl) {
70
+ await pollHealth(overrideUrl, Date.now() + HEALTH_TIMEOUT_MS);
71
+ return overrideUrl;
72
+ }
73
+ const paths = resolveKbPaths(rootOverride);
74
+ if (!existsSync(paths.serverScript)) {
75
+ throw new Error(`KnowledgeBase model_server.py not found at ${paths.serverScript} — ` +
76
+ `is the KnowledgeBase/ directory present alongside the runtime install?`);
77
+ }
78
+ if (!existsSync(paths.embedModelDir) || !existsSync(paths.rerankerModelDir)) {
79
+ throw new Error(`bge model weights missing under ${paths.modelsDir} — ` +
80
+ `run "python KnowledgeBase/scripts/setup_models.py" first.`);
81
+ }
82
+ const dir = await mkdtemp(join(tmpdir(), "bp-kb-"));
83
+ const portFile = join(dir, "port");
84
+ const child = spawn(resolveKbPython(paths.root), [paths.serverScript, "--kb-root", paths.root, "--port", "0", "--port-file", portFile], {
85
+ stdio: ["ignore", "inherit", "inherit"],
86
+ env: {
87
+ ...process.env,
88
+ BP_KB_ROOT: paths.root,
89
+ },
90
+ });
91
+ child.on("exit", (code, sig) => {
92
+ // eslint-disable-next-line no-console
93
+ console.warn(`[kb-sidecar] exited code=${code} signal=${sig}`);
94
+ // Drop the cached state so the next caller respawns.
95
+ if (STATE && STATE.proc === child) {
96
+ STATE.proc = undefined;
97
+ STATE.port = undefined;
98
+ STATE.url = undefined;
99
+ STATE.ready = null;
100
+ }
101
+ });
102
+ const deadline = Date.now() + HEALTH_TIMEOUT_MS;
103
+ let port;
104
+ try {
105
+ port = await pollPortFile(portFile, deadline);
106
+ }
107
+ catch (err) {
108
+ child.kill("SIGTERM");
109
+ throw err;
110
+ }
111
+ finally {
112
+ void unlink(portFile).catch(() => { });
113
+ }
114
+ const url = `http://127.0.0.1:${port}`;
115
+ try {
116
+ await pollHealth(url, deadline);
117
+ }
118
+ catch (err) {
119
+ child.kill("SIGTERM");
120
+ throw err;
121
+ }
122
+ // eslint-disable-next-line no-console
123
+ console.info(`[kb-sidecar] ready at ${url}`);
124
+ if (STATE) {
125
+ STATE.proc = child;
126
+ STATE.port = port;
127
+ STATE.url = url;
128
+ }
129
+ return url;
130
+ }
131
+ /** Return the sidecar URL, starting the child if needed. Concurrent callers
132
+ * share a single startup promise. */
133
+ export async function ensureSidecar(rootOverride) {
134
+ const paths = resolveKbPaths(rootOverride);
135
+ if (!STATE || STATE.paths.root !== paths.root) {
136
+ STATE = { paths, ready: null };
137
+ }
138
+ if (STATE.url)
139
+ return STATE.url;
140
+ if (!STATE.ready) {
141
+ STATE.ready = startSidecar(rootOverride).catch((err) => {
142
+ // Clear so the next call retries instead of returning a rejected promise forever.
143
+ if (STATE)
144
+ STATE.ready = null;
145
+ throw err;
146
+ });
147
+ }
148
+ return STATE.ready;
149
+ }
150
+ /** Best-effort shutdown — called from server.ts on signal. */
151
+ export function stopSidecar() {
152
+ if (STATE?.proc && !STATE.proc.killed) {
153
+ STATE.proc.kill("SIGTERM");
154
+ }
155
+ STATE = null;
156
+ }
157
+ /** Read-only inspection helper for the health UI. */
158
+ export function sidecarStatus() {
159
+ return { running: !!STATE?.url, url: STATE?.url };
160
+ }
161
+ //# sourceMappingURL=sidecar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidecar.js","sourceRoot":"","sources":["../../../src/tools/kb/sidecar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { type KbPaths } from "./paths.js";
2
+ export interface ChunkMetadata {
3
+ title?: string;
4
+ authors?: string[];
5
+ journal?: string;
6
+ published_date?: string;
7
+ abstract?: string;
8
+ pdf_url?: string;
9
+ mmd_path?: string;
10
+ chunk_index?: number;
11
+ char_start?: number;
12
+ char_end?: number;
13
+ }
14
+ export interface ChunkRecord {
15
+ chunk_id: string;
16
+ text: string;
17
+ metadata: ChunkMetadata;
18
+ }
19
+ interface CacheEntry {
20
+ embeddings: Float32Array;
21
+ chunks: ChunkRecord[];
22
+ count: number;
23
+ dim: number;
24
+ metaCount: number;
25
+ }
26
+ /**
27
+ * Load (or return the cached) vector store. If meta.json on disk reports a
28
+ * different row count than the cached entry, transparently reload — that's
29
+ * how a freshly-vectorized batch becomes visible to a running runtime
30
+ * without restart.
31
+ */
32
+ export declare function getStore(rootOverride?: string): Promise<CacheEntry & {
33
+ paths: KbPaths;
34
+ }>;
35
+ /** Best-effort sanity probe used by health endpoints / UI. */
36
+ export declare function storeHealth(rootOverride?: string): Promise<{
37
+ ready: boolean;
38
+ count: number;
39
+ reason?: string;
40
+ updatedAt?: string;
41
+ }>;
42
+ /** Drop the in-process cache (mainly for tests). */
43
+ export declare function clearStoreCache(): void;
44
+ export {};
45
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/tools/kb/store.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAkB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1D,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,UAAU,UAAU;IAClB,UAAU,EAAE,YAAY,CAAC;IACzB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAuGD;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAY9F;AAED,8DAA8D;AAC9D,wBAAsB,WAAW,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAChE,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAkBD;AAED,oDAAoD;AACpD,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * In-process load of the vector store written by ``scripts/vectorize.py``.
3
+ *
4
+ * The store is three small files:
5
+ * embeddings.npy — float32 (N, 1024), L2-normalised rows
6
+ * chunks.jsonl — one chunk per line, same row order as embeddings
7
+ * meta.json — {count, dim, model, updated_at, normalized}
8
+ *
9
+ * The matrix and the chunk list are cached for the lifetime of the process
10
+ * and reloaded transparently when ``meta.json`` reports a different count
11
+ * (i.e. the build pipeline appended new chunks since the last query).
12
+ *
13
+ * NPY parsing: we only support the exact file shape ``vectorize.py`` writes
14
+ * (float32, C-order, 2D). Anything else throws — the user re-ran some
15
+ * other tool against the store and we'd rather fail loud than mis-cast.
16
+ */
17
+ import { readFile, stat } from "node:fs/promises";
18
+ import { existsSync } from "node:fs";
19
+ import { resolveKbPaths } from "./paths.js";
20
+ let CACHE = null;
21
+ /**
22
+ * Read meta.json to learn the on-disk row count without loading the matrix.
23
+ * Returns null if meta.json is missing/unparseable — caller treats as cold.
24
+ */
25
+ async function readMetaCount(kb) {
26
+ try {
27
+ const raw = await readFile(kb.metaJson, "utf8");
28
+ const v = JSON.parse(raw)?.count;
29
+ return typeof v === "number" ? v : null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ /**
36
+ * Minimal numpy .npy v1.0/v2.0 reader for the exact shape vectorize.py writes.
37
+ * Format: '\x93NUMPY' magic, 1-byte major, 1-byte minor, header-length
38
+ * (uint16 in v1, uint32 in v2), header dict text, then row-major data.
39
+ */
40
+ function parseNpyFloat32(buf) {
41
+ if (buf.length < 10)
42
+ throw new Error(".npy: file too small");
43
+ if (buf[0] !== 0x93 ||
44
+ buf.slice(1, 6).toString("ascii") !== "NUMPY") {
45
+ throw new Error(".npy: bad magic");
46
+ }
47
+ const major = buf[1 + 5] ?? 0;
48
+ let headerLen;
49
+ let headerStart;
50
+ if (major === 1) {
51
+ headerLen = buf.readUInt16LE(8);
52
+ headerStart = 10;
53
+ }
54
+ else if (major === 2 || major === 3) {
55
+ headerLen = buf.readUInt32LE(8);
56
+ headerStart = 12;
57
+ }
58
+ else {
59
+ throw new Error(`.npy: unsupported major version ${major}`);
60
+ }
61
+ const header = buf.slice(headerStart, headerStart + headerLen).toString("ascii");
62
+ // Header is a Python dict literal. Pull just the bits we need with regex —
63
+ // bringing in a Python literal parser for this would be overkill.
64
+ const descrMatch = header.match(/'descr':\s*'([^']+)'/);
65
+ const fortranMatch = header.match(/'fortran_order':\s*(True|False)/);
66
+ const shapeMatch = header.match(/'shape':\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)/);
67
+ if (!descrMatch || !fortranMatch || !shapeMatch) {
68
+ throw new Error(`.npy: cannot parse header: ${header}`);
69
+ }
70
+ const descr = descrMatch[1];
71
+ if (descr !== "<f4" && descr !== "|f4" && descr !== "=f4") {
72
+ throw new Error(`.npy: only float32 (<f4) supported, got ${descr}`);
73
+ }
74
+ if (fortranMatch[1] === "True") {
75
+ throw new Error(".npy: fortran-order not supported");
76
+ }
77
+ const rows = Number(shapeMatch[1]);
78
+ const cols = Number(shapeMatch[2]);
79
+ const dataStart = headerStart + headerLen;
80
+ const expectedBytes = rows * cols * 4;
81
+ if (buf.length - dataStart < expectedBytes) {
82
+ throw new Error(`.npy: short data (need ${expectedBytes} bytes, have ${buf.length - dataStart})`);
83
+ }
84
+ // Float32Array view on the underlying ArrayBuffer (no copy).
85
+ const data = new Float32Array(buf.buffer, buf.byteOffset + dataStart, rows * cols);
86
+ return { data, rows, cols };
87
+ }
88
+ async function readChunksJsonl(path, expected) {
89
+ const raw = await readFile(path, "utf8");
90
+ const records = [];
91
+ // Split on \n, drop trailing empty line. Each line is one chunk.
92
+ for (const line of raw.split("\n")) {
93
+ if (!line)
94
+ continue;
95
+ records.push(JSON.parse(line));
96
+ }
97
+ if (records.length !== expected) {
98
+ throw new Error(`vector store mismatch: embeddings=${expected} chunks.jsonl=${records.length}`);
99
+ }
100
+ return records;
101
+ }
102
+ async function loadFresh(kb) {
103
+ if (!existsSync(kb.embeddingsNpy) || !existsSync(kb.chunksJsonl)) {
104
+ throw new Error(`vector store not found in ${kb.vectorstoreDir} — ` +
105
+ `run the build pipeline (scripts/build_kb.py) first.`);
106
+ }
107
+ const buf = await readFile(kb.embeddingsNpy);
108
+ const { data, rows, cols } = parseNpyFloat32(buf);
109
+ const chunks = await readChunksJsonl(kb.chunksJsonl, rows);
110
+ const metaCount = (await readMetaCount(kb)) ?? rows;
111
+ return { embeddings: data, chunks, count: rows, dim: cols, metaCount };
112
+ }
113
+ /**
114
+ * Load (or return the cached) vector store. If meta.json on disk reports a
115
+ * different row count than the cached entry, transparently reload — that's
116
+ * how a freshly-vectorized batch becomes visible to a running runtime
117
+ * without restart.
118
+ */
119
+ export async function getStore(rootOverride) {
120
+ const kb = resolveKbPaths(rootOverride);
121
+ if (CACHE && CACHE.paths.root === kb.root) {
122
+ const diskCount = await readMetaCount(kb);
123
+ if (diskCount === null || diskCount === CACHE.entry.metaCount) {
124
+ return { ...CACHE.entry, paths: kb };
125
+ }
126
+ // stale — fall through to a fresh load
127
+ }
128
+ const entry = await loadFresh(kb);
129
+ CACHE = { paths: kb, entry };
130
+ return { ...entry, paths: kb };
131
+ }
132
+ /** Best-effort sanity probe used by health endpoints / UI. */
133
+ export async function storeHealth(rootOverride) {
134
+ const kb = resolveKbPaths(rootOverride);
135
+ if (!existsSync(kb.embeddingsNpy) || !existsSync(kb.chunksJsonl)) {
136
+ return { ready: false, count: 0, reason: "vector store not built yet" };
137
+ }
138
+ try {
139
+ const meta = JSON.parse(await readFile(kb.metaJson, "utf8"));
140
+ return {
141
+ ready: typeof meta.count === "number" && meta.count > 0,
142
+ count: meta.count ?? 0,
143
+ updatedAt: meta.updated_at,
144
+ };
145
+ }
146
+ catch {
147
+ // meta.json missing but embeddings/chunks exist — still considered ready,
148
+ // just without the freshness metadata.
149
+ const st = await stat(kb.embeddingsNpy);
150
+ return { ready: true, count: 0, updatedAt: st.mtime.toISOString() };
151
+ }
152
+ }
153
+ /** Drop the in-process cache (mainly for tests). */
154
+ export function clearStoreCache() {
155
+ CACHE = null;
156
+ }
157
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/tools/kb/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Pi-native SystemTool factories for the local knowledge base:
3
+ *
4
+ * get_domain_knowledge_local
5
+ * RAG over the local vector store (bge-m3 + bge-reranker-v2-m3).
6
+ * Mirrors the legacy ``tools.py:get_domain_knowledge`` we shipped on the
7
+ * central MCP server, but everything is computed in-process: a sidecar
8
+ * Python child holds the model in memory and answers HTTP requests on
9
+ * loopback.
10
+ *
11
+ * search_papers_local
12
+ * Multi-criteria search over ``source/KB_source.json``. Same surface as
13
+ * ``tools.py:search_papers``.
14
+ *
15
+ * Both tools return STRINGS. Errors are returned as ``"ERROR: ..."`` strings
16
+ * (with the tool result also marked ``isError: true`` so Pi surfaces it as
17
+ * a failed call) — never thrown. This matches the legacy contract and lets
18
+ * agents react to a missing KB or an unreachable sidecar by simply reading
19
+ * the result.
20
+ */
21
+ import type { SystemTool } from "../../types.js";
22
+ export declare function createGetDomainKnowledgeLocalTool(): SystemTool;
23
+ export declare function createSearchPapersLocalTool(): SystemTool;
24
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/kb/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAqDjD,wBAAgB,iCAAiC,IAAI,UAAU,CAqF9D;AAID,wBAAgB,2BAA2B,IAAI,UAAU,CA8ExD"}