@lunora/ai 1.0.0-alpha.105 → 1.0.0-alpha.106

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-ChtZguP_.mjs";
2
- export { A as AI_DEFAULT_EMBEDDING_MODEL_ENV, b as AI_DEFAULT_MODEL_ENV, c as AI_GATEWAY_ACCOUNT_ID_ENV, d as AI_GATEWAY_ID_ENV, e as AI_GATEWAY_METADATA_MAX_KEYS, f as AI_GATEWAY_TAGS_ENV, g as AI_GATEWAY_TOKEN_ENV, h as AI_PROXY_TOKEN_ENV, i as AI_PROXY_URL_ENV, type j as AiBindingLike, type k as AiGatewayMetadata, type l as AiGatewayOptions, type m as AiMetrics, type n as AiModelOptions, type o as AiRunOptions, type p as AiSpan, type q as AiTelemetry, type r as AiTracer, type E as EmbeddingModelInput, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, s as buildAiGatewayMetadataFields, t as readAiGatewayEnvTags, u as resolveAiGateway } from "./packem_shared/types.d-ChtZguP_.mjs";
1
+ import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-D-WK1t2B.mjs";
2
+ export { A as AI_DEFAULT_EMBEDDING_MODEL_ENV, b as AI_DEFAULT_MODEL_ENV, c as AI_GATEWAY_ACCOUNT_ID_ENV, d as AI_GATEWAY_ID_ENV, e as AI_GATEWAY_METADATA_MAX_KEYS, f as AI_GATEWAY_TAGS_ENV, g as AI_GATEWAY_TOKEN_ENV, h as AI_PROXY_TOKEN_ENV, i as AI_PROXY_URL_ENV, type j as AiBindingLike, type k as AiGatewayMetadata, type l as AiGatewayOptions, type m as AiMetrics, type n as AiModelOptions, type o as AiRunOptions, type p as AiSpan, type q as AiTelemetry, type r as AiTracer, type s as AiWebSearchItem, type t as AiWebSearchOptions, type u as AiWebSearchProvider, type v as AiWebSearchResult, type E as EmbeddingModelInput, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, w as buildAiGatewayMetadataFields, x as readAiGatewayEnvTags, y as resolveAiGateway } from "./packem_shared/types.d-D-WK1t2B.mjs";
3
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-ChtZguP_.js";
2
- export { A as AI_DEFAULT_EMBEDDING_MODEL_ENV, b as AI_DEFAULT_MODEL_ENV, c as AI_GATEWAY_ACCOUNT_ID_ENV, d as AI_GATEWAY_ID_ENV, e as AI_GATEWAY_METADATA_MAX_KEYS, f as AI_GATEWAY_TAGS_ENV, g as AI_GATEWAY_TOKEN_ENV, h as AI_PROXY_TOKEN_ENV, i as AI_PROXY_URL_ENV, type j as AiBindingLike, type k as AiGatewayMetadata, type l as AiGatewayOptions, type m as AiMetrics, type n as AiModelOptions, type o as AiRunOptions, type p as AiSpan, type q as AiTelemetry, type r as AiTracer, type E as EmbeddingModelInput, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, s as buildAiGatewayMetadataFields, t as readAiGatewayEnvTags, u as resolveAiGateway } from "./packem_shared/types.d-ChtZguP_.js";
1
+ import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-D-WK1t2B.js";
2
+ export { A as AI_DEFAULT_EMBEDDING_MODEL_ENV, b as AI_DEFAULT_MODEL_ENV, c as AI_GATEWAY_ACCOUNT_ID_ENV, d as AI_GATEWAY_ID_ENV, e as AI_GATEWAY_METADATA_MAX_KEYS, f as AI_GATEWAY_TAGS_ENV, g as AI_GATEWAY_TOKEN_ENV, h as AI_PROXY_TOKEN_ENV, i as AI_PROXY_URL_ENV, type j as AiBindingLike, type k as AiGatewayMetadata, type l as AiGatewayOptions, type m as AiMetrics, type n as AiModelOptions, type o as AiRunOptions, type p as AiSpan, type q as AiTelemetry, type r as AiTracer, type s as AiWebSearchItem, type t as AiWebSearchOptions, type u as AiWebSearchProvider, type v as AiWebSearchResult, type E as EmbeddingModelInput, type M as ModelInput, type R as ResolvedAiGateway, type W as WorkersAiProviderLike, w as buildAiGatewayMetadataFields, x as readAiGatewayEnvTags, y as resolveAiGateway } from "./packem_shared/types.d-D-WK1t2B.js";
3
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 _}from"./packem_shared/createAi-D76ClNVB.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-Cebawe9R.mjs";import{AI_DEFAULT_EMBEDDING_MODEL_ENV as t,AI_DEFAULT_MODEL_ENV as a,AI_GATEWAY_ACCOUNT_ID_ENV as o,AI_GATEWAY_ID_ENV as r,AI_GATEWAY_METADATA_MAX_KEYS as T,AI_GATEWAY_TAGS_ENV as I,AI_GATEWAY_TOKEN_ENV as N,AI_PROXY_TOKEN_ENV as l,AI_PROXY_URL_ENV as m,buildAiGatewayMetadataFields as s,readAiGatewayEnvTags as D,resolveAiGateway as G}from"./packem_shared/AI_DEFAULT_EMBEDDING_MODEL_ENV-IwcHZiKj.mjs";import{DEFAULT_MODEL_PRICES as O,estimateModelCost as d,lookupModelPrice as i}from"./packem_shared/DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs";import{embed as Y,embedMany as x,generateObject as L,generateText as c,hasToolCall as f,jsonSchema as p,streamObject as W,streamText as b,tool as n}from"ai";import{createWorkersAI as U}from"workers-ai-provider";export{t as AI_DEFAULT_EMBEDDING_MODEL_ENV,a as AI_DEFAULT_MODEL_ENV,o as AI_GATEWAY_ACCOUNT_ID_ENV,r as AI_GATEWAY_ID_ENV,T as AI_GATEWAY_METADATA_MAX_KEYS,I as AI_GATEWAY_TAGS_ENV,N as AI_GATEWAY_TOKEN_ENV,l as AI_PROXY_TOKEN_ENV,m as AI_PROXY_URL_ENV,O as DEFAULT_MODEL_PRICES,s as buildAiGatewayMetadataFields,_ as createAi,U as createWorkersAI,Y as embed,x as embedMany,d as estimateModelCost,L as generateObject,c as generateText,f as hasToolCall,p as jsonSchema,i as lookupModelPrice,D as readAiGatewayEnvTags,G as resolveAiGateway,W as streamObject,b as streamText,n as tool};
@@ -0,0 +1 @@
1
+ import{createOpenAI as W}from"@ai-sdk/openai";import{LunoraError as f}from"@lunora/errors";import{createWorkersAI as $}from"workers-ai-provider";import{anthropic as B}from"workers-ai-provider/anthropic";import{openai as V}from"workers-ai-provider/openai";import{buildAiGatewayMetadataFields as U,readEnv as p,AI_DEFAULT_MODEL_ENV as N,AI_DEFAULT_EMBEDDING_MODEL_ENV as L,AI_PROXY_URL_ENV as _,AI_PROXY_TOKEN_ENV as R,readAiGatewayEnvTags as F,AI_GATEWAY_ID_ENV as j,warnIgnoredBindingToken as H}from"./AI_DEFAULT_EMBEDDING_MODEL_ENV-IwcHZiKj.mjs";import{wrapLanguageModel as Y}from"ai";import{m as q,r as D}from"./usage-GPnTSqR8.mjs";import"./DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs";const K={setAttribute:()=>{},setAttributes:()=>{}},P=async(e,t)=>t(P,K),X=(e,{metrics:t,trace:n=P})=>{const o={"gen_ai.operation.name":"chat","gen_ai.request.model":e};return{wrapGenerate:async({doGenerate:a})=>n("ai.generate",async(E,m)=>{const A=await a();return D(e,A,m,t),A},o),wrapStream:async({doStream:a})=>{const E=(h,d)=>{const c=h.stream.getReader();let u={};return{...h,stream:new ReadableStream({cancel:async w=>{d({outcome:u}),await c.cancel(w)},pull:async w=>{let v;try{v=await c.read()}catch(y){d({error:y,failed:!0,outcome:u}),w.error(y);return}if(v.done){d({outcome:u}),w.close();return}v.value.type==="finish"&&(u={providerMetadata:v.value.providerMetadata,usage:v.value.usage}),w.enqueue(v.value)}})}},m=Promise.withResolvers(),A=Promise.withResolvers();return n("ai.stream",async(h,d)=>{m.resolve(E(await a(),A.resolve));const c=await A.promise;if(D(e,c.outcome,d,t),c.failed===!0)throw c.error},o).catch(m.reject),m.promise}}},O=(e,t,n)=>{let o=e;return t!==void 0&&typeof e!="string"&&(o=Y({middleware:X(n??q(e)??"unknown",t),model:e})),o},Z=[V,B],S=e=>!e.startsWith("@")&&e.includes("/"),b=e=>{throw new f("INTERNAL",`@lunora/ai: ${e} needs the \`AI\` binding (env.AI). Add an \`ai\` binding to wrangler.jsonc, or set ${_} to an OpenAI-compatible proxy for "<provider>/<model>" slugs.`)},x=()=>b("this model id");x.textEmbeddingModel=()=>b("this embedding model id");const J=3040,Q=/^(?:[A-Z]\w*:\s*)?3040\s*:/iu,z=e=>e?.code===J||e instanceof Error&&Q.test(e.message),ee="default",te=new Map([[401,"UNAUTHORIZED"],[403,"FORBIDDEN"],[404,"NOT_FOUND"],[408,"SERVICE_UNAVAILABLE"],[429,"RATE_LIMITED"]]),re=e=>te.get(e)??(e>=400&&e<500?"BAD_REQUEST":"SERVICE_UNAVAILABLE"),ae=300,oe=new Set(["127.0.0.1","[::1]","localhost"]),ne=e=>{const t=p(e,_);if(t===void 0)return;const n=p(e,R),o=URL.canParse(t)?new URL(t):void 0;let a;if(o===void 0?a=`${_} is not a valid URL`:n!==void 0&&o.protocol!=="https:"&&!oe.has(o.hostname)&&(a=`${_} (${o.origin}) is not HTTPS, so ${R} would travel in cleartext — use an https:// URL`),a!==void 0){const E=()=>{throw new f("INTERNAL",`@lunora/ai: ${a}`)};return{chat:E,embedding:E}}return W({apiKey:n??"",baseURL:t,name:"lunora-proxy"})},ie=(e,t)=>{const n=e===void 0?void 0:F(e);return n===void 0?t:{...t,tags:{...n,...t?.tags}}},se=(e,t,n)=>{const o=U(n);if(e!==void 0)return o!==void 0&&e.metadata===void 0?{...e,metadata:o}:e;if(t===void 0)return;const a=p(t,j);if(a!==void 0)return H(t),o===void 0?{id:a}:{id:a,metadata:o}},ye=e=>{const{binding:t,defaultEmbeddingModel:n,defaultModel:o,env:a,gateway:E,metadata:m,provider:A,telemetry:h}=e,d=ne(a),c=ie(a,m),u=se(E,a,c),w=E?.metadata===void 0?U(c):void 0,v=o??p(a,N),y=n??p(a,L),g=A??(t?$({binding:t,gateway:u,providers:Z}):x),C=(r,i)=>S(r)?d!==void 0?d.chat(r):w===void 0?g(r):g(r,{metadata:w}):i?.rejectIfBusy===void 0?g(r):g(r,{rejectIfBusy:i.rejectIfBusy}),k=(r,i)=>{const s=r??v;if(s===void 0||s==="")throw new f("INTERNAL",`@lunora/ai: no model supplied and no default configured — pass a model id, or set ${N} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return typeof s=="string"?O(C(s,i),h,s):O(s,h)},G=r=>{if(d!==void 0&&S(r))return d.embedding(r);const i=g.textEmbeddingModel;if(typeof i!="function")throw new f("INTERNAL","@lunora/ai: the Workers AI provider does not expose `textEmbeddingModel`; pass an AI SDK EmbeddingModel (e.g. from @ai-sdk/openai) to embed()");return i.call(g,r)};return{embeddingModel:r=>{if(typeof r=="object")return r;const i=r??y;if(!i)throw new f("INTERNAL",`@lunora/ai: no embedding model supplied and no default configured — pass an embedding model id or an AI SDK EmbeddingModel, or set ${L} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return G(i)},model:k,run:async(r,i,s)=>{if(!t)return b("ai.run");const I=u!==void 0&&s?.gateway===void 0?{...s,gateway:u}:s;try{return await t.run(r,i,I)}catch(l){throw z(l)?new f("RATE_LIMITED",`@lunora/ai: Workers AI has no free capacity for ${r} (error 3040) — retry later`,{cause:l}):l}},websearch:async(r,i)=>{if(!t)return b("ai.websearch");if(typeof t.websearch!="function")throw new f("NOT_IMPLEMENTED","@lunora/ai: this Workers runtime's `AI` binding has no websearch() — update wrangler / @cloudflare/vite-plugin to a release that ships the Web Search API");const{gatewayId:s,...I}=i??{},l=await t.websearch({...I,gatewayId:s??u?.id??ee,query:r});if(!l.ok){const M=(await l.text().catch(()=>"")).slice(0,ae);throw new f(re(l.status),`@lunora/ai: web search failed with HTTP ${String(l.status)}${M===""?"":`: ${M}`}`)}const T=await l.json().catch(()=>{});if(!Array.isArray(T?.items))throw new f("INTERNAL","@lunora/ai: web search returned a body that is not JSON with an `items` array");return T},workersai:g}};export{ye as default};
@@ -188,6 +188,15 @@ 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
+ * Web Search API (beta). Optional because runtimes and test doubles that
193
+ * predate it have no such method; `ctx.ai.websearch` reports that instead of
194
+ * a bare `TypeError`.
195
+ */
196
+ websearch?: (input: Omit<AiWebSearchOptions, "gatewayId"> & {
197
+ gatewayId: string;
198
+ query: string;
199
+ }) => Promise<Response>;
191
200
  }
