@lunora/ai 1.0.0-alpha.26 → 1.0.0-alpha.28

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/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-F_Q8R8L3.mjs";
2
- export { A as AI_GATEWAY_ACCOUNT_ID_ENV, b as AI_GATEWAY_ID_ENV, c as AI_GATEWAY_TOKEN_ENV, type d as AiBindingLike, type e as AiGatewayMetadata, type f as AiGatewayOptions, type E as EmbeddingModelInput, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, g as buildAiGatewayMetadataFields, r as resolveAiGateway } from "./packem_shared/types.d-F_Q8R8L3.mjs";
1
+ import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-BcLGTChd.mjs";
2
+ export { A as AI_GATEWAY_ACCOUNT_ID_ENV, b as AI_GATEWAY_ID_ENV, c as AI_GATEWAY_TOKEN_ENV, type d as AiBindingLike, type e as AiGatewayMetadata, type f as AiGatewayOptions, type E as EmbeddingModelInput, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, g as buildAiGatewayMetadataFields, r as resolveAiGateway } from "./packem_shared/types.d-BcLGTChd.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';
5
5
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-F_Q8R8L3.js";
2
- export { A as AI_GATEWAY_ACCOUNT_ID_ENV, b as AI_GATEWAY_ID_ENV, c as AI_GATEWAY_TOKEN_ENV, type d as AiBindingLike, type e as AiGatewayMetadata, type f as AiGatewayOptions, type E as EmbeddingModelInput, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, g as buildAiGatewayMetadataFields, r as resolveAiGateway } from "./packem_shared/types.d-F_Q8R8L3.js";
1
+ import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-BcLGTChd.js";
2
+ export { A as AI_GATEWAY_ACCOUNT_ID_ENV, b as AI_GATEWAY_ID_ENV, c as AI_GATEWAY_TOKEN_ENV, type d as AiBindingLike, type e as AiGatewayMetadata, type f as AiGatewayOptions, type E as EmbeddingModelInput, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, g as buildAiGatewayMetadataFields, r as resolveAiGateway } from "./packem_shared/types.d-BcLGTChd.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';
5
5
  /**
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{default as a}from"./packem_shared/createAi-k_AcrXgH.mjs";import{AI_GATEWAY_ACCOUNT_ID_ENV as o,AI_GATEWAY_ID_ENV as A,AI_GATEWAY_TOKEN_ENV as _,buildAiGatewayMetadataFields as m,resolveAiGateway as l}from"./packem_shared/AI_GATEWAY_ACCOUNT_ID_ENV-JzYYnjLP.mjs";import{embed as T,embedMany as E,generateObject as d,generateText as x,hasToolCall as I,jsonSchema as b,streamObject as c,streamText as f,tool as i}from"ai";import{createWorkersAI as N}from"workers-ai-provider";export{o as AI_GATEWAY_ACCOUNT_ID_ENV,A as AI_GATEWAY_ID_ENV,_ as AI_GATEWAY_TOKEN_ENV,m as buildAiGatewayMetadataFields,a as createAi,N as createWorkersAI,T as embed,E as embedMany,d as generateObject,x as generateText,I as hasToolCall,b as jsonSchema,l as resolveAiGateway,c as streamObject,f as streamText,i as tool};
1
+ import{default as a}from"./packem_shared/createAi-C2ExoUDR.mjs";import{AI_GATEWAY_ACCOUNT_ID_ENV as o,AI_GATEWAY_ID_ENV as A,AI_GATEWAY_TOKEN_ENV as _,buildAiGatewayMetadataFields as m,resolveAiGateway as l}from"./packem_shared/AI_GATEWAY_ACCOUNT_ID_ENV-CPzc-So1.mjs";import{embed as T,embedMany as E,generateObject as d,generateText as x,hasToolCall as I,jsonSchema as b,streamObject as c,streamText as f,tool as i}from"ai";import{createWorkersAI as N}from"workers-ai-provider";export{o as AI_GATEWAY_ACCOUNT_ID_ENV,A as AI_GATEWAY_ID_ENV,_ as AI_GATEWAY_TOKEN_ENV,m as buildAiGatewayMetadataFields,a as createAi,N as createWorkersAI,T as embed,E as embedMany,d as generateObject,x as generateText,I as hasToolCall,b as jsonSchema,l as resolveAiGateway,c as streamObject,f as streamText,i as tool};
@@ -0,0 +1 @@
1
+ const r=(t,a)=>{const n=t[a];return typeof n=="string"&&n.length>0?n:void 0};let c=!1;const u=t=>{if(t===void 0)return;const a={};return typeof t.functionPath=="string"&&t.functionPath.length>0&&(a.functionPath=t.functionPath),typeof t.traceId=="string"&&t.traceId.length>0&&(a.traceId=t.traceId),Object.keys(a).length>0?a:void 0},h=t=>{const a=u(t);return a===void 0?void 0:JSON.stringify(a)},I="LUNORA_AI_GATEWAY_ACCOUNT_ID",_="LUNORA_AI_GATEWAY_ID",A="LUNORA_AI_GATEWAY_TOKEN",f=(t,a,n="byo-provider")=>{const o=r(t,I),e=r(t,_);if(o===void 0||e===void 0)return;const s=r(t,A),i={};s!==void 0&&(i["cf-aig-authorization"]=`Bearer ${s}`,n==="workers-ai-binding"&&!c&&(c=!0,console.warn(`[lunora:ai] ${A} 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.`)));const d=h(a);return d!==void 0&&(i["cf-aig-metadata"]=d),{accountId:o,baseURL:`https://gateway.ai.cloudflare.com/v1/${o}/${e}`,gatewayId:e,headers:i}};export{I as AI_GATEWAY_ACCOUNT_ID_ENV,_ as AI_GATEWAY_ID_ENV,A as AI_GATEWAY_TOKEN_ENV,u as buildAiGatewayMetadataFields,f as resolveAiGateway};
@@ -0,0 +1 @@
1
+ import{LunoraError as t}from"@lunora/errors";import{createWorkersAI as c}from"workers-ai-provider";import{buildAiGatewayMetadataFields as v,resolveAiGateway as A}from"./AI_GATEWAY_ACCOUNT_ID_ENV-CPzc-So1.mjs";const I=(i,o,s)=>{const r=v(s);if(i!==void 0)return r!==void 0&&i.metadata===void 0?{...i,metadata:r}:i;if(o===void 0)return;const a=A(o,s,"workers-ai-binding");if(a!==void 0)return r===void 0?{id:a.gatewayId}:{id:a.gatewayId,metadata:r}},y=(i,o)=>c({binding:i,gateway:o}),h=i=>{const{binding:o,defaultEmbeddingModel:s,defaultModel:r,env:a,gateway:f,metadata:g,provider:m}=i;if(!m&&!o)throw new t("INTERNAL","@lunora/ai: createAi requires a `binding` (env.AI) or a pre-built `provider`");const u=I(f,a,g),n=m??y(o,u),p=e=>{if(e===void 0){if(!r)throw new t("INTERNAL","@lunora/ai: no model supplied and no `defaultModel` configured — pass a model id or an AI SDK model");return n(r)}return typeof e=="string"?n(e):e},w=e=>{const d=n.textEmbeddingModel;if(typeof d!="function")throw new t("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 d.call(n,e)};return{embeddingModel:e=>{if(typeof e=="object")return e;const d=e??s;if(!d)throw new t("INTERNAL","@lunora/ai: no embedding model supplied and no `defaultEmbeddingModel` configured — pass an embedding model id or an AI SDK EmbeddingModel");return w(d)},model:p,run:async(e,d,l)=>{if(!o)throw new t("INTERNAL","@lunora/ai: ai.run requires the `binding` (env.AI) — it is unavailable when only a custom `provider` was supplied");const b=u!==void 0&&l?.gateway===void 0?{...l,gateway:u}:l;return o.run(e,d,b)},workersai:n}};export{h as default};
@@ -1,8 +1,8 @@
1
- import{LunoraError as x}from"@lunora/errors";import{tool as G,jsonSchema as J,embed as ee}from"ai";import te from"./fixedWindowChunks-XJRXHEoz.mjs";import ne from"./hybridRank-U6PmGuz1.mjs";const ae=8,re=async(e,s,h)=>{if(!Number.isInteger(s)||s<1)throw new RangeError("concurrentMap: `limit` must be a positive integer");if(e.length===0)return[];const b=Math.max(1,Math.min(s,e.length)),y=Array.from({length:e.length});let f=0,k=!1,v;const E=async()=>{for(;;){if(k)return;const I=f;if(f+=1,I>=e.length)return;try{y[I]=await h(e[I],I)}catch(N){k||(k=!0,v=N);return}}},m=Array.from({length:b},()=>E());if(await Promise.all(m),k)throw v;return y},oe=1e3,ie=200,se=5,ce=20,de=100,z="__ragChunk",V="__ragSource",A="__ragText",R="__ragHash",T="__ragChunks",M="__ragImportance",F="__ragModel",ue=new Set([z,T,R,M,F,V,A]),le=/^[\w.-]{1,40}$/,P=e=>e===void 0?"":`${encodeURIComponent(e)}#`,_=(e,s,h)=>`${P(e)}${s}#${String(h)}`,K=(e,s)=>{const h=P(s),b=h!==""&&e.startsWith(h)?e.slice(h.length):e,y=b.lastIndexOf("#"),f=y===-1?Number.NaN:Number(b.slice(y+1));return y===-1||!Number.isInteger(f)||f<0?{chunkIndex:0,sourceId:b}:{chunkIndex:f,sourceId:b.slice(0,y)}},me=async e=>{const s=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e));return[...new Uint8Array(s)].map(h=>h.toString(16).padStart(2,"0")).join("")},O=e=>{if(!e)return;const s=Object.entries(e).filter(([h])=>!ue.has(h));return s.length>0?Object.fromEntries(s):void 0},Q=new Set,he=e=>{Q.has(e)||(Q.add(e),console.warn(`[@lunora/ai/rag] index "${e}" is used without a namespace — in a multi-tenant/sharded
1
+ import{LunoraError as x}from"@lunora/errors";import{tool as G,jsonSchema as J,embed as ee}from"ai";import te from"./fixedWindowChunks-XJRXHEoz.mjs";import ne from"./hybridRank-U6PmGuz1.mjs";const ae=8,re=async(e,s,p)=>{if(!Number.isInteger(s)||s<1)throw new RangeError("concurrentMap: `limit` must be a positive integer");if(e.length===0)return[];const b=Math.max(1,Math.min(s,e.length)),y=Array.from({length:e.length});let f=0,k=!1,v;const E=async()=>{for(;;){if(k)return;const I=f;if(f+=1,I>=e.length)return;try{y[I]=await p(e[I],I)}catch(N){k||(k=!0,v=N);return}}},h=Array.from({length:b},()=>E());if(await Promise.all(h),k)throw v;return y},oe=1e3,ie=200,se=5,ce=20,de=100,z="__ragChunk",V="__ragSource",A="__ragText",R="__ragHash",T="__ragChunks",M="__ragImportance",F="__ragModel",ue=new Set([z,T,R,M,F,V,A]),me=/^[\w.-]{1,40}$/,P=e=>e===void 0?"":`${encodeURIComponent(e)}#`,_=(e,s,p)=>`${P(e)}${s}#${String(p)}`,K=(e,s)=>{const p=P(s),b=p!==""&&e.startsWith(p)?e.slice(p.length):e,y=b.lastIndexOf("#"),f=y===-1?Number.NaN:Number(b.slice(y+1));return y===-1||!Number.isInteger(f)||f<0?{chunkIndex:0,sourceId:b}:{chunkIndex:f,sourceId:b.slice(0,y)}},he=async e=>{const s=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e));return[...new Uint8Array(s)].map(p=>p.toString(16).padStart(2,"0")).join("")},O=e=>{if(!e)return;const s=Object.entries(e).filter(([p])=>!ue.has(p));return s.length>0?Object.fromEntries(s):void 0},Q=new Set,pe=e=>{Q.has(e)||(Q.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
- index() and retrieve(). Single-tenant apps suppress this via { allowSharedNamespace: true }.`))},pe=e=>e.map(s=>`[source:${s.sourceId}#${String(s.chunkIndex)}]
4
+ index() and retrieve(). Single-tenant apps suppress this via { allowSharedNamespace: true }.`))},le=e=>e.map(s=>`[source:${s.sourceId}#${String(s.chunkIndex)}]
5
5
  ${s.text}`).join(`
6
6
 
7
- `),ge=(e,s)=>{if(typeof e=="object")return e;if(s===void 0)throw new x("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 s.embeddingModel(e)},fe=e=>{const s=e.modelId;return typeof s=="string"&&s.length>0?s:void 0},we=e=>{if(!(typeof e!="object"||e===null)){for(const s of Object.values(e))if(typeof s=="object"&&s!==null){const{cost:h}=s;if(typeof h=="number"&&Number.isFinite(h))return h}}},Ie=e=>{if(typeof e.index!="string"||e.index.length===0)throw new x("BAD_REQUEST","@lunora/ai/rag: `index` must be a non-empty Vectorize index name");const s=e.chunkSize??oe,h=e.chunkOverlap??ie;if(!Number.isInteger(s)||s<1)throw new x("BAD_REQUEST","@lunora/ai/rag: `chunkSize` must be a positive integer");if(!Number.isInteger(h)||h<0||h>=s)throw new x("BAD_REQUEST","@lunora/ai/rag: `chunkOverlap` must be a non-negative integer smaller than `chunkSize`");const b=e.topK??se;if(!Number.isInteger(b)||b<1)throw new x("BAD_REQUEST","@lunora/ai/rag: `topK` must be a positive integer");if(e.embeddingModelVersion!==void 0&&!le.test(e.embeddingModelVersion))throw new x("BAD_REQUEST",'@lunora/ai/rag: `embeddingModelVersion` must match /^[A-Za-z0-9._-]{1,40}$/ (a short, stable tag like "bge-v1.5")');const y=e.chunk??(m=>te(m,s,h)),{textStore:f}=e,k=f?de:ce,v=e.embeddingModelVersion,E=m=>v===void 0?m:m===void 0?v:`${v}::${m}`;return m=>{let I;const N=typeof m.trace=="function"?m.trace:void 0,B=async t=>{I??=ge(e.embeddingModel,m.ai);const n=I,o=async d=>{const{embedding:r,providerMetadata:u,usage:l}=await ee({model:n,value:t});if(d!==void 0){const c=l.tokens;typeof c=="number"&&Number.isFinite(c)&&d.setAttribute("gen_ai.usage.input_tokens",c);const p=we(u);p!==void 0&&d.setAttribute("gen_ai.usage.cost",p)}return r};if(N===void 0)return o();const i=fe(n),a=typeof m.conversationId=="string"&&m.conversationId.length>0?m.conversationId:void 0;return N("ai.embed",(d,r)=>o(r),{"gen_ai.operation.name":"embeddings",...i===void 0?{}:{"gen_ai.request.model":i},...a===void 0?{}:{"gen_ai.conversation.id":a}})},$=t=>{if(t===void 0){if(e.requireNamespace)throw new x("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||he(e.index)}},U=async(t,n)=>{const[o]=await m.vectors.getByIds(e.index,[_(n,t,0)],n),i=o?.metadata?.[R],a=o?.metadata?.[T];return{chunks:typeof a=="number"&&Number.isInteger(a)&&a>0?a:void 0,hash:typeof i=="string"?i:void 0}},j=async(t,n,o,i)=>{const a=Array.from({length:o-n},(d,r)=>_(i,t,n+r));a.length!==0&&(await m.vectors.deleteByIds(e.index,a,i),await f?.remove?.(a,{namespace:i}),await e.lexicalStore?.remove?.(a,{namespace:i}))},W=async t=>{if($(t.namespace),t.importance!==void 0&&(typeof t.importance!="number"||t.importance<0||t.importance>1))throw new x("BAD_REQUEST","@lunora/ai/rag: `importance` must be a number in [0, 1]");const n=E(t.namespace),o=await me(t.text),i=await U(t.id,n);if(i.hash===o&&i.chunks!==void 0)return{chunks:i.chunks,ids:Array.from({length:i.chunks},(r,u)=>_(n,t.id,u)),unchanged:!0};const a=y(t.text),d=a.map((r,u)=>_(n,t.id,u));if(a.length===0&&t.allowEmptySources===!1)throw new x("BAD_REQUEST",`@lunora/ai/rag: source "${t.id}" produced zero chunks — set allowEmptySources: true to allow this`);if(a.length>0){const r=a.map((u,l)=>({chunkIndex:l,id:d[l],sourceId:t.id,text:u}));f&&await f.put(r,{namespace:n}),e.lexicalStore&&await e.lexicalStore.index(r,{namespace:n})}return await re(a,ae,async(r,u)=>{const l=d[u],c={...t.metadata,[z]:u,[V]:t.id};f||(c[A]=r),t.importance!==void 0&&(c[M]=t.importance),u===0&&(c[R]=o,c[T]=a.length,v!==void 0&&(c[F]=v)),await m.vectors.upsert(e.index,{embed:B,id:l,input:r,metadata:c,namespace:n}),t.onChunk?.({chunkIndex:u,id:l,text:r,total:a.length})}),i.chunks!==void 0&&i.chunks>a.length&&await j(t.id,a.length,i.chunks,n),{chunks:a.length,ids:d,unchanged:!1}},H=async t=>{$(t.namespace);const n=E(t.namespace),o=(await U(t.id,n)).chunks??1;await j(t.id,0,o,n)},q=async(t,n)=>{const o=new Map;if(t.length===0)return o;if(f){const a=await f.getMany(t,{namespace:n});for(const[d,r]of t.entries()){const u=a[d];typeof u=="string"&&o.set(r,u)}return o}const i=await m.vectors.getByIds(e.index,t,n);for(const a of i){const d=a.metadata?.[A];typeof d=="string"&&o.set(a.id,d)}return o},L=async(t,n,o)=>{const i=n?.chunkContext?.before??0,a=n?.chunkContext?.after??0;if(i===0&&a===0)return t;if(!Number.isInteger(i)||i<0||!Number.isInteger(a)||a<0)throw new x("BAD_REQUEST","@lunora/ai/rag: `chunkContext.before`/`chunkContext.after` must be non-negative integers");const d=new Map(t.map(c=>[c.id,c.text])),r=new Set;for(const c of t)for(let p=-i;p<=a;p+=1){const w=c.chunkIndex+p,g=_(o,c.sourceId,w);p!==0&&w>=0&&!d.has(g)&&r.add(g)}const u=await q([...r],o),l=(c,p)=>{const w=_(o,c,p);return d.get(w)??u.get(w)};return t.map(c=>{const p=[];for(let w=-i;w<=a;w+=1){const g=w===0?c.text:l(c.sourceId,c.chunkIndex+w);g!==void 0&&p.push(g)}return{...c,text:p.join(`
8
- `)}})},X=t=>{if(typeof t=="string"){const n=e.filters?.[t];if(!n)throw new x("NOT_FOUND",`@lunora/ai/rag: unknown named filter "${t}" — must be one of the keys declared in RagConfig.filters`);return n.filter}return t},Y=(t,n)=>t.matches.map(o=>{const i=o.metadata??{},a=K(o.id,n),d=i[A],r=i[M],u=typeof r=="number"&&r>=0&&r<=1?r:1;return{chunkIndex:a.chunkIndex,id:o.id,importance:u,metadata:O(i),score:o.score*u,sourceId:a.sourceId,text:typeof d=="string"?d:""}}),Z=async(t,n)=>{if(!f)return t;const o=t.map(r=>r.id),[i,a]=await Promise.all([q(o,n),m.vectors.getByIds(e.index,o,n)]),d=new Map(a.map(r=>[r.id,r.metadata]));return t.flatMap(r=>{const u=i.get(r.id);if(u===void 0)return[];const l=d.get(r.id),c=l?.[M],p=typeof c=="number"&&c>=0&&c<=1?c:r.importance,w=r.score/r.importance*p;return[{...r,importance:p,metadata:O(l)??r.metadata,score:w,text:u}]})},C=async(t,n)=>{$(n?.namespace);const o=E(n?.namespace),i=X(n?.filter),a=e.rlsFilter?await e.rlsFilter(m.auth):void 0,d=a?{...i,...a}:i,r=Math.min(n?.topK??b,k),u=await m.vectors.query(e.index,{embed:B,filter:d,input:t,namespace:o,returnMetadata:f?"indexed":"all",topK:r});let l=await Z(Y(u,o),o);const c=n?.minScore;if(c!==void 0&&(l=l.filter(g=>g.score>=c)),e.lexicalStore){const g=(await e.lexicalStore.search(t,{filter:d,namespace:o,topK:e.lexicalTopK??r})).map(S=>{const D=K(S.id,o);return{chunkIndex:D.chunkIndex,id:S.id,importance:1,metadata:void 0,score:S.score,sourceId:D.sourceId,text:S.text}});l=[...ne(l,g)]}l.sort((g,S)=>S.score-g.score),l=[...await L(l,n,o)];const p=[],w=new Set;for(const g of l)w.has(g.sourceId)||(w.add(g.sourceId),p.push({id:g.sourceId,metadata:g.metadata,weight:g.importance}));return n?.onRetrieve?.({matches:l.length,query:t}),{chunks:l,context:pe(l),sources:p}};return{asTool:t=>G({description:t?.description??`Search the "${e.index}" knowledge base for passages relevant to a natural-language query.`,execute:async({query:n})=>C(n,{namespace:t?.namespace,topK:t?.topK}),inputSchema:J({properties:{query:{description:"The natural-language search query.",type:"string"}},required:["query"],type:"object"})}),index:W,remove:H,retrieve:C}}};export{Ie as default};
7
+ `),ge=(e,s)=>{if(typeof e=="object")return e;if(s===void 0)throw new x("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 s.embeddingModel(e)},fe=e=>{const s=e.modelId;return typeof s=="string"&&s.length>0?s:void 0},we=e=>{if(!(typeof e!="object"||e===null)){for(const s of Object.values(e))if(typeof s=="object"&&s!==null){const{cost:p}=s;if(typeof p=="number"&&Number.isFinite(p))return p}}},Ie=e=>{if(typeof e.index!="string"||e.index.length===0)throw new x("BAD_REQUEST","@lunora/ai/rag: `index` must be a non-empty Vectorize index name");const s=e.chunkSize??oe,p=e.chunkOverlap??ie;if(!Number.isInteger(s)||s<1)throw new x("BAD_REQUEST","@lunora/ai/rag: `chunkSize` must be a positive integer");if(!Number.isInteger(p)||p<0||p>=s)throw new x("BAD_REQUEST","@lunora/ai/rag: `chunkOverlap` must be a non-negative integer smaller than `chunkSize`");const b=e.topK??se;if(!Number.isInteger(b)||b<1)throw new x("BAD_REQUEST","@lunora/ai/rag: `topK` must be a positive integer");if(e.embeddingModelVersion!==void 0&&!me.test(e.embeddingModelVersion))throw new x("BAD_REQUEST",'@lunora/ai/rag: `embeddingModelVersion` must match /^[A-Za-z0-9._-]{1,40}$/ (a short, stable tag like "bge-v1.5")');const y=e.chunk??(h=>te(h,s,p)),{textStore:f}=e,k=f?de:ce,v=e.embeddingModelVersion,E=h=>v===void 0?h:h===void 0?v:`${v}::${h}`;return h=>{let I;const N=typeof h.trace=="function"?h.trace:void 0,B=async t=>{I??=ge(e.embeddingModel,h.ai);const n=I,o=async d=>{const{embedding:r,providerMetadata:u,usage:m}=await ee({model:n,value:t});if(d!==void 0){const c=m.tokens;typeof c=="number"&&Number.isFinite(c)&&d.setAttribute("gen_ai.usage.input_tokens",c);const l=we(u);l!==void 0&&d.setAttribute("gen_ai.usage.cost",l)}return r};if(N===void 0)return o();const i=fe(n),a=typeof h.conversationId=="string"&&h.conversationId.length>0?h.conversationId:void 0;return N("ai.embed",(d,r)=>o(r),{"gen_ai.operation.name":"embeddings",...i===void 0?{}:{"gen_ai.request.model":i},...a===void 0?{}:{"gen_ai.conversation.id":a}})},$=t=>{if(t===void 0){if(e.requireNamespace)throw new x("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||pe(e.index)}},U=async(t,n)=>{const[o]=await h.vectors.getByIds(e.index,[_(n,t,0)],n),i=o?.metadata?.[R],a=o?.metadata?.[T];return{chunks:typeof a=="number"&&Number.isInteger(a)&&a>0?a:void 0,hash:typeof i=="string"?i:void 0}},j=async(t,n,o,i)=>{const a=Array.from({length:o-n},(d,r)=>_(i,t,n+r));a.length!==0&&(await h.vectors.deleteByIds(e.index,a,i),await f?.remove?.(a,{namespace:i}),await e.lexicalStore?.remove?.(a,{namespace:i}))},W=async t=>{if($(t.namespace),t.importance!==void 0&&(typeof t.importance!="number"||t.importance<0||t.importance>1))throw new x("BAD_REQUEST","@lunora/ai/rag: `importance` must be a number in [0, 1]");const n=E(t.namespace),o=await he(t.text),i=await U(t.id,n);if(i.hash===o&&i.chunks!==void 0)return{chunks:i.chunks,ids:Array.from({length:i.chunks},(r,u)=>_(n,t.id,u)),unchanged:!0};const a=y(t.text),d=a.map((r,u)=>_(n,t.id,u));if(a.length===0&&t.allowEmptySources===!1)throw new x("BAD_REQUEST",`@lunora/ai/rag: source "${t.id}" produced zero chunks — set allowEmptySources: true to allow this`);if(a.length>0){const r=a.map((u,m)=>({chunkIndex:m,id:d[m],sourceId:t.id,text:u}));f&&await f.put(r,{namespace:n}),e.lexicalStore&&await e.lexicalStore.index(r,{namespace:n})}return await re(a,ae,async(r,u)=>{const m=d[u],c={...t.metadata,[z]:u,[V]:t.id};f||(c[A]=r),t.importance!==void 0&&(c[M]=t.importance),u===0&&(c[R]=o,c[T]=a.length,v!==void 0&&(c[F]=v)),await h.vectors.upsert(e.index,{embed:B,id:m,input:r,metadata:c,namespace:n}),t.onChunk?.({chunkIndex:u,id:m,text:r,total:a.length})}),i.chunks!==void 0&&i.chunks>a.length&&await j(t.id,a.length,i.chunks,n),{chunks:a.length,ids:d,unchanged:!1}},H=async t=>{$(t.namespace);const n=E(t.namespace),o=(await U(t.id,n)).chunks??1;await j(t.id,0,o,n)},q=async(t,n)=>{const o=new Map;if(t.length===0)return o;if(f){const a=await f.getMany(t,{namespace:n});for(const[d,r]of t.entries()){const u=a[d];typeof u=="string"&&o.set(r,u)}return o}const i=await h.vectors.getByIds(e.index,t,n);for(const a of i){const d=a.metadata?.[A];typeof d=="string"&&o.set(a.id,d)}return o},L=async(t,n,o)=>{const i=n?.chunkContext?.before??0,a=n?.chunkContext?.after??0;if(i===0&&a===0)return t;if(!Number.isInteger(i)||i<0||!Number.isInteger(a)||a<0)throw new x("BAD_REQUEST","@lunora/ai/rag: `chunkContext.before`/`chunkContext.after` must be non-negative integers");const d=new Map(t.map(c=>[c.id,c.text])),r=new Set;for(const c of t)for(let l=-i;l<=a;l+=1){const w=c.chunkIndex+l,g=_(o,c.sourceId,w);l!==0&&w>=0&&!d.has(g)&&r.add(g)}const u=await q([...r],o),m=(c,l)=>{const w=_(o,c,l);return d.get(w)??u.get(w)};return t.map(c=>{const l=[];for(let w=-i;w<=a;w+=1){const g=w===0?c.text:m(c.sourceId,c.chunkIndex+w);g!==void 0&&l.push(g)}return{...c,text:l.join(`
8
+ `)}})},X=t=>{if(typeof t=="string"){const n=e.filters?.[t];if(!n)throw new x("NOT_FOUND",`@lunora/ai/rag: unknown named filter "${t}" — must be one of the keys declared in RagConfig.filters`);return n.filter}return t},Y=(t,n)=>t.matches.map(o=>{const i=o.metadata??{},a=K(o.id,n),d=i[A],r=i[M],u=typeof r=="number"&&r>=0&&r<=1?r:1;return{chunkIndex:a.chunkIndex,id:o.id,importance:u,metadata:O(i),score:o.score*u,sourceId:a.sourceId,text:typeof d=="string"?d:""}}),Z=async(t,n)=>{if(!f)return t;const o=t.map(r=>r.id),[i,a]=await Promise.all([q(o,n),h.vectors.getByIds(e.index,o,n)]),d=new Map(a.map(r=>[r.id,r.metadata]));return t.flatMap(r=>{const u=i.get(r.id);if(u===void 0)return[];const m=d.get(r.id),c=m?.[M],l=typeof c=="number"&&c>=0&&c<=1?c:r.importance,w=(r.importance===0?0:r.score/r.importance)*l;return[{...r,importance:l,metadata:O(m)??r.metadata,score:w,text:u}]})},C=async(t,n)=>{$(n?.namespace);const o=E(n?.namespace),i=X(n?.filter),a=e.rlsFilter?await e.rlsFilter(h.auth):void 0,d=a?{...i,...a}:i,r=Math.min(n?.topK??b,k),u=await h.vectors.query(e.index,{embed:B,filter:d,input:t,namespace:o,returnMetadata:f?"indexed":"all",topK:r});let m=await Z(Y(u,o),o);const c=n?.minScore;if(c!==void 0&&(m=m.filter(g=>g.score>=c)),e.lexicalStore){const g=(await e.lexicalStore.search(t,{filter:d,namespace:o,topK:e.lexicalTopK??r})).map(S=>{const D=K(S.id,o);return{chunkIndex:D.chunkIndex,id:S.id,importance:1,metadata:void 0,score:S.score,sourceId:D.sourceId,text:S.text}});m=[...ne(m,g)]}m.sort((g,S)=>S.score-g.score),m=[...await L(m,n,o)];const l=[],w=new Set;for(const g of m)w.has(g.sourceId)||(w.add(g.sourceId),l.push({id:g.sourceId,metadata:g.metadata,weight:g.importance}));return n?.onRetrieve?.({matches:m.length,query:t}),{chunks:m,context:le(m),sources:l}};return{asTool:t=>G({description:t?.description??`Search the "${e.index}" knowledge base for passages relevant to a natural-language query.`,execute:async({query:n})=>C(n,{namespace:t?.namespace,topK:t?.topK}),inputSchema:J({properties:{query:{description:"The natural-language search query.",type:"string"}},required:["query"],type:"object"})}),index:W,remove:H,retrieve:C}}};export{Ie as default};
@@ -1,4 +1,15 @@
1
1
  import { EmbeddingModel, LanguageModel } from 'ai';
2
+ /**
3
+ * Which surface resolved the gateway. The two paths handle the auth token
4
+ * differently: a bring-your-own AI SDK provider sends it as the
5
+ * `cf-aig-authorization` header (`ResolvedAiGateway.headers`), but the Workers AI
6
+ * **binding** routes through the gateway using the account's own credentials and
7
+ * its native `gateway` option (Cloudflare `GatewayOptions`) has **no** field to
8
+ * carry an authorization token — so a token set for the binding path cannot be
9
+ * delivered and is warned about instead of silently dropped.
10
+ * @experimental
11
+ */
12
+ type AiGatewayConsumer = "byo-provider" | "workers-ai-binding";
2
13
  /**
3
14
  * Project {@link AiGatewayMetadata} to a plain string-map of only its defined,
4
15
  * non-empty fields, or `undefined` when nothing is set. This is the shared source
@@ -56,7 +67,21 @@ interface ResolvedAiGateway {
56
67
  declare const AI_GATEWAY_ACCOUNT_ID_ENV = "LUNORA_AI_GATEWAY_ACCOUNT_ID";
57
68
  /** Env var naming the AI Gateway id (the gateway's slug). */
