@lunora/ai 1.0.0-alpha.129 → 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.
package/dist/create.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as LunoraAiOptions, L as LunoraAi } from "./packem_shared/types.d-DL0j0z-W.mjs";
2
- export type { A as AiBindingLike, b as AiWebSearchItem, c as AiWebSearchProvider } from "./packem_shared/types.d-DL0j0z-W.mjs";
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
3
  import 'ai';
4
4
  import 'workers-ai-provider';
5
5
  /**
@@ -52,12 +52,14 @@ declare const createAi: (options: LunoraAiOptions) => LunoraAi;
52
52
  * Pass your own `prices` for anything you are actually invoicing against.
53
53
  * @experimental
54
54
  */
55
- /** What one model costs, in USD per **one million** tokens. */
55
+ /** What one model costs. Token models price per **one million** tokens; image models price per generated image. */
56
56
  interface ModelPrice {
57
- /** Price per million input (prompt) tokens. */
58
- input: number;
57
+ /** Price per million input (prompt) tokens. Token-billed models only. */
58
+ input?: number;
59
59
  /** Price per million output (completion) tokens. Omit for an embedding model. */
60
60
  output?: number;
61
+ /** Price per generated image, in USD. Image models only. */
62
+ perImage?: number;
61
63
  }
62
64
  /** Token counts to price. Either may be absent. */
63
65
  interface ModelUsage {
@@ -73,9 +75,10 @@ interface ModelUsage {
73
75
  * and OpenAI — and everything else returns `undefined` rather than a guess.
74
76
  *
75
77
  * 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.
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.
79
82
  */
80
83
  declare const DEFAULT_MODEL_PRICES: Readonly<Record<string, ModelPrice>>;
81
84
  /**
@@ -93,4 +96,14 @@ declare const lookupModelPrice: (modelId: string, prices?: Readonly<Record<strin
93
96
  * @experimental
94
97
  */
95
98
  declare const estimateModelCost: (modelId: string | undefined, usage: ModelUsage, prices?: Readonly<Record<string, ModelPrice>>) => number | undefined;
96
- export { DEFAULT_MODEL_PRICES as D, type LunoraAi, ModelPrice as M, ModelUsage as a, createAi, estimateModelCost, lookupModelPrice as l };
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 };
package/dist/create.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as LunoraAiOptions, L as LunoraAi } from "./packem_shared/types.d-DL0j0z-W.js";
2
- export type { A as AiBindingLike, b as AiWebSearchItem, c as AiWebSearchProvider } from "./packem_shared/types.d-DL0j0z-W.js";
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
3
  import 'ai';
4
4
  import 'workers-ai-provider';
5
5
  /**
@@ -52,12 +52,14 @@ declare const createAi: (options: LunoraAiOptions) => LunoraAi;
52
52
  * Pass your own `prices` for anything you are actually invoicing against.
53
53
  * @experimental
54
54
  */
55
- /** What one model costs, in USD per **one million** tokens. */
55
+ /** What one model costs. Token models price per **one million** tokens; image models price per generated image. */
56
56
  interface ModelPrice {
57
- /** Price per million input (prompt) tokens. */
58
- input: number;
57
+ /** Price per million input (prompt) tokens. Token-billed models only. */
58
+ input?: number;
59
59
  /** Price per million output (completion) tokens. Omit for an embedding model. */
60
60
  output?: number;
61
+ /** Price per generated image, in USD. Image models only. */
62
+ perImage?: number;
61
63
  }
62
64
  /** Token counts to price. Either may be absent. */
63
65
  interface ModelUsage {
@@ -73,9 +75,10 @@ interface ModelUsage {
73
75
  * and OpenAI — and everything else returns `undefined` rather than a guess.
74
76
  *
75
77
  * 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.
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.
79
82
  */
80
83
  declare const DEFAULT_MODEL_PRICES: Readonly<Record<string, ModelPrice>>;
81
84
  /**
@@ -93,4 +96,14 @@ declare const lookupModelPrice: (modelId: string, prices?: Readonly<Record<strin
93
96
  * @experimental
94
97
  */
95
98
  declare const estimateModelCost: (modelId: string | undefined, usage: ModelUsage, prices?: Readonly<Record<string, ModelPrice>>) => number | undefined;
96
- export { DEFAULT_MODEL_PRICES as D, type LunoraAi, ModelPrice as M, ModelUsage as a, createAi, estimateModelCost, lookupModelPrice as l };
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 };
package/dist/create.mjs CHANGED
@@ -1 +1 @@
1
- import{default as o}from"./packem_shared/createAi-Bufv-O1b.mjs";import{estimateModelCost as a}from"./packem_shared/DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs";export{o as createAi,a as estimateModelCost};
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,4 +1,4 @@
1
- export { D as DEFAULT_MODEL_PRICES, type M as ModelPrice, type a as ModelUsage, createAi, estimateModelCost, l as lookupModelPrice } from "./create.mjs";
2
- export { d as AI_DEFAULT_EMBEDDING_MODEL_ENV, e as AI_DEFAULT_MODEL_ENV, f as AI_GATEWAY_ACCOUNT_ID_ENV, g as AI_GATEWAY_ID_ENV, h as AI_GATEWAY_METADATA_MAX_KEYS, i as AI_GATEWAY_TAGS_ENV, j as AI_GATEWAY_TOKEN_ENV, k as AI_PROXY_TOKEN_ENV, l as AI_PROXY_URL_ENV, type A as AiBindingLike, type m as AiGatewayMetadata, type n as AiGatewayOptions, type o as AiMetrics, type p as AiModelOptions, type q as AiRunOptions, type r as AiSpan, type s as AiTelemetry, type t as AiTracer, type b as AiWebSearchItem, type u as AiWebSearchOptions, type c as AiWebSearchProvider, type v 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, w as buildAiGatewayMetadataFields, x as readAiGatewayEnvTags, y as resolveAiGateway } from "./packem_shared/types.d-DL0j0z-W.mjs";
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
3
  export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, hasToolCall, jsonSchema, streamObject, streamText, tool } from 'ai';