192
201
  /**
193
202
  * A Workers AI provider instance — the value returned by `createWorkersAI(...)`.
@@ -354,6 +363,59 @@ interface LunoraAiOptions {
354
363
  */
355
364
  telemetry?: AiTelemetry;
356
365
  }
366
+ /**
367
+ * A search provider the Web Search API brokers. Each one runs under Cloudflare's
368
+ * Zero Data Retention terms. A search bills a provider key stored on the gateway
369
+ * when there is one (the `default` alias unless `byokAlias` names another),
370
+ * otherwise AI Gateway credits at list price.
371
+ * @experimental
372
+ */
373
+ type AiWebSearchProvider = "ceramic" | "exa" | "linkup";
374
+ /**
375
+ * Options for `ctx.ai.websearch(...)`.
376
+ * @experimental
377
+ */
378
+ interface AiWebSearchOptions {
379
+ /** Alias of a provider key stored on the gateway to bill instead of the `default` alias. Without a stored key, gateway credits pay. */
380
+ byokAlias?: string;
381
+ /**
382
+ * The AI Gateway that brokers and bills the search. Defaults to the gateway
383
+ * `ctx.ai` routes inference through (`LUNORA_AI_GATEWAY_ID`), else the
384
+ * account's `default` gateway.
385
+ */
386
+ gatewayId?: string;
387
+ /** Maximum results, 1–10. Defaults to 10. */
388
+ limit?: number;
389
+ /** Defaults to `"ceramic"`. */
390
+ provider?: AiWebSearchProvider;
391
+ }
392
+ /**
393
+ * One result. Web search is discovery only: a result describes a page, and
394
+ * reading the page is a separate `fetch` of its `url`. The optional fields are
395
+ * present only when the provider returns them.
396
+ * @experimental
397
+ */
398
+ interface AiWebSearchItem {
399
+ description?: string;
400
+ faviconUrl?: string;
401
+ imageUrl?: string;
402
+ /** Naive (no timezone) ISO-8601 datetime, e.g. `"2025-11-30T04:39:48"`. */
403
+ lastModifiedDate?: string;
404
+ title: string;
405
+ url: string;
406
+ }
407
+ /**
408
+ * What `ctx.ai.websearch(...)` resolves to.
409
+ * @experimental
410
+ */
411
+ interface AiWebSearchResult {
412
+ items: AiWebSearchItem[];
413
+ metadata: {
414
+ latencyMs: number;
415
+ query: string;
416
+ requestId: string;
417
+ };
418
+ }
357
419
  /**
358
420
  * A model to run against. The AI SDK's {@link LanguageModel} already admits a
359
421
  * bare `string`, so this alias covers both arms of the provider-agnostic seam:
@@ -398,7 +460,14 @@ interface LunoraAi {
398
460
  * supplied.
399
461
  */