58
69
  declare const AI_GATEWAY_ID_ENV = "LUNORA_AI_GATEWAY_ID";
59
- /** Env var carrying the gateway's authentication token (only for authenticated gateways). */
70
+ /**
71
+ * Env var carrying the gateway's authentication token (only for authenticated
72
+ * gateways).
73
+ *
74
+ * **Workers AI binding limitation.** This token is delivered only on the
75
+ * bring-your-own AI SDK provider path, as the `cf-aig-authorization` header (see
76
+ * {@link ResolvedAiGateway.headers}). The Workers AI **binding** (`ctx.ai` over
77
+ * `env.AI`) routes through the gateway with the account's own credentials and its
78
+ * native `gateway` option (Cloudflare's `GatewayOptions`: `id` / `cacheKey` /
79
+ * `metadata` / …) has no authorization field — so an *authenticated* gateway that
80
+ * requires a token cannot be reached on the binding path. Set this only for a BYO
81
+ * provider; for Workers AI, leave the gateway unauthenticated (or front it with a
82
+ * BYO provider). {@link resolveAiGateway} warns once per isolate if the token is
83
+ * set on the binding path.
84
+ */
60
85
  declare const AI_GATEWAY_TOKEN_ENV = "LUNORA_AI_GATEWAY_TOKEN";
