@browserlessapi/inferlo 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +9 -5
  2. package/dist/ai/ai-sdk.d.ts +48 -0
  3. package/dist/ai/ai-sdk.js +235 -0
  4. package/dist/ai/ai-sdk.js.map +1 -0
  5. package/dist/ai/index.d.ts +37 -3
  6. package/dist/ai/index.js +96 -7
  7. package/dist/ai/index.js.map +1 -1
  8. package/dist/ai/public.d.ts +5 -1
  9. package/dist/ai/public.js +2 -0
  10. package/dist/ai/public.js.map +1 -1
  11. package/dist/ai/ui.d.ts +47 -0
  12. package/dist/ai/ui.js +33 -0
  13. package/dist/ai/ui.js.map +1 -0
  14. package/dist/app.d.ts +55 -4
  15. package/dist/app.js +439 -39
  16. package/dist/app.js.map +1 -1
  17. package/dist/astro.d.ts +60 -0
  18. package/dist/astro.js +68 -0
  19. package/dist/astro.js.map +1 -0
  20. package/dist/audit.d.ts +8 -1
  21. package/dist/audit.js +9 -2
  22. package/dist/audit.js.map +1 -1
  23. package/dist/auth.d.ts +1 -0
  24. package/dist/auth.js +1 -0
  25. package/dist/auth.js.map +1 -1
  26. package/dist/authjs.d.ts +70 -0
  27. package/dist/authjs.js +94 -0
  28. package/dist/authjs.js.map +1 -0
  29. package/dist/azure.d.ts +66 -0
  30. package/dist/azure.js +119 -0
  31. package/dist/azure.js.map +1 -0
  32. package/dist/better-auth.d.ts +89 -0
  33. package/dist/better-auth.js +75 -0
  34. package/dist/better-auth.js.map +1 -0
  35. package/dist/cache.d.ts +13 -9
  36. package/dist/cache.js +11 -9
  37. package/dist/cache.js.map +1 -1
  38. package/dist/cli/main.js +5 -5
  39. package/dist/cli/main.js.map +1 -1
  40. package/dist/client-ip.d.ts +41 -1
  41. package/dist/client-ip.js +85 -1
  42. package/dist/client-ip.js.map +1 -1
  43. package/dist/cross-site.d.ts +14 -0
  44. package/dist/cross-site.js +56 -0
  45. package/dist/cross-site.js.map +1 -0
  46. package/dist/db/memory.js +2 -1
  47. package/dist/db/memory.js.map +1 -1
  48. package/dist/db/postgres.js +23 -6
  49. package/dist/db/postgres.js.map +1 -1
  50. package/dist/db/table.d.ts +37 -5
  51. package/dist/db/table.js +96 -15
  52. package/dist/db/table.js.map +1 -1
  53. package/dist/db/types.d.ts +2 -0
  54. package/dist/describe.d.ts +23 -5
  55. package/dist/describe.js +20 -2
  56. package/dist/describe.js.map +1 -1
  57. package/dist/disk-storage.d.ts +6 -0
  58. package/dist/disk-storage.js +96 -0
  59. package/dist/disk-storage.js.map +1 -0
  60. package/dist/events.d.ts +134 -0
  61. package/dist/events.js +196 -0
  62. package/dist/events.js.map +1 -0
  63. package/dist/files.d.ts +23 -0
  64. package/dist/files.js +168 -0
  65. package/dist/files.js.map +1 -0
  66. package/dist/index.d.ts +26 -8
  67. package/dist/index.js +10 -2
  68. package/dist/index.js.map +1 -1
  69. package/dist/jobs.d.ts +56 -10
  70. package/dist/jobs.js +71 -13
  71. package/dist/jobs.js.map +1 -1
  72. package/dist/jwt.d.ts +83 -0
  73. package/dist/jwt.js +312 -0
  74. package/dist/jwt.js.map +1 -0
  75. package/dist/langchain.d.ts +58 -0
  76. package/dist/langchain.js +220 -0
  77. package/dist/langchain.js.map +1 -0
  78. package/dist/model.d.ts +23 -6
  79. package/dist/model.js +41 -7
  80. package/dist/model.js.map +1 -1
  81. package/dist/oidc.d.ts +70 -0
  82. package/dist/oidc.js +379 -0
  83. package/dist/oidc.js.map +1 -0
  84. package/dist/openapi.js +17 -2
  85. package/dist/openapi.js.map +1 -1
  86. package/dist/passport.d.ts +54 -0
  87. package/dist/passport.js +68 -0
  88. package/dist/passport.js.map +1 -0
  89. package/dist/postgres-js.d.ts +39 -0
  90. package/dist/postgres-js.js +71 -0
  91. package/dist/postgres-js.js.map +1 -0
  92. package/dist/rate-limit.d.ts +5 -3
  93. package/dist/rate-limit.js +3 -3
  94. package/dist/rate-limit.js.map +1 -1
  95. package/dist/redis-events.d.ts +27 -0
  96. package/dist/redis-events.js +163 -0
  97. package/dist/redis-events.js.map +1 -0
  98. package/dist/redis-jobs.d.ts +24 -0
  99. package/dist/redis-jobs.js +203 -0
  100. package/dist/redis-jobs.js.map +1 -0
  101. package/dist/redis-sockets.d.ts +24 -0
  102. package/dist/redis-sockets.js +75 -0
  103. package/dist/redis-sockets.js.map +1 -0
  104. package/dist/redis.d.ts +30 -0
  105. package/dist/redis.js +106 -0
  106. package/dist/redis.js.map +1 -0
  107. package/dist/response-body.d.ts +6 -0
  108. package/dist/response-body.js +7 -0
  109. package/dist/response-body.js.map +1 -0
  110. package/dist/route.d.ts +117 -3
  111. package/dist/route.js +27 -0
  112. package/dist/route.js.map +1 -1
  113. package/dist/schema.d.ts +23 -3
  114. package/dist/schema.js +70 -0
  115. package/dist/schema.js.map +1 -1
  116. package/dist/serve.d.ts +24 -1
  117. package/dist/serve.js +200 -8
  118. package/dist/serve.js.map +1 -1
  119. package/dist/service-client.d.ts +91 -0
  120. package/dist/service-client.js +177 -0
  121. package/dist/service-client.js.map +1 -0
  122. package/dist/sessions.d.ts +2 -0
  123. package/dist/sessions.js +36 -4
  124. package/dist/sessions.js.map +1 -1
  125. package/dist/sockets.d.ts +58 -0
  126. package/dist/sockets.js +111 -0
  127. package/dist/sockets.js.map +1 -0
  128. package/dist/storage.d.ts +47 -0
  129. package/dist/storage.js +123 -0
  130. package/dist/storage.js.map +1 -0
  131. package/dist/tracing.d.ts +7 -3
  132. package/dist/tracing.js +8 -2
  133. package/dist/tracing.js.map +1 -1
  134. package/dist/websocket.d.ts +81 -0
  135. package/dist/websocket.js +251 -0
  136. package/dist/websocket.js.map +1 -0
  137. package/llms.txt +2 -0
  138. package/package.json +77 -5
  139. package/templates/app/AGENTS.md +9 -5
  140. package/templates/app/test/notes.test.ts +2 -0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **The enterprise-ready TypeScript backend framework, with AI models built in.**
