opencode-rag-plugin 1.19.0 → 1.19.1

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 CHANGED
@@ -55,7 +55,7 @@ opencode-rag query "authentication middleware"
55
55
 
56
56
  A browser-based dashboard for exploring the indexed vector database - browse and inspect chunks and evaluate the OpenCode sessions in terms of retrieved chunks, consumed tokens and more.
57
57
 
58
- ![OpenCodeRAG Web UI](doc/assets/eval.png)
58
+ ![OpenCodeRAG Web UI](doc/assets/webui-dashboard.png)
59
59
 
60
60
  Launch with `opencode-rag ui`. See [Web UI documentation](doc/webui.md) for details.
61
61
 
@@ -114,7 +114,7 @@ export const DEFAULT_CONFIG = {
114
114
  minFileSizeBytes: 0,
115
115
  concurrency: 8,
116
116
  embedBatchSize: 100,
117
- embedConcurrency: 6,
117
+ embedConcurrency: 3,
118
118
  ollamaMaxBatchSize: 500,
119
119
  descriptionConcurrency: 4,
120
120
  maxSvgSizeBytes: 1_048_576,
@@ -16,12 +16,16 @@ import type { RagConfig } from "../core/config.js";
16
16
  */
17
17
  export declare function createEmbedder(config: RagConfig): EmbeddingProvider;
18
18
  /**
19
- * Embed a list of texts in batches with optional concurrency control.
19
+ * Embed a list of texts in batches with optional concurrency control and per-batch retry.
20
20
  *
21
21
  * Splits the input texts into chunks of `batchSize` and embeds them sequentially
22
22
  * (or concurrently when `concurrency > 1`). When concurrency is limited, uses
23
23
  * `p-limit` to cap the number of in-flight requests.
24
24
  *
25
+ * Each batch is retried up to `retryMax` times with exponential backoff. If all
26
+ * retries are exhausted, the batch is skipped and empty arrays are returned for
27
+ * those texts so the caller can still process successfully embedded batches.
28
+ *
25
29
  * @param embedder - The embedding provider to use
26
30
  * @param texts - Array of text strings to embed
27
31
  * @param batchSize - Number of texts per batch (default 10)
@@ -29,6 +33,9 @@ export declare function createEmbedder(config: RagConfig): EmbeddingProvider;
29
33
  * @param concurrency - Maximum number of concurrent batch requests (default 1)
30
34
  * @param onProgress - Optional callback invoked after each batch with the running
31
35
  * completed count and total; per-text granularity when `concurrency <= 1`.
32
- * @returns A promise resolving to a flat array of embedding vectors (one per input text)
36
+ * @param retryMax - Maximum retry attempts per batch (default 3)
37
+ * @param retryBaseDelayMs - Base delay for exponential backoff (default 1000)
38
+ * @returns A promise resolving to a flat array of embedding vectors (one per input text);
39
+ * failed batches return empty arrays
33
40
  */
34
- export declare function embedBatch(embedder: EmbeddingProvider, texts: string[], batchSize?: number, purpose?: "query" | "document", concurrency?: number, onProgress?: (completed: number, total: number) => void): Promise<number[][]>;
41
+ export declare function embedBatch(embedder: EmbeddingProvider, texts: string[], batchSize?: number, purpose?: "query" | "document", concurrency?: number, onProgress?: (completed: number, total: number) => void, retryMax?: number, retryBaseDelayMs?: number): Promise<number[][]>;
@@ -35,12 +35,16 @@ export function createEmbedder(config) {
35
35
  throw new Error(`Unknown embedding provider: ${provider}`);
36
36
  }
37
37
  /**
38
- * Embed a list of texts in batches with optional concurrency control.
38
+ * Embed a list of texts in batches with optional concurrency control and per-batch retry.
39
39
  *
40
40
  * Splits the input texts into chunks of `batchSize` and embeds them sequentially
41
41
  * (or concurrently when `concurrency > 1`). When concurrency is limited, uses
42
42
  * `p-limit` to cap the number of in-flight requests.
43
43
  *
44
+ * Each batch is retried up to `retryMax` times with exponential backoff. If all
45
+ * retries are exhausted, the batch is skipped and empty arrays are returned for
46
+ * those texts so the caller can still process successfully embedded batches.
47
+ *
44
48
  * @param embedder - The embedding provider to use
45
49
  * @param texts - Array of text strings to embed
46
50
  * @param batchSize - Number of texts per batch (default 10)
@@ -48,31 +52,56 @@ export function createEmbedder(config) {
48
52
  * @param concurrency - Maximum number of concurrent batch requests (default 1)
49
53
  * @param onProgress - Optional callback invoked after each batch with the running
50
54
  * completed count and total; per-text granularity when `concurrency <= 1`.
51
- * @returns A promise resolving to a flat array of embedding vectors (one per input text)
55
+ * @param retryMax - Maximum retry attempts per batch (default 3)
56
+ * @param retryBaseDelayMs - Base delay for exponential backoff (default 1000)
57
+ * @returns A promise resolving to a flat array of embedding vectors (one per input text);
58
+ * failed batches return empty arrays
52
59
  */
53
- export async function embedBatch(embedder, texts, batchSize = 10, purpose, concurrency = 1, onProgress) {
60
+ export async function embedBatch(embedder, texts, batchSize = 10, purpose, concurrency = 1, onProgress, retryMax = 3, retryBaseDelayMs = 1000) {
54
61
  if (texts.length === 0)
55
62
  return [];
56
63
  const batches = [];
57
64
  for (let i = 0; i < texts.length; i += batchSize) {
58
65
  batches.push({ index: i, texts: texts.slice(i, i + batchSize) });
59
66
  }
67
+ async function embedWithRetry(batchTexts) {
68
+ for (let attempt = 0; attempt <= retryMax; attempt++) {
69
+ try {
70
+ return await embedder.embed(batchTexts, purpose);
71
+ }
72
+ catch (err) {
73
+ if (attempt < retryMax) {
74
+ const delay = retryBaseDelayMs * Math.pow(2, attempt);
75
+ await new Promise(resolve => setTimeout(resolve, delay));
76
+ }
77
+ }
78
+ }
79
+ return null;
80
+ }
60
81
  if (concurrency <= 1 || batches.length <= 1) {
61
82
  const results = [];
62
83
  for (const batch of batches) {
63
- const embeddings = await embedder.embed(batch.texts, purpose);
64
- results.push(...embeddings);
84
+ const embeddings = await embedWithRetry(batch.texts);
85
+ if (embeddings) {
86
+ results.push(...embeddings);
87
+ }
88
+ else {
89
+ for (let i = 0; i < batch.texts.length; i++) {
90
+ results.push([]);
91
+ }
92
+ }
65
93
  onProgress?.(results.length, texts.length);
66
94
  }
67
95
  return results;
68
96
  }
69
- const limit = pLimit(concurrency);
70
97
  let completedCount = 0;
98
+ const limit = pLimit(concurrency);
71
99
  const batchResults = await Promise.all(batches.map((batch) => limit(async () => {
72
- const embeddings = await embedder.embed(batch.texts, purpose);
73
- completedCount += embeddings.length;
100
+ const embeddings = await embedWithRetry(batch.texts);
101
+ const flatResult = embeddings ?? batch.texts.map(() => []);
102
+ completedCount += embeddings?.length ?? 0;
74
103
  onProgress?.(completedCount, texts.length);
75
- return { index: batch.index, embeddings };
104
+ return { index: batch.index, embeddings: flatResult };
76
105
  })));
77
106
  batchResults.sort((a, b) => a.index - b.index);
78
107
  const results = [];
@@ -590,7 +590,7 @@ async function runIndexPassInner(options, logger) {
590
590
  hash: prepared[fileIdx].hash,
591
591
  chunkCount: 0, fileLabel: prepared[fileIdx].fileLabel,
592
592
  isNew: false, isModified: false, isUnchanged: false, isEmpty: false,
593
- isTooSmall: false, isRemoved: true, hadChunks: false,
593
+ isTooSmall: false, isRemoved: false, hadChunks: false,
594
594
  descriptionFailed: prepared[fileIdx].descriptionFailed,
595
595
  });
596
596
  }
@@ -639,14 +639,14 @@ async function runIndexPassInner(options, logger) {
639
639
  const result = {
640
640
  normalizedPath: prep.normalizedPath,
641
641
  hash: prep.hash,
642
- chunkCount: prep.chunks?.length ?? 0,
642
+ chunkCount: validChunks.length,
643
643
  fileLabel: prep.fileLabel,
644
644
  isNew: !prep.isModified,
645
645
  isModified: prep.isModified,
646
646
  isUnchanged: false,
647
647
  isEmpty: false,
648
648
  isTooSmall: false,
649
- isRemoved: validChunks.length === 0,
649
+ isRemoved: (prep.chunks?.length ?? 0) > 0 && validChunks.length === 0 ? false : validChunks.length === 0,
650
650
  hadChunks: (prep.chunks?.length ?? 0) > 0,
651
651
  descriptionFailed: prep.descriptionFailed,
652
652
  descHash: prep.descHash,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-rag-plugin",
3
- "version": "1.19.0",
3
+ "version": "1.19.1",
4
4
  "description": "OpenCode plugin for local-first RAG-based semantic code search",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin-entry.js",