61
86
  /**
62
87
  * Resolve the Cloudflare AI Gateway coordinates from the Worker `env`, or
@@ -67,9 +92,16 @@ declare const AI_GATEWAY_TOKEN_ENV = "LUNORA_AI_GATEWAY_TOKEN";
67
92
  *
68
93
  * Pass optional {@link AiGatewayMetadata} to fold a `cf-aig-metadata` correlation
69
94
  * header into `headers` — only its defined fields are sent.
95
+ *
96
+ * `consumer` names the surface resolving the gateway (default `"byo-provider"`).
97
+ * When it is `"workers-ai-binding"` and an {@link AI_GATEWAY_TOKEN_ENV} token is
98
+ * configured, this warns once per isolate: the binding path cannot carry the
99
+ * token (see {@link AI_GATEWAY_TOKEN_ENV}), so it would otherwise be dropped with
100
+ * no diagnostic and every `ctx.ai.model(...)` call would fail against an
101
+ * authenticated gateway while the token var reads as "configured".
70
102
  * @experimental
71
103
  */
72
- declare const resolveAiGateway: (env: Record<string, unknown>, metadata?: AiGatewayMetadata) => ResolvedAiGateway | undefined;
104
+ declare const resolveAiGateway: (env: Record<string, unknown>, metadata?: AiGatewayMetadata, consumer?: AiGatewayConsumer) => ResolvedAiGateway | undefined;
73
105
  /**
74
106
  * Structural projection of the Cloudflare Workers `AI` binding (`env.AI`).
75
107
  * Declared locally so unit tests can pass a plain-object double and the real
@@ -1,4 +1,15 @@
1
1
  import { EmbeddingModel, LanguageModel } from 'ai';
2
+ /**
3
+ * Which surface resolved the gateway. The two paths handle the auth token
4
+ * differently: a bring-your-own AI SDK provider sends it as the
5
+ * `cf-aig-authorization` header (`ResolvedAiGateway.headers`), but the Workers AI
6
+ * **binding** routes through the gateway using the account's own credentials and
7
+ * its native `gateway` option (Cloudflare `GatewayOptions`) has **no** field to
8
+ * carry an authorization token — so a token set for the binding path cannot be
9
+ * delivered and is warned about instead of silently dropped.
10
+ * @experimental
11
+ */
12
+ type AiGatewayConsumer = "byo-provider" | "workers-ai-binding";
2
13
  /**
3
14
  * Project {@link AiGatewayMetadata} to a plain string-map of only its defined,
4
15
  * non-empty fields, or `undefined` when nothing is set. This is the shared source
@@ -56,7 +67,21 @@ interface ResolvedAiGateway {
56
67
  declare const AI_GATEWAY_ACCOUNT_ID_ENV = "LUNORA_AI_GATEWAY_ACCOUNT_ID";
57
68
  /** Env var naming the AI Gateway id (the gateway's slug). */
