opencode-rag-plugin 1.19.4 → 1.19.8
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/dist/chunker/base.js +19 -5
- package/dist/chunker/factory.js +27 -9
- package/dist/chunker/grammar.d.ts +18 -1
- package/dist/chunker/grammar.js +48 -10
- package/dist/chunker/image.js +5 -0
- package/dist/chunker/pdf.js +30 -14
- package/dist/cli/commands/backend-detect.d.ts +61 -0
- package/dist/cli/commands/backend-detect.js +119 -0
- package/dist/cli/commands/index-command.js +11 -0
- package/dist/cli/commands/init-helpers.d.ts +4 -1
- package/dist/cli/commands/init-helpers.js +21 -4
- package/dist/cli/commands/init.js +61 -24
- package/dist/cli/commands/quirk.js +9 -3
- package/dist/cli/commands/setup.js +12 -3
- package/dist/cli/commands/status.js +14 -5
- package/dist/cli/commands/ui.js +23 -9
- package/dist/cli/commands/update.js +4 -5
- package/dist/cli/format.d.ts +5 -2
- package/dist/cli/format.js +14 -5
- package/dist/content/image.js +33 -11
- package/dist/content/reader.js +74 -13
- package/dist/core/bootstrap.js +10 -3
- package/dist/core/config.d.ts +27 -1
- package/dist/core/config.js +41 -2
- package/dist/core/desc-cache.d.ts +8 -2
- package/dist/core/desc-cache.js +10 -3
- package/dist/core/doc-progress.js +5 -2
- package/dist/core/interfaces.d.ts +32 -4
- package/dist/core/manifest.js +1 -1
- package/dist/core/provider-defaults.d.ts +2 -0
- package/dist/core/provider-defaults.js +19 -4
- package/dist/core/runtime-overrides.d.ts +0 -6
- package/dist/core/version-check.d.ts +5 -0
- package/dist/core/version-check.js +8 -2
- package/dist/describer/anthropic.d.ts +2 -2
- package/dist/describer/anthropic.js +19 -5
- package/dist/describer/describer.d.ts +20 -0
- package/dist/describer/describer.js +132 -16
- package/dist/describer/gemini.js +25 -10
- package/dist/describer/shared.d.ts +28 -0
- package/dist/describer/shared.js +60 -0
- package/dist/embedder/factory.d.ts +5 -3
- package/dist/embedder/factory.js +42 -9
- package/dist/embedder/health.js +19 -19
- package/dist/embedder/http.d.ts +14 -1
- package/dist/embedder/http.js +60 -6
- package/dist/embedder/ollama.d.ts +3 -1
- package/dist/embedder/ollama.js +12 -2
- package/dist/eval/session-logger.js +7 -0
- package/dist/eval/storage.js +8 -0
- package/dist/indexer/git-diff.d.ts +1 -1
- package/dist/indexer/git-diff.js +5 -1
- package/dist/indexer/pipeline.js +511 -346
- package/dist/indexer/stats.d.ts +2 -0
- package/dist/indexer/stats.js +1 -0
- package/dist/indexer/watch.js +8 -1
- package/dist/indexer/worker.js +21 -0
- package/dist/mcp/cli.js +4 -0
- package/dist/mcp/handlers.js +16 -5
- package/dist/mcp/server.js +3 -0
- package/dist/opencode/create-read-tool.js +14 -3
- package/dist/opencode/tool-args.js +23 -1
- package/dist/plugin.js +66 -152
- package/dist/quirks/auto-capture.js +5 -0
- package/dist/quirks/quirk-store.d.ts +1 -1
- package/dist/quirks/quirk-store.js +56 -17
- package/dist/retriever/context-optimizer.js +18 -4
- package/dist/retriever/keyword-index.d.ts +2 -0
- package/dist/retriever/keyword-index.js +38 -4
- package/dist/retriever/retriever.js +6 -1
- package/dist/tui.js +41 -4
- package/dist/vectorstore/lancedb.d.ts +104 -8
- package/dist/vectorstore/lancedb.js +345 -71
- package/dist/vectorstore/memory.d.ts +8 -3
- package/dist/vectorstore/memory.js +27 -4
- package/dist/watcher.d.ts +8 -0
- package/dist/watcher.js +237 -85
- package/dist/web/api.d.ts +5 -1
- package/dist/web/api.js +195 -69
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.js +66 -28
- package/dist/web/static.d.ts +5 -2
- package/dist/web/static.js +9 -5
- package/dist/web/ui/assets/index-BDPYdtA1.js +3 -0
- package/dist/web/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/ui/assets/index-CJBvt6e0.js +0 -3
package/dist/core/config.d.ts
CHANGED
|
@@ -54,7 +54,18 @@ export interface DescriptionConfig {
|
|
|
54
54
|
proxy?: ProxyConfig;
|
|
55
55
|
/** System prompt instructing the LLM how to describe code. */
|
|
56
56
|
systemPrompt: string;
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* EXPERIMENTAL: enable multi-chunk batch description requests (Ollama
|
|
59
|
+
* provider only). Off by default — each chunk is described with its own
|
|
60
|
+
* request. When enabled, up to `batchMaxChunks` chunks share one request
|
|
61
|
+
* using ordinal labels ([CHUNK 1] ... reply "1: <desc>"); small models can
|
|
62
|
+
* mangle the structured output, so batches that fail to parse fall back to
|
|
63
|
+
* individual requests and batching auto-disables after 2 consecutive
|
|
64
|
+
* failures. Part of the description manifest fingerprint — toggling it
|
|
65
|
+
* re-describes files.
|
|
66
|
+
*/
|
|
67
|
+
batchEnabled?: boolean;
|
|
68
|
+
/** Maximum chunks per batch request. Only applies when `batchEnabled` is true. */
|
|
58
69
|
batchMaxChunks?: number;
|
|
59
70
|
/** Timeout per batch request in milliseconds. */
|
|
60
71
|
batchTimeoutMs?: number;
|
|
@@ -68,6 +79,8 @@ export interface DescriptionConfig {
|
|
|
68
79
|
think?: boolean;
|
|
69
80
|
/** Context window size for the LLM. */
|
|
70
81
|
numCtx?: number;
|
|
82
|
+
/** Ollama keep_alive value (e.g. "-1" for keep-in-memory) sent with /api/chat requests. */
|
|
83
|
+
keepAlive?: string;
|
|
71
84
|
/** Maximum content characters sent to the LLM. Chunks exceeding this use fallback descriptions. */
|
|
72
85
|
maxContentChars?: number;
|
|
73
86
|
}
|
|
@@ -91,6 +104,8 @@ export interface ImageDescriptionConfig {
|
|
|
91
104
|
think?: boolean;
|
|
92
105
|
/** Context window size. */
|
|
93
106
|
numCtx?: number;
|
|
107
|
+
/** Ollama keep_alive value (e.g. "-1" for keep-in-memory) sent with /api/chat requests. */
|
|
108
|
+
keepAlive?: string;
|
|
94
109
|
/** Proxy configuration. */
|
|
95
110
|
proxy?: ProxyConfig;
|
|
96
111
|
/** Maximum image dimension (pixels) — larger images are resized before sending. */
|
|
@@ -220,6 +235,8 @@ export interface RagConfig {
|
|
|
220
235
|
queryPrefix?: string;
|
|
221
236
|
/** Cached embedding vector dimension. Probed once on first startup, then persisted to config. */
|
|
222
237
|
vectorDimension?: number;
|
|
238
|
+
/** Ollama keep_alive value (e.g. "-1" for keep-in-memory) sent with /api/embed requests. */
|
|
239
|
+
keepAlive?: string;
|
|
223
240
|
};
|
|
224
241
|
/** Indexing pipeline controls: what to index, concurrency, batch sizes. */
|
|
225
242
|
indexing: {
|
|
@@ -268,6 +285,15 @@ export interface RagConfig {
|
|
|
268
285
|
* @default 1_048_576 (1 MB)
|
|
269
286
|
*/
|
|
270
287
|
maxSvgSizeBytes?: number;
|
|
288
|
+
/**
|
|
289
|
+
* Run vector-store compaction + version pruning every N windows during a
|
|
290
|
+
* long index pass. LanceDB keeps every committed version on disk, so
|
|
291
|
+
* without periodic maintenance the store phase slows down as the index
|
|
292
|
+
* grows (version-manifest accumulation). 0 disables mid-run optimization
|
|
293
|
+
* (the store is still optimized once at the end of a pass).
|
|
294
|
+
* @default 8
|
|
295
|
+
*/
|
|
296
|
+
optimizeIntervalWindows?: number;
|
|
271
297
|
};
|
|
272
298
|
/** Vector storage backend configuration. */
|
|
273
299
|
vectorStore: {
|
package/dist/core/config.js
CHANGED
|
@@ -115,9 +115,10 @@ export const DEFAULT_CONFIG = {
|
|
|
115
115
|
concurrency: 8,
|
|
116
116
|
embedBatchSize: 100,
|
|
117
117
|
embedConcurrency: 3,
|
|
118
|
-
ollamaMaxBatchSize:
|
|
118
|
+
ollamaMaxBatchSize: 100,
|
|
119
119
|
descriptionConcurrency: 4,
|
|
120
120
|
maxSvgSizeBytes: 1_048_576,
|
|
121
|
+
optimizeIntervalWindows: 8,
|
|
121
122
|
},
|
|
122
123
|
vectorStore: {
|
|
123
124
|
path: "./.opencode/rag_db",
|
|
@@ -169,7 +170,10 @@ export const DEFAULT_CONFIG = {
|
|
|
169
170
|
think: false,
|
|
170
171
|
numCtx: 4096,
|
|
171
172
|
timeoutMs: 60000,
|
|
172
|
-
systemPrompt: "Describe this code in
|
|
173
|
+
systemPrompt: "Describe this code in ONE concise sentence (max 20 words): purpose, key inputs/outputs. No code repetition.",
|
|
174
|
+
batchEnabled: false,
|
|
175
|
+
batchMaxChunks: 25,
|
|
176
|
+
batchTimeoutMs: 120000,
|
|
173
177
|
batchConcurrency: 1,
|
|
174
178
|
retryMax: 3,
|
|
175
179
|
retryBaseDelayMs: 1000,
|
|
@@ -378,6 +382,23 @@ export function validateConfig(config) {
|
|
|
378
382
|
if (config.memory?.autoInjectTopK != null && config.memory.autoInjectTopK < 1) {
|
|
379
383
|
warnings.push("memory.autoInjectTopK must be >= 1");
|
|
380
384
|
}
|
|
385
|
+
if (config.memory?.minConfidence != null) {
|
|
386
|
+
const r = config.memory.minConfidence;
|
|
387
|
+
if (r < 0 || r > 1)
|
|
388
|
+
warnings.push("memory.minConfidence must be between 0 and 1");
|
|
389
|
+
}
|
|
390
|
+
if (config.memory?.autoCaptureDedupThreshold != null) {
|
|
391
|
+
const t = config.memory.autoCaptureDedupThreshold;
|
|
392
|
+
// > 1 would make dedup reject every candidate; < 0 disables it silently
|
|
393
|
+
if (t < 0 || t > 1)
|
|
394
|
+
warnings.push("memory.autoCaptureDedupThreshold must be between 0 and 1");
|
|
395
|
+
}
|
|
396
|
+
if (config.memory?.decay && config.memory.decay.halfLifeDays <= 0) {
|
|
397
|
+
warnings.push("memory.decay.halfLifeDays must be > 0");
|
|
398
|
+
}
|
|
399
|
+
if (config.openCode.maxReadOutputChars != null && config.openCode.maxReadOutputChars <= 0) {
|
|
400
|
+
warnings.push("openCode.maxReadOutputChars must be > 0");
|
|
401
|
+
}
|
|
381
402
|
if (config.openCode.maxContextChunks <= 0) {
|
|
382
403
|
warnings.push("openCode.maxContextChunks must be > 0");
|
|
383
404
|
}
|
|
@@ -410,6 +431,10 @@ export function validateConfig(config) {
|
|
|
410
431
|
if (config.description.timeoutMs != null && config.description.timeoutMs <= 0) {
|
|
411
432
|
warnings.push("description.timeoutMs must be > 0");
|
|
412
433
|
}
|
|
434
|
+
if (config.description.batchEnabled === true) {
|
|
435
|
+
warnings.push("description.batchEnabled is EXPERIMENTAL — batching several chunks into one LLM request (Ollama only) " +
|
|
436
|
+
"is unreliable on small models; disable it if descriptions look wrong");
|
|
437
|
+
}
|
|
413
438
|
}
|
|
414
439
|
if (config.imageDescription) {
|
|
415
440
|
if (config.imageDescription.enabled) {
|
|
@@ -480,6 +505,14 @@ export function loadConfig(filePath, validate = true) {
|
|
|
480
505
|
...DEFAULT_CONFIG.retrieval.hybridSearch,
|
|
481
506
|
...(safeObj(parsed.retrieval?.hybridSearch) ?? {}),
|
|
482
507
|
},
|
|
508
|
+
// contextOptimization MUST be nested-merged too — a shallow spread
|
|
509
|
+
// would replace the whole default object with a partial user object,
|
|
510
|
+
// silently leaving maxPerFile/mergeAdjacent/similarityThreshold etc.
|
|
511
|
+
// undefined (which disables the features via NaN comparisons).
|
|
512
|
+
contextOptimization: {
|
|
513
|
+
...DEFAULT_CONFIG.retrieval.contextOptimization,
|
|
514
|
+
...(safeObj(parsed.retrieval?.contextOptimization) ?? {}),
|
|
515
|
+
},
|
|
483
516
|
},
|
|
484
517
|
openCode: (() => {
|
|
485
518
|
const base = DEFAULT_CONFIG.openCode;
|
|
@@ -528,6 +561,12 @@ export function loadConfig(filePath, validate = true) {
|
|
|
528
561
|
memory: {
|
|
529
562
|
...DEFAULT_CONFIG.memory,
|
|
530
563
|
...(safeObj(parsed.memory) ?? {}),
|
|
564
|
+
// decay is a nested object — a user config with only
|
|
565
|
+
// `memory.decay.enabled` must not lose the halfLifeDays default.
|
|
566
|
+
decay: {
|
|
567
|
+
...DEFAULT_CONFIG.memory.decay,
|
|
568
|
+
...(safeObj((safeObj(parsed.memory) ?? {})?.decay) ?? {}),
|
|
569
|
+
},
|
|
531
570
|
},
|
|
532
571
|
ui: {
|
|
533
572
|
...DEFAULT_CONFIG.ui,
|
|
@@ -22,8 +22,14 @@ export declare class DescriptionCache {
|
|
|
22
22
|
has(key: string): boolean;
|
|
23
23
|
/** Persist the cache to disk if dirty. Safe to call multiple times. */
|
|
24
24
|
save(): Promise<void>;
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Build a cache key for a code chunk.
|
|
27
|
+
*
|
|
28
|
+
* The optional `context` (relative path + line range) is included because
|
|
29
|
+
* the description prompt contains the file path — identical chunk content
|
|
30
|
+
* in two different files must not reuse a contextually wrong description.
|
|
31
|
+
*/
|
|
32
|
+
static codeKey(content: string, descConfigHash: string, context?: string): string;
|
|
27
33
|
/** Build a cache key for an image file. */
|
|
28
34
|
static imageKey(imageBytesHash: string, imageDescConfigHash: string): string;
|
|
29
35
|
/** Remove old entries to stay under the limit. */
|
package/dist/core/desc-cache.js
CHANGED
|
@@ -89,10 +89,17 @@ export class DescriptionCache {
|
|
|
89
89
|
});
|
|
90
90
|
return this.savePromise;
|
|
91
91
|
}
|
|
92
|
-
/**
|
|
93
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Build a cache key for a code chunk.
|
|
94
|
+
*
|
|
95
|
+
* The optional `context` (relative path + line range) is included because
|
|
96
|
+
* the description prompt contains the file path — identical chunk content
|
|
97
|
+
* in two different files must not reuse a contextually wrong description.
|
|
98
|
+
*/
|
|
99
|
+
static codeKey(content, descConfigHash, context) {
|
|
94
100
|
const contentHash = createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
95
|
-
|
|
101
|
+
const contextHash = context ? createHash("sha256").update(context).digest("hex").slice(0, 8) : "";
|
|
102
|
+
return contentHash + "_" + descConfigHash.slice(0, 16) + (contextHash ? "_" + contextHash : "");
|
|
96
103
|
}
|
|
97
104
|
/** Build a cache key for an image file. */
|
|
98
105
|
static imageKey(imageBytesHash, imageDescConfigHash) {
|
|
@@ -47,17 +47,20 @@ export function markFileDocumented(storePath, filePath) {
|
|
|
47
47
|
export function markSubdirectoryDocumented(storePath, subdir, allFilePaths) {
|
|
48
48
|
const normalized = subdir.replace(/\\/g, "/").replace(/\/$/, "");
|
|
49
49
|
const progress = loadDocProgress(storePath);
|
|
50
|
+
// Set lookups avoid the O(n·m) `includes` scan for large workspaces
|
|
51
|
+
const documentedSet = new Set(progress.documented);
|
|
50
52
|
let changed = false;
|
|
51
53
|
for (const filePath of allFilePaths) {
|
|
52
54
|
const normalizedFile = filePath.replace(/\\/g, "/");
|
|
53
55
|
if (normalizedFile.startsWith(normalized + "/") || normalizedFile === normalized) {
|
|
54
|
-
if (!
|
|
55
|
-
|
|
56
|
+
if (!documentedSet.has(filePath)) {
|
|
57
|
+
documentedSet.add(filePath);
|
|
56
58
|
changed = true;
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
if (changed) {
|
|
63
|
+
progress.documented = [...documentedSet];
|
|
61
64
|
progress.lastUpdated = Date.now();
|
|
62
65
|
saveDocProgress(storePath, progress);
|
|
63
66
|
}
|
|
@@ -157,10 +157,31 @@ export interface FileSummary {
|
|
|
157
157
|
language: string;
|
|
158
158
|
chunkCount: number;
|
|
159
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* A single file's chunk payload for a bulk store write.
|
|
162
|
+
* `dedup: true` removes prior-revision rows for the same file path that are
|
|
163
|
+
* not part of this write; `dedup: false` appends only (safe when writing into
|
|
164
|
+
* a freshly-created store where no rows can collide).
|
|
165
|
+
*/
|
|
166
|
+
export interface BulkChunkWrite {
|
|
167
|
+
chunks: Chunk[];
|
|
168
|
+
dedup: boolean;
|
|
169
|
+
}
|
|
160
170
|
/** Persistent vector storage and retrieval backend (LanceDB or in-memory). */
|
|
161
171
|
export interface VectorStore {
|
|
162
|
-
/**
|
|
163
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Store a batch of chunks with their embeddings.
|
|
174
|
+
* @param options - `dedup: false` skips prior-revision cleanup (append-only).
|
|
175
|
+
*/
|
|
176
|
+
addChunks(chunks: Chunk[], options?: {
|
|
177
|
+
dedup?: boolean;
|
|
178
|
+
}): Promise<void>;
|
|
179
|
+
/**
|
|
180
|
+
* Store chunks for multiple files in a single write transaction.
|
|
181
|
+
* Optional — stores without it fall back to per-file `addChunks` calls.
|
|
182
|
+
* @param items - Per-file chunk payloads with dedup flags.
|
|
183
|
+
*/
|
|
184
|
+
addChunksBulk?(items: BulkChunkWrite[]): Promise<void>;
|
|
164
185
|
/** Search for the top-K nearest neighbor chunks by embedding similarity. */
|
|
165
186
|
search(embedding: number[], topK: number): Promise<SearchResult[]>;
|
|
166
187
|
/** Search with optional metadata filtering. */
|
|
@@ -183,8 +204,15 @@ export interface VectorStore {
|
|
|
183
204
|
getChunksByFilePath(filePath: string): Promise<Chunk[]>;
|
|
184
205
|
/** Re-open the store, optionally pointing at a new database path. */
|
|
185
206
|
reopen?(newPath?: string): Promise<void>;
|
|
186
|
-
/**
|
|
187
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Compact fragments and prune old versions to prevent version-manifest
|
|
209
|
+
* accumulation.
|
|
210
|
+
* @param options - `aggressive: true` prunes all but the current version
|
|
211
|
+
* (only safe when no other process reads the store, e.g. temp rebuilds).
|
|
212
|
+
*/
|
|
213
|
+
optimize?(options?: {
|
|
214
|
+
aggressive?: boolean;
|
|
215
|
+
}): Promise<void>;
|
|
188
216
|
/**
|
|
189
217
|
* Verify that the store's data is actually readable.
|
|
190
218
|
* Returns false if data integrity is compromised (e.g., data files missing from disk).
|
package/dist/core/manifest.js
CHANGED
|
@@ -44,7 +44,7 @@ export function computeDescriptionConfigHash(config) {
|
|
|
44
44
|
return undefined;
|
|
45
45
|
const parts = [];
|
|
46
46
|
if (desc) {
|
|
47
|
-
parts.push(`desc:${desc.provider}|${desc.model}|${desc.baseUrl}|${desc.systemPrompt}`);
|
|
47
|
+
parts.push(`desc:${desc.provider}|${desc.model}|${desc.baseUrl}|${desc.systemPrompt}|batch:${desc.batchEnabled === true ? 1 : 0}`);
|
|
48
48
|
}
|
|
49
49
|
if (img) {
|
|
50
50
|
parts.push(`img:${img.provider}|${img.model}|${img.baseUrl}|${img.prompt}`);
|
|
@@ -19,3 +19,5 @@ export declare const PROVIDER_DEFAULTS: Record<string, ProviderDefaults>;
|
|
|
19
19
|
export declare function getProviderDefault(provider: string): ProviderDefaults | undefined;
|
|
20
20
|
/** Check whether a given provider uses an OpenAI-compatible API format. */
|
|
21
21
|
export declare function isOpenAiCompatible(provider: string): boolean;
|
|
22
|
+
/** Whether the given provider can produce embeddings. */
|
|
23
|
+
export declare function supportsEmbedding(provider: string): boolean;
|
|
@@ -81,11 +81,26 @@ export const PROVIDER_DEFAULTS = {
|
|
|
81
81
|
export function getProviderDefault(provider) {
|
|
82
82
|
return PROVIDER_DEFAULTS[provider];
|
|
83
83
|
}
|
|
84
|
+
/** OpenAI-compatible providers (embedding + chat via /v1-style APIs). */
|
|
85
|
+
const OPENAI_COMPATIBLE_PROVIDERS = new Set([
|
|
86
|
+
"openai",
|
|
87
|
+
"nvidia",
|
|
88
|
+
"azure",
|
|
89
|
+
"mistral",
|
|
90
|
+
"together",
|
|
91
|
+
"groq",
|
|
92
|
+
"deepseek",
|
|
93
|
+
"fireworks",
|
|
94
|
+
]);
|
|
84
95
|
/** Check whether a given provider uses an OpenAI-compatible API format. */
|
|
85
96
|
export function isOpenAiCompatible(provider) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return
|
|
97
|
+
// Only KNOWN OpenAI-compatible providers qualify — an unknown/typo'd
|
|
98
|
+
// provider name must fail fast instead of silently attempting
|
|
99
|
+
// OpenAI-style calls against a nonsense base URL.
|
|
100
|
+
return OPENAI_COMPATIBLE_PROVIDERS.has(provider);
|
|
101
|
+
}
|
|
102
|
+
/** Whether the given provider can produce embeddings. */
|
|
103
|
+
export function supportsEmbedding(provider) {
|
|
104
|
+
return PROVIDER_DEFAULTS[provider]?.supportsEmbedding ?? false;
|
|
90
105
|
}
|
|
91
106
|
//# sourceMappingURL=provider-defaults.js.map
|
|
@@ -45,6 +45,11 @@ type NpmRunner = (command: string, options: {
|
|
|
45
45
|
export declare function getCurrentVersion(): string;
|
|
46
46
|
/**
|
|
47
47
|
* Compare two semver-ish strings.
|
|
48
|
+
*
|
|
49
|
+
* Pre-release suffixes are stripped before comparison — "1.2.3-beta.1"
|
|
50
|
+
* must compare equal to "1.2.3" (parseInt would turn "3-beta.1" into 3,
|
|
51
|
+
* and non-numeric segments into NaN which compares equal).
|
|
52
|
+
*
|
|
48
53
|
* @returns 1 if a > b, -1 if a < b, 0 if equal.
|
|
49
54
|
*/
|
|
50
55
|
export declare function compareVersions(a: string, b: string): number;
|
|
@@ -38,11 +38,17 @@ function normalizeVersion(tag) {
|
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* Compare two semver-ish strings.
|
|
41
|
+
*
|
|
42
|
+
* Pre-release suffixes are stripped before comparison — "1.2.3-beta.1"
|
|
43
|
+
* must compare equal to "1.2.3" (parseInt would turn "3-beta.1" into 3,
|
|
44
|
+
* and non-numeric segments into NaN which compares equal).
|
|
45
|
+
*
|
|
41
46
|
* @returns 1 if a > b, -1 if a < b, 0 if equal.
|
|
42
47
|
*/
|
|
43
48
|
export function compareVersions(a, b) {
|
|
44
|
-
const
|
|
45
|
-
const
|
|
49
|
+
const clean = (s) => s.split("-", 1)[0] ?? s;
|
|
50
|
+
const pa = clean(a).split(".").map((s) => parseInt(s, 10));
|
|
51
|
+
const pb = clean(b).split(".").map((s) => parseInt(s, 10));
|
|
46
52
|
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
47
53
|
const na = pa[i] ?? 0;
|
|
48
54
|
const nb = pb[i] ?? 0;
|
|
@@ -25,8 +25,8 @@ export declare class AnthropicDescriptionProvider implements DescriptionProvider
|
|
|
25
25
|
generateBatchDescriptions(chunks: Chunk[], logger?: DescriptionLogger, opts?: BatchDescriptionOptions): Promise<Map<string, string>>;
|
|
26
26
|
/**
|
|
27
27
|
* Sends a request to the Anthropic Messages API with retry and exponential backoff.
|
|
28
|
-
* The system prompt is
|
|
29
|
-
* the
|
|
28
|
+
* The system prompt is sent via the native `system` field (never concatenated into
|
|
29
|
+
* the user message, so chunk content cannot compete with it).
|
|
30
30
|
*
|
|
31
31
|
* @param messages - The user messages to send.
|
|
32
32
|
* @param timeoutMs - Request timeout in milliseconds.
|
|
@@ -59,8 +59,8 @@ export class AnthropicDescriptionProvider {
|
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* Sends a request to the Anthropic Messages API with retry and exponential backoff.
|
|
62
|
-
* The system prompt is
|
|
63
|
-
* the
|
|
62
|
+
* The system prompt is sent via the native `system` field (never concatenated into
|
|
63
|
+
* the user message, so chunk content cannot compete with it).
|
|
64
64
|
*
|
|
65
65
|
* @param messages - The user messages to send.
|
|
66
66
|
* @param timeoutMs - Request timeout in milliseconds.
|
|
@@ -74,7 +74,8 @@ export class AnthropicDescriptionProvider {
|
|
|
74
74
|
const body = {
|
|
75
75
|
model: this.config.model,
|
|
76
76
|
max_tokens: 4096,
|
|
77
|
-
|
|
77
|
+
system: systemPrompt,
|
|
78
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
78
79
|
};
|
|
79
80
|
const headers = {
|
|
80
81
|
"x-api-key": apiKey,
|
|
@@ -85,7 +86,20 @@ export class AnthropicDescriptionProvider {
|
|
|
85
86
|
const retryBaseDelayMs = this.config.retryBaseDelayMs ?? 1000;
|
|
86
87
|
let lastError;
|
|
87
88
|
for (let attempt = 0; attempt <= retryMax; attempt++) {
|
|
88
|
-
|
|
89
|
+
let response;
|
|
90
|
+
try {
|
|
91
|
+
response = await postJson(`${baseUrl}/messages`, body, headers, timeoutMs, this.config.proxy);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// Network-level failures (ECONNREFUSED, socket timeouts) are transient —
|
|
95
|
+
// treat them like retryable HTTP statuses.
|
|
96
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
97
|
+
if (attempt === retryMax)
|
|
98
|
+
throw lastError;
|
|
99
|
+
const delayMs = retryBaseDelayMs * Math.pow(2, attempt) * (0.8 + Math.random() * 0.4);
|
|
100
|
+
await sleep(delayMs);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
89
103
|
if (response.ok) {
|
|
90
104
|
const json = (await response.json());
|
|
91
105
|
const text = json.content?.[0]?.text;
|
|
@@ -100,7 +114,7 @@ export class AnthropicDescriptionProvider {
|
|
|
100
114
|
throw error;
|
|
101
115
|
}
|
|
102
116
|
lastError = error;
|
|
103
|
-
const delayMs = retryBaseDelayMs * Math.pow(2, attempt);
|
|
117
|
+
const delayMs = retryBaseDelayMs * Math.pow(2, attempt) * (0.8 + Math.random() * 0.4);
|
|
104
118
|
await sleep(delayMs);
|
|
105
119
|
}
|
|
106
120
|
throw lastError ?? new Error("Anthropic LLM request failed: unknown error");
|
|
@@ -11,6 +11,10 @@ import type { DescriptionConfig } from "../core/config.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export declare class LlmDescriptionProvider implements DescriptionProvider {
|
|
13
13
|
private readonly config;
|
|
14
|
+
/** Consecutive batches that needed individual fallback; disables batching past BATCH_MAX_STREAK. */
|
|
15
|
+
private batchFailStreak;
|
|
16
|
+
/** Whether multi-chunk batching is still active (adaptive, per provider instance / index run). */
|
|
17
|
+
private adaptiveBatchActive;
|
|
14
18
|
/**
|
|
15
19
|
* @param config - Configuration for the LLM provider, including base URL, model, API key, proxy, and retry settings.
|
|
16
20
|
*/
|
|
@@ -23,6 +27,22 @@ export declare class LlmDescriptionProvider implements DescriptionProvider {
|
|
|
23
27
|
}): Promise<string>;
|
|
24
28
|
/** @inheritdoc */
|
|
25
29
|
generateBatchDescriptions(chunks: Chunk[], logger?: DescriptionLogger, opts?: BatchDescriptionOptions): Promise<Map<string, string>>;
|
|
30
|
+
/**
|
|
31
|
+
* Describe a group of chunks in a single LLM request and parse the response.
|
|
32
|
+
*
|
|
33
|
+
* Builds one chat request whose user message contains all chunks wrapped in
|
|
34
|
+
* `[CHUNK <n>]` markers and expects a `<n>: <description>` line per chunk.
|
|
35
|
+
* Labels are ordinals and are mapped back to chunks by position in the
|
|
36
|
+
* calling group; labels outside the group's range (hallucinations) are
|
|
37
|
+
* naturally dropped, prompting the caller to fall back to individual
|
|
38
|
+
* requests for the missing chunks.
|
|
39
|
+
*
|
|
40
|
+
* @param group - Chunks to describe in one request (length > 1)
|
|
41
|
+
* @param log - Logger for diagnostic messages
|
|
42
|
+
* @returns Map of ordinal label to description (may be partial or empty)
|
|
43
|
+
* @throws When the LLM request itself fails (caller falls back per chunk)
|
|
44
|
+
*/
|
|
45
|
+
private batchDescribe;
|
|
26
46
|
/**
|
|
27
47
|
* Sends a chat completion request to the LLM API with retry and exponential backoff.
|
|
28
48
|
* For Ollama, uses the `/api/chat` endpoint with streaming disabled; otherwise uses the standard `/v1/chat/completions` endpoint.
|