@lunora/ai 1.0.0-alpha.13 → 1.0.0-alpha.130

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 (34) hide show
  1. package/README.md +22 -9
  2. package/dist/create.d.mts +109 -0
  3. package/dist/create.d.ts +109 -0
  4. package/dist/create.mjs +1 -0
  5. package/dist/index.d.mts +3 -30
  6. package/dist/index.d.ts +3 -30
  7. package/dist/index.mjs +1 -3
  8. package/dist/packem_shared/AI_DEFAULT_EMBEDDING_MODEL_ENV-IwcHZiKj.mjs +1 -0
  9. package/dist/packem_shared/DEFAULT_MODEL_PRICES-C5oaHRiN.mjs +1 -0
  10. package/dist/packem_shared/VECTORIZE_CAPABILITIES-l9ceRO5j.mjs +1 -0
  11. package/dist/packem_shared/batchReranker-Bc38FBLH.mjs +1 -0
  12. package/dist/packem_shared/bm25-9q0Avwi-.mjs +1 -0
  13. package/dist/packem_shared/bm25LexicalStore-DMUzAL0O.mjs +1 -0
  14. package/dist/packem_shared/concurrent-C6nqBv41.mjs +1 -0
  15. package/dist/packem_shared/contentHash-BIn6ECP8.mjs +1 -0
  16. package/dist/packem_shared/createAi-GYEHZ8tt.mjs +1 -0
  17. package/dist/packem_shared/defineRag-BOS3IQeP.mjs +8 -0
  18. package/dist/packem_shared/defineRagSource-Q3f3niU8.mjs +1 -0
  19. package/dist/packem_shared/fixedWindowChunks-C461ahRE.mjs +1 -0
  20. package/dist/packem_shared/hybridRank-B4skyCLx.mjs +1 -0
  21. package/dist/packem_shared/markdownChunker-iW8V4klY.mjs +5 -0
  22. package/dist/packem_shared/matchesMetadataFilter-BbIOyA5g.mjs +1 -0
  23. package/dist/packem_shared/ragSyncTriggers-D933fCwt.mjs +1 -0
  24. package/dist/packem_shared/sql-D5aqEMCY.mjs +1 -0
  25. package/dist/packem_shared/sqlLexicalStore-4C_cIwef.mjs +1 -0
  26. package/dist/packem_shared/sqliteVectorStore-f24ySqtJ.mjs +1 -0
  27. package/dist/packem_shared/types.d-BG8Olmuo.d.mts +610 -0
  28. package/dist/packem_shared/types.d-BG8Olmuo.d.ts +610 -0
  29. package/dist/packem_shared/usage-C5GJNFt9.mjs +1 -0
  30. package/dist/rag/index.d.mts +1381 -0
  31. package/dist/rag/index.d.ts +1381 -0
  32. package/dist/rag/index.mjs +1 -0
  33. package/package.json +19 -16
  34. package/dist/packem_shared/createAi-CYxQuvEL.mjs +0 -55
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 cloudflare 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";
@@ -93,6 +102,10 @@ Outside an action — in the worker entry, a Durable Object, or a queue/schedule
93
102
 
94
103
  > This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs)**.
95
104
 
105
+ ### Local dev without a Cloudflare login
106
+
107
+ Workers AI only runs remotely, so the `AI` binding needs a Cloudflare login even in dev. When `lunora dev` or `vite dev` finds none (no `wrangler login`, no `CLOUDFLARE_API_TOKEN`), it leaves the binding out and prints a warning instead of failing to boot. AI SDK model objects and `"<provider>/<model>"` slugs through `LUNORA_AI_PROXY_URL` keep working; `@cf/…` models throw until you log in.
108
+
96
109
  ## Related
97
110
 
