@lunora/ai 1.0.0-alpha.2 → 1.0.0-alpha.21

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.
@@ -0,0 +1,550 @@
1
+ import { Tool } from 'ai';
2
+ import { E as EmbeddingModelInput, a as LunoraAi } from "../packem_shared/types.d-BXCiRv1x.js";
3
+ /**
4
+ * Built-in fixed-window chunker: split into `size`-char windows overlapping by
5
+ * `overlap` chars. Deliberately simple and deterministic — the zero-config
6
+ * default. Token-aware / sentence / semantic strategies plug in via
7
+ * `RagConfig.chunk`.
8
+ * @experimental
9
+ */
10
+ declare const fixedWindowChunks: (text: string, size: number, overlap: number) => ReadonlyArray<string>;
11
+ /**
12
+ * `(text) => vector` — the embedder shape `ctx.vectors` accepts on both its
13
+ * write (`upsert`) and read (`query`) inputs. Matches `@lunora/server`'s
14
+ * `VectorEmbedder` and `@lunora/bindings/vectors`' `EmbedFunction&lt;string>`.
15
+ * @experimental
16
+ */
17
+ type RagEmbedder = (input: string) => Promise<ReadonlyArray<number>> | ReadonlyArray<number>;
18
+ /**
19
+ * `RagVectorMatch` is part of the experimental `@lunora/ai` API and may change without a major version bump.
20
+ * @experimental
21
+ */
22
+ interface RagVectorMatch {
23
+ id: string;
24
+ metadata?: Record<string, unknown>;
25
+ score: number;
26
+ }
27
+ /**
28
+ * `RagVectorMatches` is part of the experimental `@lunora/ai` API and may change without a major version bump.
29
+ * @experimental
30
+ */
31
+ interface RagVectorMatches {
32
+ count: number;
33
+ matches: ReadonlyArray<RagVectorMatch>;
34
+ }
35
+ /**
36
+ * `RagVectorQueryInput` is part of the experimental `@lunora/ai` API and may change without a major version bump.
37
+ * @experimental
38
+ */
39
+ interface RagVectorQueryInput {
40
+ /** Embedder used to vectorize `input`. */
41
+ embed?: RagEmbedder;
42
+ filter?: Record<string, unknown>;
43
+ /** Natural-language query text, embedded via `embed`. */
44
+ input?: string;
45
+ namespace?: string;
46
+ /**
47
+ * How much stored metadata to return on matches. The runtime honours it even
48
+ * though `@lunora/server`'s ctx type does not declare it — the helper relies
49
+ * on it to read chunk text back in metadata mode.
50
+ */
51
+ returnMetadata?: "all" | "indexed" | "none";
52
+ topK?: number;
53
+ }
54
+ /**
55
+ * `RagVectorRecord` is part of the experimental `@lunora/ai` API and may change without a major version bump.
56
+ * @experimental
57
+ */
58
+ interface RagVectorRecord {
59
+ id: string;
60
+ metadata?: Record<string, unknown>;
61
+ }
62
+ /**
63
+ * `RagVectorUpsertInput` is part of the experimental `@lunora/ai` API and may change without a major version bump.
64
+ * @experimental
65
+ */
66
+ interface RagVectorUpsertInput {
67
+ /** Embedder used to vectorize `input`. Optional — omitted for text-search indexes. */
68
+ embed?: RagEmbedder;
69
+ id: string;
70
+ input: string;
71
+ metadata?: Record<string, unknown>;
72
+ namespace?: string;
73
+ }
74
+ /**
75
+ * Structural subset of the vector surface the RAG helper needs. Both the
76
+ * `ctx.vectors` facade on Mutation/Action ctx (`@lunora/server`'s
77
+ * `VectorSearch`) and the raw `@lunora/bindings/vectors` `LunoraVectors`
78
+ * satisfy it — declared here so `@lunora/ai` depends on neither package.
79
+ * @experimental
80
+ */
81
+ interface RagVectors {
82
+ deleteByIds: (indexName: string, ids: ReadonlyArray<string>, namespace?: string) => Promise<unknown>;
83
+ getByIds: (indexName: string, ids: ReadonlyArray<string>, namespace?: string) => Promise<ReadonlyArray<RagVectorRecord>>;
84
+ query: (indexName: string, input: RagVectorQueryInput) => Promise<RagVectorMatches>;
85
+ upsert: (indexName: string, input: RagVectorUpsertInput) => Promise<unknown>;
86
+ }
87
+ /**
88
+ * The two facades `defineRag` binds. An `ActionCtx` satisfies this directly
89
+ * (`ctx.ai` is action-only, so RAG methods run inside actions); any object
90
+ * carrying the two facades works in tests.
91
+ * @experimental
92
+ */
93
+ interface RagContext {
94
+ /**
95
+ * Resolves a Workers AI embedding-model id (or the omitted default) — an
96
+ * `ActionCtx`'s `ctx.ai` satisfies it. OPTIONAL: when
97
+ * {@link RagConfig.embeddingModel} is a direct AI SDK `EmbeddingModel` object
98
+ * (bring-your-own embeddings, e.g. `@ai-sdk/openai`), the helper uses that
99
+ * object as-is and never reads `ai`, so a hand-built context may omit it and
100
+ * no `env.AI` binding is needed. A model-id string (or an omitted model) with
101
+ * no `ai` present throws a directed error.
102
+ */
103
+ ai?: Pick<LunoraAi, "embeddingModel">;
104
+ /**
105
+ * The verified retrieval identity, read by {@link RagConfig.rlsFilter} to
106
+ * derive a per-request row filter. An `ActionCtx` carrying `ctx.auth`
107
+ * satisfies this structurally, so `docs(ctx)` picks the identity up
108
+ * automatically; tests pass any value. `unknown` on purpose — `@lunora/ai`
109
+ * stays decoupled from `@lunora/server`'s identity type; `rlsFilter` narrows.
110
+ */
111
+ auth?: unknown;
112
+ /**
113
+ * Optional `ctx.trace` span factory — an `ActionCtx`'s `ctx.trace` satisfies
114
+ * it structurally. When present, `defineRag` wraps each embedding-model
115
+ * call in a `generation` span carrying `gen_ai.operation.name: "embeddings"`
116
+ * and `gen_ai.request.model` up front, plus — attached post-hoc through the
117
+ * span handle the tracer hands the body — `gen_ai.usage.input_tokens` (from
118
+ * the embed result's token usage) and `gen_ai.usage.cost` (probed from the
119
+ * embed result's provider metadata, e.g. AI Gateway) when those are present.
120
+ * So the embed shows up on the trace waterfall with its usage like any other
121
+ * instrumented model call. `unknown` on purpose — the same decoupling
122
+ * rationale as `auth`: `defineRag` narrows it to a callable and runs embeds
123
+ * untraced when it is absent (a hand-built context / test).
124
+ */
125
+ trace?: unknown;
126
+ vectors: RagVectors;
127
+ }
128
+ /**
129
+ * Pluggable chunk-text storage. By default chunk text is stored in vector
130
+ * metadata (`__ragText`), which forces `returnMetadata: "all"` on retrieval and
131
+ * caps `topK` at 20 (the Vectorize full-metadata ceiling) — and each vector's
132
+ * metadata must stay under the ~10 KiB Vectorize cap. Supplying a text store
133
+ * (a DO table, KV, …) moves the text out of metadata: retrieval queries with
134
+ * `returnMetadata: "indexed"` (topK up to 100) and hydrates text by chunk id.
135
+ * @experimental
136
+ */
137
+ interface RagTextStore {
138
+ /** Fetch chunk texts by id, aligned with the input order; `undefined` for misses. */
139
+ getMany: (ids: ReadonlyArray<string>, options: {
140
+ namespace?: string;
141
+ }) => Promise<ReadonlyArray<string | undefined>>;
142
+ /** Persist chunk texts. Must be idempotent by chunk `id` (re-index re-puts). */
143
+ put: (chunks: ReadonlyArray<StoredRagChunk>, options: {
144
+ namespace?: string;
145
+ }) => Promise<void>;
146
+ /** Optional cleanup hook, invoked when a source's chunks are deleted. */
147
+ remove?: (ids: ReadonlyArray<string>, options: {
148
+ namespace?: string;
149
+ }) => Promise<void>;
150
+ }
151
+ /**
152
+ * A chunk handed to {@link RagTextStore.put} / {@link RagLexicalStore.index}.
153
+ * @experimental
154
+ */
155
+ interface StoredRagChunk {
156
+ chunkIndex: number;
157
+ id: string;
158
+ sourceId: string;
159
+ text: string;
160
+ }
161
+ /**
162
+ * One lexical (BM25) hit returned by {@link RagLexicalStore.search}.
163
+ * @experimental
164
+ */
165
+ interface LexicalMatch {
166
+ /** The chunk vector id — the same id scheme the vector leg uses, so RRF can fuse the two. */
167
+ id: string;
168
+ /** BM25 relevance score (higher = better). Used only for the leg's internal ranking; RRF fuses by rank. */
169
+ score: number;
170
+ /** The chunk text, returned so a fused lexical-only hit needs no extra hydration round-trip. */
171
+ text: string;
172
+ }
173
+ /**
174
+ * Pluggable lexical (BM25 / keyword) store — the production seam for hybrid
175
+ * retrieval. When {@link RagConfig.lexicalStore} is set, `index()` mirrors each
176
+ * chunk's text here and `retrieve()` fuses this store's keyword ranking with the
177
+ * vector store's semantic ranking via Reciprocal Rank Fusion. Mirrors the
178
+ * {@link RagTextStore} shape (idempotent by chunk `id`, namespace-partitioned).
179
+ *
180
+ * `@lunora/ai/rag` ships `bm25LexicalStore()`, an in-memory reference adapter;
181
+ * production deployments plug a durable one (DO SQLite inverted index, D1,
182
+ * Vectorize-adjacent search service, …) behind this same interface.
183
+ * @experimental
184
+ */
185
+ interface RagLexicalStore {
186
+ /** Index chunk texts for keyword search. Must be idempotent by chunk `id` (re-index re-puts). */
187
+ index: (chunks: ReadonlyArray<StoredRagChunk>, options: {
188
+ namespace?: string;
189
+ }) => Promise<void>;
190
+ /** Optional cleanup hook, invoked when a source's chunks are deleted or a re-index shrinks it. */
191
+ remove?: (ids: ReadonlyArray<string>, options: {
192
+ namespace?: string;
193
+ }) => Promise<void>;
194
+ /**
195
+ * Rank chunks by lexical relevance to `query`. `filter` carries the same
196
+ * (RLS-merged) metadata predicate handed to the vector leg — a store that
197
+ * indexes metadata MUST honour it so hybrid retrieval can't surface a row
198
+ * the RLS filter would exclude; a namespace-only store (the reference
199
+ * adapter) isolates by `namespace` and documents that it ignores `filter`.
200
+ */
201
+ search: (query: string, options: {
202
+ filter?: Record<string, unknown>;
203
+ namespace?: string;
204
+ topK: number;
205
+ }) => Promise<ReadonlyArray<LexicalMatch>>;
206
+ }
207
+ /**
208
+ * A pre-defined, reusable filter expression. Declared on `RagConfig.filters`
209
+ * (keyed by name) and referenced by name from `RetrieveOptions.filter` — avoids
210
+ * repeating the same tenant/RBAC filter shape across every retrieval site.
211
+ * @example
212
+ * ```ts
213
+ * const docs = defineRag({
214
+ * index: "docs",
215
+ * filters: {
216
+ * published: { filter: { status: "published", deleted: false }, description: "Only published content" },
217
+ * },
218
+ * });
219
+ * // Later — reference by name:
220
+ * docs(ctx).retrieve("query", { filter: "published" });
221
+ * ```
222
+ * @experimental
223
+ */
224
+ interface RagNamedFilter {
225
+ /** Optional human-readable description for observability / Studio display. */
226
+ description?: string;
227
+ /** The filter expression passed verbatim to Vectorize's `filter` parameter. */
228
+ filter: Record<string, unknown>;
229
+ }
230
+ /**
231
+ * `RagConfig` is part of the experimental `@lunora/ai` API and may change without a major version bump.
232
+ * @experimental
233
+ */
234
+ interface RagConfig {
235
+ /**
236
+ * Suppress the one-time dev warning emitted when `index`/`retrieve` run
237
+ * without a `namespace`. Only appropriate for genuinely single-tenant apps —
238
+ * Vectorize indexes are account-global, so a namespace-less index shares
239
+ * vectors across every tenant.
240
+ */
241
+ allowSharedNamespace?: boolean;
242
+ /** Custom chunker; overrides the built-in fixed-window splitter. */
243
+ chunk?: (text: string) => ReadonlyArray<string>;
244
+ /** Overlap (chars) between adjacent chunks. Default 200. Must be < `chunkSize`. */
245
+ chunkOverlap?: number;
246
+ /** Target chunk size (chars). Default 1000. */
247
+ chunkSize?: number;
248
+ /**
249
+ * Embedding model, declared once so index + retrieve embed identically: a
250
+ * Workers AI id (e.g. `@cf/baai/bge-base-en-v1.5`) or any AI SDK
251
+ * `EmbeddingModel`. Falls back to `createAi`'s `defaultModel` when omitted.
252
+ */
253
+ embeddingModel?: EmbeddingModelInput;
254
+ /**
255
+ * Embedding-model version tag — an opt-in discriminator that partitions the
256
+ * vector space so a model swap can never silently return garbage. Vectors
257
+ * embedded by one model live in a different space from another's, and
258
+ * querying across the two returns meaningless neighbours. When set, the tag
259
+ * is folded into the effective Vectorize namespace (and chunk-id prefix) of
260
+ * every index/retrieve/remove, so bumping it re-partitions cleanly: old
261
+ * vectors become unreachable to new queries (empty ≫ wrong) until sources
262
+ * are re-indexed under the new tag.
263
+ *
264
+ * Set + bump this whenever you change {@link RagConfig.embeddingModel} (or
265
+ * its dimensions). Opt-in and non-breaking — omitting it keeps the exact
266
+ * chunk-id/namespace scheme of un-versioned indexes. Must match
267
+ * `^[A-Za-z0-9._-]{1,40}$` (e.g. `"bge-v1.5"`, `"v2"`).
268
+ */
269
+ embeddingModelVersion?: string;
270
+ /**
271
+ * Pre-defined named filter expressions. Each key is a filter name users
272
+ * pass through `RetrieveOptions.filter`. Throws at retrieve-time if the
273
+ * name is not found here — catches spelling mistakes early.
274
+ */
275
+ filters?: Record<string, RagNamedFilter>;
276
+ /** The Vectorize index name (a `ctx.vectors` index binding key). */
277
+ index: string;
278
+ /**
279
+ * Pluggable lexical (BM25) store for hybrid retrieval. When set, `index()`
280
+ * mirrors chunk text into it and `retrieve()` fuses the vector (semantic)
281
+ * and lexical (keyword) rankings via Reciprocal Rank Fusion — recovering the
282
+ * exact-term / rare-token matches a pure-embedding search misses. Use the
283
+ * shipped `bm25LexicalStore()` reference adapter or plug your own durable
284
+ * one. See {@link RagLexicalStore}.
285
+ */
286
+ lexicalStore?: RagLexicalStore;
287
+ /** Retrieval depth for the lexical leg of hybrid search. Defaults to the effective `topK`. */
288
+ lexicalTopK?: number;
289
+ /**
290
+ * Enforce tenant isolation: throw (instead of the one-time dev warning)
291
+ * when `index`/`retrieve`/`remove` run without a `namespace`. Recommended
292
+ * for every multi-tenant app — Vectorize indexes are account-global, and
293
+ * in metadata mode the leaked payload includes raw chunk text.
294
+ */
295
+ requireNamespace?: boolean;
296
+ /**
297
+ * Row-level-security filter derived from the retrieval identity. Called once
298
+ * per `retrieve()` with {@link RagContext.auth} (the bound ctx's `auth`); the
299
+ * returned Vectorize metadata filter is merged over the caller's `filter`
300
+ * with **RLS keys winning** (a caller can never widen past what RLS allows),
301
+ * then applied to both the vector and the lexical legs. Return `undefined` to
302
+ * add no constraint (e.g. an admin identity). Runs on retrieval only —
303
+ * indexing is a trusted server path.
304
+ * @example
305
+ * ```ts
306
+ * const docs = defineRag({
307
+ * index: "docs",
308
+ * // only ever return the caller's own org, whatever else they ask for:
309
+ * rlsFilter: (auth) => ({ orgId: (auth as { orgId: string }).orgId }),
310
+ * });
311
+ * ```
312
+ */
313
+ rlsFilter?: (auth: unknown) => Promise<Record<string, unknown> | undefined> | Record<string, unknown> | undefined;
314
+ /** Chunk-text storage override — see {@link RagTextStore}. */
315
+ textStore?: RagTextStore;
316
+ /** Default retrieval depth. Default 5. Capped at 20 (metadata mode) / 100 (text-store mode). */
317
+ topK?: number;
318
+ }
319
+ /**
320
+ * `IndexInput` is part of the experimental `@lunora/ai` API and may change without a major version bump.
321
+ * @experimental
322
+ */
323
+ interface IndexInput {
324
+ /**
325
+ * When `false`, throws if the source text produces zero chunks (e.g. empty
326
+ * or whitespace-only text). Default `true` (silently produces zero chunks).
327
+ */
328
+ allowEmptySources?: boolean;
329
+ /** Source document id — chunk ids derive from it as `${id}#${chunkIndex}`. */
330
+ id: string;
331
+ /**
332
+ * Relative weight in `[0, 1]` multiplied into this source's match scores at
333
+ * retrieval time (default 1). Lets canonical docs outrank incidental ones.
334
+ */
335
+ importance?: number;
336
+ /** Source metadata copied onto every chunk vector (e.g. title, url). */
337
+ metadata?: Record<string, unknown>;
338
+ /** Tenant/shard key. Required for multi-tenant apps — Vectorize is account-global. */
339
+ namespace?: string;
340
+ /**
341
+ * Called after each chunk is successfully upserted. Useful for progress
342
+ * tracking during large indexing operations — e.g. updating a UI progress
343
+ * bar or logging per-chunk status.
344
+ */
345
+ onChunk?: (info: {
346
+ chunkIndex: number;
347
+ id: string;
348
+ text: string;
349
+ total: number;
350
+ }) => void;
351
+ /** The document body to chunk + embed + upsert. */
352
+ text: string;
353
+ }
354
+ /**
355
+ * `IndexResult` is part of the experimental `@lunora/ai` API and may change without a major version bump.
356
+ * @experimental
357
+ */
358
+ interface IndexResult {
359
+ /** Number of chunks the source is indexed into. */
360
+ chunks: number;
361
+ /** The deterministic chunk vector ids, in chunk order. */
362
+ ids: ReadonlyArray<string>;
363
+ /**
364
+ * True when the source's content hash matched the previously indexed hash —
365
+ * chunking/embedding/upserts were skipped entirely (a no-op re-sync).
366
+ */
367
+ unchanged: boolean;
368
+ }
369
+ /**
370
+ * `RemoveInput` is part of the experimental `@lunora/ai` API and may change without a major version bump.
371
+ * @experimental
372
+ */
373
+ interface RemoveInput {
374
+ /** The source document id whose chunks are removed. */
375
+ id: string;
376
+ namespace?: string;
377
+ }
378
+ /**
379
+ * `RetrieveOptions` is part of the experimental `@lunora/ai` API and may change without a major version bump.
380
+ * @experimental
381
+ */
382
+ interface RetrieveOptions {
383
+ /**
384
+ * Also return this many neighbouring chunks around each match (fetched by
385
+ * deterministic id, not re-queried) — "embed small, retrieve big". Neighbour
386
+ * text is stitched into the chunk's `text` in document order. Best combined
387
+ * with `chunkOverlap: 0`, since overlapping windows repeat boundary text.
388
+ */
389
+ chunkContext?: {
390
+ after?: number;
391
+ before?: number;
392
+ };
393
+ /**
394
+ * Vectorize filter expression — or the name of a pre-defined filter declared
395
+ * in `RagConfig.filters`. Passing a name that is not registered throws at
396
+ * call time, catching spelling mistakes early.
397
+ */
398
+ filter?: Record<string, unknown> | string;
399
+ /** Drop matches whose (importance-adjusted) score falls below this threshold. */
400
+ minScore?: number;
401
+ namespace?: string;
402
+ /**
403
+ * Fires after retrieval completes, before chunk expansion. Useful for
404
+ * observability — logging query latency, hit counts, etc.
405
+ */
406
+ onRetrieve?: (info: {
407
+ matches: number;
408
+ query: string;
409
+ }) => void;
410
+ topK?: number;
411
+ }
412
+ /**
413
+ * `RetrievedChunk` is part of the experimental `@lunora/ai` API and may change without a major version bump.
414
+ * @experimental
415
+ */
416
+ interface RetrievedChunk {
417
+ chunkIndex: number;
418
+ id: string;
419
+ /**
420
+ * The source-level importance weight that was multiplied into this chunk's
421
+ * score. `1` when no importance was set at index time.
422
+ */
423
+ importance: number;
424
+ /** Caller metadata stored on the vector (internal `__rag*` keys stripped). */
425
+ metadata?: Record<string, unknown>;
426
+ /** Cosine similarity, multiplied by the source's `importance` when one was set. */
427
+ score: number;
428
+ sourceId: string;
429
+ text: string;
430
+ }
431
+ /**
432
+ * `RagSource` is part of the experimental `@lunora/ai` API and may change without a major version bump.
433
+ * @experimental
434
+ */
435
+ interface RagSource {
436
+ id: string;
437
+ /** Caller metadata from the source's first-seen chunk (internal keys stripped). */
438
+ metadata?: Record<string, unknown>;
439
+ /**
440
+ * The source's importance weight (the `importance` value passed at index
441
+ * time, default 1), propagated so downstream consumers can factor it into
442
+ * their own ranking or UI.
443
+ */
444
+ weight?: number;
445
+ }
446
+ /**
447
+ * The retrieve return shape — designed so an agent memory step consumes it directly.
448
+ * @experimental
449
+ */
450
+ interface RetrieveResult {
451
+ /** Ranked chunks (best first). */
452
+ chunks: ReadonlyArray<RetrievedChunk>;
453
+ /** Ready-to-inject prompt context: chunks joined under `[source:&lt;id>#&lt;n>]` headers. */
454
+ context: string;
455
+ /** Deduped source references, in first-seen (best) order. */
456
+ sources: ReadonlyArray<RagSource>;
457
+ }
458
+ /**
459
+ * `RagToolOptions` is part of the experimental `@lunora/ai` API and may change without a major version bump.
460
+ * @experimental
461
+ */
462
+ interface RagToolOptions {
463
+ /** Tool description shown to the model. Defaults to a search description naming the index. */
464
+ description?: string;
465
+ /** Namespace applied to every tool-invoked retrieval (the tenant key). */
466
+ namespace?: string;
467
+ /** Retrieval depth for tool-invoked retrievals. */
468
+ topK?: number;
469
+ }
470
+ /**
471
+ * The per-request RAG surface returned by binding a ctx: `docs(ctx)`.
472
+ * @experimental
473
+ */
474
+ interface Rag {
475
+ /**
476
+ * Expose `retrieve` as an AI SDK tool (for `generateText`/`streamText`
477
+ * `tools:` maps), so a model can decide to search the index itself.
478
+ */
479
+ asTool: (options?: RagToolOptions) => Tool<{
480
+ query: string;
481
+ }, RetrieveResult>;
482
+ /**
483
+ * Chunk + embed + upsert one source document. Re-indexing the same `id` is
484
+ * an atomic-enough replace: unchanged content short-circuits via content
485
+ * hash, and stale chunks beyond the new count are deleted automatically.
486
+ */
487
+ index: (input: IndexInput) => Promise<IndexResult>;
488
+ /** Delete every chunk of a previously indexed source. */
489
+ remove: (input: RemoveInput) => Promise<void>;
490
+ /** Embed the query and return ranked chunks + prompt-ready context. */
491
+ retrieve: (query: string, options?: RetrieveOptions) => Promise<RetrieveResult>;
492
+ }
493
+ declare const defineRag: (config: RagConfig) => ((context: RagContext) => Rag);
494
+ /**
495
+ * Guess a MIME type from a file extension. Lowercases and strips a leading `.`
496
+ * from `extension`; returns `"application/octet-stream"` for unknown extensions.
497
+ *
498
+ * Covers the broad set of extensions users are likely to encounter in a web /
499
+ * document-processing context — images, video, audio, office docs, PDF, text,
500
+ * archives, and source code. Follows the same approach as Convex's
501
+ * `guessMimeType` helper.
502
+ * @experimental
503
+ */
504
+ declare const guessMimeTypeFromExtension: (extension: string) => string;
505
+ /**
506
+ * SHA-256 hex digest of binary data. Accepts a `BufferSource` (`ArrayBuffer` or
507
+ * `ArrayBufferView` such as `Uint8Array`). Useful for content-addressable
508
+ * storage — pair with `IndexInput.text` to detect duplicates across re-indexes.
509
+ * @experimental
510
+ */
511
+ declare const contentHash: (data: BufferSource) => Promise<string>;
512
+ /**
513
+ * Reciprocal Rank Fusion (RRF): merge two ranked lists of chunks by their
514
+ * _rank position_ rather than their absolute scores, which are not comparable
515
+ * across different search methods (cosine vs BM25).
516
+ *
517
+ * Each result set contributes `1 / (k + rank)` to each chunk's fused score,
518
+ * where `rank` is 0-based position in the list. The constant `k` (default 60)
519
+ * dampens the influence of high ranks — the standard value from the RRF
520
+ * literature that works well across domains.
521
+ *
522
+ * The fused list is sorted descending by fused score. Ties are broken by
523
+ * preferring the chunk ranked higher in the vector search result (typically
524
+ * the more semantically accurate of the two methods).
525
+ *
526
+ * Callers MUST ensure every chunk in both lists carries a unique, comparable
527
+ * `id` — this is guaranteed by the chunk-id scheme `${sourceId}#${chunkIndex}`.
528
+ * @experimental
529
+ */
530
+ declare const hybridRank: (vectorResults: ReadonlyArray<RetrievedChunk>, textResults: ReadonlyArray<RetrievedChunk>, k?: number) => ReadonlyArray<RetrievedChunk>;
531
+ /**
532
+ * An **in-memory** Okapi BM25 lexical store — the reference adapter behind
533
+ * `RagConfig.lexicalStore`, giving hybrid retrieval its keyword leg with zero
534
+ * infrastructure. State lives in the worker isolate: it is **not durable and not
535
+ * shared across isolates**, so it is intended for tests, local development, and
536
+ * single-isolate workloads. Production deployments plug a durable
537
+ * {@link RagLexicalStore} (a DO-SQLite inverted index, D1, or an external search
538
+ * service) behind the same seam.
539
+ *
540
+ * Tenant isolation is by `namespace` (each namespace keeps its own index). This
541
+ * store holds **no metadata**, so it cannot evaluate a metadata `filter`
542
+ * (including an `rlsFilter` result): when `search` is called with a non-empty
543
+ * filter it **fails closed** — returns no lexical hits and warns once — rather
544
+ * than risk surfacing a row the filter would exclude. If your RLS is
545
+ * metadata-based (not namespace-based) and you want a lexical leg, fold the RLS
546
+ * dimension into the `namespace` or plug a filter-aware store.
547
+ * @experimental
548
+ */
549
+ declare const bm25LexicalStore: () => RagLexicalStore;
550
+ export { type IndexInput, type IndexResult, type LexicalMatch, type Rag, type RagConfig, type RagContext, type RagEmbedder, type RagLexicalStore, type RagNamedFilter, type RagSource, type RagTextStore, type RagToolOptions, type RagVectorMatch, type RagVectorMatches, type RagVectorQueryInput, type RagVectorRecord, type RagVectorUpsertInput, type RagVectors, type RemoveInput, type RetrieveOptions, type RetrieveResult, type RetrievedChunk, type StoredRagChunk, bm25LexicalStore, contentHash, defineRag, fixedWindowChunks, guessMimeTypeFromExtension, hybridRank };
@@ -0,0 +1,5 @@
1
+ export { default as fixedWindowChunks } from '../packem_shared/fixedWindowChunks-J-WfQDw9.mjs';
2
+ export { default as defineRag } from '../packem_shared/defineRag-CQ3pnKUh.mjs';
3
+ export { contentHash, guessMimeTypeFromExtension } from '../packem_shared/contentHash-Cgz5KRGD.mjs';
4
+ export { default as hybridRank } from '../packem_shared/hybridRank-DPC9c2ON.mjs';
5
+ export { default as bm25LexicalStore } from '../packem_shared/bm25LexicalStore-BQzWLMqX.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/ai",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.21",
4
4
  "description": "Workers AI helper for Lunora: provider-agnostic AI SDK access from functions, Workers AI by default",
