@browserlessapi/inferlo 0.4.0 → 0.5.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 (96) hide show
  1. package/README.md +15 -3
  2. package/dist/adonisjs.d.ts +33 -0
  3. package/dist/adonisjs.js +30 -0
  4. package/dist/adonisjs.js.map +1 -0
  5. package/dist/ai/index.d.ts +15 -8
  6. package/dist/ai/index.js +39 -13
  7. package/dist/ai/index.js.map +1 -1
  8. package/dist/ai/providers.js +5 -4
  9. package/dist/ai/providers.js.map +1 -1
  10. package/dist/ai/public.d.ts +20 -0
  11. package/dist/ai/public.js +17 -0
  12. package/dist/ai/public.js.map +1 -0
  13. package/dist/ai/tool.d.ts +47 -0
  14. package/dist/ai/tool.js +85 -0
  15. package/dist/ai/tool.js.map +1 -0
  16. package/dist/ai-client.d.ts +3 -0
  17. package/dist/ai-client.js +8 -0
  18. package/dist/ai-client.js.map +1 -0
  19. package/dist/app.d.ts +87 -2
  20. package/dist/app.js +471 -127
  21. package/dist/app.js.map +1 -1
  22. package/dist/cli/main.js +21 -2
  23. package/dist/cli/main.js.map +1 -1
  24. package/dist/config.d.ts +16 -0
  25. package/dist/config.js +101 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/db/table.d.ts +31 -0
  28. package/dist/db/table.js +140 -1
  29. package/dist/db/table.js.map +1 -1
  30. package/dist/deprecation.d.ts +23 -0
  31. package/dist/deprecation.js +70 -0
  32. package/dist/deprecation.js.map +1 -0
  33. package/dist/describe.d.ts +27 -5
  34. package/dist/describe.js +14 -2
  35. package/dist/describe.js.map +1 -1
  36. package/dist/elysia.d.ts +49 -0
  37. package/dist/elysia.js +10 -0
  38. package/dist/elysia.js.map +1 -0
  39. package/dist/env.d.ts +2 -0
  40. package/dist/env.js +5 -4
  41. package/dist/env.js.map +1 -1
  42. package/dist/express.d.ts +38 -0
  43. package/dist/express.js +18 -0
  44. package/dist/express.js.map +1 -0
  45. package/dist/fastify.d.ts +44 -0
  46. package/dist/fastify.js +47 -0
  47. package/dist/fastify.js.map +1 -0
  48. package/dist/hono.d.ts +26 -0
  49. package/dist/hono.js +10 -0
  50. package/dist/hono.js.map +1 -0
  51. package/dist/index.d.ts +13 -4
  52. package/dist/index.js +4 -2
  53. package/dist/index.js.map +1 -1
  54. package/dist/jobs.d.ts +4 -0
  55. package/dist/jobs.js +18 -1
  56. package/dist/jobs.js.map +1 -1
  57. package/dist/logging.d.ts +23 -0
  58. package/dist/logging.js +87 -0
  59. package/dist/logging.js.map +1 -0
  60. package/dist/mcp.d.ts +0 -6
  61. package/dist/mcp.js +11 -1
  62. package/dist/mcp.js.map +1 -1
  63. package/dist/metrics.d.ts +48 -0
  64. package/dist/metrics.js +184 -0
  65. package/dist/metrics.js.map +1 -0
  66. package/dist/nestjs.d.ts +43 -0
  67. package/dist/nestjs.js +47 -0
  68. package/dist/nestjs.js.map +1 -0
  69. package/dist/openapi.d.ts +0 -1
  70. package/dist/openapi.js +4 -0
  71. package/dist/openapi.js.map +1 -1
  72. package/dist/redact.d.ts +19 -0
  73. package/dist/redact.js +150 -0
  74. package/dist/redact.js.map +1 -0
  75. package/dist/route.d.ts +18 -0
  76. package/dist/route.js.map +1 -1
  77. package/dist/secure-headers.d.ts +8 -0
  78. package/dist/secure-headers.js +61 -0
  79. package/dist/secure-headers.js.map +1 -0
  80. package/dist/serve.d.ts +53 -1
  81. package/dist/serve.js +157 -61
  82. package/dist/serve.js.map +1 -1
  83. package/dist/trpc.d.ts +57 -0
  84. package/dist/trpc.js +142 -0
  85. package/dist/trpc.js.map +1 -0
  86. package/dist/tsed.d.ts +32 -0
  87. package/dist/tsed.js +13 -0
  88. package/dist/tsed.js.map +1 -0
  89. package/llms.txt +10 -4
  90. package/package.json +57 -3
  91. package/templates/app/AGENTS.md +8 -4
  92. package/templates/app/_env.example +2 -0
  93. package/templates/app/src/app.ts +15 -8
  94. package/templates/app/src/config.ts +17 -0
  95. package/templates/app/src/server.ts +9 -9
  96. package/templates/app/src/routes/health.ts +0 -3
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Tools made from plain functions, for ai.generate({ tools }) anywhere (createAI or ctx.ai):
3
+ *
4
+ * export const getWeather = tool("getWeather", {
5
+ * description: "Current weather for a city",
6
+ * input: t.object({ city: t.string() }),
7
+ * }).handle(async ({ input }) => fetchWeather(input.city))
8
+ *
9
+ * The model's arguments are validated against input before the function runs.
10
+ */
11
+ import { InferloError } from "../errors.js";
12
+ import { ObjectSchema, ValidationError } from "../schema.js";
13
+ export class ToolBuilder {
14
+ name;
15
+ options;
16
+ constructor(name, options) {
17
+ this.name = name;
18
+ this.options = options;
19
+ }
20
+ /**
21
+ * The tool's work. Return any JSON value (or a string) for the model. Throw httpError(404, "...")
22
+ * to send a failed tool result to the model; any other error rejects ai.generate.
23
+ */
24
+ handle(handler) {
25
+ if (typeof handler !== "function")
26
+ throw invalid(this.name, ["handle() needs a function"]);
27
+ return { kind: "inferlo.tool", name: this.name, description: this.options.description, input: this.options.input, handler: handler };
28
+ }
29
+ }
30
+ /** Provider limit for tool names (Anthropic, OpenAI and Gemini). */
31
+ const TOOL_NAME = /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/;
32
+ /** Define a tool the model can call: tool("getWeather", { description, input: t.object({ ... }) }).handle(fn). */
33
+ export function tool(name, options) {
34
+ const problems = [];
35
+ if (typeof name !== "string" || !TOOL_NAME.test(name))
36
+ problems.push("the name must be letters, digits, _ and -, starting with a letter, at most 64 characters");
37
+ if (!options || typeof options !== "object")
38
+ problems.push("options must be an object with a description and an input schema");
39
+ else {
40
+ if (typeof options.description !== "string" || !options.description.trim())
41
+ problems.push("description must say what the tool does");
42
+ if (!(options.input instanceof ObjectSchema))
43
+ problems.push("input must be a t.object({ ... }) schema");
44
+ }
45
+ if (problems.length)
46
+ throw invalid(name, problems);
47
+ return new ToolBuilder(name, options);
48
+ }
49
+ function invalid(name, problems) {
50
+ return new InferloError("IL5013", `Invalid tool "${String(name)}": ${problems.join(", ")}.`, {
51
+ fix: 'Use tool("getWeather", { description: "Current weather for a city", input: t.object({ city: t.string() }) }).handle(async ({ input }) => { ... }).',
52
+ });
53
+ }
54
+ export function isTool(value) {
55
+ return typeof value === "object" && value !== null && value.kind === "inferlo.tool";
56
+ }
57
+ /** What the model is offered for a function tool. Internal. */
58
+ export function functionToolSpec(def) {
59
+ return { name: def.name, description: def.description, inputSchema: def.input.toJSONSchema() };
60
+ }
61
+ /**
62
+ * Runs one call of a function tool. Validation errors and InferloErrors with a status below 500
63
+ * (httpError, notFound, forbidden) become failed tool results for the model; other errors are rethrown.
64
+ * Internal.
65
+ */
66
+ export async function runFunctionTool(def, input, signal) {
67
+ try {
68
+ const parsed = def.input.safeParse(input);
69
+ if (!parsed.ok)
70
+ throw new ValidationError(parsed.issues, "tool input");
71
+ const output = await def.handler({ input: parsed.value, ...(signal ? { signal } : {}) });
72
+ if (typeof output === "string")
73
+ return { status: 200, output, text: output };
74
+ const text = JSON.stringify(output ?? null);
75
+ return { status: 200, output: output ?? null, text };
76
+ }
77
+ catch (error) {
78
+ if (error instanceof InferloError && error.status < 500) {
79
+ const output = error.toJSON();
80
+ return { status: error.status, output, text: JSON.stringify(output) };
81
+ }
82
+ throw error;
83
+ }
84
+ }
85
+ //# sourceMappingURL=tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/ai/tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Shared by the framework integrations: checks that a value is an AI client (createAI(...) or app.ai). */
2
+ import type { AI } from "./ai/index.ts";
3
+ export declare function assertAIClient(ai: unknown, fix: string): asserts ai is AI;
@@ -0,0 +1,8 @@
1
+ import { InferloError } from "./errors.js";
2
+ export function assertAIClient(ai, fix) {
3
+ const candidate = ai;
4
+ if (!candidate || typeof candidate.generate !== "function" || typeof candidate.stream !== "function" || typeof candidate.embed !== "function") {
5
+ throw new InferloError("IL5014", "inferloAI() needs an AI client.", { fix });
6
+ }
7
+ }
8
+ //# sourceMappingURL=ai-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-client.js","sourceRoot":"","sources":["../src/ai-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,UAAU,cAAc,CAAC,EAAW,EAAE,GAAW;IACrD,MAAM,SAAS,GAAG,EAA6B,CAAC;IAChD,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,CAAC,QAAQ,KAAK,UAAU,IAAI,OAAO,SAAS,CAAC,MAAM,KAAK,UAAU,IAAI,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QAC9I,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,iCAAiC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC"}
package/dist/app.d.ts CHANGED
@@ -10,6 +10,9 @@ import type { AnyModel } from "./model.ts";
10
10
  import { type AppDb, type AppUser, type Method, type RouteDef } from "./route.ts";
