sensemaking 0.25.1 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/README.md +48 -12
  2. package/dist/cjs/cli/index.d.cts +3 -3
  3. package/dist/cjs/cli/index.d.ts +3 -3
  4. package/dist/cjs/cli/index.js +3 -3
  5. package/dist/cjs/cli/index.js.map +1 -1
  6. package/dist/cjs/cli/path.js +5 -29
  7. package/dist/cjs/cli/path.js.map +1 -1
  8. package/dist/cjs/cli/peek.js +43 -2
  9. package/dist/cjs/cli/peek.js.map +1 -1
  10. package/dist/cjs/cli/related.js +4 -3
  11. package/dist/cjs/cli/related.js.map +1 -1
  12. package/dist/cjs/cli/search.js +38 -6
  13. package/dist/cjs/cli/search.js.map +1 -1
  14. package/dist/cjs/commands/index.d.cts +6 -3
  15. package/dist/cjs/commands/index.d.ts +6 -3
  16. package/dist/cjs/commands/index.js +4 -0
  17. package/dist/cjs/commands/index.js.map +1 -1
  18. package/dist/cjs/commands/map.d.cts +14 -5
  19. package/dist/cjs/commands/map.d.ts +14 -5
  20. package/dist/cjs/commands/map.js +42 -35
  21. package/dist/cjs/commands/map.js.map +1 -1
  22. package/dist/cjs/commands/path.d.cts +14 -0
  23. package/dist/cjs/commands/path.d.ts +14 -0
  24. package/dist/cjs/commands/path.js +209 -0
  25. package/dist/cjs/commands/path.js.map +1 -0
  26. package/dist/cjs/commands/peek.d.cts +25 -2
  27. package/dist/cjs/commands/peek.d.ts +25 -2
  28. package/dist/cjs/commands/peek.js +131 -50
  29. package/dist/cjs/commands/peek.js.map +1 -1
  30. package/dist/cjs/commands/related.d.cts +16 -3
  31. package/dist/cjs/commands/related.d.ts +16 -3
  32. package/dist/cjs/commands/related.js +131 -98
  33. package/dist/cjs/commands/related.js.map +1 -1
  34. package/dist/cjs/commands/scope.d.cts +1 -0
  35. package/dist/cjs/commands/scope.d.ts +1 -0
  36. package/dist/cjs/commands/scope.js +149 -0
  37. package/dist/cjs/commands/scope.js.map +1 -1
  38. package/dist/cjs/commands/search.d.cts +30 -3
  39. package/dist/cjs/commands/search.d.ts +30 -3
  40. package/dist/cjs/commands/search.js +240 -73
  41. package/dist/cjs/commands/search.js.map +1 -1
  42. package/dist/cjs/commands/signals.d.cts +22 -7
  43. package/dist/cjs/commands/signals.d.ts +22 -7
  44. package/dist/cjs/commands/signals.js +77 -15
  45. package/dist/cjs/commands/signals.js.map +1 -1
  46. package/dist/cjs/commands/snapshot.d.cts +3 -0
  47. package/dist/cjs/commands/snapshot.d.ts +3 -0
  48. package/dist/cjs/commands/snapshot.js +180 -0
  49. package/dist/cjs/commands/snapshot.js.map +1 -0
  50. package/dist/cjs/config/load.js +6 -0
  51. package/dist/cjs/config/load.js.map +1 -1
  52. package/dist/cjs/embed/cohere.js +41 -16
  53. package/dist/cjs/embed/cohere.js.map +1 -1
  54. package/dist/cjs/embed/endpoint.d.cts +2 -0
  55. package/dist/cjs/embed/endpoint.d.ts +2 -0
  56. package/dist/cjs/embed/endpoint.js +30 -0
  57. package/dist/cjs/embed/endpoint.js.map +1 -0
  58. package/dist/cjs/embed/handoff.d.cts +7 -2
  59. package/dist/cjs/embed/handoff.d.ts +7 -2
  60. package/dist/cjs/embed/handoff.js +10 -0
  61. package/dist/cjs/embed/handoff.js.map +1 -1
  62. package/dist/cjs/embed/http.d.cts +18 -1
  63. package/dist/cjs/embed/http.d.ts +18 -1
  64. package/dist/cjs/embed/http.js +239 -40
  65. package/dist/cjs/embed/http.js.map +1 -1
  66. package/dist/cjs/embed/openai.js +39 -17
  67. package/dist/cjs/embed/openai.js.map +1 -1
  68. package/dist/cjs/embed/query.d.cts +9 -3
  69. package/dist/cjs/embed/query.d.ts +9 -3
  70. package/dist/cjs/embed/query.js +257 -90
  71. package/dist/cjs/embed/query.js.map +1 -1
  72. package/dist/cjs/embed/registry.d.cts +2 -2
  73. package/dist/cjs/embed/registry.d.ts +2 -2
  74. package/dist/cjs/embed/registry.js +36 -4
  75. package/dist/cjs/embed/registry.js.map +1 -1
  76. package/dist/cjs/embed/static.js +6 -2
  77. package/dist/cjs/embed/static.js.map +1 -1
  78. package/dist/cjs/embed/store.js +42 -77
  79. package/dist/cjs/embed/store.js.map +1 -1
  80. package/dist/cjs/embed/types.d.cts +5 -2
  81. package/dist/cjs/embed/types.d.ts +5 -2
  82. package/dist/cjs/embed/types.js.map +1 -1
  83. package/dist/cjs/features/embed.js +290 -43
  84. package/dist/cjs/features/embed.js.map +1 -1
  85. package/dist/cjs/graph/traverse.d.cts +2 -2
  86. package/dist/cjs/graph/traverse.d.ts +2 -2
  87. package/dist/cjs/graph/traverse.js +15 -5
  88. package/dist/cjs/graph/traverse.js.map +1 -1
  89. package/dist/cjs/index.d.cts +2 -2
  90. package/dist/cjs/index.d.ts +2 -2
  91. package/dist/cjs/index.js +6 -0
  92. package/dist/cjs/index.js.map +1 -1
  93. package/dist/cjs/output/output.d.cts +4 -36
  94. package/dist/cjs/output/output.d.ts +4 -36
  95. package/dist/cjs/output/output.js +1 -2
  96. package/dist/cjs/output/output.js.map +1 -1
  97. package/dist/cjs/scan/pool.d.cts +2 -1
  98. package/dist/cjs/scan/pool.d.ts +2 -1
  99. package/dist/cjs/scan/pool.js +116 -27
  100. package/dist/cjs/scan/pool.js.map +1 -1
  101. package/dist/cjs/scan/reparse.d.cts +1 -0
  102. package/dist/cjs/scan/reparse.d.ts +1 -0
  103. package/dist/cjs/scan/reparse.js +75 -21
  104. package/dist/cjs/scan/reparse.js.map +1 -1
  105. package/dist/cjs/store/builder.d.cts +11 -21
  106. package/dist/cjs/store/builder.d.ts +11 -21
  107. package/dist/cjs/store/builder.js +1077 -438
  108. package/dist/cjs/store/builder.js.map +1 -1
  109. package/dist/cjs/store/duckdb/connection.js +16 -2
  110. package/dist/cjs/store/duckdb/connection.js.map +1 -1
  111. package/dist/cjs/store/duckdb/open.d.cts +1 -1
  112. package/dist/cjs/store/duckdb/open.d.ts +1 -1
  113. package/dist/cjs/store/duckdb/open.js +73 -65
  114. package/dist/cjs/store/duckdb/open.js.map +1 -1
  115. package/dist/cjs/store/duckdb/reconcile.js +533 -15
  116. package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
  117. package/dist/cjs/store/embed-scope.js +19 -9
  118. package/dist/cjs/store/embed-scope.js.map +1 -1
  119. package/dist/cjs/store/index.d.cts +6 -0
  120. package/dist/cjs/store/index.d.ts +6 -0
  121. package/dist/cjs/store/index.js +8 -4
  122. package/dist/cjs/store/index.js.map +1 -1
  123. package/dist/cjs/store/open.d.cts +6 -1
  124. package/dist/cjs/store/open.d.ts +6 -1
  125. package/dist/cjs/store/open.js +230 -307
  126. package/dist/cjs/store/open.js.map +1 -1
  127. package/dist/cjs/store/readiness.d.cts +5 -0
  128. package/dist/cjs/store/readiness.d.ts +5 -0
  129. package/dist/cjs/store/readiness.js +39 -0
  130. package/dist/cjs/store/readiness.js.map +1 -0
  131. package/dist/cjs/store/reconcile.d.cts +26 -1
  132. package/dist/cjs/store/reconcile.d.ts +26 -1
  133. package/dist/cjs/store/reconcile.js +670 -576
  134. package/dist/cjs/store/reconcile.js.map +1 -1
  135. package/dist/cjs/store/signature.d.cts +1 -0
  136. package/dist/cjs/store/signature.d.ts +1 -0
  137. package/dist/cjs/store/signature.js +9 -0
  138. package/dist/cjs/store/signature.js.map +1 -1
  139. package/dist/cjs/store/sqlite/open.d.cts +1 -1
  140. package/dist/cjs/store/sqlite/open.d.ts +1 -1
  141. package/dist/cjs/store/sqlite/open.js +84 -90
  142. package/dist/cjs/store/sqlite/open.js.map +1 -1
  143. package/dist/cjs/store/sqlite/reconcile.js.map +1 -1
  144. package/dist/cjs/store/sqlite/store.js +63 -60
  145. package/dist/cjs/store/sqlite/store.js.map +1 -1
  146. package/dist/cjs/store/sqlite/vectors.d.cts +2 -2
  147. package/dist/cjs/store/sqlite/vectors.d.ts +2 -2
  148. package/dist/cjs/store/sqlite/vectors.js +8 -4
  149. package/dist/cjs/store/sqlite/vectors.js.map +1 -1
  150. package/dist/cjs/store/stages.js.map +1 -1
  151. package/dist/cjs/store/turso/connection.d.cts +1 -0
  152. package/dist/cjs/store/turso/connection.d.ts +1 -0
  153. package/dist/cjs/store/turso/connection.js +319 -94
  154. package/dist/cjs/store/turso/connection.js.map +1 -1
  155. package/dist/cjs/store/turso/open.d.cts +1 -1
  156. package/dist/cjs/store/turso/open.d.ts +1 -1
  157. package/dist/cjs/store/turso/open.js +70 -74
  158. package/dist/cjs/store/turso/open.js.map +1 -1
  159. package/dist/cjs/store/turso/store.js +25 -23
  160. package/dist/cjs/store/turso/store.js.map +1 -1
  161. package/dist/cjs/store/turso/vectors.d.cts +2 -2
  162. package/dist/cjs/store/turso/vectors.d.ts +2 -2
  163. package/dist/cjs/store/turso/vectors.js +26 -16
  164. package/dist/cjs/store/turso/vectors.js.map +1 -1
  165. package/dist/cjs/store/types.d.cts +4 -1
  166. package/dist/cjs/store/types.d.ts +4 -1
  167. package/dist/cjs/store/types.js.map +1 -1
  168. package/dist/cjs/store/vectors.d.cts +14 -1
  169. package/dist/cjs/store/vectors.d.ts +14 -1
  170. package/dist/cjs/store/vectors.js +61 -0
  171. package/dist/cjs/store/vectors.js.map +1 -1
  172. package/dist/cjs/watch.d.cts +6 -0
  173. package/dist/cjs/watch.d.ts +6 -0
  174. package/dist/cjs/watch.js +42 -10
  175. package/dist/cjs/watch.js.map +1 -1
  176. package/dist/cjs/workers/parse.d.cts +4 -3
  177. package/dist/cjs/workers/parse.d.ts +4 -3
  178. package/dist/cjs/workers/parse.js +19 -17
  179. package/dist/cjs/workers/parse.js.map +1 -1
  180. package/dist/esm/cli/index.d.ts +3 -3
  181. package/dist/esm/cli/index.js +3 -3
  182. package/dist/esm/cli/index.js.map +1 -1
  183. package/dist/esm/cli/path.js +4 -10
  184. package/dist/esm/cli/path.js.map +1 -1
  185. package/dist/esm/cli/peek.js +20 -2
  186. package/dist/esm/cli/peek.js.map +1 -1
  187. package/dist/esm/cli/related.js +5 -3
  188. package/dist/esm/cli/related.js.map +1 -1
  189. package/dist/esm/cli/search.js +12 -2
  190. package/dist/esm/cli/search.js.map +1 -1
  191. package/dist/esm/commands/index.d.ts +6 -3
  192. package/dist/esm/commands/index.js +1 -0
  193. package/dist/esm/commands/index.js.map +1 -1
  194. package/dist/esm/commands/map.d.ts +14 -5
  195. package/dist/esm/commands/map.js +2 -0
  196. package/dist/esm/commands/map.js.map +1 -1
  197. package/dist/esm/commands/path.d.ts +14 -0
  198. package/dist/esm/commands/path.js +33 -0
  199. package/dist/esm/commands/path.js.map +1 -0
  200. package/dist/esm/commands/peek.d.ts +25 -2
  201. package/dist/esm/commands/peek.js +59 -19
  202. package/dist/esm/commands/peek.js.map +1 -1
  203. package/dist/esm/commands/related.d.ts +16 -3
  204. package/dist/esm/commands/related.js +45 -28
  205. package/dist/esm/commands/related.js.map +1 -1
  206. package/dist/esm/commands/scope.d.ts +1 -0
  207. package/dist/esm/commands/scope.js +58 -0
  208. package/dist/esm/commands/scope.js.map +1 -1
  209. package/dist/esm/commands/search.d.ts +30 -3
  210. package/dist/esm/commands/search.js +95 -20
  211. package/dist/esm/commands/search.js.map +1 -1
  212. package/dist/esm/commands/signals.d.ts +22 -7
  213. package/dist/esm/commands/signals.js +74 -17
  214. package/dist/esm/commands/signals.js.map +1 -1
  215. package/dist/esm/commands/snapshot.d.ts +3 -0
  216. package/dist/esm/commands/snapshot.js +25 -0
  217. package/dist/esm/commands/snapshot.js.map +1 -0
  218. package/dist/esm/config/load.js +6 -0
  219. package/dist/esm/config/load.js.map +1 -1
  220. package/dist/esm/embed/cohere.js +35 -10
  221. package/dist/esm/embed/cohere.js.map +1 -1
  222. package/dist/esm/embed/endpoint.d.ts +2 -0
  223. package/dist/esm/embed/endpoint.js +11 -0
  224. package/dist/esm/embed/endpoint.js.map +1 -0
  225. package/dist/esm/embed/handoff.d.ts +7 -2
  226. package/dist/esm/embed/handoff.js +7 -0
  227. package/dist/esm/embed/handoff.js.map +1 -1
  228. package/dist/esm/embed/http.d.ts +18 -1
  229. package/dist/esm/embed/http.js +137 -17
  230. package/dist/esm/embed/http.js.map +1 -1
  231. package/dist/esm/embed/openai.js +34 -12
  232. package/dist/esm/embed/openai.js.map +1 -1
  233. package/dist/esm/embed/query.d.ts +9 -3
  234. package/dist/esm/embed/query.js +98 -26
  235. package/dist/esm/embed/query.js.map +1 -1
  236. package/dist/esm/embed/registry.d.ts +2 -2
  237. package/dist/esm/embed/registry.js +34 -5
  238. package/dist/esm/embed/registry.js.map +1 -1
  239. package/dist/esm/embed/static.js +10 -2
  240. package/dist/esm/embed/static.js.map +1 -1
  241. package/dist/esm/embed/store.js +30 -21
  242. package/dist/esm/embed/store.js.map +1 -1
  243. package/dist/esm/embed/types.d.ts +5 -2
  244. package/dist/esm/embed/types.js.map +1 -1
  245. package/dist/esm/features/embed.js +82 -21
  246. package/dist/esm/features/embed.js.map +1 -1
  247. package/dist/esm/graph/traverse.d.ts +2 -2
  248. package/dist/esm/graph/traverse.js +4 -2
  249. package/dist/esm/graph/traverse.js.map +1 -1
  250. package/dist/esm/index.d.ts +2 -2
  251. package/dist/esm/index.js +1 -1
  252. package/dist/esm/index.js.map +1 -1
  253. package/dist/esm/output/output.d.ts +4 -36
  254. package/dist/esm/output/output.js +1 -2
  255. package/dist/esm/output/output.js.map +1 -1
  256. package/dist/esm/scan/pool.d.ts +2 -1
  257. package/dist/esm/scan/pool.js +80 -23
  258. package/dist/esm/scan/pool.js.map +1 -1
  259. package/dist/esm/scan/reparse.d.ts +1 -0
  260. package/dist/esm/scan/reparse.js +40 -12
  261. package/dist/esm/scan/reparse.js.map +1 -1
  262. package/dist/esm/store/builder.d.ts +11 -21
  263. package/dist/esm/store/builder.js +337 -133
  264. package/dist/esm/store/builder.js.map +1 -1
  265. package/dist/esm/store/duckdb/connection.js +9 -1
  266. package/dist/esm/store/duckdb/connection.js.map +1 -1
  267. package/dist/esm/store/duckdb/open.d.ts +1 -1
  268. package/dist/esm/store/duckdb/open.js +13 -9
  269. package/dist/esm/store/duckdb/open.js.map +1 -1
  270. package/dist/esm/store/duckdb/reconcile.js +145 -10
  271. package/dist/esm/store/duckdb/reconcile.js.map +1 -1
  272. package/dist/esm/store/embed-scope.js +16 -6
  273. package/dist/esm/store/embed-scope.js.map +1 -1
  274. package/dist/esm/store/index.d.ts +6 -0
  275. package/dist/esm/store/index.js +10 -4
  276. package/dist/esm/store/index.js.map +1 -1
  277. package/dist/esm/store/open.d.ts +6 -1
  278. package/dist/esm/store/open.js +77 -87
  279. package/dist/esm/store/open.js.map +1 -1
  280. package/dist/esm/store/readiness.d.ts +5 -0
  281. package/dist/esm/store/readiness.js +11 -0
  282. package/dist/esm/store/readiness.js.map +1 -0
  283. package/dist/esm/store/reconcile.d.ts +26 -1
  284. package/dist/esm/store/reconcile.js +142 -120
  285. package/dist/esm/store/reconcile.js.map +1 -1
  286. package/dist/esm/store/signature.d.ts +1 -0
  287. package/dist/esm/store/signature.js +8 -0
  288. package/dist/esm/store/signature.js.map +1 -1
  289. package/dist/esm/store/sqlite/open.d.ts +1 -1
  290. package/dist/esm/store/sqlite/open.js +12 -12
  291. package/dist/esm/store/sqlite/open.js.map +1 -1
  292. package/dist/esm/store/sqlite/reconcile.js +2 -2
  293. package/dist/esm/store/sqlite/reconcile.js.map +1 -1
  294. package/dist/esm/store/sqlite/store.js +21 -18
  295. package/dist/esm/store/sqlite/store.js.map +1 -1
  296. package/dist/esm/store/sqlite/vectors.d.ts +2 -2
  297. package/dist/esm/store/sqlite/vectors.js +7 -5
  298. package/dist/esm/store/sqlite/vectors.js.map +1 -1
  299. package/dist/esm/store/stages.js.map +1 -1
  300. package/dist/esm/store/turso/connection.d.ts +1 -0
  301. package/dist/esm/store/turso/connection.js +97 -28
  302. package/dist/esm/store/turso/connection.js.map +1 -1
  303. package/dist/esm/store/turso/open.d.ts +1 -1
  304. package/dist/esm/store/turso/open.js +9 -8
  305. package/dist/esm/store/turso/open.js.map +1 -1
  306. package/dist/esm/store/turso/store.js +16 -14
  307. package/dist/esm/store/turso/store.js.map +1 -1
  308. package/dist/esm/store/turso/vectors.d.ts +2 -2
  309. package/dist/esm/store/turso/vectors.js +27 -17
  310. package/dist/esm/store/turso/vectors.js.map +1 -1
  311. package/dist/esm/store/types.d.ts +4 -1
  312. package/dist/esm/store/types.js.map +1 -1
  313. package/dist/esm/store/vectors.d.ts +14 -1
  314. package/dist/esm/store/vectors.js +55 -0
  315. package/dist/esm/store/vectors.js.map +1 -1
  316. package/dist/esm/watch.d.ts +6 -0
  317. package/dist/esm/watch.js +40 -14
  318. package/dist/esm/watch.js.map +1 -1
  319. package/dist/esm/workers/parse.d.ts +4 -3
  320. package/dist/esm/workers/parse.js +19 -17
  321. package/dist/esm/workers/parse.js.map +1 -1
  322. package/package.json +1 -1
  323. package/skills/sense/EXAMPLES.md +3 -3
  324. package/skills/sense/SKILL.md +11 -3
  325. package/skills/sense/references/search.md +17 -1
  326. package/skills/sense/references/sql.md +1 -1
  327. package/skills/sense/references/stores/duckdb.md +1 -1
  328. package/skills/sense/references/terminology.md +32 -0
  329. package/skills/sense-setup/SKILL.md +2 -2
  330. package/skills/sense-setup/references/embeddings.md +1 -1
  331. package/skills/sense-setup/references/store-benchmarks.md +5 -5
  332. package/skills/sense-setup/references/stores/duckdb.md +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/http.ts"],"sourcesContent":["const MAX_ATTEMPTS = 3;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// Shared retry glue for the HTTP providers: 429/5xx/network failure retry with exponential\n// backoff honoring Retry-After; any other status returns immediately for the caller to handle.\nexport async function fetchWithRetry(url: string, init: RequestInit, baseDelayMs = 200): Promise<Response> {\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {\n try {\n const res = await fetch(url, init);\n if (res.ok || (res.status !== 429 && res.status < 500) || attempt === MAX_ATTEMPTS - 1) return res;\n const retryAfter = Number(res.headers.get('retry-after'));\n // Capped: a server naming a huge Retry-After must not stall an embed run for it.\n await sleep(retryAfter > 0 ? Math.min(retryAfter * 1000, 10_000) : baseDelayMs * 2 ** attempt);\n } catch (err) {\n if (attempt === MAX_ATTEMPTS - 1) throw err;\n await sleep(baseDelayMs * 2 ** attempt);\n }\n }\n throw new Error('unreachable');\n}\n\n// Any HTTP response means the endpoint is up; only a network failure or the timeout counts as\n// unreachable, so an air-gapped machine never waits more than timeoutMs on `sense status`.\nexport async function probeReachable(url: string, timeoutMs = 1500): Promise<boolean> {\n try {\n await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });\n return true;\n } catch {\n return false;\n }\n}\n"],"names":["MAX_ATTEMPTS","sleep","ms","Promise","resolve","setTimeout","fetchWithRetry","url","init","baseDelayMs","attempt","res","fetch","ok","status","retryAfter","Number","headers","get","Math","min","err","Error","probeReachable","timeoutMs","signal","AbortSignal","timeout"],"mappings":"AAAA,MAAMA,eAAe;AAErB,SAASC,MAAMC,EAAU;IACvB,OAAO,IAAIC,QAAQ,CAACC,UAAYC,WAAWD,SAASF;AACtD;AAEA,2FAA2F;AAC3F,+FAA+F;AAC/F,OAAO,eAAeI,eAAeC,GAAW,EAAEC,IAAiB,EAAEC,cAAc,GAAG;IACpF,IAAK,IAAIC,UAAU,GAAGA,UAAUV,cAAcU,UAAW;QACvD,IAAI;YACF,MAAMC,MAAM,MAAMC,MAAML,KAAKC;YAC7B,IAAIG,IAAIE,EAAE,IAAKF,IAAIG,MAAM,KAAK,OAAOH,IAAIG,MAAM,GAAG,OAAQJ,YAAYV,eAAe,GAAG,OAAOW;YAC/F,MAAMI,aAAaC,OAAOL,IAAIM,OAAO,CAACC,GAAG,CAAC;YAC1C,iFAAiF;YACjF,MAAMjB,MAAMc,aAAa,IAAII,KAAKC,GAAG,CAACL,aAAa,MAAM,SAAUN,cAAc,KAAKC;QACxF,EAAE,OAAOW,KAAK;YACZ,IAAIX,YAAYV,eAAe,GAAG,MAAMqB;YACxC,MAAMpB,MAAMQ,cAAc,KAAKC;QACjC;IACF;IACA,MAAM,IAAIY,MAAM;AAClB;AAEA,8FAA8F;AAC9F,2FAA2F;AAC3F,OAAO,eAAeC,eAAehB,GAAW,EAAEiB,YAAY,IAAI;IAChE,IAAI;QACF,MAAMZ,MAAML,KAAK;YAAEkB,QAAQC,YAAYC,OAAO,CAACH;QAAW;QAC1D,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/http.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\n\nconst MAX_ATTEMPTS = 3;\nconst MAX_RETRY_AFTER_MS = 10_000;\nconst DEFAULT_BASE_DELAY_MS = 200;\n\n// Initial policy bounds small API/metadata responses separately from model-file downloads.\nexport const HTTP_ATTEMPT_TIMEOUT_MS = 30_000;\nexport const MODEL_FILE_ATTEMPT_TIMEOUT_MS = 5 * 60_000;\n\nexport type FetchRequestInit = Omit<RequestInit, 'signal'> & { signal?: never };\n\nexport interface FetchRetryOptions {\n signal?: AbortSignal;\n attemptTimeoutMs?: number;\n baseDelayMs?: number;\n}\n\nexport interface BufferedResponse {\n readonly ok: boolean;\n readonly status: number;\n readonly headers: Headers;\n readonly body: ArrayBuffer;\n readonly url: string;\n}\n\ntype ResponsePhase = 'response headers' | 'response body';\n\nfunction safeUrl(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.username = '';\n parsed.password = '';\n parsed.search = '';\n parsed.hash = '';\n return parsed.href;\n } catch {\n return 'the configured embedding endpoint';\n }\n}\n\nfunction attemptController(\n callerSignal: AbortSignal | undefined,\n timeoutMs: number\n): {\n controller: AbortController;\n timeoutReason: object;\n cleanup: () => void;\n} {\n callerSignal?.throwIfAborted();\n const controller = new AbortController();\n const timeoutReason = {};\n const relayAbort = () => controller.abort(callerSignal?.reason);\n callerSignal?.addEventListener('abort', relayAbort, { once: true });\n if (callerSignal?.aborted) relayAbort();\n const timer = setTimeout(() => controller.abort(timeoutReason), timeoutMs);\n return {\n controller,\n timeoutReason,\n cleanup: () => {\n clearTimeout(timer);\n callerSignal?.removeEventListener('abort', relayAbort);\n },\n };\n}\n\nfunction disposeResponse(response: Response, controller: AbortController): void {\n const cancellation = response.body && !response.bodyUsed ? response.body.cancel() : undefined;\n controller.abort();\n // The controller owns socket cleanup; observe stream cancellation without delaying the attempt.\n if (cancellation)\n void cancellation.then(\n () => undefined,\n () => undefined\n );\n}\n\nfunction sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n signal?.throwIfAborted();\n if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const cleanup = () => {\n if (timer) clearTimeout(timer);\n signal.removeEventListener('abort', onAbort);\n };\n const onAbort = () => {\n cleanup();\n reject(signal.reason);\n };\n signal.addEventListener('abort', onAbort, { once: true });\n timer = setTimeout(() => {\n cleanup();\n resolve();\n }, ms);\n if (signal.aborted) onAbort();\n });\n}\n\nfunction failedRequest(url: string, phase: ResponsePhase, attempts: number, timeoutMs: number, timedOut: boolean): SenseError {\n const endpoint = safeUrl(url);\n if (timedOut) {\n return new SenseError('EMBED_MODEL', `${endpoint} timed out after ${timeoutMs}ms while waiting for ${phase} (attempt ${attempts}/${MAX_ATTEMPTS}); check the embedding service and network`);\n }\n return new SenseError('EMBED_MODEL', `${endpoint} failed while waiting for ${phase} after ${attempts} attempts; check the embedding service and network`);\n}\n\nfunction buffered(response: Response, body: ArrayBuffer, requestUrl: string): BufferedResponse {\n return {\n ok: response.ok,\n status: response.status,\n headers: response.headers,\n body,\n url: safeUrl(response.url || requestUrl),\n };\n}\n\n// Each attempt covers response headers and the complete successful body. Retry cleanup and\n// backoff honor one caller signal without transferring ownership of that signal.\nexport async function fetchWithRetry(url: string, init: FetchRequestInit, options: FetchRetryOptions = {}): Promise<BufferedResponse> {\n const timeoutMs = options.attemptTimeoutMs ?? HTTP_ATTEMPT_TIMEOUT_MS;\n const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {\n options.signal?.throwIfAborted();\n const { controller, timeoutReason, cleanup } = attemptController(options.signal, timeoutMs);\n let response: Response | undefined;\n let phase: ResponsePhase = 'response headers';\n let retryDelayMs: number | undefined;\n try {\n response = await fetch(url, { ...init, signal: controller.signal });\n if (!response.ok) {\n const retryable = response.status === 429 || response.status >= 500;\n if (retryable && attempt < MAX_ATTEMPTS - 1) {\n const retryAfter = Number(response.headers.get('retry-after'));\n retryDelayMs = retryAfter > 0 ? Math.min(retryAfter * 1000, MAX_RETRY_AFTER_MS) : baseDelayMs * 2 ** attempt;\n disposeResponse(response, controller);\n } else {\n disposeResponse(response, controller);\n options.signal?.throwIfAborted();\n return buffered(response, new ArrayBuffer(0), url);\n }\n } else {\n phase = 'response body';\n const body = await response.arrayBuffer();\n options.signal?.throwIfAborted();\n return buffered(response, body, url);\n }\n } catch {\n try {\n options.signal?.throwIfAborted();\n } finally {\n if (response) disposeResponse(response, controller);\n }\n const timedOut = controller.signal.reason === timeoutReason;\n if (attempt === MAX_ATTEMPTS - 1) throw failedRequest(url, phase, attempt + 1, timeoutMs, timedOut);\n retryDelayMs = baseDelayMs * 2 ** attempt;\n } finally {\n cleanup();\n }\n await sleep(retryDelayMs ?? baseDelayMs * 2 ** attempt, options.signal);\n }\n throw new Error('unreachable');\n}\n\n// Any HTTP response means the endpoint is up. Abort it immediately because status does not\n// consume its body, and bound the headers wait for an air-gapped machine.\nexport async function probeReachable(url: string, timeoutMs = 1500): Promise<boolean> {\n const { controller, cleanup } = attemptController(undefined, timeoutMs);\n try {\n const response = await fetch(url, { signal: controller.signal });\n disposeResponse(response, controller);\n return true;\n } catch {\n return false;\n } finally {\n cleanup();\n }\n}\n"],"names":["SenseError","MAX_ATTEMPTS","MAX_RETRY_AFTER_MS","DEFAULT_BASE_DELAY_MS","HTTP_ATTEMPT_TIMEOUT_MS","MODEL_FILE_ATTEMPT_TIMEOUT_MS","safeUrl","url","parsed","URL","username","password","search","hash","href","attemptController","callerSignal","timeoutMs","throwIfAborted","controller","AbortController","timeoutReason","relayAbort","abort","reason","addEventListener","once","aborted","timer","setTimeout","cleanup","clearTimeout","removeEventListener","disposeResponse","response","cancellation","body","bodyUsed","cancel","undefined","then","sleep","ms","signal","Promise","resolve","reject","onAbort","failedRequest","phase","attempts","timedOut","endpoint","buffered","requestUrl","ok","status","headers","fetchWithRetry","init","options","attemptTimeoutMs","baseDelayMs","attempt","retryDelayMs","fetch","retryable","retryAfter","Number","get","Math","min","ArrayBuffer","arrayBuffer","Error","probeReachable"],"mappings":"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"}