58
69
  declare const AI_GATEWAY_ID_ENV = "LUNORA_AI_GATEWAY_ID";
59
- /** Env var carrying the gateway's authentication token (only for authenticated gateways). */
70
+ /**
71
+ * Env var carrying the gateway's authentication token (only for authenticated
72
+ * gateways).
73
+ *
74
+ * **Workers AI binding limitation.** This token is delivered only on the
75
+ * bring-your-own AI SDK provider path, as the `cf-aig-authorization` header (see
76
+ * {@link ResolvedAiGateway.headers}). The Workers AI **binding** (`ctx.ai` over
77
+ * `env.AI`) routes through the gateway with the account's own credentials and its
78
+ * native `gateway` option (Cloudflare's `GatewayOptions`: `id` / `cacheKey` /
79
+ * `metadata` / …) has no authorization field — so an *authenticated* gateway that
80
+ * requires a token cannot be reached on the binding path. Set this only for a BYO
81
+ * provider; for Workers AI, leave the gateway unauthenticated (or front it with a
82
+ * BYO provider). {@link resolveAiGateway} warns once per isolate if the token is
83
+ * set on the binding path.
84
+ */
60
85
  declare const AI_GATEWAY_TOKEN_ENV = "LUNORA_AI_GATEWAY_TOKEN";
