@dudousxd/nestjs-agent-rag 0.3.9 → 0.4.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/dist/index.cjs CHANGED
@@ -84,10 +84,17 @@ __name(documentIdOf, "documentIdOf");
84
84
 
85
85
  // src/filter.ts
86
86
  function matchesFilter(metadata, filter) {
87
+ const entries = Object.entries(filter);
87
88
  if (metadata === void 0) {
88
- return Object.keys(filter).length === 0;
89
+ return entries.length === 0;
89
90
  }
90
- return Object.entries(filter).every(([key, value]) => metadata[key] === value);
91
+ return entries.every(([key, expected]) => {
92
+ if (Array.isArray(expected)) {
93
+ const actual = metadata[key];
94
+ return Array.isArray(actual) ? actual.some((value) => expected.includes(value)) : expected.includes(actual);
95
+ }
96
+ return metadata[key] === expected;
97
+ });
91
98
  }
92
99
  __name(matchesFilter, "matchesFilter");
93
100
 
@@ -216,13 +223,11 @@ var PgVectorStore = class {
216
223
  ]);
217
224
  }
218
225
  async listDocuments(filter) {
219
- const hasFilter = filter !== void 0 && Object.keys(filter).length > 0;
226
+ const where = buildWhere(filter, 1);
220
227
  const rows = await this.client.query(`SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata
221
228
  FROM ${this.table}
222
- ${hasFilter ? "WHERE metadata @> $1::jsonb" : ""}
223
- ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`, hasFilter ? [
224
- JSON.stringify(filter)
225
- ] : []);
229
+ ${where.sql}
230
+ ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`, where.params);
226
231
  return rows.map((row) => ({
227
232
  id: row.doc_id,
228
233
  ...row.metadata !== null ? {
@@ -232,18 +237,15 @@ var PgVectorStore = class {
232
237
  }
233
238
  async search(embedding, options) {
234
239
  const vector = toVectorLiteral(embedding);
235
- const hasFilter = options.filter !== void 0 && Object.keys(options.filter).length > 0;
240
+ const where = buildWhere(options.filter, 3);
236
241
  const rows = await this.client.query(`SELECT id, text, source, metadata, 1 - (embedding <=> $1::vector) AS score
237
242
  FROM ${this.table}
238
- ${hasFilter ? "WHERE metadata @> $3::jsonb" : ""}
243
+ ${where.sql}
239
244
  ORDER BY embedding <=> $1::vector
240
- LIMIT $2`, hasFilter ? [
245
+ LIMIT $2`, [
241
246
  vector,
242
247
  options.topK,
243
- JSON.stringify(options.filter)
244
- ] : [
245
- vector,
246
- options.topK
248
+ ...where.params
247
249
  ]);
248
250
  return rows.map((row) => ({
249
251
  id: row.id,
@@ -258,6 +260,45 @@ var PgVectorStore = class {
258
260
  }));
259
261
  }
260
262
  };
263
+ function buildWhere(filter, startIndex) {
264
+ if (filter === void 0 || Object.keys(filter).length === 0) {
265
+ return {
266
+ sql: "",
267
+ params: []
268
+ };
269
+ }
270
+ const clauses = [];
271
+ const params = [];
272
+ const scalar = {};
273
+ let index = startIndex;
274
+ for (const [key, value] of Object.entries(filter)) {
275
+ if (Array.isArray(value)) {
276
+ if (value.length === 0) {
277
+ clauses.push("false");
278
+ continue;
279
+ }
280
+ const keyParam = `$${index}`;
281
+ params.push(key);
282
+ index += 1;
283
+ const arrParam = `$${index}`;
284
+ params.push(value.map(String));
285
+ index += 1;
286
+ clauses.push(`(CASE WHEN jsonb_typeof(metadata->${keyParam}) = 'array' THEN metadata->${keyParam} ELSE jsonb_build_array(metadata->${keyParam}) END) ?| ${arrParam}::text[]`);
287
+ } else {
288
+ scalar[key] = value;
289
+ }
290
+ }
291
+ if (Object.keys(scalar).length > 0) {
292
+ clauses.push(`metadata @> $${index}::jsonb`);
293
+ params.push(JSON.stringify(scalar));
294
+ index += 1;
295
+ }
296
+ return {
297
+ sql: `WHERE ${clauses.join(" AND ")}`,
298
+ params
299
+ };
300
+ }
301
+ __name(buildWhere, "buildWhere");
261
302
  var DOCUMENT_ID_FROM_CHUNK = "regexp_replace(id, '#[0-9]+$', '')";
262
303
  function toVectorLiteral(embedding) {
263
304
  return `[${embedding.join(",")}]`;
@@ -334,7 +375,8 @@ var RedisVectorStore = class {
334
375
  for (const field of this.filterableFields) {
335
376
  const value = record.metadata[field];
336
377
  if (value !== void 0) {
337
- args.push(`meta_${field}`, String(value));
378
+ const tag = Array.isArray(value) ? value.map(String).join(",") : String(value);
379
+ args.push(`meta_${field}`, tag);
338
380
  }
339
381
  }
340
382
  }
@@ -370,6 +412,9 @@ var RedisVectorStore = class {
370
412
  }
371
413
  }
372
414
  async listDocuments(filter) {
415
+ if (filterMatchesNothing(filter)) {
416
+ return [];
417
+ }
373
418
  const query = buildFilter(filter);
374
419
  const documents = /* @__PURE__ */ new Map();
375
420
  const batchSize = 1e3;
@@ -410,6 +455,9 @@ var RedisVectorStore = class {
410
455
  ];
411
456
  }
