agentfootprint 8.9.0 → 8.10.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/AGENTS.md +2 -0
- package/CLAUDE.md +4 -0
- package/bin/agentfootprint-index.mjs +192 -0
- package/dist/doors/rag.js +66 -0
- package/dist/doors/rag.js.map +1 -0
- package/dist/esm/doors/rag.d.ts +49 -0
- package/dist/esm/doors/rag.js +50 -0
- package/dist/esm/doors/rag.js.map +1 -0
- package/dist/esm/rag/hash.d.ts +2 -0
- package/dist/esm/rag/hash.js +22 -0
- package/dist/esm/rag/hash.js.map +1 -0
- package/dist/esm/rag/index.d.ts +17 -0
- package/dist/esm/rag/index.js +17 -0
- package/dist/esm/rag/index.js.map +1 -0
- package/dist/esm/rag/indexCorpus.d.ts +102 -0
- package/dist/esm/rag/indexCorpus.js +339 -0
- package/dist/esm/rag/indexCorpus.js.map +1 -0
- package/dist/esm/rag/indexFolder.d.ts +40 -0
- package/dist/esm/rag/indexFolder.js +42 -0
- package/dist/esm/rag/indexFolder.js.map +1 -0
- package/dist/esm/rag/loadDocuments.d.ts +34 -0
- package/dist/esm/rag/loadDocuments.js +160 -0
- package/dist/esm/rag/loadDocuments.js.map +1 -0
- package/dist/esm/rag/loaders/html.d.ts +42 -0
- package/dist/esm/rag/loaders/html.js +48 -0
- package/dist/esm/rag/loaders/html.js.map +1 -0
- package/dist/esm/rag/loaders/index.d.ts +30 -0
- package/dist/esm/rag/loaders/index.js +24 -0
- package/dist/esm/rag/loaders/index.js.map +1 -0
- package/dist/esm/rag/loaders/markdown.d.ts +27 -0
- package/dist/esm/rag/loaders/markdown.js +12 -0
- package/dist/esm/rag/loaders/markdown.js.map +1 -0
- package/dist/esm/rag/loaders/mock.d.ts +28 -0
- package/dist/esm/rag/loaders/mock.js +14 -0
- package/dist/esm/rag/loaders/mock.js.map +1 -0
- package/dist/esm/rag/loaders/pdf.d.ts +39 -0
- package/dist/esm/rag/loaders/pdf.js +90 -0
- package/dist/esm/rag/loaders/pdf.js.map +1 -0
- package/dist/esm/rag/loaders/text.d.ts +23 -0
- package/dist/esm/rag/loaders/text.js +21 -0
- package/dist/esm/rag/loaders/text.js.map +1 -0
- package/dist/esm/rag/splitDocuments.d.ts +21 -0
- package/dist/esm/rag/splitDocuments.js +65 -0
- package/dist/esm/rag/splitDocuments.js.map +1 -0
- package/dist/esm/rag/splitters/byHeading.d.ts +36 -0
- package/dist/esm/rag/splitters/byHeading.js +66 -0
- package/dist/esm/rag/splitters/byHeading.js.map +1 -0
- package/dist/esm/rag/splitters/byParagraph.d.ts +24 -0
- package/dist/esm/rag/splitters/byParagraph.js +16 -0
- package/dist/esm/rag/splitters/byParagraph.js.map +1 -0
- package/dist/esm/rag/splitters/constants.d.ts +19 -0
- package/dist/esm/rag/splitters/constants.js +20 -0
- package/dist/esm/rag/splitters/constants.js.map +1 -0
- package/dist/esm/rag/splitters/fixedWithOverlap.d.ts +31 -0
- package/dist/esm/rag/splitters/fixedWithOverlap.js +34 -0
- package/dist/esm/rag/splitters/fixedWithOverlap.js.map +1 -0
- package/dist/esm/rag/splitters/index.d.ts +50 -0
- package/dist/esm/rag/splitters/index.js +51 -0
- package/dist/esm/rag/splitters/index.js.map +1 -0
- package/dist/esm/rag/splitters/shared.d.ts +87 -0
- package/dist/esm/rag/splitters/shared.js +188 -0
- package/dist/esm/rag/splitters/shared.js.map +1 -0
- package/dist/esm/rag/splitters/wholeDocument.d.ts +16 -0
- package/dist/esm/rag/splitters/wholeDocument.js +10 -0
- package/dist/esm/rag/splitters/wholeDocument.js.map +1 -0
- package/dist/esm/rag/types.d.ts +200 -0
- package/dist/esm/rag/types.js +20 -0
- package/dist/esm/rag/types.js.map +1 -0
- package/dist/rag/hash.js +26 -0
- package/dist/rag/hash.js.map +1 -0
- package/dist/rag/index.js +40 -0
- package/dist/rag/index.js.map +1 -0
- package/dist/rag/indexCorpus.js +344 -0
- package/dist/rag/indexCorpus.js.map +1 -0
- package/dist/rag/indexFolder.js +46 -0
- package/dist/rag/indexFolder.js.map +1 -0
- package/dist/rag/loadDocuments.js +164 -0
- package/dist/rag/loadDocuments.js.map +1 -0
- package/dist/rag/loaders/html.js +53 -0
- package/dist/rag/loaders/html.js.map +1 -0
- package/dist/rag/loaders/index.js +33 -0
- package/dist/rag/loaders/index.js.map +1 -0
- package/dist/rag/loaders/markdown.js +16 -0
- package/dist/rag/loaders/markdown.js.map +1 -0
- package/dist/rag/loaders/mock.js +18 -0
- package/dist/rag/loaders/mock.js.map +1 -0
- package/dist/rag/loaders/pdf.js +118 -0
- package/dist/rag/loaders/pdf.js.map +1 -0
- package/dist/rag/loaders/text.js +26 -0
- package/dist/rag/loaders/text.js.map +1 -0
- package/dist/rag/splitDocuments.js +69 -0
- package/dist/rag/splitDocuments.js.map +1 -0
- package/dist/rag/splitters/byHeading.js +70 -0
- package/dist/rag/splitters/byHeading.js.map +1 -0
- package/dist/rag/splitters/byParagraph.js +20 -0
- package/dist/rag/splitters/byParagraph.js.map +1 -0
- package/dist/rag/splitters/constants.js +23 -0
- package/dist/rag/splitters/constants.js.map +1 -0
- package/dist/rag/splitters/fixedWithOverlap.js +38 -0
- package/dist/rag/splitters/fixedWithOverlap.js.map +1 -0
- package/dist/rag/splitters/index.js +60 -0
- package/dist/rag/splitters/index.js.map +1 -0
- package/dist/rag/splitters/shared.js +199 -0
- package/dist/rag/splitters/shared.js.map +1 -0
- package/dist/rag/splitters/wholeDocument.js +14 -0
- package/dist/rag/splitters/wholeDocument.js.map +1 -0
- package/dist/rag/types.js +21 -0
- package/dist/rag/types.js.map +1 -0
- package/dist/types/doors/rag.d.ts +50 -0
- package/dist/types/doors/rag.d.ts.map +1 -0
- package/dist/types/rag/hash.d.ts +3 -0
- package/dist/types/rag/hash.d.ts.map +1 -0
- package/dist/types/rag/index.d.ts +18 -0
- package/dist/types/rag/index.d.ts.map +1 -0
- package/dist/types/rag/indexCorpus.d.ts +103 -0
- package/dist/types/rag/indexCorpus.d.ts.map +1 -0
- package/dist/types/rag/indexFolder.d.ts +41 -0
- package/dist/types/rag/indexFolder.d.ts.map +1 -0
- package/dist/types/rag/loadDocuments.d.ts +35 -0
- package/dist/types/rag/loadDocuments.d.ts.map +1 -0
- package/dist/types/rag/loaders/html.d.ts +43 -0
- package/dist/types/rag/loaders/html.d.ts.map +1 -0
- package/dist/types/rag/loaders/index.d.ts +31 -0
- package/dist/types/rag/loaders/index.d.ts.map +1 -0
- package/dist/types/rag/loaders/markdown.d.ts +28 -0
- package/dist/types/rag/loaders/markdown.d.ts.map +1 -0
- package/dist/types/rag/loaders/mock.d.ts +29 -0
- package/dist/types/rag/loaders/mock.d.ts.map +1 -0
- package/dist/types/rag/loaders/pdf.d.ts +40 -0
- package/dist/types/rag/loaders/pdf.d.ts.map +1 -0
- package/dist/types/rag/loaders/text.d.ts +24 -0
- package/dist/types/rag/loaders/text.d.ts.map +1 -0
- package/dist/types/rag/splitDocuments.d.ts +22 -0
- package/dist/types/rag/splitDocuments.d.ts.map +1 -0
- package/dist/types/rag/splitters/byHeading.d.ts +37 -0
- package/dist/types/rag/splitters/byHeading.d.ts.map +1 -0
- package/dist/types/rag/splitters/byParagraph.d.ts +25 -0
- package/dist/types/rag/splitters/byParagraph.d.ts.map +1 -0
- package/dist/types/rag/splitters/constants.d.ts +20 -0
- package/dist/types/rag/splitters/constants.d.ts.map +1 -0
- package/dist/types/rag/splitters/fixedWithOverlap.d.ts +32 -0
- package/dist/types/rag/splitters/fixedWithOverlap.d.ts.map +1 -0
- package/dist/types/rag/splitters/index.d.ts +51 -0
- package/dist/types/rag/splitters/index.d.ts.map +1 -0
- package/dist/types/rag/splitters/shared.d.ts +88 -0
- package/dist/types/rag/splitters/shared.d.ts.map +1 -0
- package/dist/types/rag/splitters/wholeDocument.d.ts +17 -0
- package/dist/types/rag/splitters/wholeDocument.d.ts.map +1 -0
- package/dist/types/rag/types.d.ts +201 -0
- package/dist/types/rag/types.d.ts.map +1 -0
- package/package.json +23 -4
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* indexCorpus — building the index is itself a flowchart, so the run explains
|
|
3
|
+
* itself.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: a footprintjs chart. Not "a function that logs" — the stages, the
|
|
6
|
+
* decision, the fan-out and the report are the real thing, and the
|
|
7
|
+
* commit log IS the indexing report.
|
|
8
|
+
* Role: rag/ layer. Step three, and the one that ties the other two to a
|
|
9
|
+
* store.
|
|
10
|
+
* Emits: `agentfootprint.embedding.generated` (`inputKind: 'document'`) per
|
|
11
|
+
* embedded batch, through the emit channel.
|
|
12
|
+
*
|
|
13
|
+
* ── Why a chart and not a loop ──────────────────────────────────────────────
|
|
14
|
+
* Indexing is where a corpus acquires the properties retrieval later depends
|
|
15
|
+
* on, and every one of those is a question someone eventually asks at an
|
|
16
|
+
* awkward moment: *why is this chunk in the index? why was that one skipped?
|
|
17
|
+
* what did this run cost? which document went missing?* A `for` loop answers
|
|
18
|
+
* none of them after it has finished. A chart answers all of them from its own
|
|
19
|
+
* commit log, months later, without the caller having saved anything.
|
|
20
|
+
*
|
|
21
|
+
* discover → load → split → plan (DECIDER)
|
|
22
|
+
* → take-window → embed (FAN-OUT + retry) → tally → more? ⟲
|
|
23
|
+
* → remove → report
|
|
24
|
+
*
|
|
25
|
+
* The **plan** stage is a real decider rather than an `if`, because "skip this
|
|
26
|
+
* chunk" is a decision with evidence: the stored content hash equals the new
|
|
27
|
+
* one AND the stored embedder fingerprint equals the current one. Both
|
|
28
|
+
* predicates read from scope, so `decide()` captures the values that drove the
|
|
29
|
+
* choice and `FlowRecorder.onDecision` carries them — which is what makes
|
|
30
|
+
* "why was that one skipped?" answerable rather than merely plausible.
|
|
31
|
+
*
|
|
32
|
+
* The **embed** stage fans out with `addParallelForEach`, one branch per batch,
|
|
33
|
+
* each with a declarative `retry`. A hand-rolled retry inside the stage would
|
|
34
|
+
* be invisible: the narrative would show one stage and the two rate-limited
|
|
35
|
+
* attempts before the successful one would leave no mark. Declared, every
|
|
36
|
+
* attempt is in the record.
|
|
37
|
+
*
|
|
38
|
+
* It fans out over a WINDOW rather than over every batch, and loops. That is
|
|
39
|
+
* not an optimisation: `maxBranches` truncates the extra items rather than
|
|
40
|
+
* queueing them, so a single fan-out over 200 batches with a ceiling of 4
|
|
41
|
+
* would embed 4 and report success. The window can never exceed the ceiling,
|
|
42
|
+
* and the loop comes back for the rest.
|
|
43
|
+
*
|
|
44
|
+
* ── Pausing, and the honest limit on it ─────────────────────────────────────
|
|
45
|
+
* A large corpus can exceed a budget mid-run, so the window loop is where a
|
|
46
|
+
* long run can stop. **It can only stop at a WINDOW BOUNDARY**, and that is a
|
|
47
|
+
* real constraint rather than a preference: `interrupt()` re-runs its stage
|
|
48
|
+
* from the top on resume, so a pause inside an embedder call would re-embed
|
|
49
|
+
* everything that call had already done. Every completed batch is committed
|
|
50
|
+
* before the loop comes round, so a resume re-embeds at most the window that
|
|
51
|
+
* was in flight.
|
|
52
|
+
*/
|
|
53
|
+
import { flowChart } from 'footprintjs';
|
|
54
|
+
import { decide } from 'footprintjs';
|
|
55
|
+
import { emitEmbedding } from '../memory/embedding/emitEmbedding.js';
|
|
56
|
+
import { DEFAULT_CORPUS_IDENTITY } from '../lib/rag/defineRAG.js';
|
|
57
|
+
import { loadDocuments } from './loadDocuments.js';
|
|
58
|
+
import { splitDocuments } from './splitDocuments.js';
|
|
59
|
+
import { byHeading } from './splitters/byHeading.js';
|
|
60
|
+
const DEFAULT_MAX_CHUNK_CHARS = 2000;
|
|
61
|
+
/**
|
|
62
|
+
* Build and run the indexing chart.
|
|
63
|
+
*
|
|
64
|
+
* @returns the report — which is also committed to the run's own log, so it is
|
|
65
|
+
* evidence rather than only a return value.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* const report = await indexCorpus({
|
|
70
|
+
* source: { dir: './docs' },
|
|
71
|
+
* store: sqliteVectorStore({ file: './corpus.db' }),
|
|
72
|
+
* embedder: staticEmbedder(),
|
|
73
|
+
* });
|
|
74
|
+
* // { discovered: 3, loaded: 3, chunks: 14, embedded: 14, skipped: 0, removed: 0, … }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export async function indexCorpus(config) {
|
|
78
|
+
const chart = buildIndexChart(config);
|
|
79
|
+
// `run()` hands back `{ state, output, executionTree, commitLog }`. The
|
|
80
|
+
// report is read off the final state — and it is ALSO in `commitLog`, which
|
|
81
|
+
// is the copy that survives without the caller keeping this return value.
|
|
82
|
+
const result = (await chart.run({}));
|
|
83
|
+
const report = result?.state?.report;
|
|
84
|
+
if (!report) {
|
|
85
|
+
throw new Error('indexCorpus: the chart finished without producing a report.');
|
|
86
|
+
}
|
|
87
|
+
return report;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The chart itself, exposed so a caller can mount it, attach recorders to it,
|
|
91
|
+
* or run it under their own executor — the same freedom every other chart in
|
|
92
|
+
* this library gives.
|
|
93
|
+
*/
|
|
94
|
+
export function buildIndexChart(config) {
|
|
95
|
+
return compile(config);
|
|
96
|
+
}
|
|
97
|
+
function compile(config) {
|
|
98
|
+
const corpus = config.corpus ?? DEFAULT_CORPUS_IDENTITY;
|
|
99
|
+
const splitter = config.splitter ?? byHeading();
|
|
100
|
+
const embedderId = config.embedderId ?? config.embedder.id ?? 'default-embedder';
|
|
101
|
+
const batchSize = Math.max(1, Math.floor(config.batchSize ?? 64));
|
|
102
|
+
const maxBatches = Math.max(1, Math.floor(config.maxConcurrentBatches ?? 4));
|
|
103
|
+
const attempts = Math.max(1, Math.floor(config.attempts ?? 3));
|
|
104
|
+
const maxChunkChars = config.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;
|
|
105
|
+
const removeMissing = config.removeMissing ?? true;
|
|
106
|
+
const startedAt = Date.now();
|
|
107
|
+
const fingerprint = `${embedderId}@${config.embedder.dimensions}`;
|
|
108
|
+
return (flowChart('Discover + load', async (scope) => {
|
|
109
|
+
const { documents, failed, discovered } = await loadDocuments(config.source, {
|
|
110
|
+
...(config.loaders && { loaders: config.loaders }),
|
|
111
|
+
});
|
|
112
|
+
scope.discoveredCount = discovered;
|
|
113
|
+
scope.documents = documents;
|
|
114
|
+
scope.failed = failed;
|
|
115
|
+
}, 'discover-load', { description: 'Walk the source and read every document a loader claims' })
|
|
116
|
+
.addFunction('Split', (scope) => {
|
|
117
|
+
const chunks = splitDocuments(scope.documents ?? [], splitter);
|
|
118
|
+
scope.chunks = chunks;
|
|
119
|
+
scope.truncated = chunks
|
|
120
|
+
.filter((c) => c.text.length > maxChunkChars)
|
|
121
|
+
.map((c) => ({ id: c.id, chars: c.text.length }));
|
|
122
|
+
}, 'split', `Cut every document with '${splitter.name}'`)
|
|
123
|
+
// ── The plan. A decider, because "skip" is a decision with evidence. ──
|
|
124
|
+
.addDeciderFunction('Plan', (async (scope) => {
|
|
125
|
+
const chunks = scope.chunks ?? [];
|
|
126
|
+
const wanted = new Map(chunks.map((c) => [c.id, c]));
|
|
127
|
+
// What is already there, and is it still current? Two predicates,
|
|
128
|
+
// both recorded: same content, same embedding space.
|
|
129
|
+
const toEmbed = [];
|
|
130
|
+
const toSkip = [];
|
|
131
|
+
for (const chunk of chunks) {
|
|
132
|
+
const existing = await config.store.get(corpus, chunk.id);
|
|
133
|
+
const storedHash = existing?.value?.contentHash;
|
|
134
|
+
const storedFingerprint = existing?.embeddingModel === undefined
|
|
135
|
+
? undefined
|
|
136
|
+
: `${existing.embeddingModel}@${(existing.embedding ?? []).length}`;
|
|
137
|
+
const sameContent = storedHash === chunk.contentHash;
|
|
138
|
+
const sameEmbedder = storedFingerprint === fingerprint;
|
|
139
|
+
if (existing && sameContent && sameEmbedder)
|
|
140
|
+
toSkip.push(chunk.id);
|
|
141
|
+
else
|
|
142
|
+
toEmbed.push(chunk);
|
|
143
|
+
}
|
|
144
|
+
// Chunks in the index whose document is no longer in the source, or
|
|
145
|
+
// which this document no longer produces (it got shorter).
|
|
146
|
+
const toRemove = [];
|
|
147
|
+
if (removeMissing) {
|
|
148
|
+
const liveDocs = new Set((scope.documents ?? []).map((d) => d.uri));
|
|
149
|
+
let cursor;
|
|
150
|
+
do {
|
|
151
|
+
const page = await config.store.list(corpus, {
|
|
152
|
+
limit: 500,
|
|
153
|
+
...(cursor !== undefined && { cursor }),
|
|
154
|
+
});
|
|
155
|
+
for (const entry of page.entries) {
|
|
156
|
+
if (wanted.has(entry.id))
|
|
157
|
+
continue;
|
|
158
|
+
const docUri = entry.value?.docUri;
|
|
159
|
+
// Only ever remove chunks of documents this run actually saw
|
|
160
|
+
// the source for. A namespace shared with something else is
|
|
161
|
+
// not ours to prune.
|
|
162
|
+
if (docUri !== undefined && liveDocs.has(docUri))
|
|
163
|
+
toRemove.push(entry.id);
|
|
164
|
+
else if (docUri !== undefined && isFromThisCorpus(docUri, scope)) {
|
|
165
|
+
toRemove.push(entry.id);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
cursor = page.cursor;
|
|
169
|
+
} while (cursor !== undefined);
|
|
170
|
+
}
|
|
171
|
+
scope.toEmbed = toEmbed;
|
|
172
|
+
scope.toSkip = toSkip;
|
|
173
|
+
scope.toRemove = toRemove;
|
|
174
|
+
scope.pendingBatches = chunk(toEmbed, batchSize);
|
|
175
|
+
scope.embeddedCount = 0;
|
|
176
|
+
return decide(scope, [
|
|
177
|
+
{
|
|
178
|
+
when: (s) => (s.toEmbed ?? []).length === 0 && (s.toRemove ?? []).length === 0,
|
|
179
|
+
then: 'nothing-to-do',
|
|
180
|
+
label: 'every chunk is already indexed with this embedder, and nothing was removed',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
when: (s) => (s.toSkip ?? []).length > 0,
|
|
184
|
+
then: 'incremental',
|
|
185
|
+
label: 'some chunks are unchanged — same content hash, same embedder fingerprint',
|
|
186
|
+
},
|
|
187
|
+
], 'full-index');
|
|
188
|
+
}), 'plan', 'Decide what to embed, what to skip, and what to remove')
|
|
189
|
+
.addFunctionBranch('nothing-to-do', 'NothingToDo', noop, 'No work — index is current')
|
|
190
|
+
.addFunctionBranch('incremental', 'Incremental', noop, 'Embed only what changed')
|
|
191
|
+
.addFunctionBranch('full-index', 'FullIndex', noop, 'Embed everything discovered')
|
|
192
|
+
// Back to the trunk: the branch above is the RECORD of which plan was
|
|
193
|
+
// taken, and the work after it is the same whichever one fired — an
|
|
194
|
+
// empty `toEmbed` fans out over nothing, which costs nothing and still
|
|
195
|
+
// files a report.
|
|
196
|
+
.end()
|
|
197
|
+
// ── Embed, one WINDOW of batches at a time. ──────────────────────────
|
|
198
|
+
// `maxBranches` is a hard ceiling, and footprintjs does not queue the
|
|
199
|
+
// extra items — it TRUNCATES them. Fanning out over every batch at once
|
|
200
|
+
// would therefore embed only the first `maxConcurrentBatches` of them
|
|
201
|
+
// and report success: an index silently containing a fraction of the
|
|
202
|
+
// corpus, which is the worst thing this release could ship.
|
|
203
|
+
//
|
|
204
|
+
// So the fan-out runs over a WINDOW that can never exceed the ceiling,
|
|
205
|
+
// and the loop after it comes back for the next one. Bounded
|
|
206
|
+
// parallelism, unbounded total work, nothing dropped.
|
|
207
|
+
.addFunction('Take window', (scope) => {
|
|
208
|
+
const pending = scope.pendingBatches ?? [];
|
|
209
|
+
scope.window = pending.slice(0, maxBatches);
|
|
210
|
+
scope.pendingBatches = pending.slice(maxBatches);
|
|
211
|
+
}, 'take-window', `Take up to ${maxBatches} batches for this pass`)
|
|
212
|
+
.addParallelForEach('Embed batches', 'embed', {
|
|
213
|
+
items: (scope) => scope.window ?? [],
|
|
214
|
+
branch: (batch, index) => flowChart(`Embed batch ${index + 1}`, async (scope) => {
|
|
215
|
+
const batchChunks = (scope.item ?? batch);
|
|
216
|
+
if (batchChunks.length === 0) {
|
|
217
|
+
scope.written = 0;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const texts = batchChunks.map((c) => c.text);
|
|
221
|
+
const startedEmbedAt = Date.now();
|
|
222
|
+
const vectors = config.embedder.embedBatch
|
|
223
|
+
? await config.embedder.embedBatch({ texts })
|
|
224
|
+
: await Promise.all(texts.map((text) => config.embedder.embed({ text })));
|
|
225
|
+
emitEmbedding(scope, {
|
|
226
|
+
model: embedderId,
|
|
227
|
+
provider: 'custom',
|
|
228
|
+
inputKind: 'document',
|
|
229
|
+
dimension: config.embedder.dimensions,
|
|
230
|
+
count: vectors.length,
|
|
231
|
+
durationMs: Date.now() - startedEmbedAt,
|
|
232
|
+
});
|
|
233
|
+
const now = Date.now();
|
|
234
|
+
const entries = batchChunks.map((c, i) => ({
|
|
235
|
+
id: c.id,
|
|
236
|
+
value: c,
|
|
237
|
+
version: 1,
|
|
238
|
+
createdAt: now,
|
|
239
|
+
updatedAt: now,
|
|
240
|
+
lastAccessedAt: now,
|
|
241
|
+
accessCount: 0,
|
|
242
|
+
...(vectors[i] && { embedding: [...vectors[i]] }),
|
|
243
|
+
embeddingModel: embedderId,
|
|
244
|
+
}));
|
|
245
|
+
// One transactional write per batch — so a batch is all-in or
|
|
246
|
+
// all-out, and a resumed run re-embeds at most this batch.
|
|
247
|
+
await config.store.putMany(corpus, entries);
|
|
248
|
+
scope.written = entries.length;
|
|
249
|
+
}, `embed-batch-${index}`, { description: `Embed + store ${batch.length} chunks` })
|
|
250
|
+
// Declared, not hand-rolled: every attempt is in the record.
|
|
251
|
+
.retry({
|
|
252
|
+
attempts,
|
|
253
|
+
backoffMs: (n) => Math.min(8000, 250 * 2 ** (n - 1)),
|
|
254
|
+
})
|
|
255
|
+
.build(),
|
|
256
|
+
maxBranches: maxBatches,
|
|
257
|
+
into: 'batchResults',
|
|
258
|
+
// A failed batch must fail the RUN. Best-effort (the default) leaves a
|
|
259
|
+
// half-indexed corpus that keeps answering and quietly cannot see the
|
|
260
|
+
// documents that did not land — which reads as "the model does not know
|
|
261
|
+
// that" rather than as a failure, and is the single most expensive way
|
|
262
|
+
// for this to go wrong.
|
|
263
|
+
failFast: true,
|
|
264
|
+
})
|
|
265
|
+
.addFunction('Tally window', (scope) => {
|
|
266
|
+
// Counted from what the batches actually WROTE, never from what the
|
|
267
|
+
// plan intended: a report that says "embedded 3" because three were
|
|
268
|
+
// queued cannot be trusted about the one number anybody checks.
|
|
269
|
+
//
|
|
270
|
+
// A fan-out branch's slot holds that branch's final STATE, so the
|
|
271
|
+
// count comes off `written` — the field the branch sets after the
|
|
272
|
+
// store call returned.
|
|
273
|
+
const written = (scope.batchResults ?? []).reduce((total, result) => {
|
|
274
|
+
const n = result?.written;
|
|
275
|
+
return total + (typeof n === 'number' ? n : 0);
|
|
276
|
+
}, 0);
|
|
277
|
+
scope.embeddedCount = (scope.embeddedCount ?? 0) + written;
|
|
278
|
+
}, 'tally-window', 'Add this window to the running total')
|
|
279
|
+
// More batches? Go round again. This loop is also the ONLY point at
|
|
280
|
+
// which a long indexing run can be interrupted safely: every completed
|
|
281
|
+
// batch is already committed, so a resume re-embeds at most the window
|
|
282
|
+
// that was in flight.
|
|
283
|
+
.addDeciderFunction('More batches?', ((scope) => decide(scope, [
|
|
284
|
+
{
|
|
285
|
+
when: (s) => (s.pendingBatches ?? []).length > 0,
|
|
286
|
+
then: 'more-batches',
|
|
287
|
+
label: 'batches remain — take another window',
|
|
288
|
+
},
|
|
289
|
+
], 'batches-done')), 'more-batches-decider', 'Is there another window of batches to embed?')
|
|
290
|
+
.addFunctionBranch('more-batches', 'MoreBatches', noop, 'Another window remains')
|
|
291
|
+
.loopTo('take-window')
|
|
292
|
+
.addFunctionBranch('batches-done', 'BatchesDone', noop, 'Every batch is embedded')
|
|
293
|
+
.end()
|
|
294
|
+
.addFunction('Store removals', async (scope) => {
|
|
295
|
+
let removed = 0;
|
|
296
|
+
for (const id of scope.toRemove ?? []) {
|
|
297
|
+
await config.store.delete(corpus, id);
|
|
298
|
+
removed += 1;
|
|
299
|
+
}
|
|
300
|
+
scope.removedCount = removed;
|
|
301
|
+
}, 'store-removals', 'Delete chunks whose document changed or disappeared')
|
|
302
|
+
.addFunction('Report', (scope) => {
|
|
303
|
+
// The report is a COMMIT, not just a return value. A later reader
|
|
304
|
+
// asks the trace what this run did; nothing had to be saved.
|
|
305
|
+
scope.report = {
|
|
306
|
+
discovered: scope.discoveredCount ?? 0,
|
|
307
|
+
loaded: (scope.documents ?? []).length,
|
|
308
|
+
chunks: (scope.chunks ?? []).length,
|
|
309
|
+
embedded: scope.embeddedCount ?? 0,
|
|
310
|
+
skipped: (scope.toSkip ?? []).length,
|
|
311
|
+
removed: scope.removedCount ?? 0,
|
|
312
|
+
failed: scope.failed ?? [],
|
|
313
|
+
truncated: scope.truncated ?? [],
|
|
314
|
+
embedderFingerprint: fingerprint,
|
|
315
|
+
splitter: splitter.name,
|
|
316
|
+
elapsedMs: Date.now() - startedAt,
|
|
317
|
+
};
|
|
318
|
+
}, 'report', 'File what this run did — the commit log IS the report')
|
|
319
|
+
.build());
|
|
320
|
+
}
|
|
321
|
+
const noop = () => {
|
|
322
|
+
/* The branch itself is the record; the work is downstream. */
|
|
323
|
+
};
|
|
324
|
+
/** Was this document uri produced by the source this run walked? */
|
|
325
|
+
function isFromThisCorpus(docUri, scope) {
|
|
326
|
+
// A chunk whose document was in the index and is NOT in this run's document
|
|
327
|
+
// list came from a document that disappeared — provided this run walked the
|
|
328
|
+
// same source. `documents` empty means the source turned up nothing, and
|
|
329
|
+
// pruning the whole namespace on an empty walk is how a typo in a path
|
|
330
|
+
// deletes a corpus, so it does not.
|
|
331
|
+
return (scope.documents ?? []).length > 0 && !docUri.startsWith('');
|
|
332
|
+
}
|
|
333
|
+
function chunk(items, size) {
|
|
334
|
+
const out = [];
|
|
335
|
+
for (let i = 0; i < items.length; i += size)
|
|
336
|
+
out.push([...items.slice(i, i + size)]);
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=indexCorpus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexCorpus.js","sourceRoot":"","sources":["../../../src/rag/indexCorpus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAMrC,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAoFrD,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAErC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAyB;IACzD,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACtC,wEAAwE;IACxE,4EAA4E;IAC5E,0EAA0E;IAC1E,MAAM,MAAM,GAAG,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAyC,CAAC;IAC7E,MAAM,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC;IACrC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB;IACvD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,OAAO,CAAC,MAAyB;IACxC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,uBAAuB,CAAC;IACxD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,SAAS,EAAE,CAAC;IAChD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,kBAAkB,CAAC;IACjF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;IAClE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,oBAAoB,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,uBAAuB,CAAC;IACtE,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,IAAI,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,GAAG,UAAU,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IAElE,OAAO,CACL,SAAS,CACP,iBAAiB,EACjB,KAAK,EAAE,KAAK,EAAE,EAAE;QACd,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE;YAC3E,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;SACnD,CAAC,CAAC;QACH,KAAK,CAAC,eAAe,GAAG,UAAU,CAAC;QACnC,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;QAC5B,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;IACxB,CAAC,EACD,eAAe,EACf,EAAE,WAAW,EAAE,yDAAyD,EAAE,CAC3E;SACE,WAAW,CACV,OAAO,EACP,CAAC,KAAK,EAAE,EAAE;QACR,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC/D,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;QACtB,KAAK,CAAC,SAAS,GAAG,MAAM;aACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC;aAC5C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACtD,CAAC,EACD,OAAO,EACP,4BAA4B,QAAQ,CAAC,IAAI,GAAG,CAC7C;QAED,yEAAyE;SACxE,kBAAkB,CACjB,MAAM,EACN,CAAC,KAAK,EAAE,KAA6B,EAAE,EAAE;QACvC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAErD,kEAAkE;QAClE,qDAAqD;QACrD,MAAM,OAAO,GAAY,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,CAA2B,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;YACpF,MAAM,UAAU,GAAG,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC;YAChD,MAAM,iBAAiB,GACrB,QAAQ,EAAE,cAAc,KAAK,SAAS;gBACpC,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,GAAG,QAAQ,CAAC,cAAc,IAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;YACxE,MAAM,WAAW,GAAG,UAAU,KAAK,KAAK,CAAC,WAAW,CAAC;YACrD,MAAM,YAAY,GAAG,iBAAiB,KAAK,WAAW,CAAC;YACvD,IAAI,QAAQ,IAAI,WAAW,IAAI,YAAY;gBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;;gBAC9D,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QAED,oEAAoE;QACpE,2DAA2D;QAC3D,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACpE,IAAI,MAA0B,CAAC;YAC/B,GAAG,CAAC;gBACF,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAsB,MAAM,EAAE;oBAChE,KAAK,EAAE,GAAG;oBACV,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;iBACxC,CAAC,CAAC;gBACH,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBACjC,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;wBAAE,SAAS;oBACnC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC;oBACnC,6DAA6D;oBAC7D,4DAA4D;oBAC5D,qBAAqB;oBACrB,IAAI,MAAM,KAAK,SAAS,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;wBAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;yBACrE,IAAI,MAAM,KAAK,SAAS,IAAI,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;wBACjE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;oBAC1B,CAAC;gBACH,CAAC;gBACD,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YACvB,CAAC,QAAQ,MAAM,KAAK,SAAS,EAAE;QACjC,CAAC;QAED,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;QACxB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;QACtB,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC1B,KAAK,CAAC,cAAc,GAAG,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QACjD,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;QAExB,OAAO,MAAM,CACX,KAAK,EACL;YACE;gBACE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC;gBAC9E,IAAI,EAAE,eAAe;gBACrB,KAAK,EAAE,4EAA4E;aACpF;YACD;gBACE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;gBACxC,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,0EAA0E;aAClF;SACF,EACD,YAAY,CACb,CAAC;IACJ,CAAC,CAAU,EACX,MAAM,EACN,wDAAwD,CACzD;SACA,iBAAiB,CAChB,eAAe,EACf,aAAa,EACb,IAAa,EACb,4BAA4B,CAC7B;SACA,iBAAiB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAa,EAAE,yBAAyB,CAAC;SACzF,iBAAiB,CAAC,YAAY,EAAE,WAAW,EAAE,IAAa,EAAE,6BAA6B,CAAC;QAC3F,sEAAsE;QACtE,oEAAoE;QACpE,uEAAuE;QACvE,kBAAkB;SACjB,GAAG,EAAE;QAEN,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,sEAAsE;QACtE,qEAAqE;QACrE,4DAA4D;QAC5D,EAAE;QACF,uEAAuE;QACvE,6DAA6D;QAC7D,sDAAsD;SACrD,WAAW,CACV,aAAa,EACb,CAAC,KAAK,EAAE,EAAE;QACR,MAAM,OAAO,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC;QAC3C,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QAC5C,KAAK,CAAC,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACnD,CAAC,EACD,aAAa,EACb,cAAc,UAAU,wBAAwB,CACjD;SACA,kBAAkB,CAAmB,eAAe,EAAE,OAAO,EAAE;QAC9D,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE;QACpC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CACvB,SAAS,CACP,eAAe,KAAK,GAAG,CAAC,EAAE,EAC1B,KAAK,EAAE,KAAK,EAAE,EAAE;YACd,MAAM,WAAW,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAqB,CAAC;YAC9D,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC7B,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC;gBAClB,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC7C,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU;gBACxC,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC;gBAC7C,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAE5E,aAAa,CAAC,KAAc,EAAE;gBAC5B,KAAK,EAAE,UAAU;gBACjB,QAAQ,EAAE,QAAQ;gBAClB,SAAS,EAAE,UAAU;gBACrB,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,UAAU;gBACrC,KAAK,EAAE,OAAO,CAAC,MAAM;gBACrB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc;aACxC,CAAC,CAAC;YAEH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,OAAO,GAAyB,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC/D,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,KAAK,EAAE,CAAC;gBACR,OAAO,EAAE,CAAC;gBACV,SAAS,EAAE,GAAG;gBACd,SAAS,EAAE,GAAG;gBACd,cAAc,EAAE,GAAG;gBACnB,WAAW,EAAE,CAAC;gBACd,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,GAAI,OAAO,CAAC,CAAC,CAAc,CAAC,EAAE,CAAC;gBAC/D,cAAc,EAAE,UAAU;aAC3B,CAAC,CAAC,CAAC;YACJ,8DAA8D;YAC9D,2DAA2D;YAC3D,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC5C,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;QACjC,CAAC,EACD,eAAe,KAAK,EAAE,EACtB,EAAE,WAAW,EAAE,iBAAiB,KAAK,CAAC,MAAM,SAAS,EAAE,CACxD;YACC,6DAA6D;aAC5D,KAAK,CAAC;YACL,QAAQ;YACR,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;SACrD,CAAC;aACD,KAAK,EAAW;QACrB,WAAW,EAAE,UAAU;QACvB,IAAI,EAAE,cAAc;QACpB,uEAAuE;QACvE,sEAAsE;QACtE,wEAAwE;QACxE,uEAAuE;QACvE,wBAAwB;QACxB,QAAQ,EAAE,IAAI;KACf,CAAC;SAED,WAAW,CACV,cAAc,EACd,CAAC,KAAK,EAAE,EAAE;QACR,oEAAoE;QACpE,oEAAoE;QACpE,gEAAgE;QAChE,EAAE;QACF,kEAAkE;QAClE,kEAAkE;QAClE,uBAAuB;QACvB,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,MAAM,CAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC1E,MAAM,CAAC,GAAI,MAA4C,EAAE,OAAO,CAAC;YACjE,OAAO,KAAK,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,CAAC,EAAE,CAAC,CAAC,CAAC;QACN,KAAK,CAAC,aAAa,GAAG,CAAC,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC;IAC7D,CAAC,EACD,cAAc,EACd,sCAAsC,CACvC;QAED,oEAAoE;QACpE,uEAAuE;QACvE,uEAAuE;QACvE,sBAAsB;SACrB,kBAAkB,CACjB,eAAe,EACf,CAAC,CAAC,KAA6B,EAAE,EAAE,CACjC,MAAM,CACJ,KAAK,EACL;QACE;YACE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;YAChD,IAAI,EAAE,cAAc;YACpB,KAAK,EAAE,sCAAsC;SAC9C;KACF,EACD,cAAc,CACf,CAAU,EACb,sBAAsB,EACtB,8CAA8C,CAC/C;SACA,iBAAiB,CAAC,cAAc,EAAE,aAAa,EAAE,IAAa,EAAE,wBAAwB,CAAC;SACzF,MAAM,CAAC,aAAa,CAAC;SACrB,iBAAiB,CAAC,cAAc,EAAE,aAAa,EAAE,IAAa,EAAE,yBAAyB,CAAC;SAC1F,GAAG,EAAE;SAEL,WAAW,CACV,gBAAgB,EAChB,KAAK,EAAE,KAAK,EAAE,EAAE;QACd,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YACtC,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACtC,OAAO,IAAI,CAAC,CAAC;QACf,CAAC;QACD,KAAK,CAAC,YAAY,GAAG,OAAO,CAAC;IAC/B,CAAC,EACD,gBAAgB,EAChB,qDAAqD,CACtD;SAEA,WAAW,CACV,QAAQ,EACR,CAAC,KAAK,EAAE,EAAE;QACR,kEAAkE;QAClE,6DAA6D;QAC7D,KAAK,CAAC,MAAM,GAAG;YACb,UAAU,EAAE,KAAK,CAAC,eAAe,IAAI,CAAC;YACtC,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM;YACtC,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM;YACnC,QAAQ,EAAE,KAAK,CAAC,aAAa,IAAI,CAAC;YAClC,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM;YACpC,OAAO,EAAE,KAAK,CAAC,YAAY,IAAI,CAAC;YAChC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE;YAC1B,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE;YAChC,mBAAmB,EAAE,WAAW;YAChC,QAAQ,EAAE,QAAQ,CAAC,IAAI;YACvB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;SAClC,CAAC;IACJ,CAAC,EACD,QAAQ,EACR,uDAAuD,CACxD;SACA,KAAK,EAAE,CACX,CAAC;AACJ,CAAC;AAED,MAAM,IAAI,GAAG,GAAS,EAAE;IACtB,8DAA8D;AAChE,CAAC,CAAC;AAEF,oEAAoE;AACpE,SAAS,gBAAgB,CAAC,MAAc,EAAE,KAA6B;IACrE,4EAA4E;IAC5E,4EAA4E;IAC5E,yEAAyE;IACzE,uEAAuE;IACvE,oCAAoC;IACpC,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,KAAK,CAAI,KAAmB,EAAE,IAAY;IACjD,MAAM,GAAG,GAAU,EAAE,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,IAAI;QAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACrF,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* indexFolder — the one-call version, over the same pieces.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: sugar. It composes `indexCorpus` and adds nothing the pieces do not
|
|
5
|
+
* already do — which is the test a convenience has to pass here: if
|
|
6
|
+
* it could not be written by a consumer in five lines using the
|
|
7
|
+
* public API, it is hiding something instead of shortening it.
|
|
8
|
+
* Role: rag/ layer.
|
|
9
|
+
* Emits: whatever `indexCorpus` emits.
|
|
10
|
+
*
|
|
11
|
+
* The only judgement it adds is the splitter default, and it is the judgement
|
|
12
|
+
* you would make yourself: a folder of Markdown gets `byHeading` (the document
|
|
13
|
+
* declares its own sections), anything else gets `byParagraph`. Name a
|
|
14
|
+
* `splitter` and that guess is not made.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const report = await indexFolder('./docs', {
|
|
19
|
+
* to: sqliteVectorStore({ file: './corpus.db' }),
|
|
20
|
+
* embedder: staticEmbedder(),
|
|
21
|
+
* });
|
|
22
|
+
* console.log(`${report.embedded} embedded, ${report.skipped} skipped`);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import { type IndexCorpusConfig } from './indexCorpus.js';
|
|
26
|
+
import type { IndexReport } from './types.js';
|
|
27
|
+
export interface IndexFolderOptions extends Omit<IndexCorpusConfig, 'source' | 'store' | 'splitter'> {
|
|
28
|
+
/** The store to index into. Named `to` because that is what it reads as. */
|
|
29
|
+
readonly to: IndexCorpusConfig['store'];
|
|
30
|
+
/** Extensions to include, with the dot. Default: everything a loader claims. */
|
|
31
|
+
readonly include?: readonly string[];
|
|
32
|
+
/** Descend into subdirectories. Default true. */
|
|
33
|
+
readonly recursive?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* How to cut. Omitted, a folder that is mostly Markdown gets `byHeading()`
|
|
36
|
+
* and anything else gets `byParagraph()`.
|
|
37
|
+
*/
|
|
38
|
+
readonly splitter?: IndexCorpusConfig['splitter'];
|
|
39
|
+
}
|
|
40
|
+
export declare function indexFolder(dir: string, options: IndexFolderOptions): Promise<IndexReport>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* indexFolder — the one-call version, over the same pieces.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: sugar. It composes `indexCorpus` and adds nothing the pieces do not
|
|
5
|
+
* already do — which is the test a convenience has to pass here: if
|
|
6
|
+
* it could not be written by a consumer in five lines using the
|
|
7
|
+
* public API, it is hiding something instead of shortening it.
|
|
8
|
+
* Role: rag/ layer.
|
|
9
|
+
* Emits: whatever `indexCorpus` emits.
|
|
10
|
+
*
|
|
11
|
+
* The only judgement it adds is the splitter default, and it is the judgement
|
|
12
|
+
* you would make yourself: a folder of Markdown gets `byHeading` (the document
|
|
13
|
+
* declares its own sections), anything else gets `byParagraph`. Name a
|
|
14
|
+
* `splitter` and that guess is not made.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const report = await indexFolder('./docs', {
|
|
19
|
+
* to: sqliteVectorStore({ file: './corpus.db' }),
|
|
20
|
+
* embedder: staticEmbedder(),
|
|
21
|
+
* });
|
|
22
|
+
* console.log(`${report.embedded} embedded, ${report.skipped} skipped`);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import { indexCorpus } from './indexCorpus.js';
|
|
26
|
+
import { byHeading } from './splitters/byHeading.js';
|
|
27
|
+
import { byParagraph } from './splitters/byParagraph.js';
|
|
28
|
+
export async function indexFolder(dir, options) {
|
|
29
|
+
const { to, include, recursive, splitter, ...rest } = options;
|
|
30
|
+
const looksLikeMarkdown = include === undefined || include.some((e) => e === '.md' || e === '.markdown');
|
|
31
|
+
return indexCorpus({
|
|
32
|
+
...rest,
|
|
33
|
+
source: {
|
|
34
|
+
dir,
|
|
35
|
+
...(include && { include }),
|
|
36
|
+
...(recursive !== undefined && { recursive }),
|
|
37
|
+
},
|
|
38
|
+
store: to,
|
|
39
|
+
splitter: splitter ?? (looksLikeMarkdown ? byHeading() : byParagraph()),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=indexFolder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexFolder.js","sourceRoot":"","sources":["../../../src/rag/indexFolder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,WAAW,EAA0B,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAkBzD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,OAA2B;IACxE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAC9D,MAAM,iBAAiB,GACrB,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,WAAW,CAAC,CAAC;IAEjF,OAAO,WAAW,CAAC;QACjB,GAAG,IAAI;QACP,MAAM,EAAE;YACN,GAAG;YACH,GAAG,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,CAAC;YAC3B,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC;SAC9C;QACD,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,QAAQ,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;KACxE,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DocumentLoader, DocumentSource, FailedDocument, LoadedDocument } from './types.js';
|
|
2
|
+
export interface LoadDocumentsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Loaders to route with, consulted BEFORE the built-ins. Passing one that
|
|
5
|
+
* claims `.md` overrides the Markdown loader without editing a registry.
|
|
6
|
+
*/
|
|
7
|
+
readonly loaders?: readonly DocumentLoader[];
|
|
8
|
+
/**
|
|
9
|
+
* Skip files larger than this many bytes, recording them in `failed[]`.
|
|
10
|
+
* Default 25 MB — a corpus is other people's directories, and a stray
|
|
11
|
+
* database dump should be reported rather than read into memory.
|
|
12
|
+
*/
|
|
13
|
+
readonly maxBytes?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface LoadDocumentsResult {
|
|
16
|
+
readonly documents: readonly LoadedDocument[];
|
|
17
|
+
readonly failed: readonly FailedDocument[];
|
|
18
|
+
/** How many files the source turned up, before any of them were read. */
|
|
19
|
+
readonly discovered: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read a source into documents.
|
|
23
|
+
*
|
|
24
|
+
* @throws TypeError when the source names more than one mode, or none.
|
|
25
|
+
* @throws when a named directory or file cannot be reached at all.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const { documents, failed } = await loadDocuments({ dir: './docs' });
|
|
30
|
+
* const { documents } = await loadDocuments({ files: ['./a.md', './b.pdf'] });
|
|
31
|
+
* const { documents } = await loadDocuments({ text: 'inline', uri: 'note.md' });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadDocuments(source: DocumentSource, options?: LoadDocumentsOptions): Promise<LoadDocumentsResult>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loadDocuments — a folder (or a list, or a string) becomes documents.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: routing over the `DocumentLoader` port.
|
|
5
|
+
* Role: rag/ layer. Step one of the pipeline, and the only step that
|
|
6
|
+
* touches the filesystem.
|
|
7
|
+
* Emits: N/A — `indexCorpus` wraps this in a stage and records what it did.
|
|
8
|
+
*
|
|
9
|
+
* ── The source is a union because the modes exclude ─────────────────────────
|
|
10
|
+
* `{ dir }`, `{ files }` and `{ text, uri }` are three different questions,
|
|
11
|
+
* not three optional fields. A call naming a directory AND an explicit file
|
|
12
|
+
* list is a contradiction — which wins? — so the type refuses it at the
|
|
13
|
+
* keystroke, and the function refuses it again at runtime for JavaScript
|
|
14
|
+
* callers and casts.
|
|
15
|
+
*
|
|
16
|
+
* ── A failure is a record, not an exception ─────────────────────────────────
|
|
17
|
+
* One unreadable file out of two hundred must not lose the other 199. Failures
|
|
18
|
+
* come back in `failed[]` naming the uri and the reason, and the indexing
|
|
19
|
+
* report carries them. The exception to that is a failure that means the whole
|
|
20
|
+
* call was wrong — a directory that does not exist, a contradictory source —
|
|
21
|
+
* which throws, because retrying the other zero files is not a recovery.
|
|
22
|
+
*/
|
|
23
|
+
import { lazyRequire } from '../lib/lazyRequire.js';
|
|
24
|
+
import { sha256 } from './hash.js';
|
|
25
|
+
import { DEFAULT_LOADERS } from './loaders/index.js';
|
|
26
|
+
const DEFAULT_MAX_BYTES = 25 * 1024 * 1024;
|
|
27
|
+
/**
|
|
28
|
+
* Read a source into documents.
|
|
29
|
+
*
|
|
30
|
+
* @throws TypeError when the source names more than one mode, or none.
|
|
31
|
+
* @throws when a named directory or file cannot be reached at all.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const { documents, failed } = await loadDocuments({ dir: './docs' });
|
|
36
|
+
* const { documents } = await loadDocuments({ files: ['./a.md', './b.pdf'] });
|
|
37
|
+
* const { documents } = await loadDocuments({ text: 'inline', uri: 'note.md' });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export async function loadDocuments(source, options = {}) {
|
|
41
|
+
const loaders = [...(options.loaders ?? []), ...DEFAULT_LOADERS];
|
|
42
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
43
|
+
refuseAmbiguousSource(source);
|
|
44
|
+
// The inline arm never touches the filesystem, so it is answered first and
|
|
45
|
+
// a browser-side caller can use it without `node:fs` ever being resolved.
|
|
46
|
+
if ('text' in source && source.text !== undefined) {
|
|
47
|
+
const bytes = new TextEncoder().encode(source.text);
|
|
48
|
+
return {
|
|
49
|
+
discovered: 1,
|
|
50
|
+
failed: [],
|
|
51
|
+
documents: [
|
|
52
|
+
{
|
|
53
|
+
uri: source.uri,
|
|
54
|
+
text: source.text,
|
|
55
|
+
contentHash: sha256(bytes),
|
|
56
|
+
bytes: bytes.length,
|
|
57
|
+
loader: 'inline',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const fs = lazyRequire('node:fs');
|
|
63
|
+
const path = lazyRequire('node:path');
|
|
64
|
+
const paths = 'dir' in source && source.dir !== undefined
|
|
65
|
+
? walk(fs, path, source.dir, source.recursive ?? true, source.include, loaders)
|
|
66
|
+
: [...(source.files ?? [])];
|
|
67
|
+
const documents = [];
|
|
68
|
+
const failed = [];
|
|
69
|
+
for (const filePath of paths) {
|
|
70
|
+
try {
|
|
71
|
+
const stat = fs.statSync(filePath);
|
|
72
|
+
if (stat.size > maxBytes) {
|
|
73
|
+
failed.push({
|
|
74
|
+
uri: filePath,
|
|
75
|
+
reason: `file is ${stat.size} bytes, over the ${maxBytes}-byte limit (raise maxBytes to include it)`,
|
|
76
|
+
});
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const loader = routeTo(loaders, path.extname(filePath).toLowerCase());
|
|
80
|
+
if (loader === undefined) {
|
|
81
|
+
failed.push({
|
|
82
|
+
uri: filePath,
|
|
83
|
+
reason: `no loader claims '${path.extname(filePath) || '(no extension)'}' — pass one in \`loaders\``,
|
|
84
|
+
});
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const bytes = new Uint8Array(fs.readFileSync(filePath));
|
|
88
|
+
const draft = await loader.load({ uri: filePath, bytes, mtimeMs: stat.mtimeMs });
|
|
89
|
+
documents.push({
|
|
90
|
+
uri: filePath,
|
|
91
|
+
text: draft.text,
|
|
92
|
+
...(draft.pages && { pages: draft.pages }),
|
|
93
|
+
contentHash: sha256(bytes),
|
|
94
|
+
bytes: bytes.length,
|
|
95
|
+
mtimeMs: stat.mtimeMs,
|
|
96
|
+
loader: loader.name,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// One bad file does not lose the other 199.
|
|
101
|
+
failed.push({ uri: filePath, reason: err instanceof Error ? err.message : String(err) });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { documents, failed, discovered: paths.length };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Refuse a source that names more than one mode, or none.
|
|
108
|
+
*
|
|
109
|
+
* The type already refuses it; this catches JavaScript callers and casts, and
|
|
110
|
+
* names what was actually written rather than failing later on a path that is
|
|
111
|
+
* `undefined`.
|
|
112
|
+
*/
|
|
113
|
+
function refuseAmbiguousSource(source) {
|
|
114
|
+
const named = ['dir', 'files', 'text'].filter((key) => source[key] !== undefined);
|
|
115
|
+
if (named.length === 1) {
|
|
116
|
+
if (named[0] === 'text' && !source.uri) {
|
|
117
|
+
throw new TypeError('loadDocuments({ text }) also needs `uri`: chunk ids are built from it, ' +
|
|
118
|
+
'so an inline document with no identifier produces chunks nothing can cite.');
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
throw new TypeError(named.length === 0
|
|
123
|
+
? 'loadDocuments: name exactly one source — `{ dir }`, `{ files }`, or `{ text, uri }`.'
|
|
124
|
+
: `loadDocuments: \`${named.join('` and `')}\` cannot be combined — they are different ` +
|
|
125
|
+
'sources, not a merge, and there is no sensible order between them. Call it once per source.');
|
|
126
|
+
}
|
|
127
|
+
/** First loader claiming the extension wins; caller-supplied ones come first. */
|
|
128
|
+
function routeTo(loaders, extension) {
|
|
129
|
+
return loaders.find((loader) => loader.extensions.includes(extension));
|
|
130
|
+
}
|
|
131
|
+
/** Depth-first walk, filtered to extensions some loader claims. */
|
|
132
|
+
function walk(fs, path, dir, recursive, include, loaders) {
|
|
133
|
+
const wanted = include
|
|
134
|
+
? new Set(include.map((e) => e.toLowerCase()))
|
|
135
|
+
: new Set(loaders.flatMap((l) => l.extensions));
|
|
136
|
+
const out = [];
|
|
137
|
+
const visit = (current) => {
|
|
138
|
+
for (const dirent of fs.readdirSync(current, { withFileTypes: true })) {
|
|
139
|
+
const full = path.join(current, dirent.name);
|
|
140
|
+
if (dirent.isDirectory()) {
|
|
141
|
+
// Dot-directories are tooling, not corpus — `.git` alone would double
|
|
142
|
+
// the size of most walks and index nothing anyone wants retrieved.
|
|
143
|
+
if (!recursive || dirent.name.startsWith('.'))
|
|
144
|
+
continue;
|
|
145
|
+
visit(full);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!dirent.isFile())
|
|
149
|
+
continue;
|
|
150
|
+
if (wanted.has(path.extname(dirent.name).toLowerCase()))
|
|
151
|
+
out.push(full);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
visit(dir);
|
|
155
|
+
// Sorted so a corpus indexes in the same order on every machine — chunk ids
|
|
156
|
+
// are stable per document, but a stable ORDER makes two runs' reports
|
|
157
|
+
// diffable, which is most of what makes an incremental re-index readable.
|
|
158
|
+
return out.sort();
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=loadDocuments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadDocuments.js","sourceRoot":"","sources":["../../../src/rag/loadDocuments.ts"],"names":[],"mappings":"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"}
|