4
4
 
5
- Inferlo is a batteries-included TypeScript backend framework for production APIs, SaaS products and enterprise services, in the same class as NestJS, Fastify and AdonisJS. It gives you routing, validation, a typed database layer with PostgreSQL and migrations, auth, rate limiting, background jobs, cron, OpenAPI and secure defaults in one package with zero runtime dependencies, running on Node.js, Bun and edge runtimes.
5
+ Inferlo is a batteries-included TypeScript backend framework for production APIs, SaaS products and enterprise services, in the same class as NestJS, Fastify and AdonisJS. It gives you routing, validation, a typed database layer with PostgreSQL and migrations, auth, rate limiting, background jobs, cron, OpenAPI and secure defaults in one package with zero runtime dependencies, running on Node.js, Bun, Deno and edge runtimes.
6
6
 
7
7
  What sets it apart is that AI models are part of the core, not a plugin: Anthropic, OpenAI, Gemini, local models through Ollama and any OpenAI-compatible server behind one API, with fallback between models, typed structured output, streaming, tool calling, image input and per-user usage and cost tracking, plus an MCP server built from your routes.
8
8
 
@@ -47,7 +47,7 @@ That one model definition gives you the database table, insert and update valida
47
47
  - **Embeddings** for search and recommendations.
48
48
  - **Usage, cost and budgets.** Every call is tracked per user, so you can bill customers and stop calls when their credits run out.
49
49
  - **MCP server.** Mark a route `tool: true` and AI agents can call your API, with the same permissions as any other client.
