@tekmidian/pai 0.66.5 → 0.66.7

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 (118) hide show
  1. package/dist/{aibroker-client-WCZi_tn-.mjs → aibroker-client-BevXY1Zz.mjs} +3 -3
  2. package/dist/{aibroker-client-WCZi_tn-.mjs.map → aibroker-client-BevXY1Zz.mjs.map} +1 -1
  3. package/dist/{aibroker-client-Ck3p3k_-.mjs → aibroker-client-CxTpL_u7.mjs} +1 -1
  4. package/dist/{auto-route-BnizyALK.mjs → auto-route-BVeAp0fl.mjs} +1 -1
  5. package/dist/{auto-route-BnizyALK.mjs.map → auto-route-BVeAp0fl.mjs.map} +1 -1
  6. package/dist/{auto-route-D6fW1Q8z.mjs → auto-route-CboBqsN3.mjs} +1 -1
  7. package/dist/{chain-BhPwBDXB.mjs → chain-8hFKr23C.mjs} +3 -3
  8. package/dist/{chain-BhPwBDXB.mjs.map → chain-8hFKr23C.mjs.map} +1 -1
  9. package/dist/cli/index.mjs +16 -15
  10. package/dist/cli/index.mjs.map +1 -1
  11. package/dist/cli/program.mjs +15 -14
  12. package/dist/{config-CnsDMETf.mjs → config-UWtzLpXi.mjs} +1 -1
  13. package/dist/{config-Ddc4DIPk.mjs → config-sfaArCsl.mjs} +12 -3
  14. package/dist/config-sfaArCsl.mjs.map +1 -0
  15. package/dist/{context-handover-cache-BbCYQUGo.mjs → context-handover-cache-Bb-H_mTa.mjs} +7 -8
  16. package/dist/{context-handover-cache-BbCYQUGo.mjs.map → context-handover-cache-Bb-H_mTa.mjs.map} +1 -1
  17. package/dist/daemon/index.mjs +14 -13
  18. package/dist/daemon/index.mjs.map +1 -1
  19. package/dist/daemon-BXTn2EgV.mjs +19 -0
  20. package/dist/{daemon-DzXOfieK.mjs → daemon-CkbX-hV7.mjs} +757 -20
  21. package/dist/daemon-CkbX-hV7.mjs.map +1 -0
  22. package/dist/daemon-mcp/index.mjs +12 -11
  23. package/dist/daemon-mcp/index.mjs.map +1 -1
  24. package/dist/embedding-gate-BVrr0BgS.mjs +378 -0
  25. package/dist/embedding-gate-BVrr0BgS.mjs.map +1 -0
  26. package/dist/embedding-gate-Crj8awk7.mjs +5 -0
  27. package/dist/embeddings-BuA7v4lk.mjs +3 -0
  28. package/dist/{embeddings-DMVzpvRZ.mjs → embeddings-DFl88_y-.mjs} +16 -2
  29. package/dist/{embeddings-DMVzpvRZ.mjs.map → embeddings-DFl88_y-.mjs.map} +1 -1
  30. package/dist/{env-DiolswKQ.mjs → env-DqTjeKoA.mjs} +1 -1
  31. package/dist/{env-DiolswKQ.mjs.map → env-DqTjeKoA.mjs.map} +1 -1
  32. package/dist/factory-XrH66erA.mjs +9 -0
  33. package/dist/{factory-CXFHwvcv.mjs → factory-mNBitkL_.mjs} +28 -6
  34. package/dist/factory-mNBitkL_.mjs.map +1 -0
  35. package/dist/{fallback-CoooFqyA.mjs → fallback-DdC1_s2d.mjs} +4 -4
  36. package/dist/{fallback-CoooFqyA.mjs.map → fallback-DdC1_s2d.mjs.map} +1 -1
  37. package/dist/hooks/block-sleep-poll.mjs +7 -2
  38. package/dist/hooks/block-sleep-poll.mjs.map +3 -3
  39. package/dist/hooks/context-compression-hook.mjs +2 -0
  40. package/dist/hooks/context-compression-hook.mjs.map +2 -2
  41. package/dist/hooks/load-core-context.mjs +5 -5
  42. package/dist/hooks/load-core-context.mjs.map +2 -2
  43. package/dist/hooks/load-project-context.mjs +2 -0
  44. package/dist/hooks/load-project-context.mjs.map +2 -2
  45. package/dist/hooks/post-compact-inject.mjs +2 -0
  46. package/dist/hooks/post-compact-inject.mjs.map +2 -2
  47. package/dist/hooks/route-agents-to-worker.mjs +2 -0
  48. package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
  49. package/dist/hooks/security-validator.mjs +2 -0
  50. package/dist/hooks/security-validator.mjs.map +2 -2
  51. package/dist/hooks/whisper-rules.mjs +2 -0
  52. package/dist/hooks/whisper-rules.mjs.map +2 -2
  53. package/dist/hooks/worker-guard.mjs +82 -5
  54. package/dist/hooks/worker-guard.mjs.map +4 -4
  55. package/dist/hooks/worker-proxy.mjs +2 -0
  56. package/dist/hooks/worker-proxy.mjs.map +2 -2
  57. package/dist/hooks/worker-status-line.mjs +7 -2
  58. package/dist/hooks/worker-status-line.mjs.map +3 -3
  59. package/dist/hooks/worker-supervision.mjs +2 -0
  60. package/dist/hooks/worker-supervision.mjs.map +2 -2
  61. package/dist/{ipc-client-DkxRay-o.mjs → ipc-client-BgSzY9QJ.mjs} +2 -2
  62. package/dist/{ipc-client-DkxRay-o.mjs.map → ipc-client-BgSzY9QJ.mjs.map} +1 -1
  63. package/dist/main-resolver-CobQGm2B.mjs +7 -0
  64. package/dist/{main-resolver-SfGGg1DB.mjs → main-resolver-DEXky_Km.mjs} +6 -6
  65. package/dist/{main-resolver-SfGGg1DB.mjs.map → main-resolver-DEXky_Km.mjs.map} +1 -1
  66. package/dist/{planner-C43lzcw5.mjs → planner-DBoB5GtC.mjs} +10 -9
  67. package/dist/{planner-C43lzcw5.mjs.map → planner-DBoB5GtC.mjs.map} +1 -1
  68. package/dist/{postgres-BxmyQHoA.mjs → postgres-D-96lgE1.mjs} +84 -3
  69. package/dist/postgres-D-96lgE1.mjs.map +1 -0
  70. package/dist/postgres-TedJP3yF.mjs +7 -0
  71. package/dist/{program-DB-hoyfW.mjs → program-BXns_5kO.mjs} +212 -64
  72. package/dist/program-BXns_5kO.mjs.map +1 -0
  73. package/dist/{query-feedback-BPdapXKd.mjs → query-feedback-VI7Cl-Lr.mjs} +2 -2
  74. package/dist/{query-feedback-BPdapXKd.mjs.map → query-feedback-VI7Cl-Lr.mjs.map} +1 -1
  75. package/dist/router-CKn9e0fo.mjs +3 -0
  76. package/dist/{router-BaTbc9VX.mjs → router-dfjlaOMI.mjs} +2 -2
  77. package/dist/{router-BaTbc9VX.mjs.map → router-dfjlaOMI.mjs.map} +1 -1
  78. package/dist/{run-wi5NEkJd.mjs → run-DPwQkRwh.mjs} +109 -11
  79. package/dist/run-DPwQkRwh.mjs.map +1 -0
  80. package/dist/{run-env-CQRnxNht.mjs → run-env-CkAmx3lb.mjs} +3 -3
  81. package/dist/{run-env-CQRnxNht.mjs.map → run-env-CkAmx3lb.mjs.map} +1 -1
  82. package/dist/{pai-home-UncxWxlX.mjs → runtime-paths-BiEi_gaG.mjs} +50 -2
  83. package/dist/runtime-paths-BiEi_gaG.mjs.map +1 -0
  84. package/dist/{sources-CM2g-CLT.mjs → sources-BxXbzpAJ.mjs} +1 -1
  85. package/dist/{sources-CM2g-CLT.mjs.map → sources-BxXbzpAJ.mjs.map} +1 -1
  86. package/dist/{zettelkasten-G0-ZTybr.mjs → zettelkasten-CzcPzEsL.mjs} +5 -3
  87. package/dist/{zettelkasten-BRIaHzFm.mjs → zettelkasten-wh2Yy1nS.mjs} +5 -4
  88. package/dist/zettelkasten-wh2Yy1nS.mjs.map +1 -0
  89. package/docker/docker-compose.yml +4 -0
  90. package/docs/commands/README.md +6 -0
  91. package/docs/commands/memory.md +52 -0
  92. package/docs/commands/worker.md +23 -1
  93. package/docs/install-linux.md +1 -0
  94. package/docs/memory.md +69 -0
  95. package/package.json +1 -1
  96. package/src/hooks/ts/lib/sleep-poll-gate.test.ts +2 -2
  97. package/src/hooks/ts/lib/sleep-poll-gate.ts +4 -4
  98. package/src/hooks/ts/lib/worker-guard-longwait.test.ts +48 -0
  99. package/src/hooks/ts/lib/worker-guard.ts +50 -6
  100. package/src/hooks/ts/pre-tool-use/block-sleep-poll.test.ts +1 -1
  101. package/src/hooks/ts/session-start/load-core-context.ts +11 -8
  102. package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +34 -2
  103. package/dist/config-Ddc4DIPk.mjs.map +0 -1
  104. package/dist/daemon-DzXOfieK.mjs.map +0 -1
  105. package/dist/daemon-UJtRscyO.mjs +0 -18
  106. package/dist/embeddings-D0WOQpYR.mjs +0 -3
  107. package/dist/factory-CXFHwvcv.mjs.map +0 -1
  108. package/dist/factory-CnKsU6zX.mjs +0 -8
  109. package/dist/main-resolver-DrHtx8iW.mjs +0 -7
  110. package/dist/pai-home-UncxWxlX.mjs.map +0 -1
  111. package/dist/postgres-B2BEBT-J.mjs +0 -5
  112. package/dist/postgres-BxmyQHoA.mjs.map +0 -1
  113. package/dist/program-DB-hoyfW.mjs.map +0 -1
  114. package/dist/router-BK-hFeQ6.mjs +0 -3
  115. package/dist/run-wi5NEkJd.mjs.map +0 -1
  116. package/dist/runtime-paths-Bw3Jk8qJ.mjs +0 -52
  117. package/dist/runtime-paths-Bw3Jk8qJ.mjs.map +0 -1
  118. package/dist/zettelkasten-BRIaHzFm.mjs.map +0 -1
