@arnilo/prism-rag 0.0.96 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,11 +1,128 @@
1
1
  # Changelog
2
+
3
+ ## [0.1.0] - 2026-08-09
4
+
5
+ ### Changed
6
+ - Released with exact 0.1.0 graph.
7
+
8
+ ## [0.0.28] - 2026-08-08
9
+
10
+ ### Changed
11
+ - Released with exact 0.0.28 graph.
12
+
13
+ ## [0.0.27] - 2026-08-07
14
+
15
+ ### Changed
16
+ - Released with exact 0.0.27 graph.
17
+
18
+ ## [0.0.26] - 2026-08-06
19
+
20
+ ### Changed
21
+ - Released with exact 0.0.26 graph.
22
+
23
+ ## [0.0.25] - 2026-08-06
24
+
25
+ ### Changed
26
+ - Released with exact 0.0.25 graph.
27
+
28
+ ## [0.0.24] - 2026-08-04
29
+
30
+ ### Added
31
+ - Durable `AgentEventSource` (memory + PostgreSQL LISTEN/NOTIFY), recoverable `ToolEffectStore`, and AG-UI MCP/MCP Apps/A2A fronting for Phase 7.
32
+
33
+ ### Changed
34
+ - Publishable graph remains **47** manifests at **0.0.24**; peers and lockfile move together.
35
+
36
+ See [migration guide](../../docs/migration.md) for the 0.0.23 → 0.0.24 notes.
37
+
38
+ ## [0.0.23] - 2026-08-03
39
+
40
+ ### Changed
41
+ - Released with exact 0.0.23 graph.
42
+
43
+ ## [0.0.22] - 2026-07-31
44
+
45
+ ### Changed
46
+ - Released with exact 0.0.22 graph.
47
+
48
+ ## [0.0.21] - 2026-07-31
49
+
50
+ ### Changed
51
+ - Released with exact 0.0.21 graph.
52
+
53
+ ## [0.0.20] - 2026-07-31
54
+
55
+ ### Changed
56
+ - Released with exact 0.0.20 graph.
57
+
58
+ ## [0.0.19] - 2026-07-30
59
+
60
+ ### Changed
61
+ - Released with exact 0.0.19 graph.
62
+
63
+ ## [0.0.18] - 2026-07-30
64
+
65
+ ### Changed
66
+ - Released with exact 0.0.18 graph.
67
+
68
+ ## [0.0.17] - 2026-07-29
69
+
70
+ ### Changed
71
+ - Released with exact 0.0.17 graph.
72
+
73
+ ## [0.0.16] - 2026-07-26
74
+
75
+ ### Changed
76
+ - Sourced `resolveRedactor` from `@arnilo/prism` core and deleted the private duplicate; no public API change.
77
+
78
+ ## [0.0.15] - 2026-07-26
79
+
80
+ ### Added
81
+
82
+ - Added atomic scoped `replaceSource()` / `deleteSource()` and `replaceDocument()` over source-aware transactional vector storage; the in-memory reference store implements `getBySource()` plus transactions.
83
+ - Added bounded `DocumentLoader` / `Parser` seams, reference text/Markdown/HTML/PDF parsers, and host resource/web-tools document loaders with citation/trust preservation.
84
+ - Added host-owned bounded reranking, canonical citation provenance/content-trust metadata, and scoped capped ingestion-status storage/querying with a process-local reference adapter.
85
+
86
+ ## [0.0.14] - 2026-07-26
87
+
88
+ ### Changed
89
+
90
+ - Released with exact 0.0.14 graph.
91
+
92
+ ## [0.0.13] - 2026-07-24
93
+
94
+ ### Changed
95
+
96
+ - Released with exact 0.0.13 graph.
97
+
98
+ ## [0.0.12] - 2026-07-22
99
+
100
+ ### Changed
101
+
102
+ - Released with exact 0.0.12 graph.
103
+
104
+ ## [0.0.11] - 2026-07-22
105
+
106
+ ### Changed
107
+
108
+ - Released with exact 0.0.11 graph.
109
+
110
+ ## [0.0.10] - 2026-07-21
111
+
112
+ ### Changed
113
+
114
+ - Released with exact 0.0.10 graph.
115
+
116
+ ### Changed
117
+
118
+ - Released with exact 0.0.10 graph.
119
+
2
120
  ## [0.0.96] - 2026-07-21
3
121
 
4
122
  ### Changed
5
123
 
6
124
  - Released with exact 0.0.96 graph.
7
125
 
8
-
9
126
  ## [0.0.9] - 2026-07-21
10
127
 
11
128
  - Released with the exact 0.0.9 first-party package graph.
@@ -26,7 +143,6 @@
26
143
 
27
144
  - Added optional bounded text/Markdown chunking, vector indexing/retrieval, and stable citations.
28
145
 
29
-
30
146
  ## [0.0.4] - 2026-07-14
31
147
 
32
148
  - Initial release: bounded deterministic text/Markdown chunking, batch vector indexing, filtered retrieval, stable citations, and ContextProvider integration.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @arnilo/prism-rag
2
2
 
3
- Optional bounded text/Markdown retrieval-augmented generation primitives for Prism. Reuses `Embedder` and `VectorStore` from `@arnilo/prism-memory`; no document framework, network loader, or core activation.
3
+ Optional bounded source lifecycle, document parsing, host reranking, ingestion status, and retrieval-augmented generation primitives for Prism. Reuses `Embedder` and `VectorStore` from `@arnilo/prism-memory`; no document framework, network loader, or core activation.
4
4
 
5
5
  ## Install
6
6
 
