@zvec/zvec-grep 0.1.5 → 0.1.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 (358) hide show
  1. package/README.md +154 -236
  2. package/README_CN.md +142 -233
  3. package/dist/authorization/index.d.ts +7 -0
  4. package/dist/authorization/index.js +7 -0
  5. package/dist/authorization/index.js.map +1 -0
  6. package/dist/authorization/manager.d.ts +8 -0
  7. package/dist/authorization/manager.js +19 -0
  8. package/dist/authorization/manager.js.map +1 -0
  9. package/dist/authorization/operation.d.ts +7 -0
  10. package/dist/authorization/operation.js +50 -0
  11. package/dist/authorization/operation.js.map +1 -0
  12. package/dist/authorization/planner.d.ts +21 -0
  13. package/dist/authorization/planner.js +97 -0
  14. package/dist/authorization/planner.js.map +1 -0
  15. package/dist/authorization/prompt.d.ts +11 -0
  16. package/dist/authorization/prompt.js +62 -0
  17. package/dist/authorization/prompt.js.map +1 -0
  18. package/dist/authorization/store.d.ts +20 -0
  19. package/dist/authorization/store.js +251 -0
  20. package/dist/authorization/store.js.map +1 -0
  21. package/dist/authorization/target.d.ts +15 -0
  22. package/dist/authorization/target.js +49 -0
  23. package/dist/authorization/target.js.map +1 -0
  24. package/dist/authorization/types.d.ts +61 -0
  25. package/dist/authorization/types.js +2 -0
  26. package/dist/authorization/types.js.map +1 -0
  27. package/dist/cli/args.d.ts +1 -1
  28. package/dist/cli/args.js +338 -55
  29. package/dist/cli/args.js.map +1 -1
  30. package/dist/cli/auth.d.ts +21 -0
  31. package/dist/cli/auth.js +209 -0
  32. package/dist/cli/auth.js.map +1 -0
  33. package/dist/cli/commands.js +469 -625
  34. package/dist/cli/commands.js.map +1 -1
  35. package/dist/cli/errors.js +2 -2
  36. package/dist/cli/errors.js.map +1 -1
  37. package/dist/cli/format/context.d.ts +4 -0
  38. package/dist/cli/format/context.js +311 -44
  39. package/dist/cli/format/context.js.map +1 -1
  40. package/dist/cli/format/debug.js +12 -6
  41. package/dist/cli/format/debug.js.map +1 -1
  42. package/dist/cli/format/progress.d.ts +4 -0
  43. package/dist/cli/format/progress.js +219 -46
  44. package/dist/cli/format/progress.js.map +1 -1
  45. package/dist/cli/format/status.d.ts +79 -6
  46. package/dist/cli/format/status.js +341 -111
  47. package/dist/cli/format/status.js.map +1 -1
  48. package/dist/cli/help.js +395 -63
  49. package/dist/cli/help.js.map +1 -1
  50. package/dist/cli/index.d.ts +1 -1
  51. package/dist/cli/index.js +1 -1
  52. package/dist/cli/install.d.ts +4 -0
  53. package/dist/cli/install.js +963 -0
  54. package/dist/cli/install.js.map +1 -0
  55. package/dist/cli/managed-rg.d.ts +10 -0
  56. package/dist/cli/managed-rg.js +329 -0
  57. package/dist/cli/managed-rg.js.map +1 -0
  58. package/dist/cli/server-search.d.ts +3 -0
  59. package/dist/cli/server-search.js +12 -0
  60. package/dist/cli/server-search.js.map +1 -0
  61. package/dist/cli/types.d.ts +23 -6
  62. package/dist/cli/types.js.map +1 -1
  63. package/dist/client/daemon-client.d.ts +31 -0
  64. package/dist/client/daemon-client.js +203 -0
  65. package/dist/client/daemon-client.js.map +1 -0
  66. package/dist/client/mode-router.d.ts +9 -0
  67. package/dist/client/mode-router.js +28 -0
  68. package/dist/client/mode-router.js.map +1 -0
  69. package/dist/client/search-policy.d.ts +11 -0
  70. package/dist/client/search-policy.js +15 -0
  71. package/dist/client/search-policy.js.map +1 -0
  72. package/dist/daemon/backend.d.ts +77 -0
  73. package/dist/daemon/backend.js +1087 -0
  74. package/dist/daemon/backend.js.map +1 -0
  75. package/dist/daemon/change-set.d.ts +25 -0
  76. package/dist/daemon/change-set.js +101 -0
  77. package/dist/daemon/change-set.js.map +1 -0
  78. package/dist/daemon/config.d.ts +24 -0
  79. package/dist/daemon/config.js +88 -0
  80. package/dist/daemon/config.js.map +1 -0
  81. package/dist/daemon/errors.d.ts +5 -0
  82. package/dist/daemon/errors.js +11 -0
  83. package/dist/daemon/errors.js.map +1 -0
  84. package/dist/daemon/http-server.d.ts +30 -0
  85. package/dist/daemon/http-server.js +288 -0
  86. package/dist/daemon/http-server.js.map +1 -0
  87. package/dist/daemon/index-coordinator.d.ts +24 -0
  88. package/dist/daemon/index-coordinator.js +68 -0
  89. package/dist/daemon/index-coordinator.js.map +1 -0
  90. package/dist/daemon/job-scheduler.d.ts +70 -0
  91. package/dist/daemon/job-scheduler.js +366 -0
  92. package/dist/daemon/job-scheduler.js.map +1 -0
  93. package/dist/daemon/logger.d.ts +9 -0
  94. package/dist/daemon/logger.js +53 -0
  95. package/dist/daemon/logger.js.map +1 -0
  96. package/dist/daemon/model-pool.d.ts +45 -0
  97. package/dist/daemon/model-pool.js +187 -0
  98. package/dist/daemon/model-pool.js.map +1 -0
  99. package/dist/daemon/root-lease.d.ts +14 -0
  100. package/dist/daemon/root-lease.js +188 -0
  101. package/dist/daemon/root-lease.js.map +1 -0
  102. package/dist/daemon/root-runtime.d.ts +78 -0
  103. package/dist/daemon/root-runtime.js +333 -0
  104. package/dist/daemon/root-runtime.js.map +1 -0
  105. package/dist/daemon/runtime-manager.d.ts +46 -0
  106. package/dist/daemon/runtime-manager.js +246 -0
  107. package/dist/daemon/runtime-manager.js.map +1 -0
  108. package/dist/daemon/runtime.d.ts +12 -0
  109. package/dist/daemon/runtime.js +102 -0
  110. package/dist/daemon/runtime.js.map +1 -0
  111. package/dist/daemon/server-controller.d.ts +39 -0
  112. package/dist/daemon/server-controller.js +254 -0
  113. package/dist/daemon/server-controller.js.map +1 -0
  114. package/dist/daemon/watch-manager.d.ts +50 -0
  115. package/dist/daemon/watch-manager.js +293 -0
  116. package/dist/daemon/watch-manager.js.map +1 -0
  117. package/dist/daemon/workspace-read-session-cache.d.ts +31 -0
  118. package/dist/daemon/workspace-read-session-cache.js +115 -0
  119. package/dist/daemon/workspace-read-session-cache.js.map +1 -0
  120. package/dist/engine/code-formats.d.ts +3 -0
  121. package/dist/engine/code-formats.js +14 -0
  122. package/dist/engine/code-formats.js.map +1 -0
  123. package/dist/engine/config.d.ts +30 -8
  124. package/dist/engine/config.js +171 -45
  125. package/dist/engine/config.js.map +1 -1
  126. package/dist/engine/{errors/index.d.ts → errors.d.ts} +6 -1
  127. package/dist/engine/{errors/details.js → errors.js} +17 -4
  128. package/dist/engine/errors.js.map +1 -0
  129. package/dist/engine/extraction/code/adapter.d.ts +1 -1
  130. package/dist/engine/extraction/code/adapter.js +1 -1
  131. package/dist/engine/extraction/code/adapter.js.map +1 -1
  132. package/dist/engine/extraction/code/extractor.d.ts +5 -11
  133. package/dist/engine/extraction/code/extractor.js +116 -48
  134. package/dist/engine/extraction/code/extractor.js.map +1 -1
  135. package/dist/engine/extraction/code/families/c-family.js +1 -1
  136. package/dist/engine/extraction/code/families/c-family.js.map +1 -1
  137. package/dist/engine/extraction/code/families/js-ts.d.ts +1 -1
  138. package/dist/engine/extraction/code/families/metadata.d.ts +1 -1
  139. package/dist/engine/extraction/code/tree-sitter/grammar.js.map +1 -0
  140. package/dist/engine/extraction/code/tree-sitter/nodes.js.map +1 -0
  141. package/dist/engine/extraction/code/tree-sitter/parser.js.map +1 -0
  142. package/dist/engine/extraction/image/extractor.d.ts +5 -0
  143. package/dist/engine/extraction/image/extractor.js +35 -0
  144. package/dist/engine/extraction/image/extractor.js.map +1 -0
  145. package/dist/engine/extraction/index.d.ts +4 -44
  146. package/dist/engine/extraction/index.js +2 -280
  147. package/dist/engine/extraction/index.js.map +1 -1
  148. package/dist/engine/extraction/markdown/extractor.d.ts +5 -11
  149. package/dist/engine/extraction/markdown/extractor.js +38 -17
  150. package/dist/engine/extraction/markdown/extractor.js.map +1 -1
  151. package/dist/engine/extraction/runtime.d.ts +4 -0
  152. package/dist/engine/extraction/runtime.js +26 -0
  153. package/dist/engine/extraction/runtime.js.map +1 -0
  154. package/dist/engine/extraction/source.d.ts +18 -0
  155. package/dist/engine/extraction/source.js +22 -0
  156. package/dist/engine/extraction/source.js.map +1 -0
  157. package/dist/engine/extraction/text/extractor.d.ts +7 -0
  158. package/dist/engine/extraction/text/extractor.js +168 -0
  159. package/dist/engine/extraction/text/extractor.js.map +1 -0
  160. package/dist/engine/extraction/types.d.ts +4 -0
  161. package/dist/engine/{models → extraction}/types.js.map +1 -1
  162. package/dist/engine/extraction/vector-content.d.ts +5 -0
  163. package/dist/engine/extraction/vector-content.js +95 -0
  164. package/dist/engine/extraction/vector-content.js.map +1 -0
  165. package/dist/engine/file-size-policy.d.ts +6 -0
  166. package/dist/engine/file-size-policy.js +20 -0
  167. package/dist/engine/file-size-policy.js.map +1 -0
  168. package/dist/engine/file-type.d.ts +15 -0
  169. package/dist/engine/file-type.js +152 -0
  170. package/dist/engine/file-type.js.map +1 -0
  171. package/dist/engine/index-status.d.ts +9 -0
  172. package/dist/engine/index-status.js +38 -0
  173. package/dist/engine/index-status.js.map +1 -0
  174. package/dist/engine/manifest.d.ts +13 -0
  175. package/dist/engine/manifest.js +124 -0
  176. package/dist/engine/manifest.js.map +1 -0
  177. package/dist/engine/models/{providers/llama-cpp/embedding.d.ts → backends/llama-cpp.d.ts} +25 -20
  178. package/dist/engine/models/{providers/llama-cpp/embedding.js → backends/llama-cpp.js} +127 -106
  179. package/dist/engine/models/backends/llama-cpp.js.map +1 -0
  180. package/dist/engine/models/backends/model2vec-runtime.d.ts +63 -0
  181. package/dist/engine/models/backends/model2vec-runtime.js +112 -0
  182. package/dist/engine/models/backends/model2vec-runtime.js.map +1 -0
  183. package/dist/engine/models/backends/model2vec-tokenizer.d.ts +2 -0
  184. package/dist/engine/models/backends/model2vec-tokenizer.js +42 -0
  185. package/dist/engine/models/backends/model2vec-tokenizer.js.map +1 -0
  186. package/dist/engine/models/backends/model2vec-worker-pool.d.ts +25 -0
  187. package/dist/engine/models/backends/model2vec-worker-pool.js +264 -0
  188. package/dist/engine/models/backends/model2vec-worker-pool.js.map +1 -0
  189. package/dist/engine/models/backends/model2vec-worker.d.ts +1 -0
  190. package/dist/engine/models/backends/model2vec-worker.js +41 -0
  191. package/dist/engine/models/backends/model2vec-worker.js.map +1 -0
  192. package/dist/engine/models/backends/model2vec.d.ts +40 -0
  193. package/dist/engine/models/backends/model2vec.js +311 -0
  194. package/dist/engine/models/backends/model2vec.js.map +1 -0
  195. package/dist/engine/models/backends/qwen.d.ts +44 -0
  196. package/dist/engine/models/{providers/qwen/embedding.js → backends/qwen.js} +155 -94
  197. package/dist/engine/models/backends/qwen.js.map +1 -0
  198. package/dist/engine/models/backends/transformers-js.d.ts +87 -0
  199. package/dist/engine/models/backends/transformers-js.js +316 -0
  200. package/dist/engine/models/backends/transformers-js.js.map +1 -0
  201. package/dist/engine/models/catalog.d.ts +207 -23
  202. package/dist/engine/models/catalog.js +191 -25
  203. package/dist/engine/models/catalog.js.map +1 -1
  204. package/dist/engine/models/download-progress.d.ts +15 -0
  205. package/dist/engine/models/download-progress.js +48 -0
  206. package/dist/engine/models/download-progress.js.map +1 -0
  207. package/dist/engine/models/embeddings.d.ts +59 -18
  208. package/dist/engine/models/embeddings.js +52 -28
  209. package/dist/engine/models/embeddings.js.map +1 -1
  210. package/dist/engine/models/factory.d.ts +2 -5
  211. package/dist/engine/models/factory.js +41 -56
  212. package/dist/engine/models/factory.js.map +1 -1
  213. package/dist/engine/models/index.d.ts +4 -6
  214. package/dist/engine/models/index.js +4 -4
  215. package/dist/engine/models/index.js.map +1 -1
  216. package/dist/engine/models/ranking.d.ts +7 -3
  217. package/dist/engine/models/resolution.d.ts +8 -0
  218. package/dist/engine/models/resolution.js +22 -0
  219. package/dist/engine/models/resolution.js.map +1 -0
  220. package/dist/engine/pipeline/indexing/index.d.ts +9 -7
  221. package/dist/engine/pipeline/indexing/index.js +409 -165
  222. package/dist/engine/pipeline/indexing/index.js.map +1 -1
  223. package/dist/engine/pipeline/indexing/input-budget.d.ts +2 -0
  224. package/dist/engine/pipeline/indexing/input-budget.js +51 -0
  225. package/dist/engine/pipeline/indexing/input-budget.js.map +1 -0
  226. package/dist/engine/pipeline/indexing/root-paths.js +6 -6
  227. package/dist/engine/pipeline/indexing/root-paths.js.map +1 -1
  228. package/dist/engine/pipeline/indexing/scanner/index.d.ts +9 -2
  229. package/dist/engine/pipeline/indexing/scanner/index.js +291 -35
  230. package/dist/engine/pipeline/indexing/scanner/index.js.map +1 -1
  231. package/dist/engine/pipeline/search/index.d.ts +6 -6
  232. package/dist/engine/pipeline/search/index.js +8 -10
  233. package/dist/engine/pipeline/search/index.js.map +1 -1
  234. package/dist/engine/service/index.d.ts +3 -2
  235. package/dist/engine/service/index.js +1 -1
  236. package/dist/engine/service/index.js.map +1 -1
  237. package/dist/engine/service/root.d.ts +10 -12
  238. package/dist/engine/service/root.js +29 -25
  239. package/dist/engine/service/root.js.map +1 -1
  240. package/dist/engine/service/structure-enrichment.d.ts +1 -1
  241. package/dist/engine/service/structure-enrichment.js +20 -22
  242. package/dist/engine/service/structure-enrichment.js.map +1 -1
  243. package/dist/engine/service/types.d.ts +40 -38
  244. package/dist/engine/service/workspace-index.d.ts +26 -0
  245. package/dist/engine/service/workspace-index.js +157 -0
  246. package/dist/engine/service/workspace-index.js.map +1 -0
  247. package/dist/engine/service/zvec-grep.d.ts +12 -1
  248. package/dist/engine/service/zvec-grep.js +698 -489
  249. package/dist/engine/service/zvec-grep.js.map +1 -1
  250. package/dist/engine/storage/index.d.ts +30 -20
  251. package/dist/engine/storage/index.js +2 -1
  252. package/dist/engine/storage/index.js.map +1 -1
  253. package/dist/engine/storage/layout.d.ts +10 -0
  254. package/dist/engine/storage/layout.js +29 -0
  255. package/dist/engine/storage/layout.js.map +1 -0
  256. package/dist/engine/storage/zvec.d.ts +4 -67
  257. package/dist/engine/storage/zvec.js +115 -85
  258. package/dist/engine/storage/zvec.js.map +1 -1
  259. package/dist/engine/types.d.ts +29 -23
  260. package/dist/engine/types.js +1 -1
  261. package/dist/engine/types.js.map +1 -1
  262. package/dist/engine/utils/daemon-lease.d.ts +17 -0
  263. package/dist/engine/utils/daemon-lease.js +175 -0
  264. package/dist/engine/utils/daemon-lease.js.map +1 -0
  265. package/dist/engine/utils/file-selection.js +38 -1
  266. package/dist/engine/utils/file-selection.js.map +1 -1
  267. package/dist/engine/utils/lock.js +1 -1
  268. package/dist/engine/utils/lock.js.map +1 -1
  269. package/dist/index-progress.d.ts +10 -0
  270. package/dist/index-progress.js +159 -0
  271. package/dist/index-progress.js.map +1 -0
  272. package/dist/index.d.ts +4 -4
  273. package/dist/index.js +1 -1
  274. package/dist/index.js.map +1 -1
  275. package/dist/mcp/http-transport.d.ts +27 -0
  276. package/dist/mcp/http-transport.js +182 -0
  277. package/dist/mcp/http-transport.js.map +1 -0
  278. package/dist/mcp/input-normalization.d.ts +38 -0
  279. package/dist/mcp/input-normalization.js +104 -0
  280. package/dist/mcp/input-normalization.js.map +1 -0
  281. package/dist/mcp/progress-heartbeat.d.ts +17 -0
  282. package/dist/mcp/progress-heartbeat.js +46 -0
  283. package/dist/mcp/progress-heartbeat.js.map +1 -0
  284. package/dist/mcp/request-metadata.d.ts +2 -0
  285. package/dist/mcp/request-metadata.js +11 -0
  286. package/dist/mcp/request-metadata.js.map +1 -0
  287. package/dist/mcp/request-state.d.ts +40 -0
  288. package/dist/mcp/request-state.js +210 -0
  289. package/dist/mcp/request-state.js.map +1 -0
  290. package/dist/mcp/result-format.d.ts +13 -0
  291. package/dist/mcp/result-format.js +75 -0
  292. package/dist/mcp/result-format.js.map +1 -0
  293. package/dist/mcp/schemas.d.ts +672 -0
  294. package/dist/mcp/schemas.js +506 -0
  295. package/dist/mcp/schemas.js.map +1 -0
  296. package/dist/mcp/stdio-bridge.d.ts +9 -0
  297. package/dist/mcp/stdio-bridge.js +131 -0
  298. package/dist/mcp/stdio-bridge.js.map +1 -0
  299. package/dist/mcp/tools.d.ts +153 -0
  300. package/dist/mcp/tools.js +482 -0
  301. package/dist/mcp/tools.js.map +1 -0
  302. package/dist/mcp/toolset.d.ts +5 -0
  303. package/dist/mcp/toolset.js +12 -0
  304. package/dist/mcp/toolset.js.map +1 -0
  305. package/dist/observability/trace-context.d.ts +11 -0
  306. package/dist/observability/trace-context.js +138 -0
  307. package/dist/observability/trace-context.js.map +1 -0
  308. package/dist/prompts/zvec-grep-guidance.d.ts +2 -0
  309. package/dist/prompts/zvec-grep-guidance.js +9 -0
  310. package/dist/prompts/zvec-grep-guidance.js.map +1 -0
  311. package/docs/01-agents.md +132 -0
  312. package/docs/02-cli.md +239 -0
  313. package/docs/03-mcp.md +185 -0
  314. package/docs/04-pipeline.md +226 -0
  315. package/docs/05-architecture.md +86 -0
  316. package/docs/06-server.md +215 -0
  317. package/docs/07-embedding.md +182 -0
  318. package/docs/08-roadmap.md +85 -0
  319. package/docs/README.md +53 -0
  320. package/package.json +20 -11
  321. package/dist/cli/mcp.d.ts +0 -2
  322. package/dist/cli/mcp.js +0 -354
  323. package/dist/cli/mcp.js.map +0 -1
  324. package/dist/engine/collection/index.d.ts +0 -55
  325. package/dist/engine/collection/index.js +0 -710
  326. package/dist/engine/collection/index.js.map +0 -1
  327. package/dist/engine/errors/details.d.ts +0 -6
  328. package/dist/engine/errors/details.js.map +0 -1
  329. package/dist/engine/errors/index.js +0 -16
  330. package/dist/engine/errors/index.js.map +0 -1
  331. package/dist/engine/extraction/tree-sitter/grammar.js.map +0 -1
  332. package/dist/engine/extraction/tree-sitter/nodes.js.map +0 -1
  333. package/dist/engine/extraction/tree-sitter/parser.js.map +0 -1
  334. package/dist/engine/files/file-type.d.ts +0 -5
  335. package/dist/engine/files/file-type.js +0 -119
  336. package/dist/engine/files/file-type.js.map +0 -1
  337. package/dist/engine/models/providers/index.d.ts +0 -2
  338. package/dist/engine/models/providers/index.js +0 -3
  339. package/dist/engine/models/providers/index.js.map +0 -1
  340. package/dist/engine/models/providers/llama-cpp/embedding.js.map +0 -1
  341. package/dist/engine/models/providers/llama-cpp/index.d.ts +0 -1
  342. package/dist/engine/models/providers/llama-cpp/index.js +0 -2
  343. package/dist/engine/models/providers/llama-cpp/index.js.map +0 -1
  344. package/dist/engine/models/providers/qwen/embedding.d.ts +0 -42
  345. package/dist/engine/models/providers/qwen/embedding.js.map +0 -1
  346. package/dist/engine/models/providers/qwen/index.d.ts +0 -1
  347. package/dist/engine/models/providers/qwen/index.js +0 -2
  348. package/dist/engine/models/providers/qwen/index.js.map +0 -1
  349. package/dist/engine/models/types.d.ts +0 -30
  350. package/skills/zvec-grep/SKILL.md +0 -151
  351. package/skills/zvec-grep/agents/openai.yaml +0 -4
  352. /package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/grammar.d.ts +0 -0
  353. /package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/grammar.js +0 -0
  354. /package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/nodes.d.ts +0 -0
  355. /package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/nodes.js +0 -0
  356. /package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/parser.d.ts +0 -0
  357. /package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/parser.js +0 -0
  358. /package/dist/engine/{models → extraction}/types.js +0 -0