@@ -1,17 +1,35 @@
1
1
  import { SenseError } from '../errors.js';
2
- import { fetchWithRetry } from './http.js';
2
+ import { effectiveEmbedEndpoint } from './endpoint.js';
3
+ import { fetchWithRetry, HTTP_ATTEMPT_TIMEOUT_MS } from './http.js';
3
4
  const BATCH_CAP = 64;
5
+ function isEmbedding(value) {
6
+ if (value === null || typeof value !== 'object' || !('embedding' in value) || !Array.isArray(value.embedding) || !value.embedding.every((part)=>typeof part === 'number')) return false;
7
+ return !('index' in value) || value.index === undefined || typeof value.index === 'number';
8
+ }
9
+ function parseBody(body, endpoint) {
10
+ let decoded;
11
+ try {
12
+ decoded = JSON.parse(new TextDecoder().decode(body));
13
+ } catch {
14
+ throw new SenseError('EMBED_MODEL', `${endpoint} returned invalid JSON`);
15
+ }
16
+ if (decoded === null || typeof decoded !== 'object' || !('data' in decoded) || !Array.isArray(decoded.data) || !decoded.data.every(isEmbedding)) {
17
+ throw new SenseError('EMBED_MODEL', `${endpoint} returned an invalid embedding response`);
18
+ }
19
+ return decoded.data;
20
+ }
4
21
  // One POST against any OpenAI-compatible /embeddings endpoint: Ollama, LM Studio, Cloudflare