50
- - **Use it in any framework.** `import { createAI } from "@browserlessapi/inferlo/ai"` gives you the same AI module without `createApp`, for Express, Hono, Fastify, NestJS or a plain script, and `toNodeHandler(app, { prefix: "/ai" })` mounts a whole Inferlo app (routes, OpenAPI, MCP) inside Express, Fastify, NestJS or `node:http`, `hono.use(inferloAI(ai))` from `@browserlessapi/inferlo/hono` gives Hono handlers `c.var.ai`, `server.use(inferloAI(ai))` from `@browserlessapi/inferlo/express` gives Express handlers `req.ai`, and `fastify.register(inferloPlugin, { ai })` from `@browserlessapi/inferlo/fastify` gives Fastify `request.ai`, `server.register({ plugin: inferloPlugin, options: { ai } })` from `@browserlessapi/inferlo/hapi` gives hapi `request.ai` (and `options: { app }` with `routes: { prefix }` serves a whole Inferlo app), `new Elysia().use(inferloAI(ai))` from `@browserlessapi/inferlo/elysia` gives Elysia handlers `ai`, `InferloModule.forRoot({ ai })` from `@browserlessapi/inferlo/nestjs` makes the AI client injectable in NestJS, the provider from `@browserlessapi/inferlo/adonisjs` gives AdonisJS routes `ctx.ai`, `@Configuration({ imports: [inferloAI(ai)] })` from `@browserlessapi/inferlo/tsed` makes the AI client injectable in Ts.ED as `InferloAI`, `trpc.procedure.use(inferloAI(ai))` from `@browserlessapi/inferlo/trpc` gives tRPC procedures `ctx.ai` while `trpcTools(router, { caller, procedures })` lets a model call tRPC procedures as tools, `koa.use(inferloAI(ai))` and `koa.use(toKoaMiddleware(app, { prefix: "/api" }))` from `@browserlessapi/inferlo/koa` give Koa `ctx.ai` and a whole Inferlo app, `export const { GET, POST } = toNextHandlers(app, { prefix: "/api" })` from `@browserlessapi/inferlo/nextjs` serves a whole Inferlo app from a Next.js App Router route, `export default toH3Handler(app, { prefix: "/api" })` from `@browserlessapi/inferlo/h3` serves it from a Nuxt or Nitro server route (with `event.context.ai` from a Nitro plugin), `export const { GET, POST, fallback } = toSvelteKitHandlers(app, { prefix: "/api" })` from `@browserlessapi/inferlo/sveltekit` serves it from a SvelteKit rest route (with `event.locals.ai` from a handle hook), `export const { loader, action } = toReactRouterHandlers(app, { prefix: "/api" })` from `@browserlessapi/inferlo/react-router` serves it from a React Router (Remix) resource route, `export const handler = toLambdaHandler(app)` from `@browserlessapi/inferlo/lambda` runs it on AWS Lambda (function URLs and API Gateway, with response streaming), and an Encore.ts raw endpoint serves a whole Inferlo app with `api.raw({ expose: true, method: "*", path: "/inferlo/*rest" }, toNodeHandler(app, { prefix: "/inferlo" }))` (see [Use with other frameworks](docs/integrations.md)).
50
+ - **Use it in any framework.** `import { createAI } from "@browserlessapi/inferlo/ai"` gives you the same AI module without `createApp`, for Express, Hono, Fastify, NestJS or a plain script, and `toNodeHandler(app, { prefix: "/ai" })` mounts a whole Inferlo app (routes, OpenAPI, MCP) inside Express, Fastify, NestJS or `node:http`, `hono.use(inferloAI(ai))` from `@browserlessapi/inferlo/hono` gives Hono handlers `c.var.ai`, `server.use(inferloAI(ai))` from `@browserlessapi/inferlo/express` gives Express handlers `req.ai`, and `fastify.register(inferloPlugin, { ai })` from `@browserlessapi/inferlo/fastify` gives Fastify `request.ai`, `server.register({ plugin: inferloPlugin, options: { ai } })` from `@browserlessapi/inferlo/hapi` gives hapi `request.ai` (and `options: { app }` with `routes: { prefix }` serves a whole Inferlo app), `new Elysia().use(inferloAI(ai))` from `@browserlessapi/inferlo/elysia` gives Elysia handlers `ai`, `InferloModule.forRoot({ ai })` from `@browserlessapi/inferlo/nestjs` makes the AI client injectable in NestJS, the provider from `@browserlessapi/inferlo/adonisjs` gives AdonisJS routes `ctx.ai`, `@Configuration({ imports: [inferloAI(ai)] })` from `@browserlessapi/inferlo/tsed` makes the AI client injectable in Ts.ED as `InferloAI`, `trpc.procedure.use(inferloAI(ai))` from `@browserlessapi/inferlo/trpc` gives tRPC procedures `ctx.ai` while `trpcTools(router, { caller, procedures })` lets a model call tRPC procedures as tools, `koa.use(inferloAI(ai))` and `koa.use(toKoaMiddleware(app, { prefix: "/api" }))` from `@browserlessapi/inferlo/koa` give Koa `ctx.ai` and a whole Inferlo app, `export const { GET, POST } = toNextHandlers(app, { prefix: "/api" })` from `@browserlessapi/inferlo/nextjs` serves a whole Inferlo app from a Next.js App Router route, `export default toH3Handler(app, { prefix: "/api" })` from `@browserlessapi/inferlo/h3` serves it from a Nuxt or Nitro server route (with `event.context.ai` from a Nitro plugin), `export const { GET, POST, fallback } = toSvelteKitHandlers(app, { prefix: "/api" })` from `@browserlessapi/inferlo/sveltekit` serves it from a SvelteKit rest route (with `event.locals.ai` from a handle hook), `export const ALL = toAstroHandler(app, { prefix: "/api" })` from `@browserlessapi/inferlo/astro` serves it from an Astro endpoint (with `locals.ai` from a middleware), `export const { loader, action } = toReactRouterHandlers(app, { prefix: "/api" })` from `@browserlessapi/inferlo/react-router` serves it from a React Router (Remix) resource route, `export const handler = toLambdaHandler(app)` from `@browserlessapi/inferlo/lambda` runs it on AWS Lambda (function URLs and API Gateway, with response streaming), `handler: toAzureHandler(app, { prefix: "/api" })` from `@browserlessapi/inferlo/azure` runs it in an Azure Functions HTTP trigger (with streaming), `functions.http("inferlo", toNodeHandler(app))` runs it as a Google Cloud Run function, `export default app` runs it as a Cloudflare Worker or a Vercel Function and `export default app.fetch` as a Netlify Function, and an Encore.ts raw endpoint serves a whole Inferlo app with `api.raw({ expose: true, method: "*", path: "/inferlo/*rest" }, toNodeHandler(app, { prefix: "/inferlo" }))` An app that already uses Drizzle ORM or Prisma adds Inferlo on the same Postgres pool, with Drizzle and Inferlo writes in one transaction through `tx.$pgClient()` (see [Use with other frameworks](docs/integrations.md)).
51
51
  - **Background jobs for slow AI work.** Queue a typed job from a route with `ctx.jobs.enqueue(...)` and it runs after the response, with retries, from a Postgres queue shared by every instance (`inferlo worker` runs them in their own process), and schedule jobs with `cron("0 3 * * *", job)`.
52
52
 
53
53
  ## Why Inferlo