@@ -0,0 +1,378 @@
1
+ import { n as configureEmbeddingModel, o as generateEmbeddings, t as EMBEDDING_DIM } from "./embeddings-DFl88_y-.mjs";
2
+ import { s as loadConfig } from "./config-sfaArCsl.mjs";
3
+ import { createReadStream, createWriteStream, existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { createHash } from "node:crypto";
7
+ import { execFileSync } from "node:child_process";
8
+ import { Readable } from "node:stream";
9
+ import { pipeline } from "node:stream/promises";
10
+
11
+ //#region src/memory/embedding-binding.ts
12
+ /**
13
+ * What an index holding vectors but no recorded binding is: it predates
14
+ * binding, so its vectors are transformers-cpu-q8. Model and dims are unknown
15
+ * ("" / 0 = wildcard) and are filled in on the first embed that adopts it.
16
+ */
17
+ const LEGACY_EMBEDDING_BINDING = {
18
+ backend: "transformers-cpu-q8",
19
+ model: "",
20
+ dims: 0
21
+ };
22
+ function isInferredBinding(b) {
23
+ return b.model === "";
24
+ }
25
+ function bindingMatches(b, cur) {
26
+ return b.backend === cur.id && (b.model === "" || b.model === cur.model) && (b.dims === 0 || b.dims === cur.dims);
27
+ }
28
+ function describeBinding(b) {
29
+ return b.model ? `${b.backend} (${b.model})` : b.backend;
30
+ }
31
+ /** The hint shown wherever a binding mismatch pauses embedding (CLI, gate, dashboard). */
32
+ function mismatchReason(b, cur) {
33
+ return `index embedded with ${describeBinding(b)}, configured ${describeBinding({
34
+ backend: cur.id,
35
+ model: cur.model
36
+ })}: run \`pai memory reembed\` to switch`;
37
+ }
38
+
39
+ //#endregion
40
+ //#region src/memory/backends/transformers-cpu.ts
41
+ const TRANSFORMERS_ID = "transformers-cpu-q8";
42
+ function createTransformersBackend(model) {
43
+ return {
44
+ id: TRANSFORMERS_ID,
45
+ model,
46
+ dims: EMBEDDING_DIM,
47
+ maxTokens: 512,
48
+ async available() {
49
+ return {
50
+ ok: true,
51
+ reason: "in-process CPU (q8), no server needed"
52
+ };
53
+ },
54
+ async embed(texts) {
55
+ configureEmbeddingModel(model);
56
+ return generateEmbeddings(texts);
57
+ }
58
+ };
59
+ }
60
+
61
+ //#endregion
62
+ //#region src/memory/backends/ollama.ts
63
+ const OLLAMA_ID = "ollama-f16";
64
+ const OLLAMA_DEFAULT_MODEL = "arctic-embed-m-v1.5-f16";
65
+ const OLLAMA_DEFAULT_URL = "http://127.0.0.1:11434";
66
+ const TOKENIZER_MODEL = "Snowflake/snowflake-arctic-embed-m-v1.5";
67
+ const BATCH = 64;
68
+ const MAX_CONTENT_TOKENS = 510;
69
+ async function loadTokenizer() {
70
+ const { AutoTokenizer } = await import("@huggingface/transformers");
71
+ const tok = await AutoTokenizer.from_pretrained(TOKENIZER_MODEL);
72
+ return {
73
+ encode: (t) => tok.encode(t),
74
+ decode: (ids) => tok.decode(ids, { skip_special_tokens: true })
75
+ };
76
+ }
77
+ /**
78
+ * Cut `text` to at most 510 content tokens (512 with specials). Returns the
79
+ * text unchanged when it already fits.
80
+ */
81
+ function truncateToTokens(text, tok) {
82
+ if (text.length <= MAX_CONTENT_TOKENS) return {
83
+ text,
84
+ truncated: false
85
+ };
86
+ const ids = tok.encode(text);
87
+ if (ids.length <= MAX_CONTENT_TOKENS + 2) return {
88
+ text,
89
+ truncated: false
90
+ };
91
+ let keep = MAX_CONTENT_TOKENS;
92
+ let cut;
93
+ do {
94
+ cut = tok.decode(ids.slice(1, 1 + keep));
95
+ keep -= 5;
96
+ } while (tok.encode(cut).length > MAX_CONTENT_TOKENS + 2 && keep > 0);
97
+ return {
98
+ text: cut,
99
+ truncated: true
100
+ };
101
+ }
102
+ async function probeOllama(baseUrl, model, f = fetch) {
103
+ let names;
104
+ try {
105
+ const r = await f(`${baseUrl}/api/tags`, { signal: AbortSignal.timeout(2e3) });
106
+ if (!r.ok) return {
107
+ state: "server-down",
108
+ detail: `${baseUrl} answered HTTP ${r.status}`
109
+ };
110
+ names = (await r.json()).models?.map((m) => m.name) ?? [];
111
+ } catch {
112
+ return {
113
+ state: "server-down",
114
+ detail: `no Ollama server at ${baseUrl}`
115
+ };
116
+ }
117
+ return names.some((n) => n === model || n === `${model}:latest`) ? {
118
+ state: "ready",
119
+ detail: `server at ${baseUrl}, model ${model} present`
120
+ } : {
121
+ state: "model-missing",
122
+ detail: `server at ${baseUrl} reachable, model ${model} not present`
123
+ };
124
+ }
125
+ function createOllamaBackend(opts = {}, deps = {}) {
126
+ const baseUrl = (opts.baseUrl ?? OLLAMA_DEFAULT_URL).replace(/\/+$/, "");
127
+ const model = opts.model ?? OLLAMA_DEFAULT_MODEL;
128
+ const f = deps.fetch ?? fetch;
129
+ const log = deps.log ?? ((m) => process.stderr.write(`${m}\n`));
130
+ const getTok = deps.tokenizer ?? loadTokenizer;
131
+ let tok;
132
+ const dims = 768;
133
+ async function post(input) {
134
+ let r;
135
+ try {
136
+ r = await f(`${baseUrl}/api/embed`, {
137
+ method: "POST",
138
+ headers: { "content-type": "application/json" },
139
+ body: JSON.stringify({
140
+ model,
141
+ input,
142
+ keep_alive: "10m"
143
+ })
144
+ });
145
+ } catch (e) {
146
+ const code = e.cause?.code ?? "ECONNREFUSED";
147
+ throw Object.assign(/* @__PURE__ */ new Error(`Ollama unreachable at ${baseUrl} (${code})`), { code });
148
+ }
149
+ if (!r.ok) throw new Error(`Ollama /api/embed HTTP ${r.status}: ${(await r.text()).slice(0, 200)}`);
150
+ const body = await r.json();
151
+ if (!body.embeddings || body.embeddings.length !== input.length) throw new Error(`Ollama returned ${body.embeddings?.length ?? 0} embeddings for ${input.length} inputs`);
152
+ return body.embeddings;
153
+ }
154
+ return {
155
+ id: OLLAMA_ID,
156
+ model,
157
+ dims,
158
+ maxTokens: 512,
159
+ async available() {
160
+ const p = await probeOllama(baseUrl, model, f);
161
+ return {
162
+ ok: p.state === "ready",
163
+ reason: p.detail
164
+ };
165
+ },
166
+ async embed(texts) {
167
+ if (texts.length === 0) return [];
168
+ tok ??= await getTok();
169
+ let cutCount = 0;
170
+ const prepared = texts.map((t) => {
171
+ const r = truncateToTokens(t, tok);
172
+ if (r.truncated) cutCount++;
173
+ return r.text;
174
+ });
175
+ if (cutCount > 0) log(`[pai-embed] ollama: truncated ${cutCount}/${texts.length} texts to ${MAX_CONTENT_TOKENS} tokens`);
176
+ const out = [];
177
+ for (let i = 0; i < prepared.length; i += BATCH) for (const row of await post(prepared.slice(i, i + BATCH))) {
178
+ if (row.length !== dims) throw new Error(`Ollama model ${model} returned ${row.length}-dim vectors, expected ${dims}`);
179
+ const v = Float32Array.from(row);
180
+ let n = 0;
181
+ for (let k = 0; k < v.length; k++) n += v[k] * v[k];
182
+ if (Math.abs(Math.sqrt(n) - 1) > .001) throw new Error(`Ollama model ${model} returned non-normalized vectors (|v|=${Math.sqrt(n).toFixed(4)})`);
183
+ out.push(v);
184
+ }
185
+ return out;
186
+ }
187
+ };
188
+ }
189
+
190
+ //#endregion
191
+ //#region src/memory/backends/ollama-provision.ts
192
+ /**
193
+ * Provision the F16 GGUF of arctic-embed-m-v1.5 into a local Ollama.
194
+ * `ollama pull hf.co/...` fails on a Xet redirect, so the file is fetched
195
+ * directly and imported with `ollama create`. No sudo; idempotent.
196
+ */
197
+ const GGUF_URL = "https://huggingface.co/yixuan-chia/snowflake-arctic-embed-m-v1.5-GGUF/resolve/main/snowflake-arctic-embed-m-v1.5-F16.gguf";
198
+ const GGUF_SIZE = 219454752;
199
+ const GGUF_SHA256 = "ece140d563b01d1e0cdbf836d5ac002c0a1e5bb254540177f4f4c4ae0793853a";
200
+ function ollamaBinaryPath() {
201
+ for (const dir of (process.env.PATH ?? "").split(":").concat("/opt/homebrew/bin", "/usr/local/bin")) if (dir && existsSync(join(dir, "ollama"))) return join(dir, "ollama");
202
+ return null;
203
+ }
204
+ async function sha256File(path) {
205
+ const h = createHash("sha256");
206
+ await pipeline(createReadStream(path), h);
207
+ return h.digest("hex");
208
+ }
209
+ async function provisionOllama(opts) {
210
+ const log = opts.log ?? ((m) => console.log(m));
211
+ const bin = ollamaBinaryPath();
212
+ if (!bin) throw new Error("ollama is not installed (brew install ollama)");
213
+ const probe = await probeOllama(opts.baseUrl, opts.model);
214
+ if (probe.state === "ready") {
215
+ log(`Model ${opts.model} already present; nothing to do.`);
216
+ return { created: false };
217
+ }
218
+ if (probe.state === "server-down") throw new Error(`${probe.detail}; start it with \`ollama serve\``);
219
+ const dir = opts.workDir ?? join(homedir(), ".cache", "pai", "ollama");
220
+ mkdirSync(dir, { recursive: true });
221
+ const gguf = join(dir, "snowflake-arctic-embed-m-v1.5-F16.gguf");
222
+ if (!(existsSync(gguf) && statSync(gguf).size === GGUF_SIZE)) {
223
+ log(`Downloading ${GGUF_URL} (${Math.round(GGUF_SIZE / 1e6)} MB)...`);
224
+ const res = await fetch(GGUF_URL, { redirect: "follow" });
225
+ if (!res.ok || !res.body) throw new Error(`Download failed: HTTP ${res.status}`);
226
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(gguf));
227
+ }
228
+ const size = statSync(gguf).size;
229
+ if (size !== GGUF_SIZE) throw new Error(`Downloaded ${size} bytes, expected ${GGUF_SIZE}; delete ${gguf} and retry`);
230
+ const sha = await sha256File(gguf);
231
+ if (sha !== GGUF_SHA256) throw new Error(`Checksum mismatch for ${gguf}: ${sha}`);
232
+ const modelfile = join(dir, "Modelfile");
233
+ writeFileSync(modelfile, `FROM ${gguf}\n`);
234
+ log(`Importing as ${opts.model} (ollama create)...`);
235
+ execFileSync(bin, [
236
+ "create",
237
+ opts.model,
238
+ "-f",
239
+ modelfile
240
+ ], {
241
+ stdio: "inherit",
242
+ env: {
243
+ ...process.env,
244
+ OLLAMA_HOST: opts.baseUrl
245
+ }
246
+ });
247
+ return { created: true };
248
+ }
249
+
250
+ //#endregion
251
+ //#region src/memory/backends/index.ts
252
+ /**
253
+ * Backend registry: config → backend, plus discovery.
254
+ *
255
+ * Detection order is speed order: ollama (Metal GPU) first, then
256
+ * transformers-cpu. Add an "http" backend (e.g. MLX server) by implementing
257
+ * EmbeddingBackend and adding a case to createBackend() and a probe to
258
+ * detectBackends().
259
+ */
260
+ const BACKEND_IDS = [OLLAMA_ID, TRANSFORMERS_ID];
261
+ function createBackend(id, cfg = loadConfig(), modelOverride) {
262
+ switch (id) {
263
+ case OLLAMA_ID: return createOllamaBackend({
264
+ baseUrl: cfg.embedding?.ollama?.baseUrl,
265
+ model: modelOverride ?? cfg.embedding?.model ?? OLLAMA_DEFAULT_MODEL
266
+ });
267
+ case TRANSFORMERS_ID: return createTransformersBackend(cfg.embeddingModel || "Snowflake/snowflake-arctic-embed-m-v1.5");
268
+ default: throw new Error(`Unknown embedding backend "${id}". Known: ${BACKEND_IDS.join(", ")}`);
269
+ }
270
+ }
271
+ function getConfiguredBackend(cfg = loadConfig()) {
272
+ return createBackend(cfg.embedding?.backend || TRANSFORMERS_ID, cfg);
273
+ }
274
+ async function detectBackends(cfg = loadConfig(), deps = {}) {
275
+ const ollama = createBackend(OLLAMA_ID, cfg);
276
+ const p = await probeOllama(cfg.embedding?.ollama?.baseUrl ?? OLLAMA_DEFAULT_URL, ollama.model, deps.fetch);
277
+ const results = [];
278
+ if (p.state === "ready") results.push({
279
+ id: OLLAMA_ID,
280
+ ok: true,
281
+ reason: p.detail
282
+ });
283
+ else if (p.state === "model-missing") results.push({
284
+ id: OLLAMA_ID,
285
+ ok: false,
286
+ provisionable: true,
287
+ reason: `${p.detail}; run \`pai memory backend provision ollama\``
288
+ });
289
+ else if ((deps.ollamaBinary ?? ollamaBinaryPath)()) results.push({
290
+ id: OLLAMA_ID,
291
+ ok: false,
292
+ reason: `${p.detail}; ollama is installed, start it with \`ollama serve\``
293
+ });
294
+ else results.push({
295
+ id: OLLAMA_ID,
296
+ ok: false,
297
+ reason: `${p.detail}; ollama is not installed`
298
+ });
299
+ const t = await createBackend(TRANSFORMERS_ID, cfg).available();
300
+ results.push({
301
+ id: TRANSFORMERS_ID,
302
+ ...t
303
+ });
304
+ return {
305
+ results,
306
+ recommended: results.find((r) => r.ok)?.id ?? null
307
+ };
308
+ }
309
+
310
+ //#endregion
311
+ //#region src/memory/embedding-gate.ts
312
+ const QUERY_PREFIX = "Represent this sentence for searching relevant passages: ";
313
+ /**
314
+ * @param record Writing vectors: record (or complete) the index binding. Pass
315
+ * false for queries, which must not touch the binding.
316
+ */
317
+ async function gateBackend(storage, opts) {
318
+ const backend = opts.backend ?? getConfiguredBackend();
319
+ const binding = await storage.getEmbeddingBinding();
320
+ if (binding && !bindingMatches(binding, backend)) return {
321
+ ok: false,
322
+ kind: "mismatch",
323
+ reason: mismatchReason(binding, backend)
324
+ };
325
+ const avail = await backend.available();
326
+ if (!avail.ok) return {
327
+ ok: false,
328
+ kind: "unavailable",
329
+ reason: `embedding backend ${backend.id} unavailable: ${avail.reason}`
330
+ };
331
+ if (opts.record && (!binding || isInferredBinding(binding))) await storage.setEmbeddingBinding({
332
+ backend: backend.id,
333
+ model: backend.model,
334
+ dims: backend.dims
335
+ });
336
+ return {
337
+ ok: true,
338
+ backend
339
+ };
340
+ }
341
+ /** Query embedding, or null plus a note to show next to the keyword-only results. */
342
+ async function embedQuery(storage, text, backend) {
343
+ const gate = await gateBackend(storage, {
344
+ record: false,
345
+ backend
346
+ });
347
+ if (!gate.ok) return {
348
+ vec: null,
349
+ note: `${gate.reason}. Results are keyword-only.`
350
+ };
351
+ try {
352
+ const [vec] = await gate.backend.embed([QUERY_PREFIX + text]);
353
+ return { vec };
354
+ } catch (e) {
355
+ return {
356
+ vec: null,
357
+ note: `query embedding failed (${e instanceof Error ? e.message : String(e)}). Results are keyword-only.`
358
+ };
359
+ }
360
+ }
361
+ /** For callers with no keyword fallback (zettelkasten tools): throws the note. */
362
+ async function requireQueryEmbedding(storage, text) {
363
+ const r = await embedQuery(storage, text);
364
+ if (!r.vec) throw new Error(r.note);
365
+ return r.vec;
366
+ }
367
+ /** Cheap throughput probe for ETA: embeds `texts` and returns chunks/s. */
368
+ async function measureThroughput(backend, texts) {
369
+ if (texts.length === 0) return 0;
370
+ await backend.embed(texts.slice(0, 1));
371
+ const t0 = performance.now();
372
+ await backend.embed(texts);
373
+ return texts.length / ((performance.now() - t0) / 1e3);
374
+ }
375
+
376
+ //#endregion
377
+ export { requireQueryEmbedding as a, detectBackends as c, OLLAMA_DEFAULT_MODEL as d, OLLAMA_DEFAULT_URL as f, mismatchReason as g, bindingMatches as h, measureThroughput as i, getConfiguredBackend as l, LEGACY_EMBEDDING_BINDING as m, embedQuery as n, BACKEND_IDS as o, OLLAMA_ID as p, gateBackend as r, createBackend as s, QUERY_PREFIX as t, provisionOllama as u };
378
+ //# sourceMappingURL=embedding-gate-BVrr0BgS.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedding-gate-BVrr0BgS.mjs","names":[],"sources":["../src/memory/embedding-binding.ts","../src/memory/backends/transformers-cpu.ts","../src/memory/backends/ollama.ts","../src/memory/backends/ollama-provision.ts","../src/memory/backends/index.ts","../src/memory/embedding-gate.ts"],"sourcesContent":["/**\n * Index ↔ backend binding rules. Dependency-free so the storage layer can\n * import it.\n */\n\nimport type { EmbeddingBinding } from \"../storage/interface.js\";\n\n/**\n * What an index holding vectors but no recorded binding is: it predates\n * binding, so its vectors are transformers-cpu-q8. Model and dims are unknown\n * (\"\" / 0 = wildcard) and are filled in on the first embed that adopts it.\n */\nexport const LEGACY_EMBEDDING_BINDING: EmbeddingBinding = {\n backend: \"transformers-cpu-q8\",\n model: \"\",\n dims: 0,\n};\n\nexport function isInferredBinding(b: EmbeddingBinding): boolean {\n return b.model === \"\";\n}\n\nexport function bindingMatches(b: EmbeddingBinding, cur: { id: string; model: string; dims: number }): boolean {\n return (\n b.backend === cur.id &&\n (b.model === \"\" || b.model === cur.model) &&\n (b.dims === 0 || b.dims === cur.dims)\n );\n}\n\nexport function describeBinding(b: { backend: string; model: string }): string {\n return b.model ? `${b.backend} (${b.model})` : b.backend;\n}\n\n/** The hint shown wherever a binding mismatch pauses embedding (CLI, gate, dashboard). */\nexport function mismatchReason(b: EmbeddingBinding, cur: { id: string; model: string }): string {\n return `index embedded with ${describeBinding(b)}, configured ${describeBinding({ backend: cur.id, model: cur.model })}: run \\`pai memory reembed\\` to switch`;\n}\n","/** In-process transformers.js, q8, CPU. The original backend; vectors unchanged. */\n\nimport type { EmbeddingBackend } from \"./types.js\";\nimport { EMBEDDING_DIM, configureEmbeddingModel, generateEmbeddings } from \"../embeddings.js\";\n\nexport const TRANSFORMERS_ID = \"transformers-cpu-q8\";\n\nexport function createTransformersBackend(model: string): EmbeddingBackend {\n return {\n id: TRANSFORMERS_ID,\n model,\n dims: EMBEDDING_DIM,\n maxTokens: 512,\n async available() {\n return { ok: true, reason: \"in-process CPU (q8), no server needed\" };\n },\n async embed(texts) {\n configureEmbeddingModel(model);\n return generateEmbeddings(texts);\n },\n };\n}\n","/**\n * Ollama backend: POST {baseUrl}/api/embed, Metal GPU. Measured ~51 chunks/s\n * with an F16 GGUF of arctic-embed-m-v1.5 (vs ~8 for CPU q8), cosine 0.99999\n * against fp32 for inputs up to 510 tokens.\n *\n * Ollama cuts inputs over 510 real tokens differently from the model's own\n * truncation (cos ~0.45 vs fp32), so every text is cut to 510 tokens here\n * with the model's tokenizer before it is sent. Chunk boundaries in the\n * database are not touched.\n */\n\nimport type { EmbeddingBackend } from \"./types.js\";\n\nexport const OLLAMA_ID = \"ollama-f16\";\nexport const OLLAMA_DEFAULT_MODEL = \"arctic-embed-m-v1.5-f16\";\nexport const OLLAMA_DEFAULT_URL = \"http://127.0.0.1:11434\";\nexport const TOKENIZER_MODEL = \"Snowflake/snowflake-arctic-embed-m-v1.5\";\n\nconst BATCH = 64;\nconst MAX_CONTENT_TOKENS = 510; // 512 minus [CLS] and [SEP]\n\nexport interface Tokenizer {\n encode(text: string): number[];\n decode(ids: number[]): string;\n}\n\nexport interface OllamaDeps {\n fetch?: typeof fetch;\n tokenizer?: () => Promise<Tokenizer>;\n log?: (msg: string) => void;\n}\n\nasync function loadTokenizer(): Promise<Tokenizer> {\n const { AutoTokenizer } = await import(\"@huggingface/transformers\");\n const tok = await AutoTokenizer.from_pretrained(TOKENIZER_MODEL);\n return {\n encode: (t) => tok.encode(t) as number[],\n decode: (ids) => tok.decode(ids, { skip_special_tokens: true }),\n };\n}\n\n/**\n * Cut `text` to at most 510 content tokens (512 with specials). Returns the\n * text unchanged when it already fits.\n */\nexport function truncateToTokens(text: string, tok: Tokenizer): { text: string; truncated: boolean } {\n // A token is at least one character, so a short text cannot exceed the cap.\n if (text.length <= MAX_CONTENT_TOKENS) return { text, truncated: false };\n const ids = tok.encode(text);\n if (ids.length <= MAX_CONTENT_TOKENS + 2) return { text, truncated: false };\n let keep = MAX_CONTENT_TOKENS;\n let cut: string;\n // decode+re-encode can shift the count by a few tokens; shrink until it fits.\n do {\n cut = tok.decode(ids.slice(1, 1 + keep));\n keep -= 5;\n } while (tok.encode(cut).length > MAX_CONTENT_TOKENS + 2 && keep > 0);\n return { text: cut, truncated: true };\n}\n\nexport type OllamaState = \"ready\" | \"model-missing\" | \"server-down\";\n\nexport async function probeOllama(\n baseUrl: string,\n model: string,\n f: typeof fetch = fetch,\n): Promise<{ state: OllamaState; detail: string }> {\n let names: string[];\n try {\n const r = await f(`${baseUrl}/api/tags`, { signal: AbortSignal.timeout(2000) });\n if (!r.ok) return { state: \"server-down\", detail: `${baseUrl} answered HTTP ${r.status}` };\n names = ((await r.json()) as { models?: Array<{ name: string }> }).models?.map((m) => m.name) ?? [];\n } catch {\n return { state: \"server-down\", detail: `no Ollama server at ${baseUrl}` };\n }\n const present = names.some((n) => n === model || n === `${model}:latest`);\n return present\n ? { state: \"ready\", detail: `server at ${baseUrl}, model ${model} present` }\n : { state: \"model-missing\", detail: `server at ${baseUrl} reachable, model ${model} not present` };\n}\n\nexport function createOllamaBackend(\n opts: { baseUrl?: string; model?: string } = {},\n deps: OllamaDeps = {},\n): EmbeddingBackend {\n const baseUrl = (opts.baseUrl ?? OLLAMA_DEFAULT_URL).replace(/\\/+$/, \"\");\n const model = opts.model ?? OLLAMA_DEFAULT_MODEL;\n const f = deps.fetch ?? fetch;\n const log = deps.log ?? ((m: string) => process.stderr.write(`${m}\\n`));\n const getTok = deps.tokenizer ?? loadTokenizer;\n let tok: Tokenizer | undefined;\n const dims = 768;\n\n async function post(input: string[]): Promise<number[][]> {\n let r: Response;\n try {\n r = await f(`${baseUrl}/api/embed`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ model, input, keep_alive: \"10m\" }),\n });\n } catch (e) {\n // Surface the socket code so retryTransient treats a down server as transient.\n const code = (e as { cause?: { code?: string } }).cause?.code ?? \"ECONNREFUSED\";\n throw Object.assign(new Error(`Ollama unreachable at ${baseUrl} (${code})`), { code });\n }\n if (!r.ok) throw new Error(`Ollama /api/embed HTTP ${r.status}: ${(await r.text()).slice(0, 200)}`);\n const body = (await r.json()) as { embeddings?: number[][] };\n if (!body.embeddings || body.embeddings.length !== input.length) {\n throw new Error(`Ollama returned ${body.embeddings?.length ?? 0} embeddings for ${input.length} inputs`);\n }\n return body.embeddings;\n }\n\n return {\n id: OLLAMA_ID,\n model,\n dims,\n maxTokens: 512,\n async available() {\n const p = await probeOllama(baseUrl, model, f);\n return { ok: p.state === \"ready\", reason: p.detail };\n },\n async embed(texts) {\n if (texts.length === 0) return [];\n tok ??= await getTok();\n let cutCount = 0;\n const prepared = texts.map((t) => {\n const r = truncateToTokens(t, tok!);\n if (r.truncated) cutCount++;\n return r.text;\n });\n if (cutCount > 0) log(`[pai-embed] ollama: truncated ${cutCount}/${texts.length} texts to ${MAX_CONTENT_TOKENS} tokens`);\n\n const out: Float32Array[] = [];\n for (let i = 0; i < prepared.length; i += BATCH) {\n for (const row of await post(prepared.slice(i, i + BATCH))) {\n if (row.length !== dims) throw new Error(`Ollama model ${model} returned ${row.length}-dim vectors, expected ${dims}`);\n const v = Float32Array.from(row);\n let n = 0;\n for (let k = 0; k < v.length; k++) n += v[k] * v[k];\n if (Math.abs(Math.sqrt(n) - 1) > 1e-3) throw new Error(`Ollama model ${model} returned non-normalized vectors (|v|=${Math.sqrt(n).toFixed(4)})`);\n out.push(v);\n }\n }\n return out;\n },\n };\n}\n","/**\n * Provision the F16 GGUF of arctic-embed-m-v1.5 into a local Ollama.\n * `ollama pull hf.co/...` fails on a Xet redirect, so the file is fetched\n * directly and imported with `ollama create`. No sudo; idempotent.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { createReadStream, createWriteStream, existsSync, mkdirSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { homedir } from \"node:os\";\nimport { probeOllama } from \"./ollama.js\";\n\nexport const GGUF_URL =\n \"https://huggingface.co/yixuan-chia/snowflake-arctic-embed-m-v1.5-GGUF/resolve/main/snowflake-arctic-embed-m-v1.5-F16.gguf\";\nexport const GGUF_SIZE = 219454752;\nexport const GGUF_SHA256 = \"ece140d563b01d1e0cdbf836d5ac002c0a1e5bb254540177f4f4c4ae0793853a\";\n\nexport function ollamaBinaryPath(): string | null {\n for (const dir of (process.env.PATH ?? \"\").split(\":\").concat(\"/opt/homebrew/bin\", \"/usr/local/bin\")) {\n if (dir && existsSync(join(dir, \"ollama\"))) return join(dir, \"ollama\");\n }\n return null;\n}\n\nasync function sha256File(path: string): Promise<string> {\n const h = createHash(\"sha256\");\n await pipeline(createReadStream(path), h);\n return h.digest(\"hex\");\n}\n\nexport async function provisionOllama(opts: {\n baseUrl: string;\n model: string;\n /** Scratch dir for the download; default ~/.cache/pai/ollama. */\n workDir?: string;\n log?: (m: string) => void;\n}): Promise<{ created: boolean }> {\n const log = opts.log ?? ((m) => console.log(m));\n const bin = ollamaBinaryPath();\n if (!bin) throw new Error(\"ollama is not installed (brew install ollama)\");\n\n const probe = await probeOllama(opts.baseUrl, opts.model);\n if (probe.state === \"ready\") {\n log(`Model ${opts.model} already present; nothing to do.`);\n return { created: false };\n }\n if (probe.state === \"server-down\") throw new Error(`${probe.detail}; start it with \\`ollama serve\\``);\n\n const dir = opts.workDir ?? join(homedir(), \".cache\", \"pai\", \"ollama\");\n mkdirSync(dir, { recursive: true });\n const gguf = join(dir, \"snowflake-arctic-embed-m-v1.5-F16.gguf\");\n\n if (!(existsSync(gguf) && statSync(gguf).size === GGUF_SIZE)) {\n log(`Downloading ${GGUF_URL} (${Math.round(GGUF_SIZE / 1e6)} MB)...`);\n const res = await fetch(GGUF_URL, { redirect: \"follow\" });\n if (!res.ok || !res.body) throw new Error(`Download failed: HTTP ${res.status}`);\n await pipeline(Readable.fromWeb(res.body as never), createWriteStream(gguf));\n }\n const size = statSync(gguf).size;\n if (size !== GGUF_SIZE) throw new Error(`Downloaded ${size} bytes, expected ${GGUF_SIZE}; delete ${gguf} and retry`);\n const sha = await sha256File(gguf);\n if (sha !== GGUF_SHA256) throw new Error(`Checksum mismatch for ${gguf}: ${sha}`);\n\n const modelfile = join(dir, \"Modelfile\");\n writeFileSync(modelfile, `FROM ${gguf}\\n`);\n log(`Importing as ${opts.model} (ollama create)...`);\n execFileSync(bin, [\"create\", opts.model, \"-f\", modelfile], { stdio: \"inherit\", env: { ...process.env, OLLAMA_HOST: opts.baseUrl } });\n return { created: true };\n}\n","/**\n * Backend registry: config → backend, plus discovery.\n *\n * Detection order is speed order: ollama (Metal GPU) first, then\n * transformers-cpu. Add an \"http\" backend (e.g. MLX server) by implementing\n * EmbeddingBackend and adding a case to createBackend() and a probe to\n * detectBackends().\n */\n\nimport { loadConfig, type PaiDaemonConfig } from \"../../daemon/config.js\";\nimport type { EmbeddingBackend } from \"./types.js\";\nimport { TRANSFORMERS_ID, createTransformersBackend } from \"./transformers-cpu.js\";\nimport { OLLAMA_ID, OLLAMA_DEFAULT_MODEL, OLLAMA_DEFAULT_URL, createOllamaBackend, probeOllama } from \"./ollama.js\";\nimport { ollamaBinaryPath } from \"./ollama-provision.js\";\n\nexport const BACKEND_IDS = [OLLAMA_ID, TRANSFORMERS_ID] as const;\nexport { OLLAMA_ID, TRANSFORMERS_ID, OLLAMA_DEFAULT_MODEL, OLLAMA_DEFAULT_URL };\n\nexport function createBackend(\n id: string,\n cfg: Pick<PaiDaemonConfig, \"embedding\" | \"embeddingModel\"> = loadConfig(),\n modelOverride?: string,\n): EmbeddingBackend {\n switch (id) {\n case OLLAMA_ID:\n return createOllamaBackend({\n baseUrl: cfg.embedding?.ollama?.baseUrl,\n model: modelOverride ?? cfg.embedding?.model ?? OLLAMA_DEFAULT_MODEL,\n });\n case TRANSFORMERS_ID:\n return createTransformersBackend(cfg.embeddingModel || \"Snowflake/snowflake-arctic-embed-m-v1.5\");\n default:\n throw new Error(`Unknown embedding backend \"${id}\". Known: ${BACKEND_IDS.join(\", \")}`);\n }\n}\n\nexport function getConfiguredBackend(cfg: PaiDaemonConfig = loadConfig()): EmbeddingBackend {\n return createBackend(cfg.embedding?.backend || TRANSFORMERS_ID, cfg);\n}\n\nexport interface Detection {\n id: string;\n ok: boolean;\n reason: string;\n /** Model missing on a reachable server: `pai memory backend provision ollama` fixes it. */\n provisionable?: boolean;\n}\n\nexport async function detectBackends(\n cfg: Pick<PaiDaemonConfig, \"embedding\" | \"embeddingModel\"> = loadConfig(),\n deps: { fetch?: typeof fetch; ollamaBinary?: () => string | null } = {},\n): Promise<{ results: Detection[]; recommended: string | null }> {\n const ollama = createBackend(OLLAMA_ID, cfg) as ReturnType<typeof createOllamaBackend>;\n const baseUrl = cfg.embedding?.ollama?.baseUrl ?? OLLAMA_DEFAULT_URL;\n const p = await probeOllama(baseUrl, ollama.model, deps.fetch);\n const results: Detection[] = [];\n if (p.state === \"ready\") results.push({ id: OLLAMA_ID, ok: true, reason: p.detail });\n else if (p.state === \"model-missing\") {\n results.push({ id: OLLAMA_ID, ok: false, provisionable: true, reason: `${p.detail}; run \\`pai memory backend provision ollama\\`` });\n } else if ((deps.ollamaBinary ?? ollamaBinaryPath)()) {\n results.push({ id: OLLAMA_ID, ok: false, reason: `${p.detail}; ollama is installed, start it with \\`ollama serve\\`` });\n } else {\n results.push({ id: OLLAMA_ID, ok: false, reason: `${p.detail}; ollama is not installed` });\n }\n\n const t = await createBackend(TRANSFORMERS_ID, cfg).available();\n results.push({ id: TRANSFORMERS_ID, ...t });\n\n return { results, recommended: results.find((r) => r.ok)?.id ?? null };\n}\n","/**\n * The single entry point for embedding against an index: the configured\n * backend, checked against the backend the index's vectors came from.\n *\n * Mismatch or an unavailable backend never falls back to another backend:\n * embedding pauses (chunks stay NULL) and queries go keyword-only with a note.\n */\n\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport type { EmbeddingBackend } from \"./backends/types.js\";\nimport { getConfiguredBackend } from \"./backends/index.js\";\nimport { bindingMatches, isInferredBinding, mismatchReason } from \"./embedding-binding.js\";\n\nexport const QUERY_PREFIX = \"Represent this sentence for searching relevant passages: \";\n\nexport type Gate =\n | { ok: true; backend: EmbeddingBackend }\n | { ok: false; kind: \"mismatch\" | \"unavailable\"; reason: string };\n\n/**\n * @param record Writing vectors: record (or complete) the index binding. Pass\n * false for queries, which must not touch the binding.\n */\nexport async function gateBackend(\n storage: StorageBackend,\n opts: { record: boolean; backend?: EmbeddingBackend },\n): Promise<Gate> {\n const backend = opts.backend ?? getConfiguredBackend();\n const binding = await storage.getEmbeddingBinding();\n\n if (binding && !bindingMatches(binding, backend)) {\n return {\n ok: false,\n kind: \"mismatch\",\n reason: mismatchReason(binding, backend),\n };\n }\n const avail = await backend.available();\n if (!avail.ok) {\n return { ok: false, kind: \"unavailable\", reason: `embedding backend ${backend.id} unavailable: ${avail.reason}` };\n }\n if (opts.record && (!binding || isInferredBinding(binding))) {\n await storage.setEmbeddingBinding({ backend: backend.id, model: backend.model, dims: backend.dims });\n }\n return { ok: true, backend };\n}\n\n/** Query embedding, or null plus a note to show next to the keyword-only results. */\nexport async function embedQuery(\n storage: StorageBackend,\n text: string,\n backend?: EmbeddingBackend,\n): Promise<{ vec: Float32Array; note?: undefined } | { vec: null; note: string }> {\n const gate = await gateBackend(storage, { record: false, backend });\n if (!gate.ok) return { vec: null, note: `${gate.reason}. Results are keyword-only.` };\n try {\n const [vec] = await gate.backend.embed([QUERY_PREFIX + text]);\n return { vec };\n } catch (e) {\n return { vec: null, note: `query embedding failed (${e instanceof Error ? e.message : String(e)}). Results are keyword-only.` };\n }\n}\n\n/** For callers with no keyword fallback (zettelkasten tools): throws the note. */\nexport async function requireQueryEmbedding(storage: StorageBackend, text: string): Promise<Float32Array> {\n const r = await embedQuery(storage, text);\n if (!r.vec) throw new Error(r.note);\n return r.vec;\n}\n\n/** Cheap throughput probe for ETA: embeds `texts` and returns chunks/s. */\nexport async function measureThroughput(backend: EmbeddingBackend, texts: string[]): Promise<number> {\n if (texts.length === 0) return 0;\n await backend.embed(texts.slice(0, 1)); // warm: model load\n const t0 = performance.now();\n await backend.embed(texts);\n return texts.length / ((performance.now() - t0) / 1000);\n}\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { a as requireQueryEmbedding, i as measureThroughput, n as embedQuery, r as gateBackend, t as QUERY_PREFIX } from "./embedding-gate-BVrr0BgS.mjs";
2
+ import "./embeddings-DFl88_y-.mjs";
3
+ import "./config-sfaArCsl.mjs";
4
+
5
+ export { embedQuery, gateBackend };
@@ -0,0 +1,3 @@
1
+ import { a as generateEmbedding, i as deserializeEmbedding, n as configureEmbeddingModel, o as generateEmbeddings, r as cosineSimilarity, s as serializeEmbedding, t as EMBEDDING_DIM } from "./embeddings-DFl88_y-.mjs";
2
+
3
+ export { serializeEmbedding };
@@ -1,4 +1,18 @@
1
1
  //#region src/memory/embeddings.ts