400
462
  run: (model: string, inputs: Record<string, unknown>, options?: AiRunOptions) => Promise<unknown>;
463
+ /**
464
+ * Search the web through the binding's Web Search API (beta) to ground a
465
+ * response in live information. Billed to a stored provider key or AI Gateway
466
+ * credits (see {@link AiWebSearchProvider}). Throws if no
467
+ * binding was supplied, or if the runtime's binding has no `websearch()`.
468
+ */
469
+ websearch: (query: string, options?: AiWebSearchOptions) => Promise<AiWebSearchResult>;
401
470
  /** The underlying Workers AI provider — `ai.workersai("@cf/...")` for a raw model. */
402
471
  workersai: WorkersAiProviderLike;
403
472
  }
404
- export { AI_DEFAULT_EMBEDDING_MODEL_ENV as A, EmbeddingModelInput as E, LunoraAiOptions as L, ModelInput as M, ResolvedAiGateway as R, WorkersAiProviderLike as W, LunoraAi as a, AI_DEFAULT_MODEL_ENV as b, AI_GATEWAY_ACCOUNT_ID_ENV as c, AI_GATEWAY_ID_ENV as d, AI_GATEWAY_METADATA_MAX_KEYS as e, AI_GATEWAY_TAGS_ENV as f, AI_GATEWAY_TOKEN_ENV as g, AI_PROXY_TOKEN_ENV as h, AI_PROXY_URL_ENV as i, AiBindingLike as j, AiGatewayMetadata as k, AiGatewayOptions as l, AiMetrics as m, AiModelOptions as n, AiRunOptions as o, AiSpan as p, AiTelemetry as q, AiTracer as r, buildAiGatewayMetadataFields as s, readAiGatewayEnvTags as t, resolveAiGateway as u };
473
+ export { AI_DEFAULT_EMBEDDING_MODEL_ENV as A, EmbeddingModelInput as E, LunoraAiOptions as L, ModelInput as M, ResolvedAiGateway as R, WorkersAiProviderLike as W, LunoraAi as a, AI_DEFAULT_MODEL_ENV as b, AI_GATEWAY_ACCOUNT_ID_ENV as c, AI_GATEWAY_ID_ENV as d, AI_GATEWAY_METADATA_MAX_KEYS as e, AI_GATEWAY_TAGS_ENV as f, AI_GATEWAY_TOKEN_ENV as g, AI_PROXY_TOKEN_ENV as h, AI_PROXY_URL_ENV as i, AiBindingLike as j, AiGatewayMetadata as k, AiGatewayOptions as l, AiMetrics as m, AiModelOptions as n, AiRunOptions as o, AiSpan as p, AiTelemetry as q, AiTracer as r, AiWebSearchItem as s, AiWebSearchOptions as t, AiWebSearchProvider as u, AiWebSearchResult as v, buildAiGatewayMetadataFields as w, readAiGatewayEnvTags as x, resolveAiGateway as y };
@@ -188,6 +188,15 @@ 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
+ * Web Search API (beta). Optional because runtimes and test doubles that
193
+ * predate it have no such method; `ctx.ai.websearch` reports that instead of
194
+ * a bare `TypeError`.
195
+ */
196
+ websearch?: (input: Omit<AiWebSearchOptions, "gatewayId"> & {
197
+ gatewayId: string;
198
+ query: string;
199
+ }) => Promise<Response>;
191
200
  }