4
4
  export { createWorkersAI } from 'workers-ai-provider';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { D as DEFAULT_MODEL_PRICES, type M as ModelPrice, type a as ModelUsage, createAi, estimateModelCost, l as lookupModelPrice } from "./create.js";
2
- export { d as AI_DEFAULT_EMBEDDING_MODEL_ENV, e as AI_DEFAULT_MODEL_ENV, f as AI_GATEWAY_ACCOUNT_ID_ENV, g as AI_GATEWAY_ID_ENV, h as AI_GATEWAY_METADATA_MAX_KEYS, i as AI_GATEWAY_TAGS_ENV, j as AI_GATEWAY_TOKEN_ENV, k as AI_PROXY_TOKEN_ENV, l as AI_PROXY_URL_ENV, type A as AiBindingLike, type m as AiGatewayMetadata, type n as AiGatewayOptions, type o as AiMetrics, type p as AiModelOptions, type q as AiRunOptions, type r as AiSpan, type s as AiTelemetry, type t as AiTracer, type b as AiWebSearchItem, type u as AiWebSearchOptions, type c as AiWebSearchProvider, type v 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, w as buildAiGatewayMetadataFields, x as readAiGatewayEnvTags, y as resolveAiGateway } from "./packem_shared/types.d-DL0j0z-W.js";
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
3
  export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, hasToolCall, jsonSchema, streamObject, streamText, tool } from 'ai';
4
4
  export { createWorkersAI } from 'workers-ai-provider';
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{default as _}from"./packem_shared/createAi-Bufv-O1b.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};
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 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
+ 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};
@@ -1,4 +1,4 @@
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-GPnTSqR8.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
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
2
  app this shares one tenant's chunks (text included) with every other tenant, since
