@zvec/zvec-grep 0.1.4 → 0.1.6

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 (356) hide show
  1. package/README.md +86 -162
  2. package/README_CN.md +80 -162
  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 +3 -0
  28. package/dist/cli/args.js +749 -201
  29. package/dist/cli/args.js.map +1 -1
  30. package/dist/cli/auth.d.ts +21 -0
  31. package/dist/cli/auth.js +212 -0
  32. package/dist/cli/auth.js.map +1 -0
  33. package/dist/cli/commands.d.ts +3 -1
  34. package/dist/cli/commands.js +529 -180
  35. package/dist/cli/commands.js.map +1 -1
  36. package/dist/cli/errors.js +7 -6
  37. package/dist/cli/errors.js.map +1 -1
  38. package/dist/cli/format/context.d.ts +1 -0
  39. package/dist/cli/format/context.js +270 -70
  40. package/dist/cli/format/context.js.map +1 -1
  41. package/dist/cli/format/debug.js +14 -16
  42. package/dist/cli/format/debug.js.map +1 -1
  43. package/dist/cli/format/highlight.js.map +1 -1
  44. package/dist/cli/format/progress.d.ts +4 -0
  45. package/dist/cli/format/progress.js +222 -44
  46. package/dist/cli/format/progress.js.map +1 -1
  47. package/dist/cli/format/range.js.map +1 -1
  48. package/dist/cli/format/status.d.ts +77 -6
  49. package/dist/cli/format/status.js +343 -114
  50. package/dist/cli/format/status.js.map +1 -1
  51. package/dist/cli/help.d.ts +1 -1
  52. package/dist/cli/help.js +368 -84
  53. package/dist/cli/help.js.map +1 -1
  54. package/dist/cli/index.js +4 -4
  55. package/dist/cli/index.js.map +1 -1
  56. package/dist/cli/install.d.ts +4 -0
  57. package/dist/cli/install.js +845 -0
  58. package/dist/cli/install.js.map +1 -0
  59. package/dist/cli/managed-rg.d.ts +10 -0
  60. package/dist/cli/managed-rg.js +329 -0
  61. package/dist/cli/managed-rg.js.map +1 -0
  62. package/dist/cli/types.d.ts +40 -13
  63. package/dist/cli/types.js.map +1 -1
  64. package/dist/cli/version.js +3 -1
  65. package/dist/cli/version.js.map +1 -1
  66. package/dist/client/daemon-client.d.ts +19 -0
  67. package/dist/client/daemon-client.js +180 -0
  68. package/dist/client/daemon-client.js.map +1 -0
  69. package/dist/client/mode-router.d.ts +9 -0
  70. package/dist/client/mode-router.js +28 -0
  71. package/dist/client/mode-router.js.map +1 -0
  72. package/dist/client/search-policy.d.ts +11 -0
  73. package/dist/client/search-policy.js +15 -0
  74. package/dist/client/search-policy.js.map +1 -0
  75. package/dist/daemon/backend.d.ts +76 -0
  76. package/dist/daemon/backend.js +1049 -0
  77. package/dist/daemon/backend.js.map +1 -0
  78. package/dist/daemon/change-set.d.ts +25 -0
  79. package/dist/daemon/change-set.js +101 -0
  80. package/dist/daemon/change-set.js.map +1 -0
  81. package/dist/daemon/config.d.ts +24 -0
  82. package/dist/daemon/config.js +88 -0
  83. package/dist/daemon/config.js.map +1 -0
  84. package/dist/daemon/errors.d.ts +5 -0
  85. package/dist/daemon/errors.js +11 -0
  86. package/dist/daemon/errors.js.map +1 -0
  87. package/dist/daemon/http-server.d.ts +30 -0
  88. package/dist/daemon/http-server.js +287 -0
  89. package/dist/daemon/http-server.js.map +1 -0
  90. package/dist/daemon/index-coordinator.d.ts +23 -0
  91. package/dist/daemon/index-coordinator.js +68 -0
  92. package/dist/daemon/index-coordinator.js.map +1 -0
  93. package/dist/daemon/job-scheduler.d.ts +70 -0
  94. package/dist/daemon/job-scheduler.js +366 -0
  95. package/dist/daemon/job-scheduler.js.map +1 -0
  96. package/dist/daemon/logger.d.ts +9 -0
  97. package/dist/daemon/logger.js +53 -0
  98. package/dist/daemon/logger.js.map +1 -0
  99. package/dist/daemon/model-pool.d.ts +45 -0
  100. package/dist/daemon/model-pool.js +187 -0
  101. package/dist/daemon/model-pool.js.map +1 -0
  102. package/dist/daemon/root-lease.d.ts +14 -0
  103. package/dist/daemon/root-lease.js +188 -0
  104. package/dist/daemon/root-lease.js.map +1 -0
  105. package/dist/daemon/root-runtime.d.ts +78 -0
  106. package/dist/daemon/root-runtime.js +333 -0
  107. package/dist/daemon/root-runtime.js.map +1 -0
  108. package/dist/daemon/runtime-manager.d.ts +45 -0
  109. package/dist/daemon/runtime-manager.js +243 -0
  110. package/dist/daemon/runtime-manager.js.map +1 -0
  111. package/dist/daemon/runtime.d.ts +12 -0
  112. package/dist/daemon/runtime.js +102 -0
  113. package/dist/daemon/runtime.js.map +1 -0
  114. package/dist/daemon/server-controller.d.ts +39 -0
  115. package/dist/daemon/server-controller.js +203 -0
  116. package/dist/daemon/server-controller.js.map +1 -0
  117. package/dist/daemon/watch-manager.d.ts +50 -0
  118. package/dist/daemon/watch-manager.js +293 -0
  119. package/dist/daemon/watch-manager.js.map +1 -0
  120. package/dist/daemon/workspace-read-session-cache.d.ts +31 -0
  121. package/dist/daemon/workspace-read-session-cache.js +115 -0
  122. package/dist/daemon/workspace-read-session-cache.js.map +1 -0
  123. package/dist/engine/config.d.ts +50 -0
  124. package/dist/engine/config.js +321 -0
  125. package/dist/engine/config.js.map +1 -0
  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.map +1 -1
  131. package/dist/engine/extraction/code/extractor.d.ts +5 -11
  132. package/dist/engine/extraction/code/extractor.js +184 -109
  133. package/dist/engine/extraction/code/extractor.js.map +1 -1
  134. package/dist/engine/extraction/code/families/c-family.js +13 -7
  135. package/dist/engine/extraction/code/families/c-family.js.map +1 -1
  136. package/dist/engine/extraction/code/families/js-ts.d.ts +1 -1
  137. package/dist/engine/extraction/code/families/js-ts.js +24 -18
  138. package/dist/engine/extraction/code/families/js-ts.js.map +1 -1
  139. package/dist/engine/extraction/code/families/metadata.d.ts +1 -1
  140. package/dist/engine/extraction/code/families/metadata.js +11 -8
  141. package/dist/engine/extraction/code/families/metadata.js.map +1 -1
  142. package/dist/engine/extraction/code/families/name-field.js +5 -5
  143. package/dist/engine/extraction/code/families/name-field.js.map +1 -1
  144. package/dist/engine/extraction/code/languages/c.js +1 -4
  145. package/dist/engine/extraction/code/languages/c.js.map +1 -1
  146. package/dist/engine/extraction/code/languages/cpp.js.map +1 -1
  147. package/dist/engine/extraction/code/languages/go.js +1 -3
  148. package/dist/engine/extraction/code/languages/go.js.map +1 -1
  149. package/dist/engine/extraction/code/languages/java.js.map +1 -1
  150. package/dist/engine/extraction/code/languages/javascript.js +1 -3
  151. package/dist/engine/extraction/code/languages/javascript.js.map +1 -1
  152. package/dist/engine/extraction/code/languages/python.js +5 -6
  153. package/dist/engine/extraction/code/languages/python.js.map +1 -1
  154. package/dist/engine/extraction/code/languages/rust.js +1 -5
  155. package/dist/engine/extraction/code/languages/rust.js.map +1 -1
  156. package/dist/engine/extraction/code/languages/typescript.js.map +1 -1
  157. package/dist/engine/extraction/code/tree-sitter/grammar.js.map +1 -0
  158. package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/nodes.js +2 -1
  159. package/dist/engine/extraction/code/tree-sitter/nodes.js.map +1 -0
  160. package/dist/engine/extraction/code/tree-sitter/parser.js.map +1 -0
  161. package/dist/engine/extraction/ids.js.map +1 -1
  162. package/dist/engine/extraction/image/extractor.d.ts +5 -0
  163. package/dist/engine/extraction/image/extractor.js +35 -0
  164. package/dist/engine/extraction/image/extractor.js.map +1 -0
  165. package/dist/engine/extraction/index.d.ts +3 -44
  166. package/dist/engine/extraction/index.js +1 -280
  167. package/dist/engine/extraction/index.js.map +1 -1
  168. package/dist/engine/extraction/markdown/extractor.d.ts +5 -11
  169. package/dist/engine/extraction/markdown/extractor.js +37 -16
  170. package/dist/engine/extraction/markdown/extractor.js.map +1 -1
  171. package/dist/engine/extraction/runtime.d.ts +4 -0
  172. package/dist/engine/extraction/runtime.js +26 -0
  173. package/dist/engine/extraction/runtime.js.map +1 -0
  174. package/dist/engine/extraction/source.d.ts +18 -0
  175. package/dist/engine/extraction/source.js +22 -0
  176. package/dist/engine/extraction/source.js.map +1 -0
  177. package/dist/engine/extraction/text/extractor.d.ts +7 -0
  178. package/dist/engine/extraction/text/extractor.js +168 -0
  179. package/dist/engine/extraction/text/extractor.js.map +1 -0
  180. package/dist/engine/extraction/types.d.ts +4 -0
  181. package/dist/engine/{models → extraction}/types.js.map +1 -1
  182. package/dist/engine/{files/file-type.d.ts → file-type.d.ts} +1 -1
  183. package/dist/engine/file-type.js.map +1 -0
  184. package/dist/engine/index-status.d.ts +9 -0
  185. package/dist/engine/index-status.js +38 -0
  186. package/dist/engine/index-status.js.map +1 -0
  187. package/dist/engine/manifest.d.ts +12 -0
  188. package/dist/engine/manifest.js +120 -0
  189. package/dist/engine/manifest.js.map +1 -0
  190. package/dist/engine/models/{providers/llama-cpp/embedding.d.ts → backends/llama-cpp.d.ts} +25 -20
  191. package/dist/engine/models/{providers/llama-cpp/embedding.js → backends/llama-cpp.js} +165 -113
  192. package/dist/engine/models/backends/llama-cpp.js.map +1 -0
  193. package/dist/engine/models/backends/model2vec.d.ts +57 -0
  194. package/dist/engine/models/backends/model2vec.js +332 -0
  195. package/dist/engine/models/backends/model2vec.js.map +1 -0
  196. package/dist/engine/models/backends/qwen.d.ts +44 -0
  197. package/dist/engine/models/{providers/qwen/embedding.js → backends/qwen.js} +168 -102
  198. package/dist/engine/models/backends/qwen.js.map +1 -0
  199. package/dist/engine/models/backends/transformers-js.d.ts +87 -0
  200. package/dist/engine/models/backends/transformers-js.js +316 -0
  201. package/dist/engine/models/backends/transformers-js.js.map +1 -0
  202. package/dist/engine/models/catalog.d.ts +188 -23
  203. package/dist/engine/models/catalog.js +172 -41
  204. package/dist/engine/models/catalog.js.map +1 -1
  205. package/dist/engine/models/download-progress.d.ts +15 -0
  206. package/dist/engine/models/download-progress.js +48 -0
  207. package/dist/engine/models/download-progress.js.map +1 -0
  208. package/dist/engine/models/embeddings.d.ts +58 -18
  209. package/dist/engine/models/embeddings.js +53 -29
  210. package/dist/engine/models/embeddings.js.map +1 -1
  211. package/dist/engine/models/factory.d.ts +2 -5
  212. package/dist/engine/models/factory.js +41 -56
  213. package/dist/engine/models/factory.js.map +1 -1
  214. package/dist/engine/models/index.d.ts +4 -6
  215. package/dist/engine/models/index.js +4 -4
  216. package/dist/engine/models/index.js.map +1 -1
  217. package/dist/engine/models/ranking.d.ts +7 -3
  218. package/dist/engine/models/resolution.d.ts +8 -0
  219. package/dist/engine/models/resolution.js +22 -0
  220. package/dist/engine/models/resolution.js.map +1 -0
  221. package/dist/engine/pipeline/indexing/index.d.ts +9 -7
  222. package/dist/engine/pipeline/indexing/index.js +399 -147
  223. package/dist/engine/pipeline/indexing/index.js.map +1 -1
  224. package/dist/engine/pipeline/indexing/root-paths.js +16 -19
  225. package/dist/engine/pipeline/indexing/root-paths.js.map +1 -1
  226. package/dist/engine/pipeline/indexing/scanner/index.d.ts +6 -1
  227. package/dist/engine/pipeline/indexing/scanner/index.js +318 -66
  228. package/dist/engine/pipeline/indexing/scanner/index.js.map +1 -1
  229. package/dist/engine/pipeline/search/index.d.ts +6 -6
  230. package/dist/engine/pipeline/search/index.js +76 -34
  231. package/dist/engine/pipeline/search/index.js.map +1 -1
  232. package/dist/engine/service/index.d.ts +3 -2
  233. package/dist/engine/service/index.js +1 -1
  234. package/dist/engine/service/index.js.map +1 -1
  235. package/dist/engine/service/lexical.d.ts +14 -15
  236. package/dist/engine/service/lexical.js +78 -119
  237. package/dist/engine/service/lexical.js.map +1 -1
  238. package/dist/engine/service/root.d.ts +13 -12
  239. package/dist/engine/service/root.js +35 -24
  240. package/dist/engine/service/root.js.map +1 -1
  241. package/dist/engine/service/structure-enrichment.js +29 -25
  242. package/dist/engine/service/structure-enrichment.js.map +1 -1
  243. package/dist/engine/service/types.d.ts +61 -35
  244. package/dist/engine/service/workspace-index.d.ts +24 -0
  245. package/dist/engine/service/workspace-index.js +151 -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 +878 -544
  249. package/dist/engine/service/zvec-grep.js.map +1 -1
  250. package/dist/engine/storage/index.d.ts +8 -4
  251. package/dist/engine/storage/index.js +1 -1
  252. package/dist/engine/storage/index.js.map +1 -1
  253. package/dist/engine/storage/zvec.d.ts +10 -33
  254. package/dist/engine/storage/zvec.js +110 -49
  255. package/dist/engine/storage/zvec.js.map +1 -1
  256. package/dist/engine/types.d.ts +29 -23
  257. package/dist/engine/types.js +1 -1
  258. package/dist/engine/types.js.map +1 -1
  259. package/dist/engine/utils/daemon-lease.d.ts +17 -0
  260. package/dist/engine/utils/daemon-lease.js +175 -0
  261. package/dist/engine/utils/daemon-lease.js.map +1 -0
  262. package/dist/engine/utils/file-selection.d.ts +10 -0
  263. package/dist/engine/utils/file-selection.js +157 -0
  264. package/dist/engine/utils/file-selection.js.map +1 -0
  265. package/dist/engine/utils/glob.d.ts +3 -0
  266. package/dist/engine/utils/glob.js +116 -18
  267. package/dist/engine/utils/glob.js.map +1 -1
  268. package/dist/engine/utils/hash.js.map +1 -1
  269. package/dist/engine/utils/json.d.ts +6 -2
  270. package/dist/engine/utils/json.js +50 -12
  271. package/dist/engine/utils/json.js.map +1 -1
  272. package/dist/engine/utils/lock.js +7 -7
  273. package/dist/engine/utils/lock.js.map +1 -1
  274. package/dist/engine/utils/path.js +2 -2
  275. package/dist/engine/utils/path.js.map +1 -1
  276. package/dist/engine/utils/timing.js.map +1 -1
  277. package/dist/index-progress.d.ts +10 -0
  278. package/dist/index-progress.js +159 -0
  279. package/dist/index-progress.js.map +1 -0
  280. package/dist/index.d.ts +6 -6
  281. package/dist/index.js +3 -3
  282. package/dist/index.js.map +1 -1
  283. package/dist/mcp/http-transport.d.ts +27 -0
  284. package/dist/mcp/http-transport.js +182 -0
  285. package/dist/mcp/http-transport.js.map +1 -0
  286. package/dist/mcp/input-normalization.d.ts +38 -0
  287. package/dist/mcp/input-normalization.js +95 -0
  288. package/dist/mcp/input-normalization.js.map +1 -0
  289. package/dist/mcp/progress-heartbeat.d.ts +17 -0
  290. package/dist/mcp/progress-heartbeat.js +46 -0
  291. package/dist/mcp/progress-heartbeat.js.map +1 -0
  292. package/dist/mcp/request-metadata.d.ts +2 -0
  293. package/dist/mcp/request-metadata.js +11 -0
  294. package/dist/mcp/request-metadata.js.map +1 -0
  295. package/dist/mcp/request-state.d.ts +40 -0
  296. package/dist/mcp/request-state.js +210 -0
  297. package/dist/mcp/request-state.js.map +1 -0
  298. package/dist/mcp/result-format.d.ts +13 -0
  299. package/dist/mcp/result-format.js +75 -0
  300. package/dist/mcp/result-format.js.map +1 -0
  301. package/dist/mcp/schemas.d.ts +430 -0
  302. package/dist/mcp/schemas.js +447 -0
  303. package/dist/mcp/schemas.js.map +1 -0
  304. package/dist/mcp/tools.d.ts +150 -0
  305. package/dist/mcp/tools.js +448 -0
  306. package/dist/mcp/tools.js.map +1 -0
  307. package/dist/mcp/toolset.d.ts +5 -0
  308. package/dist/mcp/toolset.js +12 -0
  309. package/dist/mcp/toolset.js.map +1 -0
  310. package/dist/observability/trace-context.d.ts +11 -0
  311. package/dist/observability/trace-context.js +138 -0
  312. package/dist/observability/trace-context.js.map +1 -0
  313. package/docs/01-agents.md +111 -0
  314. package/docs/02-cli.md +229 -0
  315. package/docs/03-mcp.md +167 -0
  316. package/docs/04-pipeline.md +218 -0
  317. package/docs/05-architecture.md +86 -0
  318. package/docs/06-server.md +210 -0
  319. package/docs/07-embedding.md +179 -0
  320. package/docs/08-roadmap.md +85 -0
  321. package/docs/README.md +53 -0
  322. package/package.json +43 -7
  323. package/skills/zvec-grep/SKILL.md +43 -123
  324. package/skills/zvec-grep/agents/openai.yaml +2 -2
  325. package/skills/zvec-grep/references/cli-fallback.md +85 -0
  326. package/dist/engine/collection/index.d.ts +0 -55
  327. package/dist/engine/collection/index.js +0 -710
  328. package/dist/engine/collection/index.js.map +0 -1
  329. package/dist/engine/errors/details.d.ts +0 -6
  330. package/dist/engine/errors/details.js.map +0 -1
  331. package/dist/engine/errors/index.js +0 -16
  332. package/dist/engine/errors/index.js.map +0 -1
  333. package/dist/engine/extraction/tree-sitter/grammar.js.map +0 -1
  334. package/dist/engine/extraction/tree-sitter/nodes.js.map +0 -1
  335. package/dist/engine/extraction/tree-sitter/parser.js.map +0 -1
  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/dist/engine/extraction/{tree-sitter → code/tree-sitter}/grammar.d.ts +0 -0
  351. /package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/grammar.js +0 -0
  352. /package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/nodes.d.ts +0 -0
  353. /package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/parser.d.ts +0 -0
  354. /package/dist/engine/extraction/{tree-sitter → code/tree-sitter}/parser.js +0 -0
  355. /package/dist/engine/{models → extraction}/types.js +0 -0
  356. /package/dist/engine/{files/file-type.js → file-type.js} +0 -0