@@ -12,7 +12,7 @@ npm install @arnilo/prism-rag @arnilo/prism-memory @arnilo/prism
12
12
 
13
13
  ```ts
14
14
  import { createHashEmbedder, createMemoryVectorStore } from "@arnilo/prism-memory";
15
- import { chunkMarkdown, createRagContextProvider, indexChunks } from "@arnilo/prism-rag";
15
+ import { chunkMarkdown, createMemoryIngestionStatusStore, createRagContextProvider, listIngestionStatus, replaceSource } from "@arnilo/prism-rag";
16
16
 
17
17
  const embedder = createHashEmbedder(); // demo/test only
18
18
  const store = createMemoryVectorStore();
@@ -20,7 +20,9 @@ const scope = { tenantId: "t1", resourceId: "docs", corpusId: "handbook" };
20
20
  const chunks = chunkMarkdown("# Approval\n\nRecheck policy before side effects.", {
21
21
  sourceId: "security-guide",
22
22
  });
23
- await indexChunks({ chunks, embedder, store, scope });
23
+ const statusStore = createMemoryIngestionStatusStore();
24
+ await replaceSource({ sourceId: "security-guide", chunks, embedder, store, scope, statusStore }); // atomic with the reference store
25
+ console.log(await listIngestionStatus({ store: statusStore, scope }));
24
26
  const context = createRagContextProvider({ embedder, store, scope, topK: 4 });
25
27
  ```
26
28
 
@@ -28,11 +30,15 @@ const context = createRagContextProvider({ embedder, store, scope, topK: 4 });
28
30
 
29
31
  - `chunkText()` / `chunkMarkdown()` — deterministic boundary-aware character chunks with overlap and stable citations.
30
32
  - `indexChunks()` — bounded batch embedding and scoped vector upsert.
31
- - `retrieveContext()` bounded candidate query, shallow metadata filter, top-K hits, and citation rendering.
33
+ - `replaceSource()` / `deleteSource()` exact-scope source lifecycle; replacement requires transactional `getBySource()` storage.
34
+ - `replaceDocument()` + `DocumentLoader` / `Parser` — host-authorized load, bounded parse, chunk, and replacement.
35
+ - `textParser` / `markdownParser` / `htmlParser` / `pdfParser` — focused reference parsers (`./parsers`); `createResourceDocumentLoader` / `createWebFetchDocumentLoader` are in `./loaders`.
36
+ - `retrieveContext()` — bounded candidate query, optional host `Reranker`, shallow metadata filter, top-K hits, attributable citations, and untrusted/inert/injection-capable trust metadata.
37
+ - `createMemoryIngestionStatusStore()` / `listIngestionStatus()` — optional capped exact-scope pending/indexed/failed/partial source progress; implement `IngestionStatusStore` for durable status.
32
38
  - `createRagContextProvider()` — explicit inert context injection through Prism's existing seam.
33
39
 
34
40
  ## Security
35
41
 
36
- Every operation requires tenant/resource/corpus scope. Configure `redactor` or `secrets` before external embedding/persistence. Package performs no I/O; load remote/local sources through host-owned resource/media policies. Retrieved text is untrusted context and grants no tools or permissions.
42
+ Every operation requires tenant/resource/corpus scope. Configure `redactor` or `secrets` before external embedding/persistence. Package performs no I/O: resource loading delegates to a host `ResourceLoader`, and web loading delegates to an existing web-tools adapter. Replacement fails closed without a scoped transaction; rerankers get redacted finite candidates and cannot alter canonical provenance/trust; HTML/PDF/web output is untrusted inert context and grants no tools or permissions.
37
43
 
38
44
  See [RAG](../../docs/rag.md).
package/dist/chunk.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import type { RagChunk, ChunkOptions } from "./types.js";
1
+ import type { ChunkOptions, RagChunk } from "./types.js";
2
2
  export declare function chunkText(text: string, options: ChunkOptions): readonly RagChunk[];
3
3
  export declare function chunkMarkdown(markdown: string, options: ChunkOptions): readonly RagChunk[];
package/dist/chunk.js CHANGED
@@ -1,5 +1,5 @@
1
- import { resolveRagLimits } from "./limits.js";
2
1
  import { RagLimitError } from "./errors.js";
2
+ import { resolveRagLimits } from "./limits.js";
3
3
  import { assertBytes, requireSourceId } from "./util.js";
4
4
  export function chunkText(text, options) {
5
5
  return chunkDocument(text, options, false);
package/dist/context.js CHANGED
@@ -7,18 +7,20 @@ export function createRagContextProvider(options) {
7
7
  context.signal?.throwIfAborted();
8
8
  const query = typeof options.query === "function"
9
9
  ? options.query({ messages: context.messages })
10
- : options.query ?? latestUserText(context.messages);
10
+ : (options.query ?? latestUserText(context.messages));
11
11
  if (!query?.trim())
12
12
  return [];
13
13
  const result = await retrieveContext(query, { ...options, signal: context.signal });
14
14
  if (!result.text)
15
15
  return [];
16
- return [{
16
+ return [
17
+ {
17
18
  id: `${options.name ?? "rag"}:context`,
18
19
  title: options.title ?? "Retrieved context",
19
20
  content: result.text,
20
- metadata: { citations: result.citations, inert: true },
21
- }];
21
+ metadata: { citations: result.citations, trust: result.trust, inert: true, untrusted: true, injectionCapable: true },
22
+ },
23
+ ];
22
24
  },
23
25
  };
