@lunora/ai 1.0.0-alpha.11 → 1.0.0-alpha.110

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.
Files changed (32) hide show
  1. package/README.md +18 -9
  2. package/dist/index.d.mts +93 -27
  3. package/dist/index.d.ts +93 -27
  4. package/dist/index.mjs +1 -3
  5. package/dist/packem_shared/AI_DEFAULT_EMBEDDING_MODEL_ENV-IwcHZiKj.mjs +1 -0
  6. package/dist/packem_shared/DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs +1 -0
  7. package/dist/packem_shared/VECTORIZE_CAPABILITIES-CUQDoxis.mjs +1 -0
  8. package/dist/packem_shared/batchReranker-Bc38FBLH.mjs +1 -0
  9. package/dist/packem_shared/bm25-9q0Avwi-.mjs +1 -0
  10. package/dist/packem_shared/bm25LexicalStore-DMUzAL0O.mjs +1 -0
  11. package/dist/packem_shared/concurrent-C6nqBv41.mjs +1 -0
  12. package/dist/packem_shared/contentHash-BIn6ECP8.mjs +1 -0
  13. package/dist/packem_shared/createAi-Cebawe9R.mjs +1 -0
  14. package/dist/packem_shared/defineRag-CMTzKfS7.mjs +8 -0
  15. package/dist/packem_shared/defineRagSource-Q3f3niU8.mjs +1 -0
  16. package/dist/packem_shared/fixedWindowChunks-C461ahRE.mjs +1 -0
  17. package/dist/packem_shared/hybridRank-B4skyCLx.mjs +1 -0
  18. package/dist/packem_shared/markdownChunker-iW8V4klY.mjs +5 -0
  19. package/dist/packem_shared/matchesMetadataFilter-BbIOyA5g.mjs +1 -0
  20. package/dist/packem_shared/ragSyncTriggers-hgMcA4f2.mjs +1 -0
  21. package/dist/packem_shared/sql-D5aqEMCY.mjs +1 -0
  22. package/dist/packem_shared/sqlLexicalStore-4C_cIwef.mjs +1 -0
  23. package/dist/packem_shared/sqliteVectorStore-SjOFKoHo.mjs +1 -0
  24. package/dist/packem_shared/stable-key-B_BlboiY.mjs +1 -0
  25. package/dist/packem_shared/types.d-D-WK1t2B.d.mts +473 -0
  26. package/dist/packem_shared/types.d-D-WK1t2B.d.ts +473 -0
  27. package/dist/packem_shared/usage-GPnTSqR8.mjs +1 -0
  28. package/dist/rag/index.d.mts +1364 -0
  29. package/dist/rag/index.d.ts +1364 -0
  30. package/dist/rag/index.mjs +1 -0
  31. package/package.json +12 -16
  32. package/dist/packem_shared/createAi-Bq_4LMcp.mjs +0 -54
package/README.md CHANGED
@@ -10,6 +10,8 @@
10
10
 
11
11
  <!-- END_PACKAGE_OG_IMAGE_PLACEHOLDER -->
12
12
 
13
+ > **Experimental** — this package is outside the Lunora 1.0 stability promise: its API may change in any release, without a major version bump.
14
+
13
15
  <br />
14
16
 
15
17
  <div align="center">
@@ -34,7 +36,7 @@
34
36
 
35
37
  ---
36
38
 
