@lunora/ai 1.0.0-alpha.4 → 1.0.0-alpha.41
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/LICENSE.md +6 -0
- package/README.md +3 -1
- package/dist/index.d.mts +26 -28
- package/dist/index.d.ts +26 -28
- package/dist/index.mjs +1 -3
- package/dist/packem_shared/AI_GATEWAY_ACCOUNT_ID_ENV-CPzc-So1.mjs +1 -0
- package/dist/packem_shared/bm25LexicalStore-RA9sesFC.mjs +1 -0
- package/dist/packem_shared/contentHash-BIn6ECP8.mjs +1 -0
- package/dist/packem_shared/createAi-C2ExoUDR.mjs +1 -0
- package/dist/packem_shared/defineRag-DUOcnSgC.mjs +8 -0
- package/dist/packem_shared/fixedWindowChunks-XJRXHEoz.mjs +1 -0
- package/dist/packem_shared/hybridRank-U6PmGuz1.mjs +1 -0
- package/dist/packem_shared/types.d-BcLGTChd.d.mts +239 -0
- package/dist/packem_shared/types.d-BcLGTChd.d.ts +239 -0
- package/dist/rag/index.d.mts +558 -0
- package/dist/rag/index.d.ts +558 -0
- package/dist/rag/index.mjs +1 -0
- package/package.json +12 -6
- package/dist/packem_shared/createAi-Bq_4LMcp.mjs +0 -54
package/LICENSE.md
CHANGED
|
@@ -103,3 +103,9 @@ Unless required by applicable law or agreed to in writing, software distributed
|
|
|
103
103
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
104
|
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
105
|
specific language governing permissions and limitations under the License.
|
|
106
|
+
|
|
107
|
+
<!-- DEPENDENCIES -->
|
|
108
|
+
<!-- /DEPENDENCIES -->
|
|
109
|
+
|
|
110
|
+
<!-- TYPE_DEPENDENCIES -->
|
|
111
|
+
<!-- /TYPE_DEPENDENCIES -->
|
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
<!-- END_PACKAGE_OG_IMAGE_PLACEHOLDER -->
|
|
12
12
|
|
|
13
|
+
> **Experimental** — this package is outside the Lunora 1.0 stability promise: its API may change in any release, without a major version bump.
|
|
14
|
+
|
|
13
15
|
<br />
|
|
14
16
|
|
|
15
17
|
<div align="center">
|
|
@@ -34,7 +36,7 @@
|
|
|
34
36
|
|
|
35
37
|
---
|
|
36
38
|
|
|
37
|
-
A small AI helper for Lunora, built on the [Vercel AI SDK](https://ai-sdk.dev)
|
|
39
|
+
A small AI helper for Lunora, built on the [Vercel AI SDK](https://ai-sdk.dev) v7 core and Cloudflare's official [`workers-ai-provider`](https://github.com/cloudflare/ai). Call `generateText`/`streamText`/`generateObject`/`embed`/`tool` from any function handler. **Cloudflare Workers AI is the zero-config default**, but the helper is provider-agnostic: every call takes either a Workers AI model id (a string) or any AI SDK model object — `@ai-sdk/openai`, `@ai-sdk/anthropic`, OpenRouter, … — so apps are never locked to Workers AI. Pair `embed` with [`@lunora/bindings/vectors`](https://www.npmjs.com/package/@lunora/bindings) for RAG.
|
|
38
40
|
|
|
39
41
|
Part of the [Lunora](https://github.com/anolilab/lunora) framework — a type-safe, real-time backend on Cloudflare Workers + Durable Objects with a Vite-first DX.
|
|
40
42
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { type
|
|
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
|
+
export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, hasToolCall, jsonSchema, streamObject, streamText, tool } from 'ai';
|
|
3
4
|
export { createWorkersAI } from 'workers-ai-provider';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
|
|
27
|
-
workersai: WorkersAiProviderLike;
|
|
28
|
-
}
|
|
5
|
+
/**
|
|
6
|
+
* Create the `ctx.ai` helper over a Workers `AI` binding.
|
|
7
|
+
*
|
|
8
|
+
* Workers AI is the zero-config default, but `@lunora/ai` is provider-agnostic:
|
|
9
|
+
* every helper takes either a model id string (resolved against the Workers AI
|
|
10
|
+
* provider) or any AI SDK {@link LanguageModel}/{@link EmbeddingModel} object
|
|
11
|
+
* (`@ai-sdk/openai`, `@ai-sdk/anthropic`, OpenRouter, …), so apps are never
|
|
12
|
+
* locked to Workers AI. Pair `embed` with `@lunora/bindings/vectors` for RAG.
|
|
13
|
+
*
|
|
14
|
+
* Combine with the re-exported `generateText`/`streamText`/`generateObject`/
|
|
15
|
+
* `embed`/`tool` from this package:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { streamText } from "@lunora/ai";
|
|
19
|
+
*
|
|
20
|
+
* const result = streamText({
|
|
21
|
+
* model: ctx.ai.model("@cf/meta/llama-3.3-70b-instruct-fp8-fast"),
|
|
22
|
+
* messages,
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
* @experimental
|
|
26
|
+
*/
|
|
29
27
|
declare const createAi: (options: LunoraAiOptions) => LunoraAi;
|
|
30
|
-
export { type
|
|
28
|
+
export { type LunoraAi, type LunoraAiOptions, createAi };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { type
|
|
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
|
+
export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, hasToolCall, jsonSchema, streamObject, streamText, tool } from 'ai';
|
|
3
4
|
export { createWorkersAI } from 'workers-ai-provider';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
|
|
27
|
-
workersai: WorkersAiProviderLike;
|
|
28
|
-
}
|
|
5
|
+
/**
|
|
6
|
+
* Create the `ctx.ai` helper over a Workers `AI` binding.
|
|
7
|
+
*
|
|
8
|
+
* Workers AI is the zero-config default, but `@lunora/ai` is provider-agnostic:
|
|
9
|
+
* every helper takes either a model id string (resolved against the Workers AI
|
|
10
|
+
* provider) or any AI SDK {@link LanguageModel}/{@link EmbeddingModel} object
|
|
11
|
+
* (`@ai-sdk/openai`, `@ai-sdk/anthropic`, OpenRouter, …), so apps are never
|
|
12
|
+
* locked to Workers AI. Pair `embed` with `@lunora/bindings/vectors` for RAG.
|
|
13
|
+
*
|
|
14
|
+
* Combine with the re-exported `generateText`/`streamText`/`generateObject`/
|
|
15
|
+
* `embed`/`tool` from this package:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { streamText } from "@lunora/ai";
|
|
19
|
+
*
|
|
20
|
+
* const result = streamText({
|
|
21
|
+
* model: ctx.ai.model("@cf/meta/llama-3.3-70b-instruct-fp8-fast"),
|
|
22
|
+
* messages,
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
* @experimental
|
|
26
|
+
*/
|
|
29
27
|
declare const createAi: (options: LunoraAiOptions) => LunoraAi;
|
|
30
|
-
export { type
|
|
28
|
+
export { type LunoraAi, type LunoraAiOptions, createAi };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { embed, embedMany, generateObject, generateText, streamObject, streamText, tool } from 'ai';
|
|
3
|
-
export { createWorkersAI } from 'workers-ai-provider';
|
|
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
|
+
const P=/[a-z0-9]+/g,x=l=>l.toLowerCase().match(P)??[],L=new WeakSet,k=()=>{const l=new Map,g=(s="")=>{let e=l.get(s);return e||(e={documents:new Map,postings:new Map,totalLength:0},l.set(s,e)),e},u=(s,e)=>{const t=g(s),o=t.documents.get(e);if(o){for(const r of o.termFrequency.keys()){const a=t.postings.get(r);a&&(a.delete(e),a.size===0&&t.postings.delete(r))}t.totalLength-=o.length,t.documents.delete(e)}},d={index:(s,e)=>{const t=g(e.namespace);for(const o of s){u(e.namespace,o.id);const r=x(o.text);if(r.length===0)continue;const a=new Map;for(const c of r)a.set(c,(a.get(c)??0)+1);for(const[c,f]of a){let n=t.postings.get(c);n||(n=new Map,t.postings.set(c,n)),n.set(o.id,f)}t.documents.set(o.id,{length:r.length,termFrequency:a,text:o.text}),t.totalLength+=r.length}return Promise.resolve()},remove:(s,e)=>{for(const t of s)u(e.namespace,t);return Promise.resolve()},search:(s,e)=>{if(e.filter&&Object.keys(e.filter).length>0)return L.has(d)||(L.add(d),console.warn("[@lunora/ai/rag] bm25LexicalStore cannot evaluate a metadata filter (it stores no metadata);\nthe lexical leg is skipped for filtered queries. Fold the RLS dimension into `namespace`,\nor plug a filter-aware RagLexicalStore, to keep a lexical leg under metadata-based RLS.")),Promise.resolve([]);const t=g(e.namespace),o=t.documents.size;if(o===0)return Promise.resolve([]);const r=[...new Set(x(s))];if(r.length===0)return Promise.resolve([]);const a=t.totalLength/o,c=new Map;for(const n of r){const i=t.postings.get(n);if(!i)continue;const p=i.size,v=Math.log(1+(o-p+.5)/(p+.5));for(const[m,h]of i){const w=t.documents.get(m);if(!w)continue;const M=h+1.5*(1-.75+.75*w.length/a),S=v*(h*(1.5+1)/M);c.set(m,(c.get(m)??0)+S)}}const f=[...c.entries()].map(([n,i])=>({id:n,score:i,text:t.documents.get(n)?.text??""}));return Promise.resolve(f.toSorted((n,i)=>i.score-n.score).slice(0,e.topK))}};return d};export{k as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=/^\.+/u,p={avif:"image/avif",bmp:"image/bmp",gif:"image/gif",ico:"image/x-icon",jpeg:"image/jpeg",jpg:"image/jpeg",png:"image/png",svg:"image/svg+xml",tiff:"image/tiff",tif:"image/tiff",webp:"image/webp",avi:"video/x-msvideo",mkv:"video/x-matroska",mov:"video/quicktime",mp4:"video/mp4",mpeg:"video/mpeg",mpg:"video/mpeg",webm:"video/webm",wmv:"video/x-ms-wmv",aac:"audio/aac",flac:"audio/flac",m4a:"audio/mp4",mp3:"audio/mpeg",ogg:"audio/ogg",opus:"audio/opus",wav:"audio/wav",wma:"audio/x-ms-wma",csv:"text/csv",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",odp:"application/vnd.oasis.opendocument.presentation",ods:"application/vnd.oasis.opendocument.spreadsheet",odt:"application/vnd.oasis.opendocument.text",pdf:"application/pdf",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",rtf:"application/rtf",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",css:"text/css",html:"text/html",htm:"text/html",ini:"text/plain",json:"application/json",js:"text/javascript",mjs:"text/javascript",md:"text/markdown",jsx:"text/javascript",ts:"text/typescript",tsx:"text/typescript",txt:"text/plain",xml:"application/xml",yaml:"application/x-yaml",yml:"application/x-yaml","7z":"application/x-7z-compressed",bz2:"application/x-bzip2",gz:"application/gzip",jar:"application/java-archive",rar:"application/vnd.rar",tar:"application/x-tar",zip:"application/zip",otf:"font/otf",ttf:"font/ttf",woff:"font/woff",woff2:"font/woff2",bin:"application/octet-stream",epub:"application/epub+zip",exe:"application/vnd.microsoft.portable-executable",iso:"application/x-iso9660-image",sql:"application/sql",toml:"application/toml"},e=t=>{const a=t.replace(o,"").toLowerCase();return p[a]??"application/octet-stream"},n=async t=>{const a=await crypto.subtle.digest("SHA-256",t);return[...new Uint8Array(a)].map(i=>i.toString(16).padStart(2,"0")).join("")};export{n as contentHash,e as guessMimeTypeFromExtension};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{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};
|
|
@@ -0,0 +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,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
|
+
app this shares one tenant's chunks (text included) with every other tenant, since
|
|
3
|
+
Vectorize indexes are account-global. Pass \`namespace\` (the shard/tenant key) on both
|
|
4
|
+
index() and retrieve(). Single-tenant apps suppress this via { allowSharedNamespace: true }.`))},le=e=>e.map(s=>`[source:${s.sourceId}#${String(s.chunkIndex)}]
|
|
5
|
+
${s.text}`).join(`
|
|
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: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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const a=(s,e,i)=>{if(!Number.isInteger(e)||e<1)throw new RangeError("fixedWindowChunks: `size` must be a positive integer");if(!Number.isInteger(i)||i<0||i>=e)throw new RangeError("fixedWindowChunks: `overlap` must be a non-negative integer smaller than `size`");const t=s.trim();if(t.length===0)return[];if(t.length<=e)return[t];const o=Math.max(1,e-i),n=[];for(let r=0;r<t.length&&(n.push(t.slice(r,r+e)),!(r+e>=t.length));r+=o);return n};export{a as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const a=(c,s,o=60)=>{const n=new Map;for(const[e,t]of c.entries())n.set(t.id,{chunk:t,score:1/(o+e),vectorRank:e});for(const[e,t]of s.entries()){const r=n.get(t.id);r?r.score+=1/(o+e):n.set(t.id,{chunk:t,score:1/(o+e),vectorRank:Number.POSITIVE_INFINITY})}return[...n.values()].toSorted((e,t)=>{const r=t.score-e.score;return r===0?e.vectorRank-t.vectorRank:r}).map(e=>e.chunk)};export{a as default};
|
|
@@ -0,0 +1,239 @@
|
|
|
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";
|
|
13
|
+
/**
|
|
14
|
+
* Project {@link AiGatewayMetadata} to a plain string-map of only its defined,
|
|
15
|
+
* non-empty fields, or `undefined` when nothing is set. This is the shared source
|
|
16
|
+
* of truth behind both gateway-correlation forms: the `cf-aig-metadata` HTTP
|
|
17
|
+
* header (bring-your-own providers) and the Workers AI binding's native
|
|
18
|
+
* `gateway.metadata` option — both encode the same `{ functionPath, traceId }`.
|
|
19
|
+
* @experimental
|
|
20
|
+
*/
|
|
21
|
+
declare const buildAiGatewayMetadataFields: (metadata: AiGatewayMetadata | undefined) => Record<string, string> | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Correlation metadata folded into the `cf-aig-metadata` request header so a
|
|
24
|
+
* gateway log entry can be tied back to the Lunora function + trace that made
|
|
25
|
+
* the call. Every field is optional — only defined ones are sent.
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
interface AiGatewayMetadata {
|
|
29
|
+
/** The Lunora function path that issued the model call (e.g. `messages:send`). */
|
|
30
|
+
functionPath?: string;
|
|
31
|
+
/** The 32-hex trace id the call belongs to, so the gateway log joins the trace. */
|
|
32
|
+
traceId?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The resolved AI Gateway coordinates.
|
|
36
|
+
*
|
|
37
|
+
* `gatewayId`/`accountId` drive the Workers AI provider's native `gateway` option
|
|
38
|
+
* (Cloudflare routes the binding call internally). `baseURL` + `headers` are for
|
|
39
|
+
* bring-your-own AI SDK providers (`@ai-sdk/openai`, …): append the provider
|
|
40
|
+
* slug to `baseURL` and spread `headers` into the provider config, e.g.
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* const gw = resolveAiGateway(env);
|
|
44
|
+
* const openai = createOpenAI(gw ? { baseURL: `${gw.baseURL}/openai`, headers: gw.headers } : {});
|
|
45
|
+
* ```
|
|
46
|
+
* @experimental
|
|
47
|
+
*/
|
|
48
|
+
interface ResolvedAiGateway {
|
|
49
|
+
/** The Cloudflare account id owning the gateway. */
|
|
50
|
+
accountId: string;
|
|
51
|
+
/**
|
|
52
|
+
* The universal gateway base URL:
|
|
53
|
+
* `https://gateway.ai.cloudflare.com/v1/{account}/{gateway}`. Append the
|
|
54
|
+
* provider slug (`/openai`, `/anthropic`, `/workers-ai`, …) per provider.
|
|
55
|
+
*/
|
|
56
|
+
baseURL: string;
|
|
57
|
+
/** The gateway id (slug). */
|
|
58
|
+
gatewayId: string;
|
|
59
|
+
/**
|
|
60
|
+
* Request headers for a gateway-routed call: `cf-aig-authorization` when the
|
|
61
|
+
* gateway is authenticated, and `cf-aig-metadata` when correlation metadata
|
|
62
|
+
* was supplied. Empty object when neither applies.
|
|
63
|
+
*/
|
|
64
|
+
headers: Record<string, string>;
|
|
65
|
+
}
|
|
66
|
+
/** Env var naming the Cloudflare account that owns the gateway. */
|
|
67
|
+
declare const AI_GATEWAY_ACCOUNT_ID_ENV = "LUNORA_AI_GATEWAY_ACCOUNT_ID";
|
|
68
|
+
/** Env var naming the AI Gateway id (the gateway's slug). */
|
|
69
|
+
declare const AI_GATEWAY_ID_ENV = "LUNORA_AI_GATEWAY_ID";
|
|
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
|
+
*/
|
|
85
|
+
declare const AI_GATEWAY_TOKEN_ENV = "LUNORA_AI_GATEWAY_TOKEN";
|
|
86
|
+
/**
|
|
87
|
+
* Resolve the Cloudflare AI Gateway coordinates from the Worker `env`, or
|
|
88
|
+
* `undefined` when the gateway is not configured (both `LUNORA_AI_GATEWAY_ACCOUNT_ID`
|
|
89
|
+
* and `LUNORA_AI_GATEWAY_ID` must be present). Opt-in and backward-compatible:
|
|
90
|
+
* an unconfigured app gets `undefined` and every caller keeps its direct-provider
|
|
91
|
+
* behavior.
|
|
92
|
+
*
|
|
93
|
+
* Pass optional {@link AiGatewayMetadata} to fold a `cf-aig-metadata` correlation
|
|
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".
|
|
102
|
+
* @experimental
|
|
103
|
+
*/
|
|
104
|
+
declare const resolveAiGateway: (env: Record<string, unknown>, metadata?: AiGatewayMetadata, consumer?: AiGatewayConsumer) => ResolvedAiGateway | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Structural projection of the Cloudflare Workers `AI` binding (`env.AI`).
|
|
107
|
+
* Declared locally so unit tests can pass a plain-object double and the real
|
|
108
|
+
* binding satisfies the same shape without importing `@cloudflare/workers-types`
|
|
109
|
+
* into the public surface. Mirrors the `run` method documented at
|
|
110
|
+
* https://developers.cloudflare.com/workers-ai/.
|
|
111
|
+
* @experimental
|
|
112
|
+
*/
|
|
113
|
+
interface AiBindingLike {
|
|
114
|
+
run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A Workers AI provider instance — the value returned by `createWorkersAI(...)`.
|
|
118
|
+
* Calling it with a model id yields an AI SDK {@link LanguageModel}; the
|
|
119
|
+
* optional `textEmbeddingModel` factory yields an {@link EmbeddingModel}.
|
|
120
|
+
* Typed structurally so `@lunora/ai` neither re-declares the provider's full
|
|
121
|
+
* surface nor hard-pins its exact type across minor releases.
|
|
122
|
+
* @experimental
|
|
123
|
+
*/
|
|
124
|
+
interface WorkersAiProviderLike {
|
|
125
|
+
(modelId: string, settings?: Record<string, unknown>): LanguageModel;
|
|
126
|
+
textEmbeddingModel?: (modelId: string) => EmbeddingModel;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* AI Gateway options forwarded to `createWorkersAI`. Lets inference route
|
|
130
|
+
* through a Cloudflare AI Gateway for caching, rate-limiting, and observability.
|
|
131
|
+
* @experimental
|
|
132
|
+
*/
|
|
133
|
+
interface AiGatewayOptions {
|
|
134
|
+
[key: string]: unknown;
|
|
135
|
+
id: string;
|
|
136
|
+
/**
|
|
137
|
+
* Custom correlation metadata surfaced in the AI Gateway logs (Cloudflare's
|
|
138
|
+
* native `gateway.metadata`). `@lunora/ai` folds `{ functionPath, traceId }`
|
|
139
|
+
* here from {@link LunoraAiOptions.metadata} when a gateway is configured.
|
|
140
|
+
*/
|
|
141
|
+
metadata?: Record<string, string>;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* `LunoraAiOptions` is part of the experimental `@lunora/ai` API and may change without a major version bump.
|
|
145
|
+
* @experimental
|
|
146
|
+
*/
|
|
147
|
+
interface LunoraAiOptions {
|
|
148
|
+
/**
|
|
149
|
+
* The Workers `AI` binding (`env.AI`). Required for the zero-config Workers
|
|
150
|
+
* AI default and for the raw `ai.run(...)` passthrough. May be omitted when
|
|
151
|
+
* a pre-built `provider` is supplied (e.g. in tests or a custom setup).
|
|
152
|
+
*/
|
|
153
|
+
binding?: AiBindingLike;
|
|
154
|
+
/**
|
|
155
|
+
* Default Workers AI **embedding** model id used by `embeddingModel()` when no
|
|
156
|
+
* explicit model is passed (e.g. `@cf/baai/bge-base-en-v1.5`). Kept separate
|
|
157
|
+
* from `defaultModel` because a language-model id and an embedding-model
|
|
158
|
+
* id belong to different Workers AI families and are never interchangeable —
|
|
159
|
+
* reusing the language-model default here would defer a wrong-family error to
|
|
160
|
+
* inference time. Has no effect on bring-your-own providers.
|
|
161
|
+
*/
|
|
162
|
+
defaultEmbeddingModel?: string;
|
|
163
|
+
/**
|
|
164
|
+
* Default Workers AI **language** model id used by `model()` when no explicit
|
|
165
|
+
* model is passed. For embeddings, set `defaultEmbeddingModel` instead.
|
|
166
|
+
* Has no effect on bring-your-own providers.
|
|
167
|
+
*/
|
|
168
|
+
defaultModel?: string;
|
|
169
|
+
/**
|
|
170
|
+
* The Worker `env`, read for opt-in Cloudflare AI Gateway routing (the
|
|
171
|
+
* `LUNORA_AI_GATEWAY_*` vars, via `resolveAiGateway`). When it configures a
|
|
172
|
+
* gateway and no explicit {@link LunoraAiOptions.gateway} is given, the
|
|
173
|
+
* Workers AI provider is routed through that gateway so token + dollar-cost
|
|
174
|
+
* telemetry is computed on the app's behalf. Unset, or with no gateway vars,
|
|
175
|
+
* behavior is unchanged (calls go straight to Workers AI).
|
|
176
|
+
*/
|
|
177
|
+
env?: Record<string, unknown>;
|
|
178
|
+
/**
|
|
179
|
+
* Route Workers AI inference through a Cloudflare AI Gateway. An explicit
|
|
180
|
+
* value wins over anything derived from {@link LunoraAiOptions.env}.
|
|
181
|
+
*/
|
|
182
|
+
gateway?: AiGatewayOptions;
|
|
183
|
+
/**
|
|
184
|
+
* Correlation metadata folded into the active gateway call (the Workers AI
|
|
185
|
+
* binding's native `gateway.metadata`) so an AI Gateway log entry ties back
|
|
186
|
+
* to the Lunora function + trace that made it. Only applied when a gateway is
|
|
187
|
+
* actually configured (explicit or env-derived) and only its defined fields
|
|
188
|
+
* are sent — absent otherwise, so behavior is unchanged. The generated
|
|
189
|
+
* `ctx.ai` facade threads `{ functionPath, traceId }` here automatically.
|
|
190
|
+
*/
|
|
191
|
+
metadata?: AiGatewayMetadata;
|
|
192
|
+
/**
|
|
193
|
+
* Pre-built Workers AI provider. When omitted, one is constructed from
|
|
194
|
+
* `binding` via `createWorkersAI`. Supplying it directly is the seam used by
|
|
195
|
+
* tests and advanced setups; it also lets callers configure the provider
|
|
196
|
+
* (e.g. `safePrompt`) before handing it to `@lunora/ai`.
|
|
197
|
+
*/
|
|
198
|
+
provider?: WorkersAiProviderLike;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* A model to run against. The AI SDK's {@link LanguageModel} already admits a
|
|
202
|
+
* bare `string`, so this alias covers both arms of the provider-agnostic seam:
|
|
203
|
+
* a string id is the Workers AI convenience path (resolved by `ctx.ai.model`),
|
|
204
|
+
* a built model object is bring-your-own (`@ai-sdk/openai`, `@ai-sdk/anthropic`,
|
|
205
|
+
* `@ai-sdk/google`, OpenRouter, …).
|
|
206
|
+
* @experimental
|
|
207
|
+
*/
|
|
208
|
+
type ModelInput = LanguageModel;
|
|
209
|
+
/**
|
|
210
|
+
* Likewise for embeddings: a Workers AI embedding model id (e.g.
|
|
211
|
+
* `@cf/baai/bge-base-en-v1.5`) or any AI SDK {@link EmbeddingModel}.
|
|
212
|
+
* @experimental
|
|
213
|
+
*/
|
|
214
|
+
type EmbeddingModelInput = EmbeddingModel | string;
|
|
215
|
+
/**
|
|
216
|
+
* The `ctx.ai` surface. `model`/`embeddingModel` resolve a Workers AI model from
|
|
217
|
+
* a string (the default provider) and pass any non-string model straight through,
|
|
218
|
+
* so both accept Workers AI and bring-your-own providers. Feed the resolved model
|
|
219
|
+
* to the AI SDK functions re-exported from `@lunora/ai` (`generateText`,
|
|
220
|
+
* `streamText`, `generateObject`, `embed`, …); `run` is the raw binding escape
|
|
221
|
+
* hatch, and `workersai` is the underlying provider for direct model access.
|
|
222
|
+
* @experimental
|
|
223
|
+
*/
|
|
224
|
+
interface LunoraAi {
|
|
225
|
+
/** Resolve an {@link EmbeddingModel}: a string → Workers AI, an object → passthrough. */
|
|
226
|
+
embeddingModel: (model?: EmbeddingModelInput) => EmbeddingModel;
|
|
227
|
+
/** Resolve a {@link LanguageModel}: a string → Workers AI, an object → passthrough. */
|
|
228
|
+
model: (model?: ModelInput) => LanguageModel;
|
|
229
|
+
/**
|
|
230
|
+
* Raw Workers AI binding passthrough (void-style `ai.run`). Bypasses the AI
|
|
231
|
+
* SDK entirely — useful for Workers-AI-only model families (image, ASR,
|
|
232
|
+
* translation) not surfaced through the provider. Throws if no binding was
|
|
233
|
+
* supplied.
|
|
234
|
+
*/
|
|
235
|
+
run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
|
|
236
|
+
/** The underlying Workers AI provider — `ai.workersai("@cf/...")` for a raw model. */
|
|
237
|
+
workersai: WorkersAiProviderLike;
|
|
238
|
+
}
|
|
239
|
+
export { AI_GATEWAY_ACCOUNT_ID_ENV as A, EmbeddingModelInput as E, LunoraAiOptions as L, ModelInput as M, ResolvedAiGateway as R, WorkersAiProviderLike as W, LunoraAi as a, AI_GATEWAY_ID_ENV as b, AI_GATEWAY_TOKEN_ENV as c, AiBindingLike as d, AiGatewayMetadata as e, AiGatewayOptions as f, buildAiGatewayMetadataFields as g, resolveAiGateway as r };
|