5
5
  "keywords": [
6
6
  "ai",
@@ -10,6 +10,8 @@
10
10
  "inference",
11
11
  "llm",
12
12
  "lunora",
13
+ "rag",
14
+ "retrieval",
13
15
  "workers",
14
16
  "workers-ai"
15
17
  ],
@@ -41,18 +43,23 @@
41
43
  "types": "./dist/index.d.ts",
42
44
  "import": "./dist/index.mjs"
43
45
  },
46
+ "./rag": {
47
+ "types": "./dist/rag/index.d.ts",
48
+ "import": "./dist/rag/index.mjs"
49
+ },
44
50
  "./package.json": "./package.json"
45
51
  },
46
52
  "publishConfig": {
47
53
  "access": "public"
48
54
  },
49
55
  "dependencies": {
50
- "ai": "6.0.208",
51
- "workers-ai-provider": "3.2.0"
56
+ "@lunora/errors": "1.0.0-alpha.7",
57
+ "ai": "7.0.31",
58
+ "workers-ai-provider": "3.3.1"
52
59
  },
53
60
  "peerDependencies": {
54
- "@ai-sdk/anthropic": "3.0.85",
55
- "@ai-sdk/openai": "3.0.73"
61
+ "@ai-sdk/anthropic": "4.0.16",
62
+ "@ai-sdk/openai": "4.0.16"
56
63
  },
57
64
  "peerDependenciesMeta": {
58
65
  "@ai-sdk/anthropic": {