@arnilo/prism-rag 0.0.14 → 0.0.16
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 +13 -0
- package/README.md +11 -5
- package/dist/chunk.d.ts +1 -1
- package/dist/chunk.js +1 -1
- package/dist/context.js +6 -4
- package/dist/index.d.ts +10 -5
- package/dist/index.js +7 -3
- package/dist/indexing.d.ts +2 -0
- package/dist/indexing.js +79 -57
- package/dist/ingestion-status.d.ts +7 -0
- package/dist/ingestion-status.js +87 -0
- package/dist/limits.d.ts +21 -0
- package/dist/limits.js +21 -0
- package/dist/loaders.d.ts +24 -0
- package/dist/loaders.js +78 -0
- package/dist/parsers.d.ts +5 -0
- package/dist/parsers.js +101 -0
- package/dist/rerank.d.ts +11 -0
- package/dist/rerank.js +62 -0
- package/dist/retrieve.js +64 -36
- package/dist/sources.d.ts +9 -0
- package/dist/sources.js +118 -0
- package/dist/types.d.ts +128 -1
- package/dist/util.d.ts +1 -2
- package/dist/util.js +1 -7
- package/package.json +11 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.16] - 2026-07-26
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Sourced `resolveRedactor` from `@arnilo/prism` core and deleted the private duplicate; no public API change.
|
|
7
|
+
|
|
8
|
+
## [0.0.15] - 2026-07-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added atomic scoped `replaceSource()` / `deleteSource()` and `replaceDocument()` over source-aware transactional vector storage; the in-memory reference store implements `getBySource()` plus transactions.
|
|
13
|
+
- Added bounded `DocumentLoader` / `Parser` seams, reference text/Markdown/HTML/PDF parsers, and host resource/web-tools document loaders with citation/trust preservation.
|
|
14
|
+
- Added host-owned bounded reranking, canonical citation provenance/content-trust metadata, and scoped capped ingestion-status storage/querying with a process-local reference adapter.
|
|
15
|
+
|
|
3
16
|
## [0.0.14] - 2026-07-26
|
|
4
17
|
|
|
5
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @arnilo/prism-rag
|
|
2
2
|
|
|
3
|
-
Optional bounded
|
|
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,
|
|
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
|
-
|
|
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
|
-
- `
|
|
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
|
|
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 {
|
|
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 {
|
|
8
|
-
export
|
|
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 {
|
|
10
|
+
export { deleteSource, replaceDocument, replaceSource } from "./sources.js";
|
|
7
11
|
export const packageName = "@arnilo/prism-rag";
|
|
8
12
|
//# sourceMappingURL=index.js.map
|
package/dist/indexing.d.ts
CHANGED
|
@@ -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 {
|
|
1
|
+
import { resolveRedactor } from "@arnilo/prism";
|
|
2
2
|
import { RagValidationError } from "./errors.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ResourceLoadContext, ResourceLoader } from "@arnilo/prism";
|
|
2
|
+
import type { DocumentLoader } from "./types.js";
|
|
3
|
+
export interface ResourceDocumentLoaderOptions {
|
|
4
|
+
readonly loader: ResourceLoader;
|
|
5
|
+
readonly context?: ResourceLoadContext;
|
|
6
|
+
}
|
|
7
|
+
export interface WebFetchDocument {
|
|
8
|
+
readonly citationId: string;
|
|
9
|
+
readonly provider: string;
|
|
10
|
+
readonly url: string;
|
|
11
|
+
readonly markdown: string;
|
|
12
|
+
readonly retrievedAt: string;
|
|
13
|
+
readonly untrusted: true;
|
|
14
|
+
}
|
|
15
|
+
export interface WebFetchAdapter {
|
|
16
|
+
fetch(url: string, options?: {
|
|
17
|
+
readonly signal?: AbortSignal;
|
|
18
|
+
}): Promise<WebFetchDocument>;
|
|
19
|
+
}
|
|
20
|
+
export interface WebFetchDocumentLoaderOptions {
|
|
21
|
+
readonly fetcher: WebFetchAdapter;
|
|
22
|
+
}
|
|
23
|
+
export declare function createResourceDocumentLoader(options: ResourceDocumentLoaderOptions): DocumentLoader;
|
|
24
|
+
export declare function createWebFetchDocumentLoader(options: WebFetchDocumentLoaderOptions): DocumentLoader;
|
package/dist/loaders.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { RagLimitError, RagValidationError } from "./errors.js";
|
|
2
|
+
import { resolveRagLimits } from "./limits.js";
|
|
3
|
+
import { nonEmpty } from "./util.js";
|
|
4
|
+
export function createResourceDocumentLoader(options) {
|
|
5
|
+
return {
|
|
6
|
+
async load(uri, loadOptions = {}) {
|
|
7
|
+
nonEmpty(uri, "uri");
|
|
8
|
+
const context = { ...options.context, ...(loadOptions.signal ? { signal: loadOptions.signal } : {}) };
|
|
9
|
+
const resource = await options.loader.load(uri, context);
|
|
10
|
+
if (resource.text === undefined && resource.data === undefined)
|
|
11
|
+
throw new RagValidationError("resource has no text or data");
|
|
12
|
+
const document = Object.freeze({
|
|
13
|
+
uri: resource.uri,
|
|
14
|
+
...(resource.mediaType ? { mediaType: resource.mediaType } : {}),
|
|
15
|
+
...(resource.text !== undefined ? { text: resource.text } : { data: new Uint8Array(resource.data) }),
|
|
16
|
+
});
|
|
17
|
+
assertDocumentBytes(document, loadOptions.maxBytes);
|
|
18
|
+
return document;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function createWebFetchDocumentLoader(options) {
|
|
23
|
+
return {
|
|
24
|
+
async load(uri, loadOptions = {}) {
|
|
25
|
+
const url = publicWebUrl(uri);
|
|
26
|
+
const document = await options.fetcher.fetch(url, { signal: loadOptions.signal });
|
|
27
|
+
publicWebUrl(document.url);
|
|
28
|
+
if (!document.untrusted || !document.markdown)
|
|
29
|
+
throw new RagValidationError("web fetcher returned an invalid untrusted document");
|
|
30
|
+
const loaded = Object.freeze({
|
|
31
|
+
uri: document.url,
|
|
32
|
+
sourceId: document.citationId,
|
|
33
|
+
mediaType: "text/markdown",
|
|
34
|
+
text: document.markdown,
|
|
35
|
+
metadata: {
|
|
36
|
+
web: {
|
|
37
|
+
citationId: document.citationId,
|
|
38
|
+
provider: document.provider,
|
|
39
|
+
url: document.url,
|
|
40
|
+
retrievedAt: document.retrievedAt,
|
|
41
|
+
},
|
|
42
|
+
untrusted: true,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
assertDocumentBytes(loaded, loadOptions.maxBytes);
|
|
46
|
+
return loaded;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function assertDocumentBytes(document, maxBytes) {
|
|
51
|
+
const limit = resolveRagLimits({ maxDocumentBytes: maxBytes }).maxDocumentBytes;
|
|
52
|
+
const bytes = document.data ?? Buffer.from(document.text ?? "", "utf8");
|
|
53
|
+
if (bytes.byteLength > limit)
|
|
54
|
+
throw new RagLimitError(`document exceeds ${limit} bytes`);
|
|
55
|
+
}
|
|
56
|
+
function publicWebUrl(input) {
|
|
57
|
+
let url;
|
|
58
|
+
try {
|
|
59
|
+
url = new URL(input);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new RagValidationError("web document URI must be an absolute HTTP(S) URL");
|
|
63
|
+
}
|
|
64
|
+
const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
65
|
+
if (!/^https?:$/u.test(url.protocol) || url.username || url.password || privateOrLocalHost(host)) {
|
|
66
|
+
throw new RagValidationError("web document URI must use a public hostname without credentials");
|
|
67
|
+
}
|
|
68
|
+
url.hash = "";
|
|
69
|
+
return url.toString();
|
|
70
|
+
}
|
|
71
|
+
function privateOrLocalHost(host) {
|
|
72
|
+
if (!host || host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local"))
|
|
73
|
+
return true;
|
|
74
|
+
if (/^\d{1,3}(?:\.\d{1,3}){3}$/u.test(host) || host.includes(":"))
|
|
75
|
+
return true; // Require fetcher DNS policy for hostnames; never fetch IP literals.
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=loaders.js.map
|