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