61
86
  /**
62
87
  * Resolve the Cloudflare AI Gateway coordinates from the Worker `env`, or
@@ -67,9 +92,16 @@ declare const AI_GATEWAY_TOKEN_ENV = "LUNORA_AI_GATEWAY_TOKEN";
67
92
  *
68
93
  * Pass optional {@link AiGatewayMetadata} to fold a `cf-aig-metadata` correlation
69
94
  * header into `headers` — only its defined fields are sent.
95
+ *
96
+ * `consumer` names the surface resolving the gateway (default `"byo-provider"`).
97
+ * When it is `"workers-ai-binding"` and an {@link AI_GATEWAY_TOKEN_ENV} token is
98
+ * configured, this warns once per isolate: the binding path cannot carry the
99
+ * token (see {@link AI_GATEWAY_TOKEN_ENV}), so it would otherwise be dropped with
100
+ * no diagnostic and every `ctx.ai.model(...)` call would fail against an
101
+ * authenticated gateway while the token var reads as "configured".
70
102
  * @experimental
71
103
  */
72
- declare const resolveAiGateway: (env: Record<string, unknown>, metadata?: AiGatewayMetadata) => ResolvedAiGateway | undefined;
104
+ declare const resolveAiGateway: (env: Record<string, unknown>, metadata?: AiGatewayMetadata, consumer?: AiGatewayConsumer) => ResolvedAiGateway | undefined;
73
105
  /**
74
106
  * Structural projection of the Cloudflare Workers `AI` binding (`env.AI`).
75
107
  * Declared locally so unit tests can pass a plain-object double and the real
@@ -1,5 +1,5 @@
1
1
  import { Tool } from 'ai';
2
- import { E as EmbeddingModelInput, a as LunoraAi } from "../packem_shared/types.d-F_Q8R8L3.mjs";
2
+ import { E as EmbeddingModelInput, a as LunoraAi } from "../packem_shared/types.d-BcLGTChd.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, a as LunoraAi } from "../packem_shared/types.d-F_Q8R8L3.js";
2
+ import { E as EmbeddingModelInput, a as LunoraAi } from "../packem_shared/types.d-BcLGTChd.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 r}from"../packem_shared/fixedWindowChunks-XJRXHEoz.mjs";import{default as a}from"../packem_shared/defineRag-BF3RwHZj.mjs";import{contentHash as s,guessMimeTypeFromExtension as d}from"../packem_shared/contentHash-BIn6ECP8.mjs";import{default as n}from"../packem_shared/hybridRank-U6PmGuz1.mjs";import{default as i}from"../packem_shared/bm25LexicalStore-RA9sesFC.mjs";export{i as bm25LexicalStore,s as contentHash,a as defineRag,r as fixedWindowChunks,d as guessMimeTypeFromExtension,n as hybridRank};
1
+ import{default as r}from"../packem_shared/fixedWindowChunks-XJRXHEoz.mjs";import{default as a}from"../packem_shared/defineRag-DUOcnSgC.mjs";import{contentHash as s,guessMimeTypeFromExtension as d}from"../packem_shared/contentHash-BIn6ECP8.mjs";import{default as n}from"../packem_shared/hybridRank-U6PmGuz1.mjs";import{default as i}from"../packem_shared/bm25LexicalStore-RA9sesFC.mjs";export{i as bm25LexicalStore,s as contentHash,a as defineRag,r as fixedWindowChunks,d as guessMimeTypeFromExtension,n as hybridRank};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/ai",
3
- "version": "1.0.0-alpha.26",
3
+ "version": "1.0.0-alpha.28",
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",
@@ -1 +0,0 @@
1
- const r=(t,a)=>{const n=t[a];return typeof n=="string"&&n.length>0?n:void 0},c=t=>{if(t===void 0)return;const a={};return typeof t.functionPath=="string"&&t.functionPath.length>0&&(a.functionPath=t.functionPath),typeof t.traceId=="string"&&t.traceId.length>0&&(a.traceId=t.traceId),Object.keys(a).length>0?a:void 0},d=t=>{const a=c(t);return a===void 0?void 0:JSON.stringify(a)},s="LUNORA_AI_GATEWAY_ACCOUNT_ID",_="LUNORA_AI_GATEWAY_ID",I="LUNORA_AI_GATEWAY_TOKEN",u=(t,a)=>{const n=r(t,s),o=r(t,_);if(n===void 0||o===void 0)return;const e=r(t,I),i={};e!==void 0&&(i["cf-aig-authorization"]=`Bearer ${e}`);const A=d(a);return A!==void 0&&(i["cf-aig-metadata"]=A),{accountId:n,baseURL:`https://gateway.ai.cloudflare.com/v1/${n}/${o}`,gatewayId:o,headers:i}};export{s as AI_GATEWAY_ACCOUNT_ID_ENV,_ as AI_GATEWAY_ID_ENV,I as AI_GATEWAY_TOKEN_ENV,c as buildAiGatewayMetadataFields,u as resolveAiGateway};
@@ -1 +0,0 @@
1
- import{LunoraError as t}from"@lunora/errors";import{createWorkersAI as v}from"workers-ai-provider";import{buildAiGatewayMetadataFields as c,resolveAiGateway as A}from"./AI_GATEWAY_ACCOUNT_ID_ENV-JzYYnjLP.mjs";const I=(i,o,s)=>{const d=c(s);if(i!==void 0)return d!==void 0&&i.metadata===void 0?{...i,metadata:d}:i;if(o===void 0)return;const a=A(o,s);if(a!==void 0)return d===void 0?{id:a.gatewayId}:{id:a.gatewayId,metadata:d}},y=(i,o)=>v({binding:i,gateway:o}),h=i=>{const{binding:o,defaultEmbeddingModel:s,defaultModel:d,env:a,gateway:f,metadata:g,provider:m}=i;if(!m&&!o)throw new t("INTERNAL","@lunora/ai: createAi requires a `binding` (env.AI) or a pre-built `provider`");const u=I(f,a,g),n=m??y(o,u),p=e=>{if(e===void 0){if(!d)throw new t("INTERNAL","@lunora/ai: no model supplied and no `defaultModel` configured — pass a model id or an AI SDK model");return n(d)}return typeof e=="string"?n(e):e},w=e=>{const r=n.textEmbeddingModel;if(typeof r!="function")throw new t("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 r.call(n,e)};return{embeddingModel:e=>{if(typeof e=="object")return e;const r=e??s;if(!r)throw new t("INTERNAL","@lunora/ai: no embedding model supplied and no `defaultEmbeddingModel` configured — pass an embedding model id or an AI SDK EmbeddingModel");return w(r)},model:p,run:async(e,r,l)=>{if(!o)throw new t("INTERNAL","@lunora/ai: ai.run requires the `binding` (env.AI) — it is unavailable when only a custom `provider` was supplied");const b=u!==void 0&&l?.gateway===void 0?{...l,gateway:u}:l;return o.run(e,r,b)},workersai:n}};export{h as default};