@@ -81,9 +81,9 @@ Inferlo is also designed so AI coding tools (Claude Code, Cursor, Codex, NeonCod
81
81
 
82
82
  ## Quick start
83
83
 
84
- Requires Node.js 22.18+ or Bun 1.3+. TypeScript runs directly with no build step.
84
+ Requires Node.js 22.18+, Bun 1.3+ or Deno 2.9+. TypeScript runs directly with no build step.
85
85
 
86
- Every release is tested on Node.js 22.18, 22, 24, 26 and the current release; Bun 1.3.0 and latest; PostgreSQL 14, 15, 16, 17, 18 and latest; and TypeScript 5.9 and latest.
86
+ Every release is tested on Node.js 22.18, 22, 24, 26 and the current release; Bun 1.3.0 and latest; Deno 2.9.0 and latest; PostgreSQL 14, 15, 16, 17, 18 and latest; and TypeScript 5.9 and latest.
87
87
 
88
88
  ```bash
89
89
  npx @browserlessapi/inferlo new my-app
@@ -98,7 +98,7 @@ npm install @browserlessapi/inferlo
98
98
 
99
99
  ## Features
100
100
 
101
- - **Routing:** `route.get/post/put/patch/delete`, typed path params, query coercion, CORS, middleware, body limits
101
+ - **Routing:** `route.get/post/put/patch/delete`, typed path params, query coercion, CORS, middleware, body limits, WebSockets with `route.ws(path, { message })` on Node.js, Bun and Deno
102
102
  - **Schemas:** `t.string()`, `t.email()`, `t.enum()`, `t.ref()`, ... with `.optional()`, `.default()`, `.pick()`, `.partial()`, JSON Schema output
103
103
  - **Database:** typed tables (`db.tickets.findMany(...)`), PostgreSQL and in-memory adapters, `inferlo db push`, versioned migrations (`inferlo db migrate`), transactions, unique and foreign keys
104
104
  - **Auth:** `auth.resolve(req)`, `bearer()` API keys, roles, password hashing with Web Crypto
@@ -119,7 +119,10 @@ npm install @browserlessapi/inferlo
119
119
  - [Auth](docs/auth.md)
120
120
  - [AI](docs/ai.md)
121
121
  - [Modules](docs/modules.md)
122
+ - [Service-to-service calls](docs/services.md)
122
123
  - [Caching](docs/caching.md)
124
+ - [File storage](docs/storage.md)
125
+ - [Redis](docs/redis.md)
123
126
  - [MCP, OpenAPI and describe](docs/mcp.md)
124
127
  - [CLI](docs/cli.md)
125
128
  - [Testing](docs/testing.md)
@@ -132,6 +135,7 @@ npm install @browserlessapi/inferlo
132
135
  - [Security](docs/security.md)
133
136
  - [Observability](docs/observability.md)
134
137
  - [Deployment](docs/deploy.md)
138
+ - [Performance](docs/performance.md)
135
139
  - [Error codes](docs/errors.md)
136
140
  - [Support policy](docs/support.md)
137
141
  - [Example: helpdesk with AI triage](examples/helpdesk)
@@ -0,0 +1,48 @@
1
+ import { type Provider } from "./providers.ts";
2
+ /** The parts of an AI SDK language model (LanguageModelV2, V3 or V4) that Inferlo uses. */
3
+ export interface AISdkLanguageModel {
4
+ readonly specificationVersion: string;
5
+ readonly provider: string;
6
+ readonly modelId: string;
7
+ doGenerate(options: any): PromiseLike<{
8
+ content: readonly object[];
9
+ usage: unknown;
10
+ }>;
11
+ doStream(options: any): PromiseLike<{
12
+ stream: ReadableStream<any>;
13
+ }>;
14
+ }
15
+ /** The parts of an AI SDK embedding model (EmbeddingModelV2, V3 or V4) that Inferlo uses. */
16
+ export interface AISdkEmbeddingModel {
17
+ readonly specificationVersion: string;
18
+ readonly modelId: string;
19
+ readonly maxEmbeddingsPerCall?: PromiseLike<number | undefined> | number | undefined;
20
+ doEmbed(options: {
21
+ values: string[];
22
+ abortSignal?: AbortSignal;
23
+ }): PromiseLike<{
24
+ embeddings: number[][];
25
+ usage?: {
26
+ tokens: number;
27
+ };
28
+ }>;
29
+ }
30
+ /**
31
+ * An AI SDK provider: what createOpenAI(), createAnthropic(), bedrock, mistral, gateway and the others
32
+ * return, and what customProvider() from "ai" builds. Only languageModel is required.
33
+ */
34
+ export interface AISdkProvider {
35
+ readonly specificationVersion?: string;
36
+ languageModel(modelId: string): AISdkLanguageModel;
37
+ embeddingModel?(modelId: string): AISdkEmbeddingModel;
38
+ textEmbeddingModel?(modelId: string): AISdkEmbeddingModel;
39
+ }
40
+ export interface AISdkOptions {
41
+ /** Provider name in errors, usage and traces. Default: the AI SDK provider's own name (the part before the first dot, e.g. "openai"). */
42
+ name?: string;
43
+ }
44
+ /**
45
+ * Use any Vercel AI SDK provider in createAI({ providers }) or createApp({ ai: { providers } }):
46
+ * providers: { openai: aiSdk(createOpenAI({ apiKey })) }, then models: { fast: "openai:gpt-5-mini" }.
47
+ */
48
+ export declare function aiSdk(provider: AISdkProvider, options?: AISdkOptions): Provider;
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Vercel AI SDK providers as Inferlo providers: every @ai-sdk/* provider (OpenAI, Anthropic, Google,
3
+ * Mistral, Amazon Bedrock, Azure, Vertex, Groq, xAI, the AI Gateway, ...) works with aliases, fallback,
4
+ * usage tracking, structured output, tools, streaming and embeddings.
5
+ *
6
+ * import { createMistral } from "@ai-sdk/mistral";
7
+ * createAI({ providers: { mistral: aiSdk(createMistral({ apiKey })) }, models: { fast: "mistral:mistral-small-latest" } })
8
+ *
9
+ * Inferlo has no dependency on the AI SDK: aiSdk() calls the provider's languageModel(id) and
10
+ * embeddingModel(id) (textEmbeddingModel(id) on older providers) and speaks the model specification
11
+ * versions v2 (ai 5), v3 (ai 6) and v4 (ai 7) itself.
12
+ */
13
+ import { InferloError } from "../errors.js";
14
+ import { imageData, ProviderError } from "./providers.js";
15
+ /** The specification versions aiSdk() speaks. */
16
+ const VERSIONS = new Set(["v2", "v3", "v4"]);
17
+ /**
18
+ * Use any Vercel AI SDK provider in createAI({ providers }) or createApp({ ai: { providers } }):
19
+ * providers: { openai: aiSdk(createOpenAI({ apiKey })) }, then models: { fast: "openai:gpt-5-mini" }.
20
+ */
21
+ export function aiSdk(provider, options = {}) {
22
+ if (!provider || typeof provider !== "object" && typeof provider !== "function" || typeof provider.languageModel !== "function") {
23
+ throw new InferloError("IL5029", "aiSdk() needs an AI SDK provider, such as createOpenAI({ apiKey }) from @ai-sdk/openai.", {
24
+ fix: 'Pass the provider, not a model: aiSdk(createOpenAI({ apiKey })) with models: { fast: "openai:gpt-5-mini" }. For single models use customProvider({ languageModels: { ... } }) from "ai".',
25
+ });
26
+ }
27
+ if (options.name !== undefined && (typeof options.name !== "string" || !/^[a-zA-Z0-9._-]{1,64}$/.test(options.name))) {
28
+ throw new InferloError("IL5029", `aiSdk() name must be 1 to 64 letters, digits, dots, dashes or underscores, got ${JSON.stringify(options.name)}.`);
29
+ }
30
+ const languageModels = new Map();
31
+ const embeddingModels = new Map();
32
+ let label = options.name;
33
+ const nameOf = (model) => (label ??= String(model.provider ?? "ai-sdk").split(".")[0] || "ai-sdk");
34
+ const languageModel = (id) => {
35
+ let model = languageModels.get(id);
36
+ if (!model) {
37
+ model = provider.languageModel(id);
38
+ checkVersion(model, id, "language");
39
+ languageModels.set(id, model);
40
+ }
41
+ return model;
42
+ };
43
+ const embeddingModel = (id) => {
44
+ let model = embeddingModels.get(id);
45
+ if (!model) {
46
+ const make = provider.embeddingModel ?? provider.textEmbeddingModel;
47
+ if (typeof make !== "function") {
48
+ throw new InferloError("IL5029", `The AI SDK provider${label ? ` "${label}"` : ""} has no embedding models.`, { fix: "Use an embedding model of another provider in ai.embed({ model })." });
49
+ }
50
+ model = make.call(provider, id);
51
+ checkVersion(model, id, "embedding");
52
+ embeddingModels.set(id, model);
53
+ }
54
+ return model;
55
+ };
56
+ const out = {
57
+ get name() {
58
+ return label ?? "ai-sdk";
59
+ },
60
+ async generate(r) {
61
+ const model = languageModel(r.model);
62
+ const name = nameOf(model);
63
+ const v2 = model.specificationVersion === "v2";
64
+ let result;
65
+ try {
66
+ result = (await model.doGenerate(callOptions(r, v2)));
67
+ }
68
+ catch (error) {
69
+ throw providerError(name, error);
70
+ }
71
+ const text = result.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
72
+ const toolCalls = result.content
73
+ .filter((c) => c.type === "tool-call")
74
+ .map((c) => ({ id: c.toolCallId ?? "", name: c.toolName ?? "", input: parseInput(c.input) }));
75
+ return { text, usage: usage(result.usage), ...(toolCalls.length ? { toolCalls } : {}) };
76
+ },
77
+ async *stream(r) {
78
+ const model = languageModel(r.model);
79
+ const name = nameOf(model);
80
+ const v2 = model.specificationVersion === "v2";
81
+ let stream;
82
+ try {
83
+ ({ stream } = await model.doStream(callOptions(r, v2)));
84
+ }
85
+ catch (error) {
86
+ throw providerError(name, error);
87
+ }
88
+ const reader = stream.getReader();
89
+ let total;
90
+ try {
91
+ while (true) {
92
+ const { done, value: part } = await reader.read();
93
+ if (done)
94
+ break;
95
+ if (part.type === "text-delta") {
96
+ const text = part.delta ?? part.textDelta;
97
+ if (text)
98
+ yield text;
99
+ }
100
+ else if (part.type === "finish")
101
+ total = usage(part.usage);
102
+ else if (part.type === "error")
103
+ throw providerError(name, part.error);
104
+ }
105
+ }
106
+ finally {
107
+ reader.releaseLock();
108
+ }
109
+ yield { usage: total ?? { inputTokens: 0, outputTokens: 0 } };
110
+ },
111
+ async embed({ model: id, input, signal }) {
112
+ const model = embeddingModel(id);
113
+ const name = nameOf({ provider: model.provider });
114
+ const max = (await model.maxEmbeddingsPerCall) ?? input.length;
115
+ const embeddings = [];
116
+ let tokens = 0;
117
+ for (let i = 0; i < input.length; i += Math.max(1, max)) {
118
+ let result;
119
+ try {
120
+ result = await model.doEmbed({ values: input.slice(i, i + Math.max(1, max)), abortSignal: signal });
121
+ }
122
+ catch (error) {
123
+ throw providerError(name, error);
124
+ }
125
+ embeddings.push(...result.embeddings.map((e) => Array.from(e)));
126
+ tokens += result.usage?.tokens ?? 0;
127
+ }
128
+ return { embeddings, usage: { inputTokens: tokens, outputTokens: 0 } };
129
+ },
130
+ };
131
+ return out;
132
+ }
133
+ function checkVersion(model, id, kind) {
134
+ if (!model || typeof model !== "object" || !VERSIONS.has(String(model.specificationVersion))) {
135
+ throw new InferloError("IL5029", `The AI SDK ${kind} model "${id}" uses specification ${String(model?.specificationVersion ?? "(none)")}, which aiSdk() does not speak.`, {
136
+ fix: "Use a provider package made for ai 5 or later (@ai-sdk/* 2.0 or later, LanguageModelV2, V3 or V4).",
137
+ });
138
+ }
139
+ }
140
+ /** Inferlo's request as AI SDK call options. v2 file parts carry data or a URL directly; v3 and v4 wrap it. */
141
+ function callOptions(r, v2) {
142
+ const out = { prompt: prompt(r.messages, v2), abortSignal: r.signal };
143
+ if (r.maxTokens !== undefined)
144
+ out.maxOutputTokens = r.maxTokens;
145
+ if (r.temperature !== undefined)
146
+ out.temperature = r.temperature;
147
+ if (r.json)
148
+ out.responseFormat = r.jsonSchema ? { type: "json", schema: r.jsonSchema, name: "output" } : { type: "json" };
149
+ if (r.tools?.length) {
150
+ out.tools = r.tools.map((tool) => ({ type: "function", name: tool.name, description: tool.description, inputSchema: tool.inputSchema }));
151
+ out.toolChoice = { type: "auto" };
152
+ }
153
+ return out;
154
+ }
155
+ function prompt(messages, v2) {
156
+ const names = new Map();
157
+ const out = [];
158
+ for (const m of messages) {
159
+ if (m.role === "system")
160
+ out.push({ role: "system", content: m.content });
161
+ else if (m.role === "user") {
162
+ const content = [];
163
+ if (m.content)
164
+ content.push({ type: "text", text: m.content });
165
+ for (const image of m.images ?? [])
166
+ content.push(filePart(image, v2));
167
+ out.push({ role: "user", content: content.length ? content : [{ type: "text", text: "" }] });
168
+ }
169
+ else if (m.role === "assistant") {
170
+ const content = [];
171
+ if (m.content)
172
+ content.push({ type: "text", text: m.content });
173
+ for (const call of m.toolCalls ?? []) {
174
+ names.set(call.id, call.name);
175
+ content.push({ type: "tool-call", toolCallId: call.id, toolName: call.name, input: call.input ?? {} });
176
+ }
177
+ out.push({ role: "assistant", content });
178
+ }
179
+ else {
180
+ const id = m.toolCallId ?? "";
181
+ const part = { type: "tool-result", toolCallId: id, toolName: names.get(id) ?? "", output: { type: m.isError ? "error-text" : "text", value: m.content } };
182
+ // Results of one assistant turn's calls go in one tool message, as the AI SDK expects.
183
+ const last = out.at(-1);
184
+ if (last?.role === "tool")
185
+ last.content.push(part);
186
+ else
187
+ out.push({ role: "tool", content: [part] });
188
+ }
189
+ }
190
+ return out;
191
+ }
192
+ function filePart(image, v2) {
193
+ const data = imageData(image);
194
+ if (v2)
195
+ return data ? { type: "file", data: data.base64, mediaType: data.mediaType } : { type: "file", data: new URL(image.url), mediaType: "image/*" };
196
+ return data
197
+ ? { type: "file", data: { type: "data", data: data.base64 }, mediaType: data.mediaType }
198
+ : { type: "file", data: { type: "url", url: new URL(image.url) }, mediaType: "image/*" };
199
+ }
200
+ /** Tool call arguments: v2 to v4 send them as a JSON string. */
201
+ function parseInput(input) {
202
+ if (typeof input !== "string")
203
+ return input ?? {};
204
+ if (!input.trim())
205
+ return {};
206
+ try {
207
+ return JSON.parse(input);
208
+ }
209
+ catch {
210
+ return input;
211
+ }
212
+ }
213
+ /** Usage of v2 ({ inputTokens: number }) and v3/v4 ({ inputTokens: { total } }). */
214
+ function usage(raw) {
215
+ const u = (raw ?? {});
216
+ const count = (value) => {
217
+ const n = typeof value === "object" && value !== null ? value.total : value;
218
+ return typeof n === "number" && Number.isFinite(n) ? n : 0;
219
+ };
220
+ return { inputTokens: count(u.inputTokens), outputTokens: count(u.outputTokens) };
221
+ }
222
+ /** AI SDK errors (APICallError has statusCode) as ProviderError, so fallback and IL5006 work as with other providers. */
223
+ function providerError(name, error) {
224
+ if (error instanceof InferloError)
225
+ return error;
226
+ if (error && typeof error === "object" && error.name === "AbortError")
227
+ return error;
228
+ const e = (error ?? {});
229
+ const status = typeof e.statusCode === "number" ? e.statusCode : 0;
230
+ const body = typeof e.responseBody === "string" && e.responseBody ? e.responseBody : String(e.message ?? error);
231
+ const wrapped = new ProviderError(name, status, body);
232
+ wrapped.cause = error;
233
+ return wrapped;
234
+ }
235
+ //# sourceMappingURL=ai-sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-sdk.js","sourceRoot":"","sources":["../../src/ai/ai-sdk.ts"],"names":[],"mappings":"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"}
@@ -2,10 +2,11 @@ import { type AnySchema, type Infer } from "../schema.ts";
2
2
  import type { RouteDef } from "../route.ts";
3
3
  import { type ToolDef } from "./tool.ts";
4
4
  import { type TracingConfig } from "../tracing.ts";
5
- import { type Image, type Message, type Provider, type ProviderUsage, type ToolSpec } from "./providers.ts";
5
+ import { type Image, type Message, type Provider, type ProviderUsage, type ToolCall, type ToolSpec } from "./providers.ts";
6
6
  export * from "./providers.ts";
7
7
  export { tool, isTool, ToolBuilder } from "./tool.ts";
8
8
  export type { ToolDef, FunctionToolOptions, ToolContext } from "./tool.ts";
9
+ export { uiChatBody, fromUIMessages, type UIChatMessage } from "./ui.ts";
9
10
  export interface Pricing {
10
11
  /** USD per million input tokens. */
11
12
  input: number;
@@ -114,18 +115,51 @@ export type ToolRunner = (route: RouteDef, input: unknown) => Promise<{
114
115
  output: unknown;
115
116
  text: string;
116
117
  }>;
118
+ /** One model call that returns the tool calls instead of running them, for agent loops such as LangChain's. */
119
+ export interface ModelCallOptions {
120
+ model?: string;
121
+ messages: Message[];
122
+ tools?: ToolSpec[];
123
+ temperature?: number;
124
+ maxTokens?: number;
125
+ for?: string;
126
+ signal?: AbortSignal;
127
+ }
128
+ export interface ModelCallResult {
129
+ text: string;
130
+ toolCalls: ToolCall[];
131
+ usage: Usage;
132
+ }
133
+ /**
134
+ * One model call with the AI's aliases, fallback, budgets, usage tracking and tracing, which returns the tool calls
135
+ * the model asked for without running them. Internal: used by the LangChain integration.
136
+ */
137
+ export declare function callModel(ai: AI, options: ModelCallOptions): Promise<ModelCallResult>;
138
+ /** The route tools of ctx.ai (a request's AI client), or undefined for other clients. Internal. */
139
+ export declare function routeToolsOf(ai: AI): {
140
+ runner: ToolRunner;
141
+ describe: (route: RouteDef) => ToolSpec;
142
+ } | undefined;
117
143
  /**
118
144
  * ctx.ai for one request: the same AI, with tools that run as the request's user, and the request's signal
119
145
  * on every call that does not pass its own. Internal: used by createApp.
120
146
  */
121
147
  export declare function bindTools(ai: AI, runner: ToolRunner, describe: (route: RouteDef) => ToolSpec, signal?: AbortSignal): AI;
148
+ /** Options of StreamResult.toResponse: a ResponseInit (status, headers) and the stream format. */
149
+ export interface StreamResponseInit extends ResponseInit {
150
+ /** "sse" (default): Inferlo's text and done events. "ai-sdk": the AI SDK UI message stream protocol, for useChat. */
151
+ format?: "sse" | "ai-sdk";
152
+ }
122
153
  export interface StreamResult {
123
154
  /** Text chunks as they arrive. */
124
155
  [Symbol.asyncIterator](): AsyncIterator<string>;
125
156
  /** Full text once the stream finishes. */
126
157
  text(): Promise<string>;
127
- /** A server-sent events Response for browsers (events: "text", then "done" with usage). */
128
- toResponse(init?: ResponseInit): Response;
158
+ /**
159
+ * A server-sent events Response for browsers. The default format sends "text" events, then "done" with usage.
160
+ * format: "ai-sdk" speaks the AI SDK UI message stream protocol (v1), for useChat from @ai-sdk/react.
161
+ */
162
+ toResponse(init?: StreamResponseInit): Response;
129
163
  /** Usage once the stream finishes. */
130
164
  usage(): Promise<Usage>;
131
165
  }
package/dist/ai/index.js CHANGED
@@ -17,7 +17,23 @@ import { checkTracing, clientTracing } from "../tracing.js";
17
17
  import { ProviderError, imageData } from "./providers.js";
18
18
  export * from "./providers.js";
19
19
  export { tool, isTool, ToolBuilder } from "./tool.js";
20
+ export { uiChatBody, fromUIMessages } from "./ui.js";
20
21
  const toolRunners = new WeakMap();
22
+ const modelCalls = new WeakMap();
23
+ /**
24
+ * One model call with the AI's aliases, fallback, budgets, usage tracking and tracing, which returns the tool calls
25
+ * the model asked for without running them. Internal: used by the LangChain integration.
26
+ */
27
+ export function callModel(ai, options) {
28
+ const call = modelCalls.get(ai);
29
+ if (!call)
30
+ throw new InferloError("IL5030", "Expected an AI client made by createAI() or createApp({ ai }).", { fix: "Pass app.ai, ctx.ai or the client createAI() returns." });
31
+ return call(options);
32
+ }
33
+ /** The route tools of ctx.ai (a request's AI client), or undefined for other clients. Internal. */
34
+ export function routeToolsOf(ai) {
35
+ return toolRunners.get(ai);
36
+ }
21
37
  /**
22
38
  * ctx.ai for one request: the same AI, with tools that run as the request's user, and the request's signal
23
39
  * on every call that does not pass its own. Internal: used by createApp.
@@ -29,6 +45,9 @@ export function bindTools(ai, runner, describe, signal) {
29
45
  bound.generate = (options) => ai.generate.call(bound, withSignal(options));
30
46
  bound.stream = (options) => ai.stream.call(bound, withSignal(options));
31
47
  bound.embed = (options) => ai.embed.call(bound, withSignal(options));
48
+ const call = modelCalls.get(ai);
49
+ if (call)
50
+ modelCalls.set(bound, (options) => call(withSignal(options)));
32
51
  return bound;
33
52
  }
34
53
  function buildMessages(o) {
@@ -469,23 +488,59 @@ export function createAI(config) {
469
488
  },
470
489
  usage: () => usagePromise,
471
490
  toResponse(init) {
491
+ const { format = "sse", ...responseInit } = init ?? {};
492
+ if (format !== "sse" && format !== "ai-sdk") {
493
+ throw new InferloError("IL5027", `Unknown stream format "${String(format)}" in toResponse({ format }).`, {
494
+ fix: 'Use toResponse() for Inferlo\'s server-sent events, or toResponse({ format: "ai-sdk" }) for useChat from the Vercel AI SDK.',
495
+ });
496
+ }
472
497
  const encoder = new TextEncoder();
473
498
  const source = start();
499
+ const aiSdk = format === "ai-sdk";
500
+ const textId = aiSdk ? `text-${crypto.randomUUID()}` : "";
501
+ let opened = false;
502
+ // AI SDK UI message stream chunks are server-sent events with only a data field.
503
+ const chunk = (value) => encoder.encode(`data: ${JSON.stringify(value)}\n\n`);
474
504
  const body = new ReadableStream({
505
+ start(controller) {
506
+ if (aiSdk)
507
+ controller.enqueue(chunk({ type: "start" }));
508
+ },
475
509
  async pull(controller) {
476
510
  try {
477
511
  const { value, done } = await source.next();
478
512
  if (done) {
479
513
  const usage = await usagePromise;
480
- controller.enqueue(encoder.encode(`event: done\ndata: ${JSON.stringify({ usage })}\n\n`));
514
+ if (!aiSdk)
515
+ controller.enqueue(encoder.encode(`event: done\ndata: ${JSON.stringify({ usage })}\n\n`));
516
+ else {
517
+ if (opened)
518
+ controller.enqueue(chunk({ type: "text-end", id: textId }));
519
+ controller.enqueue(chunk({ type: "finish" }));
520
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
521
+ }
481
522
  controller.close();
482
523
  return;
483
524
  }
484
- controller.enqueue(encoder.encode(`event: text\ndata: ${JSON.stringify(value)}\n\n`));
525
+ if (!aiSdk) {
526
+ controller.enqueue(encoder.encode(`event: text\ndata: ${JSON.stringify(value)}\n\n`));
527
+ return;
528
+ }
529
+ if (!opened) {
530
+ opened = true;
531
+ controller.enqueue(chunk({ type: "text-start", id: textId }));
532
+ }
533
+ controller.enqueue(chunk({ type: "text-delta", id: textId, delta: value }));
485
534
  }
486
535
  catch (error) {
487
536
  const code = error instanceof InferloError ? error.code : "IL9000";
488
- controller.enqueue(encoder.encode(`event: error\ndata: ${JSON.stringify({ code })}\n\n`));
537
+ if (!aiSdk)
538
+ controller.enqueue(encoder.encode(`event: error\ndata: ${JSON.stringify({ code })}\n\n`));
539
+ else {
540
+ // Only the code reaches the browser; the details are logged below.
541
+ controller.enqueue(chunk({ type: "error", errorText: `The model call failed (${code}).` }));
542
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
543
+ }
489
544
  controller.close();
490
545
  console.error("[inferlo] stream failed:", redactor.value(error));
491
546
  }
@@ -494,10 +549,13 @@ export function createAI(config) {
494
549
  void source.return(undefined);
495
550
  },
496
551
  });
497
- return new Response(body, {
498
- ...init,
499
- headers: { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache", connection: "keep-alive", ...init?.headers },
500
- });
552
+ const headers = { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache", connection: "keep-alive" };
553
+ if (aiSdk) {
554
+ headers["content-type"] = "text/event-stream";
555
+ headers["x-vercel-ai-ui-message-stream"] = "v1";
556
+ headers["x-accel-buffering"] = "no";
557
+ }
558
+ return new Response(body, { ...responseInit, headers: { ...headers, ...responseInit.headers } });
501
559
  },
502
560
  };
503
561
  },
@@ -525,6 +583,37 @@ export function createAI(config) {
525
583
  });
526
584
  },
527
585
  };
586
+ modelCalls.set(ai, async (options) => {
587
+ const requested = options.model ?? config?.defaultModel ?? Object.keys(aliases)[0] ?? "";
588
+ const targets = resolve(options.model);
589
+ await guard(options.for, requested);
590
+ if (!options.messages.some((m) => m.role !== "system")) {
591
+ throw new InferloError("IL5007", "A model call needs at least one message besides the system message.", { fix: 'Pass messages: [{ role: "user", content: "..." }].' });
592
+ }
593
+ options.messages.forEach(checkImages);
594
+ const started = Date.now();
595
+ const call = () => withFallback(targets, async (target) => {
596
+ const response = await target.provider.generate({
597
+ model: target.model,
598
+ messages: options.messages,
599
+ temperature: options.temperature,
600
+ maxTokens: options.maxTokens,
601
+ tools: options.tools?.length ? options.tools : undefined,
602
+ signal: options.signal,
603
+ });
604
+ const usage = { requested, model: target.id, ...response.usage, cost: cost(target.id, response.usage), for: options.for, kind: "generate", durationMs: Date.now() - started };
605
+ await record(usage);
606
+ return { text: response.text, toolCalls: response.toolCalls ?? [], usage };
607
+ });
608
+ if (!spans)
609
+ return call();
610
+ const { name, attributes } = spanStart("chat", requested, targets, options);
611
+ return spans.run(name, attributes, async (span) => {
612
+ const result = await call();
613
+ spanUsage(span, result.usage);
614
+ return result;
615
+ });
616
+ });
528
617
  return ai;
529
618
  }
530
619
  /** Cosine similarity of two vectors, for small in-memory searches. */