@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,12 +1,11 @@
1
- import { readFile } from "node:fs/promises";
2
- import { collectionDetail, detail, EngineError, errorDetails, isEngineError, } from "../../errors/index.js";
1
+ import { lstat, readFile } from "node:fs/promises";
2
+ import { workspaceIndexDetail, detail, EngineError, errorDetails, isEngineError, } from "../../errors.js";
3
3
  import { sha256Bytes } from "../../utils/hash.js";
4
+ import { normalizePath } from "../../utils/path.js";
4
5
  import { ConcurrentTiming, TimingCollector } from "../../utils/timing.js";
5
- import { ExtractorRegistry } from "../../extraction/index.js";
6
- import { scanRootPaths } from "./scanner/index.js";
7
- const DEFAULT_EMBEDDING_CONCURRENCY = 4;
8
- const DEFAULT_EMBEDDING_MIN_CONCURRENCY = 4;
9
- const DEFAULT_EMBEDDING_MAX_CONCURRENCY = 12;
6
+ import { extract, vectorContentForFragment, } from "../../extraction/index.js";
7
+ import { scanDirectoryPath, scanFilePath, scanRootPaths, } from "./scanner/index.js";
8
+ import { indexChunkOptions } from "./input-budget.js";
10
9
  const EMBEDDING_TRANSIENT_MAX_RETRIES = 3;
11
10
  const EMBEDDING_RATE_LIMIT_MAX_RETRIES = 6;
12
11
  const EMBEDDING_TRANSIENT_RETRY_BASE_DELAY_MS = 500;
@@ -15,33 +14,44 @@ const EMBEDDING_TRANSIENT_RETRY_MAX_DELAY_MS = 8000;
15
14
  const EMBEDDING_RATE_LIMIT_RETRY_MAX_DELAY_MS = 30000;
16
15
  const EMBEDDING_RETRY_JITTER_MS = 500;
17
16
  const EMBEDDING_SUCCESS_STREAK_MIN = 4;