@@ -1,12 +1,12 @@
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, } from "../../extraction/index.js";
7
+ import { scanDirectoryPath, scanFilePath, scanRootPaths, } from "./scanner/index.js";
8
+ const APPROXIMATE_CHARS_PER_TOKEN = 2;
9
+ const CHUNK_OVERLAP_RATIO = 0.15;
10
10
  const EMBEDDING_TRANSIENT_MAX_RETRIES = 3;
11
11
  const EMBEDDING_RATE_LIMIT_MAX_RETRIES = 6;
12
12
  const EMBEDDING_TRANSIENT_RETRY_BASE_DELAY_MS = 500;
@@ -15,33 +15,44 @@ const EMBEDDING_TRANSIENT_RETRY_MAX_DELAY_MS = 8000;
15
15
  const EMBEDDING_RATE_LIMIT_RETRY_MAX_DELAY_MS = 30000;
16
16
  const EMBEDDING_RETRY_JITTER_MS = 500;
17
17
  const EMBEDDING_SUCCESS_STREAK_MIN = 4;
18
- export async function indexCollection(ctx) {
18
+ export async function indexWorkspace(ctx) {
19
19
  try {
20
- return await indexCollectionUnchecked(ctx);
20
+ return await indexWorkspaceUnchecked(ctx);
21
21
  }
22
22
  catch (error) {
23
- throw toEngineError(error, "Indexing collection failed", {
24
- code: "ZVEC_GREP.ENGINE.INDEXING.COLLECTION_FAILED",
25
- context: collectionContext(ctx.collection),
23
+ throw toEngineError(error, "Indexing workspace failed", {
24
+ code: "ZVEC_GREP.ENGINE.INDEXING.WORKSPACE_FAILED",
25
+ context: workspaceIndexContext(ctx.workspaceIndex),
26
26
  });
27
27
  }
28
28
  }
29
- export async function getCollectionIndexStatus(collection, storedFiles) {
29
+ export async function indexWorkspacePaths(ctx, changedPaths) {
30
30
  try {
31
- const scan = await scanRootPaths(collection.id, collection.rootPaths);
31
+ return await indexWorkspacePathsUnchecked(ctx, changedPaths);
32
+ }
33
+ catch (error) {
34
+ throw toEngineError(error, "Indexing changed paths failed", {
35
+ code: "ZVEC_GREP.ENGINE.INDEXING.WORKSPACE_FAILED",
36
+ context: workspaceIndexContext(ctx.workspaceIndex),
37
+ });
38
+ }
39
+ }
40
+ export async function getWorkspaceIndexStatus(workspaceIndex, storedFiles) {
41
+ try {
42
+ const scan = await scanRootPaths(workspaceIndex.id, workspaceIndex.rootPaths);
32
43
  const diff = await computeDiffFromFiles(scan.files, storedFiles);
33
44
  const pendingFiles = storedFiles.filter((file) => file.indexStatus?.indexedTime === null);
34
45
  const failedFiles = pendingFiles.filter((file) => file.indexStatus?.error !== undefined);
35
- const indexedFiles = storedFiles.filter((file) => file.indexStatus?.indexedTime !== undefined
36
- && file.indexStatus.indexedTime !== null);
46
+ const indexedFiles = storedFiles.filter((file) => file.indexStatus?.indexedTime !== undefined &&
47
+ file.indexStatus.indexedTime !== null);
37
48
  const entitiesIndexed = indexedFiles.reduce((count, file) => count + (file.indexStatus?.entityCount ?? 0), 0);
49
+ const fragmentsTruncated = indexedFiles.reduce((count, file) => count + (file.indexStatus?.truncatedFragmentCount ?? 0), 0);
38
50
  return {
39
- collectionId: collection.id,
40
- collectionName: collection.name,
41
51
  filesScanned: scan.files.length,
42
52
  filesStored: storedFiles.length,
43
53
  filesIndexed: indexedFiles.length,
44
54
  entitiesIndexed,
55
+ fragmentsTruncated,
45
56
  filesPending: pendingFiles.length,
46
57
  filesFailed: failedFiles.length,
47
58
  filesAdded: diff.added.length,
@@ -56,36 +67,42 @@ export async function getCollectionIndexStatus(collection, storedFiles) {
56
67
  };
57
68
  }
58
69
  catch (error) {
59
- throw toEngineError(error, "Inspecting collection index status failed", {
70
+ throw toEngineError(error, "Inspecting workspace index status failed", {
60
71
  code: "ZVEC_GREP.ENGINE.INDEXING.STATUS_FAILED",
61
- context: collectionContext(collection),
72
+ context: workspaceIndexContext(workspaceIndex),
62
73
  });
63
74
  }
64
75
  }
65
- async function indexCollectionUnchecked(ctx) {
76
+ async function indexWorkspaceUnchecked(ctx) {
66
77
  const start = Date.now();
67
78
  const report = ctx.onProgress ?? (() => undefined);
68
79
  const timings = new TimingCollector();
80
+ throwIfIndexCancelled(ctx);
69
81
  const firstPass = await runIndexPass(ctx, report, "Scanning files...", timings);
70
82
  const passes = [firstPass];
83
+ let progressBase;
71
84
  if (firstPass.stats.filesFailed > 0) {
72
85
  const failed = firstPass.stats.filesFailed;
86
+ progressBase = retryProgressBase(firstPass);
73
87
  report({
74
88
  phase: "scanning",
89
+ filesTotal: progressBase.filesTotal,
90
+ filesIndexed: progressBase.filesSucceeded,
75
91
  detail: `Retrying ${failed} failed ${failed === 1 ? "file" : "files"}...`,
76
92
  });
77
- passes.push(await runIndexPass(ctx, report, "Scanning retry candidates...", timings));
93
+ passes.push(await runIndexPass(ctx, report, "Scanning retry candidates...", timings, progressBase));
78
94
  }
79
95
  const finalPass = passes[passes.length - 1];
80
- reportIndexFinalizing(ctx, report, finalPass);
81
- timings.timeSync("index_optimize", () => optimizeStorage(ctx));
96
+ throwIfIndexCancelled(ctx);
97
+ reportIndexFinalizing(ctx, report, finalPass, progressBase);
98
+ await timings.time("index_optimize", () => optimizeStorage(ctx));
82
99
  const result = buildIndexResult(ctx, passes, Date.now() - start, timings);
83
100
  if (result.filesFailed > 0) {
84
101
  report({ phase: "done", detail: "Indexing completed with failed files" });
85
102
  throw new EngineError(`Indexing completed with ${result.filesFailed} failed ${result.filesFailed === 1 ? "file" : "files"}`, {
86
103
  code: "ZVEC_GREP.ENGINE.INDEXING.FILES_FAILED",
87
104
  context: errorDetails([
88
- collectionDetail(ctx.collection.name),
105
+ workspaceIndexDetail(ctx.workspaceIndex.name),
89
106
  detail("filesFailed", result.filesFailed),
90
107
  detail("filesScanned", result.filesScanned),
91
108
  detail("failedFiles", summarizeFailedFiles(finalPass.stats.failedFiles)),
@@ -99,18 +116,78 @@ async function indexCollectionUnchecked(ctx) {
99
116
  report({ phase: "done", detail: "Indexing complete" });
100
117
  return result;
101
118
  }
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));
119
+ async function indexWorkspacePathsUnchecked(ctx, changedPaths) {
120
+ const start = Date.now();
121
+ const report = ctx.onProgress ?? (() => undefined);
122
+ const timings = new TimingCollector();
123
+ const normalizedPaths = [...new Set(changedPaths.map(normalizePath))];
124
+ throwIfIndexCancelled(ctx);
125
+ const firstPass = await runPathIndexPass(ctx, report, normalizedPaths, timings);
126
+ const passes = [firstPass];
127
+ let progressBase;
128
+ if (firstPass.stats.filesFailed > 0) {
129
+ progressBase = retryProgressBase(firstPass);
130
+ passes.push(await runPathIndexPass(ctx, report, normalizedPaths, timings, progressBase));
131
+ }
132
+ const finalPass = passes[passes.length - 1];
133
+ throwIfIndexCancelled(ctx);
134
+ reportIndexFinalizing(ctx, report, finalPass, progressBase);
135
+ await timings.time("index_optimize", () => optimizeStorage(ctx));
136
+ const result = buildIndexResult(ctx, passes, Date.now() - start, timings);
137
+ if (result.filesFailed > 0) {
138
+ throw new EngineError(`Indexing completed with ${result.filesFailed} failed files`, {
139
+ code: "ZVEC_GREP.ENGINE.INDEXING.FILES_FAILED",
140
+ context: workspaceIndexContext(ctx.workspaceIndex),
141
+ });
142
+ }
143
+ report({ phase: "done", detail: "Indexing complete" });
144
+ return result;
145
+ }
146
+ async function runPathIndexPass(ctx, report, changedPaths, timings, progressBase) {
147
+ reportScanning(report, "Scanning changed paths...", progressBase);
148
+ const scanned = await timings.time("index_scan_paths", async () => {
149
+ const files = [];
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
+ }
160
+ return [...new Map(files.map((file) => [file.id, file])).values()];
161
+ });
162
+ throwIfIndexCancelled(ctx);
163
+ const existing = [
164
+ ...new Map(changedPaths
165
+ .flatMap((path) => ctx.storage.listFilesByPathPrefix(path))
166
+ .map((file) => [file.id, file])).values(),
167
+ ];
168
+ return runDiffPass(ctx, report, scanned, existing, timings, progressBase);
169
+ }
170
+ async function runIndexPass(ctx, report, scanningDetail, timings, progressBase) {
171
+ reportScanning(report, scanningDetail, progressBase);
172
+ const scan = await timings.time("index_scan", () => scanRootPaths(ctx.workspaceIndex.id, ctx.workspaceIndex.rootPaths, {
173
+ signal: ctx.signal,
174
+ }));
175
+ throwIfIndexCancelled(ctx);
176
+ return runDiffPass(ctx, report, scan.files, ctx.storage.listFiles(), timings, progressBase);
177
+ }
178
+ async function runDiffPass(ctx, report, scannedFiles, existingFiles, timings, progressBase) {
179
+ const diff = await timings.time("index_diff", () => computeDiffFromFiles(scannedFiles, existingFiles));
180
+ throwIfIndexCancelled(ctx);
106
181
  const pending = [...diff.added, ...diff.modified, ...diff.pending];
107
182
  report({
108
183
  phase: "scanning",
109
- filesTotal: scan.files.length,
184
+ filesTotal: progressBase?.filesTotal ?? scannedFiles.length,
185
+ filesIndexed: progressBase?.filesSucceeded ?? 0,
110
186
  detail: `${diff.added.length} added, ${diff.modified.length} modified, ${diff.pending.length} pending, ${diff.deleted.length} deleted, ${diff.unchanged.length} unchanged`,
111
187
  });
112
188
  timings.timeSync("index_delete_stale", () => {
113
189
  for (const file of diff.deleted) {
190
+ throwIfIndexCancelled(ctx);
114
191
  try {
115
192
  ctx.storage.deleteFile(file.id);
116
193
  }
@@ -125,46 +202,78 @@ async function runIndexPass(ctx, report, scanningDetail, timings) {
125
202
  const reportIndexing = (currentStats, detail, embedding) => {
126
203
  report({
127
204
  phase: "indexing",
128
- filesTotal: pending.length,
129
- filesIndexed: currentStats.filesIndexed + currentStats.filesFailed,
205
+ filesTotal: progressBase?.filesTotal ?? pending.length,
206
+ filesIndexed: (progressBase?.filesSucceeded ?? 0) +
207
+ currentStats.filesIndexed +
208
+ currentStats.filesFailed,
130
209
  filesFailed: currentStats.filesFailed,
131
210
  detail,
132
211
  embedding,
133
212
  });
134
213
  };
135
- reportIndexing({ filesIndexed: 0, filesFailed: 0, failedFiles: [], failedFileReasons: [], entitiesCreated: 0 });
214
+ reportIndexing({
215
+ filesIndexed: 0,
216
+ filesFailed: 0,
217
+ failedFiles: [],
218
+ failedFileReasons: [],
219
+ entitiesCreated: 0,
220
+ });
136
221
  const stats = await indexFiles(pending, ctx, reportIndexing, timings);
222
+ throwIfIndexCancelled(ctx);
137
223
  return {
138
- filesScanned: scan.files.length,
224
+ filesScanned: scannedFiles.length,
139
225
  diff,
140
226
  stats,
141
227
  };
142
228
  }
143
- function reportIndexFinalizing(ctx, report, pass) {
229
+ function reportIndexFinalizing(ctx, report, pass, progressBase) {
144
230
  report({
145
231
  phase: "indexing",
146
- filesTotal: pass.diff.added.length + pass.diff.modified.length + pass.diff.pending.length,
147
- filesIndexed: pass.stats.filesIndexed + pass.stats.filesFailed,
232
+ filesTotal: progressBase?.filesTotal ??
233
+ pass.diff.added.length +
234
+ pass.diff.modified.length +
235
+ pass.diff.pending.length,
236
+ filesIndexed: (progressBase?.filesSucceeded ?? 0) +
237
+ pass.stats.filesIndexed +
238
+ pass.stats.filesFailed,
148
239
  filesFailed: pass.stats.filesFailed,
149
240
  detail: "finalizing index",
150
241
  });
151
242
  }
243
+ function retryProgressBase(pass) {
244
+ return {
245
+ filesSucceeded: pass.stats.filesIndexed,
246
+ filesTotal: pass.diff.added.length +
247
+ pass.diff.modified.length +
248
+ pass.diff.pending.length,
249
+ };
250
+ }
251
+ function reportScanning(report, detail, progressBase) {
252
+ report({
253
+ phase: "scanning",
254
+ ...(progressBase
255
+ ? {
256
+ filesTotal: progressBase.filesTotal,
257
+ filesIndexed: progressBase.filesSucceeded,
258
+ }
259
+ : {}),
260
+ detail,
261
+ });
262
+ }
152
263
  function buildIndexResult(ctx, passes, durationMs, timings) {
153
264
  const firstPass = passes[0];
154
265
  const retryPasses = passes.slice(1);
155
266
  const finalPass = passes[passes.length - 1];
156
267
  return {
157
- collectionId: ctx.collection.id,
158
- collectionName: ctx.collection.name,
159
268
  filesScanned: finalPass.filesScanned,
160
- filesAdded: firstPass.diff.added.length
161
- + retryPasses.reduce((count, pass) => count + pass.diff.added.length, 0),
162
- filesModified: firstPass.diff.modified.length
163
- + retryPasses.reduce((count, pass) => count + pass.diff.modified.length, 0),
164
- filesPending: firstPass.diff.pending.length
165
- + retryPasses.reduce((count, pass) => count + pass.diff.pending.length, 0),
166
- filesDeleted: firstPass.diff.deleted.length
167
- + retryPasses.reduce((count, pass) => count + pass.diff.deleted.length, 0),
269
+ filesAdded: firstPass.diff.added.length +
270
+ retryPasses.reduce((count, pass) => count + pass.diff.added.length, 0),
271
+ filesModified: firstPass.diff.modified.length +
272
+ retryPasses.reduce((count, pass) => count + pass.diff.modified.length, 0),
273
+ filesPending: firstPass.diff.pending.length +
274
+ retryPasses.reduce((count, pass) => count + pass.diff.pending.length, 0),
275
+ filesDeleted: firstPass.diff.deleted.length +
276
+ retryPasses.reduce((count, pass) => count + pass.diff.deleted.length, 0),
168
277
  filesUnchanged: firstPass.diff.unchanged.length,
169
278
  filesFailed: finalPass.stats.filesFailed,
170
279
  entitiesCreated: passes.reduce((count, pass) => count + pass.stats.entitiesCreated, 0),
@@ -172,20 +281,17 @@ function buildIndexResult(ctx, passes, durationMs, timings) {
172
281
  timings: timings.entries(),
173
282
  };
174
283
  }
175
- function optimizeStorage(ctx) {
284
+ async function optimizeStorage(ctx) {
176
285
  try {
177
- ctx.storage.optimize();
286
+ await ctx.storage.optimize();
178
287
  }
179
288
  catch (error) {
180
289
  throw toEngineError(error, "Indexing failed to finalize storage", {
181
290
  code: "ZVEC_GREP.ENGINE.INDEXING.OPTIMIZE_FAILED",
182
- context: collectionContext(ctx.collection),
291
+ context: workspaceIndexContext(ctx.workspaceIndex),
183
292
  });
184
293
  }
185
294
  }
186
- async function computeDiff(scannedFiles, storage) {
187
- return computeDiffFromFiles(scannedFiles, storage.listFiles());
188
- }
189
295
  async function computeDiffFromFiles(scannedFiles, existingFiles) {
190
296
  const existingById = new Map(existingFiles.map((file) => [file.id, file]));
191
297
  const seen = new Set();
@@ -204,15 +310,15 @@ async function computeDiffFromFiles(scannedFiles, existingFiles) {
204
310
  pending.push(await withContentHash(file));
205
311
  continue;
206
312
  }
207
- if (existing.sizeBytes === file.sizeBytes
208
- && existing.lastModifiedTime === file.lastModifiedTime
209
- && existing.contentHash) {
313
+ if (existing.sizeBytes === file.sizeBytes &&
314
+ existing.lastModifiedTime === file.lastModifiedTime &&
315
+ existing.contentHash) {
210
316
  unchanged.push(existing);
211
317
  continue;
212
318
  }
213
319
  const hashed = await withContentHash(file);
214
- if (existing.sizeBytes === hashed.sizeBytes
215
- && existing.contentHash === hashed.contentHash) {
320
+ if (existing.sizeBytes === hashed.sizeBytes &&
321
+ existing.contentHash === hashed.contentHash) {
216
322
  unchanged.push(existing);
217
323
  continue;
218
324
  }
@@ -234,10 +340,12 @@ async function indexFiles(files, ctx, onProgress, timings) {
234
340
  const embeddingScheduler = createEmbeddingScheduler(ctx.embeddingConcurrency, ctx.embeddingModel);
235
341
  const embeddingTiming = new ConcurrentTiming(timings, "index_embedding");
236
342
  const runningEmbeddings = new Set();
343
+ const chunkOptions = indexChunkOptions(ctx);
237
344
  const reportEmbeddingProgress = (currentStats, detail) => {
238
345
  onProgress(currentStats, detail, embeddingScheduler.snapshot());
239
346
  };
240
347
  const flushBatch = async () => {
348
+ throwIfIndexCancelled(ctx);
241
349
  if (batchFiles.length === 0) {
242
350
  return;
243
351
  }
@@ -246,8 +354,8 @@ async function indexFiles(files, ctx, onProgress, timings) {
246
354
  await scheduleEmbeddingTask(() => embedAndCommitBatch(filesToEmbed, ctx, stats, onProgress, embeddingScheduler, timings, embeddingTiming));
247
355
  };
248
356
  const scheduleEmbeddingTask = async (task) => {
249
- const promise = task()
250
- .finally(() => {
357
+ throwIfIndexCancelled(ctx);
358
+ const promise = task().finally(() => {
251
359
  runningEmbeddings.delete(promise);
252
360
  });
253
361
  runningEmbeddings.add(promise);
@@ -255,70 +363,109 @@ async function indexFiles(files, ctx, onProgress, timings) {
255
363
  await Promise.race(runningEmbeddings);
256
364
  }
257
365
  };
258
- for (const file of files) {
259
- onProgress(stats, `reading ${file.relativePath}`);
260
- const prepared = await timings.time("index_prepare", () => prepareFile(file, ctx));
261
- if ("failedReason" in prepared) {
262
- recordFileFailed(stats, file, prepared.failedReason);
263
- onProgress(stats, `failed ${file.relativePath}`);
264
- continue;
265
- }
266
- if (prepared.fragments.length === 0) {
267
- const committed = commitFile(prepared, [], ctx, stats);
268
- onProgress(stats, finishedFileDetail(committed, file.relativePath));
269
- continue;
270
- }
271
- if (prepared.fragments.length > ctx.embeddingModel.limits.maxBatchSize) {
272
- await flushBatch();
273
- await scheduleEmbeddingTask(async () => {
274
- reportEmbeddingProgress(stats, `embedding ${file.relativePath}`);
275
- await embedAndCommitFile(prepared, ctx, stats, onProgress, embeddingScheduler, timings, embeddingTiming);
276
- });
277
- continue;
278
- }
279
- if (batchFragmentCount > 0
280
- && batchFragmentCount + prepared.fragments.length > ctx.embeddingModel.limits.maxBatchSize) {
281
- await flushBatch();
282
- }
283
- batchFiles.push(prepared);
284
- batchFragmentCount += prepared.fragments.length;
285
- if (batchFragmentCount === ctx.embeddingModel.limits.maxBatchSize) {
286
- await flushBatch();
366
+ try {
367
+ for (const file of files) {
368
+ throwIfIndexCancelled(ctx);
369
+ onProgress(stats, `reading ${file.relativePath}`);
370
+ const prepared = await timings.time("index_prepare", () => prepareFile(file, ctx, chunkOptions));
371
+ throwIfIndexCancelled(ctx);
372
+ if ("failedReason" in prepared) {
373
+ recordFileFailed(stats, file, prepared.failedReason);
374
+ onProgress(stats, `failed ${file.relativePath}`);
375
+ continue;
376
+ }
377
+ if (prepared.fragments.length === 0) {
378
+ const committed = commitFile(prepared, [], ctx, stats);
379
+ onProgress(stats, finishedFileDetail(committed, file.relativePath));
380
+ continue;
381
+ }
382
+ if (prepared.fragments.length > ctx.embeddingModel.info.limits.maxBatchSize) {
383
+ await flushBatch();
384
+ await scheduleEmbeddingTask(async () => {
385
+ throwIfIndexCancelled(ctx);
386
+ reportEmbeddingProgress(stats, `embedding ${file.relativePath}`);
387
+ await embedAndCommitFile(prepared, ctx, stats, onProgress, embeddingScheduler, timings, embeddingTiming);
388
+ });
389
+ continue;
390
+ }
391
+ if (batchFragmentCount > 0 &&
392
+ batchFragmentCount + prepared.fragments.length >
393
+ ctx.embeddingModel.info.limits.maxBatchSize) {
394
+ await flushBatch();
395
+ }
396
+ batchFiles.push(prepared);
397
+ batchFragmentCount += prepared.fragments.length;
398
+ if (batchFragmentCount === ctx.embeddingModel.info.limits.maxBatchSize) {
399
+ await flushBatch();
400
+ }
287
401
  }
402
+ await flushBatch();
403
+ await Promise.all(runningEmbeddings);
288
404
  }
289
- await flushBatch();
290
- await Promise.all(runningEmbeddings);
405
+ catch (error) {
406
+ await Promise.allSettled(runningEmbeddings);
407
+ throw error;
408
+ }
409
+ throwIfIndexCancelled(ctx);
291
410
  return stats;
292
411
  }
293
- async function prepareFile(file, ctx) {
412
+ async function prepareFile(file, ctx, chunkOptions) {
294
413
  try {
414
+ throwIfIndexCancelled(ctx);
295
415
  const source = await readSource(file);
296
- const extracted = await new ExtractorRegistry().extract(source);
297
- const fragments = extracted.filter((fragment) => ctx.embeddingModel.supportedContentKinds.includes(fragment.content.kind));
416
+ const extracted = await extract(source, chunkOptions);
417
+ throwIfIndexCancelled(ctx);
418
+ const fragments = extracted.filter((fragment) => ctx.embeddingModel.info.inputKinds.includes(fragment.content.kind));
298
419
  return { file, fragments };
299
420
  }
300
421
  catch (error) {
422
+ if (indexIsCancelled(ctx)) {
423
+ throw indexCancellationError(ctx);
424
+ }
301
425
  return {
302
426
  file,
303
427
  failedReason: markFileFailed(ctx, file, error, "prepare"),
304
428
  };
305
429
  }
306
430
  }
431
+ function indexChunkOptions(ctx) {
432
+ const maxInputTokens = ctx.embeddingModel.info.limits.maxInputTokens;
433
+ if (maxInputTokens === undefined) {
434
+ return {};
435
+ }
436
+ const maxChunkChars = Math.floor(maxInputTokens * APPROXIMATE_CHARS_PER_TOKEN);
437
+ const chunkOverlapChars = Math.floor(maxChunkChars * CHUNK_OVERLAP_RATIO);
438
+ return { maxChunkChars, chunkOverlapChars };
439
+ }
307
440
  async function embedAndCommitBatch(files, ctx, stats, onProgress, embeddingScheduler, timings, embeddingTiming) {
308
441
  const fragments = files.flatMap((file) => file.fragments);
309
442
  try {
443
+ throwIfIndexCancelled(ctx);
310
444
  const contents = fragments.map(vectorContentForFragment);
311
445
  onProgress(stats, `embedding ${describePreparedFiles(files)}`, embeddingScheduler.snapshot());
312
- const vectors = await embeddingTiming.time(() => embedContentsWithRetry(contents, ctx.embeddingModel, embeddingScheduler));
446
+ const embedding = await embeddingTiming.time(() => embedContentsWithRetry(contents, ctx.embeddingModel, embeddingScheduler, ctx.signal, (progress) => reportModelDownloadProgress(stats, progress, onProgress, embeddingScheduler)));
447
+ const truncatedInputIndexes = new Set(embedding.truncated);
448
+ throwIfIndexCancelled(ctx);
313
449
  let offset = 0;
314
450
  for (const file of files) {
315
- const fileVectors = vectors.slice(offset, offset + file.fragments.length);
451
+ throwIfIndexCancelled(ctx);
452
+ const end = offset + file.fragments.length;
453
+ const fileVectors = embedding.vectors.slice(offset, end);
454
+ let truncatedFragmentCount = 0;
455
+ for (let index = offset; index < end; index++) {
456
+ if (truncatedInputIndexes.has(index)) {
457
+ truncatedFragmentCount++;
458
+ }
459
+ }
316
460
  offset += file.fragments.length;
317
- const committed = timings.timeSync("index_commit", () => commitFile(file, fileVectors, ctx, stats));
461
+ const committed = timings.timeSync("index_commit", () => commitFile(file, fileVectors, ctx, stats, truncatedFragmentCount));
318
462
  onProgress(stats, finishedFileDetail(committed, file.file.relativePath));
319
463
  }
320
464
  }
321
465
  catch (error) {
466
+ if (indexIsCancelled(ctx)) {
467
+ throw indexCancellationError(ctx);
468
+ }
322
469
  if (isRetryableEmbeddingError(error)) {
323
470
  for (const file of files) {
324
471
  const reason = markFileFailed(ctx, file.file, error, "embed");
@@ -335,29 +482,57 @@ async function embedAndCommitBatch(files, ctx, stats, onProgress, embeddingSched
335
482
  }
336
483
  async function embedAndCommitFile(file, ctx, stats, onProgress, embeddingScheduler, timings, embeddingTiming) {
337
484
  try {
338
- const vectors = await embeddingTiming.time(() => embedFragments(file.fragments, ctx.embeddingModel, embeddingScheduler));
339
- const committed = timings.timeSync("index_commit", () => commitFile(file, vectors, ctx, stats));
485
+ throwIfIndexCancelled(ctx);
486
+ const embedding = await embeddingTiming.time(() => embedFragments(file.fragments, ctx.embeddingModel, embeddingScheduler, ctx.signal, (progress) => reportModelDownloadProgress(stats, progress, onProgress, embeddingScheduler)));
487
+ throwIfIndexCancelled(ctx);
488
+ const committed = timings.timeSync("index_commit", () => commitFile(file, embedding.vectors, ctx, stats, embedding.truncated.length));
340
489
  onProgress(stats, finishedFileDetail(committed, file.file.relativePath));
341
490
  }
342
491
  catch (error) {
492
+ if (indexIsCancelled(ctx)) {
493
+ throw indexCancellationError(ctx);
494
+ }
343
495
  const reason = markFileFailed(ctx, file.file, error, "embed");
344
496
  recordFileFailed(stats, file.file, reason);
345
497
  onProgress(stats, finishedFileDetail(false, file.file.relativePath));
346
498
  }
347
499
  }
348
- function commitFile(file, vectors, ctx, stats) {
500
+ function commitFile(file, vectors, ctx, stats, truncatedFragmentCount = 0) {
349
501
  try {
350
- ctx.storage.upsertFile(file.file, file.fragments, vectors);
502
+ throwIfIndexCancelled(ctx);
503
+ ctx.storage.upsertFile(file.file, file.fragments, vectors, {
504
+ truncatedFragmentCount,
505
+ });
351
506
  stats.filesIndexed++;
352
507
  stats.entitiesCreated += countPublicEntities(file.fragments);
353
508
  return true;
354
509
  }
355
510
  catch (error) {
511
+ if (indexIsCancelled(ctx)) {
512
+ throw indexCancellationError(ctx);
513
+ }
356
514
  const reason = markFileFailed(ctx, file.file, error, "commit");
357
515
  recordFileFailed(stats, file.file, reason);
358
516
  return false;
359
517
  }
360
518
  }
519
+ function throwIfIndexCancelled(ctx) {
520
+ if (!indexIsCancelled(ctx)) {
521
+ return;
522
+ }
523
+ throw indexCancellationError(ctx);
524
+ }
525
+ function indexIsCancelled(ctx) {
526
+ return ctx.signal?.aborted === true;
527
+ }
528
+ function indexCancellationError(ctx) {
529
+ return ctx.signal?.reason instanceof Error
530
+ ? ctx.signal.reason
531
+ : new EngineError("Indexing was cancelled.", {
532
+ code: "ZVEC_GREP.ENGINE.INDEXING.CANCELLED",
533
+ context: workspaceIndexContext(ctx.workspaceIndex),
534
+ });
535
+ }
361
536
  function recordFileFailed(stats, file, reason) {
362
537
  stats.filesFailed++;
363
538
  stats.failedFiles.push(file.relativePath);
@@ -409,14 +584,15 @@ async function readSource(file) {
409
584
  text: bytes.toString("utf8"),
410
585
  };
411
586
  }
412
- async function embedFragments(fragments, model, embeddingScheduler) {
587
+ async function embedFragments(fragments, model, embeddingScheduler, signal, onModelProgress) {
413
588
  const batches = [];
414
- for (let start = 0; start < fragments.length; start += model.limits.maxBatchSize) {
415
- const batch = fragments.slice(start, start + model.limits.maxBatchSize);
589
+ for (let start = 0; start < fragments.length; start += model.info.limits.maxBatchSize) {
590
+ const batch = fragments.slice(start, start + model.info.limits.maxBatchSize);
416
591
  batches.push({ start, fragments: batch });
417
592
  }
418
593
  const vectors = new Array(fragments.length);
419
- const results = await Promise.allSettled(batches.map((batch) => embedFragmentBatch(batch.fragments, model, batch.start, embeddingScheduler)));
594
+ const truncatedInputIndexes = [];
595
+ const results = await Promise.allSettled(batches.map((batch) => embedFragmentBatch(batch.fragments, model, batch.start, embeddingScheduler, signal, onModelProgress)));
420
596
  let firstError;
421
597
  for (const [index, result] of results.entries()) {
422
598
  if (result.status === "rejected") {
@@ -424,43 +600,54 @@ async function embedFragments(fragments, model, embeddingScheduler) {
424
600
  continue;
425
601
  }
426
602
  const start = batches[index].start;
427
- for (const [offset, vector] of result.value.entries()) {
603
+ for (const [offset, vector] of result.value.vectors.entries()) {
428
604
  vectors[start + offset] = vector;
429
605
  }
606
+ truncatedInputIndexes.push(...result.value.truncated.map((inputIndex) => start + inputIndex));
430
607
  }
431
608
  if (firstError) {
432
609
  throw firstError;
433
610
  }
434
- return vectors;
611
+ return {
612
+ vectors,
613
+ truncated: truncatedInputIndexes,
614
+ };
435
615
  }
436
- async function embedFragmentBatch(fragments, model, startIndex, embeddingScheduler) {
616
+ async function embedFragmentBatch(fragments, model, startIndex, embeddingScheduler, signal, onModelProgress) {
437
617
  const contents = fragments.map(vectorContentForFragment);
438
618
  try {
439
- return await embedContentsWithRetry(contents, model, embeddingScheduler);
619
+ return await embedContentsWithRetry(contents, model, embeddingScheduler, signal, onModelProgress);
440
620
  }
441
621
  catch (error) {
442
622
  if (fragments.length === 1 || isRetryableEmbeddingError(error)) {
443
623
  throw error;
444
624
  }
445
- return await embedFragmentBatchOneByOne(fragments, model, startIndex, embeddingScheduler);
625
+ return await embedFragmentBatchOneByOne(fragments, model, startIndex, embeddingScheduler, signal, onModelProgress);
446
626
  }
447
627
  }
448
- async function embedFragmentBatchOneByOne(fragments, model, startIndex, embeddingScheduler) {
628
+ async function embedFragmentBatchOneByOne(fragments, model, startIndex, embeddingScheduler, signal, onModelProgress) {
449
629
  const vectors = [];
630
+ const truncatedInputIndexes = [];
450
631
  for (const [index, fragment] of fragments.entries()) {
451
632
  try {
452
- const [vector] = await embedContentsWithRetry([vectorContentForFragment(fragment)], model, embeddingScheduler);
453
- vectors.push(vector);
633
+ const result = await embedContentsWithRetry([vectorContentForFragment(fragment)], model, embeddingScheduler, signal, onModelProgress);
634
+ vectors.push(result.vectors[0]);
635
+ if (result.truncated.length > 0) {
636
+ truncatedInputIndexes.push(index);
637
+ }
454
638
  }
455
639
  catch (error) {
456
640
  throw new EngineError("Embedding entity fragment failed after one-by-one fallback", {
457
641
  code: "ZVEC_GREP.ENGINE.INDEXING.EMBEDDING_FRAGMENT_FAILED",
458
- context: `model=${model.ref.model} fragmentId=${fragment.id} fragmentIndex=${startIndex + index}`,
642
+ context: `model=${model.info.reference} fragmentId=${fragment.id} fragmentIndex=${startIndex + index}`,
459
643
  cause: error,
460
644
  });
461
645
  }
462
646
  }
463
- return vectors;
647
+ return {
648
+ vectors,
649
+ truncated: truncatedInputIndexes,
650
+ };
464
651
  }
465
652
  function vectorContentForFragment(fragment) {
466
653
  if (fragment.content.kind !== "text") {
@@ -486,13 +673,17 @@ function vectorMetadataText(metadata) {
486
673
  : `symbol: ${metadata.symbolType}`,
487
674
  metadata.scope ? `scope: ${metadata.scope}` : null,
488
675
  metadata.signature ? `signature: ${oneLine(metadata.signature)}` : null,
489
- metadata.modifiers.length > 0 ? `modifiers: ${metadata.modifiers.join(" ")}` : null,
676
+ metadata.modifiers.length > 0
677
+ ? `modifiers: ${metadata.modifiers.join(" ")}`
678
+ : null,
490
679
  metadata.doc ? `doc: ${oneLine(metadata.doc)}` : null,
491
680
  ]);
492
681
  }
493
682
  return compactMetadataLines([
494
683
  metadata.heading ? `heading: ${metadata.heading}` : null,
495
- typeof metadata.level === "number" ? `heading_level: ${metadata.level}` : null,
684
+ typeof metadata.level === "number"
685
+ ? `heading_level: ${metadata.level}`
686
+ : null,
496
687
  metadata.scope ? `scope: ${metadata.scope}` : null,
497
688
  ]);
498
689
  }
@@ -501,13 +692,18 @@ function compactMetadataLines(lines) {
501
692
  .filter((line) => line !== null && line.trim().length > 0)
502
693
  .join("\n");
503
694
  }
504
- async function embedContentsWithRetry(contents, model, embeddingScheduler) {
695
+ async function embedContentsWithRetry(contents, model, embeddingScheduler, signal, onModelProgress) {
505
696
  let attempt = 0;
506
697
  while (true) {
507
698
  let retry = nonRetryableEmbeddingError();
508
699
  let delayMs = 0;
509
700
  try {
510
- const vectors = await embeddingScheduler.run(() => model.embed(contents, { purpose: "document" }), (error) => {
701
+ throwIfAborted(signal);
702
+ const result = await embeddingScheduler.run(() => model.embed(contents, {
703
+ purpose: "document",
704
+ signal,
705
+ onProgress: onModelProgress,
706
+ }), signal, (error) => {
511
707
  retry = classifyEmbeddingRetry(error);
512
708
  delayMs = retryDelayMs(attempt, retry);
513
709
  if (retry.retryable) {
@@ -518,9 +714,14 @@ async function embedContentsWithRetry(contents, model, embeddingScheduler) {
518
714
  }
519
715
  });
520
716
  embeddingScheduler.recordSuccess();
521
- return vectors;
717
+ return result;
522
718
  }
523
719
  catch (error) {
720
+ if (signal?.aborted) {
721
+ throw signal.reason instanceof Error
722
+ ? signal.reason
723
+ : new Error("Embedding was cancelled.");
724
+ }
524
725
  if (!retry.retryable) {
525
726
  retry = classifyEmbeddingRetry(error);
526
727
  delayMs = retryDelayMs(attempt, retry);
@@ -528,18 +729,24 @@ async function embedContentsWithRetry(contents, model, embeddingScheduler) {
528
729
  if (attempt >= maxRetryAttempts(retry) || !retry.retryable) {
529
730
  throw error;
530
731
  }
531
- await delay(delayMs);
732
+ await abortableDelay(delayMs, signal);
532
733
  attempt++;
533
734
  }
534
735
  }
535
736
  }
737
+ function reportModelDownloadProgress(stats, progress, onProgress, embeddingScheduler) {
738
+ onProgress(stats, `downloading ${progress.model}`, {
739
+ ...embeddingScheduler.snapshot(),
740
+ ...progress,
741
+ });
742
+ }
536
743
  function createEmbeddingScheduler(requestedConcurrency, model) {
537
744
  return new AdaptiveEmbeddingScheduler(resolveEmbeddingConcurrencyPolicy(requestedConcurrency, model));
538
745
  }
539
746
  function resolveEmbeddingConcurrencyPolicy(requestedConcurrency, model) {
540
- if (requestedConcurrency !== undefined
541
- && Number.isInteger(requestedConcurrency)
542
- && requestedConcurrency > 0) {
747
+ if (requestedConcurrency !== undefined &&
748
+ Number.isInteger(requestedConcurrency) &&
749
+ requestedConcurrency > 0) {
543
750
  return {
544
751
  initial: requestedConcurrency,
545
752
  min: 1,
@@ -547,10 +754,11 @@ function resolveEmbeddingConcurrencyPolicy(requestedConcurrency, model) {
547
754
  adaptive: requestedConcurrency > 1,
548
755
  };
549
756
  }
550
- const initial = model.recommendedIndexConcurrency ?? DEFAULT_EMBEDDING_CONCURRENCY;
551
- const max = model.maxIndexConcurrency
552
- ?? (initial <= 1 ? initial : DEFAULT_EMBEDDING_MAX_CONCURRENCY);
553
- const min = Math.min(initial, DEFAULT_EMBEDDING_MIN_CONCURRENCY);
757
+ const remote = model.info.provider === "qwen";
758
+ const multimodal = model.info.inputKinds.includes("image");
759
+ const initial = remote ? (multimodal ? 4 : 8) : 1;
760
+ const max = remote ? (multimodal ? 8 : 12) : 1;
761
+ const min = Math.min(initial, 4);
554
762
  return {
555
763
  initial,
556
764
  min,
@@ -573,11 +781,13 @@ class AdaptiveEmbeddingScheduler {
573
781
  get taskConcurrency() {
574
782
  return this.policy.max;
575
783
  }
576
- async run(task, onError) {
577
- await this.waitForCooldown();
578
- await this.acquire();
784
+ async run(task, signal, onError) {
785
+ throwIfAborted(signal);
786
+ await this.waitForCooldown(signal);
787
+ await this.acquire(signal);
579
788
  try {
580
- await this.waitForCooldown();
789
+ await this.waitForCooldown(signal);
790
+ throwIfAborted(signal);
581
791
  return await task();
582
792
  }
583
793
  catch (error) {
@@ -593,7 +803,8 @@ class AdaptiveEmbeddingScheduler {
593
803
  return;
594
804
  }
595
805
  this.successStreak++;
596
- if (this.successStreak < Math.max(EMBEDDING_SUCCESS_STREAK_MIN, this.currentConcurrency * 2)) {
806
+ if (this.successStreak <
807
+ Math.max(EMBEDDING_SUCCESS_STREAK_MIN, this.currentConcurrency * 2)) {
597
808
  return;
598
809
  }
599
810
  this.currentConcurrency++;
@@ -618,19 +829,36 @@ class AdaptiveEmbeddingScheduler {
618
829
  retryableFailures: this.retryableFailures,
619
830
  };
620
831
  }
621
- async waitForCooldown() {
832
+ async waitForCooldown(signal) {
622
833
  const remaining = this.cooldownUntil - Date.now();
623
834
  if (remaining > 0) {
624
- await delay(remaining);
835
+ await abortableDelay(remaining, signal);
625
836
  }
626
837
  }
627
- async acquire() {
838
+ async acquire(signal) {
839
+ throwIfAborted(signal);
628
840
  if (this.active < this.currentConcurrency) {
629
841
  this.active++;
630
842
  return;
631
843
  }
632
- await new Promise((resolve) => {
633
- this.queue.push(resolve);
844
+ await new Promise((resolve, reject) => {
845
+ const queued = () => {
846
+ signal?.removeEventListener("abort", abort);
847
+ resolve();
848
+ };
849
+ const abort = () => {
850
+ const index = this.queue.indexOf(queued);
851
+ if (index >= 0) {
852
+ this.queue.splice(index, 1);
853
+ }
854
+ reject(abortError(signal));
855
+ };
856
+ if (signal?.aborted) {
857
+ reject(abortError(signal));
858
+ return;
859
+ }
860
+ signal?.addEventListener("abort", abort, { once: true });
861
+ this.queue.push(queued);
634
862
  });
635
863
  }
636
864
  release() {
@@ -659,8 +887,8 @@ function classifyEmbeddingRetry(error) {
659
887
  const context = isEngineError(error) && error.context ? ` ${error.context}` : "";
660
888
  const text = `${message}${context}`;
661
889
  const status = httpStatusFromText(text);
662
- const rateLimited = status === 429
663
- || /rate limit|quota exceeded|too many requests|request rate increased too quickly/i.test(text);
890
+ const rateLimited = status === 429 ||
891
+ /rate limit|quota exceeded|too many requests|request rate increased too quickly/i.test(text);
664
892
  const serverError = typeof status === "number" && status >= 500 && status <= 599;
665
893
  return {
666
894
  retryable: rateLimited || serverError,
@@ -711,8 +939,32 @@ function retryAfterMsFromText(text) {
711
939
  }
712
940
  return undefined;
713
941
  }
714
- function delay(ms) {
715
- return new Promise((resolve) => setTimeout(resolve, ms));
942
+ function abortableDelay(ms, signal) {
943
+ if (ms <= 0) {
944
+ return Promise.resolve();
945
+ }
946
+ throwIfAborted(signal);
947
+ return new Promise((resolve, reject) => {
948
+ const timer = setTimeout(() => {
949
+ signal?.removeEventListener("abort", abort);
950
+ resolve();
951
+ }, ms);
952
+ const abort = () => {
953
+ clearTimeout(timer);
954
+ reject(abortError(signal));
955
+ };
956
+ signal?.addEventListener("abort", abort, { once: true });
957
+ });
958
+ }
959
+ function throwIfAborted(signal) {
960
+ if (signal?.aborted) {
961
+ throw abortError(signal);
962
+ }
963
+ }
964
+ function abortError(signal) {
965
+ return signal?.reason instanceof Error
966
+ ? signal.reason
967
+ : new Error("Embedding was cancelled.");
716
968
  }
717
969
  function countPublicEntities(fragments) {
718
970
  let count = 0;
@@ -764,11 +1016,11 @@ function toEngineError(error, message, options) {
764
1016
  cause: error,
765
1017
  });
766
1018
  }
767
- function collectionContext(collection) {
768
- return errorDetails([
769
- detail("collection_id", collection.id),
770
- collectionDetail(collection.name),
771
- ]) ?? "";
1019
+ function workspaceIndexContext(workspaceIndex) {
1020
+ return (errorDetails([
1021
+ detail("workspace_index_id", workspaceIndex.id),
1022
+ workspaceIndexDetail(workspaceIndex.name),
1023
+ ]) ?? "");
772
1024
  }
773
1025
  function fileContext(file) {
774
1026
  return `fileId=${file.id} path=${file.relativePath}`;