24
26
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,14 @@
1
- export { DEFAULT_CHUNK_SIZE, HARD_CHUNK_SIZE_CAP, DEFAULT_CHUNK_OVERLAP, HARD_CHUNK_OVERLAP_CAP, DEFAULT_MAX_DOCUMENT_CHARS, HARD_MAX_DOCUMENT_CHARS_CAP, DEFAULT_MAX_CHUNKS, HARD_MAX_CHUNKS_CAP, DEFAULT_EMBED_BATCH_SIZE, HARD_EMBED_BATCH_SIZE_CAP, DEFAULT_TOP_K, HARD_TOP_K_CAP, DEFAULT_QUERY_CANDIDATES, HARD_QUERY_CANDIDATES_CAP, DEFAULT_MAX_RESULT_BYTES, HARD_MAX_RESULT_BYTES_CAP, DEFAULT_MAX_CONTEXT_TOKENS, HARD_MAX_CONTEXT_TOKENS_CAP, DEFAULT_MAX_METADATA_BYTES, HARD_MAX_METADATA_BYTES_CAP, DEFAULT_MAX_VECTOR_DIMENSIONS, resolveRagLimits, } from "./limits.js";
2
- export type { RagLimits, RagLimitsInput } from "./limits.js";
3
- export { RagAbortError, RagError, RagLimitError, RagScopeError, RagValidationError } from "./errors.js";
4
1
  export { chunkMarkdown, chunkText } from "./chunk.js";
2
+ export { createRagContextProvider } from "./context.js";
3
+ export { RagAbortError, RagError, RagLimitError, RagScopeError, RagValidationError } from "./errors.js";
5
4
  export { indexChunks } from "./indexing.js";
5
+ export { createMemoryIngestionStatusStore, listIngestionStatus } from "./ingestion-status.js";
6
+ export type { RagLimits, RagLimitsInput } from "./limits.js";
7
+ export { DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE, DEFAULT_EMBED_BATCH_SIZE, DEFAULT_INGESTION_STATUS_PAGE_SIZE, DEFAULT_MAX_CHUNKS, DEFAULT_MAX_CONTEXT_TOKENS, DEFAULT_MAX_DOCUMENT_BYTES, DEFAULT_MAX_DOCUMENT_CHARS, DEFAULT_MAX_METADATA_BYTES, DEFAULT_MAX_PARSE_MS, DEFAULT_MAX_PDF_PAGES, DEFAULT_MAX_RERANK_BYTES, DEFAULT_MAX_RERANK_MS, DEFAULT_MAX_RESULT_BYTES, DEFAULT_MAX_VECTOR_DIMENSIONS, DEFAULT_QUERY_CANDIDATES, DEFAULT_RERANK_CONCURRENCY, DEFAULT_TOP_K, HARD_CHUNK_OVERLAP_CAP, HARD_CHUNK_SIZE_CAP, HARD_EMBED_BATCH_SIZE_CAP, HARD_INGESTION_STATUS_PAGE_SIZE_CAP, HARD_MAX_CHUNKS_CAP, HARD_MAX_CONTEXT_TOKENS_CAP, HARD_MAX_DOCUMENT_BYTES_CAP, HARD_MAX_DOCUMENT_CHARS_CAP, HARD_MAX_METADATA_BYTES_CAP, HARD_MAX_PARSE_MS_CAP, HARD_MAX_PDF_PAGES_CAP, HARD_MAX_RERANK_BYTES_CAP, HARD_MAX_RERANK_MS_CAP, HARD_MAX_RESULT_BYTES_CAP, HARD_QUERY_CANDIDATES_CAP, HARD_RERANK_CONCURRENCY_CAP, HARD_TOP_K_CAP, resolveRagLimits, } from "./limits.js";
8
+ export { createResourceDocumentLoader, createWebFetchDocumentLoader } from "./loaders.js";
9
+ export { htmlParser, markdownParser, pdfParser, textParser } from "./parsers.js";
6
10
  export { retrieveContext } from "./retrieve.js";
7
- export { createRagContextProvider } from "./context.js";
8
- export type { ChunkOptions, IndexChunksOptions, IndexChunksResult, RagChunk, RagCitation, RagContextProvider, RagContextProviderOptions, RagContextResult, RagHit, RagScope, RetrieveContextOptions, } from "./types.js";
11
+ export type { SourceMutationResult } from "./sources.js";
12
+ export { deleteSource, replaceDocument, replaceSource } from "./sources.js";
13
+ export type { Chunker, ChunkOptions, DeleteSourceOptions, DocumentLoader, DocumentLoadOptions, DocumentParseOptions, IndexChunksOptions, IndexChunksResult, IngestionState, IngestionStatus, IngestionStatusQuery, IngestionStatusStore, LoadedDocument, ParsedDocument, Parser, RagChunk, RagCitation, RagContentTrust, RagContextProvider, RagContextProviderOptions, RagContextResult, RagHit, RagProvenance, RagScope, ReplaceDocumentOptions, ReplaceSourceOptions, Reranker, RetrieveContextOptions, SourceVectorStore, TransactionalVectorStore, } from "./types.js";
9
14
  export declare const packageName = "@arnilo/prism-rag";
