opencode-rag-plugin 1.15.1 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReadMe.md +4 -6
- package/dist/api.js +44 -24
- package/dist/chunker/base.d.ts +34 -3
- package/dist/chunker/base.js +70 -21
- package/dist/chunker/factory.d.ts +4 -1
- package/dist/chunker/factory.js +12 -1
- package/dist/chunker/grammar.js +3 -0
- package/dist/chunker/image.js +8 -8
- package/dist/chunker/pdf.js +11 -13
- package/dist/chunker/xml.d.ts +2 -0
- package/dist/chunker/xml.js +2 -0
- package/dist/cli/commands/index-command.js +0 -4
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/init-helpers.d.ts +12 -0
- package/dist/cli/commands/init-helpers.js +84 -2
- package/dist/cli/commands/init.js +20 -2
- package/dist/cli/commands/query.js +1 -0
- package/dist/cli/commands/setup.d.ts +2 -0
- package/dist/cli/commands/setup.js +113 -0
- package/dist/cli/commands/status.js +64 -2
- package/dist/cli/index.js +2 -2
- package/dist/content/image.js +24 -3
- package/dist/content/reader.d.ts +6 -1
- package/dist/content/reader.js +49 -4
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.js +34 -4
- package/dist/core/desc-cache.d.ts +31 -0
- package/dist/core/desc-cache.js +124 -0
- package/dist/core/interfaces.d.ts +58 -3
- package/dist/core/manifest.d.ts +24 -1
- package/dist/core/manifest.js +34 -3
- package/dist/core/resolve-api-key.js +4 -2
- package/dist/core/runtime-overrides.js +9 -7
- package/dist/core/setup-runtime.d.ts +23 -0
- package/dist/core/setup-runtime.js +183 -0
- package/dist/core/version-check.d.ts +9 -0
- package/dist/core/version-check.js +49 -0
- package/dist/describer/anthropic.d.ts +2 -2
- package/dist/describer/anthropic.js +5 -7
- package/dist/describer/describer.d.ts +2 -2
- package/dist/describer/describer.js +6 -8
- package/dist/describer/gemini.d.ts +2 -2
- package/dist/describer/gemini.js +5 -7
- package/dist/embedder/factory.d.ts +3 -1
- package/dist/embedder/factory.js +7 -1
- package/dist/embedder/health.js +4 -0
- package/dist/embedder/http.d.ts +1 -1
- package/dist/embedder/http.js +74 -43
- package/dist/eval/compare-merge.d.ts +10 -0
- package/dist/eval/compare-merge.js +537 -0
- package/dist/eval/compare-rankings.d.ts +10 -0
- package/dist/eval/compare-rankings.js +245 -0
- package/dist/eval/dump-descriptions.d.ts +7 -0
- package/dist/eval/dump-descriptions.js +58 -0
- package/dist/eval/fast-index.d.ts +8 -0
- package/dist/eval/fast-index.js +283 -0
- package/dist/eval/run-branch-compare.d.ts +7 -0
- package/dist/eval/run-branch-compare.js +220 -0
- package/dist/eval/run-token-test.js +1 -0
- package/dist/eval/test-kw.d.ts +1 -0
- package/dist/eval/test-kw.js +22 -0
- package/dist/eval/update-descriptions.d.ts +7 -0
- package/dist/eval/update-descriptions.js +84 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/indexer/embed-stage.js +2 -1
- package/dist/indexer/git-diff.js +21 -9
- package/dist/indexer/pipeline.d.ts +6 -0
- package/dist/indexer/pipeline.js +290 -37
- package/dist/indexer/watch.js +1 -3
- package/dist/indexer/worker.d.ts +15 -2
- package/dist/indexer/worker.js +25 -12
- package/dist/mcp/handlers.d.ts +9 -0
- package/dist/mcp/handlers.js +23 -6
- package/dist/mcp/server.js +2 -0
- package/dist/opencode/create-read-tool.d.ts +2 -0
- package/dist/opencode/create-read-tool.js +8 -2
- package/dist/opencode/read-fallback.d.ts +1 -5
- package/dist/opencode/read-fallback.js +1 -18
- package/dist/opencode/read-format.js +5 -3
- package/dist/opencode/tools.js +5 -7
- package/dist/plugin.js +141 -57
- package/dist/retriever/keyword-index.d.ts +3 -2
- package/dist/retriever/keyword-index.js +25 -1
- package/dist/retriever/retriever.d.ts +4 -1
- package/dist/retriever/retriever.js +34 -56
- package/dist/vectorstore/lancedb.d.ts +35 -4
- package/dist/vectorstore/lancedb.js +146 -23
- package/dist/vectorstore/memory.d.ts +6 -1
- package/dist/vectorstore/memory.js +58 -0
- package/dist/watcher.js +3 -0
- package/dist/web/api.js +10 -2
- package/dist/web/server.js +18 -3
- package/package.json +8 -9
- package/scripts/postinstall-setup.js +82 -0
- package/dist/api.js.map +0 -1
- package/dist/chunker/base.js.map +0 -1
- package/dist/chunker/bash.js.map +0 -1
- package/dist/chunker/c.js.map +0 -1
- package/dist/chunker/cpp.js.map +0 -1
- package/dist/chunker/csharp.js.map +0 -1
- package/dist/chunker/css.js.map +0 -1
- package/dist/chunker/doc.js.map +0 -1
- package/dist/chunker/dockerfile.js.map +0 -1
- package/dist/chunker/docx.js.map +0 -1
- package/dist/chunker/excel.js.map +0 -1
- package/dist/chunker/factory.js.map +0 -1
- package/dist/chunker/fallback.js.map +0 -1
- package/dist/chunker/go.js.map +0 -1
- package/dist/chunker/grammar.js.map +0 -1
- package/dist/chunker/html.js.map +0 -1
- package/dist/chunker/image.js.map +0 -1
- package/dist/chunker/ini.js.map +0 -1
- package/dist/chunker/java.js.map +0 -1
- package/dist/chunker/javascript.js.map +0 -1
- package/dist/chunker/json.js.map +0 -1
- package/dist/chunker/kotlin.js.map +0 -1
- package/dist/chunker/loader.js.map +0 -1
- package/dist/chunker/markdown.js.map +0 -1
- package/dist/chunker/pdf.js.map +0 -1
- package/dist/chunker/php.js.map +0 -1
- package/dist/chunker/powershell.js.map +0 -1
- package/dist/chunker/python.js.map +0 -1
- package/dist/chunker/razor.js.map +0 -1
- package/dist/chunker/ruby.js.map +0 -1
- package/dist/chunker/rust.js.map +0 -1
- package/dist/chunker/sln.js.map +0 -1
- package/dist/chunker/sql.js.map +0 -1
- package/dist/chunker/ssl.js.map +0 -1
- package/dist/chunker/swift.js.map +0 -1
- package/dist/chunker/tex.js.map +0 -1
- package/dist/chunker/toml.js.map +0 -1
- package/dist/chunker/typescript.js.map +0 -1
- package/dist/chunker/uuid.js.map +0 -1
- package/dist/chunker/xml.js.map +0 -1
- package/dist/chunker/yaml.js.map +0 -1
- package/dist/cli/commands/clear.js.map +0 -1
- package/dist/cli/commands/describe-image.js.map +0 -1
- package/dist/cli/commands/dump.js.map +0 -1
- package/dist/cli/commands/eval.js.map +0 -1
- package/dist/cli/commands/index-command.js.map +0 -1
- package/dist/cli/commands/index.js.map +0 -1
- package/dist/cli/commands/init-helpers.js.map +0 -1
- package/dist/cli/commands/init.js.map +0 -1
- package/dist/cli/commands/list.js.map +0 -1
- package/dist/cli/commands/mcp.js.map +0 -1
- package/dist/cli/commands/query.js.map +0 -1
- package/dist/cli/commands/show.js.map +0 -1
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/ui.js.map +0 -1
- package/dist/cli/commands/update.d.ts +0 -17
- package/dist/cli/commands/update.js +0 -79
- package/dist/cli/commands/update.js.map +0 -1
- package/dist/cli/format.js.map +0 -1
- package/dist/cli/helpers.js.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/progress.d.ts +0 -42
- package/dist/cli/progress.js +0 -137
- package/dist/cli/progress.js.map +0 -1
- package/dist/cli/types.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/content/doc.js.map +0 -1
- package/dist/content/docx.js.map +0 -1
- package/dist/content/excel.js.map +0 -1
- package/dist/content/image.js.map +0 -1
- package/dist/content/pdf.js.map +0 -1
- package/dist/content/reader.js.map +0 -1
- package/dist/content/types.js.map +0 -1
- package/dist/core/bootstrap.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/doc-progress.js.map +0 -1
- package/dist/core/fileLogger.js.map +0 -1
- package/dist/core/interfaces.js.map +0 -1
- package/dist/core/manifest.js.map +0 -1
- package/dist/core/provider-defaults.js.map +0 -1
- package/dist/core/rag-injection-flag.js.map +0 -1
- package/dist/core/resolve-api-key.js.map +0 -1
- package/dist/core/runtime-overrides.js.map +0 -1
- package/dist/describer/anthropic.js.map +0 -1
- package/dist/describer/describer.js.map +0 -1
- package/dist/describer/factory.js.map +0 -1
- package/dist/describer/gemini.js.map +0 -1
- package/dist/describer/shared.js.map +0 -1
- package/dist/embedder/cohere.js.map +0 -1
- package/dist/embedder/factory.js.map +0 -1
- package/dist/embedder/health.js.map +0 -1
- package/dist/embedder/http.js.map +0 -1
- package/dist/embedder/ollama.js.map +0 -1
- package/dist/embedder/openai.js.map +0 -1
- package/dist/eval/index.js.map +0 -1
- package/dist/eval/run-token-test.js.map +0 -1
- package/dist/eval/session-logger.js.map +0 -1
- package/dist/eval/storage.js.map +0 -1
- package/dist/eval/token-analysis.js.map +0 -1
- package/dist/eval/token-counter.js.map +0 -1
- package/dist/eval/types.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/indexer/description-stage.js.map +0 -1
- package/dist/indexer/embed-stage.js.map +0 -1
- package/dist/indexer/git-diff.js.map +0 -1
- package/dist/indexer/metadata.js.map +0 -1
- package/dist/indexer/pipeline.js.map +0 -1
- package/dist/indexer/stats.js.map +0 -1
- package/dist/indexer/watch.js.map +0 -1
- package/dist/indexer/worker.js.map +0 -1
- package/dist/indexer.js.map +0 -1
- package/dist/mcp/cli.js.map +0 -1
- package/dist/mcp/handlers.js.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/opencode/create-read-tool.js.map +0 -1
- package/dist/opencode/read-fallback.js.map +0 -1
- package/dist/opencode/read-format.js.map +0 -1
- package/dist/opencode/read-query.js.map +0 -1
- package/dist/opencode/tool-args.js.map +0 -1
- package/dist/opencode/tools.js.map +0 -1
- package/dist/plugin-entry.js.map +0 -1
- package/dist/plugin.js.map +0 -1
- package/dist/retriever/context-optimizer.js.map +0 -1
- package/dist/retriever/keyword-index.js.map +0 -1
- package/dist/retriever/retriever.js.map +0 -1
- package/dist/tui.js.map +0 -1
- package/dist/updater.d.ts +0 -45
- package/dist/updater.js +0 -175
- package/dist/updater.js.map +0 -1
- package/dist/vectorstore/factory.js.map +0 -1
- package/dist/vectorstore/lancedb.js.map +0 -1
- package/dist/vectorstore/memory.js.map +0 -1
- package/dist/watcher.js.map +0 -1
- package/dist/web/api.js.map +0 -1
- package/dist/web/server.js.map +0 -1
- package/dist/web/static.js.map +0 -1
package/dist/mcp/handlers.js
CHANGED
|
@@ -4,7 +4,26 @@ import { optimizeContext, DEFAULT_CONTEXT_OPTIMIZATION } from "../retriever/cont
|
|
|
4
4
|
import { Parser } from "web-tree-sitter";
|
|
5
5
|
import { initParser, loadLanguage, walkTree } from "../chunker/grammar.js";
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
7
|
-
import { resolve, isAbsolute } from "node:path";
|
|
7
|
+
import { resolve, isAbsolute, relative } from "node:path";
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a user-supplied file path against the worktree root, refusing to
|
|
10
|
+
* escape the worktree. Absolute paths are accepted only if they already reside
|
|
11
|
+
* under the worktree; relative paths are joined to the worktree and any `..`
|
|
12
|
+
* segments that would escape are rejected.
|
|
13
|
+
*
|
|
14
|
+
* @throws {Error} if the resolved path lies outside `worktree`.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveFilePath(filePath, worktree) {
|
|
17
|
+
const root = resolve(worktree);
|
|
18
|
+
const candidate = isAbsolute(filePath) ? resolve(filePath) : resolve(root, filePath);
|
|
19
|
+
const rel = relative(root, candidate);
|
|
20
|
+
if (rel === "")
|
|
21
|
+
return root;
|
|
22
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
23
|
+
throw new Error(`Path "${filePath}" escapes worktree root "${root}" — access denied.`);
|
|
24
|
+
}
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
8
27
|
const SKELETON_CONFIGS = {
|
|
9
28
|
".ts": { grammarName: "typescript", nodeTypes: ["function_declaration", "method_definition", "class_declaration", "interface_declaration", "type_alias_declaration", "enum_declaration"] },
|
|
10
29
|
".tsx": { grammarName: "typescript", nodeTypes: ["function_declaration", "method_definition", "class_declaration", "interface_declaration", "type_alias_declaration", "enum_declaration", "arrow_function"] },
|
|
@@ -45,11 +64,6 @@ function getExtension(filePath) {
|
|
|
45
64
|
const dot = filePath.lastIndexOf(".");
|
|
46
65
|
return dot >= 0 ? filePath.slice(dot).toLowerCase() : "";
|
|
47
66
|
}
|
|
48
|
-
function resolveFilePath(filePath, worktree) {
|
|
49
|
-
if (isAbsolute(filePath))
|
|
50
|
-
return filePath;
|
|
51
|
-
return resolve(worktree, filePath);
|
|
52
|
-
}
|
|
53
67
|
async function extractSkeleton(content, ext) {
|
|
54
68
|
const config = SKELETON_CONFIGS[ext];
|
|
55
69
|
if (!config) {
|
|
@@ -86,6 +100,8 @@ async function extractSkeleton(content, ext) {
|
|
|
86
100
|
return [];
|
|
87
101
|
const nodeTypes = new Set(config.nodeTypes);
|
|
88
102
|
const astNodes = walkTree(tree.rootNode, nodeTypes, content, 15);
|
|
103
|
+
tree.delete();
|
|
104
|
+
parser.delete();
|
|
89
105
|
return astNodes.map((node) => ({
|
|
90
106
|
type: node.type,
|
|
91
107
|
name: extractNodeName(node.text, node.type),
|
|
@@ -160,6 +176,7 @@ export async function handleSearchSemantic(params, embedder, store, cfg, keyword
|
|
|
160
176
|
minScore: cfg.retrieval.minScore,
|
|
161
177
|
keywordIndex,
|
|
162
178
|
keywordWeight: cfg.retrieval.hybridSearch?.keywordWeight,
|
|
179
|
+
hybridEnabled: cfg.retrieval.hybridSearch?.enabled,
|
|
163
180
|
queryPrefix: cfg.embedding.queryPrefix,
|
|
164
181
|
};
|
|
165
182
|
const rawResults = await retrieveFn_(query, embedder, store, retrieveOpts);
|
package/dist/mcp/server.js
CHANGED
|
@@ -24,6 +24,8 @@ export interface RagReadToolOptions {
|
|
|
24
24
|
}>;
|
|
25
25
|
/** Optional keyword index for hybrid search. */
|
|
26
26
|
keywordIndex?: KeywordIndex;
|
|
27
|
+
/** Maximum number of sessions to retain in the retrieval cache. */
|
|
28
|
+
maxSessionCacheSize?: number;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* Create the RAG-backed read tool for OpenCode plugin registration.
|
|
@@ -63,7 +63,13 @@ export function createRagReadTool(options) {
|
|
|
63
63
|
}
|
|
64
64
|
else {
|
|
65
65
|
const retrievalQuery = buildSessionQuery(messageText, resolvedPath, normalized);
|
|
66
|
-
rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, queryPrefix: config.embedding.queryPrefix });
|
|
66
|
+
rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, hybridEnabled: config.retrieval.hybridSearch?.enabled, queryPrefix: config.embedding.queryPrefix });
|
|
67
|
+
const maxSize = options.maxSessionCacheSize ?? 50;
|
|
68
|
+
if (!sessionRetrievalCache.has(sessionID) && sessionRetrievalCache.size >= maxSize) {
|
|
69
|
+
const oldest = sessionRetrievalCache.keys().next().value;
|
|
70
|
+
if (oldest !== undefined)
|
|
71
|
+
sessionRetrievalCache.delete(oldest);
|
|
72
|
+
}
|
|
67
73
|
sessionRetrievalCache.set(sessionID, { messageText, rawResults });
|
|
68
74
|
}
|
|
69
75
|
}
|
|
@@ -74,7 +80,7 @@ export function createRagReadTool(options) {
|
|
|
74
80
|
startLine: normalized.startLine,
|
|
75
81
|
endLine: normalized.endLine,
|
|
76
82
|
});
|
|
77
|
-
rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, queryPrefix: config.embedding.queryPrefix });
|
|
83
|
+
rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, hybridEnabled: config.retrieval.hybridSearch?.enabled, queryPrefix: config.embedding.queryPrefix });
|
|
78
84
|
}
|
|
79
85
|
// Collect related files from raw results (before filtering)
|
|
80
86
|
relatedFiles = collectRelatedFiles(rawResults, resolvedPath, readRelatedFilesMax);
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Provides fallback error messages and no-results behavior dispatch for the read tool.
|
|
3
3
|
*/
|
|
4
|
-
import type { ReadNoResultsBehavior } from "../core/config.js";
|
|
5
4
|
/**
|
|
6
5
|
* Error message wrapper for retrieval failures.
|
|
7
6
|
*/
|
|
8
7
|
export declare function retrievalErrorMessage(shortError: string): string;
|
|
9
|
-
/**
|
|
10
|
-
* Dispatch to the correct fallback message based on behavior config.
|
|
11
|
-
*/
|
|
12
|
-
export declare function getNoResultsMessage(behavior: ReadNoResultsBehavior, filePath?: string): string;
|
|
8
|
+
/** No results fallback messages are handled inline in create-read-tool.ts. */
|
|
@@ -12,22 +12,5 @@ export function retrievalErrorMessage(shortError) {
|
|
|
12
12
|
shortError,
|
|
13
13
|
].join("\n");
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Dispatch to the correct fallback message based on behavior config.
|
|
17
|
-
*/
|
|
18
|
-
export function getNoResultsMessage(behavior, filePath) {
|
|
19
|
-
switch (behavior) {
|
|
20
|
-
case "error":
|
|
21
|
-
throw new Error("OpenCodeRAG read: no indexed chunks found." +
|
|
22
|
-
(filePath ? ` File: ${filePath}` : ""));
|
|
23
|
-
case "empty":
|
|
24
|
-
return "No indexed chunks found.";
|
|
25
|
-
case "hint":
|
|
26
|
-
default:
|
|
27
|
-
if (filePath) {
|
|
28
|
-
return `No indexed chunks found for ${filePath}.`;
|
|
29
|
-
}
|
|
30
|
-
return "No relevant chunks found.";
|
|
31
|
-
}
|
|
32
|
-
}
|
|
15
|
+
/** No results fallback messages are handled inline in create-read-tool.ts. */
|
|
33
16
|
//# sourceMappingURL=read-fallback.js.map
|
|
@@ -124,9 +124,11 @@ function formatChunk(index, result) {
|
|
|
124
124
|
lines.push(`Score: ${score.toFixed(4)}`);
|
|
125
125
|
lines.push("");
|
|
126
126
|
lines.push("```" + language);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
if (chunk.content.endsWith("\n")) {
|
|
128
|
+
lines.push(chunk.content.slice(0, -1));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
lines.push(chunk.content);
|
|
130
132
|
}
|
|
131
133
|
lines.push("```");
|
|
132
134
|
return lines.join("\n");
|
package/dist/opencode/tools.js
CHANGED
|
@@ -19,7 +19,7 @@ import { retrieve } from "../retriever/retriever.js";
|
|
|
19
19
|
import { Parser } from "web-tree-sitter";
|
|
20
20
|
import { initParser, loadLanguage, walkTree } from "../chunker/grammar.js";
|
|
21
21
|
import { readFileSync } from "node:fs";
|
|
22
|
-
import {
|
|
22
|
+
import { resolveWorkspacePath } from "./tool-args.js";
|
|
23
23
|
const SKELETON_CONFIGS = {
|
|
24
24
|
".ts": { grammarName: "typescript", nodeTypes: ["function_declaration", "method_definition", "class_declaration", "interface_declaration", "type_alias_declaration", "enum_declaration"] },
|
|
25
25
|
".tsx": { grammarName: "typescript", nodeTypes: ["function_declaration", "method_definition", "class_declaration", "interface_declaration", "type_alias_declaration", "enum_declaration", "arrow_function"] },
|
|
@@ -68,9 +68,7 @@ function getExtension(filePath) {
|
|
|
68
68
|
* Resolve a file path relative to the workspace root.
|
|
69
69
|
*/
|
|
70
70
|
function resolveFilePath(filePath, worktree) {
|
|
71
|
-
|
|
72
|
-
return filePath;
|
|
73
|
-
return resolve(worktree, filePath);
|
|
71
|
+
return resolveWorkspacePath(worktree, filePath);
|
|
74
72
|
}
|
|
75
73
|
/**
|
|
76
74
|
* Extract structural outline from source code using tree-sitter.
|
|
@@ -116,6 +114,8 @@ async function extractSkeleton(content, ext) {
|
|
|
116
114
|
return [];
|
|
117
115
|
const nodeTypes = new Set(config.nodeTypes);
|
|
118
116
|
const astNodes = walkTree(tree.rootNode, nodeTypes, content, 15);
|
|
117
|
+
tree.delete();
|
|
118
|
+
parser.delete();
|
|
119
119
|
return astNodes.map((node) => ({
|
|
120
120
|
type: node.type,
|
|
121
121
|
name: extractNodeName(node.text, node.type),
|
|
@@ -262,9 +262,7 @@ export function createDescribeImageTool(options) {
|
|
|
262
262
|
try {
|
|
263
263
|
const { existsSync, readFileSync } = await import("node:fs");
|
|
264
264
|
const path = await import("node:path");
|
|
265
|
-
const resolvedPath =
|
|
266
|
-
? args.filePath
|
|
267
|
-
: resolve(worktree, args.filePath);
|
|
265
|
+
const resolvedPath = resolveWorkspacePath(worktree, args.filePath);
|
|
268
266
|
if (!existsSync(resolvedPath)) {
|
|
269
267
|
return {
|
|
270
268
|
title: "Describe image",
|
package/dist/plugin.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* automatic context injection and documentation mode.
|
|
5
5
|
*/
|
|
6
6
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
7
|
-
import { loadConfig, findConfigFile, DEFAULT_CONFIG, resolveLogConfig } from "./core/config.js";
|
|
7
|
+
import { loadConfig, findConfigFile, DEFAULT_CONFIG, resolveLogConfig, persistProbedDimension } from "./core/config.js";
|
|
8
8
|
import { createEmbedder } from "./embedder/factory.js";
|
|
9
9
|
import { createDescriptionProvider } from "./describer/factory.js";
|
|
10
10
|
import { createVectorStore } from "./vectorstore/factory.js";
|
|
@@ -22,12 +22,15 @@ import { loadDocProgress, markSubdirectoryDocumented } from "./core/doc-progress
|
|
|
22
22
|
import { loadManifest } from "./core/manifest.js";
|
|
23
23
|
import { createSessionLogger } from "./eval/session-logger.js";
|
|
24
24
|
import { countTokens } from "./eval/token-counter.js";
|
|
25
|
-
import { checkForUpdate } from "./
|
|
25
|
+
import { checkForUpdate } from "./core/version-check.js";
|
|
26
|
+
import { destroyAllPooledConnections } from "./embedder/http.js";
|
|
26
27
|
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
27
28
|
import path from "node:path";
|
|
28
29
|
import { fileURLToPath } from "node:url";
|
|
29
30
|
import { spawn, execSync } from "node:child_process";
|
|
30
31
|
import { tmpdir } from "node:os";
|
|
32
|
+
/** Maximum entries per session map before oldest entries are evicted. */
|
|
33
|
+
const MAX_SESSION_MAP_SIZE = 50;
|
|
31
34
|
/** Cache of loaded RAG configurations keyed by workspace directory. */
|
|
32
35
|
const configCache = new Map();
|
|
33
36
|
/** Active background indexer instances keyed by workspace directory. */
|
|
@@ -36,6 +39,52 @@ const backgroundIndexers = new Map();
|
|
|
36
39
|
const mcpServers = new Map();
|
|
37
40
|
/** Pending update notifications keyed by workspace directory. */
|
|
38
41
|
const pendingUpdateInfo = new Map();
|
|
42
|
+
/** Guard flag to prevent re-entrant shutdown. */
|
|
43
|
+
let shutdownRegistered = false;
|
|
44
|
+
/** Close all active background indexers and MCP servers, then destroy idle sockets. */
|
|
45
|
+
async function shutdownPluginResources() {
|
|
46
|
+
for (const [dir, indexer] of backgroundIndexers) {
|
|
47
|
+
try {
|
|
48
|
+
await indexer.close();
|
|
49
|
+
}
|
|
50
|
+
catch { /* best-effort */ }
|
|
51
|
+
backgroundIndexers.delete(dir);
|
|
52
|
+
}
|
|
53
|
+
for (const [dir, server] of mcpServers) {
|
|
54
|
+
try {
|
|
55
|
+
await server.close();
|
|
56
|
+
}
|
|
57
|
+
catch { /* best-effort */ }
|
|
58
|
+
mcpServers.delete(dir);
|
|
59
|
+
}
|
|
60
|
+
configCache.clear();
|
|
61
|
+
pendingUpdateInfo.clear();
|
|
62
|
+
destroyAllPooledConnections();
|
|
63
|
+
}
|
|
64
|
+
/** Register a process.beforeExit handler so that resources are cleaned up
|
|
65
|
+
* when the OpenCode process exits (rather than only on plugin reload). */
|
|
66
|
+
function registerShutdownHandler() {
|
|
67
|
+
if (shutdownRegistered)
|
|
68
|
+
return;
|
|
69
|
+
shutdownRegistered = true;
|
|
70
|
+
process.once("beforeExit", () => {
|
|
71
|
+
void shutdownPluginResources();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
registerShutdownHandler();
|
|
75
|
+
/** Set a bounded Map entry, evicting the oldest key if the map exceeds maximum size. */
|
|
76
|
+
function boundedSet(map, key, value, maxSize) {
|
|
77
|
+
if (map.has(key)) {
|
|
78
|
+
map.set(key, value);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (map.size >= maxSize) {
|
|
82
|
+
const oldest = map.keys().next().value;
|
|
83
|
+
if (oldest !== undefined)
|
|
84
|
+
map.delete(oldest);
|
|
85
|
+
}
|
|
86
|
+
map.set(key, value);
|
|
87
|
+
}
|
|
39
88
|
/** Name of the semantic search tool as exposed to the LLM. */
|
|
40
89
|
const CONTEXT_TOOL_NAME = "search_semantic";
|
|
41
90
|
/** Marker string injected into context output to identify RAG-sourced content. */
|
|
@@ -230,10 +279,10 @@ function buildRetrievalQuery(hints) {
|
|
|
230
279
|
* Perform a retrieval query against the vector store with the given parameters.
|
|
231
280
|
* Returns an empty array for blank queries.
|
|
232
281
|
*/
|
|
233
|
-
async function retrieveContext(query, embedder, store, topK, retrieveFn = retrieve, minScore = 0, keywordIndex, keywordWeight, queryPrefix, explain = false) {
|
|
282
|
+
async function retrieveContext(query, embedder, store, topK, retrieveFn = retrieve, minScore = 0, keywordIndex, keywordWeight, queryPrefix, explain = false, hybridEnabled) {
|
|
234
283
|
if (query.trim().length === 0)
|
|
235
284
|
return [];
|
|
236
|
-
return retrieveFn(query, embedder, store, { topK, minScore, keywordIndex, keywordWeight, queryPrefix, explain });
|
|
285
|
+
return retrieveFn(query, embedder, store, { topK, minScore, keywordIndex, keywordWeight, hybridEnabled, queryPrefix, explain });
|
|
237
286
|
}
|
|
238
287
|
/**
|
|
239
288
|
* Load results from one or two queries (primary + optional extra), optimize
|
|
@@ -243,9 +292,10 @@ async function retrieveContext(query, embedder, store, topK, retrieveFn = retrie
|
|
|
243
292
|
async function loadRetrievedResults(query, embedder, store, cfg, retrieveFn = retrieve, topK = cfg.retrieval.topK, extraQuery, keywordIndex, queryPrefix, explain = false) {
|
|
244
293
|
const minScore = cfg.retrieval.minScore;
|
|
245
294
|
const kw = cfg.retrieval.hybridSearch?.keywordWeight;
|
|
246
|
-
const
|
|
295
|
+
const hybridEnabled = cfg.retrieval.hybridSearch?.enabled;
|
|
296
|
+
const primaryResults = await retrieveContext(query, embedder, store, topK, retrieveFn, minScore, keywordIndex, kw, queryPrefix, explain, hybridEnabled);
|
|
247
297
|
const extraResults = extraQuery
|
|
248
|
-
? await retrieveContext(extraQuery, embedder, store, topK, retrieveFn, minScore, keywordIndex, kw, queryPrefix, explain)
|
|
298
|
+
? await retrieveContext(extraQuery, embedder, store, topK, retrieveFn, minScore, keywordIndex, kw, queryPrefix, explain, hybridEnabled)
|
|
249
299
|
: [];
|
|
250
300
|
const optCfg = cfg.retrieval.contextOptimization ?? DEFAULT_CONTEXT_OPTIMIZATION;
|
|
251
301
|
return optimizeContext([...primaryResults, ...extraResults], { topK, config: optCfg })
|
|
@@ -355,12 +405,20 @@ export function createRagHooks(options) {
|
|
|
355
405
|
let cachedOverrides = loadRuntimeOverrides(options.storePath);
|
|
356
406
|
let overridesLastCheck = 0;
|
|
357
407
|
const OVERRIDES_TTL_MS = Number(process.env.OPENCODE_RAG_OVERRIDES_TTL_MS) || 3600000;
|
|
408
|
+
let lastEffectiveCfg = null;
|
|
409
|
+
let lastEffectiveCfgTime = 0;
|
|
358
410
|
function getEffectiveCfg() {
|
|
359
411
|
if (Date.now() - overridesLastCheck > OVERRIDES_TTL_MS) {
|
|
360
412
|
cachedOverrides = loadRuntimeOverrides(options.storePath);
|
|
361
413
|
overridesLastCheck = Date.now();
|
|
414
|
+
lastEffectiveCfg = null;
|
|
415
|
+
}
|
|
416
|
+
if (lastEffectiveCfg && Date.now() - lastEffectiveCfgTime < 1000) {
|
|
417
|
+
return lastEffectiveCfg;
|
|
362
418
|
}
|
|
363
|
-
|
|
419
|
+
lastEffectiveCfg = applyRuntimeOverrides(options.cfg, cachedOverrides);
|
|
420
|
+
lastEffectiveCfgTime = Date.now();
|
|
421
|
+
return lastEffectiveCfg;
|
|
364
422
|
}
|
|
365
423
|
// Session-level caches for lazy retrieval
|
|
366
424
|
const sessionLastMessage = new Map();
|
|
@@ -538,6 +596,7 @@ export function createRagHooks(options) {
|
|
|
538
596
|
sessionLastMessage,
|
|
539
597
|
sessionRetrievalCache,
|
|
540
598
|
keywordIndex,
|
|
599
|
+
maxSessionCacheSize: MAX_SESSION_MAP_SIZE,
|
|
541
600
|
});
|
|
542
601
|
tools["read"] = readTool;
|
|
543
602
|
appendDebugLog(options.logFilePath, {
|
|
@@ -562,8 +621,8 @@ export function createRagHooks(options) {
|
|
|
562
621
|
if (msgInfo?.role === "assistant" &&
|
|
563
622
|
typeof msgInfo.sessionID === "string" &&
|
|
564
623
|
typeof msgInfo.id === "string") {
|
|
565
|
-
sessionAssistantMessageId
|
|
566
|
-
sessionAssistantText
|
|
624
|
+
boundedSet(sessionAssistantMessageId, msgInfo.sessionID, msgInfo.id, MAX_SESSION_MAP_SIZE);
|
|
625
|
+
boundedSet(sessionAssistantText, msgInfo.sessionID, "", MAX_SESSION_MAP_SIZE);
|
|
567
626
|
}
|
|
568
627
|
}
|
|
569
628
|
else if (event.type === "message.part.updated") {
|
|
@@ -577,7 +636,7 @@ export function createRagHooks(options) {
|
|
|
577
636
|
const assistantMsgId = sessionAssistantMessageId.get(part.sessionID);
|
|
578
637
|
if (assistantMsgId && part.messageID === assistantMsgId) {
|
|
579
638
|
const existing = sessionAssistantText.get(part.sessionID) ?? "";
|
|
580
|
-
sessionAssistantText
|
|
639
|
+
boundedSet(sessionAssistantText, part.sessionID, existing + delta, MAX_SESSION_MAP_SIZE);
|
|
581
640
|
}
|
|
582
641
|
}
|
|
583
642
|
}
|
|
@@ -592,42 +651,45 @@ export function createRagHooks(options) {
|
|
|
592
651
|
scope: "experimental.chat.system.transform",
|
|
593
652
|
message: "system guidance injected",
|
|
594
653
|
});
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
654
|
+
const cfg = getEffectiveCfg();
|
|
655
|
+
if (cfg.openCode.injectSystemPrompt !== false) {
|
|
656
|
+
const guidance = [
|
|
657
|
+
"MANDATORY: OpenCodeRAG tools MUST be used before any code task:",
|
|
658
|
+
"- `search_semantic(query)`: retrieve relevant code chunks. Call BEFORE planning, editing, or answering. Accepts `pathHints` and `languageHints`.",
|
|
659
|
+
"- `get_file_skeleton(filePath)`: structural overview of a file. Call BEFORE reading any file.",
|
|
660
|
+
"- `find_usages(symbolName)`: find all references. Call BEFORE editing any function, class, or variable.",
|
|
661
|
+
"- `describe_image(filePath)`: describe an image file using a vision model. Call when user refers to a screenshot, diagram, or image.",
|
|
662
|
+
"",
|
|
663
|
+
"Decision tree — ALWAYS follow this order:",
|
|
664
|
+
"1. User mentions code behavior/architecture → `search_semantic(query)`",
|
|
665
|
+
"2. User mentions a file path → `get_file_skeleton(filePath)` THEN `read` on specific lines",
|
|
666
|
+
"3. User mentions a function/class/variable to edit → `find_usages(symbolName)` THEN `search_semantic` THEN `edit`",
|
|
667
|
+
"4. User asks a code question → `search_semantic` to gather context before answering",
|
|
668
|
+
"5. User asks about an image or visual asset → `describe_image(filePath)` to retrieve its generated description, then optionally `search_semantic` for related code",
|
|
669
|
+
"",
|
|
670
|
+
"Proactive triggers — you MUST call these tools when:",
|
|
671
|
+
"- User asks about code behavior, architecture, or implementation details",
|
|
672
|
+
"- User asks to edit, refactor, or fix code — call `find_usages` first",
|
|
673
|
+
"- User references files or functions you haven't read yet",
|
|
674
|
+
"- User says \"find\", \"search\", \"look up\", \"where is\", \"how does\"",
|
|
675
|
+
"- User refers to an image, screenshot, diagram, or visual asset",
|
|
676
|
+
"- Before answering ANY code-related question, retrieve context first",
|
|
677
|
+
"- Before reading ANY file, call `get_file_skeleton` to orient first",
|
|
678
|
+
"",
|
|
679
|
+
"Anti-patterns — NEVER do these:",
|
|
680
|
+
"- Reading full files without calling `get_file_skeleton` first (wastes tokens)",
|
|
681
|
+
"- Editing a function without calling `find_usages` first (breaks call sites)",
|
|
682
|
+
"- Answering code questions without calling `search_semantic` first (you guess at behavior)",
|
|
683
|
+
"- Using `grep`/`glob` when `search_semantic` would find the answer faster",
|
|
684
|
+
"- Treating image files as text — use `describe_image` instead of reading raw bytes",
|
|
685
|
+
];
|
|
686
|
+
output.system.unshift(guidance.join("\n"));
|
|
687
|
+
}
|
|
626
688
|
// Inject update notification if available
|
|
627
689
|
const updateInfo = pendingUpdateInfo.get(options.worktree);
|
|
628
690
|
if (updateInfo) {
|
|
629
691
|
output.system.unshift(`OpenCodeRAG update available: ${updateInfo.currentVersion} → ${updateInfo.latestVersion}. ` +
|
|
630
|
-
`Run \`opencode-rag
|
|
692
|
+
`Run \`npm update -g opencode-rag-plugin && opencode-rag setup\` to install.`);
|
|
631
693
|
}
|
|
632
694
|
// Inject documentation mode system prompt if enabled
|
|
633
695
|
const docMode = getEffectiveCfg().documentationMode;
|
|
@@ -650,7 +712,7 @@ export function createRagHooks(options) {
|
|
|
650
712
|
});
|
|
651
713
|
if (text.length === 0)
|
|
652
714
|
return;
|
|
653
|
-
sessionLastMessage
|
|
715
|
+
boundedSet(sessionLastMessage, input.sessionID, text, MAX_SESSION_MAP_SIZE);
|
|
654
716
|
// Handle /doc slash command
|
|
655
717
|
if (text.startsWith("/doc")) {
|
|
656
718
|
const docMode = getEffectiveCfg().documentationMode;
|
|
@@ -1025,29 +1087,51 @@ export const ragPlugin = async (input, _options) => {
|
|
|
1025
1087
|
}
|
|
1026
1088
|
mcpServers.delete(input.directory);
|
|
1027
1089
|
}
|
|
1090
|
+
// Clean up stale config cache and pending update info for this directory
|
|
1091
|
+
configCache.delete(input.directory);
|
|
1092
|
+
pendingUpdateInfo.delete(input.directory);
|
|
1093
|
+
// Clean up idle HTTP sockets from previous provider connections
|
|
1094
|
+
destroyAllPooledConnections();
|
|
1028
1095
|
appendDebugLog(logFilePath, {
|
|
1029
1096
|
scope: "plugin",
|
|
1030
1097
|
message: `OpenCode plugin enabled for ${input.directory}`,
|
|
1031
1098
|
}, logLevel);
|
|
1032
|
-
//
|
|
1099
|
+
// Use cached dimension from config if available (avoids blocking startup with an API call)
|
|
1100
|
+
// If not set, probe the embedding provider once and persist the result.
|
|
1033
1101
|
const embedder = createEmbedder(effectiveCfg);
|
|
1034
|
-
let vectorDimension =
|
|
1035
|
-
|
|
1036
|
-
const probe = await embedder.embed(["dimension-probe"], "query");
|
|
1037
|
-
if (probe && probe[0] && probe[0].length > 0 && typeof probe[0][0] === "number") {
|
|
1038
|
-
vectorDimension = probe[0].length;
|
|
1039
|
-
}
|
|
1102
|
+
let vectorDimension = effectiveCfg.embedding.vectorDimension;
|
|
1103
|
+
if (vectorDimension && vectorDimension > 0) {
|
|
1040
1104
|
appendDebugLog(logFilePath, {
|
|
1041
1105
|
scope: "plugin",
|
|
1042
|
-
message: `Vector dimension: ${vectorDimension}`,
|
|
1106
|
+
message: `Vector dimension: ${vectorDimension} (cached in config)`,
|
|
1043
1107
|
}, logLevel);
|
|
1044
1108
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1109
|
+
else {
|
|
1110
|
+
vectorDimension = 384;
|
|
1111
|
+
try {
|
|
1112
|
+
const probe = await embedder.embed(["dimension-probe"], "query");
|
|
1113
|
+
if (probe && probe[0] && probe[0].length > 0 && typeof probe[0][0] === "number") {
|
|
1114
|
+
vectorDimension = probe[0].length;
|
|
1115
|
+
const configPath = findConfigFile(input.directory);
|
|
1116
|
+
if (configPath) {
|
|
1117
|
+
try {
|
|
1118
|
+
persistProbedDimension(configPath, vectorDimension);
|
|
1119
|
+
}
|
|
1120
|
+
catch { /* best-effort */ }
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
appendDebugLog(logFilePath, {
|
|
1124
|
+
scope: "plugin",
|
|
1125
|
+
message: `Vector dimension: ${vectorDimension}`,
|
|
1126
|
+
}, logLevel);
|
|
1127
|
+
}
|
|
1128
|
+
catch (err) {
|
|
1129
|
+
appendDebugLog(logFilePath, {
|
|
1130
|
+
scope: "plugin",
|
|
1131
|
+
message: `Dimension probe failed, falling back to ${vectorDimension}`,
|
|
1132
|
+
error: err,
|
|
1133
|
+
}, logLevel);
|
|
1134
|
+
}
|
|
1051
1135
|
}
|
|
1052
1136
|
const store = createVectorStore(effectiveCfg, storePath, vectorDimension);
|
|
1053
1137
|
// Load or create keyword index for hybrid search
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview In-memory inverted keyword index with stemming, tokenization, and optional serialization.
|
|
3
3
|
*/
|
|
4
|
-
import type { Chunk, SearchResult } from "../core/interfaces.js";
|
|
4
|
+
import type { Chunk, SearchResult, MetadataFilter } from "../core/interfaces.js";
|
|
5
5
|
/**
|
|
6
6
|
* Tokenize text into normalized tokens including stems, camelCase parts, and snake_case parts.
|
|
7
7
|
*
|
|
@@ -22,7 +22,8 @@ export declare class KeywordIndex {
|
|
|
22
22
|
addChunks(chunks: Chunk[]): void;
|
|
23
23
|
getMatchedTerms(query: string, chunkId: string): string[];
|
|
24
24
|
removeByFilePath(filePath: string): void;
|
|
25
|
-
search(query: string, topK: number): SearchResult[];
|
|
25
|
+
search(query: string, topK: number, filter?: MetadataFilter): SearchResult[];
|
|
26
|
+
close(): void;
|
|
26
27
|
clear(): void;
|
|
27
28
|
count(): number;
|
|
28
29
|
save(storePath?: string): Promise<void>;
|
|
@@ -124,7 +124,7 @@ export class KeywordIndex {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
search(query, topK) {
|
|
127
|
+
search(query, topK, filter) {
|
|
128
128
|
if (this.chunkMap.size === 0)
|
|
129
129
|
return [];
|
|
130
130
|
const queryTokens = tokenize(query);
|
|
@@ -144,6 +144,7 @@ export class KeywordIndex {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
const sorted = [...scores.entries()]
|
|
147
|
+
.filter(([id]) => matchesFilter(this.chunkMap.get(id), filter))
|
|
147
148
|
.sort((a, b) => b[1] - a[1])
|
|
148
149
|
.slice(0, topK);
|
|
149
150
|
return sorted.map(([chunkId, score]) => {
|
|
@@ -151,6 +152,10 @@ export class KeywordIndex {
|
|
|
151
152
|
return { chunk, score };
|
|
152
153
|
});
|
|
153
154
|
}
|
|
155
|
+
close() {
|
|
156
|
+
this.invertedIndex.clear();
|
|
157
|
+
this.chunkMap.clear();
|
|
158
|
+
}
|
|
154
159
|
clear() {
|
|
155
160
|
this.invertedIndex.clear();
|
|
156
161
|
this.chunkMap.clear();
|
|
@@ -232,4 +237,23 @@ export class KeywordIndex {
|
|
|
232
237
|
await writeFile(targetPath, JSON.stringify({ version: INDEX_VERSION, tokens: [], chunkMap: {} }), "utf-8");
|
|
233
238
|
}
|
|
234
239
|
}
|
|
240
|
+
function globMatch(pattern, filePath) {
|
|
241
|
+
const GLOBSTAR = "\x00GS\x00";
|
|
242
|
+
let re = pattern.replace(/\*\*/g, GLOBSTAR);
|
|
243
|
+
re = re.replace(/([.+^${}()|[\]\\])/g, "\\$1");
|
|
244
|
+
re = re.replace(new RegExp(GLOBSTAR, "g"), ".*");
|
|
245
|
+
re = re.replace(/\*/g, "[^/]*");
|
|
246
|
+
re = re.replace(/\?/g, ".");
|
|
247
|
+
return new RegExp("^" + re + "$").test(filePath);
|
|
248
|
+
}
|
|
249
|
+
function matchesFilter(chunk, filter) {
|
|
250
|
+
if (!filter)
|
|
251
|
+
return true;
|
|
252
|
+
if (filter.languages?.length && !filter.languages.includes(chunk.metadata.language))
|
|
253
|
+
return false;
|
|
254
|
+
if (filter.pathPatterns?.length) {
|
|
255
|
+
return filter.pathPatterns.some((p) => globMatch(p, chunk.metadata.filePath));
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
235
259
|
//# sourceMappingURL=keyword-index.js.map
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Performs hybrid vector-keyword retrieval with configurable scoring and explanation.
|
|
3
3
|
*/
|
|
4
|
-
import type { EmbeddingProvider, KeywordIndex, VectorStore, SearchResult } from "../core/interfaces.js";
|
|
4
|
+
import type { EmbeddingProvider, KeywordIndex, VectorStore, SearchResult, MetadataFilter } from "../core/interfaces.js";
|
|
5
5
|
/** Options controlling the retrieval behavior. */
|
|
6
6
|
export interface RetrieveOptions {
|
|
7
7
|
topK?: number;
|
|
8
8
|
minScore?: number;
|
|
9
9
|
keywordIndex?: KeywordIndex;
|
|
10
10
|
keywordWeight?: number;
|
|
11
|
+
/** Whether hybrid search is enabled. When false, keyword index is ignored. */
|
|
12
|
+
hybridEnabled?: boolean;
|
|
11
13
|
queryPrefix?: string;
|
|
12
14
|
explain?: boolean;
|
|
15
|
+
filter?: MetadataFilter;
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
18
|
* Perform hybrid vector-keyword retrieval with configurable scoring.
|