11
11
  import { type RateLimitConfig } from "./rate-limit.ts";
12
12
  import { type AppJobs, type JobQueueConfig, type JobSource } from "./jobs.ts";
13
+ import { type LogFields, type LoggingConfig } from "./logging.ts";
14
+ import { type MetricsConfig } from "./metrics.ts";
15
+ import { type SecureHeadersConfig } from "./secure-headers.ts";
13
16
  export interface AuthConfig {
14
17
  /** Return the signed-in user for this request, or null. */
15
18
  resolve(req: Request, ctx: {
@@ -30,11 +33,48 @@ export interface MiddlewareContext {
30
33
  req: Request;
31
34
  url: URL;
32
35
  state: Record<string, unknown>;
36
+ /** This request's id, sent back in the X-Request-Id header and added to its log entries. */
37
+ requestId: string;
33
38
  }
34
39
  export type Middleware = (ctx: MiddlewareContext, next: () => Promise<Response>) => Promise<Response>;
40
+ /**
41
+ * Where the app writes logs. fields holds structured values such as requestId (a request's log entries)
42
+ * or method, path, status and durationMs (request logs); loggers that only take two arguments ignore them.
43
+ */
35
44
  export interface Logger {
36
- info(message: string, data?: unknown): void;
37
- error(message: string, data?: unknown): void;
45
+ info(message: string, data?: unknown, fields?: LogFields): void;
46
+ error(message: string, data?: unknown, fields?: LogFields): void;
47
+ }
48
+ /** A readiness check: resolve (or return) when the dependency is usable, throw when it is not. */
49
+ export type HealthCheck = () => unknown | Promise<unknown>;
50
+ export interface HealthConfig {
51
+ /** Liveness endpoint: 200 while the process can answer requests. Default "/health". */
52
+ path?: string;
53
+ /** Readiness endpoint: 200 when every check passes, 503 otherwise. Default "/ready". */
54
+ readyPath?: string;
55
+ /** Your own checks, by name ("database" and "ai" are built in): { redis: () => redis.ping() }. */
56
+ checks?: Record<string, HealthCheck>;
57
+ /** Time each check may take before it fails. Default 5000 ms. */
58
+ timeoutMs?: number;
59
+ }
60
+ export interface CheckResult {
61
+ status: "ok" | "error";
62
+ durationMs: number;
63
+ /** "Check failed." unless the check threw an exposed InferloError (httpError) or exposeErrors is on. */
64
+ error?: string;
65
+ }
66
+ export interface CloseOptions {
67
+ /**
68
+ * Keep serving requests for this long after /ready starts answering 503, so load balancers stop sending
69
+ * traffic first (for example a few seconds more than the readiness probe period). Default 0.
70
+ */
71
+ delayMs?: number;
72
+ /** Longest wait for in-flight requests, waitUntil tasks and the running job before closing anyway. Default 10000. */
73
+ timeoutMs?: number;
74
+ }
75
+ export interface ReadinessReport {
76
+ status: "ready" | "not_ready";
77
+ checks: Record<string, CheckResult>;
38
78
  }
39
79
  /** Anything that contains routes: a route, an array, or a module (import * as tickets from "./routes/tickets.ts"). */
40
80
  export type RouteSource = RouteDef | readonly RouteSource[] | {
@@ -70,9 +110,44 @@ export interface AppConfig<Models extends readonly AnyModel[]> {
70
110
  * (for rateLimit by "ip"). Default false, because clients can send that header themselves.
71
111
  */
72
112
  trustProxy?: boolean;
113
+ /**
114
+ * Health endpoints for load balancers and orchestrators: GET /health (liveness) and GET /ready (readiness:
115
+ * the database, the AI configuration and your checks). Public, never cached. Off by default.
116
+ */
117
+ health?: boolean | HealthConfig;
118
+ /**
119
+ * Security headers sent on every response that does not set them itself: Content-Security-Policy,
120
+ * Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy, Referrer-Policy, X-Content-Type-Options,
121
+ * X-Frame-Options, and Strict-Transport-Security over HTTPS. Change one by name, false leaves it out:
122
+ * { "X-Frame-Options": "SAMEORIGIN", "Content-Security-Policy": false }. false sends none. Default true.
123
+ */
124
+ secureHeaders?: boolean | SecureHeadersConfig;
125
+ /**
126
+ * Longest time in milliseconds a route may take (reading the body and running the handler) before it answers
127
+ * 503 IL2007 and ctx.signal is aborted. Routes can set their own timeoutMs. false turns it off. Default 60000.
128
+ */
129
+ requestTimeoutMs?: number | false;
130
+ /**
131
+ * More values to hide from logs, besides credentials Inferlo finds itself (authorization and cookie headers,
132
+ * tokens, API keys, passwords in URLs) and the AI provider keys: your config() result (its secrets) or
133
+ * strings: redact: [settings, process.env.WEBHOOK_SECRET]. They are replaced with "[redacted]".
134
+ */
135
+ redact?: readonly (string | object | undefined | null)[];
73
136
  /** Include error messages of unexpected (500) errors in responses. Default false. */
74
137
  exposeErrors?: boolean;
138
+ /** Your own logger (for example pino or winston): { info(message, data, fields), error(message, data, fields) }. */
75
139
  logger?: Logger;
140
+ /**
141
+ * The built-in logger's format ("text" or "json", one object per line) and request logs (requests: true logs
142
+ * each request's id, method, path, status and duration). Every response has an X-Request-Id header.
143
+ */
144
+ logging?: LoggingConfig;
145
+ /**
146
+ * Prometheus metrics at GET /metrics: requests by route and status with durations, job runs, and AI calls,
147
+ * tokens and cost per alias. { token } requires Authorization: Bearer <token>; without it the endpoint is
148
+ * public, so keep it off the internet. Off by default.
149
+ */
150
+ metrics?: boolean | MetricsConfig;
76
151
  }
77
152
  export interface RequestOptions {
78
153
  method?: Method | Lowercase<Method> | "OPTIONS" | "HEAD";
@@ -102,6 +177,16 @@ export interface App<Models extends readonly AnyModel[]> {
102
177
  port: number;
103
178
  close(): Promise<void>;
104
179
  }>;
180
+ /** Run the readiness checks (the same report as GET /ready, also when health is off). */
181
+ checkReady(): Promise<ReadinessReport>;
182
+ /** True once app.close() was called: /ready answers 503. */
183
+ readonly closing: boolean;
184
+ /**
185
+ * Graceful shutdown, for SIGTERM: /ready answers 503, then (after delayMs) new requests get 503 IL2006,
186
+ * in-flight requests, waitUntil tasks and the job workers from app.jobs.start() finish (up to timeoutMs),
187
+ * servers from app.listen() close, then the database. Calling it again returns the same promise.
188
+ */
189
+ close(options?: CloseOptions): Promise<void>;
105
190
  /** Machine-readable map of the whole app: routes, models, auth and AI. */
106
191
  describe(): AppDescription;
107
192
  openapi(): Record<string, unknown>;