package/dist/index.js CHANGED
@@ -1,8 +1,12 @@
1
- export { DEFAULT_CHUNK_SIZE, HARD_CHUNK_SIZE_CAP, DEFAULT_CHUNK_OVERLAP, HARD_CHUNK_OVERLAP_CAP, DEFAULT_MAX_DOCUMENT_CHARS, HARD_MAX_DOCUMENT_CHARS_CAP, DEFAULT_MAX_CHUNKS, HARD_MAX_CHUNKS_CAP, DEFAULT_EMBED_BATCH_SIZE, HARD_EMBED_BATCH_SIZE_CAP, DEFAULT_TOP_K, HARD_TOP_K_CAP, DEFAULT_QUERY_CANDIDATES, HARD_QUERY_CANDIDATES_CAP, DEFAULT_MAX_RESULT_BYTES, HARD_MAX_RESULT_BYTES_CAP, DEFAULT_MAX_CONTEXT_TOKENS, HARD_MAX_CONTEXT_TOKENS_CAP, DEFAULT_MAX_METADATA_BYTES, HARD_MAX_METADATA_BYTES_CAP, DEFAULT_MAX_VECTOR_DIMENSIONS, resolveRagLimits, } from "./limits.js";
2
- export { RagAbortError, RagError, RagLimitError, RagScopeError, RagValidationError } from "./errors.js";
3
1
  export { chunkMarkdown, chunkText } from "./chunk.js";
2
+ export { createRagContextProvider } from "./context.js";
3
+ export { RagAbortError, RagError, RagLimitError, RagScopeError, RagValidationError } from "./errors.js";
4
4
  export { indexChunks } from "./indexing.js";
5
+ export { createMemoryIngestionStatusStore, listIngestionStatus } from "./ingestion-status.js";
6
+ export { DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE, DEFAULT_EMBED_BATCH_SIZE, DEFAULT_INGESTION_STATUS_PAGE_SIZE, DEFAULT_MAX_CHUNKS, DEFAULT_MAX_CONTEXT_TOKENS, DEFAULT_MAX_DOCUMENT_BYTES, DEFAULT_MAX_DOCUMENT_CHARS, DEFAULT_MAX_METADATA_BYTES, DEFAULT_MAX_PARSE_MS, DEFAULT_MAX_PDF_PAGES, DEFAULT_MAX_RERANK_BYTES, DEFAULT_MAX_RERANK_MS, DEFAULT_MAX_RESULT_BYTES, DEFAULT_MAX_VECTOR_DIMENSIONS, DEFAULT_QUERY_CANDIDATES, DEFAULT_RERANK_CONCURRENCY, DEFAULT_TOP_K, HARD_CHUNK_OVERLAP_CAP, HARD_CHUNK_SIZE_CAP, HARD_EMBED_BATCH_SIZE_CAP, HARD_INGESTION_STATUS_PAGE_SIZE_CAP, HARD_MAX_CHUNKS_CAP, HARD_MAX_CONTEXT_TOKENS_CAP, HARD_MAX_DOCUMENT_BYTES_CAP, HARD_MAX_DOCUMENT_CHARS_CAP, HARD_MAX_METADATA_BYTES_CAP, HARD_MAX_PARSE_MS_CAP, HARD_MAX_PDF_PAGES_CAP, HARD_MAX_RERANK_BYTES_CAP, HARD_MAX_RERANK_MS_CAP, HARD_MAX_RESULT_BYTES_CAP, HARD_QUERY_CANDIDATES_CAP, HARD_RERANK_CONCURRENCY_CAP, HARD_TOP_K_CAP, resolveRagLimits, } from "./limits.js";
7
+ export { createResourceDocumentLoader, createWebFetchDocumentLoader } from "./loaders.js";
8
+ export { htmlParser, markdownParser, pdfParser, textParser } from "./parsers.js";
5
9
  export { retrieveContext } from "./retrieve.js";
6
- export { createRagContextProvider } from "./context.js";
10
+ export { deleteSource, replaceDocument, replaceSource } from "./sources.js";
7
11
  export const packageName = "@arnilo/prism-rag";
8
12
  //# sourceMappingURL=index.js.map
@@ -1,2 +1,4 @@
1
+ import type { MemoryVectorRecord } from "@arnilo/prism-memory";
1
2
  import type { IndexChunksOptions, IndexChunksResult } from "./types.js";
2
3
  export declare function indexChunks(options: IndexChunksOptions): Promise<IndexChunksResult>;
4
+ export declare function indexChunkBatches(options: IndexChunksOptions, write: (records: readonly MemoryVectorRecord[]) => Promise<void>): Promise<IndexChunksResult>;
package/dist/indexing.js CHANGED
@@ -1,9 +1,12 @@
1
- import { HARD_CHUNK_SIZE_CAP, resolveRagLimits } from "./limits.js";
1
+ import { resolveRedactor } from "@arnilo/prism";
2
2
  import { RagValidationError } from "./errors.js";