@@ -1,27 +1,90 @@
1
- import { mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
2
- import { createHash } from "node:crypto";
3
- import { dirname, join, relative, resolve } from "node:path";
4
- import { globalConfigPath, readGlobalConfig, } from "../config.js";
5
- import { Collection, CollectionRegistry, isCollectionIndexed, } from "../collection/index.js";
6
- import { collectionDetail, detail, EngineError, errorDetails, } from "../errors/index.js";
7
- import { createEmbeddingModel, createEmbeddingModelFromReference, } from "../models/index.js";
8
- import { ANONYMOUS_COLLECTION_NAME, anonymousCollectionPath, anonymousHome, anonymousIndexLocation, resolveZvecGrepRoot, } from "./root.js";
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
3
+ import { basename, dirname, join, relative, resolve } from "node:path";
4
+ import { globalConfigPath, readGlobalConfig, resolveEmbeddingRuntimeOptions, } from "../config.js";
5
+ import { isWorkspaceIndexed, WorkspaceIndex } from "./workspace-index.js";
6
+ import { workspaceIndexDetail, detail, EngineError, errorDetails, } from "../errors.js";
7
+ import { createEmbeddingModel, EmbeddingPurpose, resolveEmbeddingReference, } from "../models/index.js";
8
+ import { CURRENT_INDEX_VERSION } from "../types.js";
9
+ import { indexStatusNeedsRefresh } from "../index-status.js";
10
+ import { workspaceIndexInfoFromManifest, CURRENT_MANIFEST_VERSION, readWorkspaceManifest, writeWorkspaceManifest, } from "../manifest.js";
11
+ import { validateRootPaths } from "../pipeline/indexing/root-paths.js";
12
+ import { findNearestWorkspace, hasWorkspaceIndex, resetWorkspaceIndex, resolveZvecGrepRoot, workspaceHome, workspaceIndexLocation, } from "./root.js";
9
13
  import { runRgSearch } from "./lexical.js";
10
14
  import { defaultHome, normalizePath } from "../utils/path.js";
11
15
  import { acquireReadWriteLock, assertNoWriteLock, } from "../utils/lock.js";
16
+ import { assertDaemonWriteAllowed } from "../utils/daemon-lease.js";
12
17
  import { TimingCollector } from "../utils/timing.js";
13
18
  import { enrichLexicalItemsWithStructure } from "./structure-enrichment.js";
19
+ import { remoteEmbeddingAuthorizationGuard } from "../../authorization/operation.js";
20
+ import { RemoteEmbeddingAuthorizationStore } from "../../authorization/store.js";
14
21
  const DEFAULT_CONTEXT_LIMIT = 10;
15
22
  const DEFAULT_CONTEXT_TOTAL_LIMIT = 30;
16
- const DEFAULT_LOCAL_EMBEDDING = "local/embeddinggemma-300m";
23
+ const DEFAULT_LOCAL_EMBEDDING = "local/potion-code-16m-v2";
24
+ const PROVIDER_API_KEY_IDENTITY_SECRET = randomBytes(32);
17
25
  const MAX_RECOVERED_EMBEDDING_MODELS = 4;
18
26
  export async function createZvecGrep(options = {}) {
19
27
  return new ZvecGrepService(options);
20
28
  }
29
+ export function openWorkspaceReadSession(startRoot, embeddingModel) {
30
+ const start = resolveZvecGrepRoot(startRoot);
31
+ assertNearestWorkspaceHomeUnlocked(start, "daemon.context.open");
32
+ const nearest = findNearestWorkspaceIndex(start);
33
+ if (!nearest) {
34
+ throw workspaceIndexMissingError(start, "undecided");
35
+ }
36
+ const { location, info } = nearest;
37
+ if (info.indexPolicy === "disabled") {
38
+ throw workspaceIndexDisabledError(location.root);
39
+ }
40
+ if (!isWorkspaceIndexed(info) || !hasWorkspaceIndex(location)) {
41
+ throw workspaceIndexMissingError(location.root, info.indexPolicy ?? "enabled");
42
+ }
43
+ const workspaceIndex = new WorkspaceIndex(info, {
44
+ mode: "read",
45
+ embeddingModel,
46
+ });
47
+ let closed = false;
48
+ return {
49
+ root: location.root,
50
+ async context(options) {
51
+ if (closed) {
52
+ throw new EngineError("Workspace read session is already closed", {
53
+ code: "ZVEC_GREP.ENGINE.SERVICE.READ_SESSION_CLOSED",
54
+ });
55
+ }
56
+ const timings = new TimingCollector();
57
+ const request = normalizeContextRequest(options);
58
+ const result = await timings.time("total", () => withHomeReadLock(location.home, "daemon.context", () => contextFromOpenWorkspaceIndex({
59
+ root: location.root,
60
+ request,
61
+ workspaceIndex,
62
+ options: { ...options, autoUpdate: false },
63
+ timings,
64
+ })));
65
+ return withContextTimings(result, timings);
66
+ },
67
+ async close() {
68
+ if (closed) {
69
+ return;
70
+ }
71
+ workspaceIndex.close();
72
+ closed = true;
73
+ },
74
+ };
75
+ }
76
+ export function createEmbeddingModelForIdentity(identity, options = {}, workspaceRuntime = {}) {
77
+ const reference = embeddingModelReference(identity);
78
+ return createServiceEmbeddingModel(reference, providerOptions(options, identity, readGlobalConfig(), workspaceRuntime), options);
79
+ }
80
+ export function embeddingModelPoolKeyForIdentity(identity, options = {}, workspaceRuntime = {}) {
81
+ const reference = embeddingModelReference(identity);
82
+ const fingerprint = providerOptionsFingerprint(providerOptions(options, identity, readGlobalConfig(), workspaceRuntime));
83
+ return [reference, fingerprint].join("/");
84
+ }
21
85
  class ZvecGrepService {
22
86
  options;
23
87
  root;
24
- collections;
25
88
  embeddingModel;
26
89
  recoveredEmbeddingModels = new Map();
27
90
  retiredEmbeddingModels = new Set();
@@ -31,23 +94,21 @@ class ZvecGrepService {
31
94
  this.options = options;
32
95
  this.root = resolveZvecGrepRoot(options.root);
33
96
  this.embeddingModel = options.embeddingModel;
34
- this.collections = {
35
- list: () => this.listCollections(),
36
- info: (name) => this.collectionInfo(name),
37
- status: (name) => this.collectionStatus(name),
38
- index: (name, paths, indexOptions) => this.indexCollection(name, paths, indexOptions),
39
- remove: (name) => this.removeCollection(name),
40
- };
41
97
  }
42
98
  async index(options = {}) {
43
99
  this.ensureOpen();
44
100
  const root = resolveZvecGrepRoot(options.root ?? this.root);
45
- const location = anonymousIndexLocation(root);
46
- return await this.withEmbeddingModelOperation(() => withHomeWriteLock(location.home, options.rebuild ? "index.rebuild" : "index", async () => {
47
- const existing = readCollectionInfo(location.home, ANONYMOUS_COLLECTION_NAME);
48
- const embeddingModel = this.embeddingModelForIndex(existing, location.home, "index");
49
- const registry = new CollectionRegistry(location.home, embeddingModel);
50
- try {
101
+ const daemonWritePermit = assertDaemonWriteAllowed(root, this.options.daemonInstanceToken);
102
+ const location = workspaceIndexLocation(root);
103
+ try {
104
+ return await this.withEmbeddingModelOperation(() => withHomeWriteLock(location.home, options.rebuild ? "index.rebuild" : "index", async () => {
105
+ const existing = readWorkspaceManifest(location.home);
106
+ const existingRuntime = existing?.embeddingRuntime ?? {};
107
+ const embeddingModel = this.embeddingModelForIndex(existing, "index", existingRuntime);
108
+ const effectiveRuntime = effectiveEmbeddingRuntime(this.options, embeddingModel, runtimeForModelProvider(existing, embeddingModel, existingRuntime));
109
+ if (!options.rebuild) {
110
+ assertWorkspaceEndpointMatchesCurrentRuntime(existing, existingRuntime, effectiveRuntime, "zg index --endpoint <url> --rebuild");
111
+ }
51
112
  const rootPaths = resolveIndexRootPaths(existing, options.rootPaths, root, {
52
113
  resetPaths: options.resetPaths === true,
53
114
  includePaths: options.includePaths,
@@ -63,56 +124,104 @@ class ZvecGrepService {
63
124
  maxFileSizeBytes: options.maxFileSizeBytes,
64
125
  follow: options.follow,
65
126
  });
66
- if (options.rebuild) {
67
- registry.remove(ANONYMOUS_COLLECTION_NAME);
127
+ if (options.rebuild || !isWorkspaceIndexed(existing)) {
128
+ resetWorkspaceIndex(location);
68
129
  }
69
- const existingAfterRebuild = registry.get(ANONYMOUS_COLLECTION_NAME);
70
- if (isCollectionIndexed(existingAfterRebuild)) {
71
- assertCollectionEmbeddingMatchesCurrentModel(existingAfterRebuild, embeddingModel, "zg index --rebuild");
130
+ const existingAfterRebuild = options.rebuild ? null : existing;
131
+ if (isWorkspaceIndexed(existingAfterRebuild)) {
132
+ assertWorkspaceEmbeddingMatchesCurrentModel(existingAfterRebuild, embeddingModel, "zg index --rebuild");
72
133
  }
73
- registry.prepareIndex(ANONYMOUS_COLLECTION_NAME, rootPaths, anonymousCollectionPath(root));
74
- return await registry.open(ANONYMOUS_COLLECTION_NAME).index({
75
- rebuild: false,
76
- embeddingConcurrency: options.embeddingConcurrency,
77
- onProgress: options.onProgress,
134
+ const embeddingRuntime = embeddingRuntimeAfterIndex(existing, existingRuntime, embeddingModel, effectiveRuntime, this.options);
135
+ const manifest = prepareWorkspaceManifest(location, existingAfterRebuild, rootPaths, embeddingModel, embeddingRuntime);
136
+ const workspaceIndex = new WorkspaceIndex(manifest, {
137
+ mode: "write",
138
+ embeddingModel,
78
139
  });
79
- }
80
- finally {
81
- registry.close();
82
- }
83
- }));
140
+ writeWorkspaceManifest(location.home, manifest);
141
+ try {
142
+ const releaseWriterContext = options.onWriterContext?.((contextOptions) => this.contextFromWriterWorkspaceIndex(root, workspaceIndex, contextOptions));
143
+ try {
144
+ const result = await workspaceIndex.index({
145
+ rebuild: false,
146
+ embeddingConcurrency: options.embeddingConcurrency,
147
+ onProgress: options.onProgress,
148
+ changedPaths: options.changedPaths,
149
+ signal: options.signal,
150
+ });
151
+ writeWorkspaceManifest(location.home, {
152
+ ...manifest,
153
+ embeddingRuntime,
154
+ updatedTime: Date.now(),
155
+ });
156
+ return result;
157
+ }
158
+ catch (error) {
159
+ writeWorkspaceManifest(location.home, {
160
+ ...manifest,
161
+ embeddingRuntime,
162
+ updatedTime: Date.now(),
163
+ });
164
+ throw error;
165
+ }
166
+ finally {
167
+ await releaseWriterContext?.();
168
+ }
169
+ }
170
+ finally {
171
+ workspaceIndex.close();
172
+ }
173
+ }));
174
+ }
175
+ finally {
176
+ daemonWritePermit?.release();
177
+ }
84
178
  }
85
179
  async dropIndex(options = {}) {
86
180
  this.ensureOpen();
87
181
  const root = resolveZvecGrepRoot(options.root ?? this.root);
88
- const location = anonymousIndexLocation(root);
89
- if (!readCollectionInfo(location.home, ANONYMOUS_COLLECTION_NAME)) {
90
- return false;
91
- }
92
- return await withHomeWriteLock(location.home, "index.drop", async () => {
93
- const registry = new CollectionRegistry(location.home, undefined);
94
- try {
95
- return registry.remove(ANONYMOUS_COLLECTION_NAME);
96
- }
97
- finally {
98
- registry.close();
182
+ const daemonWritePermit = assertDaemonWriteAllowed(root, this.options.daemonInstanceToken);
183
+ const location = workspaceIndexLocation(root);
184
+ try {
185
+ if (!readWorkspaceManifest(location.home)) {
186
+ return false;
99
187
  }
100
- });
188
+ return await withHomeWriteLock(location.home, "index.drop", async () => {
189
+ resetWorkspaceIndex(location);
190
+ return true;
191
+ });
192
+ }
193
+ finally {
194
+ daemonWritePermit?.release();
195
+ }
101
196
  }
102
197
  async disableIndex(options = {}) {
103
198
  this.ensureOpen();
104
199
  const root = resolveZvecGrepRoot(options.root ?? this.root);
105
- const location = anonymousIndexLocation(root);
106
- await withHomeWriteLock(location.home, "index.disable", async () => {
107
- const registry = new CollectionRegistry(location.home, undefined);
108
- try {
109
- registry.disableIndex(ANONYMOUS_COLLECTION_NAME, [root], location.collectionPath);
110
- }
111
- finally {
112
- registry.close();
113
- }
114
- });
115
- return this.info({ root });
200
+ const daemonWritePermit = assertDaemonWriteAllowed(root, this.options.daemonInstanceToken);
201
+ const location = workspaceIndexLocation(root);
202
+ try {
203
+ await withHomeWriteLock(location.home, "index.disable", async () => {
204
+ const existing = readWorkspaceManifest(location.home);
205
+ const now = Date.now();
206
+ writeWorkspaceManifest(location.home, {
207
+ manifestVersion: CURRENT_MANIFEST_VERSION,
208
+ id: existing?.id ?? randomUUID(),
209
+ name: existing?.name ?? workspaceDisplayName(root),
210
+ path: location.home,
211
+ rootPaths: existing?.rootPaths ?? validateRootPaths([root]),
212
+ indexPolicy: "disabled",
213
+ embedding: null,
214
+ indexVersion: null,
215
+ createdTime: existing?.createdTime ?? now,
216
+ updatedTime: now,
217
+ embeddingRuntime: existing?.embeddingRuntime ?? {},
218
+ });
219
+ });
220
+ return this.info({ root });
221
+ }
222
+ finally {
223
+ daemonWritePermit?.release();
224
+ }
116
225
  }
117
226
  async context(options) {
118
227
  this.ensureOpen();
@@ -124,76 +233,73 @@ class ZvecGrepService {
124
233
  }
125
234
  async contextWithTimings(options, timings) {
126
235
  const request = normalizeContextRequest(options);
127
- if (options.collection) {
128
- return this.contextFromNamedCollection(request, options, timings);
129
- }
130
236
  const startRoot = resolveZvecGrepRoot(options.root ?? this.root);
131
237
  if (options.rg) {
132
238
  return this.contextFromRg(startRoot, request, options, timings);
133
239
  }
134
- assertNearestAnonymousHomeUnlocked(startRoot, "context");
135
- const nearest = findNearestAnonymousCollection(startRoot);
240
+ assertNearestWorkspaceHomeUnlocked(startRoot, "context");
241
+ const nearest = findNearestWorkspaceIndex(startRoot);
136
242
  if (nearest) {
137
243
  const { location, info } = nearest;
138
244
  if (info.indexPolicy === "disabled") {
139
- throw anonymousIndexDisabledError(location.root);
245
+ throw workspaceIndexDisabledError(location.root);
140
246
  }
141
- if (!isCollectionIndexed(info)) {
142
- throw anonymousIndexMissingError(location.root, info.indexPolicy ?? "enabled");
247
+ if (!isWorkspaceIndexed(info) || !hasWorkspaceIndex(location)) {
248
+ throw workspaceIndexMissingError(location.root, info.indexPolicy ?? "enabled");
143
249
  }
144
250
  if (options.autoUpdate !== false) {
145
- await this.refreshAnonymousIndexForContext(location, options, timings);
251
+ await this.refreshWorkspaceIndexForContext(location, options, timings);
146
252
  }
147
- return await withHomeReadLock(location.home, "context", () => this.contextFromAnonymousIndex(location, request, options, timings));
253
+ return await withHomeReadLock(location.home, "context", () => this.contextFromWorkspaceIndex(location, request, options, timings));
148
254
  }
149
- throw anonymousIndexMissingError(startRoot, "undecided");
255
+ throw workspaceIndexMissingError(startRoot, "undecided");
150
256
  }
151
257
  async info(options = {}) {
152
258
  this.ensureOpen();
153
259
  const startRoot = resolveZvecGrepRoot(options.root ?? this.root);
154
- assertNearestAnonymousHomeUnlocked(startRoot, "info");
155
- const nearest = findNearestAnonymousCollection(startRoot);
260
+ assertNearestWorkspaceHomeUnlocked(startRoot, "info");
261
+ const nearest = findNearestWorkspaceIndex(startRoot);
156
262
  if (!nearest) {
157
- const location = anonymousIndexLocation(startRoot);
263
+ const location = workspaceIndexLocation(startRoot);
158
264
  return {
159
265
  root: startRoot,
160
266
  indexed: false,
161
267
  indexPolicy: "undecided",
162
268
  home: location.home,
163
- indexPath: location.collectionPath,
269
+ indexPath: location.indexPath,
164
270
  source: "unindexed",
165
271
  suggestion: "zg index or zg query --rg",
166
272
  };
167
273
  }
168
274
  return await withHomeReadLock(nearest.location.home, "info", async () => {
169
- const registry = new CollectionRegistry(nearest.location.home, undefined, true);
170
- try {
171
- const collection = registry.get(ANONYMOUS_COLLECTION_NAME);
172
- const indexed = collection !== null &&
173
- collection.indexPolicy !== "disabled" &&
174
- isCollectionIndexed(collection);
175
- return {
176
- root: nearest.location.root,
177
- indexed,
178
- indexPolicy: collection?.indexPolicy ?? "undecided",
179
- home: nearest.location.home,
180
- indexPath: nearest.location.collectionPath,
181
- source: indexed ? "index" : "unindexed",
182
- collection: collection ?? undefined,
183
- status: indexed
184
- ? await registry.status(ANONYMOUS_COLLECTION_NAME)
185
- : null,
186
- suggestion: anonymousInfoSuggestion(collection),
187
- };
188
- }
189
- finally {
190
- registry.close();
191
- }
275
+ const workspaceIndex = readWorkspaceManifest(nearest.location.home);
276
+ const indexed = workspaceIndex !== null &&
277
+ workspaceIndex.indexPolicy !== "disabled" &&
278
+ isWorkspaceIndexed(workspaceIndex) &&
279
+ hasWorkspaceIndex(nearest.location);
280
+ return {
281
+ root: nearest.location.root,
282
+ indexed,
283
+ indexPolicy: workspaceIndex?.indexPolicy ?? "undecided",
284
+ home: nearest.location.home,
285
+ indexPath: nearest.location.indexPath,
286
+ source: indexed ? "index" : "unindexed",
287
+ workspaceIndex: workspaceIndex
288
+ ? workspaceIndexInfoFromManifest(workspaceIndex)
289
+ : undefined,
290
+ status: indexed && options.includeStatus !== false
291
+ ? await workspaceIndexStatus(workspaceIndex, nearest.location)
292
+ : null,
293
+ suggestion: workspaceInfoSuggestion(workspaceIndex),
294
+ };
192
295
  });
193
296
  }
194
297
  async close() {
195
298
  const models = new Set([
196
- ...(this.embeddingModel ? [this.embeddingModel] : []),
299
+ ...(this.embeddingModel &&
300
+ this.options.embeddingModelOwnership !== "borrowed"
301
+ ? [this.embeddingModel]
302
+ : []),
197
303
  ...this.recoveredEmbeddingModels.values(),
198
304
  ...this.retiredEmbeddingModels,
199
305
  ]);
@@ -204,248 +310,86 @@ class ZvecGrepService {
204
310
  }
205
311
  this.closed = true;
206
312
  }
207
- async listCollections() {
208
- this.ensureOpen();
209
- return await withHomeReadLock(serviceHome(this.options), "collections.list", async () => {
210
- const registry = this.createRegistry(true);
211
- try {
212
- return registry.list();
213
- }
214
- finally {
215
- registry.close();
216
- }
217
- });
218
- }
219
- async collectionInfo(name) {
220
- this.ensureOpen();
221
- return await withHomeReadLock(serviceHome(this.options), "collections.info", async () => {
222
- const registry = this.createRegistry(true);
223
- try {
224
- return registry.get(name);
225
- }
226
- finally {
227
- registry.close();
228
- }
229
- });
230
- }
231
- async collectionStatus(name) {
232
- this.ensureOpen();
233
- return await withHomeReadLock(serviceHome(this.options), "collections.status", async () => {
234
- const registry = this.createRegistry(true);
235
- try {
236
- return await registry.status(name);
237
- }
238
- finally {
239
- registry.close();
240
- }
241
- });
242
- }
243
- async indexCollection(name, paths, options = {}) {
244
- this.ensureOpen();
245
- const home = serviceHome(this.options);
246
- return await this.withEmbeddingModelOperation(() => withHomeWriteLock(home, options.rebuild ? "collections.index.rebuild" : "collections.index", async () => {
247
- const existing = readCollectionInfo(home, name);
248
- const embeddingModel = this.embeddingModelForIndex(existing, home, "collections.index");
249
- const registry = this.createRegistry(false, embeddingModel);
250
- try {
251
- const requestedRootPaths = paths === undefined
252
- ? undefined
253
- : Array.isArray(paths)
254
- ? paths
255
- : [paths];
256
- const rootPaths = resolveIndexRootPaths(existing, requestedRootPaths, this.root, {
257
- resetPaths: options.resetPaths === true,
258
- includePaths: options.includePaths,
259
- excludePaths: options.excludePaths,
260
- globs: options.globs,
261
- insensitiveGlobs: options.insensitiveGlobs,
262
- fileTypes: options.fileTypes,
263
- excludedFileTypes: options.excludedFileTypes,
264
- hidden: options.hidden,
265
- noIgnore: options.noIgnore,
266
- ignoreFiles: options.ignoreFiles,
267
- maxDepth: options.maxDepth,
268
- maxFileSizeBytes: options.maxFileSizeBytes,
269
- follow: options.follow,
270
- });
271
- if (options.rebuild) {
272
- registry.remove(name);
273
- }
274
- const existingAfterRebuild = registry.get(name);
275
- if (isCollectionIndexed(existingAfterRebuild)) {
276
- assertCollectionEmbeddingMatchesCurrentModel(existingAfterRebuild, embeddingModel, "zg collections index <name> --rebuild");
277
- }
278
- registry.prepareIndex(name, rootPaths);
279
- return await registry.open(name).index({
280
- embeddingConcurrency: options.embeddingConcurrency,
281
- onProgress: options.onProgress,
282
- });
283
- }
284
- finally {
285
- registry.close();
286
- }
287
- }));
288
- }
289
- async removeCollection(name) {
290
- this.ensureOpen();
291
- return await withHomeWriteLock(serviceHome(this.options), "collections.remove", async () => {
292
- const registry = this.createRegistry(false);
293
- try {
294
- return registry.remove(name);
295
- }
296
- finally {
297
- registry.close();
298
- }
299
- });
300
- }
301
- async contextFromAnonymousIndex(location, request, options, timings) {
302
- const registry = new CollectionRegistry(location.home, undefined, true);
313
+ async contextFromWorkspaceIndex(location, request, options, timings) {
314
+ const info = readWorkspaceManifest(location.home);
315
+ if (!info) {
316
+ throw new EngineError("Workspace index manifest not found", {
317
+ code: "ZVEC_GREP.ENGINE.MANIFEST.NOT_FOUND",
318
+ });
319
+ }
320
+ const workspaceIndex = this.openWorkspaceIndexForSearch(info, request, info.embeddingRuntime);
303
321
  try {
304
- const info = registry.get(ANONYMOUS_COLLECTION_NAME);
305
- if (!info) {
306
- throw new EngineError("Collection not found", {
307
- code: "ZVEC_GREP.ENGINE.COLLECTION.NOT_FOUND",
308
- });
309
- }
310
- const collection = this.openCollectionForSearch(info, request, location.home);
311
- try {
312
- return await this.contextFromCollection({
313
- root: location.root,
314
- request,
315
- collection,
316
- anonymous: true,
317
- options,
318
- timings,
319
- });
320
- }
321
- finally {
322
- collection.close();
323
- }
322
+ return await contextFromOpenWorkspaceIndex({
323
+ root: location.root,
324
+ request,
325
+ workspaceIndex,
326
+ options,
327
+ timings,
328
+ });
324
329
  }
325
330
  finally {
326
- registry.close();
331
+ workspaceIndex.close();
327
332
  }
328
333
  }
329
- async refreshAnonymousIndexForContext(location, options, timings) {
330
- const needsRefresh = await timings.time("status_scan", () => withHomeReadLock(location.home, "context.status", async () => {
331
- const registry = new CollectionRegistry(location.home, undefined, true);
332
- try {
333
- const status = await registry.status(ANONYMOUS_COLLECTION_NAME);
334
- return status ? collectionIndexStatusNeedsRefresh(status) : false;
335
- }
336
- finally {
337
- registry.close();
338
- }
339
- }));
340
- if (!needsRefresh) {
341
- return;
342
- }
343
- await timings.time("auto_update", () => withHomeWriteLock(location.home, "context.refresh", async () => {
344
- const existing = readCollectionInfo(location.home, ANONYMOUS_COLLECTION_NAME);
345
- if (!existing) {
346
- return;
347
- }
348
- const stillNeedsRefresh = await timings.time("refresh_status_scan", () => collectionNeedsRefresh(location.home, ANONYMOUS_COLLECTION_NAME));
349
- if (!stillNeedsRefresh) {
334
+ async refreshWorkspaceIndexForContext(location, options, timings) {
335
+ const daemonWritePermit = assertDaemonWriteAllowed(location.root, this.options.daemonInstanceToken);
336
+ try {
337
+ const needsRefresh = await timings.time("status_scan", () => withHomeReadLock(location.home, "context.status", async () => {
338
+ const manifest = readWorkspaceManifest(location.home);
339
+ const status = manifest
340
+ ? await workspaceIndexStatus(manifest, location)
341
+ : null;
342
+ return indexStatusNeedsRefresh(status);
343
+ }));
344
+ if (!needsRefresh) {
350
345
  return;
351
346
  }
352
- const embeddingModel = this.embeddingModelForIndex(existing, location.home, "context.refresh");
353
- assertCollectionEmbeddingMatchesCurrentModel(existing, embeddingModel, "zg index --rebuild");
354
- const registry = new CollectionRegistry(location.home, embeddingModel);
355
- try {
356
- const result = await registry.open(ANONYMOUS_COLLECTION_NAME).index({
357
- embeddingConcurrency: options.embeddingConcurrency,
358
- onProgress: options.onAutoUpdateProgress,
359
- });
360
- timings.addEntries(result.timings, "auto_update_");
361
- }
362
- finally {
363
- registry.close();
364
- }
365
- }));
366
- }
367
- async contextFromNamedCollection(request, options, timings) {
368
- const collectionName = options.collection;
369
- return await withHomeReadLock(serviceHome(this.options), "collection.context", async () => {
370
- const registry = this.createRegistry(true, undefined);
371
- try {
372
- const info = registry.get(collectionName);
373
- if (!info) {
374
- throw new EngineError("Collection not found", {
375
- code: "ZVEC_GREP.ENGINE.COLLECTION.NOT_FOUND",
376
- context: errorDetails([collectionDetail(collectionName)]),
377
- });
347
+ await timings.time("auto_update", () => withHomeWriteLock(location.home, "context.refresh", async () => {
348
+ const existing = readWorkspaceManifest(location.home);
349
+ if (!existing) {
350
+ return;
351
+ }
352
+ const stillNeedsRefresh = await timings.time("refresh_status_scan", () => workspaceIndexNeedsRefresh(location));
353
+ if (!stillNeedsRefresh) {
354
+ return;
378
355
  }
379
- const collection = this.openCollectionForSearch(info, request, registry.home);
380
- const root = collection.info.rootPaths[0]?.absolutePath ??
381
- resolveZvecGrepRoot(options.root ?? this.root);
356
+ const workspaceRuntime = existing.embeddingRuntime;
357
+ const embeddingModel = this.embeddingModelForIndex(existing, "context.refresh", workspaceRuntime);
358
+ assertWorkspaceEndpointMatchesCurrentRuntime(existing, workspaceRuntime, effectiveEmbeddingRuntime(this.options, embeddingModel, workspaceRuntime), "zg index --endpoint <url> --rebuild");
359
+ assertWorkspaceEmbeddingMatchesCurrentModel(existing, embeddingModel, "zg index --rebuild");
360
+ const workspaceIndex = new WorkspaceIndex(existing, {
361
+ mode: "write",
362
+ embeddingModel,
363
+ });
382
364
  try {
383
- return await this.contextFromCollection({
384
- root,
385
- request,
386
- collection,
387
- anonymous: false,
388
- options: {
389
- ...options,
390
- },
391
- timings,
365
+ const result = await workspaceIndex.index({
366
+ embeddingConcurrency: options.embeddingConcurrency,
367
+ onProgress: options.onAutoUpdateProgress,
392
368
  });
369
+ timings.addEntries(result.timings, "auto_update_");
393
370
  }
394
371
  finally {
395
- collection.close();
372
+ workspaceIndex.close();
396
373
  }
397
- }
398
- finally {
399
- registry.close();
400
- }
401
- });
402
- }
403
- async contextFromCollection(input) {
404
- const searches = [];
405
- const groups = input.options.fuse
406
- ? [{ routes: input.request.routes }]
407
- : input.request.groups;
408
- const limit = contextGroupLimit(input.options.limit, groups.length);
409
- for (const group of groups) {
410
- const search = await input.collection.searchPlan({
411
- routes: group.routes,
412
- limit,
413
- trace: input.options.trace,
414
- preferSymbol: input.options.preferSymbol,
415
- symbolTypes: input.options.symbolTypes,
416
- includePaths: input.options.includePaths,
417
- excludePaths: input.options.excludePaths,
418
- globs: input.options.globs,
419
- insensitiveGlobs: input.options.insensitiveGlobs,
420
- fileTypes: input.options.fileTypes,
421
- excludedFileTypes: input.options.excludedFileTypes,
422
- modifiedAfter: input.options.modifiedAfter,
423
- modifiedBefore: input.options.modifiedBefore,
424
- });
425
- input.timings.addEntries(search.timings);
426
- searches.push(search);
374
+ }));
375
+ }
376
+ finally {
377
+ daemonWritePermit?.release();
427
378
  }
428
- const items = dedupeAndRerankContextItems(searches.flatMap((search) => searchPlanToContextItems(search, input.root)));
429
- return {
430
- query: input.request.displayQuery,
431
- root: input.root,
432
- source: "index",
433
- coverage: "ranked_sample",
434
- collection: {
435
- id: input.collection.info.id,
436
- name: input.collection.info.name,
437
- path: input.collection.info.path,
438
- anonymous: input.anonymous,
439
- },
440
- items,
441
- diagnostics: {
442
- emptyReason: items.length === 0 ? "no_matches" : undefined,
443
- index: {
444
- hitsReturned: items.length,
445
- routes: searches.flatMap((search) => search.plan.routes),
446
- },
447
- },
448
- };
379
+ }
380
+ async contextFromWriterWorkspaceIndex(root, workspaceIndex, options) {
381
+ return await this.withEmbeddingModelOperation(async () => {
382
+ const timings = new TimingCollector();
383
+ const request = normalizeContextRequest(options);
384
+ const result = await timings.time("total", () => contextFromOpenWorkspaceIndex({
385
+ root,
386
+ request,
387
+ workspaceIndex,
388
+ options: { ...options, autoUpdate: false },
389
+ timings,
390
+ }));
391
+ return withContextTimings(result, timings);
392
+ });
449
393
  }
450
394
  async contextFromRg(root, request, options, timings) {
451
395
  let rgResult;
@@ -479,7 +423,7 @@ class ZvecGrepService {
479
423
  cause,
480
424
  });
481
425
  }
482
- const structuralEnrichment = await timings.time("structure_enrichment", () => enrichLexicalItemsWithStructure(root, rgResult.items));
426
+ const structuralEnrichment = await timings.time("structure_enrichment", () => enrichLexicalItemsWithStructure(root, rgResult.items, undefined, options.maxFileSizeBytes));
483
427
  const items = dedupeAndRerankContextItems(structuralEnrichment.items);
484
428
  const emptyReason = items.length === 0 ? rgEmptyReason(rgResult.diagnostics) : undefined;
485
429
  return {
@@ -497,60 +441,69 @@ class ZvecGrepService {
497
441
  },
498
442
  };
499
443
  }
500
- createRegistry(readOnly, embeddingModel = this.embeddingModel) {
501
- return new CollectionRegistry(this.options.home, embeddingModel, readOnly);
502
- }
503
- openCollectionForSearch(info, request, registryHome) {
504
- return new Collection(info, this.embeddingModelForSearch(indexedEmbeddingSchema(info), request, registryHome), true, join(registryHome, "files.zvec"));
444
+ openWorkspaceIndexForSearch(info, request, workspaceRuntime) {
445
+ return new WorkspaceIndex(info, {
446
+ mode: "read",
447
+ embeddingModel: this.embeddingModelForSearch(indexedEmbeddingSchema(info), request, workspaceRuntime, info),
448
+ });
505
449
  }
506
- embeddingModelForSearch(schema, request, registryHome) {
450
+ embeddingModelForSearch(schema, request, workspaceRuntime, info) {
507
451
  if (!request.routes.some((route) => route.mode === "vector")) {
508
452
  return undefined;
509
453
  }
454
+ const identity = {
455
+ provider: schema.provider,
456
+ name: schema.model,
457
+ };
458
+ const effectiveRuntime = resolveEmbeddingRuntimeOptions(embeddingModelReference(identity), this.options, workspaceRuntime, readGlobalConfig());
459
+ assertSearchEndpointMatchesWorkspace(info, workspaceRuntime, effectiveRuntime);
510
460
  if (this.embeddingModel) {
511
461
  return this.embeddingModel;
512
462
  }
513
- return this.recoverEmbeddingModel(schema, registryHome);
463
+ return this.recoverEmbeddingModel(schema, workspaceRuntime);
514
464
  }
515
- embeddingModelForIndex(existing, registryHome, operation) {
516
- if (isCollectionIndexed(existing) &&
465
+ embeddingModelForIndex(existing, operation, workspaceRuntime = {}) {
466
+ if (isWorkspaceIndexed(existing) &&
517
467
  !this.embeddingModel &&
518
468
  !this.options.embedding) {
519
- return this.recoverEmbeddingModel(existing.embedding, registryHome);
469
+ return this.recoverEmbeddingModel(existing.embedding, workspaceRuntime);
520
470
  }
521
- const reference = this.options.embedding ??
522
- (this.options.defaultEmbedding === true
523
- ? DEFAULT_LOCAL_EMBEDDING
524
- : undefined);
471
+ const config = readGlobalConfig();
472
+ const reference = resolveEmbeddingReference({
473
+ explicit: this.options.embedding,
474
+ globalDefault: config.defaults?.embedding,
475
+ fallback: DEFAULT_LOCAL_EMBEDDING,
476
+ });
477
+ const referenceIdentity = reference
478
+ ? parseEmbeddingModelReference(reference)
479
+ : undefined;
480
+ const selectedWorkspaceRuntime = isWorkspaceIndexed(existing) &&
481
+ referenceIdentity &&
482
+ existing.embedding.provider !== referenceIdentity.provider
483
+ ? {}
484
+ : workspaceRuntime;
525
485
  return (this.embeddingModel ??
526
486
  (reference
527
- ? this.embeddingModelFromReference(reference, registryHome)
487
+ ? this.embeddingModelFromReference(reference, config, selectedWorkspaceRuntime)
528
488
  : undefined) ??
529
- this.configuredEmbeddingModel(registryHome) ??
530
489
  this.requireEmbeddingModel(operation));
531
490
  }
532
- recoverEmbeddingModel(schema, registryHome) {
491
+ recoverEmbeddingModel(schema, workspaceRuntime = {}) {
533
492
  const config = readGlobalConfig();
534
- const options = providerOptions(this.options, this.root, registryHome, schema.provider, config);
535
- const key = `${schema.provider}/${schema.model}/${providerOptionsFingerprint(options)}`;
536
- return this.cachedEmbeddingModel(key, () => createEmbeddingModel({
493
+ const identity = {
537
494
  provider: schema.provider,
538
- model: schema.model,
539
- }, options));
540
- }
541
- configuredEmbeddingModel(registryHome) {
542
- const config = readGlobalConfig();
543
- const reference = config.defaults?.embedding;
544
- if (!reference) {
545
- return undefined;
546
- }
547
- return this.embeddingModelFromReference(reference, registryHome, config);
548
- }
549
- embeddingModelFromReference(reference, registryHome, config = readGlobalConfig()) {
550
- const provider = providerFromReference(reference);
551
- const options = providerOptions(this.options, this.root, registryHome, provider, config);
495
+ name: schema.model,
496
+ };
497
+ const reference = embeddingModelReference(identity);
498
+ const options = providerOptions(this.options, identity, config, workspaceRuntime);
499
+ const key = `${reference}/${providerOptionsFingerprint(options)}`;
500
+ return this.cachedEmbeddingModel(key, () => createServiceEmbeddingModel(reference, options, this.options));
501
+ }
502
+ embeddingModelFromReference(reference, config = readGlobalConfig(), workspaceRuntime = {}) {
503
+ const identity = parseEmbeddingModelReference(reference);
504
+ const options = providerOptions(this.options, identity, config, workspaceRuntime);
552
505
  const key = `configured/${reference}/${providerOptionsFingerprint(options)}`;
553
- return this.cachedEmbeddingModel(key, () => createEmbeddingModelFromReference(reference, options));
506
+ return this.cachedEmbeddingModel(key, () => createServiceEmbeddingModel(reference, options, this.options));
554
507
  }
555
508
  cachedEmbeddingModel(key, create) {
556
509
  const cached = this.recoveredEmbeddingModels.get(key);
@@ -603,7 +556,7 @@ class ZvecGrepService {
603
556
  context: errorDetails([
604
557
  detail("operation", operation),
605
558
  detail("hint", `Pass "--embedding <model>", set ZVEC_GREP_EMBEDDING, or configure defaults.embedding in ${globalConfigPath()}. Existing indexes can run "zg index" without --embedding to reuse the stored schema.`),
606
- detail("examples", "local/embeddinggemma-300m, qwen/text-embedding-v4"),
559
+ detail("examples", "local/potion-code-16m-v2, qwen/text-embedding-v4"),
607
560
  ]),
608
561
  });
609
562
  }
@@ -617,6 +570,73 @@ class ZvecGrepService {
617
570
  }
618
571
  }
619
572
  }
573
+ async function contextFromOpenWorkspaceIndex(input) {
574
+ const searches = [];
575
+ const groups = input.options.fuse
576
+ ? [
577
+ {
578
+ id: "Q1",
579
+ query: input.request.displayQuery,
580
+ role: input.request.groups.some((group) => group.role === "primary")
581
+ ? "primary"
582
+ : "supplemental",
583
+ routes: input.request.routes,
584
+ },
585
+ ]
586
+ : input.request.groups;
587
+ const limit = contextGroupLimit(input.options.limit, groups.length);
588
+ for (const group of groups) {
589
+ const search = await input.workspaceIndex.searchPlan({
590
+ routes: group.routes,
591
+ limit,
592
+ trace: input.options.trace,
593
+ preferSymbol: input.options.preferSymbol,
594
+ symbolTypes: input.options.symbolTypes,
595
+ includePaths: input.options.includePaths,
596
+ excludePaths: input.options.excludePaths,
597
+ globs: input.options.globs,
598
+ insensitiveGlobs: input.options.insensitiveGlobs,
599
+ fileTypes: input.options.fileTypes,
600
+ excludedFileTypes: input.options.excludedFileTypes,
601
+ modifiedAfter: input.options.modifiedAfter,
602
+ modifiedBefore: input.options.modifiedBefore,
603
+ });
604
+ input.timings.addEntries(search.timings);
605
+ searches.push(search);
606
+ }
607
+ const groupItems = searches.map((search, index) => searchPlanToContextItems(search, input.root, groups[index]));
608
+ const items = selectAndRankContextItems(groupItems.flat(), groups.filter((group) => group.role === "primary").map((group) => group.id));
609
+ return {
610
+ query: input.request.displayQuery,
611
+ root: input.root,
612
+ source: "index",
613
+ coverage: "ranked_sample",
614
+ workspaceIndex: {
615
+ id: input.workspaceIndex.info.id,
616
+ name: input.workspaceIndex.info.name,
617
+ path: input.workspaceIndex.info.path,
618
+ },
619
+ items,
620
+ groupResults: groups.map((group, index) => ({
621
+ id: group.id,
622
+ query: group.query,
623
+ role: group.role,
624
+ items: groupItems[index] ?? [],
625
+ })),
626
+ diagnostics: {
627
+ emptyReason: items.length === 0 ? "no_matches" : undefined,
628
+ index: {
629
+ hitsReturned: items.length,
630
+ queryGroups: groups.map((group) => ({
631
+ id: group.id,
632
+ query: group.query,
633
+ role: group.role,
634
+ })),
635
+ routes: searches.flatMap((search) => search.plan.routes),
636
+ },
637
+ },
638
+ };
639
+ }
620
640
  async function withHomeReadLock(home, operation, task) {
621
641
  const lock = acquireHomeLock(home, "read", operation);
622
642
  try {
@@ -635,22 +655,12 @@ async function withHomeWriteLock(home, operation, task) {
635
655
  lock.release();
636
656
  }
637
657
  }
638
- async function collectionNeedsRefresh(home, collectionName) {
639
- const registry = new CollectionRegistry(home, undefined, true);
640
- try {
641
- const status = await registry.status(collectionName);
642
- return status ? collectionIndexStatusNeedsRefresh(status) : false;
643
- }
644
- finally {
645
- registry.close();
646
- }
647
- }
648
- function collectionIndexStatusNeedsRefresh(status) {
649
- return (status.filesAdded > 0 ||
650
- status.filesModified > 0 ||
651
- status.filesDeleted > 0 ||
652
- status.filesPending > 0 ||
653
- status.filesFailed > 0);
658
+ async function workspaceIndexNeedsRefresh(location) {
659
+ const manifest = readWorkspaceManifest(location.home);
660
+ const status = manifest
661
+ ? await workspaceIndexStatus(manifest, location)
662
+ : null;
663
+ return indexStatusNeedsRefresh(status);
654
664
  }
655
665
  function acquireHomeLock(home, mode, operation) {
656
666
  return acquireReadWriteLock(homeLockPath(home), mode, { operation });
@@ -658,10 +668,10 @@ function acquireHomeLock(home, mode, operation) {
658
668
  function assertHomeUnlocked(home, operation) {
659
669
  assertNoWriteLock(homeLockPath(home), operation);
660
670
  }
661
- function assertNearestAnonymousHomeUnlocked(start, operation) {
671
+ function assertNearestWorkspaceHomeUnlocked(start, operation) {
662
672
  let current = resolve(start);
663
673
  while (true) {
664
- assertHomeUnlocked(anonymousHome(current), operation);
674
+ assertHomeUnlocked(workspaceHome(current), operation);
665
675
  const parent = dirname(current);
666
676
  if (parent === current) {
667
677
  return;
@@ -672,9 +682,6 @@ function assertNearestAnonymousHomeUnlocked(start, operation) {
672
682
  function homeLockPath(home) {
673
683
  return join(home, "locks", "home");
674
684
  }
675
- function serviceHome(options) {
676
- return options.home ?? defaultHome();
677
- }
678
685
  function rgEmptyReason(diagnostics) {
679
686
  return diagnostics.missingPaths &&
680
687
  diagnostics.missingPaths.length > 0 &&
@@ -696,36 +703,28 @@ function withContextTimings(result, timings) {
696
703
  },
697
704
  };
698
705
  }
699
- function findNearestAnonymousCollection(start) {
700
- let current = resolve(start);
701
- while (true) {
702
- const location = anonymousIndexLocation(current);
703
- const info = readCollectionInfo(location.home, ANONYMOUS_COLLECTION_NAME);
704
- if (info) {
705
- return { location, info };
706
- }
707
- const parent = dirname(current);
708
- if (parent === current) {
709
- return null;
710
- }
711
- current = parent;
712
- }
706
+ function findNearestWorkspaceIndex(start) {
707
+ const location = findNearestWorkspace(start);
708
+ if (!location)
709
+ return null;
710
+ const info = readWorkspaceManifest(location.home);
711
+ return info ? { location, info } : null;
713
712
  }
714
- function anonymousInfoSuggestion(collection) {
715
- if (!collection) {
713
+ function workspaceInfoSuggestion(workspaceIndex) {
714
+ if (!workspaceIndex) {
716
715
  return "zg index or zg query --rg";
717
716
  }
718
- if (collection.indexPolicy === "disabled") {
717
+ if (workspaceIndex.indexPolicy === "disabled") {
719
718
  return "zg query --rg";
720
719
  }
721
- if (!isCollectionIndexed(collection)) {
720
+ if (!isWorkspaceIndexed(workspaceIndex)) {
722
721
  return "zg index";
723
722
  }
724
723
  return undefined;
725
724
  }
726
- function anonymousIndexMissingError(root, policy) {
727
- return new EngineError("No anonymous zvec-grep index found for this workspace", {
728
- code: "ZVEC_GREP.ENGINE.SERVICE.ANONYMOUS_INDEX_NOT_FOUND",
725
+ function workspaceIndexMissingError(root, policy) {
726
+ return new EngineError("No zvec-grep index found for this workspace", {
727
+ code: "ZVEC_GREP.ENGINE.SERVICE.WORKSPACE_INDEX_NOT_FOUND",
729
728
  context: errorDetails([
730
729
  detail("root", root),
731
730
  detail("policy", policy),
@@ -738,9 +737,9 @@ function anonymousIndexMissingError(root, policy) {
738
737
  ]),
739
738
  });
740
739
  }
741
- function anonymousIndexDisabledError(root) {
742
- return new EngineError("Anonymous zvec-grep index is disabled for this workspace", {
743
- code: "ZVEC_GREP.ENGINE.SERVICE.ANONYMOUS_INDEX_DISABLED",
740
+ function workspaceIndexDisabledError(root) {
741
+ return new EngineError("The zvec-grep index is disabled for this workspace", {
742
+ code: "ZVEC_GREP.ENGINE.SERVICE.WORKSPACE_INDEX_DISABLED",
744
743
  context: errorDetails([
745
744
  detail("root", root),
746
745
  detail("policy", "disabled"),
@@ -836,117 +835,216 @@ function applyRootPathOverrides(rootPath, options) {
836
835
  follow: options.follow ?? normalized.follow,
837
836
  };
838
837
  }
839
- function readCollectionInfo(home, name) {
840
- const registry = new CollectionRegistry(home, undefined, true);
838
+ function prepareWorkspaceManifest(location, existing, rootPaths, embeddingModel, embeddingRuntime) {
839
+ const now = Date.now();
840
+ return {
841
+ manifestVersion: CURRENT_MANIFEST_VERSION,
842
+ id: existing?.id ?? randomUUID(),
843
+ name: existing?.name ?? workspaceDisplayName(location.root),
844
+ path: location.home,
845
+ rootPaths: validateRootPaths(rootPaths),
846
+ indexPolicy: "enabled",
847
+ embedding: currentEmbeddingSchema(embeddingModel),
848
+ indexVersion: CURRENT_INDEX_VERSION,
849
+ createdTime: existing?.createdTime ?? now,
850
+ updatedTime: now,
851
+ embeddingRuntime,
852
+ };
853
+ }
854
+ function currentEmbeddingSchema(embeddingModel) {
855
+ return {
856
+ provider: embeddingModel.info.provider,
857
+ model: embeddingModel.info.name,
858
+ dimension: embeddingModel.info.dimension,
859
+ metric: embeddingModel.info.metric,
860
+ };
861
+ }
862
+ function workspaceDisplayName(root) {
863
+ return basename(normalizePath(root)) || "workspace";
864
+ }
865
+ async function workspaceIndexStatus(info, location) {
866
+ if (info.indexPolicy === "disabled" ||
867
+ !isWorkspaceIndexed(info) ||
868
+ !hasWorkspaceIndex(location)) {
869
+ return null;
870
+ }
871
+ const workspaceIndex = new WorkspaceIndex(info, { mode: "read" });
841
872
  try {
842
- return registry.get(name);
873
+ return await workspaceIndex.status();
843
874
  }
844
875
  finally {
845
- registry.close();
876
+ workspaceIndex.close();
846
877
  }
847
878
  }
848
879
  function indexedEmbeddingSchema(info) {
849
- if (isCollectionIndexed(info)) {
880
+ if (isWorkspaceIndexed(info)) {
850
881
  return info.embedding;
851
882
  }
852
883
  throw new EngineError("zvec-grep index has not been built", {
853
884
  code: "ZVEC_GREP.ENGINE.SERVICE.INDEX_MISSING",
854
885
  context: errorDetails([
855
- collectionDetail(info.name),
886
+ workspaceIndexDetail(info.name),
856
887
  detail("hint", "Run zg index to build this index."),
857
888
  ]),
858
889
  });
859
890
  }
860
- function providerOptions(options, root, registryHome, provider, config = readGlobalConfig()) {
861
- const providerConfig = provider ? config.providers?.[provider] : undefined;
891
+ function providerOptions(options, identity, config = readGlobalConfig(), workspaceRuntime = {}) {
892
+ const reference = embeddingModelReference(identity);
893
+ const runtime = resolveEmbeddingRuntimeOptions(reference, options, workspaceRuntime, config);
862
894
  return {
863
- apiKey: options.apiKey ?? providerConfig?.apiKey ?? "",
864
- endpoint: options.endpoint ?? providerConfig?.endpoint,
895
+ apiKey: runtime.apiKey,
896
+ endpoint: runtime.endpoint,
865
897
  modelCacheDir: options.modelCacheDir ??
866
- process.env.ZVEC_GREP_MODEL_CACHE ??
867
898
  config.defaults?.modelCacheDir ??
868
- modelCacheDir(options, root, registryHome),
869
- llamaGpu: options.llamaGpu ?? config.defaults?.llamaGpu,
870
- embeddingParallelism: options.embeddingParallelism ?? config.defaults?.embeddingParallelism,
899
+ process.env.ZVEC_GREP_MODEL_CACHE ??
900
+ join(options.home ?? defaultHome(), "models"),
901
+ device: runtime.device,
871
902
  };
872
903
  }
873
- function providerFromReference(reference) {
904
+ function createServiceEmbeddingModel(reference, modelOptions, serviceOptions) {
905
+ const model = createEmbeddingModel(reference, modelOptions);
906
+ if (model.info.provider !== "qwen") {
907
+ return model;
908
+ }
909
+ const endpoint = model.info.endpoint;
910
+ if (endpoint === undefined) {
911
+ throw new EngineError("Remote embedding model did not provide an endpoint", {
912
+ code: "ZVEC_GREP.ENGINE.MODELS.REMOTE_ENDPOINT_NOT_IMPLEMENTED",
913
+ context: `model=${model.info.reference}`,
914
+ });
915
+ }
916
+ const authorize = remoteEmbeddingAuthorizationGuard({
917
+ store: new RemoteEmbeddingAuthorizationStore({
918
+ signingKeyPath: serviceOptions.authorizationSigningKeyPath,
919
+ }),
920
+ });
921
+ const authorizedModel = {
922
+ info: model.info,
923
+ async embed(contents, embedOptions) {
924
+ await authorize({
925
+ provider: model.info.provider,
926
+ model: model.info.name,
927
+ endpoint,
928
+ purpose: embedOptions?.purpose ?? EmbeddingPurpose.Document,
929
+ contentKinds: contents.map((content) => content.kind),
930
+ contentCount: contents.length,
931
+ });
932
+ return await model.embed(contents, embedOptions);
933
+ },
934
+ dispose: () => model.dispose(),
935
+ };
936
+ return authorizedModel;
937
+ }
938
+ function parseEmbeddingModelReference(reference) {
874
939
  const separator = reference.indexOf("/");
875
- return separator > 0 ? reference.slice(0, separator) : undefined;
940
+ if (separator <= 0 || separator === reference.length - 1) {
941
+ throw new EngineError("Embedding model reference is invalid", {
942
+ code: "ZVEC_GREP.ENGINE.MODELS.EMBEDDING_INVALID_REFERENCE",
943
+ context: `reference=${reference}`,
944
+ });
945
+ }
946
+ return {
947
+ provider: reference.slice(0, separator),
948
+ name: reference.slice(separator + 1),
949
+ };
950
+ }
951
+ function embeddingModelReference(identity) {
952
+ return `${identity.provider}/${identity.name}`;
876
953
  }
877
954
  function providerOptionsFingerprint(options) {
878
- return createHash("sha256")
879
- .update(JSON.stringify([
880
- options.apiKey,
881
- options.endpoint,
882
- options.modelCacheDir,
883
- options.llamaGpu,
884
- options.embeddingParallelism,
885
- ]))
955
+ const publicOptionsFingerprint = createHash("sha256")
956
+ .update(JSON.stringify([options.endpoint, options.modelCacheDir, options.device]))
886
957
  .digest("hex");
958
+ return `${providerApiKeyIdentity(options.apiKey)}:${publicOptionsFingerprint}`;
959
+ }
960
+ function providerApiKeyIdentity(apiKey) {
961
+ return createHmac("sha256", PROVIDER_API_KEY_IDENTITY_SECRET)
962
+ .update(apiKey)
963
+ .digest("hex");
964
+ }
965
+ function effectiveEmbeddingRuntime(options, model, workspaceRuntime) {
966
+ const resolved = resolveEmbeddingRuntimeOptions(model.info.reference, options, workspaceRuntime, readGlobalConfig());
967
+ return {
968
+ apiKey: resolved.apiKey,
969
+ ...((model.info.endpoint ?? resolved.endpoint)
970
+ ? { endpoint: model.info.endpoint ?? resolved.endpoint }
971
+ : {}),
972
+ ...(model.info.provider === "local"
973
+ ? { device: resolved.device ?? "auto" }
974
+ : {}),
975
+ };
976
+ }
977
+ function runtimeForModelProvider(existing, model, workspaceRuntime) {
978
+ return isWorkspaceIndexed(existing) &&
979
+ existing.embedding.provider !== model.info.provider
980
+ ? {}
981
+ : workspaceRuntime;
982
+ }
983
+ function embeddingRuntimeAfterIndex(existing, existingRuntime, model, effectiveRuntime, explicit) {
984
+ const sameProvider = isWorkspaceIndexed(existing) &&
985
+ existing.embedding.provider === model.info.provider;
986
+ const apiKey = model.info.provider === "local"
987
+ ? undefined
988
+ : (explicit.apiKey ??
989
+ (sameProvider ? existingRuntime.apiKey : undefined));
990
+ return {
991
+ ...(apiKey !== undefined ? { apiKey } : {}),
992
+ ...(model.info.provider !== "local" &&
993
+ effectiveRuntime.endpoint !== undefined
994
+ ? { endpoint: effectiveRuntime.endpoint }
995
+ : {}),
996
+ ...(model.info.provider === "local"
997
+ ? { device: effectiveRuntime.device ?? "auto" }
998
+ : {}),
999
+ };
1000
+ }
1001
+ function assertWorkspaceEndpointMatchesCurrentRuntime(info, workspaceRuntime, effectiveRuntime, rebuildCommand) {
1002
+ if (!isWorkspaceIndexed(info) ||
1003
+ workspaceRuntime.endpoint === effectiveRuntime.endpoint) {
1004
+ return;
1005
+ }
1006
+ throw new EngineError("Existing zvec-grep index uses a different embedding endpoint", {
1007
+ code: "ZVEC_GREP.ENGINE.SERVICE.EMBEDDING_ENDPOINT_CHANGE_REQUIRES_REBUILD",
1008
+ context: errorDetails([
1009
+ workspaceIndexDetail(info.name),
1010
+ detail("hint", `Run "${rebuildCommand}" to rebuild this index with the requested endpoint.`),
1011
+ ]),
1012
+ });
1013
+ }
1014
+ function assertSearchEndpointMatchesWorkspace(info, workspaceRuntime, effectiveRuntime) {
1015
+ if (workspaceRuntime.endpoint === effectiveRuntime.endpoint) {
1016
+ return;
1017
+ }
1018
+ throw new EngineError("Search cannot override the embedding endpoint of an existing index", {
1019
+ code: "ZVEC_GREP.ENGINE.SERVICE.SEARCH_ENDPOINT_CHANGE_REQUIRES_REBUILD",
1020
+ context: errorDetails([
1021
+ workspaceIndexDetail(info.name),
1022
+ detail("hint", 'Run "zg index --endpoint <url> --rebuild" before searching with this endpoint.'),
1023
+ ]),
1024
+ });
887
1025
  }
888
- function assertCollectionEmbeddingMatchesCurrentModel(info, model, rebuildCommand) {
889
- if (!isCollectionIndexed(info)) {
1026
+ function assertWorkspaceEmbeddingMatchesCurrentModel(info, model, rebuildCommand) {
1027
+ if (!isWorkspaceIndexed(info)) {
890
1028
  return;
891
1029
  }
892
1030
  const expected = info.embedding;
893
- const changed = expected.provider !== model.ref.provider ||
894
- expected.model !== model.ref.model ||
895
- expected.dimension !== model.dimension ||
896
- expected.metric !== model.metric;
1031
+ const changed = expected.provider !== model.info.provider ||
1032
+ expected.model !== model.info.name ||
1033
+ expected.dimension !== model.info.dimension ||
1034
+ expected.metric !== model.info.metric;
897
1035
  if (!changed) {
898
1036
  return;
899
1037
  }
900
1038
  throw new EngineError("Existing zvec-grep index uses a different embedding model", {
901
1039
  code: "ZVEC_GREP.ENGINE.SERVICE.EMBEDDING_SCHEMA_CHANGE_REQUIRES_REBUILD",
902
1040
  context: errorDetails([
903
- collectionDetail(info.name),
1041
+ workspaceIndexDetail(info.name),
904
1042
  detail("existing", `${expected.provider}/${expected.model}`),
905
- detail("requested", `${model.ref.provider}/${model.ref.model}`),
1043
+ detail("requested", model.info.reference),
906
1044
  detail("hint", `Run "${rebuildCommand}" to rebuild this index with the requested embedding model.`),
907
1045
  ]),
908
1046
  });
909
1047
  }
910
- function modelCacheDir(options, root, registryHome) {
911
- if (options.modelCacheDir || process.env.ZVEC_GREP_MODEL_CACHE) {
912
- return options.modelCacheDir ?? process.env.ZVEC_GREP_MODEL_CACHE;
913
- }
914
- const globalCache = join(options.home ?? defaultHome(), "models");
915
- const legacyCaches = [
916
- registryHome ? join(registryHome, "models") : undefined,
917
- join(anonymousHome(root), "models"),
918
- ];
919
- const globalHasModel = directoryHasGguf(globalCache);
920
- for (const legacyCache of legacyCaches) {
921
- if (legacyCache && directoryHasGguf(legacyCache) && !globalHasModel) {
922
- return legacyCache;
923
- }
924
- }
925
- if (globalHasModel || canWriteCacheDirectory(globalCache)) {
926
- return globalCache;
927
- }
928
- return join(anonymousHome(root), "models");
929
- }
930
- function directoryHasGguf(path) {
931
- try {
932
- return readdirSync(path, { withFileTypes: true }).some((entry) => entry.isFile() && entry.name.endsWith(".gguf"));
933
- }
934
- catch {
935
- return false;
936
- }
937
- }
938
- function canWriteCacheDirectory(path) {
939
- const probe = join(path, `.zvec-grep-write-test-${Date.now()}-${Math.random().toString(16).slice(2)}`);
940
- try {
941
- mkdirSync(path, { recursive: true });
942
- writeFileSync(probe, "");
943
- unlinkSync(probe);
944
- return true;
945
- }
946
- catch {
947
- return false;
948
- }
949
- }
950
1048
  function contextGroupLimit(limit, groupCount) {
951
1049
  if (limit !== undefined) {
952
1050
  return limit;
@@ -1021,18 +1119,24 @@ function normalizeContextRoutes(routes) {
1021
1119
  }
1022
1120
  function contextGroups(primaryQueries, extraRoutes) {
1023
1121
  return [
1024
- ...primaryQueries.map((query) => ({
1122
+ ...primaryQueries.map((query, index) => ({
1123
+ id: `Q${index + 1}`,
1124
+ query,
1125
+ role: "primary",
1025
1126
  routes: [
1026
1127
  { mode: "fts", query },
1027
1128
  { mode: "vector", query },
1028
1129
  ],
1029
1130
  })),
1030
- ...extraRoutes.map((route) => ({
1131
+ ...extraRoutes.map((route, index) => ({
1132
+ id: `Q${primaryQueries.length + index + 1}`,
1133
+ query: route.query,
1134
+ role: "supplemental",
1031
1135
  routes: [route],
1032
1136
  })),
1033
1137
  ];
1034
1138
  }
1035
- function searchPlanToContextItems(result, root) {
1139
+ function searchPlanToContextItems(result, root, group) {
1036
1140
  return result.hits.map((hit) => {
1037
1141
  const target = contextItemTarget(hit);
1038
1142
  return {
@@ -1054,9 +1158,75 @@ function searchPlanToContextItems(result, root) {
1054
1158
  metadata: hit.entity.metadata,
1055
1159
  entityId: hit.entity.id,
1056
1160
  trace: hit.trace,
1161
+ queryGroups: [
1162
+ {
1163
+ id: group.id,
1164
+ query: group.query,
1165
+ role: group.role,
1166
+ rank: hit.rank,
1167
+ matchedBy: hit.matchedBy,
1168
+ },
1169
+ ],
1057
1170
  };
1058
1171
  });
1059
1172
  }
1173
+ const DEFAULT_CONTEXT_PRIORITY_LIMIT = 6;
1174
+ const CONTEXT_GROUP_RRF_K = 60;
1175
+ function selectAndRankContextItems(items, coverageGroupIds) {
1176
+ const deduped = new Map();
1177
+ for (const item of items) {
1178
+ const key = contextItemDedupeKey(item);
1179
+ const existing = deduped.get(key);
1180
+ if (existing) {
1181
+ existing.queryGroups = mergeContextQueryGroupMatches(existing.queryGroups ?? [], item.queryGroups ?? []);
1182
+ existing.matchedBy = mergedContextMatchedBy(existing.queryGroups);
1183
+ continue;
1184
+ }
1185
+ deduped.set(key, { ...item });
1186
+ }
1187
+ const candidates = [...deduped.values()];
1188
+ const globallyRanked = [...candidates].sort(compareContextGlobalRank);
1189
+ const selected = new Set();
1190
+ const prioritized = [];
1191
+ for (const groupId of coverageGroupIds) {
1192
+ if (prioritized.length >= DEFAULT_CONTEXT_PRIORITY_LIMIT) {
1193
+ break;
1194
+ }
1195
+ const candidate = candidates
1196
+ .filter((item) => !selected.has(contextItemDedupeKey(item)))
1197
+ .filter((item) => contextQueryGroupMatch(item, groupId) !== undefined)
1198
+ .sort((left, right) => compareContextGroupRank(left, right, groupId))[0];
1199
+ if (!candidate) {
1200
+ continue;
1201
+ }
1202
+ selected.add(contextItemDedupeKey(candidate));
1203
+ prioritized.push({
1204
+ ...candidate,
1205
+ selectionReason: "coverage",
1206
+ coverageGroup: groupId,
1207
+ });
1208
+ }
1209
+ for (const candidate of globallyRanked) {
1210
+ if (prioritized.length >= DEFAULT_CONTEXT_PRIORITY_LIMIT) {
1211
+ break;
1212
+ }
1213
+ const key = contextItemDedupeKey(candidate);
1214
+ if (selected.has(key)) {
1215
+ continue;
1216
+ }
1217
+ selected.add(key);
1218
+ prioritized.push({
1219
+ ...candidate,
1220
+ selectionReason: "global_fill",
1221
+ coverageGroup: undefined,
1222
+ });
1223
+ }
1224
+ const unprioritized = globallyRanked.filter((item) => !selected.has(contextItemDedupeKey(item)));
1225
+ return [...prioritized, ...unprioritized].map((item, index) => ({
1226
+ ...item,
1227
+ rank: index + 1,
1228
+ }));
1229
+ }
1060
1230
  function dedupeAndRerankContextItems(items) {
1061
1231
  const seen = new Set();
1062
1232
  const deduped = [];
@@ -1066,13 +1236,52 @@ function dedupeAndRerankContextItems(items) {
1066
1236
  continue;
1067
1237
  }
1068
1238
  seen.add(key);
1069
- deduped.push({
1070
- ...item,
1071
- rank: deduped.length + 1,
1072
- });
1239
+ deduped.push({ ...item, rank: deduped.length + 1 });
1073
1240
  }
1074
1241
  return deduped;
1075
1242
  }
1243
+ function mergeContextQueryGroupMatches(left, right) {
1244
+ const matches = new Map(left.map((match) => [match.id, match]));
1245
+ for (const match of right) {
1246
+ const existing = matches.get(match.id);
1247
+ if (!existing || match.rank < existing.rank) {
1248
+ matches.set(match.id, match);
1249
+ }
1250
+ }
1251
+ return [...matches.values()].sort((a, b) => contextGroupNumber(a.id) - contextGroupNumber(b.id));
1252
+ }
1253
+ function mergedContextMatchedBy(matches) {
1254
+ const hasFts = matches.some((match) => match.matchedBy === "fts" || match.matchedBy === "fts+vector");
1255
+ const hasVector = matches.some((match) => match.matchedBy === "vector" || match.matchedBy === "fts+vector");
1256
+ return hasFts && hasVector ? "fts+vector" : hasFts ? "fts" : "vector";
1257
+ }
1258
+ function compareContextGlobalRank(left, right) {
1259
+ return (contextGlobalRrfScore(right) - contextGlobalRrfScore(left) ||
1260
+ contextBestGroupRank(left) - contextBestGroupRank(right) ||
1261
+ contextFirstGroupNumber(left) - contextFirstGroupNumber(right) ||
1262
+ contextItemDedupeKey(left).localeCompare(contextItemDedupeKey(right)));
1263
+ }
1264
+ function contextGlobalRrfScore(item) {
1265
+ return (item.queryGroups ?? []).reduce((score, match) => score + 1 / (CONTEXT_GROUP_RRF_K + match.rank), 0);
1266
+ }
1267
+ function compareContextGroupRank(left, right, groupId) {
1268
+ return (contextQueryGroupMatch(left, groupId).rank -
1269
+ contextQueryGroupMatch(right, groupId).rank ||
1270
+ compareContextGlobalRank(left, right));
1271
+ }
1272
+ function contextQueryGroupMatch(item, groupId) {
1273
+ return item.queryGroups?.find((match) => match.id === groupId);
1274
+ }
1275
+ function contextBestGroupRank(item) {
1276
+ return Math.min(...(item.queryGroups ?? []).map((match) => match.rank));
1277
+ }
1278
+ function contextFirstGroupNumber(item) {
1279
+ return Math.min(...(item.queryGroups ?? []).map((match) => contextGroupNumber(match.id)));
1280
+ }
1281
+ function contextGroupNumber(id) {
1282
+ const value = Number.parseInt(id.slice(1), 10);
1283
+ return Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER;
1284
+ }
1076
1285
  function contextItemDedupeKey(item) {
1077
1286
  if (item.entityId) {
1078
1287
  return `entity:${item.entityId}`;