98
111
  - [`@lunora/server`](https://www.npmjs.com/package/@lunora/server) — function primitives whose `ctx.ai` this package backs.
@@ -0,0 +1,109 @@
1
+ import { a as LunoraAiOptions, L as LunoraAi } from "./packem_shared/types.d-BG8Olmuo.mjs";
2
+ export type { A as AiBindingLike, b as AiImage, c as AiImageGenerateInput, d as AiImageGenerateResult, e as AiToMarkdownInput, f as AiToMarkdownOptions, g as AiToMarkdownResult, h as AiWebSearchItem, i as AiWebSearchProvider } from "./packem_shared/types.d-BG8Olmuo.mjs";
3
+ import 'ai';
4
+ import 'workers-ai-provider';
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. Token models price per **one million** tokens; image models price per generated image. */
56
+ interface ModelPrice {
57
+ /** Price per million input (prompt) tokens. Token-billed models only. */
58
+ input?: number;
59
+ /** Price per million output (completion) tokens. Omit for an embedding model. */
60
+ output?: number;
61
+ /** Price per generated image, in USD. Image models only. */
62
+ perImage?: number;
63
+ }
64
+ /** Token counts to price. Either may be absent. */
65
+ interface ModelUsage {
66
+ inputTokens?: number;
67
+ outputTokens?: number;
68
+ }
69
+ /**
70
+ * An indicative price table, keyed by model id.
71
+ *
72
+ * Deliberately small: a table that tries to cover every model is a table that
73
+ * is wrong about most of them. It holds the models Lunora's own defaults and
74
+ * documented examples reference — text generation and embeddings, on Workers AI
75
+ * and OpenAI — and everything else returns `undefined` rather than a guess.
76
+ *
77
+ * Generation models carry an `output` price; embedding models do not (they have
78
+ * no completion). Image models carry a `perImage` price instead — the unit an
79
+ * image call is billed in, not tokens. A generation model missing from here is
80
+ * why a chat span would carry no cost at all off an AI Gateway, so the ids the
81
+ * docs teach are the ones that have to be in the table.
82
+ */
83
+ declare const DEFAULT_MODEL_PRICES: Readonly<Record<string, ModelPrice>>;
84
+ /**
85
+ * Look up a model's price, or `undefined` when the table does not cover it.
86
+ * @experimental
87
+ */
88
+ declare const lookupModelPrice: (modelId: string, prices?: Readonly<Record<string, ModelPrice>>) => ModelPrice | undefined;
89
+ /**
90
+ * Estimate a call's cost in USD from its token usage, or `undefined` when the
91
+ * model is not priced or no usable token count was supplied.
92
+ *
93
+ * Returns `undefined` rather than `0` for an unpriced model: zero is a
94
+ * defensible cost that would quietly sum into a total, while an absent value
95
+ * shows up as absent.
96
+ * @experimental
97
+ */
98
+ declare const estimateModelCost: (modelId: string | undefined, usage: ModelUsage, prices?: Readonly<Record<string, ModelPrice>>) => number | undefined;
99
+ /**
100
+ * Estimate the cost of generating `count` images in USD, or `undefined` when the
101
+ * model is not priced or carries no image price.
102
+ *
103
+ * Like {@link estimateModelCost}, returns `undefined` rather than `0` for an
104
+ * unpriced model, so a missing price shows up as absent instead of folding a
105
+ * false zero into a total.
106
+ * @experimental
107
+ */
108
+ declare const estimateImageCost: (modelId: string | undefined, count?: number, prices?: Readonly<Record<string, ModelPrice>>) => number | undefined;
109
+ export { DEFAULT_MODEL_PRICES as D, type LunoraAi, ModelPrice as M, ModelUsage as a, createAi, estimateImageCost, estimateModelCost, lookupModelPrice as l };
@@ -0,0 +1,109 @@
1
+ import { a as LunoraAiOptions, L as LunoraAi } from "./packem_shared/types.d-BG8Olmuo.js";
2
+ export type { A as AiBindingLike, b as AiImage, c as AiImageGenerateInput, d as AiImageGenerateResult, e as AiToMarkdownInput, f as AiToMarkdownOptions, g as AiToMarkdownResult, h as AiWebSearchItem, i as AiWebSearchProvider } from "./packem_shared/types.d-BG8Olmuo.js";
3
+ import 'ai';
4
+ import 'workers-ai-provider';
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. Token models price per **one million** tokens; image models price per generated image. */
56
+ interface ModelPrice {
57
+ /** Price per million input (prompt) tokens. Token-billed models only. */
58
+ input?: number;
59
+ /** Price per million output (completion) tokens. Omit for an embedding model. */
60
+ output?: number;
61
+ /** Price per generated image, in USD. Image models only. */
62
+ perImage?: number;
63
+ }
64
+ /** Token counts to price. Either may be absent. */
65
+ interface ModelUsage {
66
+ inputTokens?: number;
67
+ outputTokens?: number;
68
+ }
69
+ /**
70
+ * An indicative price table, keyed by model id.
71
+ *
72
+ * Deliberately small: a table that tries to cover every model is a table that
73
+ * is wrong about most of them. It holds the models Lunora's own defaults and
74
+ * documented examples reference — text generation and embeddings, on Workers AI
75
+ * and OpenAI — and everything else returns `undefined` rather than a guess.
76
+ *
77
+ * Generation models carry an `output` price; embedding models do not (they have
78
+ * no completion). Image models carry a `perImage` price instead — the unit an
79
+ * image call is billed in, not tokens. A generation model missing from here is
80
+ * why a chat span would carry no cost at all off an AI Gateway, so the ids the
81
+ * docs teach are the ones that have to be in the table.
82
+ */
83
+ declare const DEFAULT_MODEL_PRICES: Readonly<Record<string, ModelPrice>>;
84
+ /**
85
+ * Look up a model's price, or `undefined` when the table does not cover it.
86
+ * @experimental
87
+ */
88
+ declare const lookupModelPrice: (modelId: string, prices?: Readonly<Record<string, ModelPrice>>) => ModelPrice | undefined;
89
+ /**
90
+ * Estimate a call's cost in USD from its token usage, or `undefined` when the
91
+ * model is not priced or no usable token count was supplied.
92
+ *
93
+ * Returns `undefined` rather than `0` for an unpriced model: zero is a
94
+ * defensible cost that would quietly sum into a total, while an absent value
95
+ * shows up as absent.
96
+ * @experimental
97
+ */
98
+ declare const estimateModelCost: (modelId: string | undefined, usage: ModelUsage, prices?: Readonly<Record<string, ModelPrice>>) => number | undefined;
99
+ /**
100
+ * Estimate the cost of generating `count` images in USD, or `undefined` when the
101
+ * model is not priced or carries no image price.
102
+ *
103
+ * Like {@link estimateModelCost}, returns `undefined` rather than `0` for an
104
+ * unpriced model, so a missing price shows up as absent instead of folding a
105
+ * false zero into a total.
106
+ * @experimental
107
+ */
108
+ declare const estimateImageCost: (modelId: string | undefined, count?: number, prices?: Readonly<Record<string, ModelPrice>>) => number | undefined;
109
+ export { DEFAULT_MODEL_PRICES as D, type LunoraAi, ModelPrice as M, ModelUsage as a, createAi, estimateImageCost, estimateModelCost, lookupModelPrice as l };
@@ -0,0 +1 @@
1
+ import{default as o}from"./packem_shared/createAi-GYEHZ8tt.mjs";import{estimateImageCost as m,estimateModelCost as r}from"./packem_shared/DEFAULT_MODEL_PRICES-C5oaHRiN.mjs";export{o as createAi,m as estimateImageCost,r as estimateModelCost};
package/dist/index.d.mts CHANGED
@@ -1,31 +1,4 @@
1
- import { EmbeddingModel, LanguageModel } from 'ai';
2
- export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, streamObject, streamText, tool } from 'ai';
1
+ export { D as DEFAULT_MODEL_PRICES, type M as ModelPrice, type a as ModelUsage, createAi, estimateImageCost, estimateModelCost, l as lookupModelPrice } from "./create.mjs";
2
+ export { j as AI_DEFAULT_EMBEDDING_MODEL_ENV, k as AI_DEFAULT_MODEL_ENV, l as AI_GATEWAY_ACCOUNT_ID_ENV, m as AI_GATEWAY_ID_ENV, n as AI_GATEWAY_METADATA_MAX_KEYS, o as AI_GATEWAY_TAGS_ENV, p as AI_GATEWAY_TOKEN_ENV, q as AI_PROXY_TOKEN_ENV, r as AI_PROXY_URL_ENV, type A as AiBindingLike, type s as AiGatewayMetadata, type t as AiGatewayOptions, type b as AiImage, type c as AiImageGenerateInput, type d as AiImageGenerateResult, type u as AiMarkdownConversionResult, type v as AiMarkdownDocument, type w as AiMetrics, type x as AiModelOptions, type y as AiRunOptions, type z as AiSpan, type B as AiTelemetry, type e as AiToMarkdownInput, type f as AiToMarkdownOptions, type g as AiToMarkdownResult, type C as AiTracer, type h as AiWebSearchItem, type D as AiWebSearchOptions, type i as AiWebSearchProvider, type F as AiWebSearchResult, type E as EmbeddingModelInput, type L as LunoraAi, type a as LunoraAiOptions, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, G as buildAiGatewayMetadataFields, H as readAiGatewayEnvTags, I as resolveAiGateway } from "./packem_shared/types.d-BG8Olmuo.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
- defaultEmbeddingModel?: string;
18
- defaultModel?: string;
19
- gateway?: AiGatewayOptions;
20
- provider?: WorkersAiProviderLike;
21
- }
22
- type ModelInput = LanguageModel;
23
- type EmbeddingModelInput = EmbeddingModel | string;
24
- interface LunoraAi {
25
- embeddingModel: (model?: EmbeddingModelInput) => EmbeddingModel;
26
- model: (model?: ModelInput) => LanguageModel;
27
- run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
28
- workersai: WorkersAiProviderLike;
29
- }
30
- declare const createAi: (options: LunoraAiOptions) => LunoraAi;
31
- export { type AiBindingLike, type AiGatewayOptions, type EmbeddingModelInput, type LunoraAi, type LunoraAiOptions, type ModelInput, type WorkersAiProviderLike, createAi };
package/dist/index.d.ts CHANGED
@@ -1,31 +1,4 @@
1
- import { EmbeddingModel, LanguageModel } from 'ai';
2
- export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, streamObject, streamText, tool } from 'ai';
1
+ export { D as DEFAULT_MODEL_PRICES, type M as ModelPrice, type a as ModelUsage, createAi, estimateImageCost, estimateModelCost, l as lookupModelPrice } from "./create.js";
2
+ export { j as AI_DEFAULT_EMBEDDING_MODEL_ENV, k as AI_DEFAULT_MODEL_ENV, l as AI_GATEWAY_ACCOUNT_ID_ENV, m as AI_GATEWAY_ID_ENV, n as AI_GATEWAY_METADATA_MAX_KEYS, o as AI_GATEWAY_TAGS_ENV, p as AI_GATEWAY_TOKEN_ENV, q as AI_PROXY_TOKEN_ENV, r as AI_PROXY_URL_ENV, type A as AiBindingLike, type s as AiGatewayMetadata, type t as AiGatewayOptions, type b as AiImage, type c as AiImageGenerateInput, type d as AiImageGenerateResult, type u as AiMarkdownConversionResult, type v as AiMarkdownDocument, type w as AiMetrics, type x as AiModelOptions, type y as AiRunOptions, type z as AiSpan, type B as AiTelemetry, type e as AiToMarkdownInput, type f as AiToMarkdownOptions, type g as AiToMarkdownResult, type C as AiTracer, type h as AiWebSearchItem, type D as AiWebSearchOptions, type i as AiWebSearchProvider, type F as AiWebSearchResult, type E as EmbeddingModelInput, type L as LunoraAi, type a as LunoraAiOptions, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, G as buildAiGatewayMetadataFields, H as readAiGatewayEnvTags, I as resolveAiGateway } from "./packem_shared/types.d-BG8Olmuo.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
- defaultEmbeddingModel?: string;
18
- defaultModel?: string;
19
- gateway?: AiGatewayOptions;
20
- provider?: WorkersAiProviderLike;
21
- }
22
- type ModelInput = LanguageModel;
23
- type EmbeddingModelInput = EmbeddingModel | string;
24
- interface LunoraAi {
25
- embeddingModel: (model?: EmbeddingModelInput) => EmbeddingModel;
26
- model: (model?: ModelInput) => LanguageModel;
27
- run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
28
- workersai: WorkersAiProviderLike;
29
- }
30
- declare const createAi: (options: LunoraAiOptions) => LunoraAi;
31
- export { type AiBindingLike, type AiGatewayOptions, type EmbeddingModelInput, type LunoraAi, type LunoraAiOptions, type ModelInput, type WorkersAiProviderLike, createAi };
package/dist/index.mjs CHANGED
@@ -1,3 +1 @@
1
- export { default as createAi } from './packem_shared/createAi-CYxQuvEL.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-GYEHZ8tt.mjs";import{AI_DEFAULT_EMBEDDING_MODEL_ENV as E,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 m,AI_PROXY_TOKEN_ENV as s,AI_PROXY_URL_ENV as N,buildAiGatewayMetadataFields as l,readAiGatewayEnvTags as D,resolveAiGateway as G}from"./packem_shared/AI_DEFAULT_EMBEDDING_MODEL_ENV-IwcHZiKj.mjs";import{DEFAULT_MODEL_PRICES as O,estimateImageCost as d,estimateModelCost as i,lookupModelPrice as V}from"./packem_shared/DEFAULT_MODEL_PRICES-C5oaHRiN.mjs";import{embed as x,embedMany as L,generateObject as c,generateText as f,hasToolCall as p,jsonSchema as C,streamObject as W,streamText as b,tool as n}from"ai";import{createWorkersAI as g}from"workers-ai-provider";export{E 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,m as AI_GATEWAY_TOKEN_ENV,s as AI_PROXY_TOKEN_ENV,N as AI_PROXY_URL_ENV,O as DEFAULT_MODEL_PRICES,l as buildAiGatewayMetadataFields,_ as createAi,g as createWorkersAI,x as embed,L as embedMany,d as estimateImageCost,i as estimateModelCost,c as generateObject,f as generateText,p as hasToolCall,C as jsonSchema,V 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,c={"@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},"@cf/black-forest-labs/flux-1-schnell":{perImage:6336e-7},"dall-e-3":{perImage:.04},"gpt-image-1":{perImage:.04},"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}},m=i=>{const t=i.trim(),n=t.indexOf("/@"),e=n===-1?t:t.slice(n+1),o=e.lastIndexOf("/");return(o!==-1&&!e.startsWith("@")?[e,e.slice(o+1)]:[e]).flatMap(s=>{const r=p.exec(s)?.[1];return r===void 0?[s]:[s,r]})},a=(i,t=c)=>{for(const n of m(i))if(Object.hasOwn(t,n))return t[n]},l=(i,t,n)=>{if(i===void 0||i.length===0)return;const e=a(i,n);if(e?.input===void 0&&e?.output===void 0)return;const o=Number.isFinite(t.inputTokens)?Math.max(0,t.inputTokens):0,u=Number.isFinite(t.outputTokens)?Math.max(0,t.outputTokens):0;if(o<=0&&u<=0)return;const s=(o*(e.input??0)+u*(e.output??0))/1e6;return Number.isFinite(s)?s:void 0},f=(i,t=1,n)=>{if(i===void 0||i.length===0)return;const e=a(i,n);if(e?.perImage===void 0)return;const o=Number.isFinite(t)?Math.max(0,t):0;if(o<=0)return;const u=o*e.perImage;return Number.isFinite(u)?u:void 0};export{c as DEFAULT_MODEL_PRICES,f as estimateImageCost,l as estimateModelCost,a as lookupModelPrice};
@@ -0,0 +1 @@
1
+ const y={maxDimensions:1536,maxIdBytes:64,maxMetadataBytes:10240,maxTopK:100,maxTopKWithMetadata:50},p=(t,r)=>({...t.upsertMany?{upsertMany:(e,a)=>t.upsertMany?.(r,e,a)}:{},capabilities:t.deferredDelete?{...y,deferredDelete:!0}:y,deleteByIds:(e,a,s,d)=>t.deleteByIds(r,e,a,s,d),getByIds:(e,a)=>t.getByIds(r,e,a),query:e=>t.query(r,e),upsert:(e,a)=>t.upsert(r,e,a)});export{y as VECTORIZE_CAPABILITIES,p 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"./DEFAULT_MODEL_PRICES-C5oaHRiN.mjs";import{LunoraError as m}from"@lunora/errors";import{buildAiGatewayMetadataFields as J,readEnv as N,AI_DEFAULT_MODEL_ENV as F,AI_DEFAULT_EMBEDDING_MODEL_ENV as H,AI_PROXY_URL_ENV as D,AI_PROXY_TOKEN_ENV as ee,readAiGatewayEnvTags as pe,AI_GATEWAY_ID_ENV as te,warnIgnoredBindingToken as be,resolveAiGateway as Ie}from"./AI_DEFAULT_EMBEDDING_MODEL_ENV-IwcHZiKj.mjs";import{m as _e,r as z,a as Te}from"./usage-C5GJNFt9.mjs";const L=e=>{let t="";for(let n=0;n<e.length;n+=32768)t+=String.fromCharCode(...e.subarray(n,n+32768));return btoa(t)},Me=e=>{const t=atob(e),r=new Uint8Array(t.length);for(let n=0;n<t.length;n+=1)r[n]=t.codePointAt(n)??0;return r},Ne=/^\d{1,3}$/u,Pe=/^::ffff:([\da-f]{1,4}):([\da-f]{1,4})$/u,Le=/^::ffff:0:([\da-f]{1,4}):([\da-f]{1,4})$/u,Re=/^::[\da-f]{1,4}$/u,De=/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u,Oe=/^::(\d{1,3}(?:\.\d{1,3}){3})$/u,Ue=/^::([\da-f]{1,4}):([\da-f]{1,4})$/u,ke=/^64:ff9b::[\da-f]{1,4}:[\da-f]{1,4}$/u,Se=/^[\da-f]{1,4}$/u,xe=[[[8193],23],[[16383],20],[[24320],16],[[256,0,0,1],64]],We=[[[8193,1,0,0,0,0,0,1],128],[[8193,1,0,0,0,0,0,2],128],[[8193,1,0,0,0,0,0,3],128],[[8193,3],32],[[8193,4,274],48],[[8193,5],32],[[8193,32],28],[[8193,48],28]],$e=e=>{const t=e.split("::");if(t.length>2)return;const r=l=>l===void 0||l===""?[]:l.split(":").map(u=>Se.test(u)?Number.parseInt(u,16):Number.NaN),n=r(t[0]),a=r(t[1]),s=8-n.length-a.length;if(s<0||t.length===1&&s!==0)return;const d=[...n,...Array.from({length:s},()=>0),...a];return d.some(Number.isNaN)?void 0:d},K=(e,[t,r])=>{for(let n=0;n*16<r;n+=1){const s=65535<<16-Math.min(16,r-n*16)&65535;if(((e[n]??0)&s)!==((t[n]??0)&s))return!1}return!0},Ce=/^\[|\]$/gu,Be=/\.$/u,B=e=>{const t=e.split(".");if(t.length!==4)return;const r=t.map(n=>Ne.test(n)?Number(n):-1);if(!r.some(n=>n<0||n>255))return[r[0],r[1],r[2],r[3]]},O=([e,t,r])=>e===0||e===10||e===127||e===100&&t>=64&&t<=127||e===169&&t===254||e===172&&t>=16&&t<=31||e===192&&t===0&&(r===0||r===2)||e===192&&t===88&&r===99||e===192&&t===168||e===198&&(t===18||t===19)||e===198&&t===51&&r===100||e===203&&t===0&&r===113||e>=224,S=(e,t)=>{const r=Number.parseInt(e??"",16),n=Number.parseInt(t??"",16);return!Number.isFinite(r)||!Number.isFinite(n)?!0:O([Math.floor(r/256),r%256,Math.floor(n/256),n%256])},Ge=e=>{const t=e.toLowerCase(),r=Pe.exec(t);if(r)return S(r[1],r[2]);const n=Le.exec(t);if(n)return S(n[1],n[2]);const a=De.exec(t);if(a){const u=B(a[1]??"");return u===void 0||O(u)}const s=Oe.exec(t);if(s){const u=B(s[1]??"");return u===void 0||O(u)}const d=Ue.exec(t);if(d)return S(d[1],d[2]);if(Re.test(t)||ke.test(t)||t.startsWith("64:ff9b:1:")||t.startsWith("2002:"))return!0;const l=$e(t);return l!==void 0&&xe.some(u=>K(l,u))&&!We.some(u=>K(l,u))?!0:t==="::"||t==="::1"||t.startsWith("fc")||t.startsWith("fd")||t.startsWith("fe8")||t.startsWith("fe9")||t.startsWith("fea")||t.startsWith("feb")||t.startsWith("fec")||t.startsWith("fed")||t.startsWith("fee")||t.startsWith("fef")||t.startsWith("ff")||t.startsWith("2001:db8:")||t.startsWith("100::")},Ve=e=>e==="localhost"||e.endsWith(".localhost")||e.endsWith(".local")||e.endsWith(".internal")||e.endsWith(".home.arpa"),Fe=e=>e.replaceAll(Ce,"").replace(Be,"").toLowerCase(),He=e=>{const t=Fe(e);if(t.includes(":"))return Ge(t);const r=B(t);return r===void 0?Ve(t):O(r)},ze={setAttribute:()=>{},setAttributes:()=>{}},re=async(e,t)=>t(re,ze),Ke=(e,{metrics:t,trace:r=re})=>{const n={"gen_ai.operation.name":"chat","gen_ai.request.model":e};return{wrapGenerate:async({doGenerate:a})=>r("ai.generate",async(s,d)=>{const l=await a();return z(e,l,d,t),l},n),wrapStream:async({doStream:a})=>{const s=(u,v)=>{const y=u.stream.getReader();let A={};return{...u,stream:new ReadableStream({cancel:async E=>{v({outcome:A}),await y.cancel(E)},pull:async E=>{let p;try{p=await y.read()}catch(P){v({error:P,failed:!0,outcome:A}),E.error(P);return}if(p.done){v({outcome:A}),E.close();return}p.value.type==="finish"&&(A={providerMetadata:p.value.providerMetadata,usage:p.value.usage}),E.enqueue(p.value)}})}},d=Promise.withResolvers(),l=Promise.withResolvers();return r("ai.stream",async(u,v)=>{d.resolve(s(await a(),l.resolve));const y=await l.promise;if(z(e,y.outcome,v,t),y.failed===!0)throw y.error},n).catch(d.reject),d.promise}}},Y=(e,t,r)=>{if(t===void 0||typeof e=="string")return e;const n=Ke(r??_e(e)??"unknown",t);let a;const s=async()=>(a??=import("ai").then(({wrapLanguageModel:l})=>l({middleware:n,model:e}),l=>{throw a=void 0,l}),a),d=e;return{doGenerate:async l=>s().then(u=>u.doGenerate(l)),doStream:async l=>s().then(u=>u.doStream(l)),modelId:d.modelId,provider:d.provider,specificationVersion:"v4",get supportedUrls(){return s().then(l=>l.supportedUrls)}}};let x;const ne=()=>(x??=Promise.all([import("@ai-sdk/openai"),import("workers-ai-provider/openai"),import("workers-ai-provider/anthropic")]).then(([e,{openai:t},{anthropic:r}])=>({createOpenAI:e.createOpenAI,plugins:[t,r]}),e=>{throw x=void 0,e}),x),oe=(e,t,r)=>({doGenerate:async n=>r().then(a=>a.doGenerate(n)),doStream:async n=>r().then(a=>a.doStream(n)),modelId:e,provider:t,specificationVersion:"v4",get supportedUrls(){return r().then(n=>n.supportedUrls)}}),Ye={maxEmbeddingsPerCall:3e3,supportsParallelCalls:!0},ae=(e,t,r,n)=>({doEmbed:async a=>r().then(s=>s.doEmbed(a)),get maxEmbeddingsPerCall(){return n?.maxEmbeddingsPerCall??r().then(a=>a.maxEmbeddingsPerCall)},modelId:e,provider:t,specificationVersion:"v4",get supportsParallelCalls(){return n?.supportsParallelCalls??r().then(a=>a.supportsParallelCalls)}});let W;const X=()=>(W??=import("workers-ai-provider").catch(e=>{throw W=void 0,e}),W),R=e=>!e.startsWith("@")&&e.includes("/"),Xe=e=>{const t=(r,n)=>oe(r,"workersai.chat",async()=>e(r).then(a=>a(r,n)));return t.textEmbeddingModel=r=>ae(r,"workersai.embedding",async()=>e(r).then(n=>n.textEmbeddingModel?.(r)),R(r)?void 0:Ye),t},_=e=>{throw new m("INTERNAL",`@lunora/ai: ${e} needs the \`AI\` binding (env.AI). In dev, Lunora leaves it out when there is no Cloudflare login: run \`wrangler login\` or set CLOUDFLARE_API_TOKEN. Elsewhere, add an \`ai\` binding to wrangler.jsonc. Or set ${D} to an OpenAI-compatible proxy for "<provider>/<model>" slugs.`)},se=()=>_("this model id");se.textEmbeddingModel=()=>_("this embedding model id");const je=3040,Qe=/^(?:[A-Z]\w*:\s*)?3040\s*:/iu,qe=e=>e?.code===je||e instanceof Error&&Qe.test(e.message),Ze="default",Je=new Map([[401,"UNAUTHORIZED"],[403,"FORBIDDEN"],[404,"NOT_FOUND"],[408,"SERVICE_UNAVAILABLE"],[429,"RATE_LIMITED"]]),ie=e=>Je.get(e)??(e>=400&&e<500?"BAD_REQUEST":"SERVICE_UNAVAILABLE"),de=300,et=new Set(["127.0.0.1","[::1]","localhost"]),tt=(e,t)=>{const r=URL.canParse(e)?new URL(e):void 0;if(r===void 0)return`${D} is not a valid URL`;if(t!==void 0&&r.protocol!=="https:"&&!et.has(r.hostname))return`${D} (${r.origin}) is not HTTPS, so ${ee} would travel in cleartext — use an https:// URL`},rt=e=>{const t=N(e,D);if(t===void 0)return;const r=N(e,ee),n=tt(t,r);if(n!==void 0){const d=()=>{throw new m("INTERNAL",`@lunora/ai: ${n}`)};return{chat:d,embedding:d,image:d}}let a;const s=()=>(a??=ne().then(({createOpenAI:d})=>d({apiKey:r??"",baseURL:t,name:"lunora-proxy"}),d=>{throw a=void 0,d}),a);return{chat:d=>oe(d,"lunora-proxy",async()=>s().then(l=>l.chat(d))),embedding:d=>ae(d,"lunora-proxy",async()=>s().then(l=>l.embedding(d))),image:async d=>s().then(l=>l.image(d))}},nt=(e,t)=>{const r=e===void 0?void 0:pe(e);return r===void 0?t:{...t,tags:{...r,...t?.tags}}},ot=(e,t,r)=>{const n=J(r);if(e!==void 0)return n!==void 0&&e.metadata===void 0?{...e,metadata:n}:e;if(t===void 0)return;const a=N(t,te);if(a!==void 0)return be(t),n===void 0?{id:a}:{id:a,metadata:n}};let $;const at=()=>($??=Promise.all([import("@ai-sdk/openai"),import("@ai-sdk/google")]).then(([e,t])=>({createGoogle:t.createGoogle,createOpenAI:e.createOpenAI}),e=>{throw $=void 0,e}),$),j=8,st=/^[1-9]\d*x[1-9]\d*$/u,it=20*1024*1024,Q="unused",q=(e,t)=>{const r=new Headers(t?.headers);return r.delete("authorization"),r.delete("x-goog-api-key"),fetch(e,{...t,headers:r})},dt=e=>{const t=e.pathname.split("/").findLast(r=>r.length>0);return t===void 0||t===""?"document":t},C=(e,t)=>{if(e>t)throw new m("PAYLOAD_TOO_LARGE",`@lunora/ai: toMarkdown document is ${String(e)} bytes, over the ${String(t)}-byte limit — raise \`maxBytes\` to convert it`)},ct=async(e,t)=>{if(e.body===null)return new Uint8Array;let r=0;const n=e.body.pipeThrough(new TransformStream({transform(a,s){if(r+=a.byteLength,r>t){s.error(new m("PAYLOAD_TOO_LARGE",`@lunora/ai: toMarkdown document is over the ${String(t)}-byte limit — raise \`maxBytes\` to convert it`));return}s.enqueue(a)}}));return new Uint8Array(await new Response(n).arrayBuffer())},lt=e=>{if(e.protocol!=="https:"&&e.protocol!=="http:"||He(e.hostname))throw new m("BAD_REQUEST",`@lunora/ai: toMarkdown will not fetch ${e.href} — only public http(s) hosts; pass its bytes or a Blob instead`)},ut=async(e,t,r)=>{if(e instanceof URL){lt(e);const s=await fetch(e,{redirect:"manual"});if(!s.ok){const v=s.status>=300&&s.status<400?"; redirects are not followed, so pass the final URL":"";throw new m("BAD_REQUEST",`@lunora/ai: toMarkdown could not fetch ${e.href} (HTTP ${String(s.status)})${v}`)}const d=Number(s.headers.get("content-length"));s.headers.has("content-length")&&Number.isFinite(d)&&C(d,r);const l=await ct(s,r),u=s.headers.get("content-type");return{blob:new Blob([l],u===null||u===""?void 0:{type:u}),name:t?.name??dt(e)}}if(e instanceof Blob)return C(e.size,r),{blob:e,name:t?.name??"document"};const n=e instanceof Uint8Array?Uint8Array.from(e):new Uint8Array(e);C(n.byteLength,r);const a=t?.mimeType;return{blob:new Blob([n],a===void 0?void 0:{type:a}),name:t?.name??"document"}},Z=e=>{if(e===void 0||e==="")return;const t=Number(e);return Number.isInteger(t)?t:void 0},ft=e=>{if(e===void 0)return{};const[t,r]=e.split("x");return{height:Z(r),width:Z(t)}},T=e=>{const t=(r,n=0)=>r.every((a,s)=>e[n+s]===a);return t([137,80,78,71])?"image/png":t([255,216,255])?"image/jpeg":t([71,73,70,56])?"image/gif":t([82,73,70,70])&&t([87,69,66,80],8)?"image/webp":void 0},mt=e=>{try{return Me(e.slice(0,16))}catch{return new Uint8Array}},M=(e,t)=>t===void 0?{base64:e}:{base64:e,mimeType:t},ce=async e=>{if(typeof e=="string")return M(e,T(mt(e)));if(e instanceof Uint8Array)return M(L(e),T(e));if(e instanceof ArrayBuffer){const t=new Uint8Array(e);return M(L(t),T(t))}if(e instanceof Response){if(!e.ok){const n=(await e.text().catch(()=>"")).slice(0,de);throw new m(ie(e.status),`@lunora/ai: image generation failed with HTTP ${String(e.status)}${n===""?"":`: ${n}`}`)}const t=new Uint8Array(await e.arrayBuffer());return M(L(t),T(t))}if(e instanceof ReadableStream){const t=new Uint8Array(await new Response(e).arrayBuffer());return M(L(t),T(t))}if(typeof e=="object"&&e!==null&&"image"in e)return ce(e.image)},pt=e=>{const{binding:t,defaultEmbeddingModel:r,defaultModel:n,env:a,gateway:s,metadata:d,provider:l,telemetry:u}=e,v=rt(a),y=nt(a,d),A=ot(s,a,y),E=s?.metadata===void 0?J(y):void 0,p=n??N(a,F),P=r??N(a,H);let U;const le=()=>(U??=X().then(({createWorkersAI:o})=>o({binding:t,gateway:A})).catch(o=>{throw U=void 0,o}),U);let k;const ue=()=>(k??=Promise.all([X(),ne()]).then(([{createWorkersAI:o},{plugins:c}])=>o({binding:t,gateway:A,providers:c})).catch(o=>{throw k=void 0,o}),k),b=l??(t?Xe(async o=>R(o)?ue():le()):se),fe=(o,c)=>R(o)?v!==void 0?v.chat(o):E===void 0?b(o):b(o,{metadata:E}):c?.rejectIfBusy===void 0?b(o):b(o,{rejectIfBusy:c.rejectIfBusy}),me=(o,c)=>{const i=o??p;if(i===void 0||i==="")throw new m("INTERNAL",`@lunora/ai: no model supplied and no default configured — pass a model id, or set ${F} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return typeof i=="string"?Y(fe(i,c),u,i):Y(i,u)},he=o=>{if(v!==void 0&&R(o))return v.embedding(o);const c=b.textEmbeddingModel;if(typeof c!="function")throw new m("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 c.call(b,o)},ge=o=>{if(typeof o=="object")return o;const c=o??P;if(!c)throw new m("INTERNAL",`@lunora/ai: no embedding model supplied and no default configured — pass an embedding model id or an AI SDK EmbeddingModel, or set ${H} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return he(c)},G=async(o,c,i)=>{if(!t)return _("ai.run");const g=A!==void 0&&i?.gateway===void 0?{...i,gateway:A}:i;try{return await t.run(o,c,g)}catch(h){throw qe(h)?new m("RATE_LIMITED",`@lunora/ai: Workers AI has no free capacity for ${o} (error 3040) — retry later`,{cause:h}):h}},we=async(o,c)=>{if(!t)return _("ai.websearch");if(typeof t.websearch!="function")throw new m("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:i,...g}=c??{},h=await t.websearch({...g,gatewayId:i??A?.id??Ze,query:o});if(!h.ok){const I=(await h.text().catch(()=>"")).slice(0,de);throw new m(ie(h.status),`@lunora/ai: web search failed with HTTP ${String(h.status)}${I===""?"":`: ${I}`}`)}const w=await h.json().catch(()=>{});if(!Array.isArray(w?.items))throw new m("INTERNAL","@lunora/ai: web search returned a body that is not JSON with an `items` array");return w},ve=async(o,c)=>{if(!t)return _("ai.toMarkdown");if(typeof t.toMarkdown!="function")throw new m("NOT_IMPLEMENTED","@lunora/ai: this Workers runtime's `AI` binding has no toMarkdown() — update wrangler / @cloudflare/vite-plugin to a release that ships the Markdown Conversion API");const i=c?.maxBytes??it;if(!Number.isFinite(i)||i<=0)throw new m("BAD_REQUEST","@lunora/ai: toMarkdown maxBytes must be a positive number of bytes");const{blob:g,name:h}=await ut(o,c,i),w=await t.toMarkdown({blob:g,name:h},c?.conversionOptions),f=Array.isArray(w)?w[0]:w;if(f===void 0)throw new m("INTERNAL","@lunora/ai: toMarkdown returned no conversion result");if(f.format==="error")throw new m("BAD_REQUEST",`@lunora/ai: toMarkdown could not convert "${h}": ${f.error??"unknown error"}`);if(typeof f.data!="string")throw new m("INTERNAL",`@lunora/ai: toMarkdown returned a "${f.format}" result with no data`);return{markdown:f.data,...f.mimetype===void 0?{}:{mimeType:f.mimetype},...f.pages===void 0?{}:{pages:f.pages},...f.tokens===void 0?{}:{tokens:f.tokens}}},Ae=async o=>{if(!t)return _("ai.images.generate");const{height:c,width:i}=ft(o.size),g={prompt:o.prompt,...c===void 0?{}:{height:c},...i===void 0?{}:{width:i},...o.seed===void 0?{}:{seed:o.seed}},h=async(w,f)=>{if(w>=(o.count??1))return f;const I=await ce(await G(o.model,g));if(I===void 0)throw new m("INTERNAL",`@lunora/ai: ${o.model} returned no image data`);return h(w+1,[...f,I])};return{images:await h(0,[]),model:o.model}},ye=async(o,c,i)=>{const{createGoogle:g,createOpenAI:h}=await at(),w=A===void 0?void 0:Ie({...a,[te]:A.id});let f;o==="openai"?v!==void 0?f=await v.image(i.model):w===void 0?f=h().image(c):f=h({apiKey:Q,baseURL:`${w.baseURL}/openai`,fetch:q,headers:w.headers}).image(c):w===void 0?f=g().image(c):f=g({apiKey:Q,baseURL:`${w.baseURL}/google-ai-studio/v1beta`,fetch:q,headers:w.headers}).image(c);const{generateImage:I}=await import("ai");return{images:(await I({model:f,prompt:i.prompt,...i.count===void 0?{}:{n:i.count},...i.seed===void 0?{}:{seed:i.seed},...i.size===void 0?{}:{size:i.size}})).images.map(V=>({base64:V.base64,mimeType:V.mediaType})),model:i.model}},Ee=async(o,c)=>{if(o.startsWith("@"))return Ae(c);const i=o.indexOf("/"),g=i===-1?"":o.slice(0,i),h=i===-1?"":o.slice(i+1);if((g==="openai"||g==="google")&&h.length>0)return ye(g,h,c);throw new m("AI_UNKNOWN_IMAGE_MODEL",`@lunora/ai: no image provider for "${o}" — pass a Workers AI image id (@cf/…), or an "openai/<model>" or "google/<model>" slug`)};return{embeddingModel:ge,images:{generate:async o=>{const c=o.model,i=o.count??1;if(!Number.isInteger(i)||i<1||i>j)throw new m("BAD_REQUEST",`@lunora/ai: count must be an integer from 1 to ${String(j)}`,{status:400});if(o.size!==void 0&&!st.test(o.size))throw new m("BAD_REQUEST",`@lunora/ai: size must be "<width>x<height>" with positive integers, e.g. "1024x1024" (got "${o.size}")`);const g=await Ee(c,o);return Te(c,g.images.length,u?.metrics),g}},model:me,run:G,toMarkdown:ve,websearch:we,workersai:b}};export{pt as default};
@@ -0,0 +1,8 @@
1
+ import{tool as Re,jsonSchema as De}from"@ai-sdk/provider-utils";import{LunoraError as w,isLunoraError as Ce}from"@lunora/errors";import{stableStringify as Be}from"@lunora/wire";import{r as de,m as L}from"./usage-C5GJNFt9.mjs";import $e from"./fixedWindowChunks-C461ahRE.mjs";import{c as Oe,I as Ke}from"./concurrent-C6nqBv41.mjs";import{contentHash as Ue}from"./contentHash-BIn6ECP8.mjs";import{hybridRank as Qe}from"./hybridRank-B4skyCLx.mjs";import{VECTORIZE_CAPABILITIES as le,vectorizeStore as Ve}from"./VECTORIZE_CAPABILITIES-l9ceRO5j.mjs";const Fe=1e3,Le=200,ze=5,ue=1e3,Pe=4,me=le.maxMetadataBytes===!1?Number.POSITIVE_INFINITY:le.maxMetadataBytes,je=2*1024,we="__ragChunk",be="__ragSource",K="__ragText",q="__ragHash",W="__ragChunks",V="__ragImportance",ve="__ragModel",He=new Set([we,W,q,V,ve,be,K]),Ye=async(e,c,u,m)=>{if(c.length===0)return;const h=c.slice(1);if(!e.upsertMany){await Oe(h,Ke,async(E,A)=>{await e.upsert(E,m),u(A+1)}),await e.upsert(c[0],m),u(0);return}for(let E=0;E<h.length;E+=ue){const A=Math.min(E+ue,h.length);await e.upsertMany(h.slice(E,A),m);for(let U=E;U<A;U+=1)u(U+1)}await e.upsertMany([c[0]],m),u(0)},qe=(e,c,u,m)=>{if(m===!1)return;const h=new TextEncoder().encode(JSON.stringify(e)).length;if(h<=m)return;const A=(typeof e[K]=="string"?new TextEncoder().encode(e[K]).length:0)*2>h?"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 w("BAD_REQUEST",`@lunora/ai/rag: chunk ${String(c)} of "${u}" carries ${String(h)} bytes of metadata, over the store's ${String(m)}-byte per-vector ceiling — ${A}`)},We=(e,c,u)=>{if(u===!1)return;const m=new TextEncoder().encode(e).length;if(!(m<=u))throw new w("BAD_REQUEST",`@lunora/ai/rag: chunk id "${e}" for source "${c}" is ${String(m)} bytes, over the store's ${String(u)}-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`)},Ze=/^[\w.-]{1,40}$/,ye=e=>e===void 0?"":`${encodeURIComponent(e)}#`,O=(e,c,u)=>`${ye(e)}${c}#${String(u)}`,he=(e,c)=>{const u=ye(c),m=u!==""&&e.startsWith(u)?e.slice(u.length):e,h=m.lastIndexOf("#"),E=h===-1?Number.NaN:Number(m.slice(h+1));return h===-1||!Number.isInteger(E)||E<0?{chunkIndex:0,sourceId:m}:{chunkIndex:E,sourceId:m.slice(0,h)}},Xe=async e=>Ue(new TextEncoder().encode(e)),Je=e=>{try{return Be([e.text,e.metadata,e.importance])}catch{return}},Ge=(e,c)=>{const u=[],m=[];for(const h of e)h.score>=c?u.push(h):m.push(h.id);return{kept:u,rejectedIds:m}},et=e=>e!==void 0&&Object.keys(e).length>0,H=e=>{if(!e)return;const c=Object.entries(e).filter(([u])=>!He.has(u));return c.length>0?Object.fromEntries(c):void 0},pe=new Set,tt=e=>{pe.has(e)||(pe.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 }.`))},nt=e=>e.map(c=>`[source:${c.sourceId}#${String(c.chunkIndex)}]
5
+ ${c.text}`).join(`
6
+
7
+ `),ge=(e,c)=>{if(typeof e=="object")return e;if(c===void 0)throw new w("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 c.embeddingModel(e)},rt=e=>{if(e===void 0)throw new w("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};let Y;const fe=async()=>(Y??=import("ai").catch(e=>{throw Y=void 0,e}),Y),pt=e=>{if(typeof e.index!="string"||e.index.length===0)throw new w("BAD_REQUEST","@lunora/ai/rag: `index` must be a non-empty Vectorize index name");const c=e.chunkSize??Fe,u=e.chunkOverlap??Le;if(!Number.isInteger(c)||c<1)throw new w("BAD_REQUEST","@lunora/ai/rag: `chunkSize` must be a positive integer");if(!Number.isInteger(u)||u<0||u>=c)throw new w("BAD_REQUEST","@lunora/ai/rag: `chunkOverlap` must be a non-negative integer smaller than `chunkSize`");const m=me-je;if(!e.chunk&&!e.textStore&&!e.store&&c>m)throw new w("BAD_REQUEST",`@lunora/ai/rag: \`chunkSize\` of ${String(c)} leaves no room under Vectorize's ${String(me)}-byte metadata limit, which also carries this chunk's bookkeeping and any \`metadata\` you attach — keep it under ${String(m)}, or supply \`textStore\` to move chunk text out of metadata entirely`);const h=e.topK??ze;if(!Number.isInteger(h)||h<1)throw new w("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 w("BAD_REQUEST","@lunora/ai/rag: `maxEmbeddingDimensions` must be a positive integer, or `false` to disable the check");if(e.embeddingModelVersion!==void 0&&!Ze.test(e.embeddingModelVersion))throw new w("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 w("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 w("BAD_REQUEST","@lunora/ai/rag: `cacheEmbeddings` must be a non-negative integer");const E=e.cacheEmbeddings??0,A=e.rerank,U=e.chunk??(p=>$e(p,c,u)),{textStore:N}=e,Q=e.embeddingModelVersion,z=p=>Q===void 0?p:p===void 0?Q:`${Q}::${p}`;return p=>{const x=e.store?e.store(p):Ve(rt(p.vectors),e.index),Z=N?x.capabilities.maxTopK:x.capabilities.maxTopKWithMetadata,F=e.maxEmbeddingDimensions??x.capabilities.maxDimensions;let B;const X=typeof p.trace=="function"?p.trace:void 0,J=typeof p.metrics?.count=="function"?p.metrics:void 0;let G=F===!1;const ee=(t,n)=>{if(G||(G=!0,F===!1||t<=F))return;const a=L(n);throw new w("BAD_REQUEST",`@lunora/ai/rag: embedding model${a===void 0?"":` "${a}"`} produces ${String(t)}-dimension vectors, over the ${String(F)}-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`)},$=new Map,Ee=(t,n)=>{if(E!==0)for($.set(t,n);$.size>E;){const a=$.keys().next();if(a.done===!0)break;$.delete(a.value)}},te=async t=>{const n=$.get(t);if(n!==void 0)return n;B??=ge(e.embeddingModel,p.ai);const a=B,i=async r=>{const l=await fe(),{embedding:g,providerMetadata:d,usage:v}=await l.embed({model:a,value:t});return ee(g.length,a),de(L(a),{providerMetadata:d,usage:{inputTokens:{total:v.tokens}}},r,J),Ee(t,g),g};if(X===void 0)return i();const s=L(a),o=typeof p.conversationId=="string"&&p.conversationId.length>0?p.conversationId:void 0;return X("ai.embed",(r,l)=>i(l),{"gen_ai.operation.name":"embeddings",...s===void 0?{}:{"gen_ai.request.model":s},...o===void 0?{}:{"gen_ai.conversation.id":o}})},xe=async(t,n,a)=>{if(!e.transformQuery||n?.transformQuery===!1)return[t];const i=typeof p.conversationId=="string"&&p.conversationId.length>0?p.conversationId:void 0,s=await e.transformQuery(t,{conversationId:i,namespace:a}),o=(typeof s=="string"?[s]:[...s]).map(r=>r.trim()).filter(r=>r.length>0);return o.length>0?o:[t]},Ie=async t=>{const n=new Map,a=[...new Set(t.filter(i=>!$.has(i)))];if(a.length<2)return n;B??=ge(e.embeddingModel,p.ai);try{const i=await fe(),{embeddings:s,providerMetadata:o,usage:r}=await i.embedMany({model:B,values:a});if(de(L(B),{providerMetadata:o,usage:{inputTokens:{total:r.tokens}}},void 0,J),s.length!==a.length)return n;const[l]=s;l!==void 0&&ee(l.length,B);for(const[g,d]of a.entries())n.set(d,s[g])}catch(i){if(Ce(i))throw i}return n},P=t=>{if(t===void 0){if(e.requireNamespace)throw new w("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||tt(e.index)}},ne=async(t,n)=>{const[a]=await x.getByIds([O(n,t,0)],n),i=a?.metadata?.[q],s=a?.metadata?.[W];return{chunks:typeof s=="number"&&Number.isInteger(s)&&s>0?s:void 0,hash:typeof i=="string"?i:void 0}},re=async(t,n,a,i,s)=>{const o=Array.from({length:a-n},(l,g)=>O(i,t,n+g));if(o.length===0)return;const r=async()=>{await N?.remove?.(o,{namespace:i}),await e.lexicalStore?.remove?.(o,{namespace:i})};if(s===void 0||!x.capabilities.deferredDelete){await x.deleteByIds(o,i),await r();return}await x.deleteByIds(o,i,s,r)},Se=async t=>{if(P(t.namespace),t.importance!==void 0&&(typeof t.importance!="number"||t.importance<0||t.importance>1))throw new w("BAD_REQUEST","@lunora/ai/rag: `importance` must be a number in [0, 1]");const n=z(t.namespace),a=Je(t),i=await Xe(a??t.text),s=await ne(t.id,n);if(t.reindex!==!0&&a!==void 0&&s.hash===i&&s.chunks!==void 0)return{chunks:s.chunks,ids:Array.from({length:s.chunks},(b,S)=>O(n,t.id,S)),unchanged:!0};const o=U(t.text),r=o.map((b,S)=>O(n,t.id,S)),l=r.at(-1);if(l!==void 0&&We(l,t.id,x.capabilities.maxIdBytes),o.length===0&&t.allowEmptySources===!1)throw new w("BAD_REQUEST",`@lunora/ai/rag: source "${t.id}" produced zero chunks — set allowEmptySources: true to allow this`);if(o.length>0){const b=o.map((S,k)=>({chunkIndex:k,id:r[k],sourceId:t.id,text:S,...t.metadata===void 0?{}:{metadata:t.metadata}}));N&&await N.put(b,{namespace:n}),e.lexicalStore&&await e.lexicalStore.index(b,{namespace:n})}const g=await Ie(o),d=async b=>g.get(b)??await te(b),v=o.map((b,S)=>{const k=r[S],T={...t.metadata,[we]:S,[be]:t.id};return N||(T[K]=b),t.importance!==void 0&&(T[V]=t.importance),S===0&&(T[q]=i,T[W]=o.length,Q!==void 0&&(T[ve]=Q)),qe(T,S,t.id,x.capabilities.maxMetadataBytes),{embed:d,id:k,input:b,metadata:T,namespace:n}}),y=b=>{t.onChunk?.({chunkIndex:b,id:r[b],text:o[b],total:o.length})},I={};return await Ye(x,v,y,I),s.chunks!==void 0&&s.chunks>o.length&&await re(t.id,o.length,s.chunks,n,I),{chunks:o.length,ids:r,unchanged:!1}},ke=async t=>{P(t.namespace);const n=z(t.namespace),i=(await ne(t.id,n)).chunks??1;await re(t.id,0,i,n)},ae=async(t,n)=>{const a=new Map;if(t.length===0)return a;if(N){const s=await N.getMany(t,{namespace:n});for(const[o,r]of t.entries()){const l=s[o];typeof l=="string"&&a.set(r,l)}return a}const i=await x.getByIds(t,n);for(const s of i){const o=s.metadata?.[K];typeof o=="string"&&a.set(s.id,o)}return a},Te=async(t,n,a)=>{const i=n?.chunkContext?.before??0,s=n?.chunkContext?.after??0;if(i===0&&s===0)return t;if(!Number.isInteger(i)||i<0||!Number.isInteger(s)||s<0)throw new w("BAD_REQUEST","@lunora/ai/rag: `chunkContext.before`/`chunkContext.after` must be non-negative integers");const o=new Map(t.map(d=>[d.id,d.text])),r=new Set;for(const d of t)for(let v=-i;v<=s;v+=1){const y=d.chunkIndex+v,I=O(a,d.sourceId,y);v!==0&&y>=0&&!o.has(I)&&r.add(I)}const l=await ae([...r],a),g=(d,v)=>{const y=O(a,d,v);return o.get(y)??l.get(y)};return t.map(d=>{const v=[];for(let y=-i;y<=s;y+=1){const I=y===0?d.text:g(d.sourceId,d.chunkIndex+y);I!==void 0&&v.push(I)}return{...d,text:v.join(`
8
+ `)}})},_e=t=>{if(typeof t=="string"){const n=e.filters!==void 0&&Object.hasOwn(e.filters,t)?e.filters[t]:void 0;if(!n)throw new w("NOT_FOUND",`@lunora/ai/rag: unknown named filter "${t}" — must be one of the keys declared in RagConfig.filters`);return n.filter}return t},Me=(t,n)=>t.matches.map(a=>{const i=a.metadata??{},s=he(a.id,n),o=i[K],r=i[V],l=typeof r=="number"&&r>=0&&r<=1?r:1;return{chunkIndex:s.chunkIndex,id:a.id,importance:l,metadata:H(i),score:a.score*l,sourceId:s.sourceId,text:typeof o=="string"?o:""}}),Ae=async(t,n)=>{if(!N)return t;const a=t.map(r=>r.id),[i,s]=await Promise.all([ae(a,n),x.getByIds(a,n)]),o=new Map(s.map(r=>[r.id,r.metadata]));return t.flatMap(r=>{const l=i.get(r.id);if(l===void 0)return[];const g=o.get(r.id),d=g?.[V],v=typeof d=="number"&&d>=0&&d<=1?d:r.importance,I=(r.importance===0?0:r.score/r.importance)*v;return[{...r,importance:v,metadata:H(g)??r.metadata,score:I,text:l}]})},se=async(t,n,a)=>{const i=t.map(r=>r.id).filter(r=>!n.has(r)),s=i.length===0?[]:await x.getByIds(i,a),o=new Map(s.map(r=>[r.id,r.metadata]));return t.map(r=>{const l=he(r.id,a),g=o.get(r.id),d=g?.[V];return{chunkIndex:l.chunkIndex,id:r.id,importance:typeof d=="number"&&d>=0&&d<=1?d:1,metadata:H(g),score:r.score,sourceId:l.sourceId,text:r.text}})},oe=async(t,n)=>{P(n?.namespace);const a=z(n?.namespace),i=_e(n?.filter),s=e.rlsFilter?await e.rlsFilter(p.auth):void 0,o=s?{...i,...s}:i,r=Math.min(n?.topK??h,Z),l=await xe(t,n,a),g=l[0],d=A!==void 0&&n?.rerank!==!1,y=d||e.lexicalStore!==void 0||e.graphStore!==void 0||l.length>1?Math.min(e.candidates??r*Pe,Z):r,I=n?.minScore,b=new Set,S=async f=>{const R=await x.query({embed:te,filter:o,input:f,namespace:a,returnMetadata:N?"indexed":"all",topK:y}),C=await Ae(Me(R,a),a);if(I===void 0)return C;const{kept:M,rejectedIds:D}=Ge(C,I);for(const Ne of D)b.add(Ne);return M},k=[{chunks:await S(g)}];for(const f of l.slice(1))k.push({chunks:await S(f)});if(e.lexicalStore){const f=await e.lexicalStore.search(g,{filter:o,namespace:a,topK:e.lexicalTopK??y}),R=new Set(k.flatMap(M=>M.chunks).map(M=>M.id)),C=f.filter(M=>!b.has(M.id)||R.has(M.id));k.push({chunks:await se(C,R,a)})}const T=k.flatMap(f=>[...f.chunks]);if(e.graphStore&&T.length>0&&(e.graphStore.enforcesFilter||!et(o))){const f=[...new Set(T.map(D=>D.sourceId))],R=await e.graphStore.related(f,{filter:o,namespace:a,topK:e.graphTopK??y}),C=new Set(T.map(D=>D.id)),M=R.filter(D=>!b.has(D.id)||C.has(D.id));k.push({chunks:await se(M,C,a),weight:"proximity"})}const j=k.filter(f=>f.chunks.length>0);let _=j.length>1?[...Qe(j)]:[...j[0]?.chunks??[]];_.sort((f,R)=>R.score-f.score),d&&(_=[...await A(g,_)]),_=_.slice(0,r),_=[...await Te(_,n,a)];const ie=[],ce=new Set;for(const f of _)ce.has(f.sourceId)||(ce.add(f.sourceId),ie.push({id:f.sourceId,metadata:f.metadata,weight:f.importance}));return n?.onRetrieve?.({matches:_.length,query:t}),{chunks:_,context:nt(_),sources:ie}};return{asTool:t=>Re({description:t?.description??`Search the "${e.index}" knowledge base for passages relevant to a natural-language query.`,execute:async({query:n})=>oe(n,{namespace:t?.namespace,topK:t?.topK}),inputSchema:De({properties:{query:{description:"The natural-language search query.",type:"string"}},required:["query"],type:"object"})}),index:Se,remove:ke,retrieve:oe}}};export{pt 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{stableStringify as l}from"@lunora/wire";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 i=u(d);return{...o(d,a),...i===void 0?{}:{metadata:i},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),i=e(a.previous),s=o(a.doc,a.id),n=a.previous===void 0?s:o(a.previous,a.id),w=n.id!==s.id||n.namespace!==s.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===i&&!h)return;await(t===void 0?r(d,{deleted:!0,...s}):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 M from"./matchesMetadataFilter-BbIOyA5g.mjs";import{p as I,a as D,r as R,c as V,i as x}from"./sql-D5aqEMCY.mjs";import{LunoraError as B}from"@lunora/errors";const q=500,_=4096,A=(T,i)=>JSON.stringify([T,i]),U=T=>{const{dimensions:i,exec:c,table:w}=T,E=`${w}_ann`,f=`${w}_ann_ready`,u=(r,t)=>{if(r!==i)throw new B("RAG_DIMENSION_MISMATCH",`@lunora/ai/rag: ${t} is ${String(r)}-dimension but the \`ann\` index is ${String(i)}-dimension — set \`ann.dimensions\` to the embedding model's width, or reindex into a new \`table\` after changing models`)},g=async(r,t,o)=>{await c(`INSERT INTO ${E} (ref, namespace, embedding, id) VALUES (?, ?, ?, ?)`,[A(r,t),r,o,t])},S=async()=>{let r=0;for(;;){const t=await c(`SELECT rowid, id, namespace, vector FROM ${w} WHERE rowid > ? ORDER BY rowid LIMIT ?`,[r,q]);for(const o of t)u(JSON.parse(String(o.vector)).length,`stored vector "${String(o.id)}"`),await g(String(o.namespace),String(o.id),String(o.vector));if(t.length<q)return;r=Number(t.at(-1)?.rowid)}};return{ensure:async()=>{if((await c("SELECT name FROM sqlite_master WHERE name = ?",[f])).length>0){const[t]=await c(`SELECT dimensions FROM ${f}`,[]);u(Number(t?.dimensions),"the existing index");return}await c(`DROP TABLE IF EXISTS ${E}`,[]);try{await c(`CREATE VIRTUAL TABLE ${E} USING vec0(ref TEXT PRIMARY KEY, namespace TEXT PARTITION KEY, embedding FLOAT[${String(i)}] 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 c(`CREATE TABLE ${f} (dimensions INTEGER NOT NULL)`,[]),await c(`INSERT INTO ${f} (dimensions) VALUES (?)`,[i])},nearest:async(r,t,o)=>(u(t.length,"the query embedding"),(await c(`SELECT id, distance FROM ${E} WHERE embedding MATCH ? AND k = ? AND namespace = ?`,[JSON.stringify([...t]),Math.min(o,_),r])).map(v=>({distance:Number(v.distance),id:String(v.id)}))),put:async(r,t,o)=>{u(o.length,`the embedding for "${t}"`),await c(`DELETE FROM ${E} WHERE ref = ?`,[A(r,t)]),await g(r,t,JSON.stringify([...o]))},remove:async(r,t)=>{await c(`DELETE FROM ${E} WHERE ref IN (${I(t.length)})`,t.map(o=>A(r,o)))}}},X="lunora_rag_vectors",K=5e4,F=100,H=[4,16],W=null,h=T=>T??"",k=T=>{if(typeof T.exec!="function")throw new TypeError("@lunora/ai/rag: sqliteVectorStore requires an `exec` function");const i=D(T.table??X,"sqliteVectorStore `table`"),c=T.maxScan??K,{ann:w,exec:E}=T;if(w!==void 0&&(!Number.isInteger(w.dimensions)||w.dimensions<=0))throw new TypeError("@lunora/ai/rag: sqliteVectorStore `ann.dimensions` must be a positive integer");const f=w===void 0?void 0:U({dimensions:w.dimensions,exec:E,table:i}),u={deferredDelete:!0,maxDimensions:T.maxDimensions??w?.dimensions??!1,maxIdBytes:!1,maxMetadataBytes:!1,maxTopK:F,maxTopKWithMetadata:F};let g;const S=async()=>{g??=(async()=>{await E(`CREATE TABLE IF NOT EXISTS ${i} (id TEXT NOT NULL, namespace TEXT NOT NULL DEFAULT '', vector TEXT NOT NULL, metadata TEXT, PRIMARY KEY (namespace, id))`,[]),await E(`CREATE INDEX IF NOT EXISTS ${i}_namespace ON ${i} (namespace)`,[]),await f?.ensure()})().catch(e=>{throw g=void 0,e}),await g},O=async e=>{if(await S(),!e.embed)throw new TypeError("@lunora/ai/rag: sqliteVectorStore requires an `embed` function on upsert");const s=await e.embed(e.input),a=h(e.namespace);await f?.put(a,e.id,s),await E(`INSERT INTO ${i} (id, namespace, vector, metadata) VALUES (${I(4)}) ON CONFLICT(namespace, id) DO UPDATE SET vector = excluded.vector, metadata = excluded.metadata`,[e.id,a,JSON.stringify([...s]),e.metadata===void 0?W:JSON.stringify(e.metadata)])},r=async(e,s)=>{if(await S(),e.length===0)return[];const a=[];for(const d of x(e)){const m=await E(`SELECT id, metadata FROM ${i} WHERE namespace = ? AND id IN (${I(d.length)})`,[h(s),...d]);for(const n of m){const l=R(n.metadata);a.push({id:String(n.id),...l===void 0?{}:{metadata:l}})}}return a},t=async(e,s,a,d)=>{const m=h(a.namespace),n=await e.nearest(m,s,d),l=await r(n.map(L=>L.id),m),N=new Map(l.map(L=>[L.id,L])),b=[];for(const{distance:L,id:$}of n){const y=N.get($);y===void 0||!M(y.metadata,a.filter)||b.push({id:$,score:1-L,...a.returnMetadata==="none"||y.metadata===void 0?{}:{metadata:y.metadata}})}const p=b.slice(0,a.topK??10);return{count:p.length,exhausted:n.length<Math.min(d,_),matches:p}},o=async(e,s,a)=>{const d=a.topK??10;if(a.filter===void 0){const{count:m,matches:n}=await t(e,s,a,d);return{count:m,matches:n}}for(const m of H){const{count:n,exhausted:l,matches:N}=await t(e,s,a,d*m);if(n>=d||l)return{count:n,matches:N}}};return{capabilities:u,deleteByIds:async(e,s,a,d)=>{if(await S(),e.length===0){await d?.();return}for(const m of x(e))await E(`DELETE FROM ${i} WHERE namespace = ? AND id IN (${I(m.length)})`,[h(s),...m]),await f?.remove(h(s),m);await d?.()},getByIds:r,query:async e=>{await S();let s;if(e.embed&&e.input!==void 0)s=await e.embed(e.input);else throw new TypeError("@lunora/ai/rag: sqliteVectorStore query requires both `input` and `embed`");if(f!==void 0){const n=await o(f,s,e);if(n!==void 0)return n}const a=await E(`SELECT id, vector, metadata FROM ${i} WHERE namespace = ? LIMIT ?`,[h(e.namespace),c+1]);if(a.length>c)throw new RangeError(`@lunora/ai/rag: sqliteVectorStore scanned ${String(a.length)} vectors in namespace "${h(e.namespace)}", over the ${String(c)} 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 d=[];for(const n of a){const l=R(n.metadata);if(!M(l,e.filter))continue;const N=R(n.vector);N!==void 0&&d.push({id:String(n.id),score:V(s,N),...e.returnMetadata==="none"||l===void 0?{}:{metadata:l}})}const m=d.toSorted((n,l)=>l.score-n.score).slice(0,e.topK??10);return{count:m.length,matches:m}},upsert:O}};export{k as sqliteVectorStore};