3
- import { assertBytes, assertNotAborted, nonEmpty, requireScope, requireSourceId, resolveRedactor } from "./util.js";
4
- // ponytail: stable IDs make identical retries idempotent; replacing a source with
5
- // fewer chunks requires host deletion of stale source IDs before this generic upsert.
3
+ import { ingestionStatus } from "./ingestion-status.js";
4
+ import { HARD_CHUNK_SIZE_CAP, resolveRagLimits } from "./limits.js";
5
+ import { assertBytes, assertNotAborted, byteLength, nonEmpty, requireScope, requireSourceId } from "./util.js";
6
6
  export async function indexChunks(options) {
7
+ return indexChunkBatches(options, async (records) => options.store.upsert(records, { signal: options.signal }));
8
+ }
9
+ export async function indexChunkBatches(options, write) {
7
10
  const scope = requireScope(options.scope);
8
11
  const limits = resolveRagLimits({
9
12
  embedBatchSize: options.batchSize,
@@ -12,78 +15,97 @@ export async function indexChunks(options) {
12
15
  maxVectorDimensions: options.maxVectorDimensions,
13
16
  maxMetadataBytes: options.maxMetadataBytes,
14
17
  });
15
- if (!Number.isInteger(options.embedder.dimensions)
16
- || options.embedder.dimensions <= 0
17
- || options.embedder.dimensions > limits.maxVectorDimensions) {
18
+ if (!Number.isInteger(options.embedder.dimensions) ||
19
+ options.embedder.dimensions <= 0 ||
20
+ options.embedder.dimensions > limits.maxVectorDimensions) {
18
21
  throw new RagValidationError(`embedder dimensions must be an integer in 1..${limits.maxVectorDimensions}`);
19
22
  }
20
- if (options.chunks.length > limits.maxChunks) {
23
+ if (options.chunks.length > limits.maxChunks)
21
24
  throw new RagValidationError(`chunk count exceeds ${limits.maxChunks}`);
22
- }
23
25
  const redactor = resolveRedactor(options.redactor, options.secrets);
24
26
  const sourceIds = new Set();
25
27
  const chunkIds = new Set();
28
+ const total = new Map();
29
+ const written = new Map();
26
30
  for (const chunk of options.chunks) {
27
31
  nonEmpty(chunk.id, "chunk.id");
28
32
  requireSourceId(chunk.sourceId);
29
- if (chunk.id !== chunk.citationId || !chunk.citationId.startsWith(`${chunk.sourceId}#`)) {
33
+ if (chunk.id !== chunk.citationId || !chunk.citationId.startsWith(`${chunk.sourceId}#`))
30
34
  throw new RagValidationError("chunk has inconsistent citation identity");
31
- }
32
35
  if (chunkIds.has(chunk.id))
33
36
  throw new RagValidationError(`duplicate chunk id: ${chunk.id}`);
34
- if (!Number.isInteger(chunk.index)
35
- || chunk.index < 0
36
- || !Number.isInteger(chunk.start)
37
- || chunk.start < 0
38
- || !Number.isInteger(chunk.end)
39
- || chunk.end < chunk.start) {
37
+ if (!Number.isInteger(chunk.index) ||
38
+ chunk.index < 0 ||
39
+ !Number.isInteger(chunk.start) ||
40
+ chunk.start < 0 ||
41
+ !Number.isInteger(chunk.end) ||
42
+ chunk.end < chunk.start) {
40
43
  throw new RagValidationError("chunk has invalid index or offsets");
41
44
  }
42
45
  if (chunk.text.length > limits.chunkSize)
43
46
  throw new RagValidationError(`chunk text exceeds ${limits.chunkSize} characters`);
44
47
  chunkIds.add(chunk.id);
48
+ sourceIds.add(chunk.sourceId);
49
+ add(total, chunk.sourceId, byteLength(redactor?.redact(chunk.text) ?? chunk.text), 1);
45
50
  }
46
- for (let offset = 0; offset < options.chunks.length; offset += limits.embedBatchSize) {
47
- assertNotAborted(options.signal);
48
- const batch = options.chunks.slice(offset, offset + limits.embedBatchSize);
49
- const texts = batch.map((chunk) => redactor?.redact(chunk.text) ?? chunk.text);
50
- const vectors = await options.embedder.embed(texts, { signal: options.signal });
51
- if (vectors.length !== batch.length)
52
- throw new RagValidationError("embedder returned unexpected vector count");
53
- const records = batch.map((chunk, index) => {
54
- const embedding = vectors[index];
55
- if (embedding.length !== options.embedder.dimensions
56
- || embedding.some((value) => !Number.isFinite(value))) {
57
- throw new RagValidationError(`embedder returned invalid vector; expected ${options.embedder.dimensions} finite values`);
58
- }
59
- const safeMetadata = redactor?.redact(chunk.metadata ?? {}) ?? (chunk.metadata ?? {});
60
- const metadata = {
61
- ...safeMetadata,
62
- _rag: {
63
- sourceId: chunk.sourceId,
64
- citationId: chunk.citationId,
65
- chunkIndex: chunk.index,
66
- start: chunk.start,
67
- end: chunk.end,
68
- },
69
- };
70
- assertBytes(metadata, limits.maxMetadataBytes, "chunk metadata");
71
- sourceIds.add(chunk.sourceId);
72
- return {
73
- id: chunk.id,
74
- tenantId: scope.tenantId,
75
- resourceId: scope.resourceId,
76
- threadId: scope.corpusId,
77
- text: texts[index],
78
- embedding,
79
- sequence: chunk.index,
80
- metadata: metadata,
81
- createdAt: new Date(0).toISOString(),
82
- };
83
- });
84
- assertNotAborted(options.signal);
85
- await options.store.upsert(records, { signal: options.signal });
51
+ const setStatus = async (state, error) => {
52
+ if (!options.statusStore)
53
+ return;
54
+ const message = error instanceof Error ? error.message : error === undefined ? undefined : "indexing failed";
55
+ const safeError = message ? (redactor?.redact(message) ?? message) : undefined;
56
+ for (const sourceId of sourceIds) {
57
+ const progress = state === "indexed" ? total.get(sourceId) : (written.get(sourceId) ?? { bytes: 0, chunks: 0 });
58
+ await options.statusStore.set(ingestionStatus(scope, sourceId, state, progress.bytes, progress.chunks, safeError));
59
+ }
60
+ };
61
+ await setStatus("pending");
62
+ try {
63
+ for (let offset = 0; offset < options.chunks.length; offset += limits.embedBatchSize) {
64
+ assertNotAborted(options.signal);
65
+ const batch = options.chunks.slice(offset, offset + limits.embedBatchSize);
66
+ const texts = batch.map((chunk) => redactor?.redact(chunk.text) ?? chunk.text);
67
+ const vectors = await options.embedder.embed(texts, { signal: options.signal });
68
+ if (vectors.length !== batch.length)
69
+ throw new RagValidationError("embedder returned unexpected vector count");
70
+ const records = batch.map((chunk, index) => {
71
+ const embedding = vectors[index];
72
+ if (embedding.length !== options.embedder.dimensions || embedding.some((value) => !Number.isFinite(value))) {
73
+ throw new RagValidationError(`embedder returned invalid vector; expected ${options.embedder.dimensions} finite values`);
74
+ }
75
+ const safeMetadata = redactor?.redact(chunk.metadata ?? {}) ?? chunk.metadata ?? {};
76
+ const metadata = {
77
+ ...safeMetadata,
78
+ _rag: { sourceId: chunk.sourceId, citationId: chunk.citationId, chunkIndex: chunk.index, start: chunk.start, end: chunk.end },
79
+ };
80
+ assertBytes(metadata, limits.maxMetadataBytes, "chunk metadata");
81
+ return {
82
+ id: chunk.id,
83
+ tenantId: scope.tenantId,
84
+ resourceId: scope.resourceId,
85
+ threadId: scope.corpusId,
86
+ text: texts[index],
87
+ embedding,
88
+ sequence: chunk.index,
89
+ metadata: metadata,
90
+ createdAt: new Date(0).toISOString(),
91
+ };
92
+ });
93
+ assertNotAborted(options.signal);
94
+ await write(records);
95
+ for (const chunk of batch)
96
+ add(written, chunk.sourceId, byteLength(redactor?.redact(chunk.text) ?? chunk.text), 1);
97
+ await setStatus("pending");
98
+ }
99
+ await setStatus("indexed");
100
+ }
101
+ catch (error) {
102
+ await setStatus([...written.values()].some((progress) => progress.chunks > 0) ? "partial" : "failed", error);
103
+ throw error;
86
104
  }
87
105
  return Object.freeze({ indexed: options.chunks.length, sourceIds: Object.freeze([...sourceIds].sort()) });
88
106
  }
107
+ function add(target, sourceId, bytes, chunks) {
108
+ const current = target.get(sourceId) ?? { bytes: 0, chunks: 0 };
109
+ target.set(sourceId, { bytes: current.bytes + bytes, chunks: current.chunks + chunks });
110
+ }
89
111
  //# sourceMappingURL=indexing.js.map
@@ -0,0 +1,7 @@
1
+ import type { IngestionStatus, IngestionStatusQuery, IngestionStatusStore, RagScope } from "./types.js";
2
+ export declare function listIngestionStatus(options: IngestionStatusQuery): Promise<{
3
+ readonly entries: readonly IngestionStatus[];
4
+ readonly nextCursor?: string;
5
+ }>;
6
+ export declare function createMemoryIngestionStatusStore(): IngestionStatusStore;
7
+ export declare function ingestionStatus(scope: RagScope, sourceId: string, state: IngestionStatus["state"], bytes: number, chunks: number, error?: string): IngestionStatus;
@@ -0,0 +1,87 @@
1
+ import { RagScopeError, RagValidationError } from "./errors.js";
2
+ import { resolveRagLimits } from "./limits.js";
3
+ import { assertNotAborted, byteLength, requireScope, requireSourceId } from "./util.js";
4
+ export async function listIngestionStatus(options) {
5
+ const scope = requireScope(options.scope);
6
+ const limit = resolveRagLimits({ ingestionStatusPageSize: options.limit }).ingestionStatusPageSize;
7
+ if (options.cursor !== undefined)
8
+ requireSourceId(options.cursor);
9
+ assertNotAborted(options.signal);
10
+ const page = await options.store.list(scope, { limit, cursor: options.cursor, signal: options.signal });
11
+ if (page.entries.length > limit)
12
+ throw new RagValidationError("ingestion status store exceeded requested page limit");
13
+ for (const entry of page.entries)
14
+ assertStatus(entry, scope);
15
+ if (page.nextCursor !== undefined)
16
+ requireSourceId(page.nextCursor);
17
+ return Object.freeze({ entries: Object.freeze([...page.entries]), ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}) });
18
+ }
19
+ export function createMemoryIngestionStatusStore() {
20
+ const scopes = new Map();
21
+ return {
22
+ async set(status, options = {}) {
23
+ assertNotAborted(options.signal);
24
+ const scope = requireScope(status.scope);
25
+ assertStatus(status, scope);
26
+ const key = scopeKey(scope);
27
+ const entries = scopes.get(key) ?? new Map();
28
+ entries.set(status.sourceId, Object.freeze({ ...status, scope }));
29
+ scopes.set(key, entries);
30
+ },
31
+ async delete(scope, sourceId, options = {}) {
32
+ assertNotAborted(options.signal);
33
+ const entries = scopes.get(scopeKey(requireScope(scope)));
34
+ entries?.delete(requireSourceId(sourceId));
35
+ },
36
+ async list(scope, options) {
37
+ assertNotAborted(options.signal);
38
+ const required = requireScope(scope);
39
+ const limit = options.limit;
40
+ if (!Number.isInteger(limit) || limit < 1)
41
+ throw new RagValidationError("ingestion status limit must be a positive integer");
42
+ if (options.cursor !== undefined)
43
+ requireSourceId(options.cursor);
44
+ const entries = [...(scopes.get(scopeKey(required))?.values() ?? [])]
45
+ .filter((entry) => entry.sourceId > (options.cursor ?? ""))
46
+ .sort((left, right) => left.sourceId.localeCompare(right.sourceId));
47
+ const page = entries.slice(0, limit);
48
+ const next = entries.length > page.length ? page.at(-1)?.sourceId : undefined;
49
+ return Object.freeze({ entries: Object.freeze(page), ...(next ? { nextCursor: next } : {}) });
50
+ },
51
+ };
52
+ }
53
+ export function ingestionStatus(scope, sourceId, state, bytes, chunks, error) {
54
+ const status = {
55
+ sourceId: requireSourceId(sourceId),
56
+ scope: requireScope(scope),
57
+ state,
58
+ bytes,
59
+ chunks,
60
+ ...(error ? { error } : {}),
61
+ updatedAt: new Date().toISOString(),
62
+ };
63
+ assertStatus(status, status.scope);
64
+ return Object.freeze(status);
65
+ }
66
+ function assertStatus(status, scope) {
67
+ requireSourceId(status.sourceId);
68
+ if (status.scope.tenantId !== scope.tenantId ||
69
+ status.scope.resourceId !== scope.resourceId ||
70
+ status.scope.corpusId !== scope.corpusId) {
71
+ throw new RagScopeError("ingestion status crossed tenant/resource/corpus boundary");
72
+ }
73
+ if (!["pending", "indexed", "failed", "partial"].includes(status.state))
74
+ throw new RagValidationError("ingestion status state is invalid");
75
+ if (!Number.isSafeInteger(status.bytes) || status.bytes < 0 || !Number.isSafeInteger(status.chunks) || status.chunks < 0) {
76
+ throw new RagValidationError("ingestion status bytes and chunks must be non-negative safe integers");
77
+ }
78
+ if (!Number.isFinite(Date.parse(status.updatedAt)))
79
+ throw new RagValidationError("ingestion status updatedAt must be an ISO timestamp");
80
+ if (status.error !== undefined && (typeof status.error !== "string" || byteLength(status.error) > 4_096)) {
81
+ throw new RagValidationError("ingestion status error must be a string <= 4096 bytes");
82
+ }
83
+ }
84
+ function scopeKey(scope) {
85
+ return `${scope.tenantId}\0${scope.resourceId}\0${scope.corpusId}`;
86
+ }
87
+ //# sourceMappingURL=ingestion-status.js.map
package/dist/limits.d.ts CHANGED
@@ -4,6 +4,12 @@ export declare const DEFAULT_CHUNK_OVERLAP = 100;
4
4
  export declare const HARD_CHUNK_OVERLAP_CAP = 4096;
5
5
  export declare const DEFAULT_MAX_DOCUMENT_CHARS = 1048576;
6
6
  export declare const HARD_MAX_DOCUMENT_CHARS_CAP = 8388608;
7
+ export declare const DEFAULT_MAX_DOCUMENT_BYTES = 1048576;
8
+ export declare const HARD_MAX_DOCUMENT_BYTES_CAP: number;
9
+ export declare const DEFAULT_MAX_PARSE_MS = 30000;
10
+ export declare const HARD_MAX_PARSE_MS_CAP = 30000;
11
+ export declare const DEFAULT_MAX_PDF_PAGES = 256;
12
+ export declare const HARD_MAX_PDF_PAGES_CAP = 256;
7
13
  export declare const DEFAULT_MAX_CHUNKS = 2048;
8
14
  export declare const HARD_MAX_CHUNKS_CAP = 8192;
9
15
  export declare const DEFAULT_EMBED_BATCH_SIZE = 32;
@@ -19,10 +25,21 @@ export declare const HARD_MAX_CONTEXT_TOKENS_CAP = 8000;
19
25
  export declare const DEFAULT_MAX_METADATA_BYTES: number;
20
26
  export declare const HARD_MAX_METADATA_BYTES_CAP: number;
21
27
  export declare const DEFAULT_MAX_VECTOR_DIMENSIONS = 4096;
28
+ export declare const DEFAULT_MAX_RERANK_BYTES: number;
29
+ export declare const HARD_MAX_RERANK_BYTES_CAP: number;
30
+ export declare const DEFAULT_MAX_RERANK_MS = 2000;
31
+ export declare const HARD_MAX_RERANK_MS_CAP = 10000;
32
+ export declare const DEFAULT_RERANK_CONCURRENCY = 2;
33
+ export declare const HARD_RERANK_CONCURRENCY_CAP = 8;
34
+ export declare const DEFAULT_INGESTION_STATUS_PAGE_SIZE = 50;
35
+ export declare const HARD_INGESTION_STATUS_PAGE_SIZE_CAP = 200;
22
36
  export interface RagLimits {
23
37
  readonly chunkSize: number;
24
38
  readonly chunkOverlap: number;
25
39
  readonly maxDocumentChars: number;
40
+ readonly maxDocumentBytes: number;
41
+ readonly maxParseMs: number;
42
+ readonly maxPdfPages: number;
26
43
  readonly maxChunks: number;
27
44
  readonly embedBatchSize: number;
28
45
  readonly topK: number;
@@ -31,6 +48,10 @@ export interface RagLimits {
31
48
  readonly maxContextTokens: number;
32
49
  readonly maxMetadataBytes: number;
33
50
  readonly maxVectorDimensions: number;
51
+ readonly maxRerankBytes: number;
52
+ readonly maxRerankMs: number;
53
+ readonly rerankConcurrency: number;
54
+ readonly ingestionStatusPageSize: number;
34
55
  }
35
56
  export type RagLimitsInput = Partial<RagLimits>;
36
57
  export declare function resolveRagLimits(input?: RagLimitsInput): RagLimits;
package/dist/limits.js CHANGED
@@ -5,6 +5,12 @@ export const DEFAULT_CHUNK_OVERLAP = 100;
5
5
  export const HARD_CHUNK_OVERLAP_CAP = 4_096;
6
6
  export const DEFAULT_MAX_DOCUMENT_CHARS = 1_048_576;
7
7
  export const HARD_MAX_DOCUMENT_CHARS_CAP = 8_388_608;
8
+ export const DEFAULT_MAX_DOCUMENT_BYTES = 1_048_576;
9
+ export const HARD_MAX_DOCUMENT_BYTES_CAP = 8 * 1024 * 1024;
10
+ export const DEFAULT_MAX_PARSE_MS = 30_000;
11
+ export const HARD_MAX_PARSE_MS_CAP = 30_000;
12
+ export const DEFAULT_MAX_PDF_PAGES = 256;
13
+ export const HARD_MAX_PDF_PAGES_CAP = 256;
8
14
  export const DEFAULT_MAX_CHUNKS = 2_048;
9
15
  export const HARD_MAX_CHUNKS_CAP = 8_192;
10
16
  export const DEFAULT_EMBED_BATCH_SIZE = 32;
@@ -20,6 +26,14 @@ export const HARD_MAX_CONTEXT_TOKENS_CAP = 8_000;
20
26
  export const DEFAULT_MAX_METADATA_BYTES = 16 * 1024;
21
27
  export const HARD_MAX_METADATA_BYTES_CAP = 64 * 1024;
22
28
  export const DEFAULT_MAX_VECTOR_DIMENSIONS = 4_096;
29
+ export const DEFAULT_MAX_RERANK_BYTES = 64 * 1024;
30
+ export const HARD_MAX_RERANK_BYTES_CAP = 256 * 1024;
31
+ export const DEFAULT_MAX_RERANK_MS = 2_000;
32
+ export const HARD_MAX_RERANK_MS_CAP = 10_000;
33
+ export const DEFAULT_RERANK_CONCURRENCY = 2;
34
+ export const HARD_RERANK_CONCURRENCY_CAP = 8;
35
+ export const DEFAULT_INGESTION_STATUS_PAGE_SIZE = 50;
36
+ export const HARD_INGESTION_STATUS_PAGE_SIZE_CAP = 200;
23
37
  function integer(value, fallback, cap, label, minimum = 1) {
24
38
  const resolved = value ?? fallback;
25
39
  if (!Number.isInteger(resolved) || resolved < minimum) {
@@ -42,6 +56,9 @@ export function resolveRagLimits(input = {}) {
42
56
  chunkSize,
43
57
  chunkOverlap,
44
58
  maxDocumentChars: integer(input.maxDocumentChars, DEFAULT_MAX_DOCUMENT_CHARS, HARD_MAX_DOCUMENT_CHARS_CAP, "maxDocumentChars"),
59
+ maxDocumentBytes: integer(input.maxDocumentBytes, DEFAULT_MAX_DOCUMENT_BYTES, HARD_MAX_DOCUMENT_BYTES_CAP, "maxDocumentBytes"),
60
+ maxParseMs: integer(input.maxParseMs, DEFAULT_MAX_PARSE_MS, HARD_MAX_PARSE_MS_CAP, "maxParseMs"),
61
+ maxPdfPages: integer(input.maxPdfPages, DEFAULT_MAX_PDF_PAGES, HARD_MAX_PDF_PAGES_CAP, "maxPdfPages"),
45
62
  maxChunks: integer(input.maxChunks, DEFAULT_MAX_CHUNKS, HARD_MAX_CHUNKS_CAP, "maxChunks"),
46
63
  embedBatchSize: integer(input.embedBatchSize, DEFAULT_EMBED_BATCH_SIZE, HARD_EMBED_BATCH_SIZE_CAP, "embedBatchSize"),
47
64
  topK,
@@ -50,6 +67,10 @@ export function resolveRagLimits(input = {}) {
50
67
  maxContextTokens: integer(input.maxContextTokens, DEFAULT_MAX_CONTEXT_TOKENS, HARD_MAX_CONTEXT_TOKENS_CAP, "maxContextTokens"),
51
68
  maxMetadataBytes: integer(input.maxMetadataBytes, DEFAULT_MAX_METADATA_BYTES, HARD_MAX_METADATA_BYTES_CAP, "maxMetadataBytes"),
52
69
  maxVectorDimensions: integer(input.maxVectorDimensions, DEFAULT_MAX_VECTOR_DIMENSIONS, DEFAULT_MAX_VECTOR_DIMENSIONS, "maxVectorDimensions"),
70
+ maxRerankBytes: integer(input.maxRerankBytes, DEFAULT_MAX_RERANK_BYTES, HARD_MAX_RERANK_BYTES_CAP, "maxRerankBytes"),
71
+ maxRerankMs: integer(input.maxRerankMs, DEFAULT_MAX_RERANK_MS, HARD_MAX_RERANK_MS_CAP, "maxRerankMs"),
72
+ rerankConcurrency: integer(input.rerankConcurrency, DEFAULT_RERANK_CONCURRENCY, HARD_RERANK_CONCURRENCY_CAP, "rerankConcurrency"),
73
+ ingestionStatusPageSize: integer(input.ingestionStatusPageSize, DEFAULT_INGESTION_STATUS_PAGE_SIZE, HARD_INGESTION_STATUS_PAGE_SIZE_CAP, "ingestionStatusPageSize"),
53
74
  });
54
75
  }
55
76
  //# sourceMappingURL=limits.js.map