/** * In-database vector storage + brute-force search. * * Design: * - Vectors are stored as BLOB columns holding a little-endian Float32 buffer. * Encoding: `encodeVector(Float32Array) -> Buffer`. * - Each row additionally stores its squared L2 norm² (cached) so cosine * similarity can be computed without recomputing sqrt on every query. * - Search is brute-force: `SELECT id, vec, norm2 FROM WHERE vec IS NOT NULL` * then we compute cosine(q, v) in JS and keep top-K with a small heap. * - We *intentionally* don't rely on sqlite-wasm or vss. Pure JS brute is * ~1 M × 384 in <50ms on a laptop, which is plenty for local plugin use. * * When usage grows past, say, 100K rows per table, this module is the single * place to swap in an ANN index (e.g. hnswlib-node or faiss). */ import type { EmbeddingVector } from "../types.js"; import type { StorageDb } from "./types.js"; /** Float32Array → Buffer (little-endian, zero-copy when possible). */ export declare function encodeVector(vec: EmbeddingVector): Buffer; /** Buffer → Float32Array. Copies so callers can't mutate the underlying DB blob. */ export declare function decodeVector(buf: Buffer | Uint8Array | null | undefined): EmbeddingVector | null; export declare function dot(a: EmbeddingVector, b: EmbeddingVector): number; export declare function norm2(a: EmbeddingVector): number; export declare function cosine(a: EmbeddingVector, b: EmbeddingVector): number; /** * Cosine similarity using pre-computed norm² of `b`. Saves one sqrt + one pass * per candidate when the query side is fixed. */ export declare function cosinePrenormed(a: EmbeddingVector, aNorm: number, b: EmbeddingVector, bNorm2: number): number; export interface VectorRow { id: TId; vec: EmbeddingVector; /** Pre-computed L2 norm². If absent we compute + cache. */ norm2?: number; meta?: TMeta; } export interface VectorHit { id: TId; score: number; meta?: TMeta; } /** * Brute-force top-K cosine search over an in-memory array of rows. Stable * (ties ordered by input order). Mutates `rows[i].norm2` if it was missing. */ export declare function topKCosine(query: EmbeddingVector, rows: Array>, k: number): Array>; export interface VectorScanOptions { /** Name of the BLOB column holding the vector. */ vecColumn: string; /** Name of the REAL column caching norm². If absent we compute per-row. */ norm2Column?: string; /** Optional WHERE clause (without the "WHERE"). */ where?: string; /** Parameters for the WHERE clause. */ params?: Record; /** Optional LIMIT to cap candidates fetched from SQLite. */ hardCap?: number; } export interface ScanRow { id: string; vec: Buffer | null; norm2?: number | null; [k: string]: unknown; } /** * Default LIMIT applied when the caller doesn't pass an explicit * `hardCap`. Historically 100_000, which — combined with the old * `.all()` materialisation — meant one accidental "no cap" call * could pull 100k multi-KB vector BLOBs into JS memory in a single * synchronous step. On the reporter's DB in issue #2076 that * translated to 4.2 GB RSS and a 100 % CPU main-thread stall. * * 5_000 is a much safer default: it still covers realistic per-agent * corpora and it forces callers that legitimately need more to opt * in explicitly. */ export declare const DEFAULT_SCAN_HARD_CAP = 5000; /** * Stream rows from `table`, decode vectors, and run top-K cosine against * `query`. `selectExtra` lets callers bring along columns that will surface in * `VectorHit.meta`. * * Streaming: we use `.iterate()` (not `.all()`) so at most one row's * BLOB is decoded at a time. The top-K min-heap keeps only `k` * vectors of state, so peak RSS is O(k * dim) regardless of how many * rows the LIMIT allows. Fixes the "load 100k BLOBs synchronously" * pathology in #2076. */ export declare function scanAndTopK(db: StorageDb, table: string, selectExtra: string[], query: EmbeddingVector, k: number, opts: VectorScanOptions): Array>; //# sourceMappingURL=vector.d.ts.map