412
457
  async search(embedding, options) {
458
+ if (filterMatchesNothing(options.filter)) {
459
+ return [];
460
+ }
413
461
  const query = `${buildFilter(options.filter)}=>[KNN ${options.topK} @embedding $BLOB AS vector_score]`;
414
462
  const reply = await this.client.sendCommand([
415
463
  "FT.SEARCH",
@@ -445,10 +493,23 @@ function buildFilter(filter) {
445
493
  if (filter === void 0 || Object.keys(filter).length === 0) {
446
494
  return "*";
447
495
  }
448
- const clauses = Object.entries(filter).map(([key, value]) => `@meta_${key}:{${escapeTag(String(value))}}`);
496
+ const clauses = Object.entries(filter).map(([key, value]) => {
497
+ const values = Array.isArray(value) ? value : [
498
+ value
499
+ ];
500
+ const alternation = values.map((entry) => escapeTag(String(entry))).join("|");
501
+ return `@meta_${key}:{${alternation}}`;
502
+ });
449
503
  return `(${clauses.join(" ")})`;
450
504
  }
451
505
  __name(buildFilter, "buildFilter");
506
+ function filterMatchesNothing(filter) {
507
+ if (filter === void 0) {
508
+ return false;
509
+ }
510
+ return Object.values(filter).some((value) => Array.isArray(value) && value.length === 0);
511
+ }
512
+ __name(filterMatchesNothing, "filterMatchesNothing");
452
513
  function escapeTag(value) {
453
514
  return value.replace(/[^a-zA-Z0-9_]/g, (char) => `\\${char}`);
454
515
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/chunk.ts","../src/vector-store.ts","../src/filter.ts","../src/memory-vector-store.ts","../src/pg-vector-store.ts","../src/redis-vector-store.ts","../src/embedding-retriever.ts","../src/keyword-retriever.ts","../src/hybrid-retriever.ts","../src/reranking-retriever.ts","../src/filtered-retriever.ts","../src/ingest.ts","../src/retrieval-tool.ts"],"sourcesContent":["export { chunkText, type ChunkOptions } from './chunk.js';\nexport type {\n IndexedDocument,\n VectorRecord,\n VectorSearchOptions,\n VectorStore,\n} from './vector-store.js';\nexport { documentIdOf } from './vector-store.js';\nexport { MemoryVectorStore } from './memory-vector-store.js';\nexport {\n PgVectorStore,\n type PgClient,\n type PgVectorStoreOptions,\n} from './pg-vector-store.js';\nexport {\n RedisVectorStore,\n type RedisSearchClient,\n type RedisVectorStoreOptions,\n} from './redis-vector-store.js';\nexport { EmbeddingRetriever } from './embedding-retriever.js';\nexport { KeywordRetriever, type KeywordRetrieverOptions } from './keyword-retriever.js';\nexport { HybridRetriever, type HybridRetrieverOptions } from './hybrid-retriever.js';\nexport { RerankingRetriever, type RerankingRetrieverOptions } from './reranking-retriever.js';\nexport { FilteredRetriever } from './filtered-retriever.js';\nexport {\n chunkDocuments,\n ingestChunks,\n ingestDocuments,\n type ChunkRecord,\n type IngestChunksOptions,\n type IngestDocument,\n type IngestOptions,\n} from './ingest.js';\nexport {\n createRetrievalTool,\n type RetrievalTool,\n type RetrievalToolOptions,\n} from './retrieval-tool.js';\n","export interface ChunkOptions {\n /** Target max characters per chunk. Default 800. */\n chunkSize?: number;\n /** Characters of overlap carried from the end of one chunk into the next. Default 100. */\n overlap?: number;\n}\n\n/**\n * Splits text into overlapping chunks for embedding. Greedy up to `chunkSize`, but prefers to break\n * on a paragraph → sentence → word boundary in the back half of the window (so a chunk rarely cuts\n * mid-sentence), then carries `overlap` characters into the next chunk to preserve context across\n * the seam. Pure and deterministic.\n */\nexport function chunkText(text: string, options: ChunkOptions = {}): string[] {\n const chunkSize = options.chunkSize ?? 800;\n const overlap = options.overlap ?? 100;\n const normalized = text.trim();\n if (normalized.length === 0) {\n return [];\n }\n if (normalized.length <= chunkSize) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n let start = 0;\n while (start < normalized.length) {\n const end = Math.min(start + chunkSize, normalized.length);\n let breakAt = end;\n if (end < normalized.length) {\n const window = normalized.slice(start, end);\n const boundary = Math.max(\n window.lastIndexOf('\\n\\n'),\n window.lastIndexOf('. '),\n window.lastIndexOf(' '),\n );\n // Only honour a boundary in the back half, else a tiny chunk cascades.\n if (boundary > chunkSize / 2) {\n breakAt = start + boundary + 1;\n }\n }\n const chunk = normalized.slice(start, breakAt).trim();\n if (chunk.length > 0) {\n chunks.push(chunk);\n }\n if (breakAt >= normalized.length) {\n break;\n }\n // Step forward with overlap, but always make progress (guards against a stuck boundary).\n start = Math.max(breakAt - overlap, start + 1);\n }\n return chunks;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\n\n/** A stored, embedded chunk. `embedding` length must match the store's configured dimensions. */\nexport interface VectorRecord {\n id: string;\n text: string;\n embedding: number[];\n /** Citation-facing origin (document title, URL, row id). */\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface VectorSearchOptions {\n topK: number;\n /** Exact-match metadata filter (all keys must equal). Passed through from `RetrieveOptions.filter`. */\n filter?: Record<string, unknown>;\n}\n\n/**\n * The write + search side of RAG storage. `@dudousxd/nestjs-agent-rag` ships `MemoryVectorStore`\n * (in-JS cosine, tests + small scale) and `PgVectorStore` (pgvector); any impl works. Pair one with\n * an `EmbeddingProvider` via {@link import('./embedding-retriever.js').EmbeddingRetriever} to get a\n * core `Retriever`.\n */\nexport interface VectorStore {\n upsert(records: VectorRecord[]): Promise<void>;\n search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;\n /**\n * Delete every chunk belonging to a source document — all records whose id is `${documentId}`\n * or `${documentId}#<n>` (the id scheme {@link import('./ingest.js').chunkDocuments} produces).\n * Use it to drop a document from the index, and before re-ingesting one: `upsert` overwrites\n * matching ids but can't remove chunks a shorter new version no longer produces, so a re-ingest\n * without a preceding `remove` leaves the old tail orphaned.\n */\n remove(documentId: string): Promise<void>;\n /**\n * List the distinct source documents currently indexed (chunk ids collapsed back to their document\n * by stripping the trailing `#<n>`), each with a representative chunk's `metadata`, optionally\n * narrowed by a metadata `filter`. The enumeration seam for reconciliation: diff this against your\n * source of truth to find documents to ingest (missing), re-ingest (a stored fingerprint like\n * `size` changed), or {@link VectorStore.remove remove} (orphaned).\n */\n listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;\n}\n\n/** A distinct source document as seen by the index — its id plus a representative chunk's metadata. */\nexport interface IndexedDocument {\n id: string;\n metadata?: Record<string, unknown>;\n}\n\n/** Collapse a chunk id (`${documentId}#<n>`) back to its source document id. */\nexport function documentIdOf(chunkId: string): string {\n return chunkId.replace(/#\\d+$/, '');\n}\n","/** Exact-match metadata filter (every key in `filter` must equal the record's). Shared by the\n * in-memory {@link import('./memory-vector-store.js').MemoryVectorStore} and\n * {@link import('./keyword-retriever.js').KeywordRetriever}. */\nexport function matchesFilter(\n metadata: Record<string, unknown> | undefined,\n filter: Record<string, unknown>,\n): boolean {\n if (metadata === undefined) {\n return Object.keys(filter).length === 0;\n }\n return Object.entries(filter).every(([key, value]) => metadata[key] === value);\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport { matchesFilter } from './filter.js';\nimport {\n type IndexedDocument,\n type VectorRecord,\n type VectorSearchOptions,\n type VectorStore,\n documentIdOf,\n} from './vector-store.js';\n\n/**\n * An in-process {@link VectorStore} — cosine similarity over a Map, no infra. The reference adapter\n * for tests and small/embedded corpora; for production scale use `PgVectorStore` (or your own).\n */\nexport class MemoryVectorStore implements VectorStore {\n private readonly records = new Map<string, VectorRecord>();\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n this.records.set(record.id, record);\n }\n }\n\n async remove(documentId: string): Promise<void> {\n for (const id of this.records.keys()) {\n if (documentIdOf(id) === documentId) {\n this.records.delete(id);\n }\n }\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const documents = new Map<string, IndexedDocument>();\n for (const record of this.records.values()) {\n if (filter !== undefined && !matchesFilter(record.metadata, filter)) {\n continue;\n }\n const id = documentIdOf(record.id);\n if (!documents.has(id)) {\n documents.set(id, {\n id,\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n }\n return [...documents.values()];\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const scored: Passage[] = [];\n for (const record of this.records.values()) {\n if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n scored.push({\n id: record.id,\n text: record.text,\n score: cosineSimilarity(embedding, record.embedding),\n ...(record.source !== undefined ? { source: record.source } : {}),\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n return scored.sort((a, b) => b.score - a.score).slice(0, options.topK);\n }\n}\n\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n const length = Math.min(a.length, b.length);\n for (let index = 0; index < length; index += 1) {\n const valueA = a[index] ?? 0;\n const valueB = b[index] ?? 0;\n dot += valueA * valueB;\n normA += valueA * valueA;\n normB += valueB * valueB;\n }\n const denominator = Math.sqrt(normA) * Math.sqrt(normB);\n return denominator === 0 ? 0 : dot / denominator;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport type {\n IndexedDocument,\n VectorRecord,\n VectorSearchOptions,\n VectorStore,\n} from './vector-store.js';\n\n/**\n * The minimal Postgres surface {@link PgVectorStore} needs — adapt your own `pg` / `postgres.js`\n * client to it, so this package pulls in NO driver (bring your own, like the store adapters take an\n * ORM handle and the Redis transport takes a client). `query` runs parameterized SQL (`$1,$2,…`) and\n * resolves the result rows.\n */\nexport interface PgClient {\n query<Row = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<Row[]>;\n}\n\nexport interface PgVectorStoreOptions {\n /** Table name. Default `agent_rag_chunks`. */\n table?: string;\n /** Embedding width — must match your model (e.g. 1536 for text-embedding-3-small). Default 1536. */\n dimensions?: number;\n}\n\n/**\n * A pgvector-backed {@link VectorStore} — the production reference adapter. Cosine distance via the\n * `<=>` operator over an HNSW index; metadata in a `jsonb` column filtered with `@>`. Call\n * {@link PgVectorStore.ensureSchema} once at boot to create the extension, table, and index.\n */\nexport class PgVectorStore implements VectorStore {\n private readonly table: string;\n private readonly dimensions: number;\n\n constructor(\n private readonly client: PgClient,\n options: PgVectorStoreOptions = {},\n ) {\n this.table = options.table ?? 'agent_rag_chunks';\n this.dimensions = options.dimensions ?? 1536;\n }\n\n /** Idempotent DDL: the `vector` extension, the chunks table, and the cosine HNSW index. */\n async ensureSchema(): Promise<void> {\n await this.client.query('CREATE EXTENSION IF NOT EXISTS vector');\n await this.client.query(\n `CREATE TABLE IF NOT EXISTS ${this.table} (\n id TEXT PRIMARY KEY,\n text TEXT NOT NULL,\n source TEXT,\n metadata JSONB,\n embedding vector(${this.dimensions}) NOT NULL\n )`,\n );\n await this.client.query(\n `CREATE INDEX IF NOT EXISTS ${this.table}_embedding_idx\n ON ${this.table} USING hnsw (embedding vector_cosine_ops)`,\n );\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n await this.client.query(\n `INSERT INTO ${this.table} (id, text, source, metadata, embedding)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (id) DO UPDATE SET\n text = EXCLUDED.text,\n source = EXCLUDED.source,\n metadata = EXCLUDED.metadata,\n embedding = EXCLUDED.embedding`,\n [\n record.id,\n record.text,\n record.source ?? null,\n record.metadata !== undefined ? JSON.stringify(record.metadata) : null,\n toVectorLiteral(record.embedding),\n ],\n );\n }\n }\n\n async remove(documentId: string): Promise<void> {\n await this.client.query(`DELETE FROM ${this.table} WHERE ${DOCUMENT_ID_FROM_CHUNK} = $1`, [\n documentId,\n ]);\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const hasFilter = filter !== undefined && Object.keys(filter).length > 0;\n // DISTINCT ON collapses chunks to one row per document; all chunks share the doc's metadata.\n const rows = await this.client.query<{\n doc_id: string;\n metadata: Record<string, unknown> | null;\n }>(\n `SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata\n FROM ${this.table}\n ${hasFilter ? 'WHERE metadata @> $1::jsonb' : ''}\n ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`,\n hasFilter ? [JSON.stringify(filter)] : [],\n );\n return rows.map((row) => ({\n id: row.doc_id,\n ...(row.metadata !== null ? { metadata: row.metadata } : {}),\n }));\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const vector = toVectorLiteral(embedding);\n const hasFilter = options.filter !== undefined && Object.keys(options.filter).length > 0;\n const rows = await this.client.query<PgRow>(\n `SELECT id, text, source, metadata, 1 - (embedding <=> $1::vector) AS score\n FROM ${this.table}\n ${hasFilter ? 'WHERE metadata @> $3::jsonb' : ''}\n ORDER BY embedding <=> $1::vector\n LIMIT $2`,\n hasFilter ? [vector, options.topK, JSON.stringify(options.filter)] : [vector, options.topK],\n );\n return rows.map((row) => ({\n id: row.id,\n text: row.text,\n score: Number(row.score),\n ...(row.source !== null ? { source: row.source } : {}),\n ...(row.metadata !== null ? { metadata: row.metadata } : {}),\n }));\n }\n}\n\ninterface PgRow {\n id: string;\n text: string;\n source: string | null;\n metadata: Record<string, unknown> | null;\n score: number | string;\n}\n\n/**\n * SQL that collapses a chunk id (`${documentId}#<n>`) back to its source document id — the pgvector\n * mirror of {@link import('./vector-store.js').documentIdOf}. Shared by `remove` and\n * `listDocuments` so both key on the exact same definition of \"chunk belongs to document\".\n */\nconst DOCUMENT_ID_FROM_CHUNK = \"regexp_replace(id, '#[0-9]+$', '')\";\n\n/** pgvector accepts a `'[1,2,3]'` text literal cast to `vector`. */\nfunction toVectorLiteral(embedding: number[]): string {\n return `[${embedding.join(',')}]`;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport {\n type IndexedDocument,\n type VectorRecord,\n type VectorSearchOptions,\n type VectorStore,\n documentIdOf,\n} from './vector-store.js';\n\n/**\n * The minimal Redis surface {@link RedisVectorStore} needs: send a raw command and get the reply.\n * Both `node-redis` (`client.sendCommand(args)`) and `ioredis` (wrap `redis.call`) satisfy it — so\n * this package pulls in NO Redis driver (bring your own, like the other adapters). Requires the\n * RediSearch module (Redis Stack / Redis 8+).\n */\nexport interface RedisSearchClient {\n sendCommand(args: (string | Buffer)[]): Promise<unknown>;\n}\n\nexport interface RedisVectorStoreOptions {\n /** RediSearch index name. Default `agent_rag_idx`. */\n index?: string;\n /** Key prefix for the per-chunk hashes. Default `agent_rag:`. */\n prefix?: string;\n /** Embedding width — must match your model (e.g. 1536). Default 1536. */\n dimensions?: number;\n /**\n * Metadata keys to index as filterable TAG fields. RediSearch needs filter fields declared up\n * front, so only keys listed here can be passed in `search`'s `filter`. Default none.\n */\n filterableFields?: string[];\n}\n\n/**\n * A RediSearch-backed {@link VectorStore} — HNSW + cosine over Redis, the ecosystem-native option\n * for anyone already running Redis (see `-transport-redis`). Chunks are hashes under `prefix`, the\n * embedding a FLOAT32 vector field; `search` is a KNN query. Call {@link RedisVectorStore.ensureSchema}\n * once at boot to create the index.\n */\nexport class RedisVectorStore implements VectorStore {\n private readonly index: string;\n private readonly prefix: string;\n private readonly dimensions: number;\n private readonly filterableFields: string[];\n\n constructor(\n private readonly client: RedisSearchClient,\n options: RedisVectorStoreOptions = {},\n ) {\n this.index = options.index ?? 'agent_rag_idx';\n this.prefix = options.prefix ?? 'agent_rag:';\n this.dimensions = options.dimensions ?? 1536;\n this.filterableFields = options.filterableFields ?? [];\n }\n\n /** Idempotent: create the HNSW/cosine index if `FT.INFO` says it doesn't exist yet. */\n async ensureSchema(): Promise<void> {\n try {\n await this.client.sendCommand(['FT.INFO', this.index]);\n return;\n } catch {\n // index missing → create it below\n }\n const schema: string[] = ['text', 'TEXT', 'source', 'TAG', 'metadata_json', 'TEXT', 'NOINDEX'];\n for (const field of this.filterableFields) {\n schema.push(`meta_${field}`, 'TAG');\n }\n schema.push(\n 'embedding',\n 'VECTOR',\n 'HNSW',\n '6',\n 'TYPE',\n 'FLOAT32',\n 'DIM',\n String(this.dimensions),\n 'DISTANCE_METRIC',\n 'COSINE',\n );\n await this.client.sendCommand([\n 'FT.CREATE',\n this.index,\n 'ON',\n 'HASH',\n 'PREFIX',\n '1',\n this.prefix,\n 'SCHEMA',\n ...schema,\n ]);\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n const args: (string | Buffer)[] = [\n 'HSET',\n `${this.prefix}${record.id}`,\n 'text',\n record.text,\n 'embedding',\n encodeVector(record.embedding),\n ];\n if (record.source !== undefined) {\n args.push('source', record.source);\n }\n if (record.metadata !== undefined) {\n args.push('metadata_json', JSON.stringify(record.metadata));\n for (const field of this.filterableFields) {\n const value = record.metadata[field];\n if (value !== undefined) {\n args.push(`meta_${field}`, String(value));\n }\n }\n }\n await this.client.sendCommand(args);\n }\n }\n\n async remove(documentId: string): Promise<void> {\n const keys = new Set<string>([`${this.prefix}${documentId}`]);\n const pattern = `${this.prefix}${escapeGlob(documentId)}#*`;\n let cursor = '0';\n do {\n const reply = await this.client.sendCommand([\n 'SCAN',\n cursor,\n 'MATCH',\n pattern,\n 'COUNT',\n '256',\n ]);\n const page = parseScanReply(reply);\n cursor = page.cursor;\n for (const key of page.keys) {\n keys.add(key);\n }\n } while (cursor !== '0');\n if (keys.size > 0) {\n await this.client.sendCommand(['DEL', ...keys]);\n }\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const query = buildFilter(filter);\n const documents = new Map<string, IndexedDocument>();\n const batchSize = 1000;\n let offset = 0;\n for (;;) {\n // RETURN metadata_json (not NOCONTENT) so we can carry a representative chunk's metadata;\n // parseSearchReply already yields prefix-stripped ids + parsed metadata for both RESP2/RESP3.\n const reply = await this.client.sendCommand([\n 'FT.SEARCH',\n this.index,\n query,\n 'RETURN',\n '1',\n 'metadata_json',\n 'LIMIT',\n String(offset),\n String(batchSize),\n 'DIALECT',\n '2',\n ]);\n const passages = parseSearchReply(reply, this.prefix);\n for (const passage of passages) {\n const id = documentIdOf(passage.id);\n if (!documents.has(id)) {\n documents.set(id, {\n id,\n ...(passage.metadata !== undefined ? { metadata: passage.metadata } : {}),\n });\n }\n }\n if (passages.length < batchSize) {\n break;\n }\n offset += batchSize;\n }\n return [...documents.values()];\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const query = `${buildFilter(options.filter)}=>[KNN ${options.topK} @embedding $BLOB AS vector_score]`;\n const reply = await this.client.sendCommand([\n 'FT.SEARCH',\n this.index,\n query,\n 'PARAMS',\n '2',\n 'BLOB',\n encodeVector(embedding),\n 'RETURN',\n '4',\n 'text',\n 'source',\n 'metadata_json',\n 'vector_score',\n 'SORTBY',\n 'vector_score',\n 'ASC',\n 'DIALECT',\n '2',\n 'LIMIT',\n '0',\n String(options.topK),\n ]);\n return parseSearchReply(reply, this.prefix);\n }\n}\n\n/** RediSearch expects a little-endian FLOAT32 buffer for the query/stored vector. */\nfunction encodeVector(embedding: number[]): Buffer {\n return Buffer.from(new Float32Array(embedding).buffer);\n}\n\n/** `*` for no filter, else an AND of TAG clauses like `(@meta_owner:{u1} @meta_tenant:{t1})`. */\nfunction buildFilter(filter?: Record<string, unknown>): string {\n if (filter === undefined || Object.keys(filter).length === 0) {\n return '*';\n }\n const clauses = Object.entries(filter).map(\n ([key, value]) => `@meta_${key}:{${escapeTag(String(value))}}`,\n );\n return `(${clauses.join(' ')})`;\n}\n\n/** Escape RediSearch TAG punctuation so an id/tenant value matches literally. */\nfunction escapeTag(value: string): string {\n return value.replace(/[^a-zA-Z0-9_]/g, (char) => `\\\\${char}`);\n}\n\n/** Escape `SCAN MATCH` glob metacharacters so a document id matches its chunk keys literally. */\nfunction escapeGlob(value: string): string {\n return value.replace(/[*?[\\]\\\\]/g, (char) => `\\\\${char}`);\n}\n\n/**\n * Parse a `SCAN` reply into `{ cursor, keys }`, tolerating both wire shapes: the RESP2 array\n * `[cursor, [key, …]]` and node-redis's RESP3 object `{ cursor, keys }`.\n */\nfunction parseScanReply(reply: unknown): { cursor: string; keys: string[] } {\n if (Array.isArray(reply) && reply.length >= 2) {\n const rawKeys = reply[1];\n return {\n cursor: toStr(reply[0]),\n keys: Array.isArray(rawKeys) ? rawKeys.map(toStr) : [],\n };\n }\n if (typeof reply === 'object' && reply !== null && 'cursor' in reply && 'keys' in reply) {\n const { cursor, keys } = reply as { cursor: unknown; keys: unknown };\n return {\n cursor: toStr(cursor),\n keys: Array.isArray(keys) ? keys.map(toStr) : [],\n };\n }\n return { cursor: '0', keys: [] };\n}\n\nfunction toStr(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);\n}\n\n/**\n * Parse an `FT.SEARCH` reply into passages, tolerating both wire shapes: the RESP2 array\n * `[total, key, [f, v, …], …]` (ioredis / node-redis RESP2) and the RESP3 object\n * `{ results: [{ id, extra_attributes: { … } }] }` (node-redis's default). Distance → similarity\n * via `1 - distance` (COSINE), and the `prefix` is stripped back off the key to recover the chunk id.\n */\nfunction parseSearchReply(reply: unknown, prefix: string): Passage[] {\n if (Array.isArray(reply)) {\n const passages: Passage[] = [];\n for (let index = 1; index + 1 < reply.length; index += 2) {\n const rawFields = reply[index + 1];\n if (Array.isArray(rawFields)) {\n const attrs: Record<string, string> = {};\n for (let field = 0; field + 1 < rawFields.length; field += 2) {\n attrs[toStr(rawFields[field])] = toStr(rawFields[field + 1]);\n }\n passages.push(toPassage(toStr(reply[index]), attrs, prefix));\n }\n }\n return passages;\n }\n if (typeof reply === 'object' && reply !== null && 'results' in reply) {\n const results = (reply as { results: unknown }).results;\n if (Array.isArray(results)) {\n return results.map((result) => toPassage(readId(result), readAttributes(result), prefix));\n }\n }\n return [];\n}\n\nfunction readId(result: unknown): string {\n if (typeof result === 'object' && result !== null && 'id' in result) {\n return toStr((result as { id: unknown }).id);\n }\n return '';\n}\n\nfunction readAttributes(result: unknown): Record<string, string> {\n const attrs: Record<string, string> = {};\n if (typeof result === 'object' && result !== null && 'extra_attributes' in result) {\n const raw = (result as { extra_attributes: unknown }).extra_attributes;\n if (typeof raw === 'object' && raw !== null) {\n for (const [key, value] of Object.entries(raw)) {\n attrs[key] = toStr(value);\n }\n }\n }\n return attrs;\n}\n\nfunction toPassage(rawId: string, attrs: Record<string, string>, prefix: string): Passage {\n const distance = Number(attrs.vector_score ?? '1');\n const source = attrs.source;\n return {\n id: rawId.startsWith(prefix) ? rawId.slice(prefix.length) : rawId,\n text: attrs.text ?? '',\n score: 1 - distance,\n ...(source !== undefined ? { source } : {}),\n ...parseMetadata(attrs.metadata_json),\n };\n}\n\nfunction parseMetadata(raw: string | undefined): { metadata?: Record<string, unknown> } {\n if (raw === undefined) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed === 'object' && parsed !== null) {\n return { metadata: parsed as Record<string, unknown> };\n }\n } catch {\n // not JSON — no metadata\n }\n return {};\n}\n","import type {\n EmbeddingProvider,\n Passage,\n RetrieveOptions,\n Retriever,\n} from '@dudousxd/nestjs-agent-core';\nimport type { VectorStore } from './vector-store.js';\n\n/**\n * Bridges an {@link EmbeddingProvider} + {@link VectorStore} into the core {@link Retriever} SPI:\n * embed the query, then vector-search. This is what you wire as the agent's retriever (as a tool\n * via `createRetrievalTool`, or into `AgentModule.forRoot({ retrieval })` for inject mode).\n */\nexport class EmbeddingRetriever implements Retriever {\n constructor(\n private readonly embedder: EmbeddingProvider,\n private readonly store: VectorStore,\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const [embedding] = await this.embedder.embed([query]);\n if (embedding === undefined) {\n return [];\n }\n return this.store.search(embedding, {\n topK: options.topK ?? 5,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n });\n }\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\nimport { matchesFilter } from './filter.js';\nimport type { ChunkRecord } from './ingest.js';\n\nexport interface KeywordRetrieverOptions {\n /** BM25 term-frequency saturation. Default 1.5. */\n k1?: number;\n /** BM25 length normalization, 0 (off) … 1 (full). Default 0.75. */\n b?: number;\n}\n\ninterface IndexedDoc {\n record: ChunkRecord;\n /** term → frequency within this doc. */\n frequencies: Map<string, number>;\n length: number;\n}\n\n/**\n * An in-memory BM25 lexical retriever — the keyword half of hybrid search. Feed it the SAME\n * {@link ChunkRecord}s you upsert into the vector store (from `chunkDocuments`) so their chunk ids\n * line up and `HybridRetriever` can fuse the two rankings. Full BM25: idf, term-frequency saturation\n * (`k1`), and document-length normalization (`b`).\n */\nexport class KeywordRetriever implements Retriever {\n private readonly docs = new Map<string, IndexedDoc>();\n private readonly documentFrequency = new Map<string, number>();\n private totalLength = 0;\n private readonly k1: number;\n private readonly b: number;\n\n constructor(options: KeywordRetrieverOptions = {}) {\n this.k1 = options.k1 ?? 1.5;\n this.b = options.b ?? 0.75;\n }\n\n /** Index (or re-index) records. Re-adding an existing id replaces its posting and updates stats. */\n add(records: ChunkRecord[]): void {\n for (const record of records) {\n this.remove(record.id);\n const tokens = tokenize(record.text);\n const frequencies = new Map<string, number>();\n for (const token of tokens) {\n frequencies.set(token, (frequencies.get(token) ?? 0) + 1);\n }\n for (const term of frequencies.keys()) {\n this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);\n }\n this.docs.set(record.id, { record, frequencies, length: tokens.length });\n this.totalLength += tokens.length;\n }\n }\n\n private remove(id: string): void {\n const existing = this.docs.get(id);\n if (existing === undefined) {\n return;\n }\n for (const term of existing.frequencies.keys()) {\n const next = (this.documentFrequency.get(term) ?? 1) - 1;\n if (next <= 0) {\n this.documentFrequency.delete(term);\n } else {\n this.documentFrequency.set(term, next);\n }\n }\n this.totalLength -= existing.length;\n this.docs.delete(id);\n }\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const total = this.docs.size;\n if (total === 0) {\n return [];\n }\n const averageLength = this.totalLength / total;\n const queryTerms = new Set(tokenize(query));\n const scored: Passage[] = [];\n for (const { record, frequencies, length } of this.docs.values()) {\n if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n let score = 0;\n for (const term of queryTerms) {\n const termFrequency = frequencies.get(term);\n if (termFrequency === undefined) {\n continue;\n }\n const df = this.documentFrequency.get(term) ?? 0;\n const idf = Math.log(1 + (total - df + 0.5) / (df + 0.5));\n const denominator =\n termFrequency + this.k1 * (1 - this.b + (this.b * length) / averageLength);\n score += idf * ((termFrequency * (this.k1 + 1)) / denominator);\n }\n if (score > 0) {\n scored.push({\n id: record.id,\n text: record.text,\n score,\n ...(record.source !== undefined ? { source: record.source } : {}),\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n }\n return scored.sort((a, b) => b.score - a.score).slice(0, options.topK ?? 5);\n }\n}\n\nfunction tokenize(text: string): string[] {\n return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\nexport interface HybridRetrieverOptions {\n /** RRF constant — larger flattens the rank weighting. Default 60 (the value from the original paper). */\n k?: number;\n /** Candidates pulled from each retriever before fusion. Default 20. */\n fetchTopK?: number;\n /** Per-retriever multipliers on each RRF contribution (same order as `retrievers`). Default all 1. */\n weights?: number[];\n}\n\n/**\n * Fuses several retrievers with Reciprocal Rank Fusion — the robust way to combine, say, dense\n * vector search with lexical BM25. RRF scores each passage by `Σ weight / (k + rank)` across the\n * lists it appears in, so it needs no score normalization between incompatible scales (cosine vs\n * BM25). Deduplicates by passage id, so aligned chunk ids from the two retrievers reinforce.\n */\nexport class HybridRetriever implements Retriever {\n constructor(\n private readonly retrievers: Retriever[],\n private readonly options: HybridRetrieverOptions = {},\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const k = this.options.k ?? 60;\n const fetchTopK = this.options.fetchTopK ?? 20;\n const weights = this.options.weights;\n const lists = await Promise.all(\n this.retrievers.map((retriever) =>\n retriever.retrieve(query, {\n topK: fetchTopK,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n }),\n ),\n );\n\n const fused = new Map<string, { passage: Passage; score: number }>();\n lists.forEach((list, listIndex) => {\n const weight = weights?.[listIndex] ?? 1;\n list.forEach((passage, rank) => {\n const contribution = weight / (k + rank + 1);\n const existing = fused.get(passage.id);\n if (existing !== undefined) {\n existing.score += contribution;\n } else {\n fused.set(passage.id, { passage, score: contribution });\n }\n });\n });\n\n return [...fused.values()]\n .sort((a, b) => b.score - a.score)\n .slice(0, options.topK ?? 5)\n .map((entry) => ({ ...entry.passage, score: entry.score }));\n }\n}\n","import type { Passage, Reranker, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\nexport interface RerankingRetrieverOptions {\n /** How many candidates to pull from the base retriever before reranking. Default 20. */\n fetchTopK?: number;\n}\n\n/**\n * Two-stage retrieval: over-fetch cheap candidates from a base `Retriever`, then reorder them with a\n * stronger {@link Reranker} and keep the top few. The standard precision boost — a fast first stage\n * casts a wide net, a slow accurate second stage sharpens it. Composes over ANY retriever (vector,\n * hybrid, keyword), and is itself a `Retriever`.\n */\nexport class RerankingRetriever implements Retriever {\n constructor(\n private readonly base: Retriever,\n private readonly reranker: Reranker,\n private readonly options: RerankingRetrieverOptions = {},\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const candidates = await this.base.retrieve(query, {\n topK: this.options.fetchTopK ?? 20,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n });\n return this.reranker.rerank(query, candidates, { topK: options.topK ?? 5 });\n }\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\n/**\n * Wraps a `Retriever` so every `retrieve` call ANDs a fixed metadata `filter` on top of whatever the\n * caller passes — the owner/tenant-scoping primitive. `new FilteredRetriever(base, { ownerId })` only\n * ever returns that owner's passages, so a shared vector store serves per-user / per-tenant RAG\n * without the agent (or a crafted query) reaching across the boundary.\n *\n * The fixed filter takes precedence on key conflicts: a caller can narrow further but cannot widen\n * past the scope. Composes over ANY retriever (vector, hybrid, reranked) and is itself a `Retriever`.\n */\nexport class FilteredRetriever implements Retriever {\n constructor(\n private readonly base: Retriever,\n private readonly filter: Record<string, unknown>,\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n return this.base.retrieve(query, {\n ...options,\n filter: { ...options.filter, ...this.filter },\n });\n }\n}\n","import type { EmbeddingProvider } from '@dudousxd/nestjs-agent-core';\nimport { type ChunkOptions, chunkText } from './chunk.js';\nimport type { VectorRecord, VectorStore } from './vector-store.js';\n\n/** A source document to ingest. `id` scopes the chunk ids (`${id}#${n}`); `source` is the citation. */\nexport interface IngestDocument {\n id: string;\n text: string;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\n/** A chunked, not-yet-embedded record. Feed the same array to a vector store AND a keyword index so\n * both retrievers key on identical chunk ids (which is what lets hybrid fusion line them up). */\nexport interface ChunkRecord {\n id: string;\n text: string;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface IngestChunksOptions {\n embedder: EmbeddingProvider;\n store: VectorStore;\n}\n\nexport interface IngestOptions extends ChunkOptions, IngestChunksOptions {}\n\n/**\n * Split documents into chunk records. Chunk ids are `${doc.id}#${index}`, so re-chunking the same\n * document produces the same ids (upsert overwrites in place rather than duplicating).\n */\nexport function chunkDocuments(\n documents: IngestDocument[],\n options: ChunkOptions = {},\n): ChunkRecord[] {\n const chunks: ChunkRecord[] = [];\n for (const document of documents) {\n chunkText(document.text, options).forEach((text, index) => {\n chunks.push({\n id: `${document.id}#${index}`,\n text,\n ...(document.source !== undefined ? { source: document.source } : {}),\n ...(document.metadata !== undefined ? { metadata: document.metadata } : {}),\n });\n });\n }\n return chunks;\n}\n\n/** Embed pre-chunked records (one batched `embed`) and upsert them. Returns the record count. */\nexport async function ingestChunks(\n chunks: ChunkRecord[],\n options: IngestChunksOptions,\n): Promise<number> {\n if (chunks.length === 0) {\n return 0;\n }\n const embeddings = await options.embedder.embed(chunks.map((chunk) => chunk.text));\n const records: VectorRecord[] = chunks.map((chunk, index) => ({\n ...chunk,\n embedding: embeddings[index] ?? [],\n }));\n await options.store.upsert(records);\n return records.length;\n}\n\n/** Chunk → embed → upsert in one call. For hybrid search, use {@link chunkDocuments} +\n * {@link ingestChunks} and feed the same chunks to a {@link import('./keyword-retriever.js').KeywordRetriever}. */\nexport async function ingestDocuments(\n documents: IngestDocument[],\n options: IngestOptions,\n): Promise<number> {\n return ingestChunks(chunkDocuments(documents, options), options);\n}\n","import type { Passage, Retriever, ToolHandler, ToolSpec } from '@dudousxd/nestjs-agent-core';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\nexport interface RetrievalToolOptions {\n /** Tool name the model sees. Default `search_knowledge`. */\n name?: string;\n description?: string;\n /** Passages per search. Default lets the retriever decide (typically 5). */\n topK?: number;\n}\n\n/** A functional tool (`{ spec, handler }`) — pass it to nestjs `provideAgentTool`. */\nexport interface RetrievalTool {\n spec: ToolSpec;\n handler: ToolHandler;\n}\n\nfunction isQueryInput(value: unknown): value is { query: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'query' in value &&\n typeof (value as Record<'query', unknown>).query === 'string'\n );\n}\n\n/** A hand-rolled Standard Schema for `{ query: string }` — keeps this package validation-lib-free. */\nconst querySchema: StandardSchemaV1<{ query: string }> = {\n '~standard': {\n version: 1,\n vendor: 'nestjs-agent-rag',\n validate(value: unknown) {\n return isQueryInput(value)\n ? { value: { query: value.query } }\n : { issues: [{ message: 'retrieval tool expects { query: string }' }] };\n },\n },\n};\n\n/**\n * Builds the agentic-retrieval tool: a `read`-kind `search_knowledge(query)` the model calls when it\n * decides it needs context. Its output `{ passages }` persists as the tool call's result, so sources\n * flow to the frontend and telescope through the normal tool-call machinery (the citation surface).\n *\n * ```ts\n * providers: [provideAgentTool(createRetrievalTool(retriever))]\n * ```\n */\nexport function createRetrievalTool(\n retriever: Retriever,\n options: RetrievalToolOptions = {},\n): RetrievalTool {\n const topK = options.topK;\n return {\n spec: {\n name: options.name ?? 'search_knowledge',\n kind: 'read',\n description:\n options.description ??\n 'Search the knowledge base for passages relevant to a query. Returns passages with their source for citation.',\n inputSchema: querySchema,\n },\n handler: {\n async execute(input: unknown): Promise<{ passages: Passage[] }> {\n if (!isQueryInput(input)) {\n throw new Error('retrieval tool expects { query: string }');\n }\n const passages = await retriever.retrieve(input.query, topK !== undefined ? { topK } : {});\n return { passages };\n },\n },\n };\n}\n"],"mappings":"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","names":["chunkText","text","options","chunkSize","overlap","normalized","trim","length","chunks","start","end","Math","min","breakAt","window","slice","boundary","max","lastIndexOf","chunk","push","documentIdOf","chunkId","replace","matchesFilter","metadata","filter","undefined","Object","keys","length","entries","every","key","value","MemoryVectorStore","records","Map","upsert","record","set","id","remove","documentId","keys","documentIdOf","delete","listDocuments","filter","documents","values","undefined","matchesFilter","metadata","has","search","embedding","options","scored","push","text","score","cosineSimilarity","source","sort","a","b","slice","topK","dot","normA","normB","length","Math","min","index","valueA","valueB","denominator","sqrt","PgVectorStore","table","dimensions","client","options","ensureSchema","query","upsert","records","record","id","text","source","metadata","undefined","JSON","stringify","toVectorLiteral","embedding","remove","documentId","DOCUMENT_ID_FROM_CHUNK","listDocuments","filter","hasFilter","Object","keys","length","rows","map","row","doc_id","search","vector","topK","score","Number","join","RedisVectorStore","index","prefix","dimensions","filterableFields","client","options","ensureSchema","sendCommand","schema","field","push","String","upsert","records","record","args","id","text","encodeVector","embedding","source","undefined","metadata","JSON","stringify","value","remove","documentId","keys","Set","pattern","escapeGlob","cursor","reply","page","parseScanReply","key","add","size","listDocuments","filter","query","buildFilter","documents","Map","batchSize","offset","passages","parseSearchReply","passage","documentIdOf","has","set","length","values","search","topK","Buffer","from","Float32Array","buffer","Object","clauses","entries","map","escapeTag","join","replace","char","Array","isArray","rawKeys","toStr","isBuffer","toString","rawFields","attrs","toPassage","results","result","readId","readAttributes","raw","extra_attributes","rawId","distance","Number","vector_score","startsWith","slice","score","parseMetadata","metadata_json","parsed","parse","EmbeddingRetriever","embedder","store","retrieve","query","options","embedding","embed","undefined","search","topK","filter","KeywordRetriever","docs","Map","documentFrequency","totalLength","k1","b","options","add","records","record","remove","id","tokens","tokenize","text","frequencies","token","set","get","term","keys","length","existing","undefined","next","delete","retrieve","query","total","size","averageLength","queryTerms","Set","scored","values","filter","matchesFilter","metadata","score","termFrequency","df","idf","Math","log","denominator","push","source","sort","a","slice","topK","toLowerCase","match","HybridRetriever","retrievers","options","retrieve","query","k","fetchTopK","weights","lists","Promise","all","map","retriever","topK","filter","undefined","fused","Map","forEach","list","listIndex","weight","passage","rank","contribution","existing","get","id","score","set","values","sort","a","b","slice","entry","RerankingRetriever","base","reranker","options","retrieve","query","candidates","topK","fetchTopK","filter","undefined","rerank","FilteredRetriever","base","filter","retrieve","query","options","chunkDocuments","documents","options","chunks","document","chunkText","text","forEach","index","push","id","source","undefined","metadata","ingestChunks","length","embeddings","embedder","embed","map","chunk","records","embedding","store","upsert","ingestDocuments","isQueryInput","value","query","querySchema","version","vendor","validate","issues","message","createRetrievalTool","retriever","options","topK","spec","name","kind","description","inputSchema","handler","execute","input","Error","passages","retrieve","undefined"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/chunk.ts","../src/vector-store.ts","../src/filter.ts","../src/memory-vector-store.ts","../src/pg-vector-store.ts","../src/redis-vector-store.ts","../src/embedding-retriever.ts","../src/keyword-retriever.ts","../src/hybrid-retriever.ts","../src/reranking-retriever.ts","../src/filtered-retriever.ts","../src/ingest.ts","../src/retrieval-tool.ts"],"sourcesContent":["export { chunkText, type ChunkOptions } from './chunk.js';\nexport type {\n IndexedDocument,\n VectorRecord,\n VectorSearchOptions,\n VectorStore,\n} from './vector-store.js';\nexport { documentIdOf } from './vector-store.js';\nexport { MemoryVectorStore } from './memory-vector-store.js';\nexport {\n PgVectorStore,\n type PgClient,\n type PgVectorStoreOptions,\n} from './pg-vector-store.js';\nexport {\n RedisVectorStore,\n type RedisSearchClient,\n type RedisVectorStoreOptions,\n} from './redis-vector-store.js';\nexport { EmbeddingRetriever } from './embedding-retriever.js';\nexport { KeywordRetriever, type KeywordRetrieverOptions } from './keyword-retriever.js';\nexport { HybridRetriever, type HybridRetrieverOptions } from './hybrid-retriever.js';\nexport { RerankingRetriever, type RerankingRetrieverOptions } from './reranking-retriever.js';\nexport { FilteredRetriever } from './filtered-retriever.js';\nexport {\n chunkDocuments,\n ingestChunks,\n ingestDocuments,\n type ChunkRecord,\n type IngestChunksOptions,\n type IngestDocument,\n type IngestOptions,\n} from './ingest.js';\nexport {\n createRetrievalTool,\n type RetrievalTool,\n type RetrievalToolOptions,\n} from './retrieval-tool.js';\n","export interface ChunkOptions {\n /** Target max characters per chunk. Default 800. */\n chunkSize?: number;\n /** Characters of overlap carried from the end of one chunk into the next. Default 100. */\n overlap?: number;\n}\n\n/**\n * Splits text into overlapping chunks for embedding. Greedy up to `chunkSize`, but prefers to break\n * on a paragraph → sentence → word boundary in the back half of the window (so a chunk rarely cuts\n * mid-sentence), then carries `overlap` characters into the next chunk to preserve context across\n * the seam. Pure and deterministic.\n */\nexport function chunkText(text: string, options: ChunkOptions = {}): string[] {\n const chunkSize = options.chunkSize ?? 800;\n const overlap = options.overlap ?? 100;\n const normalized = text.trim();\n if (normalized.length === 0) {\n return [];\n }\n if (normalized.length <= chunkSize) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n let start = 0;\n while (start < normalized.length) {\n const end = Math.min(start + chunkSize, normalized.length);\n let breakAt = end;\n if (end < normalized.length) {\n const window = normalized.slice(start, end);\n const boundary = Math.max(\n window.lastIndexOf('\\n\\n'),\n window.lastIndexOf('. '),\n window.lastIndexOf(' '),\n );\n // Only honour a boundary in the back half, else a tiny chunk cascades.\n if (boundary > chunkSize / 2) {\n breakAt = start + boundary + 1;\n }\n }\n const chunk = normalized.slice(start, breakAt).trim();\n if (chunk.length > 0) {\n chunks.push(chunk);\n }\n if (breakAt >= normalized.length) {\n break;\n }\n // Step forward with overlap, but always make progress (guards against a stuck boundary).\n start = Math.max(breakAt - overlap, start + 1);\n }\n return chunks;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\n\n/** A stored, embedded chunk. `embedding` length must match the store's configured dimensions. */\nexport interface VectorRecord {\n id: string;\n text: string;\n embedding: number[];\n /** Citation-facing origin (document title, URL, row id). */\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface VectorSearchOptions {\n topK: number;\n /** Exact-match metadata filter (all keys must equal). Passed through from `RetrieveOptions.filter`. */\n filter?: Record<string, unknown>;\n}\n\n/**\n * The write + search side of RAG storage. `@dudousxd/nestjs-agent-rag` ships `MemoryVectorStore`\n * (in-JS cosine, tests + small scale) and `PgVectorStore` (pgvector); any impl works. Pair one with\n * an `EmbeddingProvider` via {@link import('./embedding-retriever.js').EmbeddingRetriever} to get a\n * core `Retriever`.\n */\nexport interface VectorStore {\n upsert(records: VectorRecord[]): Promise<void>;\n search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;\n /**\n * Delete every chunk belonging to a source document — all records whose id is `${documentId}`\n * or `${documentId}#<n>` (the id scheme {@link import('./ingest.js').chunkDocuments} produces).\n * Use it to drop a document from the index, and before re-ingesting one: `upsert` overwrites\n * matching ids but can't remove chunks a shorter new version no longer produces, so a re-ingest\n * without a preceding `remove` leaves the old tail orphaned.\n */\n remove(documentId: string): Promise<void>;\n /**\n * List the distinct source documents currently indexed (chunk ids collapsed back to their document\n * by stripping the trailing `#<n>`), each with a representative chunk's `metadata`, optionally\n * narrowed by a metadata `filter`. The enumeration seam for reconciliation: diff this against your\n * source of truth to find documents to ingest (missing), re-ingest (a stored fingerprint like\n * `size` changed), or {@link VectorStore.remove remove} (orphaned).\n */\n listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;\n}\n\n/** A distinct source document as seen by the index — its id plus a representative chunk's metadata. */\nexport interface IndexedDocument {\n id: string;\n metadata?: Record<string, unknown>;\n}\n\n/** Collapse a chunk id (`${documentId}#<n>`) back to its source document id. */\nexport function documentIdOf(chunkId: string): string {\n return chunkId.replace(/#\\d+$/, '');\n}\n","/** Metadata filter, shared by the in-memory {@link import('./memory-vector-store.js').MemoryVectorStore}\n * and {@link import('./keyword-retriever.js').KeywordRetriever}. Every key must match:\n *\n * - a **scalar** filter value keeps exact-match — the record's value must equal it;\n * - an **array** filter value means **match-any** (OR / set membership) — the record matches when its\n * value for that key is one of them, or (for a multi-valued record) shares at least one element.\n * An empty array matches nothing (no value is a member of the empty set) — the deny primitive.\n *\n * Array-valued filters are how a caller expresses capability-style access control over a shared store\n * (e.g. `{ audience: ['public', 'role:ADMIN', 'base:…'] }`) without the store knowing what a token means.\n */\nexport function matchesFilter(\n metadata: Record<string, unknown> | undefined,\n filter: Record<string, unknown>,\n): boolean {\n const entries = Object.entries(filter);\n if (metadata === undefined) {\n return entries.length === 0;\n }\n return entries.every(([key, expected]) => {\n if (Array.isArray(expected)) {\n const actual = metadata[key];\n return Array.isArray(actual)\n ? actual.some((value) => expected.includes(value))\n : expected.includes(actual);\n }\n return metadata[key] === expected;\n });\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport { matchesFilter } from './filter.js';\nimport {\n type IndexedDocument,\n type VectorRecord,\n type VectorSearchOptions,\n type VectorStore,\n documentIdOf,\n} from './vector-store.js';\n\n/**\n * An in-process {@link VectorStore} — cosine similarity over a Map, no infra. The reference adapter\n * for tests and small/embedded corpora; for production scale use `PgVectorStore` (or your own).\n */\nexport class MemoryVectorStore implements VectorStore {\n private readonly records = new Map<string, VectorRecord>();\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n this.records.set(record.id, record);\n }\n }\n\n async remove(documentId: string): Promise<void> {\n for (const id of this.records.keys()) {\n if (documentIdOf(id) === documentId) {\n this.records.delete(id);\n }\n }\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const documents = new Map<string, IndexedDocument>();\n for (const record of this.records.values()) {\n if (filter !== undefined && !matchesFilter(record.metadata, filter)) {\n continue;\n }\n const id = documentIdOf(record.id);\n if (!documents.has(id)) {\n documents.set(id, {\n id,\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n }\n return [...documents.values()];\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const scored: Passage[] = [];\n for (const record of this.records.values()) {\n if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n scored.push({\n id: record.id,\n text: record.text,\n score: cosineSimilarity(embedding, record.embedding),\n ...(record.source !== undefined ? { source: record.source } : {}),\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n return scored.sort((a, b) => b.score - a.score).slice(0, options.topK);\n }\n}\n\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n const length = Math.min(a.length, b.length);\n for (let index = 0; index < length; index += 1) {\n const valueA = a[index] ?? 0;\n const valueB = b[index] ?? 0;\n dot += valueA * valueB;\n normA += valueA * valueA;\n normB += valueB * valueB;\n }\n const denominator = Math.sqrt(normA) * Math.sqrt(normB);\n return denominator === 0 ? 0 : dot / denominator;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport type {\n IndexedDocument,\n VectorRecord,\n VectorSearchOptions,\n VectorStore,\n} from './vector-store.js';\n\n/**\n * The minimal Postgres surface {@link PgVectorStore} needs — adapt your own `pg` / `postgres.js`\n * client to it, so this package pulls in NO driver (bring your own, like the store adapters take an\n * ORM handle and the Redis transport takes a client). `query` runs parameterized SQL (`$1,$2,…`) and\n * resolves the result rows.\n */\nexport interface PgClient {\n query<Row = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<Row[]>;\n}\n\nexport interface PgVectorStoreOptions {\n /** Table name. Default `agent_rag_chunks`. */\n table?: string;\n /** Embedding width — must match your model (e.g. 1536 for text-embedding-3-small). Default 1536. */\n dimensions?: number;\n}\n\n/**\n * A pgvector-backed {@link VectorStore} — the production reference adapter. Cosine distance via the\n * `<=>` operator over an HNSW index; metadata in a `jsonb` column filtered with `@>`. Call\n * {@link PgVectorStore.ensureSchema} once at boot to create the extension, table, and index.\n */\nexport class PgVectorStore implements VectorStore {\n private readonly table: string;\n private readonly dimensions: number;\n\n constructor(\n private readonly client: PgClient,\n options: PgVectorStoreOptions = {},\n ) {\n this.table = options.table ?? 'agent_rag_chunks';\n this.dimensions = options.dimensions ?? 1536;\n }\n\n /** Idempotent DDL: the `vector` extension, the chunks table, and the cosine HNSW index. */\n async ensureSchema(): Promise<void> {\n await this.client.query('CREATE EXTENSION IF NOT EXISTS vector');\n await this.client.query(\n `CREATE TABLE IF NOT EXISTS ${this.table} (\n id TEXT PRIMARY KEY,\n text TEXT NOT NULL,\n source TEXT,\n metadata JSONB,\n embedding vector(${this.dimensions}) NOT NULL\n )`,\n );\n await this.client.query(\n `CREATE INDEX IF NOT EXISTS ${this.table}_embedding_idx\n ON ${this.table} USING hnsw (embedding vector_cosine_ops)`,\n );\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n await this.client.query(\n `INSERT INTO ${this.table} (id, text, source, metadata, embedding)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (id) DO UPDATE SET\n text = EXCLUDED.text,\n source = EXCLUDED.source,\n metadata = EXCLUDED.metadata,\n embedding = EXCLUDED.embedding`,\n [\n record.id,\n record.text,\n record.source ?? null,\n record.metadata !== undefined ? JSON.stringify(record.metadata) : null,\n toVectorLiteral(record.embedding),\n ],\n );\n }\n }\n\n async remove(documentId: string): Promise<void> {\n await this.client.query(`DELETE FROM ${this.table} WHERE ${DOCUMENT_ID_FROM_CHUNK} = $1`, [\n documentId,\n ]);\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const where = buildWhere(filter, 1);\n // DISTINCT ON collapses chunks to one row per document; all chunks share the doc's metadata.\n const rows = await this.client.query<{\n doc_id: string;\n metadata: Record<string, unknown> | null;\n }>(\n `SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata\n FROM ${this.table}\n ${where.sql}\n ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`,\n where.params,\n );\n return rows.map((row) => ({\n id: row.doc_id,\n ...(row.metadata !== null ? { metadata: row.metadata } : {}),\n }));\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const vector = toVectorLiteral(embedding);\n // $1 = query vector, $2 = topK; metadata-filter params start at $3.\n const where = buildWhere(options.filter, 3);\n const rows = await this.client.query<PgRow>(\n `SELECT id, text, source, metadata, 1 - (embedding <=> $1::vector) AS score\n FROM ${this.table}\n ${where.sql}\n ORDER BY embedding <=> $1::vector\n LIMIT $2`,\n [vector, options.topK, ...where.params],\n );\n return rows.map((row) => ({\n id: row.id,\n text: row.text,\n score: Number(row.score),\n ...(row.source !== null ? { source: row.source } : {}),\n ...(row.metadata !== null ? { metadata: row.metadata } : {}),\n }));\n }\n}\n\ninterface PgRow {\n id: string;\n text: string;\n source: string | null;\n metadata: Record<string, unknown> | null;\n score: number | string;\n}\n\n/**\n * Build the `WHERE` fragment for a metadata `filter`, with parameter placeholders numbered from\n * `startIndex`. Scalar values collapse into a single `@>` jsonb-containment check (exact-match, as\n * before). An **array** value is a **match-any** (OR / set membership) check via jsonb `?|`: the\n * record matches when its value for that key — scalar or array — shares an element with the filter\n * array. An empty array can never match (deny primitive). Keys are passed as parameters (`metadata->$k`)\n * so a caller-supplied metadata key can't inject SQL. Returns `{ sql: '', params: [] }` when there is\n * no filter, preserving the previous unfiltered query shape.\n */\nfunction buildWhere(\n filter: Record<string, unknown> | undefined,\n startIndex: number,\n): { sql: string; params: unknown[] } {\n if (filter === undefined || Object.keys(filter).length === 0) {\n return { sql: '', params: [] };\n }\n const clauses: string[] = [];\n const params: unknown[] = [];\n const scalar: Record<string, unknown> = {};\n let index = startIndex;\n for (const [key, value] of Object.entries(filter)) {\n if (Array.isArray(value)) {\n if (value.length === 0) {\n clauses.push('false');\n continue;\n }\n const keyParam = `$${index}`;\n params.push(key);\n index += 1;\n const arrParam = `$${index}`;\n params.push(value.map(String));\n index += 1;\n clauses.push(\n `(CASE WHEN jsonb_typeof(metadata->${keyParam}) = 'array' ` +\n `THEN metadata->${keyParam} ELSE jsonb_build_array(metadata->${keyParam}) END) ?| ${arrParam}::text[]`,\n );\n } else {\n scalar[key] = value;\n }\n }\n if (Object.keys(scalar).length > 0) {\n clauses.push(`metadata @> $${index}::jsonb`);\n params.push(JSON.stringify(scalar));\n index += 1;\n }\n return { sql: `WHERE ${clauses.join(' AND ')}`, params };\n}\n\n/**\n * SQL that collapses a chunk id (`${documentId}#<n>`) back to its source document id — the pgvector\n * mirror of {@link import('./vector-store.js').documentIdOf}. Shared by `remove` and\n * `listDocuments` so both key on the exact same definition of \"chunk belongs to document\".\n */\nconst DOCUMENT_ID_FROM_CHUNK = \"regexp_replace(id, '#[0-9]+$', '')\";\n\n/** pgvector accepts a `'[1,2,3]'` text literal cast to `vector`. */\nfunction toVectorLiteral(embedding: number[]): string {\n return `[${embedding.join(',')}]`;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport {\n type IndexedDocument,\n type VectorRecord,\n type VectorSearchOptions,\n type VectorStore,\n documentIdOf,\n} from './vector-store.js';\n\n/**\n * The minimal Redis surface {@link RedisVectorStore} needs: send a raw command and get the reply.\n * Both `node-redis` (`client.sendCommand(args)`) and `ioredis` (wrap `redis.call`) satisfy it — so\n * this package pulls in NO Redis driver (bring your own, like the other adapters). Requires the\n * RediSearch module (Redis Stack / Redis 8+).\n */\nexport interface RedisSearchClient {\n sendCommand(args: (string | Buffer)[]): Promise<unknown>;\n}\n\nexport interface RedisVectorStoreOptions {\n /** RediSearch index name. Default `agent_rag_idx`. */\n index?: string;\n /** Key prefix for the per-chunk hashes. Default `agent_rag:`. */\n prefix?: string;\n /** Embedding width — must match your model (e.g. 1536). Default 1536. */\n dimensions?: number;\n /**\n * Metadata keys to index as filterable TAG fields. RediSearch needs filter fields declared up\n * front, so only keys listed here can be passed in `search`'s `filter`. Default none.\n */\n filterableFields?: string[];\n}\n\n/**\n * A RediSearch-backed {@link VectorStore} — HNSW + cosine over Redis, the ecosystem-native option\n * for anyone already running Redis (see `-transport-redis`). Chunks are hashes under `prefix`, the\n * embedding a FLOAT32 vector field; `search` is a KNN query. Call {@link RedisVectorStore.ensureSchema}\n * once at boot to create the index.\n */\nexport class RedisVectorStore implements VectorStore {\n private readonly index: string;\n private readonly prefix: string;\n private readonly dimensions: number;\n private readonly filterableFields: string[];\n\n constructor(\n private readonly client: RedisSearchClient,\n options: RedisVectorStoreOptions = {},\n ) {\n this.index = options.index ?? 'agent_rag_idx';\n this.prefix = options.prefix ?? 'agent_rag:';\n this.dimensions = options.dimensions ?? 1536;\n this.filterableFields = options.filterableFields ?? [];\n }\n\n /** Idempotent: create the HNSW/cosine index if `FT.INFO` says it doesn't exist yet. */\n async ensureSchema(): Promise<void> {\n try {\n await this.client.sendCommand(['FT.INFO', this.index]);\n return;\n } catch {\n // index missing → create it below\n }\n const schema: string[] = ['text', 'TEXT', 'source', 'TAG', 'metadata_json', 'TEXT', 'NOINDEX'];\n for (const field of this.filterableFields) {\n schema.push(`meta_${field}`, 'TAG');\n }\n schema.push(\n 'embedding',\n 'VECTOR',\n 'HNSW',\n '6',\n 'TYPE',\n 'FLOAT32',\n 'DIM',\n String(this.dimensions),\n 'DISTANCE_METRIC',\n 'COSINE',\n );\n await this.client.sendCommand([\n 'FT.CREATE',\n this.index,\n 'ON',\n 'HASH',\n 'PREFIX',\n '1',\n this.prefix,\n 'SCHEMA',\n ...schema,\n ]);\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n const args: (string | Buffer)[] = [\n 'HSET',\n `${this.prefix}${record.id}`,\n 'text',\n record.text,\n 'embedding',\n encodeVector(record.embedding),\n ];\n if (record.source !== undefined) {\n args.push('source', record.source);\n }\n if (record.metadata !== undefined) {\n args.push('metadata_json', JSON.stringify(record.metadata));\n for (const field of this.filterableFields) {\n const value = record.metadata[field];\n if (value !== undefined) {\n // Array metadata is stored as a multi-valued TAG (comma-separated, RediSearch's default\n // separator) so a document can carry several capability tokens; individual values must\n // therefore not contain commas.\n const tag = Array.isArray(value) ? value.map(String).join(',') : String(value);\n args.push(`meta_${field}`, tag);\n }\n }\n }\n await this.client.sendCommand(args);\n }\n }\n\n async remove(documentId: string): Promise<void> {\n const keys = new Set<string>([`${this.prefix}${documentId}`]);\n const pattern = `${this.prefix}${escapeGlob(documentId)}#*`;\n let cursor = '0';\n do {\n const reply = await this.client.sendCommand([\n 'SCAN',\n cursor,\n 'MATCH',\n pattern,\n 'COUNT',\n '256',\n ]);\n const page = parseScanReply(reply);\n cursor = page.cursor;\n for (const key of page.keys) {\n keys.add(key);\n }\n } while (cursor !== '0');\n if (keys.size > 0) {\n await this.client.sendCommand(['DEL', ...keys]);\n }\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n if (filterMatchesNothing(filter)) {\n return [];\n }\n const query = buildFilter(filter);\n const documents = new Map<string, IndexedDocument>();\n const batchSize = 1000;\n let offset = 0;\n for (;;) {\n // RETURN metadata_json (not NOCONTENT) so we can carry a representative chunk's metadata;\n // parseSearchReply already yields prefix-stripped ids + parsed metadata for both RESP2/RESP3.\n const reply = await this.client.sendCommand([\n 'FT.SEARCH',\n this.index,\n query,\n 'RETURN',\n '1',\n 'metadata_json',\n 'LIMIT',\n String(offset),\n String(batchSize),\n 'DIALECT',\n '2',\n ]);\n const passages = parseSearchReply(reply, this.prefix);\n for (const passage of passages) {\n const id = documentIdOf(passage.id);\n if (!documents.has(id)) {\n documents.set(id, {\n id,\n ...(passage.metadata !== undefined ? { metadata: passage.metadata } : {}),\n });\n }\n }\n if (passages.length < batchSize) {\n break;\n }\n offset += batchSize;\n }\n return [...documents.values()];\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n if (filterMatchesNothing(options.filter)) {\n return [];\n }\n const query = `${buildFilter(options.filter)}=>[KNN ${options.topK} @embedding $BLOB AS vector_score]`;\n const reply = await this.client.sendCommand([\n 'FT.SEARCH',\n this.index,\n query,\n 'PARAMS',\n '2',\n 'BLOB',\n encodeVector(embedding),\n 'RETURN',\n '4',\n 'text',\n 'source',\n 'metadata_json',\n 'vector_score',\n 'SORTBY',\n 'vector_score',\n 'ASC',\n 'DIALECT',\n '2',\n 'LIMIT',\n '0',\n String(options.topK),\n ]);\n return parseSearchReply(reply, this.prefix);\n }\n}\n\n/** RediSearch expects a little-endian FLOAT32 buffer for the query/stored vector. */\nfunction encodeVector(embedding: number[]): Buffer {\n return Buffer.from(new Float32Array(embedding).buffer);\n}\n\n/**\n * `*` for no filter, else an AND of TAG clauses. A scalar value is an exact tag match\n * (`@meta_owner:{u1}`); an **array** value is a TAG alternation — match-any / OR\n * (`@meta_audience:{public|role\\:ADMIN}`), the capability-ACL primitive. RediSearch TAG fields are\n * multi-valued per document, so this also matches documents carrying several of the tags.\n */\nfunction buildFilter(filter?: Record<string, unknown>): string {\n if (filter === undefined || Object.keys(filter).length === 0) {\n return '*';\n }\n const clauses = Object.entries(filter).map(([key, value]) => {\n const values = Array.isArray(value) ? value : [value];\n const alternation = values.map((entry) => escapeTag(String(entry))).join('|');\n return `@meta_${key}:{${alternation}}`;\n });\n return `(${clauses.join(' ')})`;\n}\n\n/**\n * A filter with an empty-array value can never match (nothing is a member of the empty set) — and\n * RediSearch has no valid empty-tag syntax, so callers short-circuit to an empty result instead of\n * emitting a broken query. This is the deny primitive (e.g. an actor with no capability tokens).\n */\nfunction filterMatchesNothing(filter?: Record<string, unknown>): boolean {\n if (filter === undefined) {\n return false;\n }\n return Object.values(filter).some((value) => Array.isArray(value) && value.length === 0);\n}\n\n/** Escape RediSearch TAG punctuation so an id/tenant value matches literally. */\nfunction escapeTag(value: string): string {\n return value.replace(/[^a-zA-Z0-9_]/g, (char) => `\\\\${char}`);\n}\n\n/** Escape `SCAN MATCH` glob metacharacters so a document id matches its chunk keys literally. */\nfunction escapeGlob(value: string): string {\n return value.replace(/[*?[\\]\\\\]/g, (char) => `\\\\${char}`);\n}\n\n/**\n * Parse a `SCAN` reply into `{ cursor, keys }`, tolerating both wire shapes: the RESP2 array\n * `[cursor, [key, …]]` and node-redis's RESP3 object `{ cursor, keys }`.\n */\nfunction parseScanReply(reply: unknown): { cursor: string; keys: string[] } {\n if (Array.isArray(reply) && reply.length >= 2) {\n const rawKeys = reply[1];\n return {\n cursor: toStr(reply[0]),\n keys: Array.isArray(rawKeys) ? rawKeys.map(toStr) : [],\n };\n }\n if (typeof reply === 'object' && reply !== null && 'cursor' in reply && 'keys' in reply) {\n const { cursor, keys } = reply as { cursor: unknown; keys: unknown };\n return {\n cursor: toStr(cursor),\n keys: Array.isArray(keys) ? keys.map(toStr) : [],\n };\n }\n return { cursor: '0', keys: [] };\n}\n\nfunction toStr(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);\n}\n\n/**\n * Parse an `FT.SEARCH` reply into passages, tolerating both wire shapes: the RESP2 array\n * `[total, key, [f, v, …], …]` (ioredis / node-redis RESP2) and the RESP3 object\n * `{ results: [{ id, extra_attributes: { … } }] }` (node-redis's default). Distance → similarity\n * via `1 - distance` (COSINE), and the `prefix` is stripped back off the key to recover the chunk id.\n */\nfunction parseSearchReply(reply: unknown, prefix: string): Passage[] {\n if (Array.isArray(reply)) {\n const passages: Passage[] = [];\n for (let index = 1; index + 1 < reply.length; index += 2) {\n const rawFields = reply[index + 1];\n if (Array.isArray(rawFields)) {\n const attrs: Record<string, string> = {};\n for (let field = 0; field + 1 < rawFields.length; field += 2) {\n attrs[toStr(rawFields[field])] = toStr(rawFields[field + 1]);\n }\n passages.push(toPassage(toStr(reply[index]), attrs, prefix));\n }\n }\n return passages;\n }\n if (typeof reply === 'object' && reply !== null && 'results' in reply) {\n const results = (reply as { results: unknown }).results;\n if (Array.isArray(results)) {\n return results.map((result) => toPassage(readId(result), readAttributes(result), prefix));\n }\n }\n return [];\n}\n\nfunction readId(result: unknown): string {\n if (typeof result === 'object' && result !== null && 'id' in result) {\n return toStr((result as { id: unknown }).id);\n }\n return '';\n}\n\nfunction readAttributes(result: unknown): Record<string, string> {\n const attrs: Record<string, string> = {};\n if (typeof result === 'object' && result !== null && 'extra_attributes' in result) {\n const raw = (result as { extra_attributes: unknown }).extra_attributes;\n if (typeof raw === 'object' && raw !== null) {\n for (const [key, value] of Object.entries(raw)) {\n attrs[key] = toStr(value);\n }\n }\n }\n return attrs;\n}\n\nfunction toPassage(rawId: string, attrs: Record<string, string>, prefix: string): Passage {\n const distance = Number(attrs.vector_score ?? '1');\n const source = attrs.source;\n return {\n id: rawId.startsWith(prefix) ? rawId.slice(prefix.length) : rawId,\n text: attrs.text ?? '',\n score: 1 - distance,\n ...(source !== undefined ? { source } : {}),\n ...parseMetadata(attrs.metadata_json),\n };\n}\n\nfunction parseMetadata(raw: string | undefined): { metadata?: Record<string, unknown> } {\n if (raw === undefined) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed === 'object' && parsed !== null) {\n return { metadata: parsed as Record<string, unknown> };\n }\n } catch {\n // not JSON — no metadata\n }\n return {};\n}\n","import type {\n EmbeddingProvider,\n Passage,\n RetrieveOptions,\n Retriever,\n} from '@dudousxd/nestjs-agent-core';\nimport type { VectorStore } from './vector-store.js';\n\n/**\n * Bridges an {@link EmbeddingProvider} + {@link VectorStore} into the core {@link Retriever} SPI:\n * embed the query, then vector-search. This is what you wire as the agent's retriever (as a tool\n * via `createRetrievalTool`, or into `AgentModule.forRoot({ retrieval })` for inject mode).\n */\nexport class EmbeddingRetriever implements Retriever {\n constructor(\n private readonly embedder: EmbeddingProvider,\n private readonly store: VectorStore,\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const [embedding] = await this.embedder.embed([query]);\n if (embedding === undefined) {\n return [];\n }\n return this.store.search(embedding, {\n topK: options.topK ?? 5,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n });\n }\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\nimport { matchesFilter } from './filter.js';\nimport type { ChunkRecord } from './ingest.js';\n\nexport interface KeywordRetrieverOptions {\n /** BM25 term-frequency saturation. Default 1.5. */\n k1?: number;\n /** BM25 length normalization, 0 (off) … 1 (full). Default 0.75. */\n b?: number;\n}\n\ninterface IndexedDoc {\n record: ChunkRecord;\n /** term → frequency within this doc. */\n frequencies: Map<string, number>;\n length: number;\n}\n\n/**\n * An in-memory BM25 lexical retriever — the keyword half of hybrid search. Feed it the SAME\n * {@link ChunkRecord}s you upsert into the vector store (from `chunkDocuments`) so their chunk ids\n * line up and `HybridRetriever` can fuse the two rankings. Full BM25: idf, term-frequency saturation\n * (`k1`), and document-length normalization (`b`).\n */\nexport class KeywordRetriever implements Retriever {\n private readonly docs = new Map<string, IndexedDoc>();\n private readonly documentFrequency = new Map<string, number>();\n private totalLength = 0;\n private readonly k1: number;\n private readonly b: number;\n\n constructor(options: KeywordRetrieverOptions = {}) {\n this.k1 = options.k1 ?? 1.5;\n this.b = options.b ?? 0.75;\n }\n\n /** Index (or re-index) records. Re-adding an existing id replaces its posting and updates stats. */\n add(records: ChunkRecord[]): void {\n for (const record of records) {\n this.remove(record.id);\n const tokens = tokenize(record.text);\n const frequencies = new Map<string, number>();\n for (const token of tokens) {\n frequencies.set(token, (frequencies.get(token) ?? 0) + 1);\n }\n for (const term of frequencies.keys()) {\n this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);\n }\n this.docs.set(record.id, { record, frequencies, length: tokens.length });\n this.totalLength += tokens.length;\n }\n }\n\n private remove(id: string): void {\n const existing = this.docs.get(id);\n if (existing === undefined) {\n return;\n }\n for (const term of existing.frequencies.keys()) {\n const next = (this.documentFrequency.get(term) ?? 1) - 1;\n if (next <= 0) {\n this.documentFrequency.delete(term);\n } else {\n this.documentFrequency.set(term, next);\n }\n }\n this.totalLength -= existing.length;\n this.docs.delete(id);\n }\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const total = this.docs.size;\n if (total === 0) {\n return [];\n }\n const averageLength = this.totalLength / total;\n const queryTerms = new Set(tokenize(query));\n const scored: Passage[] = [];\n for (const { record, frequencies, length } of this.docs.values()) {\n if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n let score = 0;\n for (const term of queryTerms) {\n const termFrequency = frequencies.get(term);\n if (termFrequency === undefined) {\n continue;\n }\n const df = this.documentFrequency.get(term) ?? 0;\n const idf = Math.log(1 + (total - df + 0.5) / (df + 0.5));\n const denominator =\n termFrequency + this.k1 * (1 - this.b + (this.b * length) / averageLength);\n score += idf * ((termFrequency * (this.k1 + 1)) / denominator);\n }\n if (score > 0) {\n scored.push({\n id: record.id,\n text: record.text,\n score,\n ...(record.source !== undefined ? { source: record.source } : {}),\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n }\n return scored.sort((a, b) => b.score - a.score).slice(0, options.topK ?? 5);\n }\n}\n\nfunction tokenize(text: string): string[] {\n return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\nexport interface HybridRetrieverOptions {\n /** RRF constant — larger flattens the rank weighting. Default 60 (the value from the original paper). */\n k?: number;\n /** Candidates pulled from each retriever before fusion. Default 20. */\n fetchTopK?: number;\n /** Per-retriever multipliers on each RRF contribution (same order as `retrievers`). Default all 1. */\n weights?: number[];\n}\n\n/**\n * Fuses several retrievers with Reciprocal Rank Fusion — the robust way to combine, say, dense\n * vector search with lexical BM25. RRF scores each passage by `Σ weight / (k + rank)` across the\n * lists it appears in, so it needs no score normalization between incompatible scales (cosine vs\n * BM25). Deduplicates by passage id, so aligned chunk ids from the two retrievers reinforce.\n */\nexport class HybridRetriever implements Retriever {\n constructor(\n private readonly retrievers: Retriever[],\n private readonly options: HybridRetrieverOptions = {},\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const k = this.options.k ?? 60;\n const fetchTopK = this.options.fetchTopK ?? 20;\n const weights = this.options.weights;\n const lists = await Promise.all(\n this.retrievers.map((retriever) =>\n retriever.retrieve(query, {\n topK: fetchTopK,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n }),\n ),\n );\n\n const fused = new Map<string, { passage: Passage; score: number }>();\n lists.forEach((list, listIndex) => {\n const weight = weights?.[listIndex] ?? 1;\n list.forEach((passage, rank) => {\n const contribution = weight / (k + rank + 1);\n const existing = fused.get(passage.id);\n if (existing !== undefined) {\n existing.score += contribution;\n } else {\n fused.set(passage.id, { passage, score: contribution });\n }\n });\n });\n\n return [...fused.values()]\n .sort((a, b) => b.score - a.score)\n .slice(0, options.topK ?? 5)\n .map((entry) => ({ ...entry.passage, score: entry.score }));\n }\n}\n","import type { Passage, Reranker, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\nexport interface RerankingRetrieverOptions {\n /** How many candidates to pull from the base retriever before reranking. Default 20. */\n fetchTopK?: number;\n}\n\n/**\n * Two-stage retrieval: over-fetch cheap candidates from a base `Retriever`, then reorder them with a\n * stronger {@link Reranker} and keep the top few. The standard precision boost — a fast first stage\n * casts a wide net, a slow accurate second stage sharpens it. Composes over ANY retriever (vector,\n * hybrid, keyword), and is itself a `Retriever`.\n */\nexport class RerankingRetriever implements Retriever {\n constructor(\n private readonly base: Retriever,\n private readonly reranker: Reranker,\n private readonly options: RerankingRetrieverOptions = {},\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const candidates = await this.base.retrieve(query, {\n topK: this.options.fetchTopK ?? 20,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n });\n return this.reranker.rerank(query, candidates, { topK: options.topK ?? 5 });\n }\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\n/**\n * Wraps a `Retriever` so every `retrieve` call ANDs a fixed metadata `filter` on top of whatever the\n * caller passes — the owner/tenant-scoping primitive. `new FilteredRetriever(base, { ownerId })` only\n * ever returns that owner's passages, so a shared vector store serves per-user / per-tenant RAG\n * without the agent (or a crafted query) reaching across the boundary.\n *\n * The fixed filter takes precedence on key conflicts: a caller can narrow further but cannot widen\n * past the scope. Composes over ANY retriever (vector, hybrid, reranked) and is itself a `Retriever`.\n */\nexport class FilteredRetriever implements Retriever {\n constructor(\n private readonly base: Retriever,\n private readonly filter: Record<string, unknown>,\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n return this.base.retrieve(query, {\n ...options,\n filter: { ...options.filter, ...this.filter },\n });\n }\n}\n","import type { EmbeddingProvider } from '@dudousxd/nestjs-agent-core';\nimport { type ChunkOptions, chunkText } from './chunk.js';\nimport type { VectorRecord, VectorStore } from './vector-store.js';\n\n/** A source document to ingest. `id` scopes the chunk ids (`${id}#${n}`); `source` is the citation. */\nexport interface IngestDocument {\n id: string;\n text: string;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\n/** A chunked, not-yet-embedded record. Feed the same array to a vector store AND a keyword index so\n * both retrievers key on identical chunk ids (which is what lets hybrid fusion line them up). */\nexport interface ChunkRecord {\n id: string;\n text: string;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface IngestChunksOptions {\n embedder: EmbeddingProvider;\n store: VectorStore;\n}\n\nexport interface IngestOptions extends ChunkOptions, IngestChunksOptions {}\n\n/**\n * Split documents into chunk records. Chunk ids are `${doc.id}#${index}`, so re-chunking the same\n * document produces the same ids (upsert overwrites in place rather than duplicating).\n */\nexport function chunkDocuments(\n documents: IngestDocument[],\n options: ChunkOptions = {},\n): ChunkRecord[] {\n const chunks: ChunkRecord[] = [];\n for (const document of documents) {\n chunkText(document.text, options).forEach((text, index) => {\n chunks.push({\n id: `${document.id}#${index}`,\n text,\n ...(document.source !== undefined ? { source: document.source } : {}),\n ...(document.metadata !== undefined ? { metadata: document.metadata } : {}),\n });\n });\n }\n return chunks;\n}\n\n/** Embed pre-chunked records (one batched `embed`) and upsert them. Returns the record count. */\nexport async function ingestChunks(\n chunks: ChunkRecord[],\n options: IngestChunksOptions,\n): Promise<number> {\n if (chunks.length === 0) {\n return 0;\n }\n const embeddings = await options.embedder.embed(chunks.map((chunk) => chunk.text));\n const records: VectorRecord[] = chunks.map((chunk, index) => ({\n ...chunk,\n embedding: embeddings[index] ?? [],\n }));\n await options.store.upsert(records);\n return records.length;\n}\n\n/** Chunk → embed → upsert in one call. For hybrid search, use {@link chunkDocuments} +\n * {@link ingestChunks} and feed the same chunks to a {@link import('./keyword-retriever.js').KeywordRetriever}. */\nexport async function ingestDocuments(\n documents: IngestDocument[],\n options: IngestOptions,\n): Promise<number> {\n return ingestChunks(chunkDocuments(documents, options), options);\n}\n","import type { Passage, Retriever, ToolHandler, ToolSpec } from '@dudousxd/nestjs-agent-core';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\nexport interface RetrievalToolOptions {\n /** Tool name the model sees. Default `search_knowledge`. */\n name?: string;\n description?: string;\n /** Passages per search. Default lets the retriever decide (typically 5). */\n topK?: number;\n}\n\n/** A functional tool (`{ spec, handler }`) — pass it to nestjs `provideAgentTool`. */\nexport interface RetrievalTool {\n spec: ToolSpec;\n handler: ToolHandler;\n}\n\nfunction isQueryInput(value: unknown): value is { query: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'query' in value &&\n typeof (value as Record<'query', unknown>).query === 'string'\n );\n}\n\n/** A hand-rolled Standard Schema for `{ query: string }` — keeps this package validation-lib-free. */\nconst querySchema: StandardSchemaV1<{ query: string }> = {\n '~standard': {\n version: 1,\n vendor: 'nestjs-agent-rag',\n validate(value: unknown) {\n return isQueryInput(value)\n ? { value: { query: value.query } }\n : { issues: [{ message: 'retrieval tool expects { query: string }' }] };\n },\n },\n};\n\n/**\n * Builds the agentic-retrieval tool: a `read`-kind `search_knowledge(query)` the model calls when it\n * decides it needs context. Its output `{ passages }` persists as the tool call's result, so sources\n * flow to the frontend and telescope through the normal tool-call machinery (the citation surface).\n *\n * ```ts\n * providers: [provideAgentTool(createRetrievalTool(retriever))]\n * ```\n */\nexport function createRetrievalTool(\n retriever: Retriever,\n options: RetrievalToolOptions = {},\n): RetrievalTool {\n const topK = options.topK;\n return {\n spec: {\n name: options.name ?? 'search_knowledge',\n kind: 'read',\n description:\n options.description ??\n 'Search the knowledge base for passages relevant to a query. Returns passages with their source for citation.',\n inputSchema: querySchema,\n },\n handler: {\n async execute(input: unknown): Promise<{ passages: Passage[] }> {\n if (!isQueryInput(input)) {\n throw new Error('retrieval tool expects { query: string }');\n }\n const passages = await retriever.retrieve(input.query, topK !== undefined ? { topK } : {});\n return { passages };\n },\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;ACaO,SAASA,UAAUC,MAAcC,UAAwB,CAAC,GAAC;AAChE,QAAMC,YAAYD,QAAQC,aAAa;AACvC,QAAMC,UAAUF,QAAQE,WAAW;AACnC,QAAMC,aAAaJ,KAAKK,KAAI;AAC5B,MAAID,WAAWE,WAAW,GAAG;AAC3B,WAAO,CAAA;EACT;AACA,MAAIF,WAAWE,UAAUJ,WAAW;AAClC,WAAO;MAACE;;EACV;AAEA,QAAMG,SAAmB,CAAA;AACzB,MAAIC,QAAQ;AACZ,SAAOA,QAAQJ,WAAWE,QAAQ;AAChC,UAAMG,MAAMC,KAAKC,IAAIH,QAAQN,WAAWE,WAAWE,MAAM;AACzD,QAAIM,UAAUH;AACd,QAAIA,MAAML,WAAWE,QAAQ;AAC3B,YAAMO,SAAST,WAAWU,MAAMN,OAAOC,GAAAA;AACvC,YAAMM,WAAWL,KAAKM,IACpBH,OAAOI,YAAY,MAAA,GACnBJ,OAAOI,YAAY,IAAA,GACnBJ,OAAOI,YAAY,GAAA,CAAA;AAGrB,UAAIF,WAAWb,YAAY,GAAG;AAC5BU,kBAAUJ,QAAQO,WAAW;MAC/B;IACF;AACA,UAAMG,QAAQd,WAAWU,MAAMN,OAAOI,OAAAA,EAASP,KAAI;AACnD,QAAIa,MAAMZ,SAAS,GAAG;AACpBC,aAAOY,KAAKD,KAAAA;IACd;AACA,QAAIN,WAAWR,WAAWE,QAAQ;AAChC;IACF;AAEAE,YAAQE,KAAKM,IAAIJ,UAAUT,SAASK,QAAQ,CAAA;EAC9C;AACA,SAAOD;AACT;AAvCgBR;;;ACuCT,SAASqB,aAAaC,SAAe;AAC1C,SAAOA,QAAQC,QAAQ,SAAS,EAAA;AAClC;AAFgBF;;;ACzCT,SAASG,cACdC,UACAC,QAA+B;AAE/B,QAAMC,UAAUC,OAAOD,QAAQD,MAAAA;AAC/B,MAAID,aAAaI,QAAW;AAC1B,WAAOF,QAAQG,WAAW;EAC5B;AACA,SAAOH,QAAQI,MAAM,CAAC,CAACC,KAAKC,QAAAA,MAAS;AACnC,QAAIC,MAAMC,QAAQF,QAAAA,GAAW;AAC3B,YAAMG,SAASX,SAASO,GAAAA;AACxB,aAAOE,MAAMC,QAAQC,MAAAA,IACjBA,OAAOC,KAAK,CAACC,UAAUL,SAASM,SAASD,KAAAA,CAAAA,IACzCL,SAASM,SAASH,MAAAA;IACxB;AACA,WAAOX,SAASO,GAAAA,MAASC;EAC3B,CAAA;AACF;AAjBgBT;;;ACGT,IAAMgB,oBAAN,MAAMA;EAbb,OAaaA;;;EACMC,UAAU,oBAAIC,IAAAA;EAE/B,MAAMC,OAAOF,SAAwC;AACnD,eAAWG,UAAUH,SAAS;AAC5B,WAAKA,QAAQI,IAAID,OAAOE,IAAIF,MAAAA;IAC9B;EACF;EAEA,MAAMG,OAAOC,YAAmC;AAC9C,eAAWF,MAAM,KAAKL,QAAQQ,KAAI,GAAI;AACpC,UAAIC,aAAaJ,EAAAA,MAAQE,YAAY;AACnC,aAAKP,QAAQU,OAAOL,EAAAA;MACtB;IACF;EACF;EAEA,MAAMM,cAAcC,QAA8D;AAChF,UAAMC,YAAY,oBAAIZ,IAAAA;AACtB,eAAWE,UAAU,KAAKH,QAAQc,OAAM,GAAI;AAC1C,UAAIF,WAAWG,UAAa,CAACC,cAAcb,OAAOc,UAAUL,MAAAA,GAAS;AACnE;MACF;AACA,YAAMP,KAAKI,aAAaN,OAAOE,EAAE;AACjC,UAAI,CAACQ,UAAUK,IAAIb,EAAAA,GAAK;AACtBQ,kBAAUT,IAAIC,IAAI;UAChBA;UACA,GAAIF,OAAOc,aAAaF,SAAY;YAAEE,UAAUd,OAAOc;UAAS,IAAI,CAAC;QACvE,CAAA;MACF;IACF;AACA,WAAO;SAAIJ,UAAUC,OAAM;;EAC7B;EAEA,MAAMK,OAAOC,WAAqBC,SAAkD;AAClF,UAAMC,SAAoB,CAAA;AAC1B,eAAWnB,UAAU,KAAKH,QAAQc,OAAM,GAAI;AAC1C,UAAIO,QAAQT,WAAWG,UAAa,CAACC,cAAcb,OAAOc,UAAUI,QAAQT,MAAM,GAAG;AACnF;MACF;AACAU,aAAOC,KAAK;QACVlB,IAAIF,OAAOE;QACXmB,MAAMrB,OAAOqB;QACbC,OAAOC,iBAAiBN,WAAWjB,OAAOiB,SAAS;QACnD,GAAIjB,OAAOwB,WAAWZ,SAAY;UAAEY,QAAQxB,OAAOwB;QAAO,IAAI,CAAC;QAC/D,GAAIxB,OAAOc,aAAaF,SAAY;UAAEE,UAAUd,OAAOc;QAAS,IAAI,CAAC;MACvE,CAAA;IACF;AACA,WAAOK,OAAOM,KAAK,CAACC,GAAGC,MAAMA,EAAEL,QAAQI,EAAEJ,KAAK,EAAEM,MAAM,GAAGV,QAAQW,IAAI;EACvE;AACF;AAEA,SAASN,iBAAiBG,GAAaC,GAAW;AAChD,MAAIG,MAAM;AACV,MAAIC,QAAQ;AACZ,MAAIC,QAAQ;AACZ,QAAMC,SAASC,KAAKC,IAAIT,EAAEO,QAAQN,EAAEM,MAAM;AAC1C,WAASG,QAAQ,GAAGA,QAAQH,QAAQG,SAAS,GAAG;AAC9C,UAAMC,SAASX,EAAEU,KAAAA,KAAU;AAC3B,UAAME,SAASX,EAAES,KAAAA,KAAU;AAC3BN,WAAOO,SAASC;AAChBP,aAASM,SAASA;AAClBL,aAASM,SAASA;EACpB;AACA,QAAMC,cAAcL,KAAKM,KAAKT,KAAAA,IAASG,KAAKM,KAAKR,KAAAA;AACjD,SAAOO,gBAAgB,IAAI,IAAIT,MAAMS;AACvC;AAdShB;;;ACpCF,IAAMkB,gBAAN,MAAMA;EALb,OAKaA;;;;EACMC;EACAC;EAEjB,YACmBC,QACjBC,UAAgC,CAAC,GACjC;SAFiBD,SAAAA;AAGjB,SAAKF,QAAQG,QAAQH,SAAS;AAC9B,SAAKC,aAAaE,QAAQF,cAAc;EAC1C;;EAGA,MAAMG,eAA8B;AAClC,UAAM,KAAKF,OAAOG,MAAM,uCAAA;AACxB,UAAM,KAAKH,OAAOG,MAChB,8BAA8B,KAAKL,KAAK;;;;;2BAKnB,KAAKC,UAAU;QAClC;AAEJ,UAAM,KAAKC,OAAOG,MAChB,8BAA8B,KAAKL,KAAK;aACjC,KAAKA,KAAK,2CAA2C;EAEhE;EAEA,MAAMM,OAAOC,SAAwC;AACnD,eAAWC,UAAUD,SAAS;AAC5B,YAAM,KAAKL,OAAOG,MAChB,eAAe,KAAKL,KAAK;;;;;;4CAOzB;QACEQ,OAAOC;QACPD,OAAOE;QACPF,OAAOG,UAAU;QACjBH,OAAOI,aAAaC,SAAYC,KAAKC,UAAUP,OAAOI,QAAQ,IAAI;QAClEI,gBAAgBR,OAAOS,SAAS;OACjC;IAEL;EACF;EAEA,MAAMC,OAAOC,YAAmC;AAC9C,UAAM,KAAKjB,OAAOG,MAAM,eAAe,KAAKL,KAAK,UAAUoB,sBAAAA,SAA+B;MACxFD;KACD;EACH;EAEA,MAAME,cAAcC,QAA8D;AAChF,UAAMC,QAAQC,WAAWF,QAAQ,CAAA;AAEjC,UAAMG,OAAO,MAAM,KAAKvB,OAAOG,MAI7B,uBAAuBe,sBAAAA,KAA2BA,sBAAAA;cAC1C,KAAKpB,KAAK;SACfuB,MAAMG,GAAG;kBACAN,sBAAAA,IACZG,MAAMI,MAAM;AAEd,WAAOF,KAAKG,IAAI,CAACC,SAAS;MACxBpB,IAAIoB,IAAIC;MACR,GAAID,IAAIjB,aAAa,OAAO;QAAEA,UAAUiB,IAAIjB;MAAS,IAAI,CAAC;IAC5D,EAAA;EACF;EAEA,MAAMmB,OAAOd,WAAqBd,SAAkD;AAClF,UAAM6B,SAAShB,gBAAgBC,SAAAA;AAE/B,UAAMM,QAAQC,WAAWrB,QAAQmB,QAAQ,CAAA;AACzC,UAAMG,OAAO,MAAM,KAAKvB,OAAOG,MAC7B;cACQ,KAAKL,KAAK;SACfuB,MAAMG,GAAG;;kBAGZ;MAACM;MAAQ7B,QAAQ8B;SAASV,MAAMI;KAAO;AAEzC,WAAOF,KAAKG,IAAI,CAACC,SAAS;MACxBpB,IAAIoB,IAAIpB;MACRC,MAAMmB,IAAInB;MACVwB,OAAOC,OAAON,IAAIK,KAAK;MACvB,GAAIL,IAAIlB,WAAW,OAAO;QAAEA,QAAQkB,IAAIlB;MAAO,IAAI,CAAC;MACpD,GAAIkB,IAAIjB,aAAa,OAAO;QAAEA,UAAUiB,IAAIjB;MAAS,IAAI,CAAC;IAC5D,EAAA;EACF;AACF;AAmBA,SAASY,WACPF,QACAc,YAAkB;AAElB,MAAId,WAAWT,UAAawB,OAAOC,KAAKhB,MAAAA,EAAQiB,WAAW,GAAG;AAC5D,WAAO;MAAEb,KAAK;MAAIC,QAAQ,CAAA;IAAG;EAC/B;AACA,QAAMa,UAAoB,CAAA;AAC1B,QAAMb,SAAoB,CAAA;AAC1B,QAAMc,SAAkC,CAAC;AACzC,MAAIC,QAAQN;AACZ,aAAW,CAACO,KAAKC,KAAAA,KAAUP,OAAOQ,QAAQvB,MAAAA,GAAS;AACjD,QAAIwB,MAAMC,QAAQH,KAAAA,GAAQ;AACxB,UAAIA,MAAML,WAAW,GAAG;AACtBC,gBAAQQ,KAAK,OAAA;AACb;MACF;AACA,YAAMC,WAAW,IAAIP,KAAAA;AACrBf,aAAOqB,KAAKL,GAAAA;AACZD,eAAS;AACT,YAAMQ,WAAW,IAAIR,KAAAA;AACrBf,aAAOqB,KAAKJ,MAAMhB,IAAIuB,MAAAA,CAAAA;AACtBT,eAAS;AACTF,cAAQQ,KACN,qCAAqCC,QAAAA,8BACjBA,QAAAA,qCAA6CA,QAAAA,aAAqBC,QAAAA,UAAkB;IAE5G,OAAO;AACLT,aAAOE,GAAAA,IAAOC;IAChB;EACF;AACA,MAAIP,OAAOC,KAAKG,MAAAA,EAAQF,SAAS,GAAG;AAClCC,YAAQQ,KAAK,gBAAgBN,KAAAA,SAAc;AAC3Cf,WAAOqB,KAAKlC,KAAKC,UAAU0B,MAAAA,CAAAA;AAC3BC,aAAS;EACX;AACA,SAAO;IAAEhB,KAAK,SAASc,QAAQY,KAAK,OAAA,CAAA;IAAYzB;EAAO;AACzD;AArCSH;AA4CT,IAAMJ,yBAAyB;AAG/B,SAASJ,gBAAgBC,WAAmB;AAC1C,SAAO,IAAIA,UAAUmC,KAAK,GAAA,CAAA;AAC5B;AAFSpC;;;ACzJF,IAAMqC,mBAAN,MAAMA;EAtCb,OAsCaA;;;;EACMC;EACAC;EACAC;EACAC;EAEjB,YACmBC,QACjBC,UAAmC,CAAC,GACpC;SAFiBD,SAAAA;AAGjB,SAAKJ,QAAQK,QAAQL,SAAS;AAC9B,SAAKC,SAASI,QAAQJ,UAAU;AAChC,SAAKC,aAAaG,QAAQH,cAAc;AACxC,SAAKC,mBAAmBE,QAAQF,oBAAoB,CAAA;EACtD;;EAGA,MAAMG,eAA8B;AAClC,QAAI;AACF,YAAM,KAAKF,OAAOG,YAAY;QAAC;QAAW,KAAKP;OAAM;AACrD;IACF,QAAQ;IAER;AACA,UAAMQ,SAAmB;MAAC;MAAQ;MAAQ;MAAU;MAAO;MAAiB;MAAQ;;AACpF,eAAWC,SAAS,KAAKN,kBAAkB;AACzCK,aAAOE,KAAK,QAAQD,KAAAA,IAAS,KAAA;IAC/B;AACAD,WAAOE,KACL,aACA,UACA,QACA,KACA,QACA,WACA,OACAC,OAAO,KAAKT,UAAU,GACtB,mBACA,QAAA;AAEF,UAAM,KAAKE,OAAOG,YAAY;MAC5B;MACA,KAAKP;MACL;MACA;MACA;MACA;MACA,KAAKC;MACL;SACGO;KACJ;EACH;EAEA,MAAMI,OAAOC,SAAwC;AACnD,eAAWC,UAAUD,SAAS;AAC5B,YAAME,OAA4B;QAChC;QACA,GAAG,KAAKd,MAAM,GAAGa,OAAOE,EAAE;QAC1B;QACAF,OAAOG;QACP;QACAC,aAAaJ,OAAOK,SAAS;;AAE/B,UAAIL,OAAOM,WAAWC,QAAW;AAC/BN,aAAKL,KAAK,UAAUI,OAAOM,MAAM;MACnC;AACA,UAAIN,OAAOQ,aAAaD,QAAW;AACjCN,aAAKL,KAAK,iBAAiBa,KAAKC,UAAUV,OAAOQ,QAAQ,CAAA;AACzD,mBAAWb,SAAS,KAAKN,kBAAkB;AACzC,gBAAMsB,QAAQX,OAAOQ,SAASb,KAAAA;AAC9B,cAAIgB,UAAUJ,QAAW;AAIvB,kBAAMK,MAAMC,MAAMC,QAAQH,KAAAA,IAASA,MAAMI,IAAIlB,MAAAA,EAAQmB,KAAK,GAAA,IAAOnB,OAAOc,KAAAA;AACxEV,iBAAKL,KAAK,QAAQD,KAAAA,IAASiB,GAAAA;UAC7B;QACF;MACF;AACA,YAAM,KAAKtB,OAAOG,YAAYQ,IAAAA;IAChC;EACF;EAEA,MAAMgB,OAAOC,YAAmC;AAC9C,UAAMC,OAAO,oBAAIC,IAAY;MAAC,GAAG,KAAKjC,MAAM,GAAG+B,UAAAA;KAAa;AAC5D,UAAMG,UAAU,GAAG,KAAKlC,MAAM,GAAGmC,WAAWJ,UAAAA,CAAAA;AAC5C,QAAIK,SAAS;AACb,OAAG;AACD,YAAMC,QAAQ,MAAM,KAAKlC,OAAOG,YAAY;QAC1C;QACA8B;QACA;QACAF;QACA;QACA;OACD;AACD,YAAMI,OAAOC,eAAeF,KAAAA;AAC5BD,eAASE,KAAKF;AACd,iBAAWI,OAAOF,KAAKN,MAAM;AAC3BA,aAAKS,IAAID,GAAAA;MACX;IACF,SAASJ,WAAW;AACpB,QAAIJ,KAAKU,OAAO,GAAG;AACjB,YAAM,KAAKvC,OAAOG,YAAY;QAAC;WAAU0B;OAAK;IAChD;EACF;EAEA,MAAMW,cAAcC,QAA8D;AAChF,QAAIC,qBAAqBD,MAAAA,GAAS;AAChC,aAAO,CAAA;IACT;AACA,UAAME,QAAQC,YAAYH,MAAAA;AAC1B,UAAMI,YAAY,oBAAIC,IAAAA;AACtB,UAAMC,YAAY;AAClB,QAAIC,SAAS;AACb,eAAS;AAGP,YAAMd,QAAQ,MAAM,KAAKlC,OAAOG,YAAY;QAC1C;QACA,KAAKP;QACL+C;QACA;QACA;QACA;QACA;QACApC,OAAOyC,MAAAA;QACPzC,OAAOwC,SAAAA;QACP;QACA;OACD;AACD,YAAME,WAAWC,iBAAiBhB,OAAO,KAAKrC,MAAM;AACpD,iBAAWsD,WAAWF,UAAU;AAC9B,cAAMrC,KAAKwC,aAAaD,QAAQvC,EAAE;AAClC,YAAI,CAACiC,UAAUQ,IAAIzC,EAAAA,GAAK;AACtBiC,oBAAUS,IAAI1C,IAAI;YAChBA;YACA,GAAIuC,QAAQjC,aAAaD,SAAY;cAAEC,UAAUiC,QAAQjC;YAAS,IAAI,CAAC;UACzE,CAAA;QACF;MACF;AACA,UAAI+B,SAASM,SAASR,WAAW;AAC/B;MACF;AACAC,gBAAUD;IACZ;AACA,WAAO;SAAIF,UAAUW,OAAM;;EAC7B;EAEA,MAAMC,OAAO1C,WAAqBd,SAAkD;AAClF,QAAIyC,qBAAqBzC,QAAQwC,MAAM,GAAG;AACxC,aAAO,CAAA;IACT;AACA,UAAME,QAAQ,GAAGC,YAAY3C,QAAQwC,MAAM,CAAA,UAAWxC,QAAQyD,IAAI;AAClE,UAAMxB,QAAQ,MAAM,KAAKlC,OAAOG,YAAY;MAC1C;MACA,KAAKP;MACL+C;MACA;MACA;MACA;MACA7B,aAAaC,SAAAA;MACb;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACAR,OAAON,QAAQyD,IAAI;KACpB;AACD,WAAOR,iBAAiBhB,OAAO,KAAKrC,MAAM;EAC5C;AACF;AAGA,SAASiB,aAAaC,WAAmB;AACvC,SAAO4C,OAAOC,KAAK,IAAIC,aAAa9C,SAAAA,EAAW+C,MAAM;AACvD;AAFShD;AAUT,SAAS8B,YAAYH,QAAgC;AACnD,MAAIA,WAAWxB,UAAa8C,OAAOlC,KAAKY,MAAAA,EAAQc,WAAW,GAAG;AAC5D,WAAO;EACT;AACA,QAAMS,UAAUD,OAAOE,QAAQxB,MAAAA,EAAQhB,IAAI,CAAC,CAACY,KAAKhB,KAAAA,MAAM;AACtD,UAAMmC,SAASjC,MAAMC,QAAQH,KAAAA,IAASA,QAAQ;MAACA;;AAC/C,UAAM6C,cAAcV,OAAO/B,IAAI,CAAC0C,UAAUC,UAAU7D,OAAO4D,KAAAA,CAAAA,CAAAA,EAASzC,KAAK,GAAA;AACzE,WAAO,SAASW,GAAAA,KAAQ6B,WAAAA;EAC1B,CAAA;AACA,SAAO,IAAIF,QAAQtC,KAAK,GAAA,CAAA;AAC1B;AAVSkB;AAiBT,SAASF,qBAAqBD,QAAgC;AAC5D,MAAIA,WAAWxB,QAAW;AACxB,WAAO;EACT;AACA,SAAO8C,OAAOP,OAAOf,MAAAA,EAAQ4B,KAAK,CAAChD,UAAUE,MAAMC,QAAQH,KAAAA,KAAUA,MAAMkC,WAAW,CAAA;AACxF;AALSb;AAQT,SAAS0B,UAAU/C,OAAa;AAC9B,SAAOA,MAAMiD,QAAQ,kBAAkB,CAACC,SAAS,KAAKA,IAAAA,EAAM;AAC9D;AAFSH;AAKT,SAASpC,WAAWX,OAAa;AAC/B,SAAOA,MAAMiD,QAAQ,cAAc,CAACC,SAAS,KAAKA,IAAAA,EAAM;AAC1D;AAFSvC;AAQT,SAASI,eAAeF,OAAc;AACpC,MAAIX,MAAMC,QAAQU,KAAAA,KAAUA,MAAMqB,UAAU,GAAG;AAC7C,UAAMiB,UAAUtC,MAAM,CAAA;AACtB,WAAO;MACLD,QAAQwC,MAAMvC,MAAM,CAAA,CAAE;MACtBL,MAAMN,MAAMC,QAAQgD,OAAAA,IAAWA,QAAQ/C,IAAIgD,KAAAA,IAAS,CAAA;IACtD;EACF;AACA,MAAI,OAAOvC,UAAU,YAAYA,UAAU,QAAQ,YAAYA,SAAS,UAAUA,OAAO;AACvF,UAAM,EAAED,QAAQJ,KAAI,IAAKK;AACzB,WAAO;MACLD,QAAQwC,MAAMxC,MAAAA;MACdJ,MAAMN,MAAMC,QAAQK,IAAAA,IAAQA,KAAKJ,IAAIgD,KAAAA,IAAS,CAAA;IAChD;EACF;AACA,SAAO;IAAExC,QAAQ;IAAKJ,MAAM,CAAA;EAAG;AACjC;AAhBSO;AAkBT,SAASqC,MAAMpD,OAAc;AAC3B,MAAI,OAAOA,UAAU,UAAU;AAC7B,WAAOA;EACT;AACA,SAAOsC,OAAOe,SAASrD,KAAAA,IAASA,MAAMsD,SAAS,MAAA,IAAUpE,OAAOc,KAAAA;AAClE;AALSoD;AAaT,SAASvB,iBAAiBhB,OAAgBrC,QAAc;AACtD,MAAI0B,MAAMC,QAAQU,KAAAA,GAAQ;AACxB,UAAMe,WAAsB,CAAA;AAC5B,aAASrD,QAAQ,GAAGA,QAAQ,IAAIsC,MAAMqB,QAAQ3D,SAAS,GAAG;AACxD,YAAMgF,YAAY1C,MAAMtC,QAAQ,CAAA;AAChC,UAAI2B,MAAMC,QAAQoD,SAAAA,GAAY;AAC5B,cAAMC,QAAgC,CAAC;AACvC,iBAASxE,QAAQ,GAAGA,QAAQ,IAAIuE,UAAUrB,QAAQlD,SAAS,GAAG;AAC5DwE,gBAAMJ,MAAMG,UAAUvE,KAAAA,CAAM,CAAA,IAAKoE,MAAMG,UAAUvE,QAAQ,CAAA,CAAE;QAC7D;AACA4C,iBAAS3C,KAAKwE,UAAUL,MAAMvC,MAAMtC,KAAAA,CAAM,GAAGiF,OAAOhF,MAAAA,CAAAA;MACtD;IACF;AACA,WAAOoD;EACT;AACA,MAAI,OAAOf,UAAU,YAAYA,UAAU,QAAQ,aAAaA,OAAO;AACrE,UAAM6C,UAAW7C,MAA+B6C;AAChD,QAAIxD,MAAMC,QAAQuD,OAAAA,GAAU;AAC1B,aAAOA,QAAQtD,IAAI,CAACuD,WAAWF,UAAUG,OAAOD,MAAAA,GAASE,eAAeF,MAAAA,GAASnF,MAAAA,CAAAA;IACnF;EACF;AACA,SAAO,CAAA;AACT;AAtBSqD;AAwBT,SAAS+B,OAAOD,QAAe;AAC7B,MAAI,OAAOA,WAAW,YAAYA,WAAW,QAAQ,QAAQA,QAAQ;AACnE,WAAOP,MAAOO,OAA2BpE,EAAE;EAC7C;AACA,SAAO;AACT;AALSqE;AAOT,SAASC,eAAeF,QAAe;AACrC,QAAMH,QAAgC,CAAC;AACvC,MAAI,OAAOG,WAAW,YAAYA,WAAW,QAAQ,sBAAsBA,QAAQ;AACjF,UAAMG,MAAOH,OAAyCI;AACtD,QAAI,OAAOD,QAAQ,YAAYA,QAAQ,MAAM;AAC3C,iBAAW,CAAC9C,KAAKhB,KAAAA,KAAU0C,OAAOE,QAAQkB,GAAAA,GAAM;AAC9CN,cAAMxC,GAAAA,IAAOoC,MAAMpD,KAAAA;MACrB;IACF;EACF;AACA,SAAOwD;AACT;AAXSK;AAaT,SAASJ,UAAUO,OAAeR,OAA+BhF,QAAc;AAC7E,QAAMyF,WAAWC,OAAOV,MAAMW,gBAAgB,GAAA;AAC9C,QAAMxE,SAAS6D,MAAM7D;AACrB,SAAO;IACLJ,IAAIyE,MAAMI,WAAW5F,MAAAA,IAAUwF,MAAMK,MAAM7F,OAAO0D,MAAM,IAAI8B;IAC5DxE,MAAMgE,MAAMhE,QAAQ;IACpB8E,OAAO,IAAIL;IACX,GAAItE,WAAWC,SAAY;MAAED;IAAO,IAAI,CAAC;IACzC,GAAG4E,cAAcf,MAAMgB,aAAa;EACtC;AACF;AAVSf;AAYT,SAASc,cAAcT,KAAuB;AAC5C,MAAIA,QAAQlE,QAAW;AACrB,WAAO,CAAC;EACV;AACA,MAAI;AACF,UAAM6E,SAAkB3E,KAAK4E,MAAMZ,GAAAA;AACnC,QAAI,OAAOW,WAAW,YAAYA,WAAW,MAAM;AACjD,aAAO;QAAE5E,UAAU4E;MAAkC;IACvD;EACF,QAAQ;EAER;AACA,SAAO,CAAC;AACV;AAbSF;;;ACvVF,IAAMI,qBAAN,MAAMA;EALb,OAKaA;;;;;EACX,YACmBC,UACAC,OACjB;SAFiBD,WAAAA;SACAC,QAAAA;EAChB;EAEH,MAAMC,SAASC,OAAeC,UAA2B,CAAC,GAAuB;AAC/E,UAAM,CAACC,SAAAA,IAAa,MAAM,KAAKL,SAASM,MAAM;MAACH;KAAM;AACrD,QAAIE,cAAcE,QAAW;AAC3B,aAAO,CAAA;IACT;AACA,WAAO,KAAKN,MAAMO,OAAOH,WAAW;MAClCI,MAAML,QAAQK,QAAQ;MACtB,GAAIL,QAAQM,WAAWH,SAAY;QAAEG,QAAQN,QAAQM;MAAO,IAAI,CAAC;IACnE,CAAA;EACF;AACF;;;ACLO,IAAMC,mBAAN,MAAMA;EAvBb,OAuBaA;;;EACMC,OAAO,oBAAIC,IAAAA;EACXC,oBAAoB,oBAAID,IAAAA;EACjCE,cAAc;EACLC;EACAC;EAEjB,YAAYC,UAAmC,CAAC,GAAG;AACjD,SAAKF,KAAKE,QAAQF,MAAM;AACxB,SAAKC,IAAIC,QAAQD,KAAK;EACxB;;EAGAE,IAAIC,SAA8B;AAChC,eAAWC,UAAUD,SAAS;AAC5B,WAAKE,OAAOD,OAAOE,EAAE;AACrB,YAAMC,SAASC,SAASJ,OAAOK,IAAI;AACnC,YAAMC,cAAc,oBAAId,IAAAA;AACxB,iBAAWe,SAASJ,QAAQ;AAC1BG,oBAAYE,IAAID,QAAQD,YAAYG,IAAIF,KAAAA,KAAU,KAAK,CAAA;MACzD;AACA,iBAAWG,QAAQJ,YAAYK,KAAI,GAAI;AACrC,aAAKlB,kBAAkBe,IAAIE,OAAO,KAAKjB,kBAAkBgB,IAAIC,IAAAA,KAAS,KAAK,CAAA;MAC7E;AACA,WAAKnB,KAAKiB,IAAIR,OAAOE,IAAI;QAAEF;QAAQM;QAAaM,QAAQT,OAAOS;MAAO,CAAA;AACtE,WAAKlB,eAAeS,OAAOS;IAC7B;EACF;EAEQX,OAAOC,IAAkB;AAC/B,UAAMW,WAAW,KAAKtB,KAAKkB,IAAIP,EAAAA;AAC/B,QAAIW,aAAaC,QAAW;AAC1B;IACF;AACA,eAAWJ,QAAQG,SAASP,YAAYK,KAAI,GAAI;AAC9C,YAAMI,QAAQ,KAAKtB,kBAAkBgB,IAAIC,IAAAA,KAAS,KAAK;AACvD,UAAIK,QAAQ,GAAG;AACb,aAAKtB,kBAAkBuB,OAAON,IAAAA;MAChC,OAAO;AACL,aAAKjB,kBAAkBe,IAAIE,MAAMK,IAAAA;MACnC;IACF;AACA,SAAKrB,eAAemB,SAASD;AAC7B,SAAKrB,KAAKyB,OAAOd,EAAAA;EACnB;EAEA,MAAMe,SAASC,OAAerB,UAA2B,CAAC,GAAuB;AAC/E,UAAMsB,QAAQ,KAAK5B,KAAK6B;AACxB,QAAID,UAAU,GAAG;AACf,aAAO,CAAA;IACT;AACA,UAAME,gBAAgB,KAAK3B,cAAcyB;AACzC,UAAMG,aAAa,IAAIC,IAAInB,SAASc,KAAAA,CAAAA;AACpC,UAAMM,SAAoB,CAAA;AAC1B,eAAW,EAAExB,QAAQM,aAAaM,OAAM,KAAM,KAAKrB,KAAKkC,OAAM,GAAI;AAChE,UAAI5B,QAAQ6B,WAAWZ,UAAa,CAACa,cAAc3B,OAAO4B,UAAU/B,QAAQ6B,MAAM,GAAG;AACnF;MACF;AACA,UAAIG,QAAQ;AACZ,iBAAWnB,QAAQY,YAAY;AAC7B,cAAMQ,gBAAgBxB,YAAYG,IAAIC,IAAAA;AACtC,YAAIoB,kBAAkBhB,QAAW;AAC/B;QACF;AACA,cAAMiB,KAAK,KAAKtC,kBAAkBgB,IAAIC,IAAAA,KAAS;AAC/C,cAAMsB,MAAMC,KAAKC,IAAI,KAAKf,QAAQY,KAAK,QAAQA,KAAK,IAAE;AACtD,cAAMI,cACJL,gBAAgB,KAAKnC,MAAM,IAAI,KAAKC,IAAK,KAAKA,IAAIgB,SAAUS;AAC9DQ,iBAASG,OAAQF,iBAAiB,KAAKnC,KAAK,KAAMwC;MACpD;AACA,UAAIN,QAAQ,GAAG;AACbL,eAAOY,KAAK;UACVlC,IAAIF,OAAOE;UACXG,MAAML,OAAOK;UACbwB;UACA,GAAI7B,OAAOqC,WAAWvB,SAAY;YAAEuB,QAAQrC,OAAOqC;UAAO,IAAI,CAAC;UAC/D,GAAIrC,OAAO4B,aAAad,SAAY;YAAEc,UAAU5B,OAAO4B;UAAS,IAAI,CAAC;QACvE,CAAA;MACF;IACF;AACA,WAAOJ,OAAOc,KAAK,CAACC,GAAG3C,MAAMA,EAAEiC,QAAQU,EAAEV,KAAK,EAAEW,MAAM,GAAG3C,QAAQ4C,QAAQ,CAAA;EAC3E;AACF;AAEA,SAASrC,SAASC,MAAY;AAC5B,SAAOA,KAAKqC,YAAW,EAAGC,MAAM,YAAA,KAAiB,CAAA;AACnD;AAFSvC;;;AC3FF,IAAMwC,kBAAN,MAAMA;EANb,OAMaA;;;;;EACX,YACmBC,YACAC,UAAkC,CAAC,GACpD;SAFiBD,aAAAA;SACAC,UAAAA;EAChB;EAEH,MAAMC,SAASC,OAAeF,UAA2B,CAAC,GAAuB;AAC/E,UAAMG,IAAI,KAAKH,QAAQG,KAAK;AAC5B,UAAMC,YAAY,KAAKJ,QAAQI,aAAa;AAC5C,UAAMC,UAAU,KAAKL,QAAQK;AAC7B,UAAMC,QAAQ,MAAMC,QAAQC,IAC1B,KAAKT,WAAWU,IAAI,CAACC,cACnBA,UAAUT,SAASC,OAAO;MACxBS,MAAMP;MACN,GAAIJ,QAAQY,WAAWC,SAAY;QAAED,QAAQZ,QAAQY;MAAO,IAAI,CAAC;IACnE,CAAA,CAAA,CAAA;AAIJ,UAAME,QAAQ,oBAAIC,IAAAA;AAClBT,UAAMU,QAAQ,CAACC,MAAMC,cAAAA;AACnB,YAAMC,SAASd,UAAUa,SAAAA,KAAc;AACvCD,WAAKD,QAAQ,CAACI,SAASC,SAAAA;AACrB,cAAMC,eAAeH,UAAUhB,IAAIkB,OAAO;AAC1C,cAAME,WAAWT,MAAMU,IAAIJ,QAAQK,EAAE;AACrC,YAAIF,aAAaV,QAAW;AAC1BU,mBAASG,SAASJ;QACpB,OAAO;AACLR,gBAAMa,IAAIP,QAAQK,IAAI;YAAEL;YAASM,OAAOJ;UAAa,CAAA;QACvD;MACF,CAAA;IACF,CAAA;AAEA,WAAO;SAAIR,MAAMc,OAAM;MACpBC,KAAK,CAACC,GAAGC,MAAMA,EAAEL,QAAQI,EAAEJ,KAAK,EAChCM,MAAM,GAAGhC,QAAQW,QAAQ,CAAA,EACzBF,IAAI,CAACwB,WAAW;MAAE,GAAGA,MAAMb;MAASM,OAAOO,MAAMP;IAAM,EAAA;EAC5D;AACF;;;AC1CO,IAAMQ,qBAAN,MAAMA;EANb,OAMaA;;;;;;EACX,YACmBC,MACAC,UACAC,UAAqC,CAAC,GACvD;SAHiBF,OAAAA;SACAC,WAAAA;SACAC,UAAAA;EAChB;EAEH,MAAMC,SAASC,OAAeF,UAA2B,CAAC,GAAuB;AAC/E,UAAMG,aAAa,MAAM,KAAKL,KAAKG,SAASC,OAAO;MACjDE,MAAM,KAAKJ,QAAQK,aAAa;MAChC,GAAIL,QAAQM,WAAWC,SAAY;QAAED,QAAQN,QAAQM;MAAO,IAAI,CAAC;IACnE,CAAA;AACA,WAAO,KAAKP,SAASS,OAAON,OAAOC,YAAY;MAAEC,MAAMJ,QAAQI,QAAQ;IAAE,CAAA;EAC3E;AACF;;;AChBO,IAAMK,oBAAN,MAAMA;EATb,OASaA;;;;;EACX,YACmBC,MACAC,QACjB;SAFiBD,OAAAA;SACAC,SAAAA;EAChB;EAEH,MAAMC,SAASC,OAAeC,UAA2B,CAAC,GAAuB;AAC/E,WAAO,KAAKJ,KAAKE,SAASC,OAAO;MAC/B,GAAGC;MACHH,QAAQ;QAAE,GAAGG,QAAQH;QAAQ,GAAG,KAAKA;MAAO;IAC9C,CAAA;EACF;AACF;;;ACSO,SAASI,eACdC,WACAC,UAAwB,CAAC,GAAC;AAE1B,QAAMC,SAAwB,CAAA;AAC9B,aAAWC,YAAYH,WAAW;AAChCI,cAAUD,SAASE,MAAMJ,OAAAA,EAASK,QAAQ,CAACD,MAAME,UAAAA;AAC/CL,aAAOM,KAAK;QACVC,IAAI,GAAGN,SAASM,EAAE,IAAIF,KAAAA;QACtBF;QACA,GAAIF,SAASO,WAAWC,SAAY;UAAED,QAAQP,SAASO;QAAO,IAAI,CAAC;QACnE,GAAIP,SAASS,aAAaD,SAAY;UAAEC,UAAUT,SAASS;QAAS,IAAI,CAAC;MAC3E,CAAA;IACF,CAAA;EACF;AACA,SAAOV;AACT;AAhBgBH;AAmBhB,eAAsBc,aACpBX,QACAD,SAA4B;AAE5B,MAAIC,OAAOY,WAAW,GAAG;AACvB,WAAO;EACT;AACA,QAAMC,aAAa,MAAMd,QAAQe,SAASC,MAAMf,OAAOgB,IAAI,CAACC,UAAUA,MAAMd,IAAI,CAAA;AAChF,QAAMe,UAA0BlB,OAAOgB,IAAI,CAACC,OAAOZ,WAAW;IAC5D,GAAGY;IACHE,WAAWN,WAAWR,KAAAA,KAAU,CAAA;EAClC,EAAA;AACA,QAAMN,QAAQqB,MAAMC,OAAOH,OAAAA;AAC3B,SAAOA,QAAQN;AACjB;AAdsBD;AAkBtB,eAAsBW,gBACpBxB,WACAC,SAAsB;AAEtB,SAAOY,aAAad,eAAeC,WAAWC,OAAAA,GAAUA,OAAAA;AAC1D;AALsBuB;;;ACpDtB,SAASC,aAAaC,OAAc;AAClC,SACE,OAAOA,UAAU,YACjBA,UAAU,QACV,WAAWA,SACX,OAAQA,MAAmCC,UAAU;AAEzD;AAPSF;AAUT,IAAMG,cAAmD;EACvD,aAAa;IACXC,SAAS;IACTC,QAAQ;IACRC,SAASL,OAAc;AACrB,aAAOD,aAAaC,KAAAA,IAChB;QAAEA,OAAO;UAAEC,OAAOD,MAAMC;QAAM;MAAE,IAChC;QAAEK,QAAQ;UAAC;YAAEC,SAAS;UAA2C;;MAAG;IAC1E;EACF;AACF;AAWO,SAASC,oBACdC,WACAC,UAAgC,CAAC,GAAC;AAElC,QAAMC,OAAOD,QAAQC;AACrB,SAAO;IACLC,MAAM;MACJC,MAAMH,QAAQG,QAAQ;MACtBC,MAAM;MACNC,aACEL,QAAQK,eACR;MACFC,aAAad;IACf;IACAe,SAAS;MACP,MAAMC,QAAQC,OAAc;AAC1B,YAAI,CAACpB,aAAaoB,KAAAA,GAAQ;AACxB,gBAAM,IAAIC,MAAM,0CAAA;QAClB;AACA,cAAMC,WAAW,MAAMZ,UAAUa,SAASH,MAAMlB,OAAOU,SAASY,SAAY;UAAEZ;QAAK,IAAI,CAAC,CAAA;AACxF,eAAO;UAAEU;QAAS;MACpB;IACF;EACF;AACF;AAxBgBb;","names":["chunkText","text","options","chunkSize","overlap","normalized","trim","length","chunks","start","end","Math","min","breakAt","window","slice","boundary","max","lastIndexOf","chunk","push","documentIdOf","chunkId","replace","matchesFilter","metadata","filter","entries","Object","undefined","length","every","key","expected","Array","isArray","actual","some","value","includes","MemoryVectorStore","records","Map","upsert","record","set","id","remove","documentId","keys","documentIdOf","delete","listDocuments","filter","documents","values","undefined","matchesFilter","metadata","has","search","embedding","options","scored","push","text","score","cosineSimilarity","source","sort","a","b","slice","topK","dot","normA","normB","length","Math","min","index","valueA","valueB","denominator","sqrt","PgVectorStore","table","dimensions","client","options","ensureSchema","query","upsert","records","record","id","text","source","metadata","undefined","JSON","stringify","toVectorLiteral","embedding","remove","documentId","DOCUMENT_ID_FROM_CHUNK","listDocuments","filter","where","buildWhere","rows","sql","params","map","row","doc_id","search","vector","topK","score","Number","startIndex","Object","keys","length","clauses","scalar","index","key","value","entries","Array","isArray","push","keyParam","arrParam","String","join","RedisVectorStore","index","prefix","dimensions","filterableFields","client","options","ensureSchema","sendCommand","schema","field","push","String","upsert","records","record","args","id","text","encodeVector","embedding","source","undefined","metadata","JSON","stringify","value","tag","Array","isArray","map","join","remove","documentId","keys","Set","pattern","escapeGlob","cursor","reply","page","parseScanReply","key","add","size","listDocuments","filter","filterMatchesNothing","query","buildFilter","documents","Map","batchSize","offset","passages","parseSearchReply","passage","documentIdOf","has","set","length","values","search","topK","Buffer","from","Float32Array","buffer","Object","clauses","entries","alternation","entry","escapeTag","some","replace","char","rawKeys","toStr","isBuffer","toString","rawFields","attrs","toPassage","results","result","readId","readAttributes","raw","extra_attributes","rawId","distance","Number","vector_score","startsWith","slice","score","parseMetadata","metadata_json","parsed","parse","EmbeddingRetriever","embedder","store","retrieve","query","options","embedding","embed","undefined","search","topK","filter","KeywordRetriever","docs","Map","documentFrequency","totalLength","k1","b","options","add","records","record","remove","id","tokens","tokenize","text","frequencies","token","set","get","term","keys","length","existing","undefined","next","delete","retrieve","query","total","size","averageLength","queryTerms","Set","scored","values","filter","matchesFilter","metadata","score","termFrequency","df","idf","Math","log","denominator","push","source","sort","a","slice","topK","toLowerCase","match","HybridRetriever","retrievers","options","retrieve","query","k","fetchTopK","weights","lists","Promise","all","map","retriever","topK","filter","undefined","fused","Map","forEach","list","listIndex","weight","passage","rank","contribution","existing","get","id","score","set","values","sort","a","b","slice","entry","RerankingRetriever","base","reranker","options","retrieve","query","candidates","topK","fetchTopK","filter","undefined","rerank","FilteredRetriever","base","filter","retrieve","query","options","chunkDocuments","documents","options","chunks","document","chunkText","text","forEach","index","push","id","source","undefined","metadata","ingestChunks","length","embeddings","embedder","embed","map","chunk","records","embedding","store","upsert","ingestDocuments","isQueryInput","value","query","querySchema","version","vendor","validate","issues","message","createRetrievalTool","retriever","options","topK","spec","name","kind","description","inputSchema","handler","execute","input","Error","passages","retrieve","undefined"]}
package/dist/index.js CHANGED
@@ -47,10 +47,17 @@ __name(documentIdOf, "documentIdOf");
47
47
 
48
48
  // src/filter.ts
49
49
  function matchesFilter(metadata, filter) {
50
+ const entries = Object.entries(filter);
50
51
  if (metadata === void 0) {
51
- return Object.keys(filter).length === 0;
52
+ return entries.length === 0;
52
53
  }
53
- return Object.entries(filter).every(([key, value]) => metadata[key] === value);
54
+ return entries.every(([key, expected]) => {
55
+ if (Array.isArray(expected)) {
56
+ const actual = metadata[key];
57
+ return Array.isArray(actual) ? actual.some((value) => expected.includes(value)) : expected.includes(actual);
58
+ }
59
+ return metadata[key] === expected;
60
+ });
54
61
  }
55
62
  __name(matchesFilter, "matchesFilter");
56
63
 
@@ -179,13 +186,11 @@ var PgVectorStore = class {
179
186
  ]);
180
187
  }
181
188
  async listDocuments(filter) {
182
- const hasFilter = filter !== void 0 && Object.keys(filter).length > 0;
189
+ const where = buildWhere(filter, 1);
183
190
  const rows = await this.client.query(`SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata
184
191
  FROM ${this.table}
185
- ${hasFilter ? "WHERE metadata @> $1::jsonb" : ""}
186
- ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`, hasFilter ? [
187
- JSON.stringify(filter)
188
- ] : []);
192
+ ${where.sql}
193
+ ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`, where.params);
189
194
  return rows.map((row) => ({
190
195
  id: row.doc_id,
191
196
  ...row.metadata !== null ? {
@@ -195,18 +200,15 @@ var PgVectorStore = class {
195
200
  }
196
201
  async search(embedding, options) {
197
202
  const vector = toVectorLiteral(embedding);
198
- const hasFilter = options.filter !== void 0 && Object.keys(options.filter).length > 0;
203
+ const where = buildWhere(options.filter, 3);
199
204
  const rows = await this.client.query(`SELECT id, text, source, metadata, 1 - (embedding <=> $1::vector) AS score
200
205
  FROM ${this.table}
201
- ${hasFilter ? "WHERE metadata @> $3::jsonb" : ""}
206
+ ${where.sql}
202
207
  ORDER BY embedding <=> $1::vector
203
- LIMIT $2`, hasFilter ? [
208
+ LIMIT $2`, [
204
209
  vector,
205
210
  options.topK,
206
- JSON.stringify(options.filter)
207
- ] : [
208
- vector,
209
- options.topK
211
+ ...where.params
210
212
  ]);
211
213
  return rows.map((row) => ({
212
214
  id: row.id,
@@ -221,6 +223,45 @@ var PgVectorStore = class {
221
223
  }));
222
224
  }
223
225
  };
226
+ function buildWhere(filter, startIndex) {
227
+ if (filter === void 0 || Object.keys(filter).length === 0) {
228
+ return {
229
+ sql: "",
230
+ params: []
231
+ };
232
+ }
233
+ const clauses = [];
234
+ const params = [];
235
+ const scalar = {};
236
+ let index = startIndex;
237
+ for (const [key, value] of Object.entries(filter)) {
238
+ if (Array.isArray(value)) {
239
+ if (value.length === 0) {
240
+ clauses.push("false");
241
+ continue;
242
+ }
243
+ const keyParam = `$${index}`;
244
+ params.push(key);
245
+ index += 1;
246
+ const arrParam = `$${index}`;
247
+ params.push(value.map(String));
248
+ index += 1;
249
+ clauses.push(`(CASE WHEN jsonb_typeof(metadata->${keyParam}) = 'array' THEN metadata->${keyParam} ELSE jsonb_build_array(metadata->${keyParam}) END) ?| ${arrParam}::text[]`);
250
+ } else {
251
+ scalar[key] = value;
252
+ }
253
+ }
254
+ if (Object.keys(scalar).length > 0) {
255
+ clauses.push(`metadata @> $${index}::jsonb`);
256
+ params.push(JSON.stringify(scalar));
257
+ index += 1;
258
+ }
259
+ return {
260
+ sql: `WHERE ${clauses.join(" AND ")}`,
261
+ params
262
+ };
263
+ }
264
+ __name(buildWhere, "buildWhere");
224
265
  var DOCUMENT_ID_FROM_CHUNK = "regexp_replace(id, '#[0-9]+$', '')";
225
266
  function toVectorLiteral(embedding) {
226
267
  return `[${embedding.join(",")}]`;
@@ -297,7 +338,8 @@ var RedisVectorStore = class {
297
338
  for (const field of this.filterableFields) {
298
339
  const value = record.metadata[field];
299
340
  if (value !== void 0) {
300
- args.push(`meta_${field}`, String(value));
341
+ const tag = Array.isArray(value) ? value.map(String).join(",") : String(value);
342
+ args.push(`meta_${field}`, tag);
301
343
  }
302
344
  }
303
345
  }
@@ -333,6 +375,9 @@ var RedisVectorStore = class {
333
375
  }
334
376
  }
335
377
  async listDocuments(filter) {
378
+ if (filterMatchesNothing(filter)) {
379
+ return [];
380
+ }
336
381
  const query = buildFilter(filter);
337
382
  const documents = /* @__PURE__ */ new Map();
338
383
  const batchSize = 1e3;
@@ -373,6 +418,9 @@ var RedisVectorStore = class {
373
418
  ];
374
419
  }
375
420
  async search(embedding, options) {
421
+ if (filterMatchesNothing(options.filter)) {
422
+ return [];
423
+ }
376
424
  const query = `${buildFilter(options.filter)}=>[KNN ${options.topK} @embedding $BLOB AS vector_score]`;
377
425
  const reply = await this.client.sendCommand([
378
426
  "FT.SEARCH",
@@ -408,10 +456,23 @@ function buildFilter(filter) {
408
456
  if (filter === void 0 || Object.keys(filter).length === 0) {
409
457
  return "*";
410
458
  }
411
- const clauses = Object.entries(filter).map(([key, value]) => `@meta_${key}:{${escapeTag(String(value))}}`);
459
+ const clauses = Object.entries(filter).map(([key, value]) => {
460
+ const values = Array.isArray(value) ? value : [
461
+ value
462
+ ];
463
+ const alternation = values.map((entry) => escapeTag(String(entry))).join("|");
464
+ return `@meta_${key}:{${alternation}}`;
465
+ });
412
466
  return `(${clauses.join(" ")})`;
413
467
  }
414
468
  __name(buildFilter, "buildFilter");
469
+ function filterMatchesNothing(filter) {
470
+ if (filter === void 0) {
471
+ return false;
472
+ }
473
+ return Object.values(filter).some((value) => Array.isArray(value) && value.length === 0);
474
+ }
475
+ __name(filterMatchesNothing, "filterMatchesNothing");
415
476
  function escapeTag(value) {
416
477
  return value.replace(/[^a-zA-Z0-9_]/g, (char) => `\\${char}`);
417
478
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/chunk.ts","../src/vector-store.ts","../src/filter.ts","../src/memory-vector-store.ts","../src/pg-vector-store.ts","../src/redis-vector-store.ts","../src/embedding-retriever.ts","../src/keyword-retriever.ts","../src/hybrid-retriever.ts","../src/reranking-retriever.ts","../src/filtered-retriever.ts","../src/ingest.ts","../src/retrieval-tool.ts"],"sourcesContent":["export interface ChunkOptions {\n /** Target max characters per chunk. Default 800. */\n chunkSize?: number;\n /** Characters of overlap carried from the end of one chunk into the next. Default 100. */\n overlap?: number;\n}\n\n/**\n * Splits text into overlapping chunks for embedding. Greedy up to `chunkSize`, but prefers to break\n * on a paragraph → sentence → word boundary in the back half of the window (so a chunk rarely cuts\n * mid-sentence), then carries `overlap` characters into the next chunk to preserve context across\n * the seam. Pure and deterministic.\n */\nexport function chunkText(text: string, options: ChunkOptions = {}): string[] {\n const chunkSize = options.chunkSize ?? 800;\n const overlap = options.overlap ?? 100;\n const normalized = text.trim();\n if (normalized.length === 0) {\n return [];\n }\n if (normalized.length <= chunkSize) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n let start = 0;\n while (start < normalized.length) {\n const end = Math.min(start + chunkSize, normalized.length);\n let breakAt = end;\n if (end < normalized.length) {\n const window = normalized.slice(start, end);\n const boundary = Math.max(\n window.lastIndexOf('\\n\\n'),\n window.lastIndexOf('. '),\n window.lastIndexOf(' '),\n );\n // Only honour a boundary in the back half, else a tiny chunk cascades.\n if (boundary > chunkSize / 2) {\n breakAt = start + boundary + 1;\n }\n }\n const chunk = normalized.slice(start, breakAt).trim();\n if (chunk.length > 0) {\n chunks.push(chunk);\n }\n if (breakAt >= normalized.length) {\n break;\n }\n // Step forward with overlap, but always make progress (guards against a stuck boundary).\n start = Math.max(breakAt - overlap, start + 1);\n }\n return chunks;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\n\n/** A stored, embedded chunk. `embedding` length must match the store's configured dimensions. */\nexport interface VectorRecord {\n id: string;\n text: string;\n embedding: number[];\n /** Citation-facing origin (document title, URL, row id). */\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface VectorSearchOptions {\n topK: number;\n /** Exact-match metadata filter (all keys must equal). Passed through from `RetrieveOptions.filter`. */\n filter?: Record<string, unknown>;\n}\n\n/**\n * The write + search side of RAG storage. `@dudousxd/nestjs-agent-rag` ships `MemoryVectorStore`\n * (in-JS cosine, tests + small scale) and `PgVectorStore` (pgvector); any impl works. Pair one with\n * an `EmbeddingProvider` via {@link import('./embedding-retriever.js').EmbeddingRetriever} to get a\n * core `Retriever`.\n */\nexport interface VectorStore {\n upsert(records: VectorRecord[]): Promise<void>;\n search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;\n /**\n * Delete every chunk belonging to a source document — all records whose id is `${documentId}`\n * or `${documentId}#<n>` (the id scheme {@link import('./ingest.js').chunkDocuments} produces).\n * Use it to drop a document from the index, and before re-ingesting one: `upsert` overwrites\n * matching ids but can't remove chunks a shorter new version no longer produces, so a re-ingest\n * without a preceding `remove` leaves the old tail orphaned.\n */\n remove(documentId: string): Promise<void>;\n /**\n * List the distinct source documents currently indexed (chunk ids collapsed back to their document\n * by stripping the trailing `#<n>`), each with a representative chunk's `metadata`, optionally\n * narrowed by a metadata `filter`. The enumeration seam for reconciliation: diff this against your\n * source of truth to find documents to ingest (missing), re-ingest (a stored fingerprint like\n * `size` changed), or {@link VectorStore.remove remove} (orphaned).\n */\n listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;\n}\n\n/** A distinct source document as seen by the index — its id plus a representative chunk's metadata. */\nexport interface IndexedDocument {\n id: string;\n metadata?: Record<string, unknown>;\n}\n\n/** Collapse a chunk id (`${documentId}#<n>`) back to its source document id. */\nexport function documentIdOf(chunkId: string): string {\n return chunkId.replace(/#\\d+$/, '');\n}\n","/** Exact-match metadata filter (every key in `filter` must equal the record's). Shared by the\n * in-memory {@link import('./memory-vector-store.js').MemoryVectorStore} and\n * {@link import('./keyword-retriever.js').KeywordRetriever}. */\nexport function matchesFilter(\n metadata: Record<string, unknown> | undefined,\n filter: Record<string, unknown>,\n): boolean {\n if (metadata === undefined) {\n return Object.keys(filter).length === 0;\n }\n return Object.entries(filter).every(([key, value]) => metadata[key] === value);\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport { matchesFilter } from './filter.js';\nimport {\n type IndexedDocument,\n type VectorRecord,\n type VectorSearchOptions,\n type VectorStore,\n documentIdOf,\n} from './vector-store.js';\n\n/**\n * An in-process {@link VectorStore} — cosine similarity over a Map, no infra. The reference adapter\n * for tests and small/embedded corpora; for production scale use `PgVectorStore` (or your own).\n */\nexport class MemoryVectorStore implements VectorStore {\n private readonly records = new Map<string, VectorRecord>();\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n this.records.set(record.id, record);\n }\n }\n\n async remove(documentId: string): Promise<void> {\n for (const id of this.records.keys()) {\n if (documentIdOf(id) === documentId) {\n this.records.delete(id);\n }\n }\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const documents = new Map<string, IndexedDocument>();\n for (const record of this.records.values()) {\n if (filter !== undefined && !matchesFilter(record.metadata, filter)) {\n continue;\n }\n const id = documentIdOf(record.id);\n if (!documents.has(id)) {\n documents.set(id, {\n id,\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n }\n return [...documents.values()];\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const scored: Passage[] = [];\n for (const record of this.records.values()) {\n if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n scored.push({\n id: record.id,\n text: record.text,\n score: cosineSimilarity(embedding, record.embedding),\n ...(record.source !== undefined ? { source: record.source } : {}),\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n return scored.sort((a, b) => b.score - a.score).slice(0, options.topK);\n }\n}\n\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n const length = Math.min(a.length, b.length);\n for (let index = 0; index < length; index += 1) {\n const valueA = a[index] ?? 0;\n const valueB = b[index] ?? 0;\n dot += valueA * valueB;\n normA += valueA * valueA;\n normB += valueB * valueB;\n }\n const denominator = Math.sqrt(normA) * Math.sqrt(normB);\n return denominator === 0 ? 0 : dot / denominator;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport type {\n IndexedDocument,\n VectorRecord,\n VectorSearchOptions,\n VectorStore,\n} from './vector-store.js';\n\n/**\n * The minimal Postgres surface {@link PgVectorStore} needs — adapt your own `pg` / `postgres.js`\n * client to it, so this package pulls in NO driver (bring your own, like the store adapters take an\n * ORM handle and the Redis transport takes a client). `query` runs parameterized SQL (`$1,$2,…`) and\n * resolves the result rows.\n */\nexport interface PgClient {\n query<Row = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<Row[]>;\n}\n\nexport interface PgVectorStoreOptions {\n /** Table name. Default `agent_rag_chunks`. */\n table?: string;\n /** Embedding width — must match your model (e.g. 1536 for text-embedding-3-small). Default 1536. */\n dimensions?: number;\n}\n\n/**\n * A pgvector-backed {@link VectorStore} — the production reference adapter. Cosine distance via the\n * `<=>` operator over an HNSW index; metadata in a `jsonb` column filtered with `@>`. Call\n * {@link PgVectorStore.ensureSchema} once at boot to create the extension, table, and index.\n */\nexport class PgVectorStore implements VectorStore {\n private readonly table: string;\n private readonly dimensions: number;\n\n constructor(\n private readonly client: PgClient,\n options: PgVectorStoreOptions = {},\n ) {\n this.table = options.table ?? 'agent_rag_chunks';\n this.dimensions = options.dimensions ?? 1536;\n }\n\n /** Idempotent DDL: the `vector` extension, the chunks table, and the cosine HNSW index. */\n async ensureSchema(): Promise<void> {\n await this.client.query('CREATE EXTENSION IF NOT EXISTS vector');\n await this.client.query(\n `CREATE TABLE IF NOT EXISTS ${this.table} (\n id TEXT PRIMARY KEY,\n text TEXT NOT NULL,\n source TEXT,\n metadata JSONB,\n embedding vector(${this.dimensions}) NOT NULL\n )`,\n );\n await this.client.query(\n `CREATE INDEX IF NOT EXISTS ${this.table}_embedding_idx\n ON ${this.table} USING hnsw (embedding vector_cosine_ops)`,\n );\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n await this.client.query(\n `INSERT INTO ${this.table} (id, text, source, metadata, embedding)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (id) DO UPDATE SET\n text = EXCLUDED.text,\n source = EXCLUDED.source,\n metadata = EXCLUDED.metadata,\n embedding = EXCLUDED.embedding`,\n [\n record.id,\n record.text,\n record.source ?? null,\n record.metadata !== undefined ? JSON.stringify(record.metadata) : null,\n toVectorLiteral(record.embedding),\n ],\n );\n }\n }\n\n async remove(documentId: string): Promise<void> {\n await this.client.query(`DELETE FROM ${this.table} WHERE ${DOCUMENT_ID_FROM_CHUNK} = $1`, [\n documentId,\n ]);\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const hasFilter = filter !== undefined && Object.keys(filter).length > 0;\n // DISTINCT ON collapses chunks to one row per document; all chunks share the doc's metadata.\n const rows = await this.client.query<{\n doc_id: string;\n metadata: Record<string, unknown> | null;\n }>(\n `SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata\n FROM ${this.table}\n ${hasFilter ? 'WHERE metadata @> $1::jsonb' : ''}\n ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`,\n hasFilter ? [JSON.stringify(filter)] : [],\n );\n return rows.map((row) => ({\n id: row.doc_id,\n ...(row.metadata !== null ? { metadata: row.metadata } : {}),\n }));\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const vector = toVectorLiteral(embedding);\n const hasFilter = options.filter !== undefined && Object.keys(options.filter).length > 0;\n const rows = await this.client.query<PgRow>(\n `SELECT id, text, source, metadata, 1 - (embedding <=> $1::vector) AS score\n FROM ${this.table}\n ${hasFilter ? 'WHERE metadata @> $3::jsonb' : ''}\n ORDER BY embedding <=> $1::vector\n LIMIT $2`,\n hasFilter ? [vector, options.topK, JSON.stringify(options.filter)] : [vector, options.topK],\n );\n return rows.map((row) => ({\n id: row.id,\n text: row.text,\n score: Number(row.score),\n ...(row.source !== null ? { source: row.source } : {}),\n ...(row.metadata !== null ? { metadata: row.metadata } : {}),\n }));\n }\n}\n\ninterface PgRow {\n id: string;\n text: string;\n source: string | null;\n metadata: Record<string, unknown> | null;\n score: number | string;\n}\n\n/**\n * SQL that collapses a chunk id (`${documentId}#<n>`) back to its source document id — the pgvector\n * mirror of {@link import('./vector-store.js').documentIdOf}. Shared by `remove` and\n * `listDocuments` so both key on the exact same definition of \"chunk belongs to document\".\n */\nconst DOCUMENT_ID_FROM_CHUNK = \"regexp_replace(id, '#[0-9]+$', '')\";\n\n/** pgvector accepts a `'[1,2,3]'` text literal cast to `vector`. */\nfunction toVectorLiteral(embedding: number[]): string {\n return `[${embedding.join(',')}]`;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport {\n type IndexedDocument,\n type VectorRecord,\n type VectorSearchOptions,\n type VectorStore,\n documentIdOf,\n} from './vector-store.js';\n\n/**\n * The minimal Redis surface {@link RedisVectorStore} needs: send a raw command and get the reply.\n * Both `node-redis` (`client.sendCommand(args)`) and `ioredis` (wrap `redis.call`) satisfy it — so\n * this package pulls in NO Redis driver (bring your own, like the other adapters). Requires the\n * RediSearch module (Redis Stack / Redis 8+).\n */\nexport interface RedisSearchClient {\n sendCommand(args: (string | Buffer)[]): Promise<unknown>;\n}\n\nexport interface RedisVectorStoreOptions {\n /** RediSearch index name. Default `agent_rag_idx`. */\n index?: string;\n /** Key prefix for the per-chunk hashes. Default `agent_rag:`. */\n prefix?: string;\n /** Embedding width — must match your model (e.g. 1536). Default 1536. */\n dimensions?: number;\n /**\n * Metadata keys to index as filterable TAG fields. RediSearch needs filter fields declared up\n * front, so only keys listed here can be passed in `search`'s `filter`. Default none.\n */\n filterableFields?: string[];\n}\n\n/**\n * A RediSearch-backed {@link VectorStore} — HNSW + cosine over Redis, the ecosystem-native option\n * for anyone already running Redis (see `-transport-redis`). Chunks are hashes under `prefix`, the\n * embedding a FLOAT32 vector field; `search` is a KNN query. Call {@link RedisVectorStore.ensureSchema}\n * once at boot to create the index.\n */\nexport class RedisVectorStore implements VectorStore {\n private readonly index: string;\n private readonly prefix: string;\n private readonly dimensions: number;\n private readonly filterableFields: string[];\n\n constructor(\n private readonly client: RedisSearchClient,\n options: RedisVectorStoreOptions = {},\n ) {\n this.index = options.index ?? 'agent_rag_idx';\n this.prefix = options.prefix ?? 'agent_rag:';\n this.dimensions = options.dimensions ?? 1536;\n this.filterableFields = options.filterableFields ?? [];\n }\n\n /** Idempotent: create the HNSW/cosine index if `FT.INFO` says it doesn't exist yet. */\n async ensureSchema(): Promise<void> {\n try {\n await this.client.sendCommand(['FT.INFO', this.index]);\n return;\n } catch {\n // index missing → create it below\n }\n const schema: string[] = ['text', 'TEXT', 'source', 'TAG', 'metadata_json', 'TEXT', 'NOINDEX'];\n for (const field of this.filterableFields) {\n schema.push(`meta_${field}`, 'TAG');\n }\n schema.push(\n 'embedding',\n 'VECTOR',\n 'HNSW',\n '6',\n 'TYPE',\n 'FLOAT32',\n 'DIM',\n String(this.dimensions),\n 'DISTANCE_METRIC',\n 'COSINE',\n );\n await this.client.sendCommand([\n 'FT.CREATE',\n this.index,\n 'ON',\n 'HASH',\n 'PREFIX',\n '1',\n this.prefix,\n 'SCHEMA',\n ...schema,\n ]);\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n const args: (string | Buffer)[] = [\n 'HSET',\n `${this.prefix}${record.id}`,\n 'text',\n record.text,\n 'embedding',\n encodeVector(record.embedding),\n ];\n if (record.source !== undefined) {\n args.push('source', record.source);\n }\n if (record.metadata !== undefined) {\n args.push('metadata_json', JSON.stringify(record.metadata));\n for (const field of this.filterableFields) {\n const value = record.metadata[field];\n if (value !== undefined) {\n args.push(`meta_${field}`, String(value));\n }\n }\n }\n await this.client.sendCommand(args);\n }\n }\n\n async remove(documentId: string): Promise<void> {\n const keys = new Set<string>([`${this.prefix}${documentId}`]);\n const pattern = `${this.prefix}${escapeGlob(documentId)}#*`;\n let cursor = '0';\n do {\n const reply = await this.client.sendCommand([\n 'SCAN',\n cursor,\n 'MATCH',\n pattern,\n 'COUNT',\n '256',\n ]);\n const page = parseScanReply(reply);\n cursor = page.cursor;\n for (const key of page.keys) {\n keys.add(key);\n }\n } while (cursor !== '0');\n if (keys.size > 0) {\n await this.client.sendCommand(['DEL', ...keys]);\n }\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const query = buildFilter(filter);\n const documents = new Map<string, IndexedDocument>();\n const batchSize = 1000;\n let offset = 0;\n for (;;) {\n // RETURN metadata_json (not NOCONTENT) so we can carry a representative chunk's metadata;\n // parseSearchReply already yields prefix-stripped ids + parsed metadata for both RESP2/RESP3.\n const reply = await this.client.sendCommand([\n 'FT.SEARCH',\n this.index,\n query,\n 'RETURN',\n '1',\n 'metadata_json',\n 'LIMIT',\n String(offset),\n String(batchSize),\n 'DIALECT',\n '2',\n ]);\n const passages = parseSearchReply(reply, this.prefix);\n for (const passage of passages) {\n const id = documentIdOf(passage.id);\n if (!documents.has(id)) {\n documents.set(id, {\n id,\n ...(passage.metadata !== undefined ? { metadata: passage.metadata } : {}),\n });\n }\n }\n if (passages.length < batchSize) {\n break;\n }\n offset += batchSize;\n }\n return [...documents.values()];\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const query = `${buildFilter(options.filter)}=>[KNN ${options.topK} @embedding $BLOB AS vector_score]`;\n const reply = await this.client.sendCommand([\n 'FT.SEARCH',\n this.index,\n query,\n 'PARAMS',\n '2',\n 'BLOB',\n encodeVector(embedding),\n 'RETURN',\n '4',\n 'text',\n 'source',\n 'metadata_json',\n 'vector_score',\n 'SORTBY',\n 'vector_score',\n 'ASC',\n 'DIALECT',\n '2',\n 'LIMIT',\n '0',\n String(options.topK),\n ]);\n return parseSearchReply(reply, this.prefix);\n }\n}\n\n/** RediSearch expects a little-endian FLOAT32 buffer for the query/stored vector. */\nfunction encodeVector(embedding: number[]): Buffer {\n return Buffer.from(new Float32Array(embedding).buffer);\n}\n\n/** `*` for no filter, else an AND of TAG clauses like `(@meta_owner:{u1} @meta_tenant:{t1})`. */\nfunction buildFilter(filter?: Record<string, unknown>): string {\n if (filter === undefined || Object.keys(filter).length === 0) {\n return '*';\n }\n const clauses = Object.entries(filter).map(\n ([key, value]) => `@meta_${key}:{${escapeTag(String(value))}}`,\n );\n return `(${clauses.join(' ')})`;\n}\n\n/** Escape RediSearch TAG punctuation so an id/tenant value matches literally. */\nfunction escapeTag(value: string): string {\n return value.replace(/[^a-zA-Z0-9_]/g, (char) => `\\\\${char}`);\n}\n\n/** Escape `SCAN MATCH` glob metacharacters so a document id matches its chunk keys literally. */\nfunction escapeGlob(value: string): string {\n return value.replace(/[*?[\\]\\\\]/g, (char) => `\\\\${char}`);\n}\n\n/**\n * Parse a `SCAN` reply into `{ cursor, keys }`, tolerating both wire shapes: the RESP2 array\n * `[cursor, [key, …]]` and node-redis's RESP3 object `{ cursor, keys }`.\n */\nfunction parseScanReply(reply: unknown): { cursor: string; keys: string[] } {\n if (Array.isArray(reply) && reply.length >= 2) {\n const rawKeys = reply[1];\n return {\n cursor: toStr(reply[0]),\n keys: Array.isArray(rawKeys) ? rawKeys.map(toStr) : [],\n };\n }\n if (typeof reply === 'object' && reply !== null && 'cursor' in reply && 'keys' in reply) {\n const { cursor, keys } = reply as { cursor: unknown; keys: unknown };\n return {\n cursor: toStr(cursor),\n keys: Array.isArray(keys) ? keys.map(toStr) : [],\n };\n }\n return { cursor: '0', keys: [] };\n}\n\nfunction toStr(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);\n}\n\n/**\n * Parse an `FT.SEARCH` reply into passages, tolerating both wire shapes: the RESP2 array\n * `[total, key, [f, v, …], …]` (ioredis / node-redis RESP2) and the RESP3 object\n * `{ results: [{ id, extra_attributes: { … } }] }` (node-redis's default). Distance → similarity\n * via `1 - distance` (COSINE), and the `prefix` is stripped back off the key to recover the chunk id.\n */\nfunction parseSearchReply(reply: unknown, prefix: string): Passage[] {\n if (Array.isArray(reply)) {\n const passages: Passage[] = [];\n for (let index = 1; index + 1 < reply.length; index += 2) {\n const rawFields = reply[index + 1];\n if (Array.isArray(rawFields)) {\n const attrs: Record<string, string> = {};\n for (let field = 0; field + 1 < rawFields.length; field += 2) {\n attrs[toStr(rawFields[field])] = toStr(rawFields[field + 1]);\n }\n passages.push(toPassage(toStr(reply[index]), attrs, prefix));\n }\n }\n return passages;\n }\n if (typeof reply === 'object' && reply !== null && 'results' in reply) {\n const results = (reply as { results: unknown }).results;\n if (Array.isArray(results)) {\n return results.map((result) => toPassage(readId(result), readAttributes(result), prefix));\n }\n }\n return [];\n}\n\nfunction readId(result: unknown): string {\n if (typeof result === 'object' && result !== null && 'id' in result) {\n return toStr((result as { id: unknown }).id);\n }\n return '';\n}\n\nfunction readAttributes(result: unknown): Record<string, string> {\n const attrs: Record<string, string> = {};\n if (typeof result === 'object' && result !== null && 'extra_attributes' in result) {\n const raw = (result as { extra_attributes: unknown }).extra_attributes;\n if (typeof raw === 'object' && raw !== null) {\n for (const [key, value] of Object.entries(raw)) {\n attrs[key] = toStr(value);\n }\n }\n }\n return attrs;\n}\n\nfunction toPassage(rawId: string, attrs: Record<string, string>, prefix: string): Passage {\n const distance = Number(attrs.vector_score ?? '1');\n const source = attrs.source;\n return {\n id: rawId.startsWith(prefix) ? rawId.slice(prefix.length) : rawId,\n text: attrs.text ?? '',\n score: 1 - distance,\n ...(source !== undefined ? { source } : {}),\n ...parseMetadata(attrs.metadata_json),\n };\n}\n\nfunction parseMetadata(raw: string | undefined): { metadata?: Record<string, unknown> } {\n if (raw === undefined) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed === 'object' && parsed !== null) {\n return { metadata: parsed as Record<string, unknown> };\n }\n } catch {\n // not JSON — no metadata\n }\n return {};\n}\n","import type {\n EmbeddingProvider,\n Passage,\n RetrieveOptions,\n Retriever,\n} from '@dudousxd/nestjs-agent-core';\nimport type { VectorStore } from './vector-store.js';\n\n/**\n * Bridges an {@link EmbeddingProvider} + {@link VectorStore} into the core {@link Retriever} SPI:\n * embed the query, then vector-search. This is what you wire as the agent's retriever (as a tool\n * via `createRetrievalTool`, or into `AgentModule.forRoot({ retrieval })` for inject mode).\n */\nexport class EmbeddingRetriever implements Retriever {\n constructor(\n private readonly embedder: EmbeddingProvider,\n private readonly store: VectorStore,\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const [embedding] = await this.embedder.embed([query]);\n if (embedding === undefined) {\n return [];\n }\n return this.store.search(embedding, {\n topK: options.topK ?? 5,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n });\n }\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\nimport { matchesFilter } from './filter.js';\nimport type { ChunkRecord } from './ingest.js';\n\nexport interface KeywordRetrieverOptions {\n /** BM25 term-frequency saturation. Default 1.5. */\n k1?: number;\n /** BM25 length normalization, 0 (off) … 1 (full). Default 0.75. */\n b?: number;\n}\n\ninterface IndexedDoc {\n record: ChunkRecord;\n /** term → frequency within this doc. */\n frequencies: Map<string, number>;\n length: number;\n}\n\n/**\n * An in-memory BM25 lexical retriever — the keyword half of hybrid search. Feed it the SAME\n * {@link ChunkRecord}s you upsert into the vector store (from `chunkDocuments`) so their chunk ids\n * line up and `HybridRetriever` can fuse the two rankings. Full BM25: idf, term-frequency saturation\n * (`k1`), and document-length normalization (`b`).\n */\nexport class KeywordRetriever implements Retriever {\n private readonly docs = new Map<string, IndexedDoc>();\n private readonly documentFrequency = new Map<string, number>();\n private totalLength = 0;\n private readonly k1: number;\n private readonly b: number;\n\n constructor(options: KeywordRetrieverOptions = {}) {\n this.k1 = options.k1 ?? 1.5;\n this.b = options.b ?? 0.75;\n }\n\n /** Index (or re-index) records. Re-adding an existing id replaces its posting and updates stats. */\n add(records: ChunkRecord[]): void {\n for (const record of records) {\n this.remove(record.id);\n const tokens = tokenize(record.text);\n const frequencies = new Map<string, number>();\n for (const token of tokens) {\n frequencies.set(token, (frequencies.get(token) ?? 0) + 1);\n }\n for (const term of frequencies.keys()) {\n this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);\n }\n this.docs.set(record.id, { record, frequencies, length: tokens.length });\n this.totalLength += tokens.length;\n }\n }\n\n private remove(id: string): void {\n const existing = this.docs.get(id);\n if (existing === undefined) {\n return;\n }\n for (const term of existing.frequencies.keys()) {\n const next = (this.documentFrequency.get(term) ?? 1) - 1;\n if (next <= 0) {\n this.documentFrequency.delete(term);\n } else {\n this.documentFrequency.set(term, next);\n }\n }\n this.totalLength -= existing.length;\n this.docs.delete(id);\n }\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const total = this.docs.size;\n if (total === 0) {\n return [];\n }\n const averageLength = this.totalLength / total;\n const queryTerms = new Set(tokenize(query));\n const scored: Passage[] = [];\n for (const { record, frequencies, length } of this.docs.values()) {\n if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n let score = 0;\n for (const term of queryTerms) {\n const termFrequency = frequencies.get(term);\n if (termFrequency === undefined) {\n continue;\n }\n const df = this.documentFrequency.get(term) ?? 0;\n const idf = Math.log(1 + (total - df + 0.5) / (df + 0.5));\n const denominator =\n termFrequency + this.k1 * (1 - this.b + (this.b * length) / averageLength);\n score += idf * ((termFrequency * (this.k1 + 1)) / denominator);\n }\n if (score > 0) {\n scored.push({\n id: record.id,\n text: record.text,\n score,\n ...(record.source !== undefined ? { source: record.source } : {}),\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n }\n return scored.sort((a, b) => b.score - a.score).slice(0, options.topK ?? 5);\n }\n}\n\nfunction tokenize(text: string): string[] {\n return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\nexport interface HybridRetrieverOptions {\n /** RRF constant — larger flattens the rank weighting. Default 60 (the value from the original paper). */\n k?: number;\n /** Candidates pulled from each retriever before fusion. Default 20. */\n fetchTopK?: number;\n /** Per-retriever multipliers on each RRF contribution (same order as `retrievers`). Default all 1. */\n weights?: number[];\n}\n\n/**\n * Fuses several retrievers with Reciprocal Rank Fusion — the robust way to combine, say, dense\n * vector search with lexical BM25. RRF scores each passage by `Σ weight / (k + rank)` across the\n * lists it appears in, so it needs no score normalization between incompatible scales (cosine vs\n * BM25). Deduplicates by passage id, so aligned chunk ids from the two retrievers reinforce.\n */\nexport class HybridRetriever implements Retriever {\n constructor(\n private readonly retrievers: Retriever[],\n private readonly options: HybridRetrieverOptions = {},\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const k = this.options.k ?? 60;\n const fetchTopK = this.options.fetchTopK ?? 20;\n const weights = this.options.weights;\n const lists = await Promise.all(\n this.retrievers.map((retriever) =>\n retriever.retrieve(query, {\n topK: fetchTopK,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n }),\n ),\n );\n\n const fused = new Map<string, { passage: Passage; score: number }>();\n lists.forEach((list, listIndex) => {\n const weight = weights?.[listIndex] ?? 1;\n list.forEach((passage, rank) => {\n const contribution = weight / (k + rank + 1);\n const existing = fused.get(passage.id);\n if (existing !== undefined) {\n existing.score += contribution;\n } else {\n fused.set(passage.id, { passage, score: contribution });\n }\n });\n });\n\n return [...fused.values()]\n .sort((a, b) => b.score - a.score)\n .slice(0, options.topK ?? 5)\n .map((entry) => ({ ...entry.passage, score: entry.score }));\n }\n}\n","import type { Passage, Reranker, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\nexport interface RerankingRetrieverOptions {\n /** How many candidates to pull from the base retriever before reranking. Default 20. */\n fetchTopK?: number;\n}\n\n/**\n * Two-stage retrieval: over-fetch cheap candidates from a base `Retriever`, then reorder them with a\n * stronger {@link Reranker} and keep the top few. The standard precision boost — a fast first stage\n * casts a wide net, a slow accurate second stage sharpens it. Composes over ANY retriever (vector,\n * hybrid, keyword), and is itself a `Retriever`.\n */\nexport class RerankingRetriever implements Retriever {\n constructor(\n private readonly base: Retriever,\n private readonly reranker: Reranker,\n private readonly options: RerankingRetrieverOptions = {},\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const candidates = await this.base.retrieve(query, {\n topK: this.options.fetchTopK ?? 20,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n });\n return this.reranker.rerank(query, candidates, { topK: options.topK ?? 5 });\n }\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\n/**\n * Wraps a `Retriever` so every `retrieve` call ANDs a fixed metadata `filter` on top of whatever the\n * caller passes — the owner/tenant-scoping primitive. `new FilteredRetriever(base, { ownerId })` only\n * ever returns that owner's passages, so a shared vector store serves per-user / per-tenant RAG\n * without the agent (or a crafted query) reaching across the boundary.\n *\n * The fixed filter takes precedence on key conflicts: a caller can narrow further but cannot widen\n * past the scope. Composes over ANY retriever (vector, hybrid, reranked) and is itself a `Retriever`.\n */\nexport class FilteredRetriever implements Retriever {\n constructor(\n private readonly base: Retriever,\n private readonly filter: Record<string, unknown>,\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n return this.base.retrieve(query, {\n ...options,\n filter: { ...options.filter, ...this.filter },\n });\n }\n}\n","import type { EmbeddingProvider } from '@dudousxd/nestjs-agent-core';\nimport { type ChunkOptions, chunkText } from './chunk.js';\nimport type { VectorRecord, VectorStore } from './vector-store.js';\n\n/** A source document to ingest. `id` scopes the chunk ids (`${id}#${n}`); `source` is the citation. */\nexport interface IngestDocument {\n id: string;\n text: string;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\n/** A chunked, not-yet-embedded record. Feed the same array to a vector store AND a keyword index so\n * both retrievers key on identical chunk ids (which is what lets hybrid fusion line them up). */\nexport interface ChunkRecord {\n id: string;\n text: string;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface IngestChunksOptions {\n embedder: EmbeddingProvider;\n store: VectorStore;\n}\n\nexport interface IngestOptions extends ChunkOptions, IngestChunksOptions {}\n\n/**\n * Split documents into chunk records. Chunk ids are `${doc.id}#${index}`, so re-chunking the same\n * document produces the same ids (upsert overwrites in place rather than duplicating).\n */\nexport function chunkDocuments(\n documents: IngestDocument[],\n options: ChunkOptions = {},\n): ChunkRecord[] {\n const chunks: ChunkRecord[] = [];\n for (const document of documents) {\n chunkText(document.text, options).forEach((text, index) => {\n chunks.push({\n id: `${document.id}#${index}`,\n text,\n ...(document.source !== undefined ? { source: document.source } : {}),\n ...(document.metadata !== undefined ? { metadata: document.metadata } : {}),\n });\n });\n }\n return chunks;\n}\n\n/** Embed pre-chunked records (one batched `embed`) and upsert them. Returns the record count. */\nexport async function ingestChunks(\n chunks: ChunkRecord[],\n options: IngestChunksOptions,\n): Promise<number> {\n if (chunks.length === 0) {\n return 0;\n }\n const embeddings = await options.embedder.embed(chunks.map((chunk) => chunk.text));\n const records: VectorRecord[] = chunks.map((chunk, index) => ({\n ...chunk,\n embedding: embeddings[index] ?? [],\n }));\n await options.store.upsert(records);\n return records.length;\n}\n\n/** Chunk → embed → upsert in one call. For hybrid search, use {@link chunkDocuments} +\n * {@link ingestChunks} and feed the same chunks to a {@link import('./keyword-retriever.js').KeywordRetriever}. */\nexport async function ingestDocuments(\n documents: IngestDocument[],\n options: IngestOptions,\n): Promise<number> {\n return ingestChunks(chunkDocuments(documents, options), options);\n}\n","import type { Passage, Retriever, ToolHandler, ToolSpec } from '@dudousxd/nestjs-agent-core';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\nexport interface RetrievalToolOptions {\n /** Tool name the model sees. Default `search_knowledge`. */\n name?: string;\n description?: string;\n /** Passages per search. Default lets the retriever decide (typically 5). */\n topK?: number;\n}\n\n/** A functional tool (`{ spec, handler }`) — pass it to nestjs `provideAgentTool`. */\nexport interface RetrievalTool {\n spec: ToolSpec;\n handler: ToolHandler;\n}\n\nfunction isQueryInput(value: unknown): value is { query: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'query' in value &&\n typeof (value as Record<'query', unknown>).query === 'string'\n );\n}\n\n/** A hand-rolled Standard Schema for `{ query: string }` — keeps this package validation-lib-free. */\nconst querySchema: StandardSchemaV1<{ query: string }> = {\n '~standard': {\n version: 1,\n vendor: 'nestjs-agent-rag',\n validate(value: unknown) {\n return isQueryInput(value)\n ? { value: { query: value.query } }\n : { issues: [{ message: 'retrieval tool expects { query: string }' }] };\n },\n },\n};\n\n/**\n * Builds the agentic-retrieval tool: a `read`-kind `search_knowledge(query)` the model calls when it\n * decides it needs context. Its output `{ passages }` persists as the tool call's result, so sources\n * flow to the frontend and telescope through the normal tool-call machinery (the citation surface).\n *\n * ```ts\n * providers: [provideAgentTool(createRetrievalTool(retriever))]\n * ```\n */\nexport function createRetrievalTool(\n retriever: Retriever,\n options: RetrievalToolOptions = {},\n): RetrievalTool {\n const topK = options.topK;\n return {\n spec: {\n name: options.name ?? 'search_knowledge',\n kind: 'read',\n description:\n options.description ??\n 'Search the knowledge base for passages relevant to a query. Returns passages with their source for citation.',\n inputSchema: querySchema,\n },\n handler: {\n async execute(input: unknown): Promise<{ passages: Passage[] }> {\n if (!isQueryInput(input)) {\n throw new Error('retrieval tool expects { query: string }');\n }\n const passages = await retriever.retrieve(input.query, topK !== undefined ? { topK } : {});\n return { passages };\n },\n },\n };\n}\n"],"mappings":"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","names":["chunkText","text","options","chunkSize","overlap","normalized","trim","length","chunks","start","end","Math","min","breakAt","window","slice","boundary","max","lastIndexOf","chunk","push","documentIdOf","chunkId","replace","matchesFilter","metadata","filter","undefined","Object","keys","length","entries","every","key","value","MemoryVectorStore","records","Map","upsert","record","set","id","remove","documentId","keys","documentIdOf","delete","listDocuments","filter","documents","values","undefined","matchesFilter","metadata","has","search","embedding","options","scored","push","text","score","cosineSimilarity","source","sort","a","b","slice","topK","dot","normA","normB","length","Math","min","index","valueA","valueB","denominator","sqrt","PgVectorStore","table","dimensions","client","options","ensureSchema","query","upsert","records","record","id","text","source","metadata","undefined","JSON","stringify","toVectorLiteral","embedding","remove","documentId","DOCUMENT_ID_FROM_CHUNK","listDocuments","filter","hasFilter","Object","keys","length","rows","map","row","doc_id","search","vector","topK","score","Number","join","RedisVectorStore","index","prefix","dimensions","filterableFields","client","options","ensureSchema","sendCommand","schema","field","push","String","upsert","records","record","args","id","text","encodeVector","embedding","source","undefined","metadata","JSON","stringify","value","remove","documentId","keys","Set","pattern","escapeGlob","cursor","reply","page","parseScanReply","key","add","size","listDocuments","filter","query","buildFilter","documents","Map","batchSize","offset","passages","parseSearchReply","passage","documentIdOf","has","set","length","values","search","topK","Buffer","from","Float32Array","buffer","Object","clauses","entries","map","escapeTag","join","replace","char","Array","isArray","rawKeys","toStr","isBuffer","toString","rawFields","attrs","toPassage","results","result","readId","readAttributes","raw","extra_attributes","rawId","distance","Number","vector_score","startsWith","slice","score","parseMetadata","metadata_json","parsed","parse","EmbeddingRetriever","embedder","store","retrieve","query","options","embedding","embed","undefined","search","topK","filter","KeywordRetriever","docs","Map","documentFrequency","totalLength","k1","b","options","add","records","record","remove","id","tokens","tokenize","text","frequencies","token","set","get","term","keys","length","existing","undefined","next","delete","retrieve","query","total","size","averageLength","queryTerms","Set","scored","values","filter","matchesFilter","metadata","score","termFrequency","df","idf","Math","log","denominator","push","source","sort","a","slice","topK","toLowerCase","match","HybridRetriever","retrievers","options","retrieve","query","k","fetchTopK","weights","lists","Promise","all","map","retriever","topK","filter","undefined","fused","Map","forEach","list","listIndex","weight","passage","rank","contribution","existing","get","id","score","set","values","sort","a","b","slice","entry","RerankingRetriever","base","reranker","options","retrieve","query","candidates","topK","fetchTopK","filter","undefined","rerank","FilteredRetriever","base","filter","retrieve","query","options","chunkDocuments","documents","options","chunks","document","chunkText","text","forEach","index","push","id","source","undefined","metadata","ingestChunks","length","embeddings","embedder","embed","map","chunk","records","embedding","store","upsert","ingestDocuments","isQueryInput","value","query","querySchema","version","vendor","validate","issues","message","createRetrievalTool","retriever","options","topK","spec","name","kind","description","inputSchema","handler","execute","input","Error","passages","retrieve","undefined"]}
1
+ {"version":3,"sources":["../src/chunk.ts","../src/vector-store.ts","../src/filter.ts","../src/memory-vector-store.ts","../src/pg-vector-store.ts","../src/redis-vector-store.ts","../src/embedding-retriever.ts","../src/keyword-retriever.ts","../src/hybrid-retriever.ts","../src/reranking-retriever.ts","../src/filtered-retriever.ts","../src/ingest.ts","../src/retrieval-tool.ts"],"sourcesContent":["export interface ChunkOptions {\n /** Target max characters per chunk. Default 800. */\n chunkSize?: number;\n /** Characters of overlap carried from the end of one chunk into the next. Default 100. */\n overlap?: number;\n}\n\n/**\n * Splits text into overlapping chunks for embedding. Greedy up to `chunkSize`, but prefers to break\n * on a paragraph → sentence → word boundary in the back half of the window (so a chunk rarely cuts\n * mid-sentence), then carries `overlap` characters into the next chunk to preserve context across\n * the seam. Pure and deterministic.\n */\nexport function chunkText(text: string, options: ChunkOptions = {}): string[] {\n const chunkSize = options.chunkSize ?? 800;\n const overlap = options.overlap ?? 100;\n const normalized = text.trim();\n if (normalized.length === 0) {\n return [];\n }\n if (normalized.length <= chunkSize) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n let start = 0;\n while (start < normalized.length) {\n const end = Math.min(start + chunkSize, normalized.length);\n let breakAt = end;\n if (end < normalized.length) {\n const window = normalized.slice(start, end);\n const boundary = Math.max(\n window.lastIndexOf('\\n\\n'),\n window.lastIndexOf('. '),\n window.lastIndexOf(' '),\n );\n // Only honour a boundary in the back half, else a tiny chunk cascades.\n if (boundary > chunkSize / 2) {\n breakAt = start + boundary + 1;\n }\n }\n const chunk = normalized.slice(start, breakAt).trim();\n if (chunk.length > 0) {\n chunks.push(chunk);\n }\n if (breakAt >= normalized.length) {\n break;\n }\n // Step forward with overlap, but always make progress (guards against a stuck boundary).\n start = Math.max(breakAt - overlap, start + 1);\n }\n return chunks;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\n\n/** A stored, embedded chunk. `embedding` length must match the store's configured dimensions. */\nexport interface VectorRecord {\n id: string;\n text: string;\n embedding: number[];\n /** Citation-facing origin (document title, URL, row id). */\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface VectorSearchOptions {\n topK: number;\n /** Exact-match metadata filter (all keys must equal). Passed through from `RetrieveOptions.filter`. */\n filter?: Record<string, unknown>;\n}\n\n/**\n * The write + search side of RAG storage. `@dudousxd/nestjs-agent-rag` ships `MemoryVectorStore`\n * (in-JS cosine, tests + small scale) and `PgVectorStore` (pgvector); any impl works. Pair one with\n * an `EmbeddingProvider` via {@link import('./embedding-retriever.js').EmbeddingRetriever} to get a\n * core `Retriever`.\n */\nexport interface VectorStore {\n upsert(records: VectorRecord[]): Promise<void>;\n search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;\n /**\n * Delete every chunk belonging to a source document — all records whose id is `${documentId}`\n * or `${documentId}#<n>` (the id scheme {@link import('./ingest.js').chunkDocuments} produces).\n * Use it to drop a document from the index, and before re-ingesting one: `upsert` overwrites\n * matching ids but can't remove chunks a shorter new version no longer produces, so a re-ingest\n * without a preceding `remove` leaves the old tail orphaned.\n */\n remove(documentId: string): Promise<void>;\n /**\n * List the distinct source documents currently indexed (chunk ids collapsed back to their document\n * by stripping the trailing `#<n>`), each with a representative chunk's `metadata`, optionally\n * narrowed by a metadata `filter`. The enumeration seam for reconciliation: diff this against your\n * source of truth to find documents to ingest (missing), re-ingest (a stored fingerprint like\n * `size` changed), or {@link VectorStore.remove remove} (orphaned).\n */\n listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;\n}\n\n/** A distinct source document as seen by the index — its id plus a representative chunk's metadata. */\nexport interface IndexedDocument {\n id: string;\n metadata?: Record<string, unknown>;\n}\n\n/** Collapse a chunk id (`${documentId}#<n>`) back to its source document id. */\nexport function documentIdOf(chunkId: string): string {\n return chunkId.replace(/#\\d+$/, '');\n}\n","/** Metadata filter, shared by the in-memory {@link import('./memory-vector-store.js').MemoryVectorStore}\n * and {@link import('./keyword-retriever.js').KeywordRetriever}. Every key must match:\n *\n * - a **scalar** filter value keeps exact-match — the record's value must equal it;\n * - an **array** filter value means **match-any** (OR / set membership) — the record matches when its\n * value for that key is one of them, or (for a multi-valued record) shares at least one element.\n * An empty array matches nothing (no value is a member of the empty set) — the deny primitive.\n *\n * Array-valued filters are how a caller expresses capability-style access control over a shared store\n * (e.g. `{ audience: ['public', 'role:ADMIN', 'base:…'] }`) without the store knowing what a token means.\n */\nexport function matchesFilter(\n metadata: Record<string, unknown> | undefined,\n filter: Record<string, unknown>,\n): boolean {\n const entries = Object.entries(filter);\n if (metadata === undefined) {\n return entries.length === 0;\n }\n return entries.every(([key, expected]) => {\n if (Array.isArray(expected)) {\n const actual = metadata[key];\n return Array.isArray(actual)\n ? actual.some((value) => expected.includes(value))\n : expected.includes(actual);\n }\n return metadata[key] === expected;\n });\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport { matchesFilter } from './filter.js';\nimport {\n type IndexedDocument,\n type VectorRecord,\n type VectorSearchOptions,\n type VectorStore,\n documentIdOf,\n} from './vector-store.js';\n\n/**\n * An in-process {@link VectorStore} — cosine similarity over a Map, no infra. The reference adapter\n * for tests and small/embedded corpora; for production scale use `PgVectorStore` (or your own).\n */\nexport class MemoryVectorStore implements VectorStore {\n private readonly records = new Map<string, VectorRecord>();\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n this.records.set(record.id, record);\n }\n }\n\n async remove(documentId: string): Promise<void> {\n for (const id of this.records.keys()) {\n if (documentIdOf(id) === documentId) {\n this.records.delete(id);\n }\n }\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const documents = new Map<string, IndexedDocument>();\n for (const record of this.records.values()) {\n if (filter !== undefined && !matchesFilter(record.metadata, filter)) {\n continue;\n }\n const id = documentIdOf(record.id);\n if (!documents.has(id)) {\n documents.set(id, {\n id,\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n }\n return [...documents.values()];\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const scored: Passage[] = [];\n for (const record of this.records.values()) {\n if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n scored.push({\n id: record.id,\n text: record.text,\n score: cosineSimilarity(embedding, record.embedding),\n ...(record.source !== undefined ? { source: record.source } : {}),\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n return scored.sort((a, b) => b.score - a.score).slice(0, options.topK);\n }\n}\n\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n const length = Math.min(a.length, b.length);\n for (let index = 0; index < length; index += 1) {\n const valueA = a[index] ?? 0;\n const valueB = b[index] ?? 0;\n dot += valueA * valueB;\n normA += valueA * valueA;\n normB += valueB * valueB;\n }\n const denominator = Math.sqrt(normA) * Math.sqrt(normB);\n return denominator === 0 ? 0 : dot / denominator;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport type {\n IndexedDocument,\n VectorRecord,\n VectorSearchOptions,\n VectorStore,\n} from './vector-store.js';\n\n/**\n * The minimal Postgres surface {@link PgVectorStore} needs — adapt your own `pg` / `postgres.js`\n * client to it, so this package pulls in NO driver (bring your own, like the store adapters take an\n * ORM handle and the Redis transport takes a client). `query` runs parameterized SQL (`$1,$2,…`) and\n * resolves the result rows.\n */\nexport interface PgClient {\n query<Row = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<Row[]>;\n}\n\nexport interface PgVectorStoreOptions {\n /** Table name. Default `agent_rag_chunks`. */\n table?: string;\n /** Embedding width — must match your model (e.g. 1536 for text-embedding-3-small). Default 1536. */\n dimensions?: number;\n}\n\n/**\n * A pgvector-backed {@link VectorStore} — the production reference adapter. Cosine distance via the\n * `<=>` operator over an HNSW index; metadata in a `jsonb` column filtered with `@>`. Call\n * {@link PgVectorStore.ensureSchema} once at boot to create the extension, table, and index.\n */\nexport class PgVectorStore implements VectorStore {\n private readonly table: string;\n private readonly dimensions: number;\n\n constructor(\n private readonly client: PgClient,\n options: PgVectorStoreOptions = {},\n ) {\n this.table = options.table ?? 'agent_rag_chunks';\n this.dimensions = options.dimensions ?? 1536;\n }\n\n /** Idempotent DDL: the `vector` extension, the chunks table, and the cosine HNSW index. */\n async ensureSchema(): Promise<void> {\n await this.client.query('CREATE EXTENSION IF NOT EXISTS vector');\n await this.client.query(\n `CREATE TABLE IF NOT EXISTS ${this.table} (\n id TEXT PRIMARY KEY,\n text TEXT NOT NULL,\n source TEXT,\n metadata JSONB,\n embedding vector(${this.dimensions}) NOT NULL\n )`,\n );\n await this.client.query(\n `CREATE INDEX IF NOT EXISTS ${this.table}_embedding_idx\n ON ${this.table} USING hnsw (embedding vector_cosine_ops)`,\n );\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n await this.client.query(\n `INSERT INTO ${this.table} (id, text, source, metadata, embedding)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (id) DO UPDATE SET\n text = EXCLUDED.text,\n source = EXCLUDED.source,\n metadata = EXCLUDED.metadata,\n embedding = EXCLUDED.embedding`,\n [\n record.id,\n record.text,\n record.source ?? null,\n record.metadata !== undefined ? JSON.stringify(record.metadata) : null,\n toVectorLiteral(record.embedding),\n ],\n );\n }\n }\n\n async remove(documentId: string): Promise<void> {\n await this.client.query(`DELETE FROM ${this.table} WHERE ${DOCUMENT_ID_FROM_CHUNK} = $1`, [\n documentId,\n ]);\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const where = buildWhere(filter, 1);\n // DISTINCT ON collapses chunks to one row per document; all chunks share the doc's metadata.\n const rows = await this.client.query<{\n doc_id: string;\n metadata: Record<string, unknown> | null;\n }>(\n `SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata\n FROM ${this.table}\n ${where.sql}\n ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`,\n where.params,\n );\n return rows.map((row) => ({\n id: row.doc_id,\n ...(row.metadata !== null ? { metadata: row.metadata } : {}),\n }));\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const vector = toVectorLiteral(embedding);\n // $1 = query vector, $2 = topK; metadata-filter params start at $3.\n const where = buildWhere(options.filter, 3);\n const rows = await this.client.query<PgRow>(\n `SELECT id, text, source, metadata, 1 - (embedding <=> $1::vector) AS score\n FROM ${this.table}\n ${where.sql}\n ORDER BY embedding <=> $1::vector\n LIMIT $2`,\n [vector, options.topK, ...where.params],\n );\n return rows.map((row) => ({\n id: row.id,\n text: row.text,\n score: Number(row.score),\n ...(row.source !== null ? { source: row.source } : {}),\n ...(row.metadata !== null ? { metadata: row.metadata } : {}),\n }));\n }\n}\n\ninterface PgRow {\n id: string;\n text: string;\n source: string | null;\n metadata: Record<string, unknown> | null;\n score: number | string;\n}\n\n/**\n * Build the `WHERE` fragment for a metadata `filter`, with parameter placeholders numbered from\n * `startIndex`. Scalar values collapse into a single `@>` jsonb-containment check (exact-match, as\n * before). An **array** value is a **match-any** (OR / set membership) check via jsonb `?|`: the\n * record matches when its value for that key — scalar or array — shares an element with the filter\n * array. An empty array can never match (deny primitive). Keys are passed as parameters (`metadata->$k`)\n * so a caller-supplied metadata key can't inject SQL. Returns `{ sql: '', params: [] }` when there is\n * no filter, preserving the previous unfiltered query shape.\n */\nfunction buildWhere(\n filter: Record<string, unknown> | undefined,\n startIndex: number,\n): { sql: string; params: unknown[] } {\n if (filter === undefined || Object.keys(filter).length === 0) {\n return { sql: '', params: [] };\n }\n const clauses: string[] = [];\n const params: unknown[] = [];\n const scalar: Record<string, unknown> = {};\n let index = startIndex;\n for (const [key, value] of Object.entries(filter)) {\n if (Array.isArray(value)) {\n if (value.length === 0) {\n clauses.push('false');\n continue;\n }\n const keyParam = `$${index}`;\n params.push(key);\n index += 1;\n const arrParam = `$${index}`;\n params.push(value.map(String));\n index += 1;\n clauses.push(\n `(CASE WHEN jsonb_typeof(metadata->${keyParam}) = 'array' ` +\n `THEN metadata->${keyParam} ELSE jsonb_build_array(metadata->${keyParam}) END) ?| ${arrParam}::text[]`,\n );\n } else {\n scalar[key] = value;\n }\n }\n if (Object.keys(scalar).length > 0) {\n clauses.push(`metadata @> $${index}::jsonb`);\n params.push(JSON.stringify(scalar));\n index += 1;\n }\n return { sql: `WHERE ${clauses.join(' AND ')}`, params };\n}\n\n/**\n * SQL that collapses a chunk id (`${documentId}#<n>`) back to its source document id — the pgvector\n * mirror of {@link import('./vector-store.js').documentIdOf}. Shared by `remove` and\n * `listDocuments` so both key on the exact same definition of \"chunk belongs to document\".\n */\nconst DOCUMENT_ID_FROM_CHUNK = \"regexp_replace(id, '#[0-9]+$', '')\";\n\n/** pgvector accepts a `'[1,2,3]'` text literal cast to `vector`. */\nfunction toVectorLiteral(embedding: number[]): string {\n return `[${embedding.join(',')}]`;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport {\n type IndexedDocument,\n type VectorRecord,\n type VectorSearchOptions,\n type VectorStore,\n documentIdOf,\n} from './vector-store.js';\n\n/**\n * The minimal Redis surface {@link RedisVectorStore} needs: send a raw command and get the reply.\n * Both `node-redis` (`client.sendCommand(args)`) and `ioredis` (wrap `redis.call`) satisfy it — so\n * this package pulls in NO Redis driver (bring your own, like the other adapters). Requires the\n * RediSearch module (Redis Stack / Redis 8+).\n */\nexport interface RedisSearchClient {\n sendCommand(args: (string | Buffer)[]): Promise<unknown>;\n}\n\nexport interface RedisVectorStoreOptions {\n /** RediSearch index name. Default `agent_rag_idx`. */\n index?: string;\n /** Key prefix for the per-chunk hashes. Default `agent_rag:`. */\n prefix?: string;\n /** Embedding width — must match your model (e.g. 1536). Default 1536. */\n dimensions?: number;\n /**\n * Metadata keys to index as filterable TAG fields. RediSearch needs filter fields declared up\n * front, so only keys listed here can be passed in `search`'s `filter`. Default none.\n */\n filterableFields?: string[];\n}\n\n/**\n * A RediSearch-backed {@link VectorStore} — HNSW + cosine over Redis, the ecosystem-native option\n * for anyone already running Redis (see `-transport-redis`). Chunks are hashes under `prefix`, the\n * embedding a FLOAT32 vector field; `search` is a KNN query. Call {@link RedisVectorStore.ensureSchema}\n * once at boot to create the index.\n */\nexport class RedisVectorStore implements VectorStore {\n private readonly index: string;\n private readonly prefix: string;\n private readonly dimensions: number;\n private readonly filterableFields: string[];\n\n constructor(\n private readonly client: RedisSearchClient,\n options: RedisVectorStoreOptions = {},\n ) {\n this.index = options.index ?? 'agent_rag_idx';\n this.prefix = options.prefix ?? 'agent_rag:';\n this.dimensions = options.dimensions ?? 1536;\n this.filterableFields = options.filterableFields ?? [];\n }\n\n /** Idempotent: create the HNSW/cosine index if `FT.INFO` says it doesn't exist yet. */\n async ensureSchema(): Promise<void> {\n try {\n await this.client.sendCommand(['FT.INFO', this.index]);\n return;\n } catch {\n // index missing → create it below\n }\n const schema: string[] = ['text', 'TEXT', 'source', 'TAG', 'metadata_json', 'TEXT', 'NOINDEX'];\n for (const field of this.filterableFields) {\n schema.push(`meta_${field}`, 'TAG');\n }\n schema.push(\n 'embedding',\n 'VECTOR',\n 'HNSW',\n '6',\n 'TYPE',\n 'FLOAT32',\n 'DIM',\n String(this.dimensions),\n 'DISTANCE_METRIC',\n 'COSINE',\n );\n await this.client.sendCommand([\n 'FT.CREATE',\n this.index,\n 'ON',\n 'HASH',\n 'PREFIX',\n '1',\n this.prefix,\n 'SCHEMA',\n ...schema,\n ]);\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n const args: (string | Buffer)[] = [\n 'HSET',\n `${this.prefix}${record.id}`,\n 'text',\n record.text,\n 'embedding',\n encodeVector(record.embedding),\n ];\n if (record.source !== undefined) {\n args.push('source', record.source);\n }\n if (record.metadata !== undefined) {\n args.push('metadata_json', JSON.stringify(record.metadata));\n for (const field of this.filterableFields) {\n const value = record.metadata[field];\n if (value !== undefined) {\n // Array metadata is stored as a multi-valued TAG (comma-separated, RediSearch's default\n // separator) so a document can carry several capability tokens; individual values must\n // therefore not contain commas.\n const tag = Array.isArray(value) ? value.map(String).join(',') : String(value);\n args.push(`meta_${field}`, tag);\n }\n }\n }\n await this.client.sendCommand(args);\n }\n }\n\n async remove(documentId: string): Promise<void> {\n const keys = new Set<string>([`${this.prefix}${documentId}`]);\n const pattern = `${this.prefix}${escapeGlob(documentId)}#*`;\n let cursor = '0';\n do {\n const reply = await this.client.sendCommand([\n 'SCAN',\n cursor,\n 'MATCH',\n pattern,\n 'COUNT',\n '256',\n ]);\n const page = parseScanReply(reply);\n cursor = page.cursor;\n for (const key of page.keys) {\n keys.add(key);\n }\n } while (cursor !== '0');\n if (keys.size > 0) {\n await this.client.sendCommand(['DEL', ...keys]);\n }\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n if (filterMatchesNothing(filter)) {\n return [];\n }\n const query = buildFilter(filter);\n const documents = new Map<string, IndexedDocument>();\n const batchSize = 1000;\n let offset = 0;\n for (;;) {\n // RETURN metadata_json (not NOCONTENT) so we can carry a representative chunk's metadata;\n // parseSearchReply already yields prefix-stripped ids + parsed metadata for both RESP2/RESP3.\n const reply = await this.client.sendCommand([\n 'FT.SEARCH',\n this.index,\n query,\n 'RETURN',\n '1',\n 'metadata_json',\n 'LIMIT',\n String(offset),\n String(batchSize),\n 'DIALECT',\n '2',\n ]);\n const passages = parseSearchReply(reply, this.prefix);\n for (const passage of passages) {\n const id = documentIdOf(passage.id);\n if (!documents.has(id)) {\n documents.set(id, {\n id,\n ...(passage.metadata !== undefined ? { metadata: passage.metadata } : {}),\n });\n }\n }\n if (passages.length < batchSize) {\n break;\n }\n offset += batchSize;\n }\n return [...documents.values()];\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n if (filterMatchesNothing(options.filter)) {\n return [];\n }\n const query = `${buildFilter(options.filter)}=>[KNN ${options.topK} @embedding $BLOB AS vector_score]`;\n const reply = await this.client.sendCommand([\n 'FT.SEARCH',\n this.index,\n query,\n 'PARAMS',\n '2',\n 'BLOB',\n encodeVector(embedding),\n 'RETURN',\n '4',\n 'text',\n 'source',\n 'metadata_json',\n 'vector_score',\n 'SORTBY',\n 'vector_score',\n 'ASC',\n 'DIALECT',\n '2',\n 'LIMIT',\n '0',\n String(options.topK),\n ]);\n return parseSearchReply(reply, this.prefix);\n }\n}\n\n/** RediSearch expects a little-endian FLOAT32 buffer for the query/stored vector. */\nfunction encodeVector(embedding: number[]): Buffer {\n return Buffer.from(new Float32Array(embedding).buffer);\n}\n\n/**\n * `*` for no filter, else an AND of TAG clauses. A scalar value is an exact tag match\n * (`@meta_owner:{u1}`); an **array** value is a TAG alternation — match-any / OR\n * (`@meta_audience:{public|role\\:ADMIN}`), the capability-ACL primitive. RediSearch TAG fields are\n * multi-valued per document, so this also matches documents carrying several of the tags.\n */\nfunction buildFilter(filter?: Record<string, unknown>): string {\n if (filter === undefined || Object.keys(filter).length === 0) {\n return '*';\n }\n const clauses = Object.entries(filter).map(([key, value]) => {\n const values = Array.isArray(value) ? value : [value];\n const alternation = values.map((entry) => escapeTag(String(entry))).join('|');\n return `@meta_${key}:{${alternation}}`;\n });\n return `(${clauses.join(' ')})`;\n}\n\n/**\n * A filter with an empty-array value can never match (nothing is a member of the empty set) — and\n * RediSearch has no valid empty-tag syntax, so callers short-circuit to an empty result instead of\n * emitting a broken query. This is the deny primitive (e.g. an actor with no capability tokens).\n */\nfunction filterMatchesNothing(filter?: Record<string, unknown>): boolean {\n if (filter === undefined) {\n return false;\n }\n return Object.values(filter).some((value) => Array.isArray(value) && value.length === 0);\n}\n\n/** Escape RediSearch TAG punctuation so an id/tenant value matches literally. */\nfunction escapeTag(value: string): string {\n return value.replace(/[^a-zA-Z0-9_]/g, (char) => `\\\\${char}`);\n}\n\n/** Escape `SCAN MATCH` glob metacharacters so a document id matches its chunk keys literally. */\nfunction escapeGlob(value: string): string {\n return value.replace(/[*?[\\]\\\\]/g, (char) => `\\\\${char}`);\n}\n\n/**\n * Parse a `SCAN` reply into `{ cursor, keys }`, tolerating both wire shapes: the RESP2 array\n * `[cursor, [key, …]]` and node-redis's RESP3 object `{ cursor, keys }`.\n */\nfunction parseScanReply(reply: unknown): { cursor: string; keys: string[] } {\n if (Array.isArray(reply) && reply.length >= 2) {\n const rawKeys = reply[1];\n return {\n cursor: toStr(reply[0]),\n keys: Array.isArray(rawKeys) ? rawKeys.map(toStr) : [],\n };\n }\n if (typeof reply === 'object' && reply !== null && 'cursor' in reply && 'keys' in reply) {\n const { cursor, keys } = reply as { cursor: unknown; keys: unknown };\n return {\n cursor: toStr(cursor),\n keys: Array.isArray(keys) ? keys.map(toStr) : [],\n };\n }\n return { cursor: '0', keys: [] };\n}\n\nfunction toStr(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);\n}\n\n/**\n * Parse an `FT.SEARCH` reply into passages, tolerating both wire shapes: the RESP2 array\n * `[total, key, [f, v, …], …]` (ioredis / node-redis RESP2) and the RESP3 object\n * `{ results: [{ id, extra_attributes: { … } }] }` (node-redis's default). Distance → similarity\n * via `1 - distance` (COSINE), and the `prefix` is stripped back off the key to recover the chunk id.\n */\nfunction parseSearchReply(reply: unknown, prefix: string): Passage[] {\n if (Array.isArray(reply)) {\n const passages: Passage[] = [];\n for (let index = 1; index + 1 < reply.length; index += 2) {\n const rawFields = reply[index + 1];\n if (Array.isArray(rawFields)) {\n const attrs: Record<string, string> = {};\n for (let field = 0; field + 1 < rawFields.length; field += 2) {\n attrs[toStr(rawFields[field])] = toStr(rawFields[field + 1]);\n }\n passages.push(toPassage(toStr(reply[index]), attrs, prefix));\n }\n }\n return passages;\n }\n if (typeof reply === 'object' && reply !== null && 'results' in reply) {\n const results = (reply as { results: unknown }).results;\n if (Array.isArray(results)) {\n return results.map((result) => toPassage(readId(result), readAttributes(result), prefix));\n }\n }\n return [];\n}\n\nfunction readId(result: unknown): string {\n if (typeof result === 'object' && result !== null && 'id' in result) {\n return toStr((result as { id: unknown }).id);\n }\n return '';\n}\n\nfunction readAttributes(result: unknown): Record<string, string> {\n const attrs: Record<string, string> = {};\n if (typeof result === 'object' && result !== null && 'extra_attributes' in result) {\n const raw = (result as { extra_attributes: unknown }).extra_attributes;\n if (typeof raw === 'object' && raw !== null) {\n for (const [key, value] of Object.entries(raw)) {\n attrs[key] = toStr(value);\n }\n }\n }\n return attrs;\n}\n\nfunction toPassage(rawId: string, attrs: Record<string, string>, prefix: string): Passage {\n const distance = Number(attrs.vector_score ?? '1');\n const source = attrs.source;\n return {\n id: rawId.startsWith(prefix) ? rawId.slice(prefix.length) : rawId,\n text: attrs.text ?? '',\n score: 1 - distance,\n ...(source !== undefined ? { source } : {}),\n ...parseMetadata(attrs.metadata_json),\n };\n}\n\nfunction parseMetadata(raw: string | undefined): { metadata?: Record<string, unknown> } {\n if (raw === undefined) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed === 'object' && parsed !== null) {\n return { metadata: parsed as Record<string, unknown> };\n }\n } catch {\n // not JSON — no metadata\n }\n return {};\n}\n","import type {\n EmbeddingProvider,\n Passage,\n RetrieveOptions,\n Retriever,\n} from '@dudousxd/nestjs-agent-core';\nimport type { VectorStore } from './vector-store.js';\n\n/**\n * Bridges an {@link EmbeddingProvider} + {@link VectorStore} into the core {@link Retriever} SPI:\n * embed the query, then vector-search. This is what you wire as the agent's retriever (as a tool\n * via `createRetrievalTool`, or into `AgentModule.forRoot({ retrieval })` for inject mode).\n */\nexport class EmbeddingRetriever implements Retriever {\n constructor(\n private readonly embedder: EmbeddingProvider,\n private readonly store: VectorStore,\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const [embedding] = await this.embedder.embed([query]);\n if (embedding === undefined) {\n return [];\n }\n return this.store.search(embedding, {\n topK: options.topK ?? 5,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n });\n }\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\nimport { matchesFilter } from './filter.js';\nimport type { ChunkRecord } from './ingest.js';\n\nexport interface KeywordRetrieverOptions {\n /** BM25 term-frequency saturation. Default 1.5. */\n k1?: number;\n /** BM25 length normalization, 0 (off) … 1 (full). Default 0.75. */\n b?: number;\n}\n\ninterface IndexedDoc {\n record: ChunkRecord;\n /** term → frequency within this doc. */\n frequencies: Map<string, number>;\n length: number;\n}\n\n/**\n * An in-memory BM25 lexical retriever — the keyword half of hybrid search. Feed it the SAME\n * {@link ChunkRecord}s you upsert into the vector store (from `chunkDocuments`) so their chunk ids\n * line up and `HybridRetriever` can fuse the two rankings. Full BM25: idf, term-frequency saturation\n * (`k1`), and document-length normalization (`b`).\n */\nexport class KeywordRetriever implements Retriever {\n private readonly docs = new Map<string, IndexedDoc>();\n private readonly documentFrequency = new Map<string, number>();\n private totalLength = 0;\n private readonly k1: number;\n private readonly b: number;\n\n constructor(options: KeywordRetrieverOptions = {}) {\n this.k1 = options.k1 ?? 1.5;\n this.b = options.b ?? 0.75;\n }\n\n /** Index (or re-index) records. Re-adding an existing id replaces its posting and updates stats. */\n add(records: ChunkRecord[]): void {\n for (const record of records) {\n this.remove(record.id);\n const tokens = tokenize(record.text);\n const frequencies = new Map<string, number>();\n for (const token of tokens) {\n frequencies.set(token, (frequencies.get(token) ?? 0) + 1);\n }\n for (const term of frequencies.keys()) {\n this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);\n }\n this.docs.set(record.id, { record, frequencies, length: tokens.length });\n this.totalLength += tokens.length;\n }\n }\n\n private remove(id: string): void {\n const existing = this.docs.get(id);\n if (existing === undefined) {\n return;\n }\n for (const term of existing.frequencies.keys()) {\n const next = (this.documentFrequency.get(term) ?? 1) - 1;\n if (next <= 0) {\n this.documentFrequency.delete(term);\n } else {\n this.documentFrequency.set(term, next);\n }\n }\n this.totalLength -= existing.length;\n this.docs.delete(id);\n }\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const total = this.docs.size;\n if (total === 0) {\n return [];\n }\n const averageLength = this.totalLength / total;\n const queryTerms = new Set(tokenize(query));\n const scored: Passage[] = [];\n for (const { record, frequencies, length } of this.docs.values()) {\n if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n let score = 0;\n for (const term of queryTerms) {\n const termFrequency = frequencies.get(term);\n if (termFrequency === undefined) {\n continue;\n }\n const df = this.documentFrequency.get(term) ?? 0;\n const idf = Math.log(1 + (total - df + 0.5) / (df + 0.5));\n const denominator =\n termFrequency + this.k1 * (1 - this.b + (this.b * length) / averageLength);\n score += idf * ((termFrequency * (this.k1 + 1)) / denominator);\n }\n if (score > 0) {\n scored.push({\n id: record.id,\n text: record.text,\n score,\n ...(record.source !== undefined ? { source: record.source } : {}),\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n }\n return scored.sort((a, b) => b.score - a.score).slice(0, options.topK ?? 5);\n }\n}\n\nfunction tokenize(text: string): string[] {\n return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\nexport interface HybridRetrieverOptions {\n /** RRF constant — larger flattens the rank weighting. Default 60 (the value from the original paper). */\n k?: number;\n /** Candidates pulled from each retriever before fusion. Default 20. */\n fetchTopK?: number;\n /** Per-retriever multipliers on each RRF contribution (same order as `retrievers`). Default all 1. */\n weights?: number[];\n}\n\n/**\n * Fuses several retrievers with Reciprocal Rank Fusion — the robust way to combine, say, dense\n * vector search with lexical BM25. RRF scores each passage by `Σ weight / (k + rank)` across the\n * lists it appears in, so it needs no score normalization between incompatible scales (cosine vs\n * BM25). Deduplicates by passage id, so aligned chunk ids from the two retrievers reinforce.\n */\nexport class HybridRetriever implements Retriever {\n constructor(\n private readonly retrievers: Retriever[],\n private readonly options: HybridRetrieverOptions = {},\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const k = this.options.k ?? 60;\n const fetchTopK = this.options.fetchTopK ?? 20;\n const weights = this.options.weights;\n const lists = await Promise.all(\n this.retrievers.map((retriever) =>\n retriever.retrieve(query, {\n topK: fetchTopK,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n }),\n ),\n );\n\n const fused = new Map<string, { passage: Passage; score: number }>();\n lists.forEach((list, listIndex) => {\n const weight = weights?.[listIndex] ?? 1;\n list.forEach((passage, rank) => {\n const contribution = weight / (k + rank + 1);\n const existing = fused.get(passage.id);\n if (existing !== undefined) {\n existing.score += contribution;\n } else {\n fused.set(passage.id, { passage, score: contribution });\n }\n });\n });\n\n return [...fused.values()]\n .sort((a, b) => b.score - a.score)\n .slice(0, options.topK ?? 5)\n .map((entry) => ({ ...entry.passage, score: entry.score }));\n }\n}\n","import type { Passage, Reranker, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\nexport interface RerankingRetrieverOptions {\n /** How many candidates to pull from the base retriever before reranking. Default 20. */\n fetchTopK?: number;\n}\n\n/**\n * Two-stage retrieval: over-fetch cheap candidates from a base `Retriever`, then reorder them with a\n * stronger {@link Reranker} and keep the top few. The standard precision boost — a fast first stage\n * casts a wide net, a slow accurate second stage sharpens it. Composes over ANY retriever (vector,\n * hybrid, keyword), and is itself a `Retriever`.\n */\nexport class RerankingRetriever implements Retriever {\n constructor(\n private readonly base: Retriever,\n private readonly reranker: Reranker,\n private readonly options: RerankingRetrieverOptions = {},\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const candidates = await this.base.retrieve(query, {\n topK: this.options.fetchTopK ?? 20,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n });\n return this.reranker.rerank(query, candidates, { topK: options.topK ?? 5 });\n }\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\n/**\n * Wraps a `Retriever` so every `retrieve` call ANDs a fixed metadata `filter` on top of whatever the\n * caller passes — the owner/tenant-scoping primitive. `new FilteredRetriever(base, { ownerId })` only\n * ever returns that owner's passages, so a shared vector store serves per-user / per-tenant RAG\n * without the agent (or a crafted query) reaching across the boundary.\n *\n * The fixed filter takes precedence on key conflicts: a caller can narrow further but cannot widen\n * past the scope. Composes over ANY retriever (vector, hybrid, reranked) and is itself a `Retriever`.\n */\nexport class FilteredRetriever implements Retriever {\n constructor(\n private readonly base: Retriever,\n private readonly filter: Record<string, unknown>,\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n return this.base.retrieve(query, {\n ...options,\n filter: { ...options.filter, ...this.filter },\n });\n }\n}\n","import type { EmbeddingProvider } from '@dudousxd/nestjs-agent-core';\nimport { type ChunkOptions, chunkText } from './chunk.js';\nimport type { VectorRecord, VectorStore } from './vector-store.js';\n\n/** A source document to ingest. `id` scopes the chunk ids (`${id}#${n}`); `source` is the citation. */\nexport interface IngestDocument {\n id: string;\n text: string;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\n/** A chunked, not-yet-embedded record. Feed the same array to a vector store AND a keyword index so\n * both retrievers key on identical chunk ids (which is what lets hybrid fusion line them up). */\nexport interface ChunkRecord {\n id: string;\n text: string;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface IngestChunksOptions {\n embedder: EmbeddingProvider;\n store: VectorStore;\n}\n\nexport interface IngestOptions extends ChunkOptions, IngestChunksOptions {}\n\n/**\n * Split documents into chunk records. Chunk ids are `${doc.id}#${index}`, so re-chunking the same\n * document produces the same ids (upsert overwrites in place rather than duplicating).\n */\nexport function chunkDocuments(\n documents: IngestDocument[],\n options: ChunkOptions = {},\n): ChunkRecord[] {\n const chunks: ChunkRecord[] = [];\n for (const document of documents) {\n chunkText(document.text, options).forEach((text, index) => {\n chunks.push({\n id: `${document.id}#${index}`,\n text,\n ...(document.source !== undefined ? { source: document.source } : {}),\n ...(document.metadata !== undefined ? { metadata: document.metadata } : {}),\n });\n });\n }\n return chunks;\n}\n\n/** Embed pre-chunked records (one batched `embed`) and upsert them. Returns the record count. */\nexport async function ingestChunks(\n chunks: ChunkRecord[],\n options: IngestChunksOptions,\n): Promise<number> {\n if (chunks.length === 0) {\n return 0;\n }\n const embeddings = await options.embedder.embed(chunks.map((chunk) => chunk.text));\n const records: VectorRecord[] = chunks.map((chunk, index) => ({\n ...chunk,\n embedding: embeddings[index] ?? [],\n }));\n await options.store.upsert(records);\n return records.length;\n}\n\n/** Chunk → embed → upsert in one call. For hybrid search, use {@link chunkDocuments} +\n * {@link ingestChunks} and feed the same chunks to a {@link import('./keyword-retriever.js').KeywordRetriever}. */\nexport async function ingestDocuments(\n documents: IngestDocument[],\n options: IngestOptions,\n): Promise<number> {\n return ingestChunks(chunkDocuments(documents, options), options);\n}\n","import type { Passage, Retriever, ToolHandler, ToolSpec } from '@dudousxd/nestjs-agent-core';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\nexport interface RetrievalToolOptions {\n /** Tool name the model sees. Default `search_knowledge`. */\n name?: string;\n description?: string;\n /** Passages per search. Default lets the retriever decide (typically 5). */\n topK?: number;\n}\n\n/** A functional tool (`{ spec, handler }`) — pass it to nestjs `provideAgentTool`. */\nexport interface RetrievalTool {\n spec: ToolSpec;\n handler: ToolHandler;\n}\n\nfunction isQueryInput(value: unknown): value is { query: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'query' in value &&\n typeof (value as Record<'query', unknown>).query === 'string'\n );\n}\n\n/** A hand-rolled Standard Schema for `{ query: string }` — keeps this package validation-lib-free. */\nconst querySchema: StandardSchemaV1<{ query: string }> = {\n '~standard': {\n version: 1,\n vendor: 'nestjs-agent-rag',\n validate(value: unknown) {\n return isQueryInput(value)\n ? { value: { query: value.query } }\n : { issues: [{ message: 'retrieval tool expects { query: string }' }] };\n },\n },\n};\n\n/**\n * Builds the agentic-retrieval tool: a `read`-kind `search_knowledge(query)` the model calls when it\n * decides it needs context. Its output `{ passages }` persists as the tool call's result, so sources\n * flow to the frontend and telescope through the normal tool-call machinery (the citation surface).\n *\n * ```ts\n * providers: [provideAgentTool(createRetrievalTool(retriever))]\n * ```\n */\nexport function createRetrievalTool(\n retriever: Retriever,\n options: RetrievalToolOptions = {},\n): RetrievalTool {\n const topK = options.topK;\n return {\n spec: {\n name: options.name ?? 'search_knowledge',\n kind: 'read',\n description:\n options.description ??\n 'Search the knowledge base for passages relevant to a query. Returns passages with their source for citation.',\n inputSchema: querySchema,\n },\n handler: {\n async execute(input: unknown): Promise<{ passages: Passage[] }> {\n if (!isQueryInput(input)) {\n throw new Error('retrieval tool expects { query: string }');\n }\n const passages = await retriever.retrieve(input.query, topK !== undefined ? { topK } : {});\n return { passages };\n },\n },\n };\n}\n"],"mappings":";;;;AAaO,SAASA,UAAUC,MAAcC,UAAwB,CAAC,GAAC;AAChE,QAAMC,YAAYD,QAAQC,aAAa;AACvC,QAAMC,UAAUF,QAAQE,WAAW;AACnC,QAAMC,aAAaJ,KAAKK,KAAI;AAC5B,MAAID,WAAWE,WAAW,GAAG;AAC3B,WAAO,CAAA;EACT;AACA,MAAIF,WAAWE,UAAUJ,WAAW;AAClC,WAAO;MAACE;;EACV;AAEA,QAAMG,SAAmB,CAAA;AACzB,MAAIC,QAAQ;AACZ,SAAOA,QAAQJ,WAAWE,QAAQ;AAChC,UAAMG,MAAMC,KAAKC,IAAIH,QAAQN,WAAWE,WAAWE,MAAM;AACzD,QAAIM,UAAUH;AACd,QAAIA,MAAML,WAAWE,QAAQ;AAC3B,YAAMO,SAAST,WAAWU,MAAMN,OAAOC,GAAAA;AACvC,YAAMM,WAAWL,KAAKM,IACpBH,OAAOI,YAAY,MAAA,GACnBJ,OAAOI,YAAY,IAAA,GACnBJ,OAAOI,YAAY,GAAA,CAAA;AAGrB,UAAIF,WAAWb,YAAY,GAAG;AAC5BU,kBAAUJ,QAAQO,WAAW;MAC/B;IACF;AACA,UAAMG,QAAQd,WAAWU,MAAMN,OAAOI,OAAAA,EAASP,KAAI;AACnD,QAAIa,MAAMZ,SAAS,GAAG;AACpBC,aAAOY,KAAKD,KAAAA;IACd;AACA,QAAIN,WAAWR,WAAWE,QAAQ;AAChC;IACF;AAEAE,YAAQE,KAAKM,IAAIJ,UAAUT,SAASK,QAAQ,CAAA;EAC9C;AACA,SAAOD;AACT;AAvCgBR;;;ACuCT,SAASqB,aAAaC,SAAe;AAC1C,SAAOA,QAAQC,QAAQ,SAAS,EAAA;AAClC;AAFgBF;;;ACzCT,SAASG,cACdC,UACAC,QAA+B;AAE/B,QAAMC,UAAUC,OAAOD,QAAQD,MAAAA;AAC/B,MAAID,aAAaI,QAAW;AAC1B,WAAOF,QAAQG,WAAW;EAC5B;AACA,SAAOH,QAAQI,MAAM,CAAC,CAACC,KAAKC,QAAAA,MAAS;AACnC,QAAIC,MAAMC,QAAQF,QAAAA,GAAW;AAC3B,YAAMG,SAASX,SAASO,GAAAA;AACxB,aAAOE,MAAMC,QAAQC,MAAAA,IACjBA,OAAOC,KAAK,CAACC,UAAUL,SAASM,SAASD,KAAAA,CAAAA,IACzCL,SAASM,SAASH,MAAAA;IACxB;AACA,WAAOX,SAASO,GAAAA,MAASC;EAC3B,CAAA;AACF;AAjBgBT;;;ACGT,IAAMgB,oBAAN,MAAMA;EAbb,OAaaA;;;EACMC,UAAU,oBAAIC,IAAAA;EAE/B,MAAMC,OAAOF,SAAwC;AACnD,eAAWG,UAAUH,SAAS;AAC5B,WAAKA,QAAQI,IAAID,OAAOE,IAAIF,MAAAA;IAC9B;EACF;EAEA,MAAMG,OAAOC,YAAmC;AAC9C,eAAWF,MAAM,KAAKL,QAAQQ,KAAI,GAAI;AACpC,UAAIC,aAAaJ,EAAAA,MAAQE,YAAY;AACnC,aAAKP,QAAQU,OAAOL,EAAAA;MACtB;IACF;EACF;EAEA,MAAMM,cAAcC,QAA8D;AAChF,UAAMC,YAAY,oBAAIZ,IAAAA;AACtB,eAAWE,UAAU,KAAKH,QAAQc,OAAM,GAAI;AAC1C,UAAIF,WAAWG,UAAa,CAACC,cAAcb,OAAOc,UAAUL,MAAAA,GAAS;AACnE;MACF;AACA,YAAMP,KAAKI,aAAaN,OAAOE,EAAE;AACjC,UAAI,CAACQ,UAAUK,IAAIb,EAAAA,GAAK;AACtBQ,kBAAUT,IAAIC,IAAI;UAChBA;UACA,GAAIF,OAAOc,aAAaF,SAAY;YAAEE,UAAUd,OAAOc;UAAS,IAAI,CAAC;QACvE,CAAA;MACF;IACF;AACA,WAAO;SAAIJ,UAAUC,OAAM;;EAC7B;EAEA,MAAMK,OAAOC,WAAqBC,SAAkD;AAClF,UAAMC,SAAoB,CAAA;AAC1B,eAAWnB,UAAU,KAAKH,QAAQc,OAAM,GAAI;AAC1C,UAAIO,QAAQT,WAAWG,UAAa,CAACC,cAAcb,OAAOc,UAAUI,QAAQT,MAAM,GAAG;AACnF;MACF;AACAU,aAAOC,KAAK;QACVlB,IAAIF,OAAOE;QACXmB,MAAMrB,OAAOqB;QACbC,OAAOC,iBAAiBN,WAAWjB,OAAOiB,SAAS;QACnD,GAAIjB,OAAOwB,WAAWZ,SAAY;UAAEY,QAAQxB,OAAOwB;QAAO,IAAI,CAAC;QAC/D,GAAIxB,OAAOc,aAAaF,SAAY;UAAEE,UAAUd,OAAOc;QAAS,IAAI,CAAC;MACvE,CAAA;IACF;AACA,WAAOK,OAAOM,KAAK,CAACC,GAAGC,MAAMA,EAAEL,QAAQI,EAAEJ,KAAK,EAAEM,MAAM,GAAGV,QAAQW,IAAI;EACvE;AACF;AAEA,SAASN,iBAAiBG,GAAaC,GAAW;AAChD,MAAIG,MAAM;AACV,MAAIC,QAAQ;AACZ,MAAIC,QAAQ;AACZ,QAAMC,SAASC,KAAKC,IAAIT,EAAEO,QAAQN,EAAEM,MAAM;AAC1C,WAASG,QAAQ,GAAGA,QAAQH,QAAQG,SAAS,GAAG;AAC9C,UAAMC,SAASX,EAAEU,KAAAA,KAAU;AAC3B,UAAME,SAASX,EAAES,KAAAA,KAAU;AAC3BN,WAAOO,SAASC;AAChBP,aAASM,SAASA;AAClBL,aAASM,SAASA;EACpB;AACA,QAAMC,cAAcL,KAAKM,KAAKT,KAAAA,IAASG,KAAKM,KAAKR,KAAAA;AACjD,SAAOO,gBAAgB,IAAI,IAAIT,MAAMS;AACvC;AAdShB;;;ACpCF,IAAMkB,gBAAN,MAAMA;EALb,OAKaA;;;;EACMC;EACAC;EAEjB,YACmBC,QACjBC,UAAgC,CAAC,GACjC;SAFiBD,SAAAA;AAGjB,SAAKF,QAAQG,QAAQH,SAAS;AAC9B,SAAKC,aAAaE,QAAQF,cAAc;EAC1C;;EAGA,MAAMG,eAA8B;AAClC,UAAM,KAAKF,OAAOG,MAAM,uCAAA;AACxB,UAAM,KAAKH,OAAOG,MAChB,8BAA8B,KAAKL,KAAK;;;;;2BAKnB,KAAKC,UAAU;QAClC;AAEJ,UAAM,KAAKC,OAAOG,MAChB,8BAA8B,KAAKL,KAAK;aACjC,KAAKA,KAAK,2CAA2C;EAEhE;EAEA,MAAMM,OAAOC,SAAwC;AACnD,eAAWC,UAAUD,SAAS;AAC5B,YAAM,KAAKL,OAAOG,MAChB,eAAe,KAAKL,KAAK;;;;;;4CAOzB;QACEQ,OAAOC;QACPD,OAAOE;QACPF,OAAOG,UAAU;QACjBH,OAAOI,aAAaC,SAAYC,KAAKC,UAAUP,OAAOI,QAAQ,IAAI;QAClEI,gBAAgBR,OAAOS,SAAS;OACjC;IAEL;EACF;EAEA,MAAMC,OAAOC,YAAmC;AAC9C,UAAM,KAAKjB,OAAOG,MAAM,eAAe,KAAKL,KAAK,UAAUoB,sBAAAA,SAA+B;MACxFD;KACD;EACH;EAEA,MAAME,cAAcC,QAA8D;AAChF,UAAMC,QAAQC,WAAWF,QAAQ,CAAA;AAEjC,UAAMG,OAAO,MAAM,KAAKvB,OAAOG,MAI7B,uBAAuBe,sBAAAA,KAA2BA,sBAAAA;cAC1C,KAAKpB,KAAK;SACfuB,MAAMG,GAAG;kBACAN,sBAAAA,IACZG,MAAMI,MAAM;AAEd,WAAOF,KAAKG,IAAI,CAACC,SAAS;MACxBpB,IAAIoB,IAAIC;MACR,GAAID,IAAIjB,aAAa,OAAO;QAAEA,UAAUiB,IAAIjB;MAAS,IAAI,CAAC;IAC5D,EAAA;EACF;EAEA,MAAMmB,OAAOd,WAAqBd,SAAkD;AAClF,UAAM6B,SAAShB,gBAAgBC,SAAAA;AAE/B,UAAMM,QAAQC,WAAWrB,QAAQmB,QAAQ,CAAA;AACzC,UAAMG,OAAO,MAAM,KAAKvB,OAAOG,MAC7B;cACQ,KAAKL,KAAK;SACfuB,MAAMG,GAAG;;kBAGZ;MAACM;MAAQ7B,QAAQ8B;SAASV,MAAMI;KAAO;AAEzC,WAAOF,KAAKG,IAAI,CAACC,SAAS;MACxBpB,IAAIoB,IAAIpB;MACRC,MAAMmB,IAAInB;MACVwB,OAAOC,OAAON,IAAIK,KAAK;MACvB,GAAIL,IAAIlB,WAAW,OAAO;QAAEA,QAAQkB,IAAIlB;MAAO,IAAI,CAAC;MACpD,GAAIkB,IAAIjB,aAAa,OAAO;QAAEA,UAAUiB,IAAIjB;MAAS,IAAI,CAAC;IAC5D,EAAA;EACF;AACF;AAmBA,SAASY,WACPF,QACAc,YAAkB;AAElB,MAAId,WAAWT,UAAawB,OAAOC,KAAKhB,MAAAA,EAAQiB,WAAW,GAAG;AAC5D,WAAO;MAAEb,KAAK;MAAIC,QAAQ,CAAA;IAAG;EAC/B;AACA,QAAMa,UAAoB,CAAA;AAC1B,QAAMb,SAAoB,CAAA;AAC1B,QAAMc,SAAkC,CAAC;AACzC,MAAIC,QAAQN;AACZ,aAAW,CAACO,KAAKC,KAAAA,KAAUP,OAAOQ,QAAQvB,MAAAA,GAAS;AACjD,QAAIwB,MAAMC,QAAQH,KAAAA,GAAQ;AACxB,UAAIA,MAAML,WAAW,GAAG;AACtBC,gBAAQQ,KAAK,OAAA;AACb;MACF;AACA,YAAMC,WAAW,IAAIP,KAAAA;AACrBf,aAAOqB,KAAKL,GAAAA;AACZD,eAAS;AACT,YAAMQ,WAAW,IAAIR,KAAAA;AACrBf,aAAOqB,KAAKJ,MAAMhB,IAAIuB,MAAAA,CAAAA;AACtBT,eAAS;AACTF,cAAQQ,KACN,qCAAqCC,QAAAA,8BACjBA,QAAAA,qCAA6CA,QAAAA,aAAqBC,QAAAA,UAAkB;IAE5G,OAAO;AACLT,aAAOE,GAAAA,IAAOC;IAChB;EACF;AACA,MAAIP,OAAOC,KAAKG,MAAAA,EAAQF,SAAS,GAAG;AAClCC,YAAQQ,KAAK,gBAAgBN,KAAAA,SAAc;AAC3Cf,WAAOqB,KAAKlC,KAAKC,UAAU0B,MAAAA,CAAAA;AAC3BC,aAAS;EACX;AACA,SAAO;IAAEhB,KAAK,SAASc,QAAQY,KAAK,OAAA,CAAA;IAAYzB;EAAO;AACzD;AArCSH;AA4CT,IAAMJ,yBAAyB;AAG/B,SAASJ,gBAAgBC,WAAmB;AAC1C,SAAO,IAAIA,UAAUmC,KAAK,GAAA,CAAA;AAC5B;AAFSpC;;;ACzJF,IAAMqC,mBAAN,MAAMA;EAtCb,OAsCaA;;;;EACMC;EACAC;EACAC;EACAC;EAEjB,YACmBC,QACjBC,UAAmC,CAAC,GACpC;SAFiBD,SAAAA;AAGjB,SAAKJ,QAAQK,QAAQL,SAAS;AAC9B,SAAKC,SAASI,QAAQJ,UAAU;AAChC,SAAKC,aAAaG,QAAQH,cAAc;AACxC,SAAKC,mBAAmBE,QAAQF,oBAAoB,CAAA;EACtD;;EAGA,MAAMG,eAA8B;AAClC,QAAI;AACF,YAAM,KAAKF,OAAOG,YAAY;QAAC;QAAW,KAAKP;OAAM;AACrD;IACF,QAAQ;IAER;AACA,UAAMQ,SAAmB;MAAC;MAAQ;MAAQ;MAAU;MAAO;MAAiB;MAAQ;;AACpF,eAAWC,SAAS,KAAKN,kBAAkB;AACzCK,aAAOE,KAAK,QAAQD,KAAAA,IAAS,KAAA;IAC/B;AACAD,WAAOE,KACL,aACA,UACA,QACA,KACA,QACA,WACA,OACAC,OAAO,KAAKT,UAAU,GACtB,mBACA,QAAA;AAEF,UAAM,KAAKE,OAAOG,YAAY;MAC5B;MACA,KAAKP;MACL;MACA;MACA;MACA;MACA,KAAKC;MACL;SACGO;KACJ;EACH;EAEA,MAAMI,OAAOC,SAAwC;AACnD,eAAWC,UAAUD,SAAS;AAC5B,YAAME,OAA4B;QAChC;QACA,GAAG,KAAKd,MAAM,GAAGa,OAAOE,EAAE;QAC1B;QACAF,OAAOG;QACP;QACAC,aAAaJ,OAAOK,SAAS;;AAE/B,UAAIL,OAAOM,WAAWC,QAAW;AAC/BN,aAAKL,KAAK,UAAUI,OAAOM,MAAM;MACnC;AACA,UAAIN,OAAOQ,aAAaD,QAAW;AACjCN,aAAKL,KAAK,iBAAiBa,KAAKC,UAAUV,OAAOQ,QAAQ,CAAA;AACzD,mBAAWb,SAAS,KAAKN,kBAAkB;AACzC,gBAAMsB,QAAQX,OAAOQ,SAASb,KAAAA;AAC9B,cAAIgB,UAAUJ,QAAW;AAIvB,kBAAMK,MAAMC,MAAMC,QAAQH,KAAAA,IAASA,MAAMI,IAAIlB,MAAAA,EAAQmB,KAAK,GAAA,IAAOnB,OAAOc,KAAAA;AACxEV,iBAAKL,KAAK,QAAQD,KAAAA,IAASiB,GAAAA;UAC7B;QACF;MACF;AACA,YAAM,KAAKtB,OAAOG,YAAYQ,IAAAA;IAChC;EACF;EAEA,MAAMgB,OAAOC,YAAmC;AAC9C,UAAMC,OAAO,oBAAIC,IAAY;MAAC,GAAG,KAAKjC,MAAM,GAAG+B,UAAAA;KAAa;AAC5D,UAAMG,UAAU,GAAG,KAAKlC,MAAM,GAAGmC,WAAWJ,UAAAA,CAAAA;AAC5C,QAAIK,SAAS;AACb,OAAG;AACD,YAAMC,QAAQ,MAAM,KAAKlC,OAAOG,YAAY;QAC1C;QACA8B;QACA;QACAF;QACA;QACA;OACD;AACD,YAAMI,OAAOC,eAAeF,KAAAA;AAC5BD,eAASE,KAAKF;AACd,iBAAWI,OAAOF,KAAKN,MAAM;AAC3BA,aAAKS,IAAID,GAAAA;MACX;IACF,SAASJ,WAAW;AACpB,QAAIJ,KAAKU,OAAO,GAAG;AACjB,YAAM,KAAKvC,OAAOG,YAAY;QAAC;WAAU0B;OAAK;IAChD;EACF;EAEA,MAAMW,cAAcC,QAA8D;AAChF,QAAIC,qBAAqBD,MAAAA,GAAS;AAChC,aAAO,CAAA;IACT;AACA,UAAME,QAAQC,YAAYH,MAAAA;AAC1B,UAAMI,YAAY,oBAAIC,IAAAA;AACtB,UAAMC,YAAY;AAClB,QAAIC,SAAS;AACb,eAAS;AAGP,YAAMd,QAAQ,MAAM,KAAKlC,OAAOG,YAAY;QAC1C;QACA,KAAKP;QACL+C;QACA;QACA;QACA;QACA;QACApC,OAAOyC,MAAAA;QACPzC,OAAOwC,SAAAA;QACP;QACA;OACD;AACD,YAAME,WAAWC,iBAAiBhB,OAAO,KAAKrC,MAAM;AACpD,iBAAWsD,WAAWF,UAAU;AAC9B,cAAMrC,KAAKwC,aAAaD,QAAQvC,EAAE;AAClC,YAAI,CAACiC,UAAUQ,IAAIzC,EAAAA,GAAK;AACtBiC,oBAAUS,IAAI1C,IAAI;YAChBA;YACA,GAAIuC,QAAQjC,aAAaD,SAAY;cAAEC,UAAUiC,QAAQjC;YAAS,IAAI,CAAC;UACzE,CAAA;QACF;MACF;AACA,UAAI+B,SAASM,SAASR,WAAW;AAC/B;MACF;AACAC,gBAAUD;IACZ;AACA,WAAO;SAAIF,UAAUW,OAAM;;EAC7B;EAEA,MAAMC,OAAO1C,WAAqBd,SAAkD;AAClF,QAAIyC,qBAAqBzC,QAAQwC,MAAM,GAAG;AACxC,aAAO,CAAA;IACT;AACA,UAAME,QAAQ,GAAGC,YAAY3C,QAAQwC,MAAM,CAAA,UAAWxC,QAAQyD,IAAI;AAClE,UAAMxB,QAAQ,MAAM,KAAKlC,OAAOG,YAAY;MAC1C;MACA,KAAKP;MACL+C;MACA;MACA;MACA;MACA7B,aAAaC,SAAAA;MACb;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACAR,OAAON,QAAQyD,IAAI;KACpB;AACD,WAAOR,iBAAiBhB,OAAO,KAAKrC,MAAM;EAC5C;AACF;AAGA,SAASiB,aAAaC,WAAmB;AACvC,SAAO4C,OAAOC,KAAK,IAAIC,aAAa9C,SAAAA,EAAW+C,MAAM;AACvD;AAFShD;AAUT,SAAS8B,YAAYH,QAAgC;AACnD,MAAIA,WAAWxB,UAAa8C,OAAOlC,KAAKY,MAAAA,EAAQc,WAAW,GAAG;AAC5D,WAAO;EACT;AACA,QAAMS,UAAUD,OAAOE,QAAQxB,MAAAA,EAAQhB,IAAI,CAAC,CAACY,KAAKhB,KAAAA,MAAM;AACtD,UAAMmC,SAASjC,MAAMC,QAAQH,KAAAA,IAASA,QAAQ;MAACA;;AAC/C,UAAM6C,cAAcV,OAAO/B,IAAI,CAAC0C,UAAUC,UAAU7D,OAAO4D,KAAAA,CAAAA,CAAAA,EAASzC,KAAK,GAAA;AACzE,WAAO,SAASW,GAAAA,KAAQ6B,WAAAA;EAC1B,CAAA;AACA,SAAO,IAAIF,QAAQtC,KAAK,GAAA,CAAA;AAC1B;AAVSkB;AAiBT,SAASF,qBAAqBD,QAAgC;AAC5D,MAAIA,WAAWxB,QAAW;AACxB,WAAO;EACT;AACA,SAAO8C,OAAOP,OAAOf,MAAAA,EAAQ4B,KAAK,CAAChD,UAAUE,MAAMC,QAAQH,KAAAA,KAAUA,MAAMkC,WAAW,CAAA;AACxF;AALSb;AAQT,SAAS0B,UAAU/C,OAAa;AAC9B,SAAOA,MAAMiD,QAAQ,kBAAkB,CAACC,SAAS,KAAKA,IAAAA,EAAM;AAC9D;AAFSH;AAKT,SAASpC,WAAWX,OAAa;AAC/B,SAAOA,MAAMiD,QAAQ,cAAc,CAACC,SAAS,KAAKA,IAAAA,EAAM;AAC1D;AAFSvC;AAQT,SAASI,eAAeF,OAAc;AACpC,MAAIX,MAAMC,QAAQU,KAAAA,KAAUA,MAAMqB,UAAU,GAAG;AAC7C,UAAMiB,UAAUtC,MAAM,CAAA;AACtB,WAAO;MACLD,QAAQwC,MAAMvC,MAAM,CAAA,CAAE;MACtBL,MAAMN,MAAMC,QAAQgD,OAAAA,IAAWA,QAAQ/C,IAAIgD,KAAAA,IAAS,CAAA;IACtD;EACF;AACA,MAAI,OAAOvC,UAAU,YAAYA,UAAU,QAAQ,YAAYA,SAAS,UAAUA,OAAO;AACvF,UAAM,EAAED,QAAQJ,KAAI,IAAKK;AACzB,WAAO;MACLD,QAAQwC,MAAMxC,MAAAA;MACdJ,MAAMN,MAAMC,QAAQK,IAAAA,IAAQA,KAAKJ,IAAIgD,KAAAA,IAAS,CAAA;IAChD;EACF;AACA,SAAO;IAAExC,QAAQ;IAAKJ,MAAM,CAAA;EAAG;AACjC;AAhBSO;AAkBT,SAASqC,MAAMpD,OAAc;AAC3B,MAAI,OAAOA,UAAU,UAAU;AAC7B,WAAOA;EACT;AACA,SAAOsC,OAAOe,SAASrD,KAAAA,IAASA,MAAMsD,SAAS,MAAA,IAAUpE,OAAOc,KAAAA;AAClE;AALSoD;AAaT,SAASvB,iBAAiBhB,OAAgBrC,QAAc;AACtD,MAAI0B,MAAMC,QAAQU,KAAAA,GAAQ;AACxB,UAAMe,WAAsB,CAAA;AAC5B,aAASrD,QAAQ,GAAGA,QAAQ,IAAIsC,MAAMqB,QAAQ3D,SAAS,GAAG;AACxD,YAAMgF,YAAY1C,MAAMtC,QAAQ,CAAA;AAChC,UAAI2B,MAAMC,QAAQoD,SAAAA,GAAY;AAC5B,cAAMC,QAAgC,CAAC;AACvC,iBAASxE,QAAQ,GAAGA,QAAQ,IAAIuE,UAAUrB,QAAQlD,SAAS,GAAG;AAC5DwE,gBAAMJ,MAAMG,UAAUvE,KAAAA,CAAM,CAAA,IAAKoE,MAAMG,UAAUvE,QAAQ,CAAA,CAAE;QAC7D;AACA4C,iBAAS3C,KAAKwE,UAAUL,MAAMvC,MAAMtC,KAAAA,CAAM,GAAGiF,OAAOhF,MAAAA,CAAAA;MACtD;IACF;AACA,WAAOoD;EACT;AACA,MAAI,OAAOf,UAAU,YAAYA,UAAU,QAAQ,aAAaA,OAAO;AACrE,UAAM6C,UAAW7C,MAA+B6C;AAChD,QAAIxD,MAAMC,QAAQuD,OAAAA,GAAU;AAC1B,aAAOA,QAAQtD,IAAI,CAACuD,WAAWF,UAAUG,OAAOD,MAAAA,GAASE,eAAeF,MAAAA,GAASnF,MAAAA,CAAAA;IACnF;EACF;AACA,SAAO,CAAA;AACT;AAtBSqD;AAwBT,SAAS+B,OAAOD,QAAe;AAC7B,MAAI,OAAOA,WAAW,YAAYA,WAAW,QAAQ,QAAQA,QAAQ;AACnE,WAAOP,MAAOO,OAA2BpE,EAAE;EAC7C;AACA,SAAO;AACT;AALSqE;AAOT,SAASC,eAAeF,QAAe;AACrC,QAAMH,QAAgC,CAAC;AACvC,MAAI,OAAOG,WAAW,YAAYA,WAAW,QAAQ,sBAAsBA,QAAQ;AACjF,UAAMG,MAAOH,OAAyCI;AACtD,QAAI,OAAOD,QAAQ,YAAYA,QAAQ,MAAM;AAC3C,iBAAW,CAAC9C,KAAKhB,KAAAA,KAAU0C,OAAOE,QAAQkB,GAAAA,GAAM;AAC9CN,cAAMxC,GAAAA,IAAOoC,MAAMpD,KAAAA;MACrB;IACF;EACF;AACA,SAAOwD;AACT;AAXSK;AAaT,SAASJ,UAAUO,OAAeR,OAA+BhF,QAAc;AAC7E,QAAMyF,WAAWC,OAAOV,MAAMW,gBAAgB,GAAA;AAC9C,QAAMxE,SAAS6D,MAAM7D;AACrB,SAAO;IACLJ,IAAIyE,MAAMI,WAAW5F,MAAAA,IAAUwF,MAAMK,MAAM7F,OAAO0D,MAAM,IAAI8B;IAC5DxE,MAAMgE,MAAMhE,QAAQ;IACpB8E,OAAO,IAAIL;IACX,GAAItE,WAAWC,SAAY;MAAED;IAAO,IAAI,CAAC;IACzC,GAAG4E,cAAcf,MAAMgB,aAAa;EACtC;AACF;AAVSf;AAYT,SAASc,cAAcT,KAAuB;AAC5C,MAAIA,QAAQlE,QAAW;AACrB,WAAO,CAAC;EACV;AACA,MAAI;AACF,UAAM6E,SAAkB3E,KAAK4E,MAAMZ,GAAAA;AACnC,QAAI,OAAOW,WAAW,YAAYA,WAAW,MAAM;AACjD,aAAO;QAAE5E,UAAU4E;MAAkC;IACvD;EACF,QAAQ;EAER;AACA,SAAO,CAAC;AACV;AAbSF;;;ACvVF,IAAMI,qBAAN,MAAMA;EALb,OAKaA;;;;;EACX,YACmBC,UACAC,OACjB;SAFiBD,WAAAA;SACAC,QAAAA;EAChB;EAEH,MAAMC,SAASC,OAAeC,UAA2B,CAAC,GAAuB;AAC/E,UAAM,CAACC,SAAAA,IAAa,MAAM,KAAKL,SAASM,MAAM;MAACH;KAAM;AACrD,QAAIE,cAAcE,QAAW;AAC3B,aAAO,CAAA;IACT;AACA,WAAO,KAAKN,MAAMO,OAAOH,WAAW;MAClCI,MAAML,QAAQK,QAAQ;MACtB,GAAIL,QAAQM,WAAWH,SAAY;QAAEG,QAAQN,QAAQM;MAAO,IAAI,CAAC;IACnE,CAAA;EACF;AACF;;;ACLO,IAAMC,mBAAN,MAAMA;EAvBb,OAuBaA;;;EACMC,OAAO,oBAAIC,IAAAA;EACXC,oBAAoB,oBAAID,IAAAA;EACjCE,cAAc;EACLC;EACAC;EAEjB,YAAYC,UAAmC,CAAC,GAAG;AACjD,SAAKF,KAAKE,QAAQF,MAAM;AACxB,SAAKC,IAAIC,QAAQD,KAAK;EACxB;;EAGAE,IAAIC,SAA8B;AAChC,eAAWC,UAAUD,SAAS;AAC5B,WAAKE,OAAOD,OAAOE,EAAE;AACrB,YAAMC,SAASC,SAASJ,OAAOK,IAAI;AACnC,YAAMC,cAAc,oBAAId,IAAAA;AACxB,iBAAWe,SAASJ,QAAQ;AAC1BG,oBAAYE,IAAID,QAAQD,YAAYG,IAAIF,KAAAA,KAAU,KAAK,CAAA;MACzD;AACA,iBAAWG,QAAQJ,YAAYK,KAAI,GAAI;AACrC,aAAKlB,kBAAkBe,IAAIE,OAAO,KAAKjB,kBAAkBgB,IAAIC,IAAAA,KAAS,KAAK,CAAA;MAC7E;AACA,WAAKnB,KAAKiB,IAAIR,OAAOE,IAAI;QAAEF;QAAQM;QAAaM,QAAQT,OAAOS;MAAO,CAAA;AACtE,WAAKlB,eAAeS,OAAOS;IAC7B;EACF;EAEQX,OAAOC,IAAkB;AAC/B,UAAMW,WAAW,KAAKtB,KAAKkB,IAAIP,EAAAA;AAC/B,QAAIW,aAAaC,QAAW;AAC1B;IACF;AACA,eAAWJ,QAAQG,SAASP,YAAYK,KAAI,GAAI;AAC9C,YAAMI,QAAQ,KAAKtB,kBAAkBgB,IAAIC,IAAAA,KAAS,KAAK;AACvD,UAAIK,QAAQ,GAAG;AACb,aAAKtB,kBAAkBuB,OAAON,IAAAA;MAChC,OAAO;AACL,aAAKjB,kBAAkBe,IAAIE,MAAMK,IAAAA;MACnC;IACF;AACA,SAAKrB,eAAemB,SAASD;AAC7B,SAAKrB,KAAKyB,OAAOd,EAAAA;EACnB;EAEA,MAAMe,SAASC,OAAerB,UAA2B,CAAC,GAAuB;AAC/E,UAAMsB,QAAQ,KAAK5B,KAAK6B;AACxB,QAAID,UAAU,GAAG;AACf,aAAO,CAAA;IACT;AACA,UAAME,gBAAgB,KAAK3B,cAAcyB;AACzC,UAAMG,aAAa,IAAIC,IAAInB,SAASc,KAAAA,CAAAA;AACpC,UAAMM,SAAoB,CAAA;AAC1B,eAAW,EAAExB,QAAQM,aAAaM,OAAM,KAAM,KAAKrB,KAAKkC,OAAM,GAAI;AAChE,UAAI5B,QAAQ6B,WAAWZ,UAAa,CAACa,cAAc3B,OAAO4B,UAAU/B,QAAQ6B,MAAM,GAAG;AACnF;MACF;AACA,UAAIG,QAAQ;AACZ,iBAAWnB,QAAQY,YAAY;AAC7B,cAAMQ,gBAAgBxB,YAAYG,IAAIC,IAAAA;AACtC,YAAIoB,kBAAkBhB,QAAW;AAC/B;QACF;AACA,cAAMiB,KAAK,KAAKtC,kBAAkBgB,IAAIC,IAAAA,KAAS;AAC/C,cAAMsB,MAAMC,KAAKC,IAAI,KAAKf,QAAQY,KAAK,QAAQA,KAAK,IAAE;AACtD,cAAMI,cACJL,gBAAgB,KAAKnC,MAAM,IAAI,KAAKC,IAAK,KAAKA,IAAIgB,SAAUS;AAC9DQ,iBAASG,OAAQF,iBAAiB,KAAKnC,KAAK,KAAMwC;MACpD;AACA,UAAIN,QAAQ,GAAG;AACbL,eAAOY,KAAK;UACVlC,IAAIF,OAAOE;UACXG,MAAML,OAAOK;UACbwB;UACA,GAAI7B,OAAOqC,WAAWvB,SAAY;YAAEuB,QAAQrC,OAAOqC;UAAO,IAAI,CAAC;UAC/D,GAAIrC,OAAO4B,aAAad,SAAY;YAAEc,UAAU5B,OAAO4B;UAAS,IAAI,CAAC;QACvE,CAAA;MACF;IACF;AACA,WAAOJ,OAAOc,KAAK,CAACC,GAAG3C,MAAMA,EAAEiC,QAAQU,EAAEV,KAAK,EAAEW,MAAM,GAAG3C,QAAQ4C,QAAQ,CAAA;EAC3E;AACF;AAEA,SAASrC,SAASC,MAAY;AAC5B,SAAOA,KAAKqC,YAAW,EAAGC,MAAM,YAAA,KAAiB,CAAA;AACnD;AAFSvC;;;AC3FF,IAAMwC,kBAAN,MAAMA;EANb,OAMaA;;;;;EACX,YACmBC,YACAC,UAAkC,CAAC,GACpD;SAFiBD,aAAAA;SACAC,UAAAA;EAChB;EAEH,MAAMC,SAASC,OAAeF,UAA2B,CAAC,GAAuB;AAC/E,UAAMG,IAAI,KAAKH,QAAQG,KAAK;AAC5B,UAAMC,YAAY,KAAKJ,QAAQI,aAAa;AAC5C,UAAMC,UAAU,KAAKL,QAAQK;AAC7B,UAAMC,QAAQ,MAAMC,QAAQC,IAC1B,KAAKT,WAAWU,IAAI,CAACC,cACnBA,UAAUT,SAASC,OAAO;MACxBS,MAAMP;MACN,GAAIJ,QAAQY,WAAWC,SAAY;QAAED,QAAQZ,QAAQY;MAAO,IAAI,CAAC;IACnE,CAAA,CAAA,CAAA;AAIJ,UAAME,QAAQ,oBAAIC,IAAAA;AAClBT,UAAMU,QAAQ,CAACC,MAAMC,cAAAA;AACnB,YAAMC,SAASd,UAAUa,SAAAA,KAAc;AACvCD,WAAKD,QAAQ,CAACI,SAASC,SAAAA;AACrB,cAAMC,eAAeH,UAAUhB,IAAIkB,OAAO;AAC1C,cAAME,WAAWT,MAAMU,IAAIJ,QAAQK,EAAE;AACrC,YAAIF,aAAaV,QAAW;AAC1BU,mBAASG,SAASJ;QACpB,OAAO;AACLR,gBAAMa,IAAIP,QAAQK,IAAI;YAAEL;YAASM,OAAOJ;UAAa,CAAA;QACvD;MACF,CAAA;IACF,CAAA;AAEA,WAAO;SAAIR,MAAMc,OAAM;MACpBC,KAAK,CAACC,GAAGC,MAAMA,EAAEL,QAAQI,EAAEJ,KAAK,EAChCM,MAAM,GAAGhC,QAAQW,QAAQ,CAAA,EACzBF,IAAI,CAACwB,WAAW;MAAE,GAAGA,MAAMb;MAASM,OAAOO,MAAMP;IAAM,EAAA;EAC5D;AACF;;;AC1CO,IAAMQ,qBAAN,MAAMA;EANb,OAMaA;;;;;;EACX,YACmBC,MACAC,UACAC,UAAqC,CAAC,GACvD;SAHiBF,OAAAA;SACAC,WAAAA;SACAC,UAAAA;EAChB;EAEH,MAAMC,SAASC,OAAeF,UAA2B,CAAC,GAAuB;AAC/E,UAAMG,aAAa,MAAM,KAAKL,KAAKG,SAASC,OAAO;MACjDE,MAAM,KAAKJ,QAAQK,aAAa;MAChC,GAAIL,QAAQM,WAAWC,SAAY;QAAED,QAAQN,QAAQM;MAAO,IAAI,CAAC;IACnE,CAAA;AACA,WAAO,KAAKP,SAASS,OAAON,OAAOC,YAAY;MAAEC,MAAMJ,QAAQI,QAAQ;IAAE,CAAA;EAC3E;AACF;;;AChBO,IAAMK,oBAAN,MAAMA;EATb,OASaA;;;;;EACX,YACmBC,MACAC,QACjB;SAFiBD,OAAAA;SACAC,SAAAA;EAChB;EAEH,MAAMC,SAASC,OAAeC,UAA2B,CAAC,GAAuB;AAC/E,WAAO,KAAKJ,KAAKE,SAASC,OAAO;MAC/B,GAAGC;MACHH,QAAQ;QAAE,GAAGG,QAAQH;QAAQ,GAAG,KAAKA;MAAO;IAC9C,CAAA;EACF;AACF;;;ACSO,SAASI,eACdC,WACAC,UAAwB,CAAC,GAAC;AAE1B,QAAMC,SAAwB,CAAA;AAC9B,aAAWC,YAAYH,WAAW;AAChCI,cAAUD,SAASE,MAAMJ,OAAAA,EAASK,QAAQ,CAACD,MAAME,UAAAA;AAC/CL,aAAOM,KAAK;QACVC,IAAI,GAAGN,SAASM,EAAE,IAAIF,KAAAA;QACtBF;QACA,GAAIF,SAASO,WAAWC,SAAY;UAAED,QAAQP,SAASO;QAAO,IAAI,CAAC;QACnE,GAAIP,SAASS,aAAaD,SAAY;UAAEC,UAAUT,SAASS;QAAS,IAAI,CAAC;MAC3E,CAAA;IACF,CAAA;EACF;AACA,SAAOV;AACT;AAhBgBH;AAmBhB,eAAsBc,aACpBX,QACAD,SAA4B;AAE5B,MAAIC,OAAOY,WAAW,GAAG;AACvB,WAAO;EACT;AACA,QAAMC,aAAa,MAAMd,QAAQe,SAASC,MAAMf,OAAOgB,IAAI,CAACC,UAAUA,MAAMd,IAAI,CAAA;AAChF,QAAMe,UAA0BlB,OAAOgB,IAAI,CAACC,OAAOZ,WAAW;IAC5D,GAAGY;IACHE,WAAWN,WAAWR,KAAAA,KAAU,CAAA;EAClC,EAAA;AACA,QAAMN,QAAQqB,MAAMC,OAAOH,OAAAA;AAC3B,SAAOA,QAAQN;AACjB;AAdsBD;AAkBtB,eAAsBW,gBACpBxB,WACAC,SAAsB;AAEtB,SAAOY,aAAad,eAAeC,WAAWC,OAAAA,GAAUA,OAAAA;AAC1D;AALsBuB;;;ACpDtB,SAASC,aAAaC,OAAc;AAClC,SACE,OAAOA,UAAU,YACjBA,UAAU,QACV,WAAWA,SACX,OAAQA,MAAmCC,UAAU;AAEzD;AAPSF;AAUT,IAAMG,cAAmD;EACvD,aAAa;IACXC,SAAS;IACTC,QAAQ;IACRC,SAASL,OAAc;AACrB,aAAOD,aAAaC,KAAAA,IAChB;QAAEA,OAAO;UAAEC,OAAOD,MAAMC;QAAM;MAAE,IAChC;QAAEK,QAAQ;UAAC;YAAEC,SAAS;UAA2C;;MAAG;IAC1E;EACF;AACF;AAWO,SAASC,oBACdC,WACAC,UAAgC,CAAC,GAAC;AAElC,QAAMC,OAAOD,QAAQC;AACrB,SAAO;IACLC,MAAM;MACJC,MAAMH,QAAQG,QAAQ;MACtBC,MAAM;MACNC,aACEL,QAAQK,eACR;MACFC,aAAad;IACf;IACAe,SAAS;MACP,MAAMC,QAAQC,OAAc;AAC1B,YAAI,CAACpB,aAAaoB,KAAAA,GAAQ;AACxB,gBAAM,IAAIC,MAAM,0CAAA;QAClB;AACA,cAAMC,WAAW,MAAMZ,UAAUa,SAASH,MAAMlB,OAAOU,SAASY,SAAY;UAAEZ;QAAK,IAAI,CAAC,CAAA;AACxF,eAAO;UAAEU;QAAS;MACpB;IACF;EACF;AACF;AAxBgBb;","names":["chunkText","text","options","chunkSize","overlap","normalized","trim","length","chunks","start","end","Math","min","breakAt","window","slice","boundary","max","lastIndexOf","chunk","push","documentIdOf","chunkId","replace","matchesFilter","metadata","filter","entries","Object","undefined","length","every","key","expected","Array","isArray","actual","some","value","includes","MemoryVectorStore","records","Map","upsert","record","set","id","remove","documentId","keys","documentIdOf","delete","listDocuments","filter","documents","values","undefined","matchesFilter","metadata","has","search","embedding","options","scored","push","text","score","cosineSimilarity","source","sort","a","b","slice","topK","dot","normA","normB","length","Math","min","index","valueA","valueB","denominator","sqrt","PgVectorStore","table","dimensions","client","options","ensureSchema","query","upsert","records","record","id","text","source","metadata","undefined","JSON","stringify","toVectorLiteral","embedding","remove","documentId","DOCUMENT_ID_FROM_CHUNK","listDocuments","filter","where","buildWhere","rows","sql","params","map","row","doc_id","search","vector","topK","score","Number","startIndex","Object","keys","length","clauses","scalar","index","key","value","entries","Array","isArray","push","keyParam","arrParam","String","join","RedisVectorStore","index","prefix","dimensions","filterableFields","client","options","ensureSchema","sendCommand","schema","field","push","String","upsert","records","record","args","id","text","encodeVector","embedding","source","undefined","metadata","JSON","stringify","value","tag","Array","isArray","map","join","remove","documentId","keys","Set","pattern","escapeGlob","cursor","reply","page","parseScanReply","key","add","size","listDocuments","filter","filterMatchesNothing","query","buildFilter","documents","Map","batchSize","offset","passages","parseSearchReply","passage","documentIdOf","has","set","length","values","search","topK","Buffer","from","Float32Array","buffer","Object","clauses","entries","alternation","entry","escapeTag","some","replace","char","rawKeys","toStr","isBuffer","toString","rawFields","attrs","toPassage","results","result","readId","readAttributes","raw","extra_attributes","rawId","distance","Number","vector_score","startsWith","slice","score","parseMetadata","metadata_json","parsed","parse","EmbeddingRetriever","embedder","store","retrieve","query","options","embedding","embed","undefined","search","topK","filter","KeywordRetriever","docs","Map","documentFrequency","totalLength","k1","b","options","add","records","record","remove","id","tokens","tokenize","text","frequencies","token","set","get","term","keys","length","existing","undefined","next","delete","retrieve","query","total","size","averageLength","queryTerms","Set","scored","values","filter","matchesFilter","metadata","score","termFrequency","df","idf","Math","log","denominator","push","source","sort","a","slice","topK","toLowerCase","match","HybridRetriever","retrievers","options","retrieve","query","k","fetchTopK","weights","lists","Promise","all","map","retriever","topK","filter","undefined","fused","Map","forEach","list","listIndex","weight","passage","rank","contribution","existing","get","id","score","set","values","sort","a","b","slice","entry","RerankingRetriever","base","reranker","options","retrieve","query","candidates","topK","fetchTopK","filter","undefined","rerank","FilteredRetriever","base","filter","retrieve","query","options","chunkDocuments","documents","options","chunks","document","chunkText","text","forEach","index","push","id","source","undefined","metadata","ingestChunks","length","embeddings","embedder","embed","map","chunk","records","embedding","store","upsert","ingestDocuments","isQueryInput","value","query","querySchema","version","vendor","validate","issues","message","createRetrievalTool","retriever","options","topK","spec","name","kind","description","inputSchema","handler","execute","input","Error","passages","retrieve","undefined"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dudousxd/nestjs-agent-rag",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/DavideCarvalho/nestjs-agent.git",
@@ -29,7 +29,7 @@
29
29
  "dist"
30
30
  ],
31
31
  "peerDependencies": {
32
- "@dudousxd/nestjs-agent-core": "0.9.0"
32
+ "@dudousxd/nestjs-agent-core": "0.10.0"
33
33
  },
34
34
  "dependencies": {
35
35
  "@standard-schema/spec": "1.1.0"
@@ -43,8 +43,8 @@
43
43
  "tsup": "8.3.5",
44
44
  "typescript": "5.9.3",
45
45
  "vitest": "2.1.9",
46
- "@dudousxd/nestjs-agent-core": "0.9.0",
47
- "@dudousxd/nestjs-agent-testing": "0.8.0"
46
+ "@dudousxd/nestjs-agent-core": "0.10.0",
47
+ "@dudousxd/nestjs-agent-testing": "0.8.1"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsup",