opencode-rag-plugin 1.22.0 → 1.22.2
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/cli/commands/index-command.js +10 -1
- package/dist/cli/commands/status.js +14 -0
- package/dist/cli/format.js +10 -1
- package/dist/core/bootstrap.d.ts +2 -0
- package/dist/core/bootstrap.js +45 -16
- package/dist/core/config.d.ts +14 -0
- package/dist/core/config.js +2 -0
- package/dist/core/interfaces.d.ts +8 -0
- package/dist/core/runtime-overrides.d.ts +1 -0
- package/dist/core/runtime-overrides.js +1 -0
- package/dist/embedder/factory.d.ts +20 -0
- package/dist/embedder/factory.js +24 -0
- package/dist/indexer/pipeline.js +175 -64
- package/dist/indexer/stats.d.ts +4 -0
- package/dist/indexer/stats.js +2 -0
- package/dist/indexer/worker.d.ts +8 -2
- package/dist/indexer/worker.js +9 -4
- package/dist/plugin.js +32 -15
- package/dist/retriever/context-optimizer.js +9 -2
- package/dist/tui.js +11 -3
- package/dist/vectorstore/lancedb.d.ts +60 -1
- package/dist/vectorstore/lancedb.js +204 -5
- package/package.json +1 -1
|
@@ -53,10 +53,45 @@ export declare function isCorruptionError(err: unknown): boolean;
|
|
|
53
53
|
* @returns True if the error matches a transient transaction conflict.
|
|
54
54
|
*/
|
|
55
55
|
export declare function isTransientConflictError(err: unknown): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Thrown when a write supplies embeddings whose length differs from the
|
|
58
|
+
* store's vector column dimension. LanceDB itself does NOT validate this —
|
|
59
|
+
* it silently zero-pads or truncates vectors into the fixed-size column,
|
|
60
|
+
* producing rows that can never be found by a query. Failing loudly here
|
|
61
|
+
* keeps that silent corruption out of the store.
|
|
62
|
+
*/
|
|
63
|
+
export declare class DimensionMismatchError extends Error {
|
|
64
|
+
/** Dimension of the store's vector column. */
|
|
65
|
+
readonly storeDimension: number;
|
|
66
|
+
/** Dimension of the embedding that was supplied. */
|
|
67
|
+
readonly embeddingDimension: number;
|
|
68
|
+
constructor(storeDimension: number, embeddingDimension: number, action?: string);
|
|
69
|
+
}
|
|
70
|
+
/** Type guard for {@link DimensionMismatchError}. */
|
|
71
|
+
export declare function isDimensionMismatchError(err: unknown): err is DimensionMismatchError;
|
|
72
|
+
/**
|
|
73
|
+
* Extract the fixed-size vector dimension of the `embedding` column from a
|
|
74
|
+
* LanceDB/Arrow schema. Returns `undefined` when the column is missing or is
|
|
75
|
+
* not a fixed-size list (e.g. before the table is created).
|
|
76
|
+
*/
|
|
77
|
+
export declare function extractEmbeddingDimension(fields: ReadonlyArray<{
|
|
78
|
+
name: string;
|
|
79
|
+
type: unknown;
|
|
80
|
+
}>): number | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Read the embedding dimension of an existing store without creating a table
|
|
83
|
+
* or holding a long-lived connection. Returns `undefined` when the store or
|
|
84
|
+
* table does not exist (or cannot be read).
|
|
85
|
+
*
|
|
86
|
+
* Used by the CLI bootstrap to avoid creating a brand-new store with a
|
|
87
|
+
* speculative dimension when the embedding provider cannot be probed.
|
|
88
|
+
*/
|
|
89
|
+
export declare function readStoreDimension(storePath: string): Promise<number | undefined>;
|
|
56
90
|
/**
|
|
57
91
|
* Atomically replace one LanceDB store directory with another.
|
|
58
92
|
* Swaps the real directory with a temporary one that was built during a rebuild.
|
|
59
|
-
* The old directory is moved to `${realPath}_old` and deleted asynchronously
|
|
93
|
+
* The old directory is moved to `${realPath}_old` and deleted asynchronously
|
|
94
|
+
* after non-Lance artifacts (quirk memory, caches) have been carried over.
|
|
60
95
|
*
|
|
61
96
|
* @param tempPath - Path to the newly built store (source).
|
|
62
97
|
* @param realPath - Path to the current store (destination, will be replaced).
|
|
@@ -94,6 +129,13 @@ export declare class LanceDbStore implements VectorStore {
|
|
|
94
129
|
private indexRepairPromise;
|
|
95
130
|
/** Consecutive failed repair attempts — bounded so a broken store cannot retrain forever. */
|
|
96
131
|
private indexRepairFailures;
|
|
132
|
+
/**
|
|
133
|
+
* Actual dimension of the `embedding` column in the opened table. Read from
|
|
134
|
+
* the schema on first table access and used to validate writes/searches —
|
|
135
|
+
* the constructor's `vectorDimension` describes what the caller *expects*,
|
|
136
|
+
* which can drift from the store when the embedding model changes.
|
|
137
|
+
*/
|
|
138
|
+
private knownDimension;
|
|
97
139
|
/**
|
|
98
140
|
* Execute an async function under an exclusive write lock.
|
|
99
141
|
*
|
|
@@ -120,6 +162,23 @@ export declare class LanceDbStore implements VectorStore {
|
|
|
120
162
|
private getTable;
|
|
121
163
|
private initTable;
|
|
122
164
|
private tableHasDescriptionColumn;
|
|
165
|
+
/**
|
|
166
|
+
* Read and cache the actual fixed-size dimension of the `embedding` column.
|
|
167
|
+
* Called whenever the table is (re)opened so write/search validation uses
|
|
168
|
+
* the store's real schema instead of the constructor's expectation.
|
|
169
|
+
*/
|
|
170
|
+
private cacheTableDimension;
|
|
171
|
+
/**
|
|
172
|
+
* Return the actual embedding dimension of the store's vector column, or
|
|
173
|
+
* `undefined` when no table exists yet. Never creates the table.
|
|
174
|
+
*/
|
|
175
|
+
getVectorDimension(): Promise<number | undefined>;
|
|
176
|
+
/**
|
|
177
|
+
* Fail loudly when rows carry embeddings whose length differs from the
|
|
178
|
+
* store's vector column. LanceDB would silently pad/truncate them instead,
|
|
179
|
+
* leaving rows that no query can match.
|
|
180
|
+
*/
|
|
181
|
+
private assertEmbeddingDimensions;
|
|
123
182
|
private hasColumn;
|
|
124
183
|
/** Add kind/quirkType/tags columns if missing from an existing table. */
|
|
125
184
|
private migrateNewColumns;
|
|
@@ -148,10 +148,129 @@ export function isTransientConflictError(err) {
|
|
|
148
148
|
}
|
|
149
149
|
return false;
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Thrown when a write supplies embeddings whose length differs from the
|
|
153
|
+
* store's vector column dimension. LanceDB itself does NOT validate this —
|
|
154
|
+
* it silently zero-pads or truncates vectors into the fixed-size column,
|
|
155
|
+
* producing rows that can never be found by a query. Failing loudly here
|
|
156
|
+
* keeps that silent corruption out of the store.
|
|
157
|
+
*/
|
|
158
|
+
export class DimensionMismatchError extends Error {
|
|
159
|
+
/** Dimension of the store's vector column. */
|
|
160
|
+
storeDimension;
|
|
161
|
+
/** Dimension of the embedding that was supplied. */
|
|
162
|
+
embeddingDimension;
|
|
163
|
+
constructor(storeDimension, embeddingDimension, action = "rebuild it with 'opencode-rag index --force'") {
|
|
164
|
+
super(`Embedding dimension mismatch: the store holds ${storeDimension}-dimensional vectors ` +
|
|
165
|
+
`but the embedding model produced ${embeddingDimension}. The index was built with a ` +
|
|
166
|
+
`different embedding model — ${action}.`);
|
|
167
|
+
this.name = "DimensionMismatchError";
|
|
168
|
+
this.storeDimension = storeDimension;
|
|
169
|
+
this.embeddingDimension = embeddingDimension;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** Type guard for {@link DimensionMismatchError}. */
|
|
173
|
+
export function isDimensionMismatchError(err) {
|
|
174
|
+
return err instanceof DimensionMismatchError || (err instanceof Error && err.name === "DimensionMismatchError");
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Extract the fixed-size vector dimension of the `embedding` column from a
|
|
178
|
+
* LanceDB/Arrow schema. Returns `undefined` when the column is missing or is
|
|
179
|
+
* not a fixed-size list (e.g. before the table is created).
|
|
180
|
+
*/
|
|
181
|
+
export function extractEmbeddingDimension(fields) {
|
|
182
|
+
const field = fields.find((f) => f.name === "embedding");
|
|
183
|
+
const type = field?.type;
|
|
184
|
+
if (type && typeof type.listSize === "number" && type.listSize > 0) {
|
|
185
|
+
return type.listSize;
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Read the embedding dimension of an existing store without creating a table
|
|
191
|
+
* or holding a long-lived connection. Returns `undefined` when the store or
|
|
192
|
+
* table does not exist (or cannot be read).
|
|
193
|
+
*
|
|
194
|
+
* Used by the CLI bootstrap to avoid creating a brand-new store with a
|
|
195
|
+
* speculative dimension when the embedding provider cannot be probed.
|
|
196
|
+
*/
|
|
197
|
+
export async function readStoreDimension(storePath) {
|
|
198
|
+
if (storePath.startsWith("memory:"))
|
|
199
|
+
return undefined;
|
|
200
|
+
try {
|
|
201
|
+
// Do not create a store directory just to read a schema.
|
|
202
|
+
await fs.access(storePath);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const db = await lancedb.connect(storePath);
|
|
209
|
+
const tableNames = await db.tableNames();
|
|
210
|
+
if (!tableNames.includes(TABLE_NAME))
|
|
211
|
+
return undefined;
|
|
212
|
+
const table = await db.openTable(TABLE_NAME);
|
|
213
|
+
const schema = await table.schema();
|
|
214
|
+
return extractEmbeddingDimension(schema.fields);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Non-Lance artifacts that live in the store directory next to the LanceDB
|
|
222
|
+
* data and must survive a rebuild swap. `chunks.lance`/`manifest.json` are
|
|
223
|
+
* rebuilt by the pipeline; the files below are not derivable from the new
|
|
224
|
+
* index and would otherwise be destroyed with the old directory.
|
|
225
|
+
*/
|
|
226
|
+
const PRESERVED_STORE_ENTRIES = [
|
|
227
|
+
"quirks.jsonl",
|
|
228
|
+
"runtime-overrides.json",
|
|
229
|
+
"watcher-status.json",
|
|
230
|
+
".desc-cache.json",
|
|
231
|
+
"keyword-index.json",
|
|
232
|
+
"eval-sessions",
|
|
233
|
+
];
|
|
234
|
+
/**
|
|
235
|
+
* Carry non-Lance store artifacts (quirk memory, caches, overrides) from the
|
|
236
|
+
* pre-swap directory into the freshly promoted one. Best-effort: a failure to
|
|
237
|
+
* preserve one entry must not abort the swap.
|
|
238
|
+
*/
|
|
239
|
+
async function preserveStoreArtifacts(oldPath, realPath) {
|
|
240
|
+
for (const entry of PRESERVED_STORE_ENTRIES) {
|
|
241
|
+
const src = path.join(oldPath, entry);
|
|
242
|
+
const dest = path.join(realPath, entry);
|
|
243
|
+
try {
|
|
244
|
+
await fs.access(src);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
continue; // artifact not present in the old store
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
try {
|
|
251
|
+
await fs.access(dest);
|
|
252
|
+
continue; // destination already has this artifact (e.g. re-saved desc cache)
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// destination missing — move or copy it over
|
|
256
|
+
}
|
|
257
|
+
await fs.rename(src, dest);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
try {
|
|
261
|
+
await fs.cp(src, dest, { recursive: true, force: true });
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// best-effort — keep the swap result even if an artifact cannot be carried over
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
151
269
|
/**
|
|
152
270
|
* Atomically replace one LanceDB store directory with another.
|
|
153
271
|
* Swaps the real directory with a temporary one that was built during a rebuild.
|
|
154
|
-
* The old directory is moved to `${realPath}_old` and deleted asynchronously
|
|
272
|
+
* The old directory is moved to `${realPath}_old` and deleted asynchronously
|
|
273
|
+
* after non-Lance artifacts (quirk memory, caches) have been carried over.
|
|
155
274
|
*
|
|
156
275
|
* @param tempPath - Path to the newly built store (source).
|
|
157
276
|
* @param realPath - Path to the current store (destination, will be replaced).
|
|
@@ -176,7 +295,9 @@ export async function swapStoreDirectories(tempPath, realPath) {
|
|
|
176
295
|
catch { }
|
|
177
296
|
throw err;
|
|
178
297
|
}
|
|
179
|
-
//
|
|
298
|
+
// Preserve quirks.jsonl & friends (the temp store contains only Lance data),
|
|
299
|
+
// then clean up the old directory best-effort.
|
|
300
|
+
await preserveStoreArtifacts(oldPath, realPath);
|
|
180
301
|
fs.rm(oldPath, { recursive: true, force: true }).catch(() => { });
|
|
181
302
|
}
|
|
182
303
|
/**
|
|
@@ -201,6 +322,13 @@ export class LanceDbStore {
|
|
|
201
322
|
indexRepairPromise = null;
|
|
202
323
|
/** Consecutive failed repair attempts — bounded so a broken store cannot retrain forever. */
|
|
203
324
|
indexRepairFailures = 0;
|
|
325
|
+
/**
|
|
326
|
+
* Actual dimension of the `embedding` column in the opened table. Read from
|
|
327
|
+
* the schema on first table access and used to validate writes/searches —
|
|
328
|
+
* the constructor's `vectorDimension` describes what the caller *expects*,
|
|
329
|
+
* which can drift from the store when the embedding model changes.
|
|
330
|
+
*/
|
|
331
|
+
knownDimension = null;
|
|
204
332
|
/**
|
|
205
333
|
* Execute an async function under an exclusive write lock.
|
|
206
334
|
*
|
|
@@ -270,6 +398,7 @@ export class LanceDbStore {
|
|
|
270
398
|
const tableNames = await db.tableNames();
|
|
271
399
|
if (tableNames.includes(TABLE_NAME)) {
|
|
272
400
|
this.table = await db.openTable(TABLE_NAME);
|
|
401
|
+
await this.cacheTableDimension(this.table);
|
|
273
402
|
if (await this.tableHasDescriptionColumn()) {
|
|
274
403
|
await this.migrateNewColumns();
|
|
275
404
|
return this.table;
|
|
@@ -323,6 +452,7 @@ export class LanceDbStore {
|
|
|
323
452
|
data: [seedRow],
|
|
324
453
|
mode: "overwrite",
|
|
325
454
|
});
|
|
455
|
+
this.knownDimension = this.vectorDimension;
|
|
326
456
|
const deleted = await this.table.delete('id = "__seed__"');
|
|
327
457
|
if (deleted === undefined) {
|
|
328
458
|
// LanceDB may not return a count; try a direct query to verify
|
|
@@ -342,6 +472,59 @@ export class LanceDbStore {
|
|
|
342
472
|
return false;
|
|
343
473
|
}
|
|
344
474
|
}
|
|
475
|
+
/**
|
|
476
|
+
* Read and cache the actual fixed-size dimension of the `embedding` column.
|
|
477
|
+
* Called whenever the table is (re)opened so write/search validation uses
|
|
478
|
+
* the store's real schema instead of the constructor's expectation.
|
|
479
|
+
*/
|
|
480
|
+
async cacheTableDimension(table) {
|
|
481
|
+
if (this.knownDimension !== null)
|
|
482
|
+
return this.knownDimension;
|
|
483
|
+
try {
|
|
484
|
+
const schema = await table.schema();
|
|
485
|
+
const dim = extractEmbeddingDimension(schema.fields);
|
|
486
|
+
if (dim !== undefined)
|
|
487
|
+
this.knownDimension = dim;
|
|
488
|
+
return dim;
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Return the actual embedding dimension of the store's vector column, or
|
|
496
|
+
* `undefined` when no table exists yet. Never creates the table.
|
|
497
|
+
*/
|
|
498
|
+
async getVectorDimension() {
|
|
499
|
+
if (this.knownDimension !== null)
|
|
500
|
+
return this.knownDimension;
|
|
501
|
+
try {
|
|
502
|
+
const db = await this.getDb();
|
|
503
|
+
const tableNames = await db.tableNames();
|
|
504
|
+
if (!tableNames.includes(TABLE_NAME))
|
|
505
|
+
return undefined;
|
|
506
|
+
const table = await this.getTable();
|
|
507
|
+
return await this.cacheTableDimension(table);
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Fail loudly when rows carry embeddings whose length differs from the
|
|
515
|
+
* store's vector column. LanceDB would silently pad/truncate them instead,
|
|
516
|
+
* leaving rows that no query can match.
|
|
517
|
+
*/
|
|
518
|
+
assertEmbeddingDimensions(rows) {
|
|
519
|
+
const storeDim = this.knownDimension;
|
|
520
|
+
if (storeDim === null)
|
|
521
|
+
return;
|
|
522
|
+
for (const row of rows) {
|
|
523
|
+
if (row.embedding.length !== storeDim) {
|
|
524
|
+
throw new DimensionMismatchError(storeDim, row.embedding.length);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
345
528
|
async hasColumn(name) {
|
|
346
529
|
try {
|
|
347
530
|
const schema = await this.table.schema();
|
|
@@ -489,6 +672,7 @@ export class LanceDbStore {
|
|
|
489
672
|
.filter((r) => r !== null);
|
|
490
673
|
if (rows.length === 0)
|
|
491
674
|
return;
|
|
675
|
+
this.assertEmbeddingDimensions(rows);
|
|
492
676
|
// INSERT FIRST: data is safely stored before any delete
|
|
493
677
|
await table.add(rows);
|
|
494
678
|
if (!dedup)
|
|
@@ -530,6 +714,7 @@ export class LanceDbStore {
|
|
|
530
714
|
}
|
|
531
715
|
if (allRows.length === 0)
|
|
532
716
|
return;
|
|
717
|
+
this.assertEmbeddingDimensions(allRows);
|
|
533
718
|
// INSERT FIRST (single add for the whole batch), then per-file dedup
|
|
534
719
|
await table.add(allRows);
|
|
535
720
|
for (const [filePath, ids] of dedupByFile) {
|
|
@@ -551,9 +736,21 @@ export class LanceDbStore {
|
|
|
551
736
|
async searchWithFilter(embedding, topK, filter) {
|
|
552
737
|
try {
|
|
553
738
|
// Guard against dimension mismatch BEFORE the native call — LanceDB
|
|
554
|
-
// throws a cryptic
|
|
555
|
-
|
|
556
|
-
|
|
739
|
+
// throws a cryptic "No vector column found to match with the query
|
|
740
|
+
// vector dimension" error. Compare against the table's *actual* column
|
|
741
|
+
// dimension, not the constructor's expectation: a store built by a
|
|
742
|
+
// different embedding model has a mismatching schema even when the
|
|
743
|
+
// handle was constructed with the current model's dimension.
|
|
744
|
+
const storeDimension = (await this.getVectorDimension()) ?? this.vectorDimension;
|
|
745
|
+
if (embedding.length !== storeDimension) {
|
|
746
|
+
if (storeDimension !== this.vectorDimension) {
|
|
747
|
+
console.warn(`[lancedb] Store vector column is ${storeDimension}-dimensional but this handle expects ` +
|
|
748
|
+
`${this.vectorDimension} — the index was built with a different embedding model. ` +
|
|
749
|
+
"Rebuild it with 'opencode-rag index --force' (a plain 'opencode-rag index' also rebuilds automatically).");
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
console.warn(`[lancedb] searchWithFilter: query embedding dimension ${embedding.length} != store dimension ${storeDimension} — returning empty`);
|
|
753
|
+
}
|
|
557
754
|
return [];
|
|
558
755
|
}
|
|
559
756
|
return await this.searchInternal(embedding, topK, filter);
|
|
@@ -1090,6 +1287,7 @@ export class LanceDbStore {
|
|
|
1090
1287
|
await this.close();
|
|
1091
1288
|
if (newPath)
|
|
1092
1289
|
this.dbPath = newPath;
|
|
1290
|
+
this.knownDimension = null;
|
|
1093
1291
|
}
|
|
1094
1292
|
/**
|
|
1095
1293
|
* Close the database connection and release resources.
|
|
@@ -1153,6 +1351,7 @@ export class LanceDbStore {
|
|
|
1153
1351
|
console.warn(`[lancedb] Backed up chunks.lance to ${backup}`);
|
|
1154
1352
|
await this.table?.close();
|
|
1155
1353
|
this.table = null;
|
|
1354
|
+
this.knownDimension = null;
|
|
1156
1355
|
try {
|
|
1157
1356
|
const db = await this.getDb();
|
|
1158
1357
|
const tableNames = await db.tableNames();
|