2
+ /**
3
+ * Embedding generation for the PAI federation memory engine (Phase 2.5).
4
+ *
5
+ * Uses @huggingface/transformers with the Snowflake/snowflake-arctic-embed-m-v1.5 model
6
+ * (768 dims, q8 quantization, MTEB strong retrieval quality).
7
+ *
8
+ * The model uses CLS pooling (first token) — NOT mean pooling.
9
+ * For retrieval, queries require a prefix: "Represent this sentence for searching relevant passages: "
10
+ * Documents should be embedded WITHOUT a prefix.
11
+ *
12
+ * The pipeline is a lazy singleton — loaded on first call, reused thereafter.
13
+ * This avoids loading the heavy ML model on every CLI invocation.
14
+ */
15
+ const EMBEDDING_DIM = 768;
2
16
  const DEFAULT_EMBEDDING_MODEL = "Snowflake/snowflake-arctic-embed-m-v1.5";
3
17
  /** Texts per forward pass in generateEmbeddings (length-sorted, see there). */
4
18
  const SUB_BATCH = 16;
@@ -114,5 +128,5 @@ function cosineSimilarity(a, b) {
114
128
  }
115
129
 
116
130
  //#endregion
117
- export { generateEmbeddings as a, generateEmbedding as i, cosineSimilarity as n, serializeEmbedding as o, deserializeEmbedding as r, configureEmbeddingModel as t };
118
- //# sourceMappingURL=embeddings-DMVzpvRZ.mjs.map
131
+ export { generateEmbedding as a, deserializeEmbedding as i, configureEmbeddingModel as n, generateEmbeddings as o, cosineSimilarity as r, serializeEmbedding as s, EMBEDDING_DIM as t };
132
+ //# sourceMappingURL=embeddings-DFl88_y-.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"embeddings-DMVzpvRZ.mjs","names":[],"sources":["../src/memory/embeddings.ts"],"sourcesContent":["/**\n * Embedding generation for the PAI federation memory engine (Phase 2.5).\n *\n * Uses @huggingface/transformers with the Snowflake/snowflake-arctic-embed-m-v1.5 model\n * (768 dims, q8 quantization, MTEB strong retrieval quality).\n *\n * The model uses CLS pooling (first token) — NOT mean pooling.\n * For retrieval, queries require a prefix: \"Represent this sentence for searching relevant passages: \"\n * Documents should be embedded WITHOUT a prefix.\n *\n * The pipeline is a lazy singleton — loaded on first call, reused thereafter.\n * This avoids loading the heavy ML model on every CLI invocation.\n */\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nexport const EMBEDDING_DIM = 768;\nconst DEFAULT_EMBEDDING_MODEL = \"Snowflake/snowflake-arctic-embed-m-v1.5\";\n\n/** Texts per forward pass in generateEmbeddings (length-sorted, see there). */\nconst SUB_BATCH = 16;\n\n/** Query prefix required by Snowflake Arctic Embed for retrieval tasks. */\nconst QUERY_PREFIX = \"Represent this sentence for searching relevant passages: \";\n\n// ---------------------------------------------------------------------------\n// Lazy pipeline singleton\n// ---------------------------------------------------------------------------\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _embeddingPipeline: any = null;\nlet _currentModel: string | null = null;\n\n/**\n * Configure the embedding model to use.\n * Must be called before the first generateEmbedding() call.\n * If the pipeline is already loaded with a different model, it will be reloaded.\n *\n * @param model HuggingFace model ID (e.g. \"Snowflake/snowflake-arctic-embed-m-v1.5\").\n * Pass undefined or empty string to use the default model.\n */\nexport function configureEmbeddingModel(model?: string): void {\n const resolved = model?.trim() || DEFAULT_EMBEDDING_MODEL;\n if (_currentModel !== null && _currentModel !== resolved) {\n // Model changed — force reload on next call\n _embeddingPipeline = null;\n }\n _currentModel = resolved;\n}\n\nasync function getEmbedder() {\n const model = _currentModel ?? DEFAULT_EMBEDDING_MODEL;\n if (!_embeddingPipeline) {\n // Dynamic import to avoid loading the ML runtime on startup\n const { pipeline } = await import(\"@huggingface/transformers\");\n _embeddingPipeline = await pipeline(\n \"feature-extraction\",\n model,\n { dtype: \"q8\" },\n );\n }\n return _embeddingPipeline;\n}\n\n// ---------------------------------------------------------------------------\n// Embedding generation\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a normalized 768-dim embedding for the given text.\n *\n * Uses CLS pooling (first token) and L2 normalization (cosine similarity ready).\n *\n * @param text The text to embed.\n * @param isQuery If true, prepend the Snowflake query prefix. Use for search queries.\n * Documents should be embedded without the prefix (default: false).\n */\nexport async function generateEmbedding(text: string, isQuery: boolean = false): Promise<Float32Array> {\n const prefix = isQuery ? QUERY_PREFIX : \"\";\n const input = prefix + text;\n const extractor = await getEmbedder();\n // Snowflake Arctic Embed uses CLS pooling (first token), not mean pooling\n const output = await extractor(input, { pooling: \"cls\", normalize: true });\n return new Float32Array(output.data);\n}\n\n/**\n * Generate normalized embeddings for several documents in one forward pass.\n *\n * The model call dominates embedding cost, and calling it once per chunk leaves\n * most of the available throughput unused: measured on this machine, one-at-a-\n * time runs at ~5 chunks/s, which turns a six-figure backlog into days of\n * work. Batching amortises the per-call overhead across the batch.\n *\n * The pipeline returns one flat buffer for the whole batch, so it is sliced\n * back apart by row. Order is preserved: result[i] corresponds to texts[i].\n *\n * Documents only — queries take the prefixed single-text path, since a search\n * embeds exactly one string and gains nothing here.\n */\nexport async function generateEmbeddings(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n if (texts.length === 1) return [await generateEmbedding(texts[0])];\n\n const extractor = await getEmbedder();\n\n // A batch is padded to its longest text, and cost grows with padded length.\n // Sorting by length and running small sub-batches keeps neighbours similar:\n // measured 2.1x faster on mixed-length texts (5.5 -> 11.7 chunks/s).\n const order = texts.map((_, i) => i).sort((a, b) => texts[a].length - texts[b].length);\n const out: Float32Array[] = new Array(texts.length);\n for (let s = 0; s < order.length; s += SUB_BATCH) {\n const idx = order.slice(s, s + SUB_BATCH);\n const output = await extractor(idx.map((i) => texts[i]), { pooling: \"cls\", normalize: true });\n const flat = output.data as Float32Array;\n\n const dim = flat.length / idx.length;\n if (!Number.isInteger(dim)) {\n throw new Error(\n `Batched embedding returned ${flat.length} values for ${idx.length} inputs — not divisible`\n );\n }\n idx.forEach((orig, k) => {\n // Copy rather than subarray: the caller stores these, and a view would\n // pin the whole batch buffer in memory for the lifetime of one vector.\n out[orig] = new Float32Array(flat.slice(k * dim, (k + 1) * dim));\n });\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Serialization helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize a Float32Array to a Buffer for storage in a SQLite BLOB column.\n */\nexport function serializeEmbedding(vec: Float32Array): Buffer {\n return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);\n}\n\n/**\n * Deserialize a Buffer (from a SQLite BLOB column) back into a Float32Array.\n */\nexport function deserializeEmbedding(blob: Buffer): Float32Array {\n return new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);\n}\n\n// ---------------------------------------------------------------------------\n// Similarity computation\n// ---------------------------------------------------------------------------\n\n/**\n * Compute cosine similarity between two normalized embedding vectors.\n *\n * Since both vectors are already L2-normalized by the embedding model,\n * cosine similarity reduces to a dot product — but we compute the full\n * formula for correctness when embeddings may not be pre-normalized.\n *\n * Returns a value in [-1, 1] where 1 = identical.\n */\nexport function cosineSimilarity(a: Float32Array, b: Float32Array): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n if (denom === 0) return 0;\n return dot / denom;\n}\n"],"mappings":";AAmBA,MAAM,0BAA0B;;AAGhC,MAAM,YAAY;;AAGlB,MAAM,eAAe;AAOrB,IAAI,qBAA0B;AAC9B,IAAI,gBAA+B;;;;;;;;;AAUnC,SAAgB,wBAAwB,OAAsB;CAC5D,MAAM,WAAW,OAAO,MAAM,IAAI;AAClC,KAAI,kBAAkB,QAAQ,kBAAkB,SAE9C,sBAAqB;AAEvB,iBAAgB;;AAGlB,eAAe,cAAc;CAC3B,MAAM,QAAQ,iBAAiB;AAC/B,KAAI,CAAC,oBAAoB;EAEvB,MAAM,EAAE,aAAa,MAAM,OAAO;AAClC,uBAAqB,MAAM,SACzB,sBACA,OACA,EAAE,OAAO,MAAM,CAChB;;AAEH,QAAO;;;;;;;;;;;AAgBT,eAAsB,kBAAkB,MAAc,UAAmB,OAA8B;CAErG,MAAM,SADS,UAAU,eAAe,MACjB;CAGvB,MAAM,SAAS,OAFG,MAAM,aAAa,EAEN,OAAO;EAAE,SAAS;EAAO,WAAW;EAAM,CAAC;AAC1E,QAAO,IAAI,aAAa,OAAO,KAAK;;;;;;;;;;;;;;;;AAiBtC,eAAsB,mBAAmB,OAA0C;AACjF,KAAI,MAAM,WAAW,EAAG,QAAO,EAAE;AACjC,KAAI,MAAM,WAAW,EAAG,QAAO,CAAC,MAAM,kBAAkB,MAAM,GAAG,CAAC;CAElE,MAAM,YAAY,MAAM,aAAa;CAKrC,MAAM,QAAQ,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,MAAM,GAAG,SAAS,MAAM,GAAG,OAAO;CACtF,MAAM,MAAsB,IAAI,MAAM,MAAM,OAAO;AACnD,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,WAAW;EAChD,MAAM,MAAM,MAAM,MAAM,GAAG,IAAI,UAAU;EAEzC,MAAM,QADS,MAAM,UAAU,IAAI,KAAK,MAAM,MAAM,GAAG,EAAE;GAAE,SAAS;GAAO,WAAW;GAAM,CAAC,EACzE;EAEpB,MAAM,MAAM,KAAK,SAAS,IAAI;AAC9B,MAAI,CAAC,OAAO,UAAU,IAAI,CACxB,OAAM,IAAI,MACR,8BAA8B,KAAK,OAAO,cAAc,IAAI,OAAO,yBACpE;AAEH,MAAI,SAAS,MAAM,MAAM;AAGvB,OAAI,QAAQ,IAAI,aAAa,KAAK,MAAM,IAAI,MAAM,IAAI,KAAK,IAAI,CAAC;IAChE;;AAEJ,QAAO;;;;;AAUT,SAAgB,mBAAmB,KAA2B;AAC5D,QAAO,OAAO,KAAK,IAAI,QAAQ,IAAI,YAAY,IAAI,WAAW;;;;;AAMhE,SAAgB,qBAAqB,MAA4B;AAC/D,QAAO,IAAI,aAAa,KAAK,QAAQ,KAAK,YAAY,KAAK,aAAa,EAAE;;;;;;;;;;;AAgB5E,SAAgB,iBAAiB,GAAiB,GAAyB;CACzE,IAAI,MAAM;CACV,IAAI,QAAQ;CACZ,IAAI,QAAQ;AACZ,MAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,SAAO,EAAE,KAAK,EAAE;AAChB,WAAS,EAAE,KAAK,EAAE;AAClB,WAAS,EAAE,KAAK,EAAE;;CAEpB,MAAM,QAAQ,KAAK,KAAK,MAAM,GAAG,KAAK,KAAK,MAAM;AACjD,KAAI,UAAU,EAAG,QAAO;AACxB,QAAO,MAAM"}
1
+ {"version":3,"file":"embeddings-DFl88_y-.mjs","names":[],"sources":["../src/memory/embeddings.ts"],"sourcesContent":["/**\n * Embedding generation for the PAI federation memory engine (Phase 2.5).\n *\n * Uses @huggingface/transformers with the Snowflake/snowflake-arctic-embed-m-v1.5 model\n * (768 dims, q8 quantization, MTEB strong retrieval quality).\n *\n * The model uses CLS pooling (first token) — NOT mean pooling.\n * For retrieval, queries require a prefix: \"Represent this sentence for searching relevant passages: \"\n * Documents should be embedded WITHOUT a prefix.\n *\n * The pipeline is a lazy singleton — loaded on first call, reused thereafter.\n * This avoids loading the heavy ML model on every CLI invocation.\n */\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nexport const EMBEDDING_DIM = 768;\nconst DEFAULT_EMBEDDING_MODEL = \"Snowflake/snowflake-arctic-embed-m-v1.5\";\n\n/** Texts per forward pass in generateEmbeddings (length-sorted, see there). */\nconst SUB_BATCH = 16;\n\n/** Query prefix required by Snowflake Arctic Embed for retrieval tasks. */\nconst QUERY_PREFIX = \"Represent this sentence for searching relevant passages: \";\n\n// ---------------------------------------------------------------------------\n// Lazy pipeline singleton\n// ---------------------------------------------------------------------------\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _embeddingPipeline: any = null;\nlet _currentModel: string | null = null;\n\n/**\n * Configure the embedding model to use.\n * Must be called before the first generateEmbedding() call.\n * If the pipeline is already loaded with a different model, it will be reloaded.\n *\n * @param model HuggingFace model ID (e.g. \"Snowflake/snowflake-arctic-embed-m-v1.5\").\n * Pass undefined or empty string to use the default model.\n */\nexport function configureEmbeddingModel(model?: string): void {\n const resolved = model?.trim() || DEFAULT_EMBEDDING_MODEL;\n if (_currentModel !== null && _currentModel !== resolved) {\n // Model changed — force reload on next call\n _embeddingPipeline = null;\n }\n _currentModel = resolved;\n}\n\nasync function getEmbedder() {\n const model = _currentModel ?? DEFAULT_EMBEDDING_MODEL;\n if (!_embeddingPipeline) {\n // Dynamic import to avoid loading the ML runtime on startup\n const { pipeline } = await import(\"@huggingface/transformers\");\n _embeddingPipeline = await pipeline(\n \"feature-extraction\",\n model,\n { dtype: \"q8\" },\n );\n }\n return _embeddingPipeline;\n}\n\n// ---------------------------------------------------------------------------\n// Embedding generation\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a normalized 768-dim embedding for the given text.\n *\n * Uses CLS pooling (first token) and L2 normalization (cosine similarity ready).\n *\n * @param text The text to embed.\n * @param isQuery If true, prepend the Snowflake query prefix. Use for search queries.\n * Documents should be embedded without the prefix (default: false).\n */\nexport async function generateEmbedding(text: string, isQuery: boolean = false): Promise<Float32Array> {\n const prefix = isQuery ? QUERY_PREFIX : \"\";\n const input = prefix + text;\n const extractor = await getEmbedder();\n // Snowflake Arctic Embed uses CLS pooling (first token), not mean pooling\n const output = await extractor(input, { pooling: \"cls\", normalize: true });\n return new Float32Array(output.data);\n}\n\n/**\n * Generate normalized embeddings for several documents in one forward pass.\n *\n * The model call dominates embedding cost, and calling it once per chunk leaves\n * most of the available throughput unused: measured on this machine, one-at-a-\n * time runs at ~5 chunks/s, which turns a six-figure backlog into days of\n * work. Batching amortises the per-call overhead across the batch.\n *\n * The pipeline returns one flat buffer for the whole batch, so it is sliced\n * back apart by row. Order is preserved: result[i] corresponds to texts[i].\n *\n * Documents only — queries take the prefixed single-text path, since a search\n * embeds exactly one string and gains nothing here.\n */\nexport async function generateEmbeddings(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n if (texts.length === 1) return [await generateEmbedding(texts[0])];\n\n const extractor = await getEmbedder();\n\n // A batch is padded to its longest text, and cost grows with padded length.\n // Sorting by length and running small sub-batches keeps neighbours similar:\n // measured 2.1x faster on mixed-length texts (5.5 -> 11.7 chunks/s).\n const order = texts.map((_, i) => i).sort((a, b) => texts[a].length - texts[b].length);\n const out: Float32Array[] = new Array(texts.length);\n for (let s = 0; s < order.length; s += SUB_BATCH) {\n const idx = order.slice(s, s + SUB_BATCH);\n const output = await extractor(idx.map((i) => texts[i]), { pooling: \"cls\", normalize: true });\n const flat = output.data as Float32Array;\n\n const dim = flat.length / idx.length;\n if (!Number.isInteger(dim)) {\n throw new Error(\n `Batched embedding returned ${flat.length} values for ${idx.length} inputs — not divisible`\n );\n }\n idx.forEach((orig, k) => {\n // Copy rather than subarray: the caller stores these, and a view would\n // pin the whole batch buffer in memory for the lifetime of one vector.\n out[orig] = new Float32Array(flat.slice(k * dim, (k + 1) * dim));\n });\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Serialization helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize a Float32Array to a Buffer for storage in a SQLite BLOB column.\n */\nexport function serializeEmbedding(vec: Float32Array): Buffer {\n return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);\n}\n\n/**\n * Deserialize a Buffer (from a SQLite BLOB column) back into a Float32Array.\n */\nexport function deserializeEmbedding(blob: Buffer): Float32Array {\n return new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);\n}\n\n// ---------------------------------------------------------------------------\n// Similarity computation\n// ---------------------------------------------------------------------------\n\n/**\n * Compute cosine similarity between two normalized embedding vectors.\n *\n * Since both vectors are already L2-normalized by the embedding model,\n * cosine similarity reduces to a dot product — but we compute the full\n * formula for correctness when embeddings may not be pre-normalized.\n *\n * Returns a value in [-1, 1] where 1 = identical.\n */\nexport function cosineSimilarity(a: Float32Array, b: Float32Array): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n if (denom === 0) return 0;\n return dot / denom;\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,MAAa,gBAAgB;AAC7B,MAAM,0BAA0B;;AAGhC,MAAM,YAAY;;AAGlB,MAAM,eAAe;AAOrB,IAAI,qBAA0B;AAC9B,IAAI,gBAA+B;;;;;;;;;AAUnC,SAAgB,wBAAwB,OAAsB;CAC5D,MAAM,WAAW,OAAO,MAAM,IAAI;AAClC,KAAI,kBAAkB,QAAQ,kBAAkB,SAE9C,sBAAqB;AAEvB,iBAAgB;;AAGlB,eAAe,cAAc;CAC3B,MAAM,QAAQ,iBAAiB;AAC/B,KAAI,CAAC,oBAAoB;EAEvB,MAAM,EAAE,aAAa,MAAM,OAAO;AAClC,uBAAqB,MAAM,SACzB,sBACA,OACA,EAAE,OAAO,MAAM,CAChB;;AAEH,QAAO;;;;;;;;;;;AAgBT,eAAsB,kBAAkB,MAAc,UAAmB,OAA8B;CAErG,MAAM,SADS,UAAU,eAAe,MACjB;CAGvB,MAAM,SAAS,OAFG,MAAM,aAAa,EAEN,OAAO;EAAE,SAAS;EAAO,WAAW;EAAM,CAAC;AAC1E,QAAO,IAAI,aAAa,OAAO,KAAK;;;;;;;;;;;;;;;;AAiBtC,eAAsB,mBAAmB,OAA0C;AACjF,KAAI,MAAM,WAAW,EAAG,QAAO,EAAE;AACjC,KAAI,MAAM,WAAW,EAAG,QAAO,CAAC,MAAM,kBAAkB,MAAM,GAAG,CAAC;CAElE,MAAM,YAAY,MAAM,aAAa;CAKrC,MAAM,QAAQ,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,MAAM,GAAG,SAAS,MAAM,GAAG,OAAO;CACtF,MAAM,MAAsB,IAAI,MAAM,MAAM,OAAO;AACnD,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,WAAW;EAChD,MAAM,MAAM,MAAM,MAAM,GAAG,IAAI,UAAU;EAEzC,MAAM,QADS,MAAM,UAAU,IAAI,KAAK,MAAM,MAAM,GAAG,EAAE;GAAE,SAAS;GAAO,WAAW;GAAM,CAAC,EACzE;EAEpB,MAAM,MAAM,KAAK,SAAS,IAAI;AAC9B,MAAI,CAAC,OAAO,UAAU,IAAI,CACxB,OAAM,IAAI,MACR,8BAA8B,KAAK,OAAO,cAAc,IAAI,OAAO,yBACpE;AAEH,MAAI,SAAS,MAAM,MAAM;AAGvB,OAAI,QAAQ,IAAI,aAAa,KAAK,MAAM,IAAI,MAAM,IAAI,KAAK,IAAI,CAAC;IAChE;;AAEJ,QAAO;;;;;AAUT,SAAgB,mBAAmB,KAA2B;AAC5D,QAAO,OAAO,KAAK,IAAI,QAAQ,IAAI,YAAY,IAAI,WAAW;;;;;AAMhE,SAAgB,qBAAqB,MAA4B;AAC/D,QAAO,IAAI,aAAa,KAAK,QAAQ,KAAK,YAAY,KAAK,aAAa,EAAE;;;;;;;;;;;AAgB5E,SAAgB,iBAAiB,GAAiB,GAAyB;CACzE,IAAI,MAAM;CACV,IAAI,QAAQ;CACZ,IAAI,QAAQ;AACZ,MAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,SAAO,EAAE,KAAK,EAAE;AAChB,WAAS,EAAE,KAAK,EAAE;AAClB,WAAS,EAAE,KAAK,EAAE;;CAEpB,MAAM,QAAQ,KAAK,KAAK,MAAM,GAAG,KAAK,KAAK,MAAM;AACjD,KAAI,UAAU,EAAG,QAAO;AACxB,QAAO,MAAM"}
@@ -11,4 +11,4 @@ function dropItermBundleId(env = process.env, platform = process.platform) {
11
11
 
12
12
  //#endregion
13
13
  export { dropItermBundleId as t };
14
- //# sourceMappingURL=env-DiolswKQ.mjs.map
14
+ //# sourceMappingURL=env-DqTjeKoA.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"env-DiolswKQ.mjs","names":[],"sources":["../src/utils/env.ts"],"sourcesContent":["/**\n * iTerm2 exports __CFBundleIdentifier into its shells. Any inherited process\n * that touches AppKit/LaunchServices (osascript, chromedriver, helpers) then\n * registers AS iTerm2, and AppleScript sent to iTerm2 hits that impostor\n * (-600/-1708). Entry points call this first so nothing they spawn inherits it.\n */\nexport function dropItermBundleId(\n env: NodeJS.ProcessEnv = process.env,\n platform: NodeJS.Platform = process.platform,\n): void {\n if (platform === \"darwin\" && env.__CFBundleIdentifier === \"com.googlecode.iterm2\") {\n delete env.__CFBundleIdentifier;\n }\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,kBACd,MAAyB,QAAQ,KACjC,WAA4B,QAAQ,UAC9B;AACN,KAAI,aAAa,YAAY,IAAI,yBAAyB,wBACxD,QAAO,IAAI"}
1
+ {"version":3,"file":"env-DqTjeKoA.mjs","names":[],"sources":["../src/utils/env.ts"],"sourcesContent":["/**\n * iTerm2 exports __CFBundleIdentifier into its shells. Any inherited process\n * that touches AppKit/LaunchServices (osascript, chromedriver, helpers) then\n * registers AS iTerm2, and AppleScript sent to iTerm2 hits that impostor\n * (-600/-1708). Entry points call this first so nothing they spawn inherits it.\n */\nexport function dropItermBundleId(\n env: NodeJS.ProcessEnv = process.env,\n platform: NodeJS.Platform = process.platform,\n): void {\n if (platform === \"darwin\" && env.__CFBundleIdentifier === \"com.googlecode.iterm2\") {\n delete env.__CFBundleIdentifier;\n }\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,kBACd,MAAyB,QAAQ,KACjC,WAA4B,QAAQ,UAC9B;AACN,KAAI,aAAa,YAAY,IAAI,yBAAyB,wBACxD,QAAO,IAAI"}
@@ -0,0 +1,9 @@
1
+ import "./helpers-CZsi_49C.mjs";
2
+ import "./postgres-D-96lgE1.mjs";
3
+ import { a as getStorageBackend, i as getRegistryBackend, n as createRegistryBackend, r as createStorageBackend, t as closeStorage } from "./factory-mNBitkL_.mjs";
4
+ import "./embedding-gate-BVrr0BgS.mjs";
5
+ import "./embeddings-DFl88_y-.mjs";
6
+ import "./config-sfaArCsl.mjs";
7
+ import "./router-dfjlaOMI.mjs";
8
+
9
+ export { createStorageBackend, getRegistryBackend };
@@ -1,11 +1,12 @@
1
1
  import { a as parseSessionTitleChunk, c as yieldToEventLoop, f as fileContentHash, h as sha256File, i as isPathTooBroadForContentScan, n as chunkId, o as walkContentFiles, r as detectTier, s as walkMdFiles, t as INDEX_YIELD_EVERY, u as chunkMarkdown } from "./helpers-CZsi_49C.mjs";
2
2
  import { t as STOP_WORDS } from "./stop-words-DtxaTWU_.mjs";
3
3
  import { n as buildFtsQuery, r as isQuerySyntaxError } from "./search-CNAGTiJP.mjs";
4
- import { a as entityContentId, t as PostgresBackend } from "./postgres-BxmyQHoA.mjs";
5
- import { n as cosineSimilarity, r as deserializeEmbedding } from "./embeddings-DMVzpvRZ.mjs";
6
- import { t as routeNotification } from "./router-BaTbc9VX.mjs";
7
- import { s as loadConfig } from "./config-Ddc4DIPk.mjs";
8
- import { a as paiHomePath, o as resolvePaiFile } from "./pai-home-UncxWxlX.mjs";
4
+ import { a as entityContentId, t as PostgresBackend } from "./postgres-D-96lgE1.mjs";
5
+ import { m as LEGACY_EMBEDDING_BINDING } from "./embedding-gate-BVrr0BgS.mjs";
6
+ import { i as deserializeEmbedding, r as cosineSimilarity } from "./embeddings-DFl88_y-.mjs";
7
+ import { s as loadConfig } from "./config-sfaArCsl.mjs";
8
+ import { d as resolvePaiFile, u as paiHomePath } from "./runtime-paths-BiEi_gaG.mjs";
9
+ import { t as routeNotification } from "./router-dfjlaOMI.mjs";
9
10
  import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
10
11
  import { homedir } from "node:os";
11
12
  import { basename, dirname, join, relative } from "node:path";
@@ -77,6 +78,14 @@ CREATE TABLE IF NOT EXISTS memory_chunks (
77
78
  embedding BLOB
78
79
  );
79
80
 
81
+ CREATE TABLE IF NOT EXISTS embedding_binding (
82
+ id INTEGER PRIMARY KEY CHECK (id = 1),
83
+ backend TEXT NOT NULL,
84
+ model TEXT NOT NULL,
85
+ dims INTEGER NOT NULL,
86
+ updated_at INTEGER NOT NULL
87
+ );
88
+
80
89
  CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
81
90
  text,
82
91
  id UNINDEXED,
@@ -845,6 +854,19 @@ var SQLiteBackend = class {
845
854
  async updateEmbedding(chunkId, embedding) {
846
855
  this.db.prepare("UPDATE memory_chunks SET embedding = ? WHERE id = ?").run(embedding, chunkId);
847
856
  }
857
+ async getEmbeddingBinding() {
858
+ const row = this.db.prepare("SELECT backend, model, dims FROM embedding_binding WHERE id = 1").get();
859
+ if (row) return row;
860
+ return this.db.prepare("SELECT 1 FROM memory_chunks WHERE embedding IS NOT NULL LIMIT 1").get() ? { ...LEGACY_EMBEDDING_BINDING } : null;
861
+ }
862
+ async setEmbeddingBinding(b) {
863
+ this.db.prepare(`INSERT INTO embedding_binding (id, backend, model, dims, updated_at) VALUES (1, ?, ?, ?, ?)
864
+ ON CONFLICT(id) DO UPDATE SET backend = excluded.backend, model = excluded.model,
865
+ dims = excluded.dims, updated_at = excluded.updated_at`).run(b.backend, b.model, b.dims, Date.now());
866
+ }
867
+ async clearEmbeddingsBatch(limit) {
868
+ return this.db.prepare("UPDATE memory_chunks SET embedding = NULL WHERE id IN (SELECT id FROM memory_chunks WHERE embedding IS NOT NULL LIMIT ?)").run(limit).changes;
869
+ }
848
870
  async searchKeyword(query, opts) {
849
871
  return searchMemory(this.db, query, opts);
850
872
  }
@@ -2889,4 +2911,4 @@ async function closeStorage() {
2889
2911
 
2890
2912
  //#endregion
2891
2913
  export { getStorageBackend as a, MergeError as c, getBackendOutage as d, humanDuration as f, getRegistryBackend as i, federationDbPath as l, createRegistryBackend as n, oldRegistryPath as o, createStorageBackend as r, registryDbPath as s, closeStorage as t, oldFederationPath as u };
2892
- //# sourceMappingURL=factory-CXFHwvcv.mjs.map
2914
+ //# sourceMappingURL=factory-mNBitkL_.mjs.map