37
- A small AI helper for Lunora, built on the [Vercel AI SDK](https://ai-sdk.dev) v6 core and Cloudflare's official [`workers-ai-provider`](https://github.com/cloudflare/ai). Call `generateText`/`streamText`/`generateObject`/`embed`/`tool` from any function handler. **Cloudflare Workers AI is the zero-config default**, but the helper is provider-agnostic: every call takes either a Workers AI model id (a string) or any AI SDK model object — `@ai-sdk/openai`, `@ai-sdk/anthropic`, OpenRouter, … — so apps are never locked to Workers AI. Pair `embed` with [`@lunora/bindings/vectors`](https://www.npmjs.com/package/@lunora/bindings) for RAG.
39
+ A small AI helper for Lunora, built on the [Vercel AI SDK](https://ai-sdk.dev) v7 core and Cloudflare's official [`workers-ai-provider`](https://github.com/cloudflare/ai). Call `generateText`/`streamText`/`generateObject`/`embed`/`tool` from any function handler. **Cloudflare Workers AI is the zero-config default**, but the helper is provider-agnostic: every call takes either a Workers AI model id (a string) or any AI SDK model object — `@ai-sdk/openai`, `@ai-sdk/anthropic`, OpenRouter, … — so apps are never locked to Workers AI. Pair `embed` with [`@lunora/bindings/vectors`](https://www.npmjs.com/package/@lunora/bindings) for RAG.
38
40
 
39
41
  Part of the [Lunora](https://github.com/anolilab/lunora) framework — a type-safe, real-time backend on Cloudflare Workers + Durable Objects with a Vite-first DX.
40
42
 
@@ -52,19 +54,25 @@ yarn add @lunora/ai
52
54
  pnpm add @lunora/ai
53
55
  ```
54
56
 
55
- To use another provider, install it alongside (optional): `pnpm add @ai-sdk/openai`.
57
+ Other providers need no extra install: `ctx.ai.model("anthropic/claude-sonnet-5")` routes through Cloudflare AI Gateway (see below).
56
58
 
57
59
  ## Usage
58
60
 
59
- When a function uses AI, codegen wires a typed **`ctx.ai`** onto the action context (inference is an external call, so — like `ctx.fetch` — it lives on actions). Workers AI is the zero-config default; pass any AI SDK model to use another provider.
61
+ When a function uses AI, codegen wires a typed **`ctx.ai`** onto the action context (inference is an external call, so — like `ctx.fetch` — it lives on actions). Workers AI is the zero-config default; a `"<provider>/<model>"` id reaches any other provider through Cloudflare AI Gateway, and any AI SDK model object passes straight through.
60
62
 
61
63
  ```ts
62
64
  // lunora/summarize.ts — ctx.ai (codegen-wired), Workers AI by default
63
65
  import { action, v } from "@/lunora/_generated/server";
64
66
  import { generateText } from "@lunora/ai";
65
67
 
66
- export const summarize = action.input({ text: v.string() }).action(async ({ args: { text }, ctx }) => {
68
+ export const summarize = action.input({ text: v.string().max(20_000) }).action(async ({ args: { text }, ctx }) => {
67
69
  const { text: summary } = await generateText({
70
+ // Both ends of the token bill are bounded: the input by `.max()` above,
71
+ // the completion here. An `action` is public RPC and inference is
72
+ // metered, so an unbounded completion is a denial-of-wallet vector — a
73
+ // short prompt can ask for an arbitrarily long answer, and output
74
+ // tokens are the expensive half.
75
+ maxOutputTokens: 300,
68
76
  model: ctx.ai.model("@cf/meta/llama-3.3-70b-instruct-fp8-fast"),
69
77
  prompt: `Summarize:\n\n${text}`,
70
78
  });
@@ -74,13 +82,14 @@ export const summarize = action.input({ text: v.string() }).action(async ({ args
74
82
  ```
75
83
 
76
84
  ```ts
77
- // Bring-your-own provider — same call surface, no lock-in
78
- import { streamText } from "@lunora/ai";
79
- import { openai } from "@ai-sdk/openai";
80
-
81
- const result = streamText({ model: openai("gpt-5"), messages });
85
+ // Any provider: change the string, nothing else. Routed through Cloudflare AI
86
+ // Gateway over the same `AI` binding — Unified Billing or keys stored on the
87
+ // gateway, so the app holds no provider API key.
88
+ const result = streamText({ model: ctx.ai.model("anthropic/claude-sonnet-5"), messages });
82
89
  ```
83
90
 
91
+ Every `ctx.ai.model(...)` call is traced and its tokens and cost are counted per function (`gen_ai.usage.*`), which Studio's **AI usage** page charts. `lunora ai gateway` creates a gateway for the app and sets `LUNORA_AI_GATEWAY_ID`; without it, calls use the account's `default` gateway.
92
+
84
93
  ```ts
85
94
  // RAG: embed via ctx.ai, store/search with @lunora/bindings/vectors (ctx.vectors)
86
95
  import { embed } from "@lunora/ai";
package/dist/index.d.mts CHANGED
@@ -1,30 +1,96 @@
1
- import { EmbeddingModel, LanguageModel } from 'ai';
2
- export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, streamObject, streamText, tool } from 'ai';
1
+ import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-D-WK1t2B.mjs";
2
+ export { A as AI_DEFAULT_EMBEDDING_MODEL_ENV, b as AI_DEFAULT_MODEL_ENV, c as AI_GATEWAY_ACCOUNT_ID_ENV, d as AI_GATEWAY_ID_ENV, e as AI_GATEWAY_METADATA_MAX_KEYS, f as AI_GATEWAY_TAGS_ENV, g as AI_GATEWAY_TOKEN_ENV, h as AI_PROXY_TOKEN_ENV, i as AI_PROXY_URL_ENV, type j as AiBindingLike, type k as AiGatewayMetadata, type l as AiGatewayOptions, type m as AiMetrics, type n as AiModelOptions, type o as AiRunOptions, type p as AiSpan, type q as AiTelemetry, type r as AiTracer, type s as AiWebSearchItem, type t as AiWebSearchOptions, type u as AiWebSearchProvider, type v as AiWebSearchResult, type E as EmbeddingModelInput, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, w as buildAiGatewayMetadataFields, x as readAiGatewayEnvTags, y as resolveAiGateway } from "./packem_shared/types.d-D-WK1t2B.mjs";
3
+ export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, hasToolCall, jsonSchema, streamObject, streamText, tool } from 'ai';
3
4
  export { createWorkersAI } from 'workers-ai-provider';
4
- interface AiBindingLike {
5
- run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
6
- }
7
- interface WorkersAiProviderLike {
8
- (modelId: string, settings?: Record<string, unknown>): LanguageModel;
9
- textEmbeddingModel?: (modelId: string) => EmbeddingModel;
10
- }
11
- interface AiGatewayOptions {
12
- [key: string]: unknown;
13
- id: string;
14
- }
15
- interface LunoraAiOptions {
16
- binding?: AiBindingLike;
17
- defaultModel?: string;
18
- gateway?: AiGatewayOptions;
19
- provider?: WorkersAiProviderLike;
5
+ /**
6
+ * Create the `ctx.ai` helper over a Workers `AI` binding.
7
+ *
8
+ * Workers AI is the zero-config default, but `@lunora/ai` is provider-agnostic:
9
+ * every helper takes either a model id string (resolved against the Workers AI
10
+ * provider) or any AI SDK {@link LanguageModel}/{@link EmbeddingModel} object
11
+ * (`@ai-sdk/openai`, `@ai-sdk/anthropic`, OpenRouter, …), so apps are never
12
+ * locked to Workers AI. Pair `embed` with `@lunora/bindings/vectors` for RAG.
13
+ *
14
+ * Without a binding, a string id resolves only as a `"<provider>/<model>"` slug
15
+ * through {@link AI_PROXY_URL_ENV}; everything else that needs the binding
16
+ * throws a directed error when called, never at construction — so the
17
+ * generated `ctx.ai` is always this facade.
18
+ *
19
+ * Combine with the re-exported `generateText`/`streamText`/`generateObject`/
20
+ * `embed`/`tool` from this package:
21
+ *
22
+ * ```ts
23
+ * import { streamText } from "@lunora/ai";
24
+ *
25
+ * const result = streamText({
26
+ * model: ctx.ai.model("@cf/meta/llama-3.3-70b-instruct-fp8-fast"),
27
+ * messages,
28
+ * });
29
+ * ```
30
+ * @experimental
31
+ */
32
+ declare const createAi: (options: LunoraAiOptions) => LunoraAi;
33
+ /**
34
+ * Model cost estimation without an AI Gateway.
35
+ *
36
+ * Per-request dollar cost previously reached a span only when a Cloudflare AI
37
+ * Gateway put it in `providerMetadata`. Run the same model through
38
+ * `@ai-sdk/openai` directly, or on any non-Cloudflare host, and spend
39
+ * visibility silently disappeared — inside a telemetry stack that is otherwise
40
+ * host-neutral by design.
41
+ *
42
+ * This derives cost from token usage and a price table instead, so the number
43
+ * is there either way.
44
+ *
45
+ * **An estimate is never presented as a measurement.** A provider-reported cost
46
+ * always wins, and a span carrying an estimate is tagged
47
+ * `lunora.usage.cost.source: "estimated"` so a dashboard can tell the two
48
+ * apart. Getting that wrong turns a rounding error into a billing dispute.
49
+ *
50
+ * **Prices go stale.** The shipped table is indicative, not authoritative — it
51
+ * is a hand-maintained snapshot, and providers change prices without warning.
52
+ * Pass your own `prices` for anything you are actually invoicing against.
53
+ * @experimental
54
+ */
55
+ /** What one model costs, in USD per **one million** tokens. */
56
+ interface ModelPrice {
57
+ /** Price per million input (prompt) tokens. */
58
+ input: number;
59
+ /** Price per million output (completion) tokens. Omit for an embedding model. */
60
+ output?: number;
20
61
  }
21
- type ModelInput = LanguageModel;
22
- type EmbeddingModelInput = EmbeddingModel | string;
23
- interface LunoraAi {
24
- embeddingModel: (model?: EmbeddingModelInput) => EmbeddingModel;
25
- model: (model?: ModelInput) => LanguageModel;
26
- run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
27
- workersai: WorkersAiProviderLike;
62
+ /** Token counts to price. Either may be absent. */
63
+ interface ModelUsage {
64
+ inputTokens?: number;
65
+ outputTokens?: number;
28
66
  }
29
- declare const createAi: (options: LunoraAiOptions) => LunoraAi;
30
- export { type AiBindingLike, type AiGatewayOptions, type EmbeddingModelInput, type LunoraAi, type LunoraAiOptions, type ModelInput, type WorkersAiProviderLike, createAi };
67
+ /**
68
+ * An indicative price table, keyed by model id.
69
+ *
70
+ * Deliberately small: a table that tries to cover every model is a table that
71
+ * is wrong about most of them. It holds the models Lunora's own defaults and
72
+ * documented examples reference — text generation and embeddings, on Workers AI
73
+ * and OpenAI — and everything else returns `undefined` rather than a guess.
74
+ *
75
+ * Generation models carry an `output` price; embedding models do not (they have
76
+ * no completion). A generation model missing from here is why a chat span would
77
+ * carry no cost at all off an AI Gateway, so the ids the docs teach are the ones
78
+ * that have to be in the table.
79
+ */
80
+ declare const DEFAULT_MODEL_PRICES: Readonly<Record<string, ModelPrice>>;
81
+ /**
82
+ * Look up a model's price, or `undefined` when the table does not cover it.
83
+ * @experimental
84
+ */
85
+ declare const lookupModelPrice: (modelId: string, prices?: Readonly<Record<string, ModelPrice>>) => ModelPrice | undefined;
86
+ /**
87
+ * Estimate a call's cost in USD from its token usage, or `undefined` when the
88
+ * model is not priced or no usable token count was supplied.
89
+ *
90
+ * Returns `undefined` rather than `0` for an unpriced model: zero is a
91
+ * defensible cost that would quietly sum into a total, while an absent value
92
+ * shows up as absent.
93
+ * @experimental
94
+ */
95
+ declare const estimateModelCost: (modelId: string | undefined, usage: ModelUsage, prices?: Readonly<Record<string, ModelPrice>>) => number | undefined;
96
+ export { DEFAULT_MODEL_PRICES, type LunoraAi, type LunoraAiOptions, type ModelPrice, type ModelUsage, createAi, estimateModelCost, lookupModelPrice };
package/dist/index.d.ts CHANGED
@@ -1,30 +1,96 @@
1
- import { EmbeddingModel, LanguageModel } from 'ai';
2
- export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, streamObject, streamText, tool } from 'ai';
1
+ import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-D-WK1t2B.js";
2
+ export { A as AI_DEFAULT_EMBEDDING_MODEL_ENV, b as AI_DEFAULT_MODEL_ENV, c as AI_GATEWAY_ACCOUNT_ID_ENV, d as AI_GATEWAY_ID_ENV, e as AI_GATEWAY_METADATA_MAX_KEYS, f as AI_GATEWAY_TAGS_ENV, g as AI_GATEWAY_TOKEN_ENV, h as AI_PROXY_TOKEN_ENV, i as AI_PROXY_URL_ENV, type j as AiBindingLike, type k as AiGatewayMetadata, type l as AiGatewayOptions, type m as AiMetrics, type n as AiModelOptions, type o as AiRunOptions, type p as AiSpan, type q as AiTelemetry, type r as AiTracer, type s as AiWebSearchItem, type t as AiWebSearchOptions, type u as AiWebSearchProvider, type v as AiWebSearchResult, type E as EmbeddingModelInput, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, w as buildAiGatewayMetadataFields, x as readAiGatewayEnvTags, y as resolveAiGateway } from "./packem_shared/types.d-D-WK1t2B.js";
3
+ export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, hasToolCall, jsonSchema, streamObject, streamText, tool } from 'ai';
3
4
  export { createWorkersAI } from 'workers-ai-provider';
4
- interface AiBindingLike {
5
- run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
6
- }
7
- interface WorkersAiProviderLike {
8
- (modelId: string, settings?: Record<string, unknown>): LanguageModel;
9
- textEmbeddingModel?: (modelId: string) => EmbeddingModel;
10
- }
11
- interface AiGatewayOptions {
12
- [key: string]: unknown;
13
- id: string;
14
- }
15
- interface LunoraAiOptions {
16
- binding?: AiBindingLike;
17
- defaultModel?: string;
18
- gateway?: AiGatewayOptions;
19
- provider?: WorkersAiProviderLike;
5
+ /**
6
+ * Create the `ctx.ai` helper over a Workers `AI` binding.
7
+ *
8
+ * Workers AI is the zero-config default, but `@lunora/ai` is provider-agnostic:
9
+ * every helper takes either a model id string (resolved against the Workers AI
10
+ * provider) or any AI SDK {@link LanguageModel}/{@link EmbeddingModel} object
11
+ * (`@ai-sdk/openai`, `@ai-sdk/anthropic`, OpenRouter, …), so apps are never
12
+ * locked to Workers AI. Pair `embed` with `@lunora/bindings/vectors` for RAG.
13
+ *
14
+ * Without a binding, a string id resolves only as a `"<provider>/<model>"` slug
15
+ * through {@link AI_PROXY_URL_ENV}; everything else that needs the binding
16
+ * throws a directed error when called, never at construction — so the
17
+ * generated `ctx.ai` is always this facade.
18
+ *
19
+ * Combine with the re-exported `generateText`/`streamText`/`generateObject`/
20
+ * `embed`/`tool` from this package:
21
+ *
22
+ * ```ts
23
+ * import { streamText } from "@lunora/ai";
24
+ *
25
+ * const result = streamText({
26
+ * model: ctx.ai.model("@cf/meta/llama-3.3-70b-instruct-fp8-fast"),
27
+ * messages,
28
+ * });
29
+ * ```
30
+ * @experimental
31
+ */
32
+ declare const createAi: (options: LunoraAiOptions) => LunoraAi;
33
+ /**
34
+ * Model cost estimation without an AI Gateway.
35
+ *
36
+ * Per-request dollar cost previously reached a span only when a Cloudflare AI
37
+ * Gateway put it in `providerMetadata`. Run the same model through
38
+ * `@ai-sdk/openai` directly, or on any non-Cloudflare host, and spend
39
+ * visibility silently disappeared — inside a telemetry stack that is otherwise
40
+ * host-neutral by design.
41
+ *
42
+ * This derives cost from token usage and a price table instead, so the number
43
+ * is there either way.
44
+ *
45
+ * **An estimate is never presented as a measurement.** A provider-reported cost
46
+ * always wins, and a span carrying an estimate is tagged
47
+ * `lunora.usage.cost.source: "estimated"` so a dashboard can tell the two
48
+ * apart. Getting that wrong turns a rounding error into a billing dispute.
49
+ *
50
+ * **Prices go stale.** The shipped table is indicative, not authoritative — it
51
+ * is a hand-maintained snapshot, and providers change prices without warning.
52
+ * Pass your own `prices` for anything you are actually invoicing against.
53
+ * @experimental
54
+ */
55
+ /** What one model costs, in USD per **one million** tokens. */
56
+ interface ModelPrice {
57
+ /** Price per million input (prompt) tokens. */
58
+ input: number;
59
+ /** Price per million output (completion) tokens. Omit for an embedding model. */
60
+ output?: number;
20
61
  }
21
- type ModelInput = LanguageModel;
22
- type EmbeddingModelInput = EmbeddingModel | string;
23
- interface LunoraAi {
24
- embeddingModel: (model?: EmbeddingModelInput) => EmbeddingModel;
25
- model: (model?: ModelInput) => LanguageModel;
26
- run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
27
- workersai: WorkersAiProviderLike;
62
+ /** Token counts to price. Either may be absent. */
63
+ interface ModelUsage {
64
+ inputTokens?: number;
65
+ outputTokens?: number;
28
66
  }
29
- declare const createAi: (options: LunoraAiOptions) => LunoraAi;
30
- export { type AiBindingLike, type AiGatewayOptions, type EmbeddingModelInput, type LunoraAi, type LunoraAiOptions, type ModelInput, type WorkersAiProviderLike, createAi };
67
+ /**
68
+ * An indicative price table, keyed by model id.
69
+ *
70
+ * Deliberately small: a table that tries to cover every model is a table that
71
+ * is wrong about most of them. It holds the models Lunora's own defaults and
72
+ * documented examples reference — text generation and embeddings, on Workers AI
73
+ * and OpenAI — and everything else returns `undefined` rather than a guess.
74
+ *
75
+ * Generation models carry an `output` price; embedding models do not (they have
76
+ * no completion). A generation model missing from here is why a chat span would
77
+ * carry no cost at all off an AI Gateway, so the ids the docs teach are the ones
78
+ * that have to be in the table.
79
+ */
80
+ declare const DEFAULT_MODEL_PRICES: Readonly<Record<string, ModelPrice>>;
81
+ /**
82
+ * Look up a model's price, or `undefined` when the table does not cover it.
83
+ * @experimental
84
+ */
85
+ declare const lookupModelPrice: (modelId: string, prices?: Readonly<Record<string, ModelPrice>>) => ModelPrice | undefined;
86
+ /**
87
+ * Estimate a call's cost in USD from its token usage, or `undefined` when the
88
+ * model is not priced or no usable token count was supplied.
89
+ *
90
+ * Returns `undefined` rather than `0` for an unpriced model: zero is a
91
+ * defensible cost that would quietly sum into a total, while an absent value
92
+ * shows up as absent.
93
+ * @experimental
94
+ */
95
+ declare const estimateModelCost: (modelId: string | undefined, usage: ModelUsage, prices?: Readonly<Record<string, ModelPrice>>) => number | undefined;
96
+ export { DEFAULT_MODEL_PRICES, type LunoraAi, type LunoraAiOptions, type ModelPrice, type ModelUsage, createAi, estimateModelCost, lookupModelPrice };
package/dist/index.mjs CHANGED
@@ -1,3 +1 @@
1
- export { default as createAi } from './packem_shared/createAi-Bq_4LMcp.mjs';
2
- export { embed, embedMany, generateObject, generateText, streamObject, streamText, tool } from 'ai';
3
- export { createWorkersAI } from 'workers-ai-provider';
1
+ import{default as _}from"./packem_shared/createAi-Cebawe9R.mjs";import{AI_DEFAULT_EMBEDDING_MODEL_ENV as t,AI_DEFAULT_MODEL_ENV as a,AI_GATEWAY_ACCOUNT_ID_ENV as o,AI_GATEWAY_ID_ENV as r,AI_GATEWAY_METADATA_MAX_KEYS as T,AI_GATEWAY_TAGS_ENV as I,AI_GATEWAY_TOKEN_ENV as N,AI_PROXY_TOKEN_ENV as l,AI_PROXY_URL_ENV as m,buildAiGatewayMetadataFields as s,readAiGatewayEnvTags as D,resolveAiGateway as G}from"./packem_shared/AI_DEFAULT_EMBEDDING_MODEL_ENV-IwcHZiKj.mjs";import{DEFAULT_MODEL_PRICES as O,estimateModelCost as d,lookupModelPrice as i}from"./packem_shared/DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs";import{embed as Y,embedMany as x,generateObject as L,generateText as c,hasToolCall as f,jsonSchema as p,streamObject as W,streamText as b,tool as n}from"ai";import{createWorkersAI as U}from"workers-ai-provider";export{t as AI_DEFAULT_EMBEDDING_MODEL_ENV,a as AI_DEFAULT_MODEL_ENV,o as AI_GATEWAY_ACCOUNT_ID_ENV,r as AI_GATEWAY_ID_ENV,T as AI_GATEWAY_METADATA_MAX_KEYS,I as AI_GATEWAY_TAGS_ENV,N as AI_GATEWAY_TOKEN_ENV,l as AI_PROXY_TOKEN_ENV,m as AI_PROXY_URL_ENV,O as DEFAULT_MODEL_PRICES,s as buildAiGatewayMetadataFields,_ as createAi,U as createWorkersAI,Y as embed,x as embedMany,d as estimateModelCost,L as generateObject,c as generateText,f as hasToolCall,p as jsonSchema,i as lookupModelPrice,D as readAiGatewayEnvTags,G as resolveAiGateway,W as streamObject,b as streamText,n as tool};
@@ -0,0 +1 @@
1
+ const a=(t,n)=>{if(t===void 0)return;const e=t[n];return typeof e=="string"&&e.length>0?e:void 0};let _=!1,d=!1;const f=5,E=t=>{if(t===void 0)return;const n={};typeof t.functionPath=="string"&&t.functionPath.length>0&&(n.functionPath=t.functionPath),typeof t.traceId=="string"&&t.traceId.length>0&&(n.traceId=t.traceId);const e={};for(const[o,s]of Object.entries(t.tags??{}))typeof s=="string"&&s.length>0&&o.length>0&&!Object.hasOwn(n,o)&&(e[o]=s);Object.assign(e,n);const r=Object.keys(e);if(r.length===0)return;if(r.length<=f)return e;const i=r.slice(-f);return Object.fromEntries(i.map(o=>[o,e[o]]))},g=t=>{const n=E(t);return n===void 0?void 0:JSON.stringify(n)},h="LUNORA_AI_DEFAULT_MODEL",T="LUNORA_AI_DEFAULT_EMBEDDING_MODEL",l="LUNORA_AI_GATEWAY_ACCOUNT_ID",I="LUNORA_AI_GATEWAY_ID",c="LUNORA_AI_GATEWAY_TOKEN",u="LUNORA_AI_GATEWAY_TAGS",N="LUNORA_AI_PROXY_URL",y="LUNORA_AI_PROXY_TOKEN",v=t=>{const n=a(t,u);if(n!==void 0)try{const e=JSON.parse(n);if(typeof e!="object"||e===null||Array.isArray(e))throw new TypeError("expected a JSON object");const r={};for(const[i,o]of Object.entries(e))typeof o=="string"&&o.length>0&&(r[i]=o);return Object.keys(r).length>0?r:void 0}catch{d||(d=!0,console.warn(`[lunora:ai] ${u} is not a flat JSON object of strings — AI Gateway tags from it are ignored.`));return}},O=t=>{_||a(t,c)===void 0||(_=!0,console.warn(`[lunora:ai] ${c} is set, but the Workers AI binding cannot send a gateway auth token — Cloudflare's native gateway option has no authorization field. The token is ignored on this path; use a bring-your-own AI SDK provider (which sends cf-aig-authorization), or make the AI Gateway unauthenticated for Workers AI.`))},w=(t,n,e="byo-provider")=>{const r=a(t,l),i=a(t,I);if(r===void 0||i===void 0)return;const o=a(t,c),s={};o!==void 0&&(s["cf-aig-authorization"]=`Bearer ${o}`,e==="workers-ai-binding"&&O(t));const A=g(n);return A!==void 0&&(s["cf-aig-metadata"]=A),{accountId:r,baseURL:`https://gateway.ai.cloudflare.com/v1/${r}/${i}`,gatewayId:i,headers:s}};export{T as AI_DEFAULT_EMBEDDING_MODEL_ENV,h as AI_DEFAULT_MODEL_ENV,l as AI_GATEWAY_ACCOUNT_ID_ENV,I as AI_GATEWAY_ID_ENV,f as AI_GATEWAY_METADATA_MAX_KEYS,u as AI_GATEWAY_TAGS_ENV,c as AI_GATEWAY_TOKEN_ENV,y as AI_PROXY_TOKEN_ENV,N as AI_PROXY_URL_ENV,E as buildAiGatewayMetadataFields,v as readAiGatewayEnvTags,a as readEnv,w as resolveAiGateway,O as warnIgnoredBindingToken};
@@ -0,0 +1 @@
1
+ const p=/^(.*)-\d{4}-\d{2}-\d{2}$/u,a={"@cf/baai/bge-base-en-v1.5":{input:.067},"@cf/baai/bge-large-en-v1.5":{input:.204},"@cf/baai/bge-m3":{input:.012},"@cf/baai/bge-small-en-v1.5":{input:.02},"@cf/meta/llama-3.1-8b-instruct":{input:.28,output:.83},"@cf/meta/llama-3.3-70b-instruct-fp8-fast":{input:.29,output:2.25},"text-embedding-3-large":{input:.13},"text-embedding-3-small":{input:.02},"gpt-4o":{input:2.5,output:10},"gpt-4o-mini":{input:.15,output:.6},"gpt-5":{input:1.25,output:10}},c=e=>{const t=e.trim(),i=t.indexOf("/@"),n=i===-1?t:t.slice(i+1),u=n.lastIndexOf("/");return(u!==-1&&!n.startsWith("@")?[n,n.slice(u+1)]:[n]).flatMap(o=>{const r=p.exec(o)?.[1];return r===void 0?[o]:[o,r]})},b=(e,t=a)=>{for(const i of c(e))if(Object.hasOwn(t,i))return t[i]},d=(e,t,i)=>{if(e===void 0||e.length===0)return;const n=b(e,i);if(n===void 0)return;const u=Number.isFinite(t.inputTokens)?Math.max(0,t.inputTokens):0,s=Number.isFinite(t.outputTokens)?Math.max(0,t.outputTokens):0;if(u<=0&&s<=0)return;const o=(u*n.input+s*(n.output??0))/1e6;return Number.isFinite(o)?o:void 0};export{a as DEFAULT_MODEL_PRICES,d as estimateModelCost,b as lookupModelPrice};
@@ -0,0 +1 @@
1
+ const I={maxDimensions:1536,maxIdBytes:64,maxMetadataBytes:10240,maxTopK:100,maxTopKWithMetadata:50},y=(e,a)=>({capabilities:I,deleteByIds:(t,s)=>e.deleteByIds(a,t,s),getByIds:(t,s)=>e.getByIds(a,t,s),query:t=>e.query(a,t),upsert:t=>e.upsert(a,t)});export{I as VECTORIZE_CAPABILITIES,y as vectorizeStore};
@@ -0,0 +1 @@
1
+ import{c as s}from"./concurrent-C6nqBv41.mjs";const a=8,c=(t,n,e)=>t.map((r,o)=>({chunk:r,score:n[o]})).filter(r=>Number.isFinite(r.score)&&(e===void 0||r.score>=e)).toSorted((r,o)=>o.score-r.score).map(r=>({...r.chunk,score:r.score})),l=t=>{if(typeof t.score!="function")throw new TypeError("scoreReranker: `score` must be a function");return async(n,e)=>{if(e.length===0)return e;const r=await s(e,a,async o=>t.score(n,o.text));return c(e,r,t.minScore)}},f=t=>{if(typeof t.scoreAll!="function")throw new TypeError("batchReranker: `scoreAll` must be a function");return async(n,e)=>{if(e.length===0)return e;const r=await t.scoreAll(n,e.map(o=>o.text));if(r.length!==e.length)throw new TypeError(`batchReranker: \`scoreAll\` returned ${String(r.length)} scores for ${String(e.length)} passages — it must return one score per passage, in order`);return c(e,r,t.minScore)}};export{f as batchReranker,l as scoreReranker};
@@ -0,0 +1 @@
1
+ const S=["de","en","es","fr","it","nl","none","pt"],b=n=>S.includes(n),v="a an and are as at be but by for if in into is it no not of on or such that the their then there these they this to was will with",x="aber als am an auch auf aus bei bin bis bist da dass der den des dem die das denn dir du ein eine für hat ich im in ist mit nicht noch nur oder sich sie sind über und von vor war wie wir zu zum zur",z="a al como con de del el en es la las lo los mas no o para pero por que se su sus un una uno y ya",j="au aux avec ce ces dans de des du elle en et eux il je la le les leur lui ma mais me même mes moi mon ne nos notre nous on ou par pas pour qu que qui sa se ses son sur ta te tes toi ton tu un une vos votre vous y",_="a ai al alla anche che chi ci coi col come con da dal degli dei del della di do e ed gli ha hai hanno i il in la le lo ma mi ne nei nel non o per più quale quanto se si sono su sul tra un una uno vi",E="aan al als bij dan dat de der deze die dit door een en er het hij ij in is je kan me men met mij na naar niet nog nu of om ons ook op over te tot uit van voor was wat we wij zij zijn zo",q="a ao aos as até com como da das de do dos e em entre era essa esse esta este eu foi há isso já mais mas me mesmo meu na nas no nos num numa o os ou para pela pelo por qual que quem se sem seu só sua também te tem um uma você",y=new RegExp("(?<=\\p{Script=Cyrillic})[\\u0300-\\u0305\\u0307\\u0309-\\u036F]|(?<!\\p{Script=Cyrillic})[\\u0300-\\u036F]","gu"),F=/[\u0080-\u{10FFFF}]/u,k=/[\p{L}\p{N}]+/gu,C=/[\p{Script_Extensions=Han}\p{Script_Extensions=Hiragana}\p{Script_Extensions=Katakana}\p{Script_Extensions=Hangul}]+/gu,f=/[\p{Script_Extensions=Han}\p{Script_Extensions=Hiragana}\p{Script_Extensions=Katakana}\p{Script_Extensions=Hangul}]/u,H=n=>{if(!f.test(n))return[n];const e=[];let t=0;for(const s of n.matchAll(C)){const o=s.index,i=[...s[0]];if(o>t&&e.push(n.slice(t,o)),t=o+s[0].length,i.length===1){e.push(s[0]);continue}for(let a=0;a+1<i.length;a+=1)e.push(`${String(i[a])}${String(i[a+1])}`)}return t<n.length&&e.push(n.slice(t)),e},g=n=>F.test(n)?n.normalize("NFD").replaceAll(y,"").normalize("NFC").toLowerCase():n.toLowerCase(),r=n=>new Set(g(n).split(" ")),M={de:r(x),en:r(v),es:r(z),fr:r(j),it:r(_),nl:r(E),none:new Set,pt:r(q)},N=3,$=256,m=new Map,B=n=>{const e=n!==void 0&&b(n)?n:"none",t=m.get(e);if(t)return t;const s=M[e],o=a=>{const u=g(a),l=u.match(k)??[],d=(f.test(u)?l.flatMap(c=>H(c)):l).filter(c=>c.length<=$);return s.size===0?d:d.filter(c=>!s.has(c))},i={document:o,profile:`${e}-v${String(N)}`,query:a=>{const u=o(a);return u.filter((l,d)=>u.lastIndexOf(l)===d)}};return m.set(e,i),i},p=1.5,h=.75,w=B(void 0),K=n=>w.document(n),L=n=>w.query(n),A=(n,e)=>Math.log(1+(n-e+.5)/(e+.5)),D=(n,e,t,s)=>{const o=e+p*(1-h+h*t/s);return n*(e*(p+1)/o)};export{K as a,D as b,A as c,L as t};
@@ -0,0 +1 @@
1
+ import{t as x,b as y,a as M,c as k}from"./bm25-9q0Avwi-.mjs";import q from"./matchesMetadataFilter-BbIOyA5g.mjs";const F=()=>{const d=new Map,m=(s="")=>{let t=d.get(s);return t||(t={documents:new Map,postings:new Map,totalLength:0},d.set(s,t)),t},l=(s,t)=>{const e=m(s),n=e.documents.get(t);if(n){for(const r of n.termFrequency.keys()){const c=e.postings.get(r);c&&(c.delete(t),c.size===0&&e.postings.delete(r))}e.totalLength-=n.length,e.documents.delete(t)}};return{index:(s,t)=>{const e=m(t.namespace);for(const n of s){l(t.namespace,n.id);const r=M(n.text);if(r.length===0)continue;const c=new Map;for(const a of r)c.set(a,(c.get(a)??0)+1);for(const[a,u]of c){let o=e.postings.get(a);o||(o=new Map,e.postings.set(a,o)),o.set(n.id,u)}e.documents.set(n.id,{length:r.length,termFrequency:c,text:n.text,...n.metadata===void 0?{}:{metadata:n.metadata}}),e.totalLength+=r.length}return Promise.resolve()},remove:(s,t)=>{for(const e of s)l(t.namespace,e);return Promise.resolve()},search:(s,t)=>{const e=m(t.namespace),n=e.documents.size;if(n===0)return Promise.resolve([]);const r=x(s);if(r.length===0)return Promise.resolve([]);const c=e.totalLength/n,a=new Map;for(const o of r){const i=e.postings.get(o);if(!i)continue;const p=i.size,h=k(n,p);for(const[f,v]of i){const g=e.documents.get(f);g&&q(g.metadata,t.filter)&&a.set(f,(a.get(f)??0)+y(h,v,g.length,c))}}const u=[...a.entries()].map(([o,i])=>({id:o,score:i,text:e.documents.get(o)?.text??""}));return Promise.resolve(u.toSorted((o,i)=>i.score-o.score).slice(0,t.topK))}}};export{F as default};
@@ -0,0 +1 @@
1
+ const h=8,w=async(t,n,l)=>{if(!Number.isInteger(n)||n<1)throw new RangeError("concurrentMap: `limit` must be a positive integer");if(t.length===0)return[];const s=Math.max(1,Math.min(n,t.length)),e=Array.from({length:t.length});let c=0,a=!1,i;const u=async()=>{for(;;){if(a)return;const o=c;if(c+=1,o>=t.length)return;try{e[o]=await l(t[o],o)}catch(r){a||(a=!0,i=r);return}}},f=Array.from({length:s},()=>u());if(await Promise.all(f),a)throw i;return e},y=async(t,n,l)=>{if(!Number.isInteger(n)||n<1)throw new RangeError("concurrentForEach: `limit` must be a positive integer");const s=Symbol.asyncIterator in t?t[Symbol.asyncIterator]():t[Symbol.iterator]();let e=!1,c;const a=r=>{e||(e=!0,c=r)};let i=Promise.resolve();const u=()=>{const r=i.then(async()=>await s.next());return i=r.catch(()=>{}),r},f=async()=>{if(e)return;const r=await u();return r.done===!0||e?void 0:{item:r.value}},o=async()=>{for(;;)try{const r=await f();if(r===void 0)return;await l(r.item)}catch(r){a(r);return}};if(await Promise.all(Array.from({length:n},async()=>{await o()})),e){try{await s.return?.()}catch{}throw c}};export{h as I,y as a,w as c};
@@ -0,0 +1 @@
1
+ const o=/^\.+/u,p={avif:"image/avif",bmp:"image/bmp",gif:"image/gif",ico:"image/x-icon",jpeg:"image/jpeg",jpg:"image/jpeg",png:"image/png",svg:"image/svg+xml",tiff:"image/tiff",tif:"image/tiff",webp:"image/webp",avi:"video/x-msvideo",mkv:"video/x-matroska",mov:"video/quicktime",mp4:"video/mp4",mpeg:"video/mpeg",mpg:"video/mpeg",webm:"video/webm",wmv:"video/x-ms-wmv",aac:"audio/aac",flac:"audio/flac",m4a:"audio/mp4",mp3:"audio/mpeg",ogg:"audio/ogg",opus:"audio/opus",wav:"audio/wav",wma:"audio/x-ms-wma",csv:"text/csv",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",odp:"application/vnd.oasis.opendocument.presentation",ods:"application/vnd.oasis.opendocument.spreadsheet",odt:"application/vnd.oasis.opendocument.text",pdf:"application/pdf",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",rtf:"application/rtf",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",css:"text/css",html:"text/html",htm:"text/html",ini:"text/plain",json:"application/json",js:"text/javascript",mjs:"text/javascript",md:"text/markdown",jsx:"text/javascript",ts:"text/typescript",tsx:"text/typescript",txt:"text/plain",xml:"application/xml",yaml:"application/x-yaml",yml:"application/x-yaml","7z":"application/x-7z-compressed",bz2:"application/x-bzip2",gz:"application/gzip",jar:"application/java-archive",rar:"application/vnd.rar",tar:"application/x-tar",zip:"application/zip",otf:"font/otf",ttf:"font/ttf",woff:"font/woff",woff2:"font/woff2",bin:"application/octet-stream",epub:"application/epub+zip",exe:"application/vnd.microsoft.portable-executable",iso:"application/x-iso9660-image",sql:"application/sql",toml:"application/toml"},e=t=>{const a=t.replace(o,"").toLowerCase();return p[a]??"application/octet-stream"},n=async t=>{const a=await crypto.subtle.digest("SHA-256",t);return[...new Uint8Array(a)].map(i=>i.toString(16).padStart(2,"0")).join("")};export{n as contentHash,e as guessMimeTypeFromExtension};
@@ -0,0 +1 @@
1
+ import{createOpenAI as W}from"@ai-sdk/openai";import{LunoraError as f}from"@lunora/errors";import{createWorkersAI as $}from"workers-ai-provider";import{anthropic as B}from"workers-ai-provider/anthropic";import{openai as V}from"workers-ai-provider/openai";import{buildAiGatewayMetadataFields as U,readEnv as p,AI_DEFAULT_MODEL_ENV as N,AI_DEFAULT_EMBEDDING_MODEL_ENV as L,AI_PROXY_URL_ENV as _,AI_PROXY_TOKEN_ENV as R,readAiGatewayEnvTags as F,AI_GATEWAY_ID_ENV as j,warnIgnoredBindingToken as H}from"./AI_DEFAULT_EMBEDDING_MODEL_ENV-IwcHZiKj.mjs";import{wrapLanguageModel as Y}from"ai";import{m as q,r as D}from"./usage-GPnTSqR8.mjs";import"./DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs";const K={setAttribute:()=>{},setAttributes:()=>{}},P=async(e,t)=>t(P,K),X=(e,{metrics:t,trace:n=P})=>{const o={"gen_ai.operation.name":"chat","gen_ai.request.model":e};return{wrapGenerate:async({doGenerate:a})=>n("ai.generate",async(E,m)=>{const A=await a();return D(e,A,m,t),A},o),wrapStream:async({doStream:a})=>{const E=(h,d)=>{const c=h.stream.getReader();let u={};return{...h,stream:new ReadableStream({cancel:async w=>{d({outcome:u}),await c.cancel(w)},pull:async w=>{let v;try{v=await c.read()}catch(y){d({error:y,failed:!0,outcome:u}),w.error(y);return}if(v.done){d({outcome:u}),w.close();return}v.value.type==="finish"&&(u={providerMetadata:v.value.providerMetadata,usage:v.value.usage}),w.enqueue(v.value)}})}},m=Promise.withResolvers(),A=Promise.withResolvers();return n("ai.stream",async(h,d)=>{m.resolve(E(await a(),A.resolve));const c=await A.promise;if(D(e,c.outcome,d,t),c.failed===!0)throw c.error},o).catch(m.reject),m.promise}}},O=(e,t,n)=>{let o=e;return t!==void 0&&typeof e!="string"&&(o=Y({middleware:X(n??q(e)??"unknown",t),model:e})),o},Z=[V,B],S=e=>!e.startsWith("@")&&e.includes("/"),b=e=>{throw new f("INTERNAL",`@lunora/ai: ${e} needs the \`AI\` binding (env.AI). Add an \`ai\` binding to wrangler.jsonc, or set ${_} to an OpenAI-compatible proxy for "<provider>/<model>" slugs.`)},x=()=>b("this model id");x.textEmbeddingModel=()=>b("this embedding model id");const J=3040,Q=/^(?:[A-Z]\w*:\s*)?3040\s*:/iu,z=e=>e?.code===J||e instanceof Error&&Q.test(e.message),ee="default",te=new Map([[401,"UNAUTHORIZED"],[403,"FORBIDDEN"],[404,"NOT_FOUND"],[408,"SERVICE_UNAVAILABLE"],[429,"RATE_LIMITED"]]),re=e=>te.get(e)??(e>=400&&e<500?"BAD_REQUEST":"SERVICE_UNAVAILABLE"),ae=300,oe=new Set(["127.0.0.1","[::1]","localhost"]),ne=e=>{const t=p(e,_);if(t===void 0)return;const n=p(e,R),o=URL.canParse(t)?new URL(t):void 0;let a;if(o===void 0?a=`${_} is not a valid URL`:n!==void 0&&o.protocol!=="https:"&&!oe.has(o.hostname)&&(a=`${_} (${o.origin}) is not HTTPS, so ${R} would travel in cleartext — use an https:// URL`),a!==void 0){const E=()=>{throw new f("INTERNAL",`@lunora/ai: ${a}`)};return{chat:E,embedding:E}}return W({apiKey:n??"",baseURL:t,name:"lunora-proxy"})},ie=(e,t)=>{const n=e===void 0?void 0:F(e);return n===void 0?t:{...t,tags:{...n,...t?.tags}}},se=(e,t,n)=>{const o=U(n);if(e!==void 0)return o!==void 0&&e.metadata===void 0?{...e,metadata:o}:e;if(t===void 0)return;const a=p(t,j);if(a!==void 0)return H(t),o===void 0?{id:a}:{id:a,metadata:o}},ye=e=>{const{binding:t,defaultEmbeddingModel:n,defaultModel:o,env:a,gateway:E,metadata:m,provider:A,telemetry:h}=e,d=ne(a),c=ie(a,m),u=se(E,a,c),w=E?.metadata===void 0?U(c):void 0,v=o??p(a,N),y=n??p(a,L),g=A??(t?$({binding:t,gateway:u,providers:Z}):x),C=(r,i)=>S(r)?d!==void 0?d.chat(r):w===void 0?g(r):g(r,{metadata:w}):i?.rejectIfBusy===void 0?g(r):g(r,{rejectIfBusy:i.rejectIfBusy}),k=(r,i)=>{const s=r??v;if(s===void 0||s==="")throw new f("INTERNAL",`@lunora/ai: no model supplied and no default configured — pass a model id, or set ${N} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return typeof s=="string"?O(C(s,i),h,s):O(s,h)},G=r=>{if(d!==void 0&&S(r))return d.embedding(r);const i=g.textEmbeddingModel;if(typeof i!="function")throw new f("INTERNAL","@lunora/ai: the Workers AI provider does not expose `textEmbeddingModel`; pass an AI SDK EmbeddingModel (e.g. from @ai-sdk/openai) to embed()");return i.call(g,r)};return{embeddingModel:r=>{if(typeof r=="object")return r;const i=r??y;if(!i)throw new f("INTERNAL",`@lunora/ai: no embedding model supplied and no default configured — pass an embedding model id or an AI SDK EmbeddingModel, or set ${L} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return G(i)},model:k,run:async(r,i,s)=>{if(!t)return b("ai.run");const I=u!==void 0&&s?.gateway===void 0?{...s,gateway:u}:s;try{return await t.run(r,i,I)}catch(l){throw z(l)?new f("RATE_LIMITED",`@lunora/ai: Workers AI has no free capacity for ${r} (error 3040) — retry later`,{cause:l}):l}},websearch:async(r,i)=>{if(!t)return b("ai.websearch");if(typeof t.websearch!="function")throw new f("NOT_IMPLEMENTED","@lunora/ai: this Workers runtime's `AI` binding has no websearch() — update wrangler / @cloudflare/vite-plugin to a release that ships the Web Search API");const{gatewayId:s,...I}=i??{},l=await t.websearch({...I,gatewayId:s??u?.id??ee,query:r});if(!l.ok){const M=(await l.text().catch(()=>"")).slice(0,ae);throw new f(re(l.status),`@lunora/ai: web search failed with HTTP ${String(l.status)}${M===""?"":`: ${M}`}`)}const T=await l.json().catch(()=>{});if(!Array.isArray(T?.items))throw new f("INTERNAL","@lunora/ai: web search returned a body that is not JSON with an `items` array");return T},workersai:g}};export{ye as default};
@@ -0,0 +1,8 @@
1
+ import{LunoraError as f,isLunoraError as Me}from"@lunora/errors";import{tool as Ae,jsonSchema as Ne,embedMany as De,embed as Re}from"ai";import{s as Ce}from"./stable-key-B_BlboiY.mjs";import{r as ie,m as Q}from"./usage-GPnTSqR8.mjs";import Be from"./fixedWindowChunks-C461ahRE.mjs";import{c as $e,I as Oe}from"./concurrent-C6nqBv41.mjs";import{contentHash as Ke}from"./contentHash-BIn6ECP8.mjs";import{hybridRank as Ue}from"./hybridRank-B4skyCLx.mjs";import{VECTORIZE_CAPABILITIES as de,vectorizeStore as Qe}from"./VECTORIZE_CAPABILITIES-CUQDoxis.mjs";const Ve=1e3,Fe=200,Le=5,ze=4,ce=de.maxMetadataBytes===!1?Number.POSITIVE_INFINITY:de.maxMetadataBytes,Pe=2*1024,he="__ragChunk",pe="__ragSource",B="__ragText",j="__ragHash",Y="__ragChunks",O="__ragImportance",ge="__ragModel",je=new Set([he,Y,j,O,ge,pe,B]),Ye=(e,l,h,w)=>{if(w===!1)return;const v=new TextEncoder().encode(JSON.stringify(e)).length;if(v<=w)return;const K=(typeof e[B]=="string"?new TextEncoder().encode(e[B]).length:0)*2>v?"lower `chunkSize` (it counts characters, not bytes — multibyte text costs up to 3 bytes each), or supply `textStore` to move chunk text out of metadata entirely":"attach less per-source `metadata`";throw new f("BAD_REQUEST",`@lunora/ai/rag: chunk ${String(l)} of "${h}" carries ${String(v)} bytes of metadata, over the store's ${String(w)}-byte per-vector ceiling — ${K}`)},He=(e,l,h)=>{if(h===!1)return;const w=new TextEncoder().encode(e).length;if(!(w<=h))throw new f("BAD_REQUEST",`@lunora/ai/rag: chunk id "${e}" for source "${l}" is ${String(w)} bytes, over the store's ${String(h)}-byte per-vector id ceiling — shorten the source id (hash long keys before indexing them) or shorten the \`namespace\`, which is prefixed onto every chunk id`)},qe=/^[\w.-]{1,40}$/,fe=e=>e===void 0?"":`${encodeURIComponent(e)}#`,C=(e,l,h)=>`${fe(e)}${l}#${String(h)}`,ue=(e,l)=>{const h=fe(l),w=h!==""&&e.startsWith(h)?e.slice(h.length):e,v=w.lastIndexOf("#"),M=v===-1?Number.NaN:Number(w.slice(v+1));return v===-1||!Number.isInteger(M)||M<0?{chunkIndex:0,sourceId:w}:{chunkIndex:M,sourceId:w.slice(0,v)}},We=async e=>Ke(new TextEncoder().encode(e)),Ze=e=>{try{return Ce([e.text,e.metadata,e.importance])}catch{return}},Xe=(e,l)=>{const h=[],w=[];for(const v of e)v.score>=l?h.push(v):w.push(v.id);return{kept:h,rejectedIds:w}},Je=e=>e!==void 0&&Object.keys(e).length>0,P=e=>{if(!e)return;const l=Object.entries(e).filter(([h])=>!je.has(h));return l.length>0?Object.fromEntries(l):void 0},le=new Set,Ge=e=>{le.has(e)||(le.add(e),console.warn(`[@lunora/ai/rag] index "${e}" is used without a namespace — in a multi-tenant/sharded
2
+ app this shares one tenant's chunks (text included) with every other tenant, since
3
+ Vectorize indexes are account-global. Pass \`namespace\` (the shard/tenant key) on both
4
+ index() and retrieve(). Single-tenant apps suppress this via { allowSharedNamespace: true }.`))},et=e=>e.map(l=>`[source:${l.sourceId}#${String(l.chunkIndex)}]
5
+ ${l.text}`).join(`
6
+
7
+ `),me=(e,l)=>{if(typeof e=="object")return e;if(l===void 0)throw new f("INTERNAL","@lunora/ai/rag: `embeddingModel` is a Workers AI model id (or omitted) but the bound context has no `ai` (env.AI). Pass an AI SDK EmbeddingModel object to embed without Workers AI, or bind a context whose `ctx.ai` is wired.");return l.embeddingModel(e)},tt=e=>{if(e===void 0)throw new f("INTERNAL","@lunora/ai/rag: the bound context has no `vectors` (env.VECTORIZE) and no `store` is configured — bind a context whose `ctx.vectors` is wired, or configure `store` (e.g. `sqliteVectorStore`) to back this index without Vectorize.");return e},mt=e=>{if(typeof e.index!="string"||e.index.length===0)throw new f("BAD_REQUEST","@lunora/ai/rag: `index` must be a non-empty Vectorize index name");const l=e.chunkSize??Ve,h=e.chunkOverlap??Fe;if(!Number.isInteger(l)||l<1)throw new f("BAD_REQUEST","@lunora/ai/rag: `chunkSize` must be a positive integer");if(!Number.isInteger(h)||h<0||h>=l)throw new f("BAD_REQUEST","@lunora/ai/rag: `chunkOverlap` must be a non-negative integer smaller than `chunkSize`");const w=ce-Pe;if(!e.chunk&&!e.textStore&&!e.store&&l>w)throw new f("BAD_REQUEST",`@lunora/ai/rag: \`chunkSize\` of ${String(l)} leaves no room under Vectorize's ${String(ce)}-byte metadata limit, which also carries this chunk's bookkeeping and any \`metadata\` you attach — keep it under ${String(w)}, or supply \`textStore\` to move chunk text out of metadata entirely`);const v=e.topK??Le;if(!Number.isInteger(v)||v<1)throw new f("BAD_REQUEST","@lunora/ai/rag: `topK` must be a positive integer");if(e.maxEmbeddingDimensions!==void 0&&e.maxEmbeddingDimensions!==!1&&(!Number.isInteger(e.maxEmbeddingDimensions)||e.maxEmbeddingDimensions<1))throw new f("BAD_REQUEST","@lunora/ai/rag: `maxEmbeddingDimensions` must be a positive integer, or `false` to disable the check");if(e.embeddingModelVersion!==void 0&&!qe.test(e.embeddingModelVersion))throw new f("BAD_REQUEST",'@lunora/ai/rag: `embeddingModelVersion` must match /^[A-Za-z0-9._-]{1,40}$/ (a short, stable tag like "bge-v1.5")');if(e.candidates!==void 0&&(!Number.isInteger(e.candidates)||e.candidates<1))throw new f("BAD_REQUEST","@lunora/ai/rag: `candidates` must be a positive integer");if(e.cacheEmbeddings!==void 0&&(!Number.isInteger(e.cacheEmbeddings)||e.cacheEmbeddings<0))throw new f("BAD_REQUEST","@lunora/ai/rag: `cacheEmbeddings` must be a non-negative integer");const M=e.cacheEmbeddings??0,K=e.rerank,we=e.chunk??(p=>Be(p,l,h)),{textStore:k}=e,$=e.embeddingModelVersion,V=p=>$===void 0?p:p===void 0?$:`${$}::${p}`;return p=>{const E=e.store?e.store(p):Qe(tt(p.vectors),e.index),H=k?E.capabilities.maxTopK:E.capabilities.maxTopKWithMetadata,U=e.maxEmbeddingDimensions??E.capabilities.maxDimensions;let N;const q=typeof p.trace=="function"?p.trace:void 0,W=typeof p.metrics?.count=="function"?p.metrics:void 0;let Z=U===!1;const X=(t,r)=>{if(Z||(Z=!0,U===!1||t<=U))return;const n=Q(r);throw new f("BAD_REQUEST",`@lunora/ai/rag: embedding model${n===void 0?"":` "${n}"`} produces ${String(t)}-dimension vectors, over the ${String(U)}-dimension ceiling of index "${e.index}" — either truncate them with the provider's \`dimensions\` option (Matryoshka models such as text-embedding-3-large support this), or set \`maxEmbeddingDimensions: false\` if this index is not Vectorize-backed`)},D=new Map,be=(t,r)=>{if(M!==0)for(D.set(t,r);D.size>M;){const n=D.keys().next();if(n.done===!0)break;D.delete(n.value)}},J=async t=>{const r=D.get(t);if(r!==void 0)return r;N??=me(e.embeddingModel,p.ai);const n=N,o=async a=>{const{embedding:c,providerMetadata:b,usage:d}=await Re({model:n,value:t});return X(c.length,n),ie(Q(n),{providerMetadata:b,usage:{inputTokens:{total:d.tokens}}},a,W),be(t,c),c};if(q===void 0)return o();const s=Q(n),i=typeof p.conversationId=="string"&&p.conversationId.length>0?p.conversationId:void 0;return q("ai.embed",(a,c)=>o(c),{"gen_ai.operation.name":"embeddings",...s===void 0?{}:{"gen_ai.request.model":s},...i===void 0?{}:{"gen_ai.conversation.id":i}})},ve=async(t,r,n)=>{if(!e.transformQuery||r?.transformQuery===!1)return[t];const o=typeof p.conversationId=="string"&&p.conversationId.length>0?p.conversationId:void 0,s=await e.transformQuery(t,{conversationId:o,namespace:n}),i=(typeof s=="string"?[s]:[...s]).map(a=>a.trim()).filter(a=>a.length>0);return i.length>0?i:[t]},ye=async t=>{const r=new Map,n=[...new Set(t.filter(o=>!D.has(o)))];if(n.length<2)return r;N??=me(e.embeddingModel,p.ai);try{const{embeddings:o,providerMetadata:s,usage:i}=await De({model:N,values:n});if(ie(Q(N),{providerMetadata:s,usage:{inputTokens:{total:i.tokens}}},void 0,W),o.length!==n.length)return r;const[a]=o;a!==void 0&&X(a.length,N);for(const[c,b]of n.entries())r.set(b,o[c])}catch(o){if(Me(o))throw o}return r},F=t=>{if(t===void 0){if(e.requireNamespace)throw new f("BAD_REQUEST",`@lunora/ai/rag: index "${e.index}" requires a namespace (requireNamespace is set) — pass the tenant/shard key on index()/retrieve()/remove()`);e.allowSharedNamespace||Ge(e.index)}},G=async(t,r)=>{const[n]=await E.getByIds([C(r,t,0)],r),o=n?.metadata?.[j],s=n?.metadata?.[Y];return{chunks:typeof s=="number"&&Number.isInteger(s)&&s>0?s:void 0,hash:typeof o=="string"?o:void 0}},ee=async(t,r,n,o)=>{const s=Array.from({length:n-r},(i,a)=>C(o,t,r+a));s.length!==0&&(await E.deleteByIds(s,o),await k?.remove?.(s,{namespace:o}),await e.lexicalStore?.remove?.(s,{namespace:o}))},Ee=async t=>{if(F(t.namespace),t.importance!==void 0&&(typeof t.importance!="number"||t.importance<0||t.importance>1))throw new f("BAD_REQUEST","@lunora/ai/rag: `importance` must be a number in [0, 1]");const r=V(t.namespace),n=Ze(t),o=await We(n??t.text),s=await G(t.id,r);if(t.reindex!==!0&&n!==void 0&&s.hash===o&&s.chunks!==void 0)return{chunks:s.chunks,ids:Array.from({length:s.chunks},(m,u)=>C(r,t.id,u)),unchanged:!0};const i=we(t.text),a=i.map((m,u)=>C(r,t.id,u)),c=a.at(-1);if(c!==void 0&&He(c,t.id,E.capabilities.maxIdBytes),i.length===0&&t.allowEmptySources===!1)throw new f("BAD_REQUEST",`@lunora/ai/rag: source "${t.id}" produced zero chunks — set allowEmptySources: true to allow this`);if(i.length>0){const m=i.map((u,y)=>({chunkIndex:y,id:a[y],sourceId:t.id,text:u,...t.metadata===void 0?{}:{metadata:t.metadata}}));k&&await k.put(m,{namespace:r}),e.lexicalStore&&await e.lexicalStore.index(m,{namespace:r})}const b=await ye(i),d=async m=>b.get(m)??await J(m);return await $e(i,Oe,async(m,u)=>{const y=a[u],x={...t.metadata,[he]:u,[pe]:t.id};k||(x[B]=m),t.importance!==void 0&&(x[O]=t.importance),u===0&&(x[j]=o,x[Y]=i.length,$!==void 0&&(x[ge]=$)),Ye(x,u,t.id,E.capabilities.maxMetadataBytes),await E.upsert({embed:d,id:y,input:m,metadata:x,namespace:r}),t.onChunk?.({chunkIndex:u,id:y,text:m,total:i.length})}),s.chunks!==void 0&&s.chunks>i.length&&await ee(t.id,i.length,s.chunks,r),{chunks:i.length,ids:a,unchanged:!1}},xe=async t=>{F(t.namespace);const r=V(t.namespace),o=(await G(t.id,r)).chunks??1;await ee(t.id,0,o,r)},te=async(t,r)=>{const n=new Map;if(t.length===0)return n;if(k){const s=await k.getMany(t,{namespace:r});for(const[i,a]of t.entries()){const c=s[i];typeof c=="string"&&n.set(a,c)}return n}const o=await E.getByIds(t,r);for(const s of o){const i=s.metadata?.[B];typeof i=="string"&&n.set(s.id,i)}return n},Ie=async(t,r,n)=>{const o=r?.chunkContext?.before??0,s=r?.chunkContext?.after??0;if(o===0&&s===0)return t;if(!Number.isInteger(o)||o<0||!Number.isInteger(s)||s<0)throw new f("BAD_REQUEST","@lunora/ai/rag: `chunkContext.before`/`chunkContext.after` must be non-negative integers");const i=new Map(t.map(d=>[d.id,d.text])),a=new Set;for(const d of t)for(let m=-o;m<=s;m+=1){const u=d.chunkIndex+m,y=C(n,d.sourceId,u);m!==0&&u>=0&&!i.has(y)&&a.add(y)}const c=await te([...a],n),b=(d,m)=>{const u=C(n,d,m);return i.get(u)??c.get(u)};return t.map(d=>{const m=[];for(let u=-o;u<=s;u+=1){const y=u===0?d.text:b(d.sourceId,d.chunkIndex+u);y!==void 0&&m.push(y)}return{...d,text:m.join(`
8
+ `)}})},Se=t=>{if(typeof t=="string"){const r=e.filters!==void 0&&Object.hasOwn(e.filters,t)?e.filters[t]:void 0;if(!r)throw new f("NOT_FOUND",`@lunora/ai/rag: unknown named filter "${t}" — must be one of the keys declared in RagConfig.filters`);return r.filter}return t},ke=(t,r)=>t.matches.map(n=>{const o=n.metadata??{},s=ue(n.id,r),i=o[B],a=o[O],c=typeof a=="number"&&a>=0&&a<=1?a:1;return{chunkIndex:s.chunkIndex,id:n.id,importance:c,metadata:P(o),score:n.score*c,sourceId:s.sourceId,text:typeof i=="string"?i:""}}),_e=async(t,r)=>{if(!k)return t;const n=t.map(a=>a.id),[o,s]=await Promise.all([te(n,r),E.getByIds(n,r)]),i=new Map(s.map(a=>[a.id,a.metadata]));return t.flatMap(a=>{const c=o.get(a.id);if(c===void 0)return[];const b=i.get(a.id),d=b?.[O],m=typeof d=="number"&&d>=0&&d<=1?d:a.importance,y=(a.importance===0?0:a.score/a.importance)*m;return[{...a,importance:m,metadata:P(b)??a.metadata,score:y,text:c}]})},re=async(t,r,n)=>{const o=t.map(a=>a.id).filter(a=>!r.has(a)),s=o.length===0?[]:await E.getByIds(o,n),i=new Map(s.map(a=>[a.id,a.metadata]));return t.map(a=>{const c=ue(a.id,n),b=i.get(a.id),d=b?.[O];return{chunkIndex:c.chunkIndex,id:a.id,importance:typeof d=="number"&&d>=0&&d<=1?d:1,metadata:P(b),score:a.score,sourceId:c.sourceId,text:a.text}})},ne=async(t,r)=>{F(r?.namespace);const n=V(r?.namespace),o=Se(r?.filter),s=e.rlsFilter?await e.rlsFilter(p.auth):void 0,i=s?{...o,...s}:o,a=Math.min(r?.topK??v,H),c=await ve(t,r,n),b=c[0],d=K!==void 0&&r?.rerank!==!1,u=d||e.lexicalStore!==void 0||e.graphStore!==void 0||c.length>1?Math.min(e.candidates??a*ze,H):a,y=r?.minScore,x=new Set,ae=async g=>{const _=await E.query({embed:J,filter:i,input:g,namespace:n,returnMetadata:k?"indexed":"all",topK:u}),A=await _e(ke(_,n),n);if(y===void 0)return A;const{kept:S,rejectedIds:T}=Xe(A,y);for(const Te of T)x.add(Te);return S},R=[{chunks:await ae(b)}];for(const g of c.slice(1))R.push({chunks:await ae(g)});if(e.lexicalStore){const g=await e.lexicalStore.search(b,{filter:i,namespace:n,topK:e.lexicalTopK??u}),_=new Set(R.flatMap(S=>S.chunks).map(S=>S.id)),A=g.filter(S=>!x.has(S.id)||_.has(S.id));R.push({chunks:await re(A,_,n)})}const L=R.flatMap(g=>[...g.chunks]);if(e.graphStore&&L.length>0&&(e.graphStore.enforcesFilter||!Je(i))){const g=[...new Set(L.map(T=>T.sourceId))],_=await e.graphStore.related(g,{filter:i,namespace:n,topK:e.graphTopK??u}),A=new Set(L.map(T=>T.id)),S=_.filter(T=>!x.has(T.id)||A.has(T.id));R.push({chunks:await re(S,A,n),weight:"proximity"})}const z=R.filter(g=>g.chunks.length>0);let I=z.length>1?[...Ue(z)]:[...z[0]?.chunks??[]];I.sort((g,_)=>_.score-g.score),d&&(I=[...await K(b,I)]),I=I.slice(0,a),I=[...await Ie(I,r,n)];const se=[],oe=new Set;for(const g of I)oe.has(g.sourceId)||(oe.add(g.sourceId),se.push({id:g.sourceId,metadata:g.metadata,weight:g.importance}));return r?.onRetrieve?.({matches:I.length,query:t}),{chunks:I,context:et(I),sources:se}};return{asTool:t=>Ae({description:t?.description??`Search the "${e.index}" knowledge base for passages relevant to a natural-language query.`,execute:async({query:r})=>ne(r,{namespace:t?.namespace,topK:t?.topK}),inputSchema:Ne({properties:{query:{description:"The natural-language search query.",type:"string"}},required:["query"],type:"object"})}),index:Ee,remove:xe,retrieve:ne}}};export{mt as default};
@@ -0,0 +1 @@
1
+ import{LunoraError as m}from"@lunora/errors";import{a as h,c as f}from"./concurrent-C6nqBv41.mjs";import{guessMimeTypeFromExtension as w}from"./contentHash-BIn6ECP8.mjs";const l=4,i=(a,e)=>a.localeCompare(e),k=a=>(a.contentType??w(a.key.slice(a.key.lastIndexOf(".")))).split(";")[0]?.trim()??"",g=(a,e)=>{if(!e)return;const s=k(a);return Object.hasOwn(e,s)?e[s]:Object.hasOwn(e,"*")?e["*"]:void 0},x=new Set(["application/json","text/csv","text/markdown","text/plain"]),T=(a,e={})=>{const s=e.concurrency??l;if(!Number.isInteger(s)||s<1)throw new m("BAD_REQUEST","@lunora/ai/rag: `concurrency` must be a positive integer");return{sync:async(u,p={})=>{const t={indexed:[],pruned:[],skipped:[],unchanged:[]},o=new Set;if(await h(u.list(),s,async n=>{o.add(n.key);const r=await u.get(n);if(r===void 0){t.skipped.push(n.key),e.onObject?.({chunks:0,key:n.key,status:"skipped"});return}const y=g(n,e.extractors);let d;if(y?d=await y(r,n):x.has(k(n))&&(d=r),d===void 0||d.trim().length===0){t.skipped.push(n.key),e.onObject?.({chunks:0,key:n.key,status:"skipped"});return}const c=await a.index({id:n.key,text:d,...e.namespace===void 0?{}:{namespace:e.namespace},...n.metadata===void 0?{}:{metadata:n.metadata}});c.unchanged?t.unchanged.push(n.key):t.indexed.push(n.key),e.onObject?.({chunks:c.chunks,key:n.key,status:c.unchanged?"unchanged":"indexed"})}),p.knownKeys!==void 0){const n=[...p.knownKeys].filter(r=>!o.has(r));await f(n,s,async r=>{await a.remove({id:r,...e.namespace===void 0?{}:{namespace:e.namespace}}),t.pruned.push(r)})}return{indexed:t.indexed.toSorted(i),pruned:t.pruned.toSorted(i),skipped:t.skipped.toSorted(i),unchanged:t.unchanged.toSorted(i)}}}};export{T as defineRagSource};
@@ -0,0 +1 @@
1
+ const o=(s,e,r)=>{if(!Number.isInteger(e)||e<1)throw new RangeError("fixedWindowChunks: `size` must be a positive integer");if(!Number.isInteger(r)||r<0||r>=e)throw new RangeError("fixedWindowChunks: `overlap` must be a non-negative integer smaller than `size`");const t=s.trim();if(t.length===0)return[];if(t.length<=e)return[t];const h=Math.max(1,e-r),i=[];for(let n=0;n<t.length&&(i.push(t.slice(n,n+e)),!(n+e>=t.length));n+=h);return i};export{o as default};
@@ -0,0 +1 @@
1
+ const d=(a,k={})=>{const m=k.k??60,t=new Map;for(const[r,e]of a.entries()){const o=e.weight==="proximity";for(const[s,n]of e.chunks.entries()){const c=(o?Math.min(Math.max(n.score,0),1):1)/(m+s),i=t.get(n.id);i?i.score+=c:t.set(n.id,{chunk:n,primaryRank:r===0?s:Number.POSITIVE_INFINITY,score:c})}}return[...t.values()].map(r=>({...r,scored:{...r.chunk,score:r.score*r.chunk.importance}})).toSorted((r,e)=>{const o=e.scored.score-r.scored.score;return o!==0?o:r.primaryRank===e.primaryRank?0:r.primaryRank<e.primaryRank?-1:1}).map(r=>r.scored)};export{d as hybridRank};
@@ -0,0 +1,5 @@
1
+ import f from"./fixedWindowChunks-C461ahRE.mjs";const E=1e3,T=200,w=256,b=/(?<=[!.?])\s/u,z=/^(#{1,6})\s(.*)$/u,C=/^ {0,3}(?:`{3,}|~{3,})/u,k=(o,r)=>{const e=o?.size??E,t=o?.overlap??T;if(!Number.isInteger(e)||e<1)throw new RangeError(`${r}: \`size\` must be a positive integer`);if(!Number.isInteger(t)||t<0||t>=e)throw new RangeError(`${r}: \`overlap\` must be a non-negative integer smaller than \`size\``);return{overlap:t,size:e}},d=o=>o.split(b).map(r=>r.trim()).filter(r=>r.length>0),x=(o,r)=>o.length===0?0:o.reduce((e,t)=>e+t.length,0)+r.length*(o.length-1),N=(o,r)=>{if(r.overlap===0)return[];const e=[];for(let t=o.length-1;t>=0&&e.length<o.length-1&&(e.unshift(o[t]),!(r.measure(e)>=r.overlap));t-=1);return e},v=(o,r)=>{const{budget:e,measure:t,separator:u,splitOversized:s}=r,c=[];let n=[];const i=()=>{n.length>0&&c.push(n.join(u))};for(const l of o)if(l.trim().length!==0){if(t([l])>e){i(),n=[],c.push(...s(l));continue}if(n.length>0&&t([...n,l])>e)for(i(),n=N(n,r);n.length>0&&t([...n,l])>e;)n.shift();n.push(l)}return i(),c.filter(l=>l.trim().length>0)},A=o=>{const r=[];let e=[],t=[],u=[],s=!1;const c=()=>{t.some(n=>n.trim().length>0)&&r.push({body:t,trail:u}),t=[]};for(const n of o.split(`
2
+ `)){if(C.test(n)){s=!s,t.push(n);continue}const i=s?void 0:z.exec(n)??void 0;if(i===void 0){t.push(n);continue}c();const l=i[1].length,a=i[2].trim();e=[...e.filter(h=>h.indexOf(" ")<l),`${"#".repeat(l)} ${a}`],u=e}return c(),r},m=o=>{const{overlap:r,size:e}=k(o,"sentenceChunker");return t=>{const u=t.trim();return u.length===0?[]:v(d(u),{budget:e,measure:s=>x(s," "),overlap:r,separator:" ",splitOversized:s=>f(s,e,r)})}},M=o=>{const{overlap:r,size:e}=k(o,"markdownChunker"),t=m({overlap:r,size:e}),u=(s,c)=>{const n=e-c.length;return n<Math.ceil(e/4)?t(s):m({overlap:Math.min(r,n-1),size:n})(s).map(i=>`${c}${i}`)};return s=>{if(s.trim().length===0)return[];const c=[];for(const n of A(s)){const i=n.body.join(`
3
+ `).trim();i.length>0&&c.push(...u(i,n.trail.length>0?`${n.trail.join(" > ")}
4
+
5
+ `:""))}return c}},S=o=>{const{countTokens:r}=o,e=o.maxTokens??w,t=o.overlapTokens??0;if(typeof r!="function")throw new TypeError("tokenChunker: `countTokens` must be a function — pass your model's tokenizer (e.g. js-tiktoken)");if(!Number.isInteger(e)||e<1)throw new RangeError("tokenChunker: `maxTokens` must be a positive integer");if(!Number.isInteger(t)||t<0||t>=e)throw new RangeError("tokenChunker: `overlapTokens` must be a non-negative integer smaller than `maxTokens`");const u=s=>{const c=[],n=[s];for(;n.length>0;){const i=n.pop(),l=r(i);if(l<=e||i.length<=1){c.push(i);continue}const a=Math.floor(i.length*e/Math.max(1,l)),h=Math.min(i.length-1,Math.max(1,a)),p=f(i,h,0);for(let g=p.length-1;g>=0;g-=1)n.push(p[g])}return c};return s=>{const c=s.trim();return c.length===0?[]:v(d(c),{budget:e,measure:n=>r(n.join(" ")),overlap:t,separator:" ",splitOversized:u})}};export{M as markdownChunker,m as sentenceChunker,S as tokenChunker};
@@ -0,0 +1 @@
1
+ const o=new Set(["$eq","$gt","$gte","$in","$lt","$lte","$ne","$nin"]),u=(e,t)=>{if(Object.hasOwn(e,t))return e[t];let r=e;for(const n of t.split(".")){if(typeof r!="object"||r===null||!Object.hasOwn(r,n))return;r=r[n]}return r},f=(e,t)=>{if(typeof e=="number"&&typeof t=="number")return e-t;if(typeof e=="string"&&typeof t=="string")return e===t?0:e<t?-1:1},$=(e,t,r)=>{switch(e){case"$eq":return r===t;case"$in":return Array.isArray(t)&&t.includes(r);case"$ne":return r!==void 0&&r!==t;case"$nin":return r!==void 0&&Array.isArray(t)&&!t.includes(r);default:{const n=f(r,t);return n===void 0?!1:e==="$lt"?n<0:e==="$lte"?n<=0:e==="$gt"?n>0:n>=0}}},y=e=>typeof e=="object"&&e!==null&&!Array.isArray(e)&&Object.keys(e).some(t=>t.startsWith("$")),O=(e,t)=>{if(t===void 0||Object.keys(t).length===0)return!0;if(e===void 0)return!1;for(const[r,n]of Object.entries(t)){const s=u(e,r);if(!y(n)){if(s!==n)return!1;continue}for(const[i,c]of Object.entries(n))if(!o.has(i)||!$(i,c,s))return!1}return!0};export{O as default};
@@ -0,0 +1 @@
1
+ import{s as l}from"./stable-key-B_BlboiY.mjs";const v=c=>{const m=c.delayMs??0,y=(d,a)=>c.id===void 0?a:c.id(d),e=d=>d===void 0?void 0:c.text(d),u=d=>c.metadata?.(d),p=d=>c.namespace?.(d),o=(d,a)=>{const t=p(d);return{id:y(d,a),...t===void 0?{}:{namespace:t}}},f=(d,a,t)=>{const s=u(d);return{...o(d,a),...s===void 0?{}:{metadata:s},text:t}},g=(d,a)=>{try{return l(u(d))===l(u(a))}catch{return!1}},r=async(d,a)=>{await d.scheduler.runAfter(m,c.action,a)};return{afterDelete:async(d,a)=>{const t=a.previous??a.doc;await r(d,{deleted:!0,...t===void 0?{id:a.id}:o(t,a.id)})},afterInsert:async(d,a)=>{const t=e(a.doc);t===void 0||a.doc===void 0||await r(d,f(a.doc,a.id,t))},afterUpdate:async(d,a)=>{if(a.doc===void 0)return;const t=e(a.doc),s=e(a.previous),i=o(a.doc,a.id),n=a.previous===void 0?i:o(a.previous,a.id),w=n.id!==i.id||n.namespace!==i.namespace,h=c.metadata!==void 0&&a.previous!==void 0&&!g(a.doc,a.previous);if(w)await r(d,{deleted:!0,...n});else if(t===s&&!h)return;await(t===void 0?r(d,{deleted:!0,...i}):r(d,f(a.doc,a.id,t)))}}};export{v as ragSyncTriggers};
@@ -0,0 +1 @@
1
+ import{LunoraError as h}from"@lunora/errors";const l=t=>/^[A-Z_]\w*$/i.test(t),u=t=>Array.from({length:t}).fill("?").join(", "),c=64,f=(t,e=c)=>{const n=Math.max(1,Math.floor(e)),i=[];for(let r=0;r<t.length;r+=n)i.push(t.slice(r,r+n));return i},m=(t,e)=>{if(!l(t))throw new TypeError(`@lunora/ai/rag: ${e} must be a bare SQL identifier (letters, digits, underscore; not starting with a digit) — got "${t}"`);return t},p=(t,e)=>{if(t.length!==e.length)throw new h("RAG_DIMENSION_MISMATCH",`@lunora/ai/rag: the stored vectors are ${String(e.length)}-dimension but the query embedding is ${String(t.length)}-dimension — they were written by a different embedding model. Restore the previous \`embeddingModel\`, or reindex this namespace (bump \`embeddingModelVersion\`)`);if(t.length===0)return 0;let n=0,i=0,r=0;for(const[d,o]of t.entries()){const s=e[d];n+=o*s,i+=o*o,r+=s*s}const a=Math.sqrt(i)*Math.sqrt(r);return a===0?0:n/a},b=t=>{if(t!=null){if(typeof t=="string")try{return JSON.parse(t)}catch{return}return t}};export{c as I,m as a,p as c,f as i,u as p,b as r};
@@ -0,0 +1 @@
1
+ import{t as x,c as U,b,a as F}from"./bm25-9q0Avwi-.mjs";import M from"./matchesMetadataFilter-BbIOyA5g.mjs";import{a as q,i as p,p as L,r as C,I as D}from"./sql-D5aqEMCY.mjs";const X="lunora_rag_lexical",_=null,I=m=>m??"",S=m=>{const s=typeof m=="number"?m:Number(m);return Number.isFinite(s)?s:0},K=m=>{if(typeof m.exec!="function")throw new TypeError("@lunora/ai/rag: sqlLexicalStore requires an `exec` function");const s=q(m.table??X,"sqlLexicalStore `table`"),f=`${s}_terms`,{exec:r}=m;let w;const h=async()=>{w??=(async()=>{await r(`CREATE TABLE IF NOT EXISTS ${s} (id TEXT NOT NULL, namespace TEXT NOT NULL DEFAULT '', text TEXT NOT NULL, length INTEGER NOT NULL, metadata TEXT, PRIMARY KEY (namespace, id))`,[]),await r(`CREATE TABLE IF NOT EXISTS ${f} (term TEXT NOT NULL, id TEXT NOT NULL, namespace TEXT NOT NULL DEFAULT '', frequency INTEGER NOT NULL, PRIMARY KEY (namespace, term, id))`,[]),await r(`CREATE INDEX IF NOT EXISTS ${f}_lookup ON ${f} (namespace, term)`,[]),await r(`CREATE INDEX IF NOT EXISTS ${s}_namespace ON ${s} (namespace)`,[])})().catch(c=>{throw w=void 0,c}),await w},A=async(c,n)=>{for(const o of p(c)){const i=L(o.length);await r(`DELETE FROM ${f} WHERE namespace = ? AND id IN (${i})`,[n,...o]),await r(`DELETE FROM ${s} WHERE namespace = ? AND id IN (${i})`,[n,...o])}},R=async(c,n,o)=>{const i=`(${L(n)})`;for(const E of p(o,D/n))await r(`${c} VALUES ${Array.from({length:E.length}).fill(i).join(", ")}`,E.flatMap(e=>[...e]))};return{index:async(c,n)=>{await h();const o=I(n.namespace);await A(c.map(e=>e.id),o);const i=[],E=[];for(const e of c){const N=F(e.text);if(N.length===0)continue;const d=new Map;for(const T of N)d.set(T,(d.get(T)??0)+1);i.push([e.id,o,e.text,N.length,e.metadata===void 0?_:JSON.stringify(e.metadata)]);for(const[T,l]of d)E.push([T,e.id,o,l])}await R(`INSERT INTO ${f} (term, id, namespace, frequency)`,4,E),await R(`INSERT INTO ${s} (id, namespace, text, length, metadata)`,5,i)},remove:async(c,n)=>{await h(),await A(c,I(n.namespace))},search:async(c,n)=>{await h();const o=I(n.namespace),i=x(c);if(i.length===0)return[];const[E]=await r(`SELECT COUNT(*) AS document_count, COALESCE(SUM(length), 0) AS total_length FROM ${s} WHERE namespace = ?`,[o]),e=S(E?.document_count);if(e===0)return[];const N=S(E?.total_length)/e,d=[];for(const t of p(i)){const a=await r(`SELECT t.term AS term, t.id AS id, t.frequency AS frequency, d.length AS length, d.metadata AS metadata FROM ${f} t JOIN ${s} d ON d.id = t.id AND d.namespace = t.namespace WHERE t.namespace = ? AND t.term IN (${L(t.length)})`,[o,...t]);d.push(...a)}const T=new Map;for(const t of d){const a=String(t.term);T.set(a,(T.get(a)??0)+1)}const l=new Map;for(const t of d){const a=C(t.metadata);if(!M(a,n.filter))continue;const u=String(t.id),y=U(e,T.get(String(t.term))??1),$=b(y,S(t.frequency),S(t.length),N);l.set(u,(l.get(u)??0)+$)}const g=[...l.entries()].toSorted(([,t],[,a])=>a-t).slice(0,n.topK);if(g.length===0)return[];const O=new Map;for(const t of p(g.map(([a])=>a))){const a=await r(`SELECT id, text FROM ${s} WHERE namespace = ? AND id IN (${L(t.length)})`,[o,...t]);for(const u of a)O.set(String(u.id),String(u.text))}return g.map(([t,a])=>({id:t,score:a,text:O.get(t)??""}))}}};export{K as sqlLexicalStore};
@@ -0,0 +1 @@
1
+ import x from"./matchesMetadataFilter-BbIOyA5g.mjs";import{p as I,a as q,r as y,c as F,i as O}from"./sql-D5aqEMCY.mjs";import{LunoraError as _}from"@lunora/errors";const p=500,C=4096,R=(d,r)=>JSON.stringify([d,r]),D=d=>{const{dimensions:r,exec:o,table:m}=d,s=`${m}_ann`,E=`${m}_ann_ready`,u=(a,t)=>{if(a!==r)throw new _("RAG_DIMENSION_MISMATCH",`@lunora/ai/rag: ${t} is ${String(a)}-dimension but the \`ann\` index is ${String(r)}-dimension — set \`ann.dimensions\` to the embedding model's width, or reindex into a new \`table\` after changing models`)},g=async(a,t,i)=>{await o(`INSERT INTO ${s} (ref, namespace, embedding, id) VALUES (?, ?, ?, ?)`,[R(a,t),a,i,t])},S=async()=>{let a=0;for(;;){const t=await o(`SELECT rowid, id, namespace, vector FROM ${m} WHERE rowid > ? ORDER BY rowid LIMIT ?`,[a,p]);for(const i of t)u(JSON.parse(String(i.vector)).length,`stored vector "${String(i.id)}"`),await g(String(i.namespace),String(i.id),String(i.vector));if(t.length<p)return;a=Number(t.at(-1)?.rowid)}};return{ensure:async()=>{if((await o("SELECT name FROM sqlite_master WHERE name = ?",[E])).length>0){const[t]=await o(`SELECT dimensions FROM ${E}`,[]);u(Number(t?.dimensions),"the existing index");return}await o(`DROP TABLE IF EXISTS ${s}`,[]);try{await o(`CREATE VIRTUAL TABLE ${s} USING vec0(ref TEXT PRIMARY KEY, namespace TEXT PARTITION KEY, embedding FLOAT[${String(r)}] distance_metric=cosine, +id TEXT)`,[])}catch(t){throw t instanceof Error&&t.message.includes("no such module: vec0")?new Error("@lunora/ai/rag: sqliteVectorStore `ann` needs the sqlite-vec extension in this SQLite (`no such module: vec0`) — on celld set the `sqlite_vec` compatibility flag, on node:sqlite load the extension",{cause:t}):t}await S(),await o(`CREATE TABLE ${E} (dimensions INTEGER NOT NULL)`,[]),await o(`INSERT INTO ${E} (dimensions) VALUES (?)`,[r])},nearest:async(a,t,i)=>(u(t.length,"the query embedding"),(await o(`SELECT id, distance FROM ${s} WHERE embedding MATCH ? AND k = ? AND namespace = ?`,[JSON.stringify([...t]),Math.min(i,C),a])).map(e=>({distance:Number(e.distance),id:String(e.id)}))),put:async(a,t,i)=>{u(i.length,`the embedding for "${t}"`),await o(`DELETE FROM ${s} WHERE ref = ?`,[R(a,t)]),await g(a,t,JSON.stringify([...i]))},remove:async(a,t)=>{await o(`DELETE FROM ${s} WHERE ref IN (${I(t.length)})`,t.map(i=>R(a,i)))}}},B="lunora_rag_vectors",U=5e4,$=100,V=null,h=d=>d??"",W=d=>{if(typeof d.exec!="function")throw new TypeError("@lunora/ai/rag: sqliteVectorStore requires an `exec` function");const r=q(d.table??B,"sqliteVectorStore `table`"),o=d.maxScan??U,{ann:m,exec:s}=d;if(m!==void 0&&(!Number.isInteger(m.dimensions)||m.dimensions<=0))throw new TypeError("@lunora/ai/rag: sqliteVectorStore `ann.dimensions` must be a positive integer");const E=m===void 0?void 0:D({dimensions:m.dimensions,exec:s,table:r}),u={maxDimensions:d.maxDimensions??m?.dimensions??!1,maxIdBytes:!1,maxMetadataBytes:!1,maxTopK:$,maxTopKWithMetadata:$};let g;const S=async()=>{g??=(async()=>{await s(`CREATE TABLE IF NOT EXISTS ${r} (id TEXT NOT NULL, namespace TEXT NOT NULL DEFAULT '', vector TEXT NOT NULL, metadata TEXT, PRIMARY KEY (namespace, id))`,[]),await s(`CREATE INDEX IF NOT EXISTS ${r}_namespace ON ${r} (namespace)`,[]),await E?.ensure()})().catch(e=>{throw g=void 0,e}),await g},b=async e=>{if(await S(),!e.embed)throw new TypeError("@lunora/ai/rag: sqliteVectorStore requires an `embed` function on upsert");const c=await e.embed(e.input),n=h(e.namespace);await E?.put(n,e.id,c),await s(`INSERT INTO ${r} (id, namespace, vector, metadata) VALUES (${I(4)}) ON CONFLICT(namespace, id) DO UPDATE SET vector = excluded.vector, metadata = excluded.metadata`,[e.id,n,JSON.stringify([...c]),e.metadata===void 0?V:JSON.stringify(e.metadata)])},a=async(e,c)=>{if(await S(),e.length===0)return[];const n=[];for(const w of O(e)){const f=await s(`SELECT id, metadata FROM ${r} WHERE namespace = ? AND id IN (${I(w.length)})`,[h(c),...w]);for(const l of f){const T=y(l.metadata);n.push({id:String(l.id),...T===void 0?{}:{metadata:T}})}}return n},t=async(e,c,n)=>{const w=h(n.namespace),f=await e.nearest(w,c,n.topK??10),l=await a(f.map(L=>L.id),w),T=new Map(l.map(L=>[L.id,L])),N=[];for(const{distance:L,id:A}of f){const v=T.get(A);v!==void 0&&N.push({id:A,score:1-L,...n.returnMetadata==="none"||v.metadata===void 0?{}:{metadata:v.metadata}})}return{count:N.length,matches:N}};return{capabilities:u,deleteByIds:async(e,c)=>{if(await S(),e.length!==0)for(const n of O(e))await s(`DELETE FROM ${r} WHERE namespace = ? AND id IN (${I(n.length)})`,[h(c),...n]),await E?.remove(h(c),n)},getByIds:a,query:async e=>{await S();let c;if(e.embed&&e.input!==void 0)c=await e.embed(e.input);else throw new TypeError("@lunora/ai/rag: sqliteVectorStore query requires both `input` and `embed`");if(E!==void 0&&e.filter===void 0)return t(E,c,e);const n=await s(`SELECT id, vector, metadata FROM ${r} WHERE namespace = ? LIMIT ?`,[h(e.namespace),o+1]);if(n.length>o)throw new RangeError(`@lunora/ai/rag: sqliteVectorStore scanned ${String(n.length)} vectors in namespace "${h(e.namespace)}", over the ${String(o)} limit — search here is brute force and linear, so this namespace has outgrown it. Shard it further, or move this index to Vectorize or a pgvector backend`);const w=[];for(const l of n){const T=y(l.metadata);if(!x(T,e.filter))continue;const N=y(l.vector);N!==void 0&&w.push({id:String(l.id),score:F(c,N),...e.returnMetadata==="none"||T===void 0?{}:{metadata:T}})}const f=w.toSorted((l,T)=>T.score-l.score).slice(0,e.topK??10);return{count:f.length,matches:f}},upsert:b}};export{W as sqliteVectorStore};
@@ -0,0 +1 @@
1
+ const a=/["\\\u0000-\u001F\uD800-\uDFFF]/,f=t=>a.test(t)?JSON.stringify(t):`"${t}"`,y=t=>{if(t===void 0)return"null";if(typeof t=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(typeof t=="number"){if(Number.isNaN(t))return"nan";if(t===1/0)return"inf";if(t===-1/0)return"-inf";if(Object.is(t,-0))return"-0"}if(typeof t=="string")return f(t);if(t===null||typeof t!="object")return JSON.stringify(t);if(Array.isArray(t)){let r="[";for(let n=0;n<t.length;n++)n>0&&(r+=","),r+=y(t[n]);return r+"]"}const i=Object.getPrototypeOf(t);if(i!==null&&i!==Object.prototype){const r=t.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${r} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const o=t,c=Object.keys(o).sort();let e="{",s=!0;for(const r of c){const n=o[r];n!==void 0&&(s?s=!1:e+=",",e+=f(r),e+=":",e+=y(n))}return e+"}"};export{y as s};