5
22
  // Workers AI, Jina, Voyage, and Gemini's compat base URL all serve this shape.
6
23
  export async function openaiProvider(model, url, keyEnv) {
7
- if (!url) throw new SenseError('EMBED_MODEL', 'embed.provider "openai" requires a url');
8
- const base = url.replace(/\/+$/, '');
24
+ const base = effectiveEmbedEndpoint('openai', url);
25
+ if (!base) throw new SenseError('EMBED_MODEL', 'embed.provider "openai" requires a url');
9
26
  const headers = {
10
27
  'content-type': 'application/json'
11
28
  };
12
29
  const key = keyEnv ? process.env[keyEnv] : undefined;
13
30
  if (key) headers.authorization = `Bearer ${key}`;
14
- async function post(texts) {
31
+ async function post(texts, options) {
32
+ var _options_signal;
15
33
  const res = await fetchWithRetry(`${base}/embeddings`, {
16
34
  method: 'POST',
17
35
  headers,
@@ -19,17 +37,21 @@ export async function openaiProvider(model, url, keyEnv) {
19
37
  model,
20
38
  input: texts
21
39
  })
40
+ }, {
41
+ signal: options === null || options === void 0 ? void 0 : options.signal,
42
+ attemptTimeoutMs: HTTP_ATTEMPT_TIMEOUT_MS
22
43
  });
23
- if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/embeddings -> HTTP ${res.status}`);
24
- const body = await res.json();
44
+ options === null || options === void 0 ? void 0 : (_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
45
+ if (!res.ok) throw new SenseError('EMBED_MODEL', `${res.url} -> HTTP ${res.status}`);
46
+ const body = parseBody(res.body, res.url);
25
47
  // The schema carries `index` because response order is not promised, and the caller maps
26
48
  // vectors back to chunks positionally: sort by it whenever every element has one.
27
- const data = body.data.every((d)=>typeof d.index === 'number') ? [
28
- ...body.data
29
- ].sort((a, b)=>a.index - b.index) : body.data;
49
+ const data = body.every((d)=>typeof d.index === 'number') ? [
50
+ ...body
51
+ ].sort((a, b)=>a.index - b.index) : body;
30
52
  // A server that drops an input (empty or over-length strings are the usual cause) would
31
53
  // otherwise shift every later vector onto the wrong chunk, silently.
32
- if (data.length !== texts.length) throw new SenseError('EMBED_MODEL', `${base}/embeddings returned ${data.length} embeddings for ${texts.length} inputs`);
54
+ if (data.length !== texts.length) throw new SenseError('EMBED_MODEL', `${res.url} returned ${data.length} embeddings for ${texts.length} inputs`);
33
55
  return data.map((d)=>Float32Array.from(d.embedding));
34
56
  }
35
57
  const dims = (await post([
@@ -42,8 +64,8 @@ export async function openaiProvider(model, url, keyEnv) {
42
64
  dims,
43
65
  batchCap: BATCH_CAP,
44
66
  embedDocuments: post,
45
- embedQuery: async (text)=>(await post([
67
+ embedQuery: async (text, options)=>(await post([
46
68
  text
47
- ]))[0]
69
+ ], options))[0]
48
70
  };
49
71
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/openai.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { fetchWithRetry } from './http.ts';\nimport type { EmbedProvider } from './types.ts';\n\nconst BATCH_CAP = 64;\n\n// One POST against any OpenAI-compatible /embeddings endpoint: Ollama, LM Studio, Cloudflare\n// Workers AI, Jina, Voyage, and Gemini's compat base URL all serve this shape.\nexport async function openaiProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n if (!url) throw new SenseError('EMBED_MODEL', 'embed.provider \"openai\" requires a url');\n const base = url.replace(/\\/+$/, '');\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n const key = keyEnv ? process.env[keyEnv] : undefined;\n if (key) headers.authorization = `Bearer ${key}`;\n\n async function post(texts: string[]): Promise<Float32Array[]> {\n const res = await fetchWithRetry(`${base}/embeddings`, { method: 'POST', headers, body: JSON.stringify({ model, input: texts }) });\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/embeddings -> HTTP ${res.status}`);\n const body = (await res.json()) as { data: Array<{ embedding: number[]; index?: number }> };\n // The schema carries `index` because response order is not promised, and the caller maps\n // vectors back to chunks positionally: sort by it whenever every element has one.\n const data = body.data.every((d) => typeof d.index === 'number') ? [...body.data].sort((a, b) => (a.index as number) - (b.index as number)) : body.data;\n // A server that drops an input (empty or over-length strings are the usual cause) would\n // otherwise shift every later vector onto the wrong chunk, silently.\n if (data.length !== texts.length) throw new SenseError('EMBED_MODEL', `${base}/embeddings returned ${data.length} embeddings for ${texts.length} inputs`);\n return data.map((d) => Float32Array.from(d.embedding));\n }\n\n const dims = (await post(['dimension probe']))[0].length;\n // embedQuery and embedDocuments are symmetric here; per-model prefixes like nomic's\n // search_document/search_query are not applied.\n return { id: `openai:${base}:${model}`, dims, batchCap: BATCH_CAP, embedDocuments: post, embedQuery: async (text) => (await post([text]))[0] };\n}\n"],"names":["SenseError","fetchWithRetry","BATCH_CAP","openaiProvider","model","url","keyEnv","base","replace","headers","key","process","env","undefined","authorization","post","texts","res","method","body","JSON","stringify","input","ok","status","json","data","every","d","index","sort","a","b","length","map","Float32Array","from","embedding","dims","id","batchCap","embedDocuments","embedQuery","text"],"mappings":"AAAA,SAASA,UAAU,QAAQ,eAAe;AAC1C,SAASC,cAAc,QAAQ,YAAY;AAG3C,MAAMC,YAAY;AAElB,6FAA6F;AAC7F,+EAA+E;AAC/E,OAAO,eAAeC,eAAeC,KAAa,EAAEC,GAAuB,EAAEC,MAA0B;IACrG,IAAI,CAACD,KAAK,MAAM,IAAIL,WAAW,eAAe;IAC9C,MAAMO,OAAOF,IAAIG,OAAO,CAAC,QAAQ;IACjC,MAAMC,UAAkC;QAAE,gBAAgB;IAAmB;IAC7E,MAAMC,MAAMJ,SAASK,QAAQC,GAAG,CAACN,OAAO,GAAGO;IAC3C,IAAIH,KAAKD,QAAQK,aAAa,GAAG,CAAC,OAAO,EAAEJ,KAAK;IAEhD,eAAeK,KAAKC,KAAe;QACjC,MAAMC,MAAM,MAAMhB,eAAe,GAAGM,KAAK,WAAW,CAAC,EAAE;YAAEW,QAAQ;YAAQT;YAASU,MAAMC,KAAKC,SAAS,CAAC;gBAAEjB;gBAAOkB,OAAON;YAAM;QAAG;QAChI,IAAI,CAACC,IAAIM,EAAE,EAAE,MAAM,IAAIvB,WAAW,eAAe,GAAGO,KAAK,oBAAoB,EAAEU,IAAIO,MAAM,EAAE;QAC3F,MAAML,OAAQ,MAAMF,IAAIQ,IAAI;QAC5B,yFAAyF;QACzF,kFAAkF;QAClF,MAAMC,OAAOP,KAAKO,IAAI,CAACC,KAAK,CAAC,CAACC,IAAM,OAAOA,EAAEC,KAAK,KAAK,YAAY;eAAIV,KAAKO,IAAI;SAAC,CAACI,IAAI,CAAC,CAACC,GAAGC,IAAM,AAACD,EAAEF,KAAK,GAAeG,EAAEH,KAAK,IAAeV,KAAKO,IAAI;QACvJ,wFAAwF;QACxF,qEAAqE;QACrE,IAAIA,KAAKO,MAAM,KAAKjB,MAAMiB,MAAM,EAAE,MAAM,IAAIjC,WAAW,eAAe,GAAGO,KAAK,qBAAqB,EAAEmB,KAAKO,MAAM,CAAC,gBAAgB,EAAEjB,MAAMiB,MAAM,CAAC,OAAO,CAAC;QACxJ,OAAOP,KAAKQ,GAAG,CAAC,CAACN,IAAMO,aAAaC,IAAI,CAACR,EAAES,SAAS;IACtD;IAEA,MAAMC,OAAO,AAAC,CAAA,MAAMvB,KAAK;QAAC;KAAkB,CAAA,CAAE,CAAC,EAAE,CAACkB,MAAM;IACxD,oFAAoF;IACpF,gDAAgD;IAChD,OAAO;QAAEM,IAAI,CAAC,OAAO,EAAEhC,KAAK,CAAC,EAAEH,OAAO;QAAEkC;QAAME,UAAUtC;QAAWuC,gBAAgB1B;QAAM2B,YAAY,OAAOC,OAAS,AAAC,CAAA,MAAM5B,KAAK;gBAAC4B;aAAK,CAAA,CAAE,CAAC,EAAE;IAAC;AAC/I"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/openai.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { effectiveEmbedEndpoint } from './endpoint.ts';\nimport { fetchWithRetry, HTTP_ATTEMPT_TIMEOUT_MS } from './http.ts';\nimport type { EmbedCallOptions, EmbedProvider } from './types.ts';\n\nconst BATCH_CAP = 64;\n\ninterface OpenaiEmbedding {\n embedding: number[];\n index?: number;\n}\n\nfunction isEmbedding(value: unknown): value is OpenaiEmbedding {\n if (value === null || typeof value !== 'object' || !('embedding' in value) || !Array.isArray(value.embedding) || !value.embedding.every((part) => typeof part === 'number')) return false;\n return !('index' in value) || value.index === undefined || typeof value.index === 'number';\n}\n\nfunction parseBody(body: ArrayBuffer, endpoint: string): OpenaiEmbedding[] {\n let decoded: unknown;\n try {\n decoded = JSON.parse(new TextDecoder().decode(body));\n } catch {\n throw new SenseError('EMBED_MODEL', `${endpoint} returned invalid JSON`);\n }\n if (decoded === null || typeof decoded !== 'object' || !('data' in decoded) || !Array.isArray(decoded.data) || !decoded.data.every(isEmbedding)) {\n throw new SenseError('EMBED_MODEL', `${endpoint} returned an invalid embedding response`);\n }\n return decoded.data;\n}\n\n// One POST against any OpenAI-compatible /embeddings endpoint: Ollama, LM Studio, Cloudflare\n// Workers AI, Jina, Voyage, and Gemini's compat base URL all serve this shape.\nexport async function openaiProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n const base = effectiveEmbedEndpoint('openai', url);\n if (!base) throw new SenseError('EMBED_MODEL', 'embed.provider \"openai\" requires a url');\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n const key = keyEnv ? process.env[keyEnv] : undefined;\n if (key) headers.authorization = `Bearer ${key}`;\n\n async function post(texts: string[], options?: EmbedCallOptions): Promise<Float32Array[]> {\n const res = await fetchWithRetry(`${base}/embeddings`, { method: 'POST', headers, body: JSON.stringify({ model, input: texts }) }, { signal: options?.signal, attemptTimeoutMs: HTTP_ATTEMPT_TIMEOUT_MS });\n options?.signal?.throwIfAborted();\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${res.url} -> HTTP ${res.status}`);\n const body = parseBody(res.body, res.url);\n // The schema carries `index` because response order is not promised, and the caller maps\n // vectors back to chunks positionally: sort by it whenever every element has one.\n const data = body.every((d): d is OpenaiEmbedding & { index: number } => typeof d.index === 'number') ? [...body].sort((a, b) => a.index - b.index) : body;\n // A server that drops an input (empty or over-length strings are the usual cause) would\n // otherwise shift every later vector onto the wrong chunk, silently.\n if (data.length !== texts.length) throw new SenseError('EMBED_MODEL', `${res.url} returned ${data.length} embeddings for ${texts.length} inputs`);\n return data.map((d) => Float32Array.from(d.embedding));\n }\n\n const dims = (await post(['dimension probe']))[0].length;\n // embedQuery and embedDocuments are symmetric here; per-model prefixes like nomic's\n // search_document/search_query are not applied.\n return { id: `openai:${base}:${model}`, dims, batchCap: BATCH_CAP, embedDocuments: post, embedQuery: async (text, options) => (await post([text], options))[0] };\n}\n"],"names":["SenseError","effectiveEmbedEndpoint","fetchWithRetry","HTTP_ATTEMPT_TIMEOUT_MS","BATCH_CAP","isEmbedding","value","Array","isArray","embedding","every","part","index","undefined","parseBody","body","endpoint","decoded","JSON","parse","TextDecoder","decode","data","openaiProvider","model","url","keyEnv","base","headers","key","process","env","authorization","post","texts","options","res","method","stringify","input","signal","attemptTimeoutMs","throwIfAborted","ok","status","d","sort","a","b","length","map","Float32Array","from","dims","id","batchCap","embedDocuments","embedQuery","text"],"mappings":"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"}
@@ -1,12 +1,18 @@
1
- import type { Config } from '../config/index.js';
1
+ import { type Config } from '../config/index.js';
2
2
  import type { Store, VectorCandidate, VectorSimilar } from '../store/types.js';
3
+ import { type EmbedCallOptions } from './types.js';
3
4
  export declare function toStore(full: Float32Array, dims: number, int8: boolean): {
4
5
  v: Float32Array;
5
6
  scale: number;
6
7
  };
7
- export declare function embedPending(store: Store, cfg: Config, allowed?: ReadonlySet<string>): Promise<void>;
8
+ export declare function embedPending(store: Store, cfg: Config, allowed?: ReadonlySet<string>, expectedSignature?: string | null, options?: EmbedCallOptions): Promise<void>;
8
9
  export declare function ensureDocumentEmbeddings(store: Store, _cfg: Config, allowed: ReadonlySet<string>): Promise<void>;
9
- export declare function semanticCandidates(store: Store, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]>;
10
+ export interface PreparedSemanticQuery {
11
+ readonly vector: Float32Array;
12
+ readonly storeDims: number;
13
+ }
14
+ export declare function prepareSemanticQuery(cfg: Config, terms: string, options?: EmbedCallOptions): Promise<PreparedSemanticQuery>;
15
+ export declare function semanticCandidates(store: Store, prepared: PreparedSemanticQuery, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]>;
10
16
  export declare function hasEmbedding(store: Store, path: string): Promise<boolean>;
11
17
  export declare function similarNotes(store: Store, _cfg: Config, path: string, opts: {
12
18
  exclude: Set<string>;
@@ -1,8 +1,13 @@
1
+ import { featureSignature } from '../config/index.js';
1
2
  import { SenseError } from '../errors.js';
2
3
  import { embed } from '../features/embed.js';
4
+ import { FEATURES } from '../features/index.js';
3
5
  import { progress } from '../output/progress.js';
4
6
  import { parseSource } from '../scan/index.js';
5
- import { takeChunkText } from './handoff.js';
7
+ import { FEATURE_SIGNATURE_META_KEY } from '../store/readiness.js';
8
+ import { getMeta } from '../store/shared.js';
9
+ import { embeddingSignatureCompatible } from '../store/signature.js';
10
+ import { identifyChunkText, takeChunkText } from './handoff.js';
6
11
  import { checkLanguageFit } from './langfit.js';
7
12
  import { getProvider } from './registry.js';
8
13
  import { STORE_DIMS } from './types.js';
@@ -28,38 +33,64 @@ export function toStore(full, dims, int8) {
28
33
  // Embed rows whose vector is NULL. Reconcile hands its chunk text over in memory when it ran in
29
34
  // this process (handoff.ts); a row left pending by an earlier command re-derives from the exact
30
35
  // source string persisted with that indexed generation.
31
- export async function embedPending(store, cfg, allowed) {
36
+ function staleEmbeddingWork(reason) {
37
+ return new SenseError('INDEX_NOT_READY', `${reason}; run \`sense build\` (or library build(config)) again to prepare the current index generation`);
38
+ }
39
+ // expectedSignature is captured by open() before provider work. Direct internal callers omit it
40
+ // and capture the durable value here, before selecting chunks or constructing a provider.
41
+ export async function embedPending(store, cfg, allowed, expectedSignature, options = {}) {
32
42
  var _takeChunkText;
33
- const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table
34
- const dirty = (await store.vectors.pending()).filter((row)=>{
43
+ var _options_signal, _options_signal1, _options_signal2, _options_signal3, // Over the exact texts about to be embedded, before any of them are: a mismatch fails
44
+ // this run loudly instead of quietly storing vectors from the wrong model.
45
+ _options_signal4, _options_signal5;
46
+ (_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
47
+ const expectedDurableSignature = expectedSignature === undefined ? await getMeta(store, FEATURE_SIGNATURE_META_KEY) : expectedSignature;
48
+ (_options_signal1 = options.signal) === null || _options_signal1 === void 0 ? void 0 : _options_signal1.throwIfAborted();
49
+ if (!embeddingSignatureCompatible(expectedDurableSignature, featureSignature(cfg, FEATURES))) {
50
+ throw staleEmbeddingWork('the index configuration changed before vectors could be prepared');
51
+ }
52
+ const pendingStmt = await store.prepare('SELECT "path", chunk, content_identity FROM embeddings WHERE vector IS NULL ORDER BY "path", chunk');
53
+ const dirty = (await pendingStmt.all()).filter((row)=>{
35
54
  var _ref;
36
55
  return (_ref = allowed === null || allowed === void 0 ? void 0 : allowed.has(row.path)) !== null && _ref !== void 0 ? _ref : true;
37
56
  });
57
+ (_options_signal2 = options.signal) === null || _options_signal2 === void 0 ? void 0 : _options_signal2.throwIfAborted();
38
58
  if (dirty.length === 0) return;
59
+ const provider = await getProvider(cfg, options);
60
+ (_options_signal3 = options.signal) === null || _options_signal3 === void 0 ? void 0 : _options_signal3.throwIfAborted();
61
+ const submittedSignature = featureSignature(cfg, FEATURES);
62
+ if (!embeddingSignatureCompatible(expectedDurableSignature, submittedSignature)) {
63
+ throw staleEmbeddingWork('the index configuration changed while the embedding provider was being prepared');
64
+ }
39
65
  const storeDims = Math.min(STORE_DIMS, provider.dims);
40
66
  const byPath = new Map();
41
67
  for (const row of dirty){
42
68
  var _byPath_get;
43
69
  const list = (_byPath_get = byPath.get(row.path)) !== null && _byPath_get !== void 0 ? _byPath_get : [];
44
- list.push(row.chunk);
70
+ list.push(row);
45
71
  byPath.set(row.path, list);
46
72
  }
47
73
  const textByPath = (_takeChunkText = takeChunkText(store)) !== null && _takeChunkText !== void 0 ? _takeChunkText : new Map();
48
74
  const sourceStmt = await store.prepare('SELECT text FROM indexed_sources WHERE "path" = ?');
49
75
  const jobs = [];
50
- for (const [path, chunkIdxs] of byPath){
76
+ for (const [path, pending] of byPath){
77
+ var _options_signal6;
51
78
  const texts = textByPath.get(path);
52
- if (texts && chunkIdxs.every((idx)=>idx < texts.length)) {
53
- for (const idx of chunkIdxs)jobs.push({
79
+ if (texts && pending.every((row)=>{
80
+ var _texts_row_chunk;
81
+ return ((_texts_row_chunk = texts[row.chunk]) === null || _texts_row_chunk === void 0 ? void 0 : _texts_row_chunk.identity) === row.content_identity;
82
+ })) {
83
+ for (const row of pending)jobs.push({
54
84
  path,
55
- chunk: idx,
56
- text: texts[idx]
85
+ chunk: row.chunk,
86
+ ...texts[row.chunk]
57
87
  });
58
88
  continue;
59
89
  }
60
90
  // A prior process may have reconciled the row, so its in-memory handoff is gone. Re-derive
61
91
  // from that indexed generation's stored source, never from a newer live file.
62
92
  const source = await sourceStmt.get(path);
93
+ (_options_signal6 = options.signal) === null || _options_signal6 === void 0 ? void 0 : _options_signal6.throwIfAborted();
63
94
  if (!source) throw new SenseError('INDEX_NOT_READY', `indexed source text is missing for ${path}; run \`sense build --force\` (or library build(config, { force: true })) before preparing vectors`);
64
95
  const chunks = parseSource({
65
96
  relPath: path,
@@ -72,26 +103,33 @@ export async function embedPending(store, cfg, allowed) {
72
103
  }, source.text, [
73
104
  embed
74
105
  ], cfg).doc.extracted.embed;
75
- for (const idx of chunkIdxs){
76
- const chunk = chunks[idx];
106
+ for (const row of pending){
107
+ const chunk = chunks[row.chunk];
77
108
  if (!chunk) throw new SenseError('INDEX_NOT_READY', `indexed vector metadata for ${path} does not match its indexed source; run \`sense build --force\` (or library build(config, { force: true }))`);
109
+ const identified = identifyChunkText(chunk.text);
110
+ if (identified.identity !== row.content_identity) throw staleEmbeddingWork(`indexed chunk ${path}#${row.chunk} changed before vectors could be prepared`);
78
111
  jobs.push({
79
112
  path,
80
- chunk: idx,
81
- text: chunk.text
113
+ chunk: row.chunk,
114
+ ...identified
82
115
  });
83
116
  }
84
117
  }
85
- // Over the exact texts about to be embedded, before any of them are: a mismatch fails
86
- // this run loudly instead of quietly storing vectors from the wrong model.
118
+ (_options_signal4 = options.signal) === null || _options_signal4 === void 0 ? void 0 : _options_signal4.throwIfAborted();
87
119
  await checkLanguageFit(store, provider, jobs.map((j)=>j.text));
120
+ (_options_signal5 = options.signal) === null || _options_signal5 === void 0 ? void 0 : _options_signal5.throwIfAborted();
88
121
  // Preparing a large configured scope can take long enough to look stalled, so report each
89
122
  // completed provider batch.
90
123
  const report = progress('embedding chunks', jobs.length);
91
124
  for(let i = 0; i < jobs.length; i += provider.batchCap){
125
+ var _options_signal7, _options_signal8, _options_signal9;
92
126
  const batch = jobs.slice(i, i + provider.batchCap);
93
- const vectors = await provider.embedDocuments(batch.map((j)=>j.text));
94
- await store.vectors.writeVectors(batch.map((job, j)=>{
127
+ (_options_signal7 = options.signal) === null || _options_signal7 === void 0 ? void 0 : _options_signal7.throwIfAborted();
128
+ if (featureSignature(cfg, FEATURES) !== submittedSignature) throw staleEmbeddingWork('the embedding configuration changed before a provider batch was submitted');
129
+ const vectors = await provider.embedDocuments(batch.map((j)=>j.text), options);
130
+ (_options_signal8 = options.signal) === null || _options_signal8 === void 0 ? void 0 : _options_signal8.throwIfAborted();
131
+ if (featureSignature(cfg, FEATURES) !== submittedSignature) throw staleEmbeddingWork('the embedding configuration changed while a provider batch was running');
132
+ const writes = batch.map((job, j)=>{
95
133
  const { v, scale } = toStore(vectors[j], storeDims, true);
96
134
  const q = new Int8Array(storeDims);
97
135
  for(let d = 0; d < storeDims; d++)q[d] = Math.round(v[d] / scale);
@@ -101,7 +139,28 @@ export async function embedPending(store, cfg, allowed) {
101
139
  scale,
102
140
  vector: Buffer.from(q.buffer)
103
141
  };
104
- }));
142
+ });
143
+ (_options_signal9 = options.signal) === null || _options_signal9 === void 0 ? void 0 : _options_signal9.throwIfAborted();
144
+ await store.transaction(async ()=>{
145
+ var // Write first so SQLite owns the write lock before the fence reads. Any mismatch throws
146
+ // inside this transaction and rolls every row in the provider batch back together.
147
+ _options_signal, _options_signal1, _options_signal2;
148
+ (_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
149
+ await store.vectors.writeVectors(writes);
150
+ (_options_signal1 = options.signal) === null || _options_signal1 === void 0 ? void 0 : _options_signal1.throwIfAborted();
151
+ if (await getMeta(store, FEATURE_SIGNATURE_META_KEY) !== expectedDurableSignature) throw staleEmbeddingWork('the durable embedding configuration changed while a provider batch was running');
152
+ const currentStmt = await store.prepare(`SELECT "path", chunk, content_identity FROM embeddings WHERE ${batch.map(()=>'("path" = ? AND chunk = ?)').join(' OR ')}`);
153
+ const current = await currentStmt.all(...batch.flatMap((job)=>[
154
+ job.path,
155
+ job.chunk
156
+ ]));
157
+ const identities = new Map(current.map((row)=>[
158
+ `${row.path}\0${Number(row.chunk)}`,
159
+ row.content_identity
160
+ ]));
161
+ if (batch.some((job)=>identities.get(`${job.path}\0${job.chunk}`) !== job.identity)) throw staleEmbeddingWork('indexed chunks changed while a provider batch was running');
162
+ (_options_signal2 = options.signal) === null || _options_signal2 === void 0 ? void 0 : _options_signal2.throwIfAborted();
163
+ });
105
164
  report.tick(Math.min(i + provider.batchCap, jobs.length));
106
165
  }
107
166
  report.finish();
@@ -112,22 +171,35 @@ export async function ensureDocumentEmbeddings(store, _cfg, allowed) {
112
171
  throw new SenseError('INDEX_NOT_READY', `${pending.length} indexed chunk(s) in the requested scope still need vectors; run \`sense build\` (or library build(config)) before querying with build disabled`);
113
172
  }
114
173
  }
115
- // Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as
116
- // lexical syntax. Similarity comes back because the fused score cannot express match quality.
117
- export async function semanticCandidates(store, cfg, terms, fetch, allowed) {
174
+ // Provider/model work is independent of the index snapshot. FTS5 operators are stripped as
175
+ // lexical syntax before the query is embedded, matching the prior semantic query behavior.
176
+ export async function prepareSemanticQuery(cfg, terms, options = {}) {
118
177
  var _cfg_rootDir, _terms_match;
178
+ var _options_signal, _options_signal1, _options_signal2;
119
179
  const rootDir = (_cfg_rootDir = cfg.rootDir) !== null && _cfg_rootDir !== void 0 ? _cfg_rootDir : cfg.baseDir;
120
180
  if (!rootDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a resolved config (use loadConfig/open)');
121
- const provider = await getProvider(cfg);
122
- const storeDims = Math.min(STORE_DIMS, provider.dims);
123
181
  const text = ((_terms_match = terms.match(/[\p{L}\p{N}]+/gu)) !== null && _terms_match !== void 0 ? _terms_match : []).filter((t)=>![
124
182
  'AND',
125
183
  'OR',
126
184
  'NOT',
127
185
  'NEAR'
128
186
  ].includes(t)).join(' ');
129
- const { v: qv } = toStore(await provider.embedQuery(text), storeDims, false);
130
- return store.vectors.candidates(qv, storeDims, fetch, allowed);
187
+ (_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
188
+ const provider = await getProvider(cfg, options);
189
+ (_options_signal1 = options.signal) === null || _options_signal1 === void 0 ? void 0 : _options_signal1.throwIfAborted();
190
+ const storeDims = Math.min(STORE_DIMS, provider.dims);
191
+ const queryVector = await provider.embedQuery(text, options);
192
+ (_options_signal2 = options.signal) === null || _options_signal2 === void 0 ? void 0 : _options_signal2.throwIfAborted();
193
+ const { v: qv } = toStore(queryVector, storeDims, false);
194
+ return {
195
+ vector: qv,
196
+ storeDims
197
+ };
198
+ }
199
+ // Best chunk per file by cosine, its line range riding along. The caller owns the read snapshot
200
+ // and the prepared vector, so this performs no provider work and only delegates to the store.
201
+ export function semanticCandidates(store, prepared, fetch, allowed) {
202
+ return store.vectors.candidates(prepared.vector, prepared.storeDims, fetch, allowed);
131
203
  }
132
204
  // Whether a note has any chunk with a vector. Distinguishes "nothing is near this note" from
133
205
  // "this note has no text", which look the same in an empty result.
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/query.ts"],"sourcesContent":["import type { Config, ResolvedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Chunk } from '../features/embed.ts';\nimport { embed } from '../features/embed.ts';\nimport { progress } from '../output/progress.ts';\nimport { parseSource } from '../scan/index.ts';\nimport type { Store, VectorCandidate, VectorSimilar } from '../store/types.ts';\nimport { takeChunkText } from './handoff.ts';\nimport { checkLanguageFit } from './langfit.ts';\nimport { getProvider } from './registry.ts';\nimport { STORE_DIMS } from './types.ts';\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage. Exported for\n// benchmark/lib/embed.mjs, which scores the same lever math offline.\nexport function toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL. Reconcile hands its chunk text over in memory when it ran in\n// this process (handoff.ts); a row left pending by an earlier command re-derives from the exact\n// source string persisted with that indexed generation.\nexport async function embedPending(store: Store, cfg: Config, allowed?: ReadonlySet<string>): Promise<void> {\n const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table\n const dirty = (await store.vectors.pending()).filter((row) => allowed?.has(row.path) ?? true);\n if (dirty.length === 0) return;\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, number[]>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row.chunk);\n byPath.set(row.path, list);\n }\n\n const textByPath = takeChunkText(store) ?? new Map<string, string[]>();\n const sourceStmt = await store.prepare('SELECT text FROM indexed_sources WHERE \"path\" = ?');\n\n const jobs: Array<{ path: string; chunk: number; text: string }> = [];\n for (const [path, chunkIdxs] of byPath) {\n const texts = textByPath.get(path);\n if (texts && chunkIdxs.every((idx) => idx < texts.length)) {\n for (const idx of chunkIdxs) jobs.push({ path, chunk: idx, text: texts[idx] });\n continue;\n }\n // A prior process may have reconciled the row, so its in-memory handoff is gone. Re-derive\n // from that indexed generation's stored source, never from a newer live file.\n const source = (await sourceStmt.get(path)) as { text: string } | undefined;\n if (!source) throw new SenseError('INDEX_NOT_READY', `indexed source text is missing for ${path}; run \\`sense build --force\\` (or library build(config, { force: true })) before preparing vectors`);\n const chunks = parseSource({ relPath: path, absPath: path, mtimeMs: 0, ctimeMs: 0, size: Buffer.byteLength(source.text), presets: [], embed: true }, source.text, [embed], cfg).doc.extracted.embed as Chunk[];\n for (const idx of chunkIdxs) {\n const chunk = chunks[idx];\n if (!chunk) throw new SenseError('INDEX_NOT_READY', `indexed vector metadata for ${path} does not match its indexed source; run \\`sense build --force\\` (or library build(config, { force: true }))`);\n jobs.push({ path, chunk: idx, text: chunk.text });\n }\n }\n\n // Over the exact texts about to be embedded, before any of them are: a mismatch fails\n // this run loudly instead of quietly storing vectors from the wrong model.\n await checkLanguageFit(\n store,\n provider,\n jobs.map((j) => j.text)\n );\n\n // Preparing a large configured scope can take long enough to look stalled, so report each\n // completed provider batch.\n const report = progress('embedding chunks', jobs.length);\n for (let i = 0; i < jobs.length; i += provider.batchCap) {\n const batch = jobs.slice(i, i + provider.batchCap);\n const vectors = await provider.embedDocuments(batch.map((j) => j.text));\n await store.vectors.writeVectors(\n batch.map((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n return { path: job.path, chunk: job.chunk, scale, vector: Buffer.from(q.buffer) };\n })\n );\n report.tick(Math.min(i + provider.batchCap, jobs.length));\n }\n report.finish();\n}\n\nexport async function ensureDocumentEmbeddings(store: Store, _cfg: Config, allowed: ReadonlySet<string>): Promise<void> {\n const pending = (await store.vectors.pending()).filter((row) => allowed.has(row.path));\n if (pending.length > 0) {\n throw new SenseError('INDEX_NOT_READY', `${pending.length} indexed chunk(s) in the requested scope still need vectors; run \\`sense build\\` (or library build(config)) before querying with build disabled`);\n }\n}\n\n// Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as\n// lexical syntax. Similarity comes back because the fused score cannot express match quality.\nexport async function semanticCandidates(store: Store, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]> {\n const rootDir = (cfg as Partial<ResolvedConfig>).rootDir ?? (cfg as Partial<ResolvedConfig>).baseDir;\n if (!rootDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a resolved config (use loadConfig/open)');\n const provider = await getProvider(cfg);\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n const { v: qv } = toStore(await provider.embedQuery(text), storeDims, false);\n\n return store.vectors.candidates(qv, storeDims, fetch, allowed);\n}\n\n// Whether a note has any chunk with a vector. Distinguishes \"nothing is near this note\" from\n// \"this note has no text\", which look the same in an empty result.\nexport async function hasEmbedding(store: Store, path: string): Promise<boolean> {\n return store.vectors.hasVector(path);\n}\n\n// Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear\n// scan of stored vectors.\nexport async function similarNotes(store: Store, _cfg: Config, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Promise<VectorSimilar[]> {\n return store.vectors.similar(path, opts);\n}\n"],"names":["SenseError","embed","progress","parseSource","takeChunkText","checkLanguageFit","getProvider","STORE_DIMS","toStore","full","dims","int8","v","Float32Array","norm","d","Math","sqrt","scale","max","abs","embedPending","store","cfg","allowed","provider","dirty","vectors","pending","filter","row","has","path","length","storeDims","min","byPath","Map","list","get","push","chunk","set","textByPath","sourceStmt","prepare","jobs","chunkIdxs","texts","every","idx","text","source","chunks","relPath","absPath","mtimeMs","ctimeMs","size","Buffer","byteLength","presets","doc","extracted","map","j","report","i","batchCap","batch","slice","embedDocuments","writeVectors","job","q","Int8Array","round","vector","from","buffer","tick","finish","ensureDocumentEmbeddings","_cfg","semanticCandidates","terms","fetch","rootDir","baseDir","match","t","includes","join","qv","embedQuery","candidates","hasEmbedding","hasVector","similarNotes","opts","similar"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/query.ts"],"sourcesContent":["import { type Config, featureSignature, type ResolvedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Chunk } from '../features/embed.ts';\nimport { embed } from '../features/embed.ts';\nimport { FEATURES } from '../features/index.ts';\nimport { progress } from '../output/progress.ts';\nimport { parseSource } from '../scan/index.ts';\nimport { FEATURE_SIGNATURE_META_KEY } from '../store/readiness.ts';\nimport { getMeta } from '../store/shared.ts';\nimport { embeddingSignatureCompatible } from '../store/signature.ts';\nimport type { Store, VectorCandidate, VectorSimilar } from '../store/types.ts';\nimport { identifyChunkText, takeChunkText } from './handoff.ts';\nimport { checkLanguageFit } from './langfit.ts';\nimport { getProvider } from './registry.ts';\nimport { type EmbedCallOptions, STORE_DIMS } from './types.ts';\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage. Exported for\n// benchmark/lib/embed.mjs, which scores the same lever math offline.\nexport function toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL. Reconcile hands its chunk text over in memory when it ran in\n// this process (handoff.ts); a row left pending by an earlier command re-derives from the exact\n// source string persisted with that indexed generation.\nfunction staleEmbeddingWork(reason: string): SenseError {\n return new SenseError('INDEX_NOT_READY', `${reason}; run \\`sense build\\` (or library build(config)) again to prepare the current index generation`);\n}\n\n// expectedSignature is captured by open() before provider work. Direct internal callers omit it\n// and capture the durable value here, before selecting chunks or constructing a provider.\nexport async function embedPending(store: Store, cfg: Config, allowed?: ReadonlySet<string>, expectedSignature?: string | null, options: EmbedCallOptions = {}): Promise<void> {\n options.signal?.throwIfAborted();\n const expectedDurableSignature = expectedSignature === undefined ? await getMeta(store, FEATURE_SIGNATURE_META_KEY) : expectedSignature;\n options.signal?.throwIfAborted();\n if (!embeddingSignatureCompatible(expectedDurableSignature, featureSignature(cfg, FEATURES))) {\n throw staleEmbeddingWork('the index configuration changed before vectors could be prepared');\n }\n\n const pendingStmt = await store.prepare('SELECT \"path\", chunk, content_identity FROM embeddings WHERE vector IS NULL ORDER BY \"path\", chunk');\n const dirty = ((await pendingStmt.all()) as Array<{ path: string; chunk: number; content_identity: string }>).filter((row) => allowed?.has(row.path) ?? true);\n options.signal?.throwIfAborted();\n if (dirty.length === 0) return;\n\n const provider = await getProvider(cfg, options);\n options.signal?.throwIfAborted();\n const submittedSignature = featureSignature(cfg, FEATURES);\n if (!embeddingSignatureCompatible(expectedDurableSignature, submittedSignature)) {\n throw staleEmbeddingWork('the index configuration changed while the embedding provider was being prepared');\n }\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, typeof dirty>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row);\n byPath.set(row.path, list);\n }\n\n const textByPath = takeChunkText(store) ?? new Map();\n const sourceStmt = await store.prepare('SELECT text FROM indexed_sources WHERE \"path\" = ?');\n\n const jobs: Array<{ path: string; chunk: number; text: string; identity: string }> = [];\n for (const [path, pending] of byPath) {\n const texts = textByPath.get(path);\n if (texts && pending.every((row) => texts[row.chunk]?.identity === row.content_identity)) {\n for (const row of pending) jobs.push({ path, chunk: row.chunk, ...texts[row.chunk] });\n continue;\n }\n // A prior process may have reconciled the row, so its in-memory handoff is gone. Re-derive\n // from that indexed generation's stored source, never from a newer live file.\n const source = (await sourceStmt.get(path)) as { text: string } | undefined;\n options.signal?.throwIfAborted();\n if (!source) throw new SenseError('INDEX_NOT_READY', `indexed source text is missing for ${path}; run \\`sense build --force\\` (or library build(config, { force: true })) before preparing vectors`);\n const chunks = parseSource({ relPath: path, absPath: path, mtimeMs: 0, ctimeMs: 0, size: Buffer.byteLength(source.text), presets: [], embed: true }, source.text, [embed], cfg).doc.extracted.embed as Chunk[];\n for (const row of pending) {\n const chunk = chunks[row.chunk];\n if (!chunk) throw new SenseError('INDEX_NOT_READY', `indexed vector metadata for ${path} does not match its indexed source; run \\`sense build --force\\` (or library build(config, { force: true }))`);\n const identified = identifyChunkText(chunk.text);\n if (identified.identity !== row.content_identity) throw staleEmbeddingWork(`indexed chunk ${path}#${row.chunk} changed before vectors could be prepared`);\n jobs.push({ path, chunk: row.chunk, ...identified });\n }\n }\n\n // Over the exact texts about to be embedded, before any of them are: a mismatch fails\n // this run loudly instead of quietly storing vectors from the wrong model.\n options.signal?.throwIfAborted();\n await checkLanguageFit(\n store,\n provider,\n jobs.map((j) => j.text)\n );\n options.signal?.throwIfAborted();\n\n // Preparing a large configured scope can take long enough to look stalled, so report each\n // completed provider batch.\n const report = progress('embedding chunks', jobs.length);\n for (let i = 0; i < jobs.length; i += provider.batchCap) {\n const batch = jobs.slice(i, i + provider.batchCap);\n options.signal?.throwIfAborted();\n if (featureSignature(cfg, FEATURES) !== submittedSignature) throw staleEmbeddingWork('the embedding configuration changed before a provider batch was submitted');\n const vectors = await provider.embedDocuments(\n batch.map((j) => j.text),\n options\n );\n options.signal?.throwIfAborted();\n if (featureSignature(cfg, FEATURES) !== submittedSignature) throw staleEmbeddingWork('the embedding configuration changed while a provider batch was running');\n const writes = batch.map((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n return { path: job.path, chunk: job.chunk, scale, vector: Buffer.from(q.buffer) };\n });\n options.signal?.throwIfAborted();\n await store.transaction(async () => {\n // Write first so SQLite owns the write lock before the fence reads. Any mismatch throws\n // inside this transaction and rolls every row in the provider batch back together.\n options.signal?.throwIfAborted();\n await store.vectors.writeVectors(writes);\n options.signal?.throwIfAborted();\n if ((await getMeta(store, FEATURE_SIGNATURE_META_KEY)) !== expectedDurableSignature) throw staleEmbeddingWork('the durable embedding configuration changed while a provider batch was running');\n const currentStmt = await store.prepare(`SELECT \"path\", chunk, content_identity FROM embeddings WHERE ${batch.map(() => '(\"path\" = ? AND chunk = ?)').join(' OR ')}`);\n const current = (await currentStmt.all(...batch.flatMap((job) => [job.path, job.chunk]))) as Array<{ path: string; chunk: number; content_identity: string }>;\n const identities = new Map(current.map((row) => [`${row.path}\\0${Number(row.chunk)}`, row.content_identity]));\n if (batch.some((job) => identities.get(`${job.path}\\0${job.chunk}`) !== job.identity)) throw staleEmbeddingWork('indexed chunks changed while a provider batch was running');\n options.signal?.throwIfAborted();\n });\n report.tick(Math.min(i + provider.batchCap, jobs.length));\n }\n report.finish();\n}\n\nexport async function ensureDocumentEmbeddings(store: Store, _cfg: Config, allowed: ReadonlySet<string>): Promise<void> {\n const pending = (await store.vectors.pending()).filter((row) => allowed.has(row.path));\n if (pending.length > 0) {\n throw new SenseError('INDEX_NOT_READY', `${pending.length} indexed chunk(s) in the requested scope still need vectors; run \\`sense build\\` (or library build(config)) before querying with build disabled`);\n }\n}\n\nexport interface PreparedSemanticQuery {\n readonly vector: Float32Array;\n readonly storeDims: number;\n}\n\n// Provider/model work is independent of the index snapshot. FTS5 operators are stripped as\n// lexical syntax before the query is embedded, matching the prior semantic query behavior.\nexport async function prepareSemanticQuery(cfg: Config, terms: string, options: EmbedCallOptions = {}): Promise<PreparedSemanticQuery> {\n const rootDir = (cfg as Partial<ResolvedConfig>).rootDir ?? (cfg as Partial<ResolvedConfig>).baseDir;\n if (!rootDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a resolved config (use loadConfig/open)');\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n options.signal?.throwIfAborted();\n const provider = await getProvider(cfg, options);\n options.signal?.throwIfAborted();\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const queryVector = await provider.embedQuery(text, options);\n options.signal?.throwIfAborted();\n const { v: qv } = toStore(queryVector, storeDims, false);\n return { vector: qv, storeDims };\n}\n\n// Best chunk per file by cosine, its line range riding along. The caller owns the read snapshot\n// and the prepared vector, so this performs no provider work and only delegates to the store.\nexport function semanticCandidates(store: Store, prepared: PreparedSemanticQuery, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]> {\n return store.vectors.candidates(prepared.vector, prepared.storeDims, fetch, allowed);\n}\n\n// Whether a note has any chunk with a vector. Distinguishes \"nothing is near this note\" from\n// \"this note has no text\", which look the same in an empty result.\nexport async function hasEmbedding(store: Store, path: string): Promise<boolean> {\n return store.vectors.hasVector(path);\n}\n\n// Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear\n// scan of stored vectors.\nexport async function similarNotes(store: Store, _cfg: Config, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Promise<VectorSimilar[]> {\n return store.vectors.similar(path, opts);\n}\n"],"names":["featureSignature","SenseError","embed","FEATURES","progress","parseSource","FEATURE_SIGNATURE_META_KEY","getMeta","embeddingSignatureCompatible","identifyChunkText","takeChunkText","checkLanguageFit","getProvider","STORE_DIMS","toStore","full","dims","int8","v","Float32Array","norm","d","Math","sqrt","scale","max","abs","staleEmbeddingWork","reason","embedPending","store","cfg","allowed","expectedSignature","options","signal","throwIfAborted","expectedDurableSignature","undefined","pendingStmt","prepare","dirty","all","filter","row","has","path","length","provider","submittedSignature","storeDims","min","byPath","Map","list","get","push","set","textByPath","sourceStmt","jobs","pending","texts","every","chunk","identity","content_identity","source","chunks","relPath","absPath","mtimeMs","ctimeMs","size","Buffer","byteLength","text","presets","doc","extracted","identified","map","j","report","i","batchCap","batch","slice","vectors","embedDocuments","writes","job","q","Int8Array","round","vector","from","buffer","transaction","writeVectors","currentStmt","join","current","flatMap","identities","Number","some","tick","finish","ensureDocumentEmbeddings","_cfg","prepareSemanticQuery","terms","rootDir","baseDir","match","t","includes","queryVector","embedQuery","qv","semanticCandidates","prepared","fetch","candidates","hasEmbedding","hasVector","similarNotes","opts","similar"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  import type { Config } from '../config/index.js';
2
- import type { EmbedProvider } from './types.js';
3
- export declare function getProvider(cfg: Config): Promise<EmbedProvider>;
2
+ import type { EmbedCallOptions, EmbedProvider } from './types.js';
3
+ export declare function getProvider(cfg: Config, options?: EmbedCallOptions): Promise<EmbedProvider>;
@@ -1,6 +1,7 @@
1
1
  import { embedConfig } from '../config/index.js';
2
2
  import { SenseError } from '../errors.js';
3
3
  import { cohereProvider } from './cohere.js';
4
+ import { effectiveEmbedEndpoint } from './endpoint.js';
4
5
  import { openaiProvider } from './openai.js';
5
6
  import { staticProvider } from './static.js';
6
7
  // One factory per wire protocol: adding a protocol is a new file plus an entry here, not
@@ -11,24 +12,52 @@ const REGISTRY = {
11
12
  cohere: (model, url, key)=>cohereProvider(model, url, key)
12
13
  };
13
14
  const providers = new Map();
14
- export function getProvider(cfg) {
15
- var _e_url, _e_key, _e_languages;
15
+ function aborted(reason) {
16
+ return Promise.reject(reason);
17
+ }
18
+ function waitForProvider(provider, signal) {
19
+ if (signal.aborted) return aborted(signal.reason);
20
+ return new Promise((resolve, reject)=>{
21
+ const cleanup = ()=>signal.removeEventListener('abort', onAbort);
22
+ const onAbort = ()=>{
23
+ cleanup();
24
+ reject(signal.reason);
25
+ };
26
+ signal.addEventListener('abort', onAbort, {
27
+ once: true
28
+ });
29
+ provider.then((value)=>{
30
+ cleanup();
31
+ resolve(value);
32
+ }, (err)=>{
33
+ cleanup();
34
+ reject(err);
35
+ });
36
+ if (signal.aborted) onAbort();
37
+ });
38
+ }
39
+ export function getProvider(cfg, options = {}) {
40
+ var _e_key, _e_languages;
41
+ var _options_signal;
42
+ // A caller that has already left must not start shared model initialization.
43
+ if ((_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.aborted) return aborted(options.signal.reason);
16
44
  const e = embedConfig(cfg);
17
45
  if (!e) throw new SenseError('EMBED_DISABLED', 'this tree has no embedding model: add an "embed" block naming one to sense.config.json; a Hugging Face id fetches automatically at first use, or run `sense download` to prefetch it');
18
46
  const factory = REGISTRY[e.provider];
19
47
  if (!factory) throw new SenseError('EMBED_MODEL', `embed.provider "${e.provider}" has no provider implementation in this build`);
48
+ const endpoint = effectiveEmbedEndpoint(e.provider, e.url);
20
49
  // Every input that shapes the provider is in the key: two trees sharing an endpoint and
21
50
  // model but declaring different languages or key env vars are two providers, not one.
22
51
  const sig = JSON.stringify([
23
52
  e.provider,
24
53
  e.model,
25
- (_e_url = e.url) !== null && _e_url !== void 0 ? _e_url : '',
54
+ endpoint !== null && endpoint !== void 0 ? endpoint : '',
26
55
  (_e_key = e.key) !== null && _e_key !== void 0 ? _e_key : '',
27
56
  (_e_languages = e.languages) !== null && _e_languages !== void 0 ? _e_languages : []
28
57
  ]);
29
58
  let p = providers.get(sig);
30
59
  if (!p) {
31
- p = factory(e.model, e.url, e.key);
60
+ p = factory(e.model, endpoint, e.key);
32
61
  // Owner-declared languages override a provider's own: the config is the one home for
33
62
  // what no model card can state (http models have no fetchable card here).
34
63
  if (e.languages) p = p.then((prov)=>({
@@ -40,5 +69,5 @@ export function getProvider(cfg) {
40
69
  // poisoning this config for the process lifetime.
41
70
  p.catch(()=>providers.delete(sig));
42
71
  }
43
- return p;
72
+ return options.signal ? waitForProvider(p, options.signal) : p;
44
73
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/registry.ts"],"sourcesContent":["import type { Config } from '../config/index.ts';\nimport { embedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { cohereProvider } from './cohere.ts';\nimport { openaiProvider } from './openai.ts';\nimport { staticProvider } from './static.ts';\nimport type { EmbedProvider } from './types.ts';\n\ntype ProviderFactory = (model: string, url: string | undefined, key: string | undefined) => Promise<EmbedProvider>;\n\n// One factory per wire protocol: adding a protocol is a new file plus an entry here, not\n// an edit to a dispatch ternary.\nconst REGISTRY: Record<string, ProviderFactory> = {\n static: (model) => staticProvider(model),\n openai: (model, url, key) => openaiProvider(model, url, key),\n cohere: (model, url, key) => cohereProvider(model, url, key),\n};\n\nconst providers = new Map<string, Promise<EmbedProvider>>();\n\nexport function getProvider(cfg: Config): Promise<EmbedProvider> {\n const e = embedConfig(cfg);\n if (!e) throw new SenseError('EMBED_DISABLED', 'this tree has no embedding model: add an \"embed\" block naming one to sense.config.json; a Hugging Face id fetches automatically at first use, or run `sense download` to prefetch it');\n const factory = REGISTRY[e.provider];\n if (!factory) throw new SenseError('EMBED_MODEL', `embed.provider \"${e.provider}\" has no provider implementation in this build`);\n // Every input that shapes the provider is in the key: two trees sharing an endpoint and\n // model but declaring different languages or key env vars are two providers, not one.\n const sig = JSON.stringify([e.provider, e.model, e.url ?? '', e.key ?? '', e.languages ?? []]);\n let p = providers.get(sig);\n if (!p) {\n p = factory(e.model, e.url, e.key);\n // Owner-declared languages override a provider's own: the config is the one home for\n // what no model card can state (http models have no fetchable card here).\n if (e.languages) p = p.then((prov) => ({ ...prov, languages: e.languages }));\n providers.set(sig, p);\n // A rejected construction is evicted, so a transient failure retries instead of\n // poisoning this config for the process lifetime.\n p.catch(() => providers.delete(sig));\n }\n return p;\n}\n"],"names":["embedConfig","SenseError","cohereProvider","openaiProvider","staticProvider","REGISTRY","static","model","openai","url","key","cohere","providers","Map","getProvider","cfg","e","factory","provider","sig","JSON","stringify","languages","p","get","then","prov","set","catch","delete"],"mappings":"AACA,SAASA,WAAW,QAAQ,qBAAqB;AACjD,SAASC,UAAU,QAAQ,eAAe;AAC1C,SAASC,cAAc,QAAQ,cAAc;AAC7C,SAASC,cAAc,QAAQ,cAAc;AAC7C,SAASC,cAAc,QAAQ,cAAc;AAK7C,yFAAyF;AACzF,iCAAiC;AACjC,MAAMC,WAA4C;IAChDC,QAAQ,CAACC,QAAUH,eAAeG;IAClCC,QAAQ,CAACD,OAAOE,KAAKC,MAAQP,eAAeI,OAAOE,KAAKC;IACxDC,QAAQ,CAACJ,OAAOE,KAAKC,MAAQR,eAAeK,OAAOE,KAAKC;AAC1D;AAEA,MAAME,YAAY,IAAIC;AAEtB,OAAO,SAASC,YAAYC,GAAW;QAOYC,QAAaA,QAAaA;IAN3E,MAAMA,IAAIhB,YAAYe;IACtB,IAAI,CAACC,GAAG,MAAM,IAAIf,WAAW,kBAAkB;IAC/C,MAAMgB,UAAUZ,QAAQ,CAACW,EAAEE,QAAQ,CAAC;IACpC,IAAI,CAACD,SAAS,MAAM,IAAIhB,WAAW,eAAe,CAAC,gBAAgB,EAAEe,EAAEE,QAAQ,CAAC,8CAA8C,CAAC;IAC/H,wFAAwF;IACxF,sFAAsF;IACtF,MAAMC,MAAMC,KAAKC,SAAS,CAAC;QAACL,EAAEE,QAAQ;QAAEF,EAAET,KAAK;SAAES,SAAAA,EAAEP,GAAG,cAALO,oBAAAA,SAAS;SAAIA,SAAAA,EAAEN,GAAG,cAALM,oBAAAA,SAAS;SAAIA,eAAAA,EAAEM,SAAS,cAAXN,0BAAAA,eAAe,EAAE;KAAC;IAC7F,IAAIO,IAAIX,UAAUY,GAAG,CAACL;IACtB,IAAI,CAACI,GAAG;QACNA,IAAIN,QAAQD,EAAET,KAAK,EAAES,EAAEP,GAAG,EAAEO,EAAEN,GAAG;QACjC,qFAAqF;QACrF,0EAA0E;QAC1E,IAAIM,EAAEM,SAAS,EAAEC,IAAIA,EAAEE,IAAI,CAAC,CAACC,OAAU,CAAA;gBAAE,GAAGA,IAAI;gBAAEJ,WAAWN,EAAEM,SAAS;YAAC,CAAA;QACzEV,UAAUe,GAAG,CAACR,KAAKI;QACnB,gFAAgF;QAChF,kDAAkD;QAClDA,EAAEK,KAAK,CAAC,IAAMhB,UAAUiB,MAAM,CAACV;IACjC;IACA,OAAOI;AACT"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/registry.ts"],"sourcesContent":["import type { Config } from '../config/index.ts';\nimport { embedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { cohereProvider } from './cohere.ts';\nimport { effectiveEmbedEndpoint } from './endpoint.ts';\nimport { openaiProvider } from './openai.ts';\nimport { staticProvider } from './static.ts';\nimport type { EmbedCallOptions, EmbedProvider } from './types.ts';\n\ntype ProviderFactory = (model: string, url: string | undefined, key: string | undefined) => Promise<EmbedProvider>;\n\n// One factory per wire protocol: adding a protocol is a new file plus an entry here, not\n// an edit to a dispatch ternary.\nconst REGISTRY: Record<string, ProviderFactory> = {\n static: (model) => staticProvider(model),\n openai: (model, url, key) => openaiProvider(model, url, key),\n cohere: (model, url, key) => cohereProvider(model, url, key),\n};\n\nconst providers = new Map<string, Promise<EmbedProvider>>();\n\nfunction aborted(reason: unknown): Promise<never> {\n return Promise.reject(reason);\n}\n\nfunction waitForProvider(provider: Promise<EmbedProvider>, signal: AbortSignal): Promise<EmbedProvider> {\n if (signal.aborted) return aborted(signal.reason);\n return new Promise((resolve, reject) => {\n const cleanup = () => signal.removeEventListener('abort', onAbort);\n const onAbort = () => {\n cleanup();\n reject(signal.reason);\n };\n signal.addEventListener('abort', onAbort, { once: true });\n provider.then(\n (value) => {\n cleanup();\n resolve(value);\n },\n (err: unknown) => {\n cleanup();\n reject(err);\n }\n );\n if (signal.aborted) onAbort();\n });\n}\n\nexport function getProvider(cfg: Config, options: EmbedCallOptions = {}): Promise<EmbedProvider> {\n // A caller that has already left must not start shared model initialization.\n if (options.signal?.aborted) return aborted(options.signal.reason);\n const e = embedConfig(cfg);\n if (!e) throw new SenseError('EMBED_DISABLED', 'this tree has no embedding model: add an \"embed\" block naming one to sense.config.json; a Hugging Face id fetches automatically at first use, or run `sense download` to prefetch it');\n const factory = REGISTRY[e.provider];\n if (!factory) throw new SenseError('EMBED_MODEL', `embed.provider \"${e.provider}\" has no provider implementation in this build`);\n const endpoint = effectiveEmbedEndpoint(e.provider, e.url);\n // Every input that shapes the provider is in the key: two trees sharing an endpoint and\n // model but declaring different languages or key env vars are two providers, not one.\n const sig = JSON.stringify([e.provider, e.model, endpoint ?? '', e.key ?? '', e.languages ?? []]);\n let p = providers.get(sig);\n if (!p) {\n p = factory(e.model, endpoint, e.key);\n // Owner-declared languages override a provider's own: the config is the one home for\n // what no model card can state (http models have no fetchable card here).\n if (e.languages) p = p.then((prov) => ({ ...prov, languages: e.languages }));\n providers.set(sig, p);\n // A rejected construction is evicted, so a transient failure retries instead of\n // poisoning this config for the process lifetime.\n p.catch(() => providers.delete(sig));\n }\n return options.signal ? waitForProvider(p, options.signal) : p;\n}\n"],"names":["embedConfig","SenseError","cohereProvider","effectiveEmbedEndpoint","openaiProvider","staticProvider","REGISTRY","static","model","openai","url","key","cohere","providers","Map","aborted","reason","Promise","reject","waitForProvider","provider","signal","resolve","cleanup","removeEventListener","onAbort","addEventListener","once","then","value","err","getProvider","cfg","options","e","factory","endpoint","sig","JSON","stringify","languages","p","get","prov","set","catch","delete"],"mappings":"AACA,SAASA,WAAW,QAAQ,qBAAqB;AACjD,SAASC,UAAU,QAAQ,eAAe;AAC1C,SAASC,cAAc,QAAQ,cAAc;AAC7C,SAASC,sBAAsB,QAAQ,gBAAgB;AACvD,SAASC,cAAc,QAAQ,cAAc;AAC7C,SAASC,cAAc,QAAQ,cAAc;AAK7C,yFAAyF;AACzF,iCAAiC;AACjC,MAAMC,WAA4C;IAChDC,QAAQ,CAACC,QAAUH,eAAeG;IAClCC,QAAQ,CAACD,OAAOE,KAAKC,MAAQP,eAAeI,OAAOE,KAAKC;IACxDC,QAAQ,CAACJ,OAAOE,KAAKC,MAAQT,eAAeM,OAAOE,KAAKC;AAC1D;AAEA,MAAME,YAAY,IAAIC;AAEtB,SAASC,QAAQC,MAAe;IAC9B,OAAOC,QAAQC,MAAM,CAACF;AACxB;AAEA,SAASG,gBAAgBC,QAAgC,EAAEC,MAAmB;IAC5E,IAAIA,OAAON,OAAO,EAAE,OAAOA,QAAQM,OAAOL,MAAM;IAChD,OAAO,IAAIC,QAAQ,CAACK,SAASJ;QAC3B,MAAMK,UAAU,IAAMF,OAAOG,mBAAmB,CAAC,SAASC;QAC1D,MAAMA,UAAU;YACdF;YACAL,OAAOG,OAAOL,MAAM;QACtB;QACAK,OAAOK,gBAAgB,CAAC,SAASD,SAAS;YAAEE,MAAM;QAAK;QACvDP,SAASQ,IAAI,CACX,CAACC;YACCN;YACAD,QAAQO;QACV,GACA,CAACC;YACCP;YACAL,OAAOY;QACT;QAEF,IAAIT,OAAON,OAAO,EAAEU;IACtB;AACF;AAEA,OAAO,SAASM,YAAYC,GAAW,EAAEC,UAA4B,CAAC,CAAC;QAUJC,QAAaA;QAR1ED;IADJ,6EAA6E;IAC7E,KAAIA,kBAAAA,QAAQZ,MAAM,cAAdY,sCAAAA,gBAAgBlB,OAAO,EAAE,OAAOA,QAAQkB,QAAQZ,MAAM,CAACL,MAAM;IACjE,MAAMkB,IAAIlC,YAAYgC;IACtB,IAAI,CAACE,GAAG,MAAM,IAAIjC,WAAW,kBAAkB;IAC/C,MAAMkC,UAAU7B,QAAQ,CAAC4B,EAAEd,QAAQ,CAAC;IACpC,IAAI,CAACe,SAAS,MAAM,IAAIlC,WAAW,eAAe,CAAC,gBAAgB,EAAEiC,EAAEd,QAAQ,CAAC,8CAA8C,CAAC;IAC/H,MAAMgB,WAAWjC,uBAAuB+B,EAAEd,QAAQ,EAAEc,EAAExB,GAAG;IACzD,wFAAwF;IACxF,sFAAsF;IACtF,MAAM2B,MAAMC,KAAKC,SAAS,CAAC;QAACL,EAAEd,QAAQ;QAAEc,EAAE1B,KAAK;QAAE4B,qBAAAA,sBAAAA,WAAY;SAAIF,SAAAA,EAAEvB,GAAG,cAALuB,oBAAAA,SAAS;SAAIA,eAAAA,EAAEM,SAAS,cAAXN,0BAAAA,eAAe,EAAE;KAAC;IAChG,IAAIO,IAAI5B,UAAU6B,GAAG,CAACL;IACtB,IAAI,CAACI,GAAG;QACNA,IAAIN,QAAQD,EAAE1B,KAAK,EAAE4B,UAAUF,EAAEvB,GAAG;QACpC,qFAAqF;QACrF,0EAA0E;QAC1E,IAAIuB,EAAEM,SAAS,EAAEC,IAAIA,EAAEb,IAAI,CAAC,CAACe,OAAU,CAAA;gBAAE,GAAGA,IAAI;gBAAEH,WAAWN,EAAEM,SAAS;YAAC,CAAA;QACzE3B,UAAU+B,GAAG,CAACP,KAAKI;QACnB,gFAAgF;QAChF,kDAAkD;QAClDA,EAAEI,KAAK,CAAC,IAAMhC,UAAUiC,MAAM,CAACT;IACjC;IACA,OAAOJ,QAAQZ,MAAM,GAAGF,gBAAgBsB,GAAGR,QAAQZ,MAAM,IAAIoB;AAC/D"}
@@ -68,7 +68,15 @@ export async function staticProvider(model, root) {
68
68
  dims,
69
69
  batchCap: BATCH_CAP,
70
70
  languages,
71
- embedDocuments: async (texts)=>texts.map(one),
72
- embedQuery: async (text)=>one(text)
71
+ embedDocuments: async (texts, options)=>{
72
+ var _options_signal;
73
+ options === null || options === void 0 ? void 0 : (_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
74
+ return texts.map(one);
75
+ },
76
+ embedQuery: async (text, options)=>{
77
+ var _options_signal;
78
+ options === null || options === void 0 ? void 0 : (_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.throwIfAborted();
79
+ return one(text);
80
+ }
73
81
  };
74
82
  }