3
3
  Vectorize indexes are account-global. Pass \`namespace\` (the shard/tenant key) on both
4
4
  index() and retrieve(). Single-tenant apps suppress this via { allowSharedNamespace: true }.`))},nt=e=>e.map(c=>`[source:${c.sourceId}#${String(c.chunkIndex)}]
@@ -188,6 +188,14 @@ declare const resolveAiGateway: (env: Record<string, unknown>, metadata?: AiGate
188
188
  */
189
189
  interface AiBindingLike {
190
190
  run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
191
+ /**
192
+ * Markdown Conversion API (beta) — converts a document (PDF, Office file,
193
+ * HTML, image) to Markdown. Optional because runtimes and test doubles that
194
+ * predate it have no such method; `ctx.ai.toMarkdown` reports that instead
195
+ * of a bare `TypeError`. Mirrors `env.AI.toMarkdown(files, options)` at
196
+ * https://developers.cloudflare.com/workers-ai/features/markdown-conversion/.
197
+ */
198
+ toMarkdown?: (files: AiMarkdownDocument | AiMarkdownDocument[], options?: Record<string, unknown>) => Promise<AiMarkdownConversionResult | AiMarkdownConversionResult[]>;
191
199
  /**
192
200
  * Web Search API (beta). Optional because runtimes and test doubles that
193
201
  * predate it have no such method; `ctx.ai.websearch` reports that instead of
@@ -198,6 +206,109 @@ interface AiBindingLike {
198
206
  query: string;
199
207
  }) => Promise<Response>;
200
208
  }
209
+ /**
210
+ * One document handed to `ctx.ai.toMarkdown(...)`: the Workers AI
211
+ * `MarkdownDocument` shape (a name plus a Blob carrying the bytes).
212
+ * @experimental
213
+ */
214
+ interface AiMarkdownDocument {
215
+ blob: Blob;
216
+ name: string;
217
+ }
218
+ /**
219
+ * One Workers AI `ConversionResult`. `data` and `tokens` are present only when
220
+ * `format` is `"markdown"` (or `"text"`); an `"error"` result carries `error`
221
+ * instead. `pages` is not in the current binding contract — carried here so a
222
+ * binding that starts reporting it passes through unchanged.
223
+ * @experimental
224
+ */
225
+ interface AiMarkdownConversionResult {
226
+ data?: string;
227
+ error?: string;
228
+ format: "error" | "markdown" | "text";
229
+ id?: string;
230
+ /** The detected document mime type (spelled `mimetype` by the binding). */
231
+ mimetype?: string;
232
+ name?: string;
233
+ /** Page count, when the binding reports it. */
234
+ pages?: number;
235
+ tokens?: number;
236
+ }
237
+ /**
238
+ * What `ctx.ai.toMarkdown(...)` accepts: a `Blob`, raw bytes, or a `URL` that
239
+ * is fetched first.
240
+ * @experimental
241
+ */
242
+ type AiToMarkdownInput = ArrayBuffer | Blob | Uint8Array | URL;
243
+ /**
244
+ * Options for `ctx.ai.toMarkdown(...)`.
245
+ * @experimental
246
+ */
247
+ interface AiToMarkdownOptions {
248
+ /** Conversion options forwarded verbatim to the binding's `toMarkdown(files, options)`. */
249
+ conversionOptions?: Record<string, unknown>;
250
+ /**
251
+ * Byte cap for a `URL`/raw-bytes input, guarding against an accidental huge
252
+ * download. Defaults to 20 MiB. A `Blob` is checked against the same cap.
253
+ */
254
+ maxBytes?: number;
255
+ /** MIME type for a raw `ArrayBuffer`/`Uint8Array` input (a `Blob`/`URL` carries its own). */
256
+ mimeType?: string;
257
+ /** Document name sent to the binding; defaults to the URL's last path segment, else `"document"`. */
258
+ name?: string;
259
+ }
260
+ /**
261
+ * What `ctx.ai.toMarkdown(...)` resolves to — the first (and only) converted
262
+ * document, normalised across the binding's single/array return.
263
+ * @experimental
264
+ */
265
+ interface AiToMarkdownResult {
266
+ markdown: string;
267
+ /** The detected mime type of the source document, when the binding reports it. */
268
+ mimeType?: string;
269
+ /** Page count, when the binding reports it. */
270
+ pages?: number;
271
+ /** The binding's estimated token count for the converted Markdown, when reported. */
272
+ tokens?: number;
273
+ }
274
+ /**
275
+ * One generated image: base64 bytes (`base64`), a URL, or the provider's media
276
+ * type. Which fields are present depends on the provider.
277
+ * @experimental
278
+ */
279
+ interface AiImage {
280
+ base64?: string;
281
+ mimeType?: string;
282
+ url?: string;
283
+ }
284
+ /**
285
+ * Input for `ctx.ai.images.generate(...)`.
286
+ * @experimental
287
+ */
288
+ interface AiImageGenerateInput {
289
+ /** How many images to generate. Defaults to 1. */
290
+ count?: number;
291
+ /**
292
+ * A model id: a Workers AI image model (`@cf/black-forest-labs/flux-1-schnell`),
293
+ * or an `"openai/<model>"` / `"google/<model>"` slug routed through the AI
294
+ * Gateway. Any other slug throws `AI_UNKNOWN_IMAGE_MODEL`.
295
+ */
296
+ model: string;
297
+ prompt: string;
298
+ /** Seed for reproducible generation, for providers that support it. */
299
+ seed?: number;
300
+ /** Image size as `"<width>x<height>"`, e.g. `"1024x1024"`. */
301
+ size?: `${number}x${number}`;
302
+ }
303
+ /**
304
+ * What `ctx.ai.images.generate(...)` resolves to.
305
+ * @experimental
306
+ */
307
+ interface AiImageGenerateResult {
308
+ images: AiImage[];
309
+ /** The model id the caller asked for (the unmodified `input.model`). */
310
+ model: string;
311
+ }
201
312
  /**
202
313
  * A Workers AI provider instance — the value returned by `createWorkersAI(...)`.
203
314
  * Calling it with a model id yields an AI SDK {@link LanguageModel}; the
@@ -447,6 +558,16 @@ type EmbeddingModelInput = EmbeddingModel | string;
447
558
  interface LunoraAi {
448
559
  /** Resolve an {@link EmbeddingModel}: a string → Workers AI, an object → passthrough. */
449
560
  embeddingModel: (model?: EmbeddingModelInput) => EmbeddingModel;
561
+ /**
562
+ * Generate images. A Workers AI image model id (`@cf/…`) runs through the
563
+ * binding (and the AI Gateway when configured); an `"openai/<model>"` or
564
+ * `"google/<model>"` slug routes to that provider's image endpoint through
565
+ * the AI Gateway. Any other slug throws `AI_UNKNOWN_IMAGE_MODEL`; a
566
+ * Workers AI id without a binding throws the same directed error `run` does.
567
+ */
568
+ images: {
569
+ generate: (input: AiImageGenerateInput) => Promise<AiImageGenerateResult>;
570
+ };
450
571
  /**
451
572
  * Resolve a {@link LanguageModel}: a `@cf/…` id → Workers AI, a
452
573
  * `"<provider>/<model>"` slug or `dynamic/<route>` → Cloudflare AI Gateway
@@ -462,6 +583,13 @@ interface LunoraAi {
462
583
  * supplied.
463
584
  */
464
585
  run: (model: string, inputs: Record<string, unknown>, options?: AiRunOptions) => Promise<unknown>;
586
+ /**
587
+ * Convert a document (PDF, Office file, HTML, image) to Markdown through the
588
+ * binding's Markdown Conversion API (beta). Accepts a `Blob`, raw bytes or a
589
+ * `URL` (fetched first). Throws if no binding was supplied, or if the
590
+ * runtime's binding has no `toMarkdown()`.
591
+ */
592
+ toMarkdown: (input: AiToMarkdownInput, options?: AiToMarkdownOptions) => Promise<AiToMarkdownResult>;
465
593
  /**
466
594
  * Search the web through the binding's Web Search API (beta) to ground a
467
595
  * response in live information. Billed to a stored provider key or AI Gateway
@@ -479,4 +607,4 @@ interface LunoraAi {
479
607
  */
480
608
  workersai: WorkersAiProviderLike;
481
609
  }
482
- export { AiBindingLike as A, EmbeddingModelInput as E, LunoraAi as L, ModelInput as M, ResolvedAiGateway as R, WorkersAiProviderLike as W, LunoraAiOptions as a, AiWebSearchItem as b, AiWebSearchProvider as c, AI_DEFAULT_EMBEDDING_MODEL_ENV as d, AI_DEFAULT_MODEL_ENV as e, AI_GATEWAY_ACCOUNT_ID_ENV as f, AI_GATEWAY_ID_ENV as g, AI_GATEWAY_METADATA_MAX_KEYS as h, AI_GATEWAY_TAGS_ENV as i, AI_GATEWAY_TOKEN_ENV as j, AI_PROXY_TOKEN_ENV as k, AI_PROXY_URL_ENV as l, AiGatewayMetadata as m, AiGatewayOptions as n, AiMetrics as o, AiModelOptions as p, AiRunOptions as q, AiSpan as r, AiTelemetry as s, AiTracer as t, AiWebSearchOptions as u, AiWebSearchResult as v, buildAiGatewayMetadataFields as w, readAiGatewayEnvTags as x, resolveAiGateway as y };
610
+ export { AiBindingLike as A, AiTelemetry as B, AiTracer as C, AiWebSearchOptions as D, EmbeddingModelInput as E, AiWebSearchResult as F, buildAiGatewayMetadataFields as G, readAiGatewayEnvTags as H, resolveAiGateway as I, LunoraAi as L, ModelInput as M, ResolvedAiGateway as R, WorkersAiProviderLike as W, LunoraAiOptions as a, AiImage as b, AiImageGenerateInput as c, AiImageGenerateResult as d, AiToMarkdownInput as e, AiToMarkdownOptions as f, AiToMarkdownResult as g, AiWebSearchItem as h, AiWebSearchProvider as i, AI_DEFAULT_EMBEDDING_MODEL_ENV as j, AI_DEFAULT_MODEL_ENV as k, AI_GATEWAY_ACCOUNT_ID_ENV as l, AI_GATEWAY_ID_ENV as m, AI_GATEWAY_METADATA_MAX_KEYS as n, AI_GATEWAY_TAGS_ENV as o, AI_GATEWAY_TOKEN_ENV as p, AI_PROXY_TOKEN_ENV as q, AI_PROXY_URL_ENV as r, AiGatewayMetadata as s, AiGatewayOptions as t, AiMarkdownConversionResult as u, AiMarkdownDocument as v, AiMetrics as w, AiModelOptions as x, AiRunOptions as y, AiSpan as z };
@@ -188,6 +188,14 @@ declare const resolveAiGateway: (env: Record<string, unknown>, metadata?: AiGate
188
188
  */
189
189
  interface AiBindingLike {
190
190
  run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
191
+ /**
192
+ * Markdown Conversion API (beta) — converts a document (PDF, Office file,
193
+ * HTML, image) to Markdown. Optional because runtimes and test doubles that
194
+ * predate it have no such method; `ctx.ai.toMarkdown` reports that instead
195
+ * of a bare `TypeError`. Mirrors `env.AI.toMarkdown(files, options)` at
196
+ * https://developers.cloudflare.com/workers-ai/features/markdown-conversion/.
197
+ */
198
+ toMarkdown?: (files: AiMarkdownDocument | AiMarkdownDocument[], options?: Record<string, unknown>) => Promise<AiMarkdownConversionResult | AiMarkdownConversionResult[]>;
191
199
  /**
192
200
  * Web Search API (beta). Optional because runtimes and test doubles that
193
201
  * predate it have no such method; `ctx.ai.websearch` reports that instead of
@@ -198,6 +206,109 @@ interface AiBindingLike {
198
206
  query: string;
199
207
  }) => Promise<Response>;
200
208
  }
209
+ /**
210
+ * One document handed to `ctx.ai.toMarkdown(...)`: the Workers AI
211
+ * `MarkdownDocument` shape (a name plus a Blob carrying the bytes).
212
+ * @experimental
213
+ */
214
+ interface AiMarkdownDocument {
215
+ blob: Blob;
216
+ name: string;
217
+ }
218
+ /**
219
+ * One Workers AI `ConversionResult`. `data` and `tokens` are present only when
220
+ * `format` is `"markdown"` (or `"text"`); an `"error"` result carries `error`
221
+ * instead. `pages` is not in the current binding contract — carried here so a
222
+ * binding that starts reporting it passes through unchanged.
223
+ * @experimental
224
+ */
225
+ interface AiMarkdownConversionResult {
226
+ data?: string;
227
+ error?: string;
228
+ format: "error" | "markdown" | "text";
229
+ id?: string;
230
+ /** The detected document mime type (spelled `mimetype` by the binding). */
231
+ mimetype?: string;
232
+ name?: string;
233
+ /** Page count, when the binding reports it. */
234
+ pages?: number;
235
+ tokens?: number;
236
+ }
237
+ /**
238
+ * What `ctx.ai.toMarkdown(...)` accepts: a `Blob`, raw bytes, or a `URL` that
239
+ * is fetched first.
240
+ * @experimental
241
+ */
242
+ type AiToMarkdownInput = ArrayBuffer | Blob | Uint8Array | URL;
243
+ /**
244
+ * Options for `ctx.ai.toMarkdown(...)`.
245
+ * @experimental
246
+ */
247
+ interface AiToMarkdownOptions {
248
+ /** Conversion options forwarded verbatim to the binding's `toMarkdown(files, options)`. */
249
+ conversionOptions?: Record<string, unknown>;
250
+ /**
251
+ * Byte cap for a `URL`/raw-bytes input, guarding against an accidental huge
252
+ * download. Defaults to 20 MiB. A `Blob` is checked against the same cap.
253
+ */
254
+ maxBytes?: number;
255
+ /** MIME type for a raw `ArrayBuffer`/`Uint8Array` input (a `Blob`/`URL` carries its own). */
256
+ mimeType?: string;
257
+ /** Document name sent to the binding; defaults to the URL's last path segment, else `"document"`. */
258
+ name?: string;
259
+ }
260
+ /**
261
+ * What `ctx.ai.toMarkdown(...)` resolves to — the first (and only) converted
262
+ * document, normalised across the binding's single/array return.
263
+ * @experimental
264
+ */
265
+ interface AiToMarkdownResult {
266
+ markdown: string;
267
+ /** The detected mime type of the source document, when the binding reports it. */
268
+ mimeType?: string;
269
+ /** Page count, when the binding reports it. */
270
+ pages?: number;
271
+ /** The binding's estimated token count for the converted Markdown, when reported. */
272
+ tokens?: number;
273
+ }
274
+ /**
275
+ * One generated image: base64 bytes (`base64`), a URL, or the provider's media
276
+ * type. Which fields are present depends on the provider.
277
+ * @experimental
278
+ */
279
+ interface AiImage {
280
+ base64?: string;
281
+ mimeType?: string;
282
+ url?: string;
283
+ }
284
+ /**
285
+ * Input for `ctx.ai.images.generate(...)`.
286
+ * @experimental
287
+ */
288
+ interface AiImageGenerateInput {
289
+ /** How many images to generate. Defaults to 1. */
290
+ count?: number;
291
+ /**
292
+ * A model id: a Workers AI image model (`@cf/black-forest-labs/flux-1-schnell`),
293
+ * or an `"openai/<model>"` / `"google/<model>"` slug routed through the AI
294
+ * Gateway. Any other slug throws `AI_UNKNOWN_IMAGE_MODEL`.
295
+ */
296
+ model: string;
297
+ prompt: string;
298
+ /** Seed for reproducible generation, for providers that support it. */
299
+ seed?: number;
300
+ /** Image size as `"<width>x<height>"`, e.g. `"1024x1024"`. */
301
+ size?: `${number}x${number}`;
302
+ }
303
+ /**
304
+ * What `ctx.ai.images.generate(...)` resolves to.
305
+ * @experimental
306
+ */
307
+ interface AiImageGenerateResult {
308
+ images: AiImage[];
309
+ /** The model id the caller asked for (the unmodified `input.model`). */
310
+ model: string;
311
+ }
201
312
  /**
202
313
  * A Workers AI provider instance — the value returned by `createWorkersAI(...)`.
203
314
  * Calling it with a model id yields an AI SDK {@link LanguageModel}; the
@@ -447,6 +558,16 @@ type EmbeddingModelInput = EmbeddingModel | string;
447
558
  interface LunoraAi {
448
559
  /** Resolve an {@link EmbeddingModel}: a string → Workers AI, an object → passthrough. */
449
560
  embeddingModel: (model?: EmbeddingModelInput) => EmbeddingModel;
561
+ /**
562
+ * Generate images. A Workers AI image model id (`@cf/…`) runs through the
563
+ * binding (and the AI Gateway when configured); an `"openai/<model>"` or
564
+ * `"google/<model>"` slug routes to that provider's image endpoint through
565
+ * the AI Gateway. Any other slug throws `AI_UNKNOWN_IMAGE_MODEL`; a
566
+ * Workers AI id without a binding throws the same directed error `run` does.
567
+ */
568
+ images: {
569
+ generate: (input: AiImageGenerateInput) => Promise<AiImageGenerateResult>;
570
+ };
450
571
  /**
451
572
  * Resolve a {@link LanguageModel}: a `@cf/…` id → Workers AI, a
452
573
  * `"<provider>/<model>"` slug or `dynamic/<route>` → Cloudflare AI Gateway
@@ -462,6 +583,13 @@ interface LunoraAi {
462
583
  * supplied.
463
584
  */
464
585
  run: (model: string, inputs: Record<string, unknown>, options?: AiRunOptions) => Promise<unknown>;
586
+ /**
587
+ * Convert a document (PDF, Office file, HTML, image) to Markdown through the
588
+ * binding's Markdown Conversion API (beta). Accepts a `Blob`, raw bytes or a
589
+ * `URL` (fetched first). Throws if no binding was supplied, or if the
590
+ * runtime's binding has no `toMarkdown()`.
591
+ */
592
+ toMarkdown: (input: AiToMarkdownInput, options?: AiToMarkdownOptions) => Promise<AiToMarkdownResult>;
465
593
  /**
466
594
  * Search the web through the binding's Web Search API (beta) to ground a
467
595
  * response in live information. Billed to a stored provider key or AI Gateway
@@ -479,4 +607,4 @@ interface LunoraAi {
479
607
  */
480
608
  workersai: WorkersAiProviderLike;
481
609
  }
482
- export { AiBindingLike as A, EmbeddingModelInput as E, LunoraAi as L, ModelInput as M, ResolvedAiGateway as R, WorkersAiProviderLike as W, LunoraAiOptions as a, AiWebSearchItem as b, AiWebSearchProvider as c, AI_DEFAULT_EMBEDDING_MODEL_ENV as d, AI_DEFAULT_MODEL_ENV as e, AI_GATEWAY_ACCOUNT_ID_ENV as f, AI_GATEWAY_ID_ENV as g, AI_GATEWAY_METADATA_MAX_KEYS as h, AI_GATEWAY_TAGS_ENV as i, AI_GATEWAY_TOKEN_ENV as j, AI_PROXY_TOKEN_ENV as k, AI_PROXY_URL_ENV as l, AiGatewayMetadata as m, AiGatewayOptions as n, AiMetrics as o, AiModelOptions as p, AiRunOptions as q, AiSpan as r, AiTelemetry as s, AiTracer as t, AiWebSearchOptions as u, AiWebSearchResult as v, buildAiGatewayMetadataFields as w, readAiGatewayEnvTags as x, resolveAiGateway as y };
610
+ export { AiBindingLike as A, AiTelemetry as B, AiTracer as C, AiWebSearchOptions as D, EmbeddingModelInput as E, AiWebSearchResult as F, buildAiGatewayMetadataFields as G, readAiGatewayEnvTags as H, resolveAiGateway as I, LunoraAi as L, ModelInput as M, ResolvedAiGateway as R, WorkersAiProviderLike as W, LunoraAiOptions as a, AiImage as b, AiImageGenerateInput as c, AiImageGenerateResult as d, AiToMarkdownInput as e, AiToMarkdownOptions as f, AiToMarkdownResult as g, AiWebSearchItem as h, AiWebSearchProvider as i, AI_DEFAULT_EMBEDDING_MODEL_ENV as j, AI_DEFAULT_MODEL_ENV as k, AI_GATEWAY_ACCOUNT_ID_ENV as l, AI_GATEWAY_ID_ENV as m, AI_GATEWAY_METADATA_MAX_KEYS as n, AI_GATEWAY_TAGS_ENV as o, AI_GATEWAY_TOKEN_ENV as p, AI_PROXY_TOKEN_ENV as q, AI_PROXY_URL_ENV as r, AiGatewayMetadata as s, AiGatewayOptions as t, AiMarkdownConversionResult as u, AiMarkdownDocument as v, AiMetrics as w, AiModelOptions as x, AiRunOptions as y, AiSpan as z };
@@ -0,0 +1 @@
1
+ import{estimateImageCost as d,estimateModelCost as f}from"./DEFAULT_MODEL_PRICES-C5oaHRiN.mjs";const g=t=>typeof t=="number"&&Number.isFinite(t)&&t>=0?t:void 0,p=t=>{if(!(typeof t!="object"||t===null)){for(const o of Object.values(t))if(typeof o=="object"&&o!==null){const{cost:e}=o;if(typeof e=="number"&&Number.isFinite(e))return e}}},b=t=>{const o=t.modelId;return typeof o=="string"&&o.length>0?o:void 0},l=(t,o,e,s)=>{const n=g(o.usage?.inputTokens?.total),u=g(o.usage?.outputTokens?.total),c=p(o.providerMetadata),i=c??f(t,{inputTokens:n,outputTokens:u}),a=c===void 0?"estimated":"provider",r={"gen_ai.request.model":t??"unknown"};n!==void 0&&(e?.setAttribute("gen_ai.usage.input_tokens",n),s?.count("gen_ai.usage.input_tokens",n,r)),u!==void 0&&(e?.setAttribute("gen_ai.usage.output_tokens",u),s?.count("gen_ai.usage.output_tokens",u,r)),i!==void 0&&(e?.setAttributes({"gen_ai.usage.cost":i,"lunora.usage.cost.source":a}),s?.count("gen_ai.usage.cost",i,{...r,"lunora.usage.cost.source":a}))},m=(t,o,e)=>{const s=d(t,o);s!==void 0&&e?.count("gen_ai.usage.cost",s,{"gen_ai.request.model":t,"lunora.usage.cost.source":"estimated"})};export{m as a,b as m,l as r};
@@ -1,5 +1,5 @@
1
1
  import { Tool } from 'ai';
2
- import { E as EmbeddingModelInput, L as LunoraAi } from "../packem_shared/types.d-DL0j0z-W.mjs";
2
+ import { E as EmbeddingModelInput, L as LunoraAi } from "../packem_shared/types.d-BG8Olmuo.mjs";
3
3
  /**
4
4
  * Built-in fixed-window chunker: split into `size`-char windows overlapping by
5
5
  * `overlap` chars. Deliberately simple and deterministic — the zero-config
@@ -1,5 +1,5 @@
1
1
  import { Tool } from 'ai';
2
- import { E as EmbeddingModelInput, L as LunoraAi } from "../packem_shared/types.d-DL0j0z-W.js";
2
+ import { E as EmbeddingModelInput, L as LunoraAi } from "../packem_shared/types.d-BG8Olmuo.js";
3
3
  /**
4
4
  * Built-in fixed-window chunker: split into `size`-char windows overlapping by
5
5
  * `overlap` chars. Deliberately simple and deterministic — the zero-config
@@ -1 +1 @@
1
- import{default as o}from"../packem_shared/fixedWindowChunks-C461ahRE.mjs";import{markdownChunker as a,sentenceChunker as f,tokenChunker as n}from"../packem_shared/markdownChunker-iW8V4klY.mjs";import{default as x}from"../packem_shared/defineRag-CJudmmcj.mjs";import{contentHash as p,guessMimeTypeFromExtension as i}from"../packem_shared/contentHash-BIn6ECP8.mjs";import{hybridRank as d}from"../packem_shared/hybridRank-B4skyCLx.mjs";import{default as k}from"../packem_shared/bm25LexicalStore-DMUzAL0O.mjs";import{default as h}from"../packem_shared/matchesMetadataFilter-BbIOyA5g.mjs";import{batchReranker as g,scoreReranker as C}from"../packem_shared/batchReranker-Bc38FBLH.mjs";import{defineRagSource as E}from"../packem_shared/defineRagSource-Q3f3niU8.mjs";import{sqlLexicalStore as T}from"../packem_shared/sqlLexicalStore-4C_cIwef.mjs";import{sqliteVectorStore as y}from"../packem_shared/sqliteVectorStore-f24ySqtJ.mjs";import{ragSyncTriggers as q}from"../packem_shared/ragSyncTriggers-D933fCwt.mjs";import{VECTORIZE_CAPABILITIES as A,vectorizeStore as F}from"../packem_shared/VECTORIZE_CAPABILITIES-l9ceRO5j.mjs";export{A as VECTORIZE_CAPABILITIES,g as batchReranker,k as bm25LexicalStore,p as contentHash,x as defineRag,E as defineRagSource,o as fixedWindowChunks,i as guessMimeTypeFromExtension,d as hybridRank,a as markdownChunker,h as matchesMetadataFilter,q as ragSyncTriggers,C as scoreReranker,f as sentenceChunker,T as sqlLexicalStore,y as sqliteVectorStore,n as tokenChunker,F as vectorizeStore};
1
+ import{default as o}from"../packem_shared/fixedWindowChunks-C461ahRE.mjs";import{markdownChunker as a,sentenceChunker as f,tokenChunker as n}from"../packem_shared/markdownChunker-iW8V4klY.mjs";import{default as x}from"../packem_shared/defineRag-BOS3IQeP.mjs";import{contentHash as p,guessMimeTypeFromExtension as i}from"../packem_shared/contentHash-BIn6ECP8.mjs";import{hybridRank as d}from"../packem_shared/hybridRank-B4skyCLx.mjs";import{default as k}from"../packem_shared/bm25LexicalStore-DMUzAL0O.mjs";import{default as h}from"../packem_shared/matchesMetadataFilter-BbIOyA5g.mjs";import{batchReranker as g,scoreReranker as C}from"../packem_shared/batchReranker-Bc38FBLH.mjs";import{defineRagSource as E}from"../packem_shared/defineRagSource-Q3f3niU8.mjs";import{sqlLexicalStore as T}from"../packem_shared/sqlLexicalStore-4C_cIwef.mjs";import{sqliteVectorStore as y}from"../packem_shared/sqliteVectorStore-f24ySqtJ.mjs";import{ragSyncTriggers as q}from"../packem_shared/ragSyncTriggers-D933fCwt.mjs";import{VECTORIZE_CAPABILITIES as A,vectorizeStore as F}from"../packem_shared/VECTORIZE_CAPABILITIES-l9ceRO5j.mjs";export{A as VECTORIZE_CAPABILITIES,g as batchReranker,k as bm25LexicalStore,p as contentHash,x as defineRag,E as defineRagSource,o as fixedWindowChunks,i as guessMimeTypeFromExtension,d as hybridRank,a as markdownChunker,h as matchesMetadataFilter,q as ragSyncTriggers,C as scoreReranker,f as sentenceChunker,T as sqlLexicalStore,y as sqliteVectorStore,n as tokenChunker,F as vectorizeStore};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/ai",
3
- "version": "1.0.0-alpha.129",
3
+ "version": "1.0.0-alpha.130",
4
4
  "description": "Workers AI helper for Lunora: provider-agnostic AI SDK access from functions, Workers AI by default",
5
5
  "keywords": [
6
6
  "ai",
@@ -58,6 +58,7 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "@ai-sdk/anthropic": "4.0.71",
61
+ "@ai-sdk/google": "4.0.87",
61
62
  "@ai-sdk/openai": "4.0.83",
62
63
  "@ai-sdk/provider-utils": "5.0.53",
63
64
  "@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
@@ -1 +0,0 @@
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};
@@ -1 +0,0 @@
1
- import"./DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs";import{LunoraError as g}from"@lunora/errors";import{buildAiGatewayMetadataFields as W,readEnv as A,AI_DEFAULT_MODEL_ENV as S,AI_DEFAULT_EMBEDDING_MODEL_ENV as x,AI_PROXY_URL_ENV as b,AI_PROXY_TOKEN_ENV as C,readAiGatewayEnvTags as X,AI_GATEWAY_ID_ENV as z,warnIgnoredBindingToken as Z}from"./AI_DEFAULT_EMBEDDING_MODEL_ENV-IwcHZiKj.mjs";import{m as J,r as O}from"./usage-GPnTSqR8.mjs";const Q={setAttribute:()=>{},setAttributes:()=>{}},G=async(e,t)=>t(G,Q),ee=(e,{metrics:t,trace:a=G})=>{const s={"gen_ai.operation.name":"chat","gen_ai.request.model":e};return{wrapGenerate:async({doGenerate:r})=>a("ai.generate",async(d,l)=>{const o=await r();return O(e,o,l,t),o},s),wrapStream:async({doStream:r})=>{const d=(c,w)=>{const E=c.stream.getReader();let h={};return{...c,stream:new ReadableStream({cancel:async v=>{w({outcome:h}),await E.cancel(v)},pull:async v=>{let f;try{f=await E.read()}catch(y){w({error:y,failed:!0,outcome:h}),v.error(y);return}if(f.done){w({outcome:h}),v.close();return}f.value.type==="finish"&&(h={providerMetadata:f.value.providerMetadata,usage:f.value.usage}),v.enqueue(f.value)}})}},l=Promise.withResolvers(),o=Promise.withResolvers();return a("ai.stream",async(c,w)=>{l.resolve(d(await r(),o.resolve));const E=await o.promise;if(O(e,E.outcome,w,t),E.failed===!0)throw E.error},s).catch(l.reject),l.promise}}},U=(e,t,a)=>{if(t===void 0||typeof e=="string")return e;const s=ee(a??J(e)??"unknown",t);let r;const d=async()=>(r??=import("ai").then(({wrapLanguageModel:o})=>o({middleware:s,model:e}),o=>{throw r=void 0,o}),r),l=e;return{doGenerate:async o=>d().then(c=>c.doGenerate(o)),doStream:async o=>d().then(c=>c.doStream(o)),modelId:l.modelId,provider:l.provider,specificationVersion:"v4",get supportedUrls(){return d().then(o=>o.supportedUrls)}}};let N;const B=()=>(N??=Promise.all([import("@ai-sdk/openai"),import("workers-ai-provider/openai"),import("workers-ai-provider/anthropic")]).then(([e,{openai:t},{anthropic:a}])=>({createOpenAI:e.createOpenAI,plugins:[t,a]}),e=>{throw N=void 0,e}),N),$=(e,t,a)=>({doGenerate:async s=>a().then(r=>r.doGenerate(s)),doStream:async s=>a().then(r=>r.doStream(s)),modelId:e,provider:t,specificationVersion:"v4",get supportedUrls(){return a().then(s=>s.supportedUrls)}}),te={maxEmbeddingsPerCall:3e3,supportsParallelCalls:!0},V=(e,t,a,s)=>({doEmbed:async r=>a().then(d=>d.doEmbed(r)),get maxEmbeddingsPerCall(){return s?.maxEmbeddingsPerCall??a().then(r=>r.maxEmbeddingsPerCall)},modelId:e,provider:t,specificationVersion:"v4",get supportsParallelCalls(){return s?.supportsParallelCalls??a().then(r=>r.supportsParallelCalls)}});let R;const k=()=>(R??=import("workers-ai-provider").catch(e=>{throw R=void 0,e}),R),_=e=>!e.startsWith("@")&&e.includes("/"),re=e=>{const t=(a,s)=>$(a,"workersai.chat",async()=>e(a).then(r=>r(a,s)));return t.textEmbeddingModel=a=>V(a,"workersai.embedding",async()=>e(a).then(s=>s.textEmbeddingModel?.(a)),_(a)?void 0:te),t},I=e=>{throw new g("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 ${b} to an OpenAI-compatible proxy for "<provider>/<model>" slugs.`)},F=()=>I("this model id");F.textEmbeddingModel=()=>I("this embedding model id");const ae=3040,ne=/^(?:[A-Z]\w*:\s*)?3040\s*:/iu,oe=e=>e?.code===ae||e instanceof Error&&ne.test(e.message),se="default",ie=new Map([[401,"UNAUTHORIZED"],[403,"FORBIDDEN"],[404,"NOT_FOUND"],[408,"SERVICE_UNAVAILABLE"],[429,"RATE_LIMITED"]]),de=e=>ie.get(e)??(e>=400&&e<500?"BAD_REQUEST":"SERVICE_UNAVAILABLE"),ce=300,le=new Set(["127.0.0.1","[::1]","localhost"]),ue=e=>{const t=A(e,b);if(t===void 0)return;const a=A(e,C),s=URL.canParse(t)?new URL(t):void 0;let r;if(s===void 0?r=`${b} is not a valid URL`:a!==void 0&&s.protocol!=="https:"&&!le.has(s.hostname)&&(r=`${b} (${s.origin}) is not HTTPS, so ${C} would travel in cleartext — use an https:// URL`),r!==void 0){const o=()=>{throw new g("INTERNAL",`@lunora/ai: ${r}`)};return{chat:o,embedding:o}}let d;const l=()=>(d??=B().then(({createOpenAI:o})=>o({apiKey:a??"",baseURL:t,name:"lunora-proxy"}),o=>{throw d=void 0,o}),d);return{chat:o=>$(o,"lunora-proxy",async()=>l().then(c=>c.chat(o))),embedding:o=>V(o,"lunora-proxy",async()=>l().then(c=>c.embedding(o)))}},we=(e,t)=>{const a=e===void 0?void 0:X(e);return a===void 0?t:{...t,tags:{...a,...t?.tags}}},he=(e,t,a)=>{const s=W(a);if(e!==void 0)return s!==void 0&&e.metadata===void 0?{...e,metadata:s}:e;if(t===void 0)return;const r=A(t,z);if(r!==void 0)return Z(t),s===void 0?{id:r}:{id:r,metadata:s}},be=e=>{const{binding:t,defaultEmbeddingModel:a,defaultModel:s,env:r,gateway:d,metadata:l,provider:o,telemetry:c}=e,w=ue(r),E=we(r,l),h=he(d,r,E),v=d?.metadata===void 0?W(E):void 0,f=s??A(r,S),y=a??A(r,x);let T;const j=()=>(T??=k().then(({createWorkersAI:n})=>n({binding:t,gateway:h})).catch(n=>{throw T=void 0,n}),T);let M;const H=()=>(M??=Promise.all([k(),B()]).then(([{createWorkersAI:n},{plugins:i}])=>n({binding:t,gateway:h,providers:i})).catch(n=>{throw M=void 0,n}),M),m=o??(t?re(async n=>_(n)?H():j()):F),K=(n,i)=>_(n)?w!==void 0?w.chat(n):v===void 0?m(n):m(n,{metadata:v}):i?.rejectIfBusy===void 0?m(n):m(n,{rejectIfBusy:i.rejectIfBusy}),Y=(n,i)=>{const u=n??f;if(u===void 0||u==="")throw new g("INTERNAL",`@lunora/ai: no model supplied and no default configured — pass a model id, or set ${S} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return typeof u=="string"?U(K(u,i),c,u):U(u,c)},q=n=>{if(w!==void 0&&_(n))return w.embedding(n);const i=m.textEmbeddingModel;if(typeof i!="function")throw new g("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(m,n)};return{embeddingModel:n=>{if(typeof n=="object")return n;const i=n??y;if(!i)throw new g("INTERNAL",`@lunora/ai: no embedding model supplied and no default configured — pass an embedding model id or an AI SDK EmbeddingModel, or set ${x} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return q(i)},model:Y,run:async(n,i,u)=>{if(!t)return I("ai.run");const L=h!==void 0&&u?.gateway===void 0?{...u,gateway:h}:u;try{return await t.run(n,i,L)}catch(p){throw oe(p)?new g("RATE_LIMITED",`@lunora/ai: Workers AI has no free capacity for ${n} (error 3040) — retry later`,{cause:p}):p}},websearch:async(n,i)=>{if(!t)return I("ai.websearch");if(typeof t.websearch!="function")throw new g("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:u,...L}=i??{},p=await t.websearch({...L,gatewayId:u??h?.id??se,query:n});if(!p.ok){const P=(await p.text().catch(()=>"")).slice(0,ce);throw new g(de(p.status),`@lunora/ai: web search failed with HTTP ${String(p.status)}${P===""?"":`: ${P}`}`)}const D=await p.json().catch(()=>{});if(!Array.isArray(D?.items))throw new g("INTERNAL","@lunora/ai: web search returned a body that is not JSON with an `items` array");return D},workersai:m}};export{be as default};
@@ -1 +0,0 @@
1
- import{estimateModelCost as d}from"./DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs";const a=t=>typeof t=="number"&&Number.isFinite(t)&&t>=0?t:void 0,f=t=>{if(!(typeof t!="object"||t===null)){for(const o of Object.values(t))if(typeof o=="object"&&o!==null){const{cost:e}=o;if(typeof e=="number"&&Number.isFinite(e))return e}}},b=t=>{const o=t.modelId;return typeof o=="string"&&o.length>0?o:void 0},l=(t,o,e,u)=>{const s=a(o.usage?.inputTokens?.total),n=a(o.usage?.outputTokens?.total),c=f(o.providerMetadata),i=c??d(t,{inputTokens:s,outputTokens:n}),g=c===void 0?"estimated":"provider",r={"gen_ai.request.model":t??"unknown"};s!==void 0&&(e?.setAttribute("gen_ai.usage.input_tokens",s),u?.count("gen_ai.usage.input_tokens",s,r)),n!==void 0&&(e?.setAttribute("gen_ai.usage.output_tokens",n),u?.count("gen_ai.usage.output_tokens",n,r)),i!==void 0&&(e?.setAttributes({"gen_ai.usage.cost":i,"lunora.usage.cost.source":g}),u?.count("gen_ai.usage.cost",i,{...r,"lunora.usage.cost.source":g}))};export{b as m,l as r};