18
- export async function indexCollection(ctx) {
17
+ export async function indexWorkspace(ctx) {
19
18
  try {
20
- return await indexCollectionUnchecked(ctx);
19
+ return await indexWorkspaceUnchecked(ctx);
21
20
  }
22
21
  catch (error) {
23
- throw toEngineError(error, "Indexing collection failed", {
24
- code: "ZVEC_GREP.ENGINE.INDEXING.COLLECTION_FAILED",
25
- context: collectionContext(ctx.collection),
22
+ throw toEngineError(error, "Indexing workspace failed", {
23
+ code: "ZVEC_GREP.ENGINE.INDEXING.WORKSPACE_FAILED",
24
+ context: workspaceIndexContext(ctx.workspaceIndex),
26
25
  });
27
26
  }
28
27
  }
29
- export async function getCollectionIndexStatus(collection, storedFiles) {
28
+ export async function indexWorkspacePaths(ctx, changedPaths) {
30
29
  try {
31
- const scan = await scanRootPaths(collection.id, collection.rootPaths);
30
+ return await indexWorkspacePathsUnchecked(ctx, changedPaths);
31
+ }
32
+ catch (error) {
33
+ throw toEngineError(error, "Indexing changed paths failed", {
34
+ code: "ZVEC_GREP.ENGINE.INDEXING.WORKSPACE_FAILED",
35
+ context: workspaceIndexContext(ctx.workspaceIndex),
36
+ });
37
+ }
38
+ }
39
+ export async function getWorkspaceIndexStatus(workspaceIndex, storedFiles) {
40
+ try {
41
+ const scan = await scanRootPaths(workspaceIndex.id, workspaceIndex.rootPaths);
32
42
  const diff = await computeDiffFromFiles(scan.files, storedFiles);
33
43
  const pendingFiles = storedFiles.filter((file) => file.indexStatus?.indexedTime === null);
34
44
  const failedFiles = pendingFiles.filter((file) => file.indexStatus?.error !== undefined);
35
45
  const indexedFiles = storedFiles.filter((file) => file.indexStatus?.indexedTime !== undefined &&
36
46
  file.indexStatus.indexedTime !== null);
37
47
  const entitiesIndexed = indexedFiles.reduce((count, file) => count + (file.indexStatus?.entityCount ?? 0), 0);
48
+ const fragmentsTruncated = indexedFiles.reduce((count, file) => count + (file.indexStatus?.truncatedFragmentCount ?? 0), 0);
38
49
  return {
39
- collectionId: collection.id,
40
- collectionName: collection.name,
41
50
  filesScanned: scan.files.length,
42
51
  filesStored: storedFiles.length,
43
52
  filesIndexed: indexedFiles.length,
44
53
  entitiesIndexed,
54
+ fragmentsTruncated,
45
55
  filesPending: pendingFiles.length,
46
56
  filesFailed: failedFiles.length,
47
57
  filesAdded: diff.added.length,
@@ -56,36 +66,42 @@ export async function getCollectionIndexStatus(collection, storedFiles) {
56
66
  };
57
67
  }
58
68
  catch (error) {
59
- throw toEngineError(error, "Inspecting collection index status failed", {
69
+ throw toEngineError(error, "Inspecting workspace index status failed", {
60
70
  code: "ZVEC_GREP.ENGINE.INDEXING.STATUS_FAILED",
61
- context: collectionContext(collection),
71
+ context: workspaceIndexContext(workspaceIndex),
62
72
  });
63
73
  }
64
74
  }
65
- async function indexCollectionUnchecked(ctx) {
75
+ async function indexWorkspaceUnchecked(ctx) {
66
76
  const start = Date.now();
67
77
  const report = ctx.onProgress ?? (() => undefined);
68
78
  const timings = new TimingCollector();
79
+ throwIfIndexCancelled(ctx);
69
80
  const firstPass = await runIndexPass(ctx, report, "Scanning files...", timings);
70
81
  const passes = [firstPass];
82
+ let progressBase;
71
83
  if (firstPass.stats.filesFailed > 0) {
72
84
  const failed = firstPass.stats.filesFailed;
85
+ progressBase = retryProgressBase(firstPass);
73
86
  report({
74
87
  phase: "scanning",
88
+ filesTotal: progressBase.filesTotal,
89
+ filesIndexed: progressBase.filesSucceeded,
75
90
  detail: `Retrying ${failed} failed ${failed === 1 ? "file" : "files"}...`,
76
91
  });
77
- passes.push(await runIndexPass(ctx, report, "Scanning retry candidates...", timings));
92
+ passes.push(await runIndexPass(ctx, report, "Scanning retry candidates...", timings, progressBase));
78
93
  }
79
94
  const finalPass = passes[passes.length - 1];
80
- reportIndexFinalizing(ctx, report, finalPass);
81
- timings.timeSync("index_optimize", () => optimizeStorage(ctx));
95
+ throwIfIndexCancelled(ctx);
96
+ reportIndexFinalizing(ctx, report, finalPass, progressBase);
97
+ await timings.time("index_optimize", () => optimizeStorage(ctx));
82
98
  const result = buildIndexResult(ctx, passes, Date.now() - start, timings);
83
99
  if (result.filesFailed > 0) {
84
100
  report({ phase: "done", detail: "Indexing completed with failed files" });
85
101
  throw new EngineError(`Indexing completed with ${result.filesFailed} failed ${result.filesFailed === 1 ? "file" : "files"}`, {
86
102
  code: "ZVEC_GREP.ENGINE.INDEXING.FILES_FAILED",
87
103
  context: errorDetails([
88
- collectionDetail(ctx.collection.name),
104
+ workspaceIndexDetail(ctx.workspaceIndex.name),
89
105
  detail("filesFailed", result.filesFailed),
90
106
  detail("filesScanned", result.filesScanned),
91
107
  detail("failedFiles", summarizeFailedFiles(finalPass.stats.failedFiles)),
@@ -99,18 +115,83 @@ async function indexCollectionUnchecked(ctx) {
99
115
  report({ phase: "done", detail: "Indexing complete" });
100
116
  return result;
101
117
  }
102
- async function runIndexPass(ctx, report, scanningDetail, timings) {
103
- report({ phase: "scanning", detail: scanningDetail });
104
- const scan = await timings.time("index_scan", () => scanRootPaths(ctx.collection.id, ctx.collection.rootPaths));
105
- const diff = await timings.time("index_diff", () => computeDiff(scan.files, ctx.storage));
118
+ async function indexWorkspacePathsUnchecked(ctx, changedPaths) {
119
+ const start = Date.now();
120
+ const report = ctx.onProgress ?? (() => undefined);
121
+ const timings = new TimingCollector();
122
+ const normalizedPaths = [...new Set(changedPaths.map(normalizePath))];
123
+ throwIfIndexCancelled(ctx);
124
+ const firstPass = await runPathIndexPass(ctx, report, normalizedPaths, timings);
125
+ const passes = [firstPass];
126
+ let progressBase;
127
+ if (firstPass.stats.filesFailed > 0) {
128
+ progressBase = retryProgressBase(firstPass);
129
+ passes.push(await runPathIndexPass(ctx, report, normalizedPaths, timings, progressBase));
130
+ }
131
+ const finalPass = passes[passes.length - 1];
132
+ throwIfIndexCancelled(ctx);
133
+ reportIndexFinalizing(ctx, report, finalPass, progressBase);
134
+ await timings.time("index_optimize", () => optimizeStorage(ctx));
135
+ const result = buildIndexResult(ctx, passes, Date.now() - start, timings);
136
+ if (result.filesFailed > 0) {
137
+ throw new EngineError(`Indexing completed with ${result.filesFailed} failed files`, {
138
+ code: "ZVEC_GREP.ENGINE.INDEXING.FILES_FAILED",
139
+ context: workspaceIndexContext(ctx.workspaceIndex),
140
+ });
141
+ }
142
+ report({ phase: "done", detail: "Indexing complete" });
143
+ return result;
144
+ }
145
+ async function runPathIndexPass(ctx, report, changedPaths, timings, progressBase) {
146
+ reportScanning(report, "Scanning changed paths...", progressBase);
147
+ const scanned = await timings.time("index_scan_paths", async () => {
148
+ const files = [];
149
+ const diagnostics = emptyScanDiagnostics();
150
+ for (const path of changedPaths) {
151
+ throwIfIndexCancelled(ctx);
152
+ const info = await lstat(path).catch(() => null);
153
+ const scan = info?.isDirectory()
154
+ ? await scanDirectoryPath(ctx.workspaceIndex.id, ctx.workspaceIndex.rootPaths, path, { signal: ctx.signal })
155
+ : await scanFilePath(ctx.workspaceIndex.id, ctx.workspaceIndex.rootPaths, path, {
156
+ signal: ctx.signal,
157
+ });
158
+ files.push(...scan.files);
159
+ mergeScanDiagnostics(diagnostics, scan.diagnostics);
160
+ }
161
+ return {
162
+ files: [...new Map(files.map((file) => [file.id, file])).values()],
163
+ diagnostics,
164
+ };
165
+ });
166
+ throwIfIndexCancelled(ctx);
167
+ const existing = [
168
+ ...new Map(changedPaths
169
+ .flatMap((path) => ctx.storage.listFilesByPathPrefix(path))
170
+ .map((file) => [file.id, file])).values(),
171
+ ];
172
+ return runDiffPass(ctx, report, scanned.files, existing, timings, progressBase, scanned.diagnostics);
173
+ }
174
+ async function runIndexPass(ctx, report, scanningDetail, timings, progressBase) {
175
+ reportScanning(report, scanningDetail, progressBase);
176
+ const scan = await timings.time("index_scan", () => scanRootPaths(ctx.workspaceIndex.id, ctx.workspaceIndex.rootPaths, {
177
+ signal: ctx.signal,
178
+ }));
179
+ throwIfIndexCancelled(ctx);
180
+ return runDiffPass(ctx, report, scan.files, ctx.storage.listFiles(), timings, progressBase, scan.diagnostics);
181
+ }
182
+ async function runDiffPass(ctx, report, scannedFiles, existingFiles, timings, progressBase, scanDiagnostics = emptyScanDiagnostics()) {
183
+ const diff = await timings.time("index_diff", () => computeDiffFromFiles(scannedFiles, existingFiles));
184
+ throwIfIndexCancelled(ctx);
106
185
  const pending = [...diff.added, ...diff.modified, ...diff.pending];
107
186
  report({
108
187
  phase: "scanning",
109
- filesTotal: scan.files.length,
188
+ filesTotal: progressBase?.filesTotal ?? scannedFiles.length,
189
+ filesIndexed: progressBase?.filesSucceeded ?? 0,
110
190
  detail: `${diff.added.length} added, ${diff.modified.length} modified, ${diff.pending.length} pending, ${diff.deleted.length} deleted, ${diff.unchanged.length} unchanged`,
111
191
  });
112
192
  timings.timeSync("index_delete_stale", () => {
113
193
  for (const file of diff.deleted) {
194
+ throwIfIndexCancelled(ctx);
114
195
  try {
115
196
  ctx.storage.deleteFile(file.id);
116
197
  }
@@ -125,8 +206,10 @@ async function runIndexPass(ctx, report, scanningDetail, timings) {
125
206
  const reportIndexing = (currentStats, detail, embedding) => {
126
207
  report({
127
208
  phase: "indexing",
128
- filesTotal: pending.length,
129
- filesIndexed: currentStats.filesIndexed + currentStats.filesFailed,
209
+ filesTotal: progressBase?.filesTotal ?? pending.length,
210
+ filesIndexed: (progressBase?.filesSucceeded ?? 0) +
211
+ currentStats.filesIndexed +
212
+ currentStats.filesFailed,
130
213
  filesFailed: currentStats.filesFailed,
131
214
  detail,
132
215
  embedding,
@@ -140,21 +223,46 @@ async function runIndexPass(ctx, report, scanningDetail, timings) {
140
223
  entitiesCreated: 0,
141
224
  });
142
225
  const stats = await indexFiles(pending, ctx, reportIndexing, timings);
226
+ throwIfIndexCancelled(ctx);
143
227
  return {
144
- filesScanned: scan.files.length,
228
+ filesScanned: scannedFiles.length,
229
+ scanDiagnostics,
145
230
  diff,
146
231
  stats,
147
232
  };
148
233
  }
149
- function reportIndexFinalizing(ctx, report, pass) {
234
+ function reportIndexFinalizing(ctx, report, pass, progressBase) {
150
235
  report({
151
236
  phase: "indexing",
237
+ filesTotal: progressBase?.filesTotal ??
238
+ pass.diff.added.length +
239
+ pass.diff.modified.length +
240
+ pass.diff.pending.length,
241
+ filesIndexed: (progressBase?.filesSucceeded ?? 0) +
242
+ pass.stats.filesIndexed +
243
+ pass.stats.filesFailed,
244
+ filesFailed: pass.stats.filesFailed,
245
+ detail: "finalizing index",
246
+ });
247
+ }
248
+ function retryProgressBase(pass) {
249
+ return {
250
+ filesSucceeded: pass.stats.filesIndexed,
152
251
  filesTotal: pass.diff.added.length +
153
252
  pass.diff.modified.length +
154
253
  pass.diff.pending.length,
155
- filesIndexed: pass.stats.filesIndexed + pass.stats.filesFailed,
156
- filesFailed: pass.stats.filesFailed,
157
- detail: "finalizing index",
254
+ };
255
+ }
256
+ function reportScanning(report, detail, progressBase) {
257
+ report({
258
+ phase: "scanning",
259
+ ...(progressBase
260
+ ? {
261
+ filesTotal: progressBase.filesTotal,
262
+ filesIndexed: progressBase.filesSucceeded,
263
+ }
264
+ : {}),
265
+ detail,
158
266
  });
159
267
  }
160
268
  function buildIndexResult(ctx, passes, durationMs, timings) {
@@ -162,8 +270,6 @@ function buildIndexResult(ctx, passes, durationMs, timings) {
162
270
  const retryPasses = passes.slice(1);
163
271
  const finalPass = passes[passes.length - 1];
164
272
  return {
165
- collectionId: ctx.collection.id,
166
- collectionName: ctx.collection.name,
167
273
  filesScanned: finalPass.filesScanned,
168
274
  filesAdded: firstPass.diff.added.length +
169
275
  retryPasses.reduce((count, pass) => count + pass.diff.added.length, 0),
@@ -178,22 +284,50 @@ function buildIndexResult(ctx, passes, durationMs, timings) {
178
284
  entitiesCreated: passes.reduce((count, pass) => count + pass.stats.entitiesCreated, 0),
179
285
  durationMs,
180
286
  timings: timings.entries(),
287
+ ...(finalPass.scanDiagnostics.skippedFiles > 0
288
+ ? { scanDiagnostics: finalPass.scanDiagnostics }
289
+ : {}),
290
+ };
291
+ }
292
+ const MAX_SKIPPED_FILE_SAMPLES = 20;
293
+ function emptyScanDiagnostics() {
294
+ return {
295
+ skippedFiles: 0,
296
+ skippedByReason: {
297
+ empty: 0,
298
+ too_large: 0,
299
+ unsupported: 0,
300
+ binary: 0,
301
+ },
302
+ skippedSamples: [],
181
303
  };
182
304
  }
183
- function optimizeStorage(ctx) {
305
+ function mergeScanDiagnostics(target, source) {
306
+ target.skippedFiles += source.skippedFiles;
307
+ for (const reason of [
308
+ "empty",
309
+ "too_large",
310
+ "unsupported",
311
+ "binary",
312
+ ]) {
313
+ target.skippedByReason[reason] += source.skippedByReason[reason];
314
+ }
315
+ const remaining = MAX_SKIPPED_FILE_SAMPLES - target.skippedSamples.length;
316
+ if (remaining > 0) {
317
+ target.skippedSamples.push(...source.skippedSamples.slice(0, remaining));
318
+ }
319
+ }
320
+ async function optimizeStorage(ctx) {
184
321
  try {
185
- ctx.storage.optimize();
322
+ await ctx.storage.finalizeWrites();
186
323
  }
187
324
  catch (error) {
188
325
  throw toEngineError(error, "Indexing failed to finalize storage", {
189
326
  code: "ZVEC_GREP.ENGINE.INDEXING.OPTIMIZE_FAILED",
190
- context: collectionContext(ctx.collection),
327
+ context: workspaceIndexContext(ctx.workspaceIndex),
191
328
  });
192
329
  }
193
330
  }
194
- async function computeDiff(scannedFiles, storage) {
195
- return computeDiffFromFiles(scannedFiles, storage.listFiles());
196
- }
197
331
  async function computeDiffFromFiles(scannedFiles, existingFiles) {
198
332
  const existingById = new Map(existingFiles.map((file) => [file.id, file]));
199
333
  const seen = new Set();
@@ -246,6 +380,7 @@ async function indexFiles(files, ctx, onProgress, timings) {
246
380
  onProgress(currentStats, detail, embeddingScheduler.snapshot());
247
381
  };
248
382
  const flushBatch = async () => {
383
+ throwIfIndexCancelled(ctx);
249
384
  if (batchFiles.length === 0) {
250
385
  return;
251
386
  }
@@ -254,6 +389,7 @@ async function indexFiles(files, ctx, onProgress, timings) {
254
389
  await scheduleEmbeddingTask(() => embedAndCommitBatch(filesToEmbed, ctx, stats, onProgress, embeddingScheduler, timings, embeddingTiming));
255
390
  };
256
391
  const scheduleEmbeddingTask = async (task) => {
392
+ throwIfIndexCancelled(ctx);
257
393
  const promise = task().finally(() => {
258
394
  runningEmbeddings.delete(promise);
259
395
  });
@@ -262,50 +398,66 @@ async function indexFiles(files, ctx, onProgress, timings) {
262
398
  await Promise.race(runningEmbeddings);
263
399
  }
264
400
  };
265
- for (const file of files) {
266
- onProgress(stats, `reading ${file.relativePath}`);
267
- const prepared = await timings.time("index_prepare", () => prepareFile(file, ctx));
268
- if ("failedReason" in prepared) {
269
- recordFileFailed(stats, file, prepared.failedReason);
270
- onProgress(stats, `failed ${file.relativePath}`);
271
- continue;
272
- }
273
- if (prepared.fragments.length === 0) {
274
- const committed = commitFile(prepared, [], ctx, stats);
275
- onProgress(stats, finishedFileDetail(committed, file.relativePath));
276
- continue;
277
- }
278
- if (prepared.fragments.length > ctx.embeddingModel.limits.maxBatchSize) {
279
- await flushBatch();
280
- await scheduleEmbeddingTask(async () => {
281
- reportEmbeddingProgress(stats, `embedding ${file.relativePath}`);
282
- await embedAndCommitFile(prepared, ctx, stats, onProgress, embeddingScheduler, timings, embeddingTiming);
283
- });
284
- continue;
285
- }
286
- if (batchFragmentCount > 0 &&
287
- batchFragmentCount + prepared.fragments.length >
288
- ctx.embeddingModel.limits.maxBatchSize) {
289
- await flushBatch();
290
- }
291
- batchFiles.push(prepared);
292
- batchFragmentCount += prepared.fragments.length;
293
- if (batchFragmentCount === ctx.embeddingModel.limits.maxBatchSize) {
294
- await flushBatch();
401
+ try {
402
+ for (const file of files) {
403
+ throwIfIndexCancelled(ctx);
404
+ onProgress(stats, `reading ${file.relativePath}`);
405
+ const prepared = await timings.time("index_prepare", () => prepareFile(file, ctx));
406
+ throwIfIndexCancelled(ctx);
407
+ if ("failedReason" in prepared) {
408
+ recordFileFailed(stats, file, prepared.failedReason);
409
+ onProgress(stats, `failed ${file.relativePath}`);
410
+ continue;
411
+ }
412
+ if (prepared.fragments.length === 0) {
413
+ const committed = commitFile(prepared, [], ctx, stats);
414
+ onProgress(stats, finishedFileDetail(committed, file.relativePath));
415
+ continue;
416
+ }
417
+ if (prepared.fragments.length > ctx.embeddingModel.info.limits.maxBatchSize) {
418
+ await flushBatch();
419
+ await scheduleEmbeddingTask(async () => {
420
+ throwIfIndexCancelled(ctx);
421
+ reportEmbeddingProgress(stats, `embedding ${file.relativePath}`);
422
+ await embedAndCommitFile(prepared, ctx, stats, onProgress, embeddingScheduler, timings, embeddingTiming);
423
+ });
424
+ continue;
425
+ }
426
+ if (batchFragmentCount > 0 &&
427
+ batchFragmentCount + prepared.fragments.length >
428
+ ctx.embeddingModel.info.limits.maxBatchSize) {
429
+ await flushBatch();
430
+ }
431
+ batchFiles.push(prepared);
432
+ batchFragmentCount += prepared.fragments.length;
433
+ if (batchFragmentCount === ctx.embeddingModel.info.limits.maxBatchSize) {
434
+ await flushBatch();
435
+ }
295
436
  }
437
+ await flushBatch();
438
+ await Promise.all(runningEmbeddings);
296
439
  }
297
- await flushBatch();
298
- await Promise.all(runningEmbeddings);
440
+ catch (error) {
441
+ await Promise.allSettled(runningEmbeddings);
442
+ throw error;
443
+ }
444
+ throwIfIndexCancelled(ctx);
299
445
  return stats;
300
446
  }
301
447
  async function prepareFile(file, ctx) {
302
448
  try {
449
+ throwIfIndexCancelled(ctx);
303
450
  const source = await readSource(file);
304
- const extracted = await new ExtractorRegistry().extract(source);
305
- const fragments = extracted.filter((fragment) => ctx.embeddingModel.supportedContentKinds.includes(fragment.content.kind));
306
- return { file, fragments };
451
+ const chunkOptions = indexChunkOptions(ctx.embeddingModel.info.limits.maxInputTokens, source.kind === "text" ? source.text : undefined);
452
+ const extracted = await extract(source, chunkOptions);
453
+ throwIfIndexCancelled(ctx);
454
+ const fragments = extracted.filter((fragment) => ctx.embeddingModel.info.inputKinds.includes(fragment.content.kind));
455
+ return { file, fragments, maxChars: chunkOptions.maxChunkChars };
307
456
  }
308
457
  catch (error) {
458
+ if (indexIsCancelled(ctx)) {
459
+ throw indexCancellationError(ctx);
460
+ }
309
461
  return {
310
462
  file,
311
463
  failedReason: markFileFailed(ctx, file, error, "prepare"),
@@ -313,20 +465,33 @@ async function prepareFile(file, ctx) {
313
465
  }
314
466
  }
315
467
  async function embedAndCommitBatch(files, ctx, stats, onProgress, embeddingScheduler, timings, embeddingTiming) {
316
- const fragments = files.flatMap((file) => file.fragments);
317
468
  try {
318
- const contents = fragments.map(vectorContentForFragment);
469
+ throwIfIndexCancelled(ctx);
470
+ const contents = files.flatMap((file) => file.fragments.map((fragment) => vectorContentForFragment(fragment, file.maxChars)));
319
471
  onProgress(stats, `embedding ${describePreparedFiles(files)}`, embeddingScheduler.snapshot());
320
- const vectors = await embeddingTiming.time(() => embedContentsWithRetry(contents, ctx.embeddingModel, embeddingScheduler));
472
+ const embedding = await embeddingTiming.time(() => embedContentsWithRetry(contents, ctx.embeddingModel, embeddingScheduler, ctx.signal, (progress) => reportModelDownloadProgress(stats, progress, onProgress, embeddingScheduler)));
473
+ const truncatedInputIndexes = new Set(embedding.truncated);
474
+ throwIfIndexCancelled(ctx);
321
475
  let offset = 0;
322
476
  for (const file of files) {
323
- const fileVectors = vectors.slice(offset, offset + file.fragments.length);
477
+ throwIfIndexCancelled(ctx);
478
+ const end = offset + file.fragments.length;
479
+ const fileVectors = embedding.vectors.slice(offset, end);
480
+ let truncatedFragmentCount = 0;
481
+ for (let index = offset; index < end; index++) {
482
+ if (truncatedInputIndexes.has(index)) {
483
+ truncatedFragmentCount++;
484
+ }
485
+ }
324
486
  offset += file.fragments.length;
325
- const committed = timings.timeSync("index_commit", () => commitFile(file, fileVectors, ctx, stats));
487
+ const committed = timings.timeSync("index_commit", () => commitFile(file, fileVectors, ctx, stats, truncatedFragmentCount));
326
488
  onProgress(stats, finishedFileDetail(committed, file.file.relativePath));
327
489
  }
328
490
  }
329
491
  catch (error) {
492
+ if (indexIsCancelled(ctx)) {
493
+ throw indexCancellationError(ctx);
494
+ }
330
495
  if (isRetryableEmbeddingError(error)) {
331
496
  for (const file of files) {
332
497
  const reason = markFileFailed(ctx, file.file, error, "embed");
@@ -343,29 +508,66 @@ async function embedAndCommitBatch(files, ctx, stats, onProgress, embeddingSched
343
508
  }
344
509
  async function embedAndCommitFile(file, ctx, stats, onProgress, embeddingScheduler, timings, embeddingTiming) {
345
510
  try {
346
- const vectors = await embeddingTiming.time(() => embedFragments(file.fragments, ctx.embeddingModel, embeddingScheduler));
347
- const committed = timings.timeSync("index_commit", () => commitFile(file, vectors, ctx, stats));
511
+ throwIfIndexCancelled(ctx);
512
+ const embedding = await embeddingTiming.time(() => embedFragments(file.fragments, ctx.embeddingModel, embeddingScheduler, file.maxChars, ctx.signal, (progress) => reportModelDownloadProgress(stats, progress, onProgress, embeddingScheduler)));
513
+ throwIfIndexCancelled(ctx);
514
+ const committed = timings.timeSync("index_commit", () => commitFile(file, embedding.vectors, ctx, stats, embedding.truncated.length));
348
515
  onProgress(stats, finishedFileDetail(committed, file.file.relativePath));
349
516
  }
350
517
  catch (error) {
518
+ if (indexIsCancelled(ctx)) {
519
+ throw indexCancellationError(ctx);
520
+ }
351
521
  const reason = markFileFailed(ctx, file.file, error, "embed");
352
522
  recordFileFailed(stats, file.file, reason);
353
523
  onProgress(stats, finishedFileDetail(false, file.file.relativePath));
354
524
  }
355
525
  }
356
- function commitFile(file, vectors, ctx, stats) {
526
+ function commitFile(file, vectors, ctx, stats, truncatedFragmentCount = 0) {
357
527
  try {
358
- ctx.storage.upsertFile(file.file, file.fragments, vectors);
528
+ throwIfIndexCancelled(ctx);
529
+ if (file.fragments.length !== vectors.length) {
530
+ throw new EngineError("Embedding returned mismatched entity/vector counts", {
531
+ code: "ZVEC_GREP.ENGINE.STORAGE.ENTITY_VECTOR_COUNT_MISMATCH",
532
+ context: `fileId=${file.file.id} fragmentCount=${file.fragments.length} vectorCount=${vectors.length}`,
533
+ });
534
+ }
535
+ ctx.storage.replaceFile(file.file, file.fragments.map((fragment, index) => ({
536
+ fragment,
537
+ vector: vectors[index],
538
+ })), {
539
+ truncatedFragmentCount,
540
+ });
359
541
  stats.filesIndexed++;
360
542
  stats.entitiesCreated += countPublicEntities(file.fragments);
361
543
  return true;
362
544
  }
363
545
  catch (error) {
546
+ if (indexIsCancelled(ctx)) {
547
+ throw indexCancellationError(ctx);
548
+ }
364
549
  const reason = markFileFailed(ctx, file.file, error, "commit");
365
550
  recordFileFailed(stats, file.file, reason);
366
551
  return false;
367
552
  }
368
553
  }
554
+ function throwIfIndexCancelled(ctx) {
555
+ if (!indexIsCancelled(ctx)) {
556
+ return;
557
+ }
558
+ throw indexCancellationError(ctx);
559
+ }
560
+ function indexIsCancelled(ctx) {
561
+ return ctx.signal?.aborted === true;
562
+ }
563
+ function indexCancellationError(ctx) {
564
+ return ctx.signal?.reason instanceof Error
565
+ ? ctx.signal.reason
566
+ : new EngineError("Indexing was cancelled.", {
567
+ code: "ZVEC_GREP.ENGINE.INDEXING.CANCELLED",
568
+ context: workspaceIndexContext(ctx.workspaceIndex),
569
+ });
570
+ }
369
571
  function recordFileFailed(stats, file, reason) {
370
572
  stats.filesFailed++;
371
573
  stats.failedFiles.push(file.relativePath);
@@ -417,14 +619,15 @@ async function readSource(file) {
417
619
  text: bytes.toString("utf8"),
418
620
  };
419
621
  }
420
- async function embedFragments(fragments, model, embeddingScheduler) {
622
+ async function embedFragments(fragments, model, embeddingScheduler, maxChars, signal, onModelProgress) {
421
623
  const batches = [];
422
- for (let start = 0; start < fragments.length; start += model.limits.maxBatchSize) {
423
- const batch = fragments.slice(start, start + model.limits.maxBatchSize);
624
+ for (let start = 0; start < fragments.length; start += model.info.limits.maxBatchSize) {
625
+ const batch = fragments.slice(start, start + model.info.limits.maxBatchSize);
424
626
  batches.push({ start, fragments: batch });
425
627
  }
426
628
  const vectors = new Array(fragments.length);
427
- const results = await Promise.allSettled(batches.map((batch) => embedFragmentBatch(batch.fragments, model, batch.start, embeddingScheduler)));
629
+ const truncatedInputIndexes = [];
630
+ const results = await Promise.allSettled(batches.map((batch) => embedFragmentBatch(batch.fragments, model, batch.start, embeddingScheduler, maxChars, signal, onModelProgress)));
428
631
  let firstError;
429
632
  for (const [index, result] of results.entries()) {
430
633
  if (result.status === "rejected") {
@@ -432,94 +635,74 @@ async function embedFragments(fragments, model, embeddingScheduler) {
432
635
  continue;
433
636
  }
434
637
  const start = batches[index].start;
435
- for (const [offset, vector] of result.value.entries()) {
638
+ for (const [offset, vector] of result.value.vectors.entries()) {
436
639
  vectors[start + offset] = vector;
437
640
  }
641
+ truncatedInputIndexes.push(...result.value.truncated.map((inputIndex) => start + inputIndex));
438
642
  }
439
643
  if (firstError) {
440
644
  throw firstError;
441
645
  }
442
- return vectors;
646
+ return {
647
+ vectors,
648
+ truncated: truncatedInputIndexes,
649
+ };
443
650
  }
444
- async function embedFragmentBatch(fragments, model, startIndex, embeddingScheduler) {
445
- const contents = fragments.map(vectorContentForFragment);
651
+ async function embedFragmentBatch(fragments, model, startIndex, embeddingScheduler, maxChars, signal, onModelProgress) {
652
+ const contents = fragments.map((fragment) => vectorContentForFragment(fragment, maxChars));
446
653
  try {
447
- return await embedContentsWithRetry(contents, model, embeddingScheduler);
654
+ return await embedContentsWithRetry(contents, model, embeddingScheduler, signal, onModelProgress);
448
655
  }
449
656
  catch (error) {
450
657
  if (fragments.length === 1 || isRetryableEmbeddingError(error)) {
451
658
  throw error;
452
659
  }
453
- return await embedFragmentBatchOneByOne(fragments, model, startIndex, embeddingScheduler);
660
+ return await embedFragmentBatchOneByOne(fragments, model, startIndex, embeddingScheduler, maxChars, signal, onModelProgress);
454
661
  }
455
662
  }
456
- async function embedFragmentBatchOneByOne(fragments, model, startIndex, embeddingScheduler) {
663
+ async function embedFragmentBatchOneByOne(fragments, model, startIndex, embeddingScheduler, maxChars, signal, onModelProgress) {
457
664
  const vectors = [];
665
+ const truncatedInputIndexes = [];
458
666
  for (const [index, fragment] of fragments.entries()) {
459
667
  try {
460
- const [vector] = await embedContentsWithRetry([vectorContentForFragment(fragment)], model, embeddingScheduler);
461
- vectors.push(vector);
668
+ const result = await embedContentsWithRetry([vectorContentForFragment(fragment, maxChars)], model, embeddingScheduler, signal, onModelProgress);
669
+ vectors.push(result.vectors[0]);
670
+ if (result.truncated.length > 0) {
671
+ truncatedInputIndexes.push(index);
672
+ }
462
673
  }
463
674
  catch (error) {
464
675
  throw new EngineError("Embedding entity fragment failed after one-by-one fallback", {
465
676
  code: "ZVEC_GREP.ENGINE.INDEXING.EMBEDDING_FRAGMENT_FAILED",
466
- context: `model=${model.ref.model} fragmentId=${fragment.id} fragmentIndex=${startIndex + index}`,
677
+ context: errorDetails([
678
+ detail("model", model.info.reference),
679
+ detail("fragmentId", fragment.id),
680
+ detail("fragmentIndex", startIndex + index),
681
+ isEngineError(error) ? detail("causeCode", error.code) : null,
682
+ error instanceof Error ? detail("cause", error.message) : null,
683
+ isEngineError(error) ? error.context : null,
684
+ ]),
467
685
  cause: error,
468
686
  });
469
687
  }
470
688
  }
471
- return vectors;
472
- }
473
- function vectorContentForFragment(fragment) {
474
- if (fragment.content.kind !== "text") {
475
- return fragment.content;
476
- }
477
- const metadata = vectorMetadataText(fragment.metadata);
478
- if (metadata.length === 0) {
479
- return fragment.content;
480
- }
481
689
  return {
482
- kind: "text",
483
- text: `${metadata}\n${fragment.content.text}`,
690
+ vectors,
691
+ truncated: truncatedInputIndexes,
484
692
  };
485
693
  }
486
- function vectorMetadataText(metadata) {
487
- if (!metadata) {
488
- return "";
489
- }
490
- if (metadata.kind === "code") {
491
- return compactMetadataLines([
492
- metadata.symbolName
493
- ? `symbol: ${metadata.symbolType} ${metadata.symbolName}`
494
- : `symbol: ${metadata.symbolType}`,
495
- metadata.scope ? `scope: ${metadata.scope}` : null,
496
- metadata.signature ? `signature: ${oneLine(metadata.signature)}` : null,
497
- metadata.modifiers.length > 0
498
- ? `modifiers: ${metadata.modifiers.join(" ")}`
499
- : null,
500
- metadata.doc ? `doc: ${oneLine(metadata.doc)}` : null,
501
- ]);
502
- }
503
- return compactMetadataLines([
504
- metadata.heading ? `heading: ${metadata.heading}` : null,
505
- typeof metadata.level === "number"
506
- ? `heading_level: ${metadata.level}`
507
- : null,
508
- metadata.scope ? `scope: ${metadata.scope}` : null,
509
- ]);
510
- }
511
- function compactMetadataLines(lines) {
512
- return lines
513
- .filter((line) => line !== null && line.trim().length > 0)
514
- .join("\n");
515
- }
516
- async function embedContentsWithRetry(contents, model, embeddingScheduler) {
694
+ async function embedContentsWithRetry(contents, model, embeddingScheduler, signal, onModelProgress) {
517
695
  let attempt = 0;
518
696
  while (true) {
519
697
  let retry = nonRetryableEmbeddingError();
520
698
  let delayMs = 0;
521
699
  try {
522
- const vectors = await embeddingScheduler.run(() => model.embed(contents, { purpose: "document" }), (error) => {
700
+ throwIfAborted(signal);
701
+ const result = await embeddingScheduler.run(() => model.embed(contents, {
702
+ purpose: "document",
703
+ signal,
704
+ onProgress: onModelProgress,
705
+ }), signal, (error) => {
523
706
  retry = classifyEmbeddingRetry(error);
524
707
  delayMs = retryDelayMs(attempt, retry);
525
708
  if (retry.retryable) {
@@ -530,9 +713,14 @@ async function embedContentsWithRetry(contents, model, embeddingScheduler) {
530
713
  }
531
714
  });
532
715
  embeddingScheduler.recordSuccess();
533
- return vectors;
716
+ return result;
534
717
  }
535
718
  catch (error) {
719
+ if (signal?.aborted) {
720
+ throw signal.reason instanceof Error
721
+ ? signal.reason
722
+ : new Error("Embedding was cancelled.");
723
+ }
536
724
  if (!retry.retryable) {
537
725
  retry = classifyEmbeddingRetry(error);
538
726
  delayMs = retryDelayMs(attempt, retry);
@@ -540,11 +728,17 @@ async function embedContentsWithRetry(contents, model, embeddingScheduler) {
540
728
  if (attempt >= maxRetryAttempts(retry) || !retry.retryable) {
541
729
  throw error;
542
730
  }
543
- await delay(delayMs);
731
+ await abortableDelay(delayMs, signal);
544
732
  attempt++;
545
733
  }
546
734
  }
547
735
  }
736
+ function reportModelDownloadProgress(stats, progress, onProgress, embeddingScheduler) {
737
+ onProgress(stats, `downloading ${progress.model}`, {
738
+ ...embeddingScheduler.snapshot(),
739
+ ...progress,
740
+ });
741
+ }
548
742
  function createEmbeddingScheduler(requestedConcurrency, model) {
549
743
  return new AdaptiveEmbeddingScheduler(resolveEmbeddingConcurrencyPolicy(requestedConcurrency, model));
550
744
  }
@@ -559,10 +753,17 @@ function resolveEmbeddingConcurrencyPolicy(requestedConcurrency, model) {
559
753
  adaptive: requestedConcurrency > 1,
560
754
  };
561
755
  }
562
- const initial = model.recommendedIndexConcurrency ?? DEFAULT_EMBEDDING_CONCURRENCY;
563
- const max = model.maxIndexConcurrency ??
564
- (initial <= 1 ? initial : DEFAULT_EMBEDDING_MAX_CONCURRENCY);
565
- const min = Math.min(initial, DEFAULT_EMBEDDING_MIN_CONCURRENCY);
756
+ const remote = model.info.provider === "qwen";
757
+ const multimodal = model.info.inputKinds.includes("image");
758
+ const configuredLocalDefault = model.info.defaultConcurrency;
759
+ const localDefault = configuredLocalDefault !== undefined &&
760
+ Number.isInteger(configuredLocalDefault) &&
761
+ configuredLocalDefault > 0
762
+ ? configuredLocalDefault
763
+ : 1;
764
+ const initial = remote ? (multimodal ? 4 : 8) : localDefault;
765
+ const max = remote ? (multimodal ? 8 : 12) : localDefault;
766
+ const min = Math.min(initial, 4);
566
767
  return {
567
768
  initial,
568
769
  min,
@@ -585,11 +786,13 @@ class AdaptiveEmbeddingScheduler {
585
786
  get taskConcurrency() {
586
787
  return this.policy.max;
587
788
  }
588
- async run(task, onError) {
589
- await this.waitForCooldown();
590
- await this.acquire();
789
+ async run(task, signal, onError) {
790
+ throwIfAborted(signal);
791
+ await this.waitForCooldown(signal);
792
+ await this.acquire(signal);
591
793
  try {
592
- await this.waitForCooldown();
794
+ await this.waitForCooldown(signal);
795
+ throwIfAborted(signal);
593
796
  return await task();
594
797
  }
595
798
  catch (error) {
@@ -631,19 +834,36 @@ class AdaptiveEmbeddingScheduler {
631
834
  retryableFailures: this.retryableFailures,
632
835
  };
633
836
  }
634
- async waitForCooldown() {
837
+ async waitForCooldown(signal) {
635
838
  const remaining = this.cooldownUntil - Date.now();
636
839
  if (remaining > 0) {
637
- await delay(remaining);
840
+ await abortableDelay(remaining, signal);
638
841
  }
639
842
  }
640
- async acquire() {
843
+ async acquire(signal) {
844
+ throwIfAborted(signal);
641
845
  if (this.active < this.currentConcurrency) {
642
846
  this.active++;
643
847
  return;
644
848
  }
645
- await new Promise((resolve) => {
646
- this.queue.push(resolve);
849
+ await new Promise((resolve, reject) => {
850
+ const queued = () => {
851
+ signal?.removeEventListener("abort", abort);
852
+ resolve();
853
+ };
854
+ const abort = () => {
855
+ const index = this.queue.indexOf(queued);
856
+ if (index >= 0) {
857
+ this.queue.splice(index, 1);
858
+ }
859
+ reject(abortError(signal));
860
+ };
861
+ if (signal?.aborted) {
862
+ reject(abortError(signal));
863
+ return;
864
+ }
865
+ signal?.addEventListener("abort", abort, { once: true });
866
+ this.queue.push(queued);
647
867
  });
648
868
  }
649
869
  release() {
@@ -724,8 +944,32 @@ function retryAfterMsFromText(text) {
724
944
  }
725
945
  return undefined;
726
946
  }
727
- function delay(ms) {
728
- return new Promise((resolve) => setTimeout(resolve, ms));
947
+ function abortableDelay(ms, signal) {
948
+ if (ms <= 0) {
949
+ return Promise.resolve();
950
+ }
951
+ throwIfAborted(signal);
952
+ return new Promise((resolve, reject) => {
953
+ const timer = setTimeout(() => {
954
+ signal?.removeEventListener("abort", abort);
955
+ resolve();
956
+ }, ms);
957
+ const abort = () => {
958
+ clearTimeout(timer);
959
+ reject(abortError(signal));
960
+ };
961
+ signal?.addEventListener("abort", abort, { once: true });
962
+ });
963
+ }
964
+ function throwIfAborted(signal) {
965
+ if (signal?.aborted) {
966
+ throw abortError(signal);
967
+ }
968
+ }
969
+ function abortError(signal) {
970
+ return signal?.reason instanceof Error
971
+ ? signal.reason
972
+ : new Error("Embedding was cancelled.");
729
973
  }
730
974
  function countPublicEntities(fragments) {
731
975
  let count = 0;
@@ -777,10 +1021,10 @@ function toEngineError(error, message, options) {
777
1021
  cause: error,
778
1022
  });
779
1023
  }
780
- function collectionContext(collection) {
1024
+ function workspaceIndexContext(workspaceIndex) {
781
1025
  return (errorDetails([
782
- detail("collection_id", collection.id),
783
- collectionDetail(collection.name),
1026
+ detail("workspace_index_id", workspaceIndex.id),
1027
+ workspaceIndexDetail(workspaceIndex.name),
784
1028
  ]) ?? "");
785
1029
  }
786
1030
  function fileContext(file) {