192
201
  /**
193
202
  * A Workers AI provider instance — the value returned by `createWorkersAI(...)`.
@@ -354,6 +363,59 @@ interface LunoraAiOptions {
354
363
  */
355
364
  telemetry?: AiTelemetry;
356
365
  }
366
+ /**
367
+ * A search provider the Web Search API brokers. Each one runs under Cloudflare's
368
+ * Zero Data Retention terms. A search bills a provider key stored on the gateway
369
+ * when there is one (the `default` alias unless `byokAlias` names another),
370
+ * otherwise AI Gateway credits at list price.
371
+ * @experimental
372
+ */
373
+ type AiWebSearchProvider = "ceramic" | "exa" | "linkup";
374
+ /**
375
+ * Options for `ctx.ai.websearch(...)`.
376
+ * @experimental
377
+ */
378
+ interface AiWebSearchOptions {
379
+ /** Alias of a provider key stored on the gateway to bill instead of the `default` alias. Without a stored key, gateway credits pay. */
380
+ byokAlias?: string;
381
+ /**
382
+ * The AI Gateway that brokers and bills the search. Defaults to the gateway
383
+ * `ctx.ai` routes inference through (`LUNORA_AI_GATEWAY_ID`), else the
384
+ * account's `default` gateway.
385
+ */
386
+ gatewayId?: string;
387
+ /** Maximum results, 1–10. Defaults to 10. */
388
+ limit?: number;
389
+ /** Defaults to `"ceramic"`. */
390
+ provider?: AiWebSearchProvider;
391
+ }
392
+ /**
393
+ * One result. Web search is discovery only: a result describes a page, and
394
+ * reading the page is a separate `fetch` of its `url`. The optional fields are
395
+ * present only when the provider returns them.
396
+ * @experimental
397
+ */
398
+ interface AiWebSearchItem {
399
+ description?: string;
400
+ faviconUrl?: string;
401
+ imageUrl?: string;
402
+ /** Naive (no timezone) ISO-8601 datetime, e.g. `"2025-11-30T04:39:48"`. */
403
+ lastModifiedDate?: string;
404
+ title: string;
405
+ url: string;
406
+ }
407
+ /**
408
+ * What `ctx.ai.websearch(...)` resolves to.
409
+ * @experimental
410
+ */
411
+ interface AiWebSearchResult {
412
+ items: AiWebSearchItem[];
413
+ metadata: {
414
+ latencyMs: number;
415
+ query: string;
416
+ requestId: string;
417
+ };
418
+ }
357
419
  /**
358
420
  * A model to run against. The AI SDK's {@link LanguageModel} already admits a
359
421
  * bare `string`, so this alias covers both arms of the provider-agnostic seam:
@@ -398,7 +460,14 @@ interface LunoraAi {
398
460
  * supplied.
399
461
  */
400
462
  run: (model: string, inputs: Record<string, unknown>, options?: AiRunOptions) => Promise<unknown>;
463
+ /**
464
+ * Search the web through the binding's Web Search API (beta) to ground a
465
+ * response in live information. Billed to a stored provider key or AI Gateway
466
+ * credits (see {@link AiWebSearchProvider}). Throws if no
467
+ * binding was supplied, or if the runtime's binding has no `websearch()`.
468
+ */
469
+ websearch: (query: string, options?: AiWebSearchOptions) => Promise<AiWebSearchResult>;
401
470
  /** The underlying Workers AI provider — `ai.workersai("@cf/...")` for a raw model. */
402
471
  workersai: WorkersAiProviderLike;
403
472
  }
404
- export { AI_DEFAULT_EMBEDDING_MODEL_ENV as A, EmbeddingModelInput as E, LunoraAiOptions as L, ModelInput as M, ResolvedAiGateway as R, WorkersAiProviderLike as W, LunoraAi as a, AI_DEFAULT_MODEL_ENV as b, AI_GATEWAY_ACCOUNT_ID_ENV as c, AI_GATEWAY_ID_ENV as d, AI_GATEWAY_METADATA_MAX_KEYS as e, AI_GATEWAY_TAGS_ENV as f, AI_GATEWAY_TOKEN_ENV as g, AI_PROXY_TOKEN_ENV as h, AI_PROXY_URL_ENV as i, AiBindingLike as j, AiGatewayMetadata as k, AiGatewayOptions as l, AiMetrics as m, AiModelOptions as n, AiRunOptions as o, AiSpan as p, AiTelemetry as q, AiTracer as r, buildAiGatewayMetadataFields as s, readAiGatewayEnvTags as t, resolveAiGateway as u };
473
+ export { AI_DEFAULT_EMBEDDING_MODEL_ENV as A, EmbeddingModelInput as E, LunoraAiOptions as L, ModelInput as M, ResolvedAiGateway as R, WorkersAiProviderLike as W, LunoraAi as a, AI_DEFAULT_MODEL_ENV as b, AI_GATEWAY_ACCOUNT_ID_ENV as c, AI_GATEWAY_ID_ENV as d, AI_GATEWAY_METADATA_MAX_KEYS as e, AI_GATEWAY_TAGS_ENV as f, AI_GATEWAY_TOKEN_ENV as g, AI_PROXY_TOKEN_ENV as h, AI_PROXY_URL_ENV as i, AiBindingLike as j, AiGatewayMetadata as k, AiGatewayOptions as l, AiMetrics as m, AiModelOptions as n, AiRunOptions as o, AiSpan as p, AiTelemetry as q, AiTracer as r, AiWebSearchItem as s, AiWebSearchOptions as t, AiWebSearchProvider as u, AiWebSearchResult as v, buildAiGatewayMetadataFields as w, readAiGatewayEnvTags as x, resolveAiGateway as y };
@@ -1,5 +1,5 @@
1
1
  import { Tool } from 'ai';
2
- import { E as EmbeddingModelInput, a as LunoraAi } from "../packem_shared/types.d-ChtZguP_.mjs";
2
+ import { E as EmbeddingModelInput, a as LunoraAi } from "../packem_shared/types.d-D-WK1t2B.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-ChtZguP_.js";
2
+ import { E as EmbeddingModelInput, a as LunoraAi } from "../packem_shared/types.d-D-WK1t2B.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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/ai",
3
- "version": "1.0.0-alpha.105",
3
+ "version": "1.0.0-alpha.106",
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",
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "@ai-sdk/anthropic": "4.0.49",
57
57
  "@ai-sdk/openai": "4.0.60",
58
- "@lunora/errors": "1.0.0-alpha.48",
58
+ "@lunora/errors": "1.0.0-alpha.49",
59
59
  "ai": "7.0.93",
60
60
  "workers-ai-provider": "4.0.0"
61
61
  },
@@ -1 +0,0 @@
1
- import{createOpenAI as S}from"@ai-sdk/openai";import{LunoraError as E}from"@lunora/errors";import{createWorkersAI as $}from"workers-ai-provider";import{anthropic as C}from"workers-ai-provider/anthropic";import{openai as W}from"workers-ai-provider/openai";import{buildAiGatewayMetadataFields as D,readEnv as A,AI_DEFAULT_MODEL_ENV as M,AI_DEFAULT_EMBEDDING_MODEL_ENV as I,AI_PROXY_URL_ENV as h,AI_PROXY_TOKEN_ENV as R,readAiGatewayEnvTags as j,AI_GATEWAY_ID_ENV as B,warnIgnoredBindingToken as V}from"./AI_DEFAULT_EMBEDDING_MODEL_ENV-IwcHZiKj.mjs";import{wrapLanguageModel as Y}from"ai";import{m as F,r as T}from"./usage-GPnTSqR8.mjs";import"./DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs";const K={setAttribute:()=>{},setAttributes:()=>{}},O=async(e,t)=>t(O,K),q=(e,{metrics:t,trace:n=O})=>{const a={"gen_ai.operation.name":"chat","gen_ai.request.model":e};return{wrapGenerate:async({doGenerate:o})=>n("ai.generate",async(u,m)=>{const g=await o();return T(e,g,m,t),g},a),wrapStream:async({doStream:o})=>{const u=(w,s)=>{const c=w.stream.getReader();let l={};return{...w,stream:new ReadableStream({cancel:async f=>{s({outcome:l}),await c.cancel(f)},pull:async f=>{let v;try{v=await c.read()}catch(y){s({error:y,failed:!0,outcome:l}),f.error(y);return}if(v.done){s({outcome:l}),f.close();return}v.value.type==="finish"&&(l={providerMetadata:v.value.providerMetadata,usage:v.value.usage}),f.enqueue(v.value)}})}},m=Promise.withResolvers(),g=Promise.withResolvers();return n("ai.stream",async(w,s)=>{m.resolve(u(await o(),g.resolve));const c=await g.promise;if(T(e,c.outcome,s,t),c.failed===!0)throw c.error},a).catch(m.reject),m.promise}}},L=(e,t,n)=>{let a=e;return t!==void 0&&typeof e!="string"&&(a=Y({middleware:q(n??F(e)??"unknown",t),model:e})),a},X=[W,C],N=e=>!e.startsWith("@")&&e.includes("/"),b=e=>{throw new E("INTERNAL",`@lunora/ai: ${e} needs the \`AI\` binding (env.AI). Add an \`ai\` binding to wrangler.jsonc, or set ${h} to an OpenAI-compatible proxy for "<provider>/<model>" slugs.`)},P=()=>b("this model id");P.textEmbeddingModel=()=>b("this embedding model id");const H=3040,Z=/^(?:[A-Z]\w*:\s*)?3040\s*:/iu,z=e=>e?.code===H||e instanceof Error&&Z.test(e.message),J=new Set(["127.0.0.1","[::1]","localhost"]),Q=e=>{const t=A(e,h);if(t===void 0)return;const n=A(e,R),a=URL.canParse(t)?new URL(t):void 0;let o;if(a===void 0?o=`${h} is not a valid URL`:n!==void 0&&a.protocol!=="https:"&&!J.has(a.hostname)&&(o=`${h} (${a.origin}) is not HTTPS, so ${R} would travel in cleartext — use an https:// URL`),o!==void 0){const u=()=>{throw new E("INTERNAL",`@lunora/ai: ${o}`)};return{chat:u,embedding:u}}return S({apiKey:n??"",baseURL:t,name:"lunora-proxy"})},ee=(e,t)=>{const n=e===void 0?void 0:j(e);return n===void 0?t:{...t,tags:{...n,...t?.tags}}},te=(e,t,n)=>{const a=D(n);if(e!==void 0)return a!==void 0&&e.metadata===void 0?{...e,metadata:a}:e;if(t===void 0)return;const o=A(t,B);if(o!==void 0)return V(t),a===void 0?{id:o}:{id:o,metadata:a}},ve=e=>{const{binding:t,defaultEmbeddingModel:n,defaultModel:a,env:o,gateway:u,metadata:m,provider:g,telemetry:w}=e,s=Q(o),c=ee(o,m),l=te(u,o,c),f=u?.metadata===void 0?D(c):void 0,v=a??A(o,M),y=n??A(o,I),p=g??(t?$({binding:t,gateway:l,providers:X}):P),x=(r,i)=>N(r)?s!==void 0?s.chat(r):f===void 0?p(r):p(r,{metadata:f}):i?.rejectIfBusy===void 0?p(r):p(r,{rejectIfBusy:i.rejectIfBusy}),U=(r,i)=>{const d=r??v;if(d===void 0||d==="")throw new E("INTERNAL",`@lunora/ai: no model supplied and no default configured — pass a model id, or set ${M} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return typeof d=="string"?L(x(d,i),w,d):L(d,w)},k=r=>{if(s!==void 0&&N(r))return s.embedding(r);const i=p.textEmbeddingModel;if(typeof i!="function")throw new E("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(p,r)};return{embeddingModel:r=>{if(typeof r=="object")return r;const i=r??y;if(!i)throw new E("INTERNAL",`@lunora/ai: no embedding model supplied and no default configured — pass an embedding model id or an AI SDK EmbeddingModel, or set ${I} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return k(i)},model:U,run:async(r,i,d)=>{if(!t)return b("ai.run");const G=l!==void 0&&d?.gateway===void 0?{...d,gateway:l}:d;try{return await t.run(r,i,G)}catch(_){throw z(_)?new E("RATE_LIMITED",`@lunora/ai: Workers AI has no free capacity for ${r} (error 3040) — retry later`,{cause:_}):_}},workersai:p}};export{ve as default};