universal-llm-client 4.2.0 → 4.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 (108) hide show
  1. package/CHANGELOG.md +142 -103
  2. package/LICENSE +21 -21
  3. package/README.md +640 -591
  4. package/dist/ai-model.d.ts +12 -1
  5. package/dist/ai-model.d.ts.map +1 -1
  6. package/dist/ai-model.js +36 -1
  7. package/dist/ai-model.js.map +1 -1
  8. package/dist/gemma-channel.d.ts +14 -0
  9. package/dist/gemma-channel.d.ts.map +1 -0
  10. package/dist/gemma-channel.js +38 -0
  11. package/dist/gemma-channel.js.map +1 -0
  12. package/dist/gemma-diffusion.d.ts +49 -0
  13. package/dist/gemma-diffusion.d.ts.map +1 -0
  14. package/dist/gemma-diffusion.js +147 -0
  15. package/dist/gemma-diffusion.js.map +1 -0
  16. package/dist/http.d.ts +4 -0
  17. package/dist/http.d.ts.map +1 -1
  18. package/dist/http.js +14 -1
  19. package/dist/http.js.map +1 -1
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +4 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/interfaces.d.ts +183 -7
  25. package/dist/interfaces.d.ts.map +1 -1
  26. package/dist/interfaces.js.map +1 -1
  27. package/dist/providers/anthropic.d.ts.map +1 -1
  28. package/dist/providers/anthropic.js +28 -3
  29. package/dist/providers/anthropic.js.map +1 -1
  30. package/dist/providers/google.d.ts +22 -1
  31. package/dist/providers/google.d.ts.map +1 -1
  32. package/dist/providers/google.js +225 -13
  33. package/dist/providers/google.js.map +1 -1
  34. package/dist/providers/ollama.d.ts +2 -0
  35. package/dist/providers/ollama.d.ts.map +1 -1
  36. package/dist/providers/ollama.js +59 -30
  37. package/dist/providers/ollama.js.map +1 -1
  38. package/dist/providers/openai.d.ts +14 -0
  39. package/dist/providers/openai.d.ts.map +1 -1
  40. package/dist/providers/openai.js +200 -22
  41. package/dist/providers/openai.js.map +1 -1
  42. package/dist/router.d.ts +2 -0
  43. package/dist/router.d.ts.map +1 -1
  44. package/dist/router.js +4 -0
  45. package/dist/router.js.map +1 -1
  46. package/dist/stream-decoder.d.ts +12 -0
  47. package/dist/stream-decoder.d.ts.map +1 -1
  48. package/dist/stream-decoder.js +182 -5
  49. package/dist/stream-decoder.js.map +1 -1
  50. package/dist/thinking.d.ts +36 -0
  51. package/dist/thinking.d.ts.map +1 -0
  52. package/dist/thinking.js +52 -0
  53. package/dist/thinking.js.map +1 -0
  54. package/package.json +118 -116
  55. package/src/ai-model.ts +400 -350
  56. package/src/auditor.ts +213 -213
  57. package/src/client.ts +402 -402
  58. package/src/debug/debug-google-streaming.ts +1 -1
  59. package/src/demos/basic/universal-llm-examples.ts +3 -3
  60. package/src/demos/diffusion-gemma/.env +29 -0
  61. package/src/demos/diffusion-gemma/.env.example +27 -0
  62. package/src/demos/diffusion-gemma/CLAUDE.md +95 -0
  63. package/src/demos/diffusion-gemma/README.md +59 -0
  64. package/src/demos/diffusion-gemma/canvas.ts +1606 -0
  65. package/src/demos/diffusion-gemma/docker-compose.yml +29 -0
  66. package/src/demos/diffusion-gemma/probe-stream.ts +51 -0
  67. package/src/demos/diffusion-gemma/probe-tools.ts +55 -0
  68. package/src/demos/diffusion-gemma/server.ts +1205 -0
  69. package/src/demos/diffusion-gemma/start-vllm.sh +98 -0
  70. package/src/gemma-channel.ts +47 -0
  71. package/src/gemma-diffusion.ts +167 -0
  72. package/src/http.ts +261 -247
  73. package/src/index.ts +180 -161
  74. package/src/interfaces.ts +843 -657
  75. package/src/mcp.ts +345 -345
  76. package/src/providers/anthropic.ts +796 -762
  77. package/src/providers/google.ts +840 -620
  78. package/src/providers/index.ts +8 -8
  79. package/src/providers/ollama.ts +503 -469
  80. package/src/providers/openai.ts +587 -392
  81. package/src/router.ts +785 -780
  82. package/src/stream-decoder.ts +535 -361
  83. package/src/structured-output.ts +759 -759
  84. package/src/test-scripts/test-google-deep-research.ts +33 -0
  85. package/src/test-scripts/test-google-streaming-enhanced.ts +147 -147
  86. package/src/test-scripts/test-google-streaming.ts +1 -1
  87. package/src/test-scripts/test-google-system-prompt-comprehensive.ts +189 -189
  88. package/src/test-scripts/test-google-thinking.ts +46 -0
  89. package/src/test-scripts/test-system-message-positions.ts +163 -163
  90. package/src/test-scripts/test-system-prompt-improvement-demo.ts +83 -83
  91. package/src/test-scripts/test-vllm-qwen36.ts +256 -0
  92. package/src/tests/ai-model.test.ts +1614 -1614
  93. package/src/tests/auditor.test.ts +224 -224
  94. package/src/tests/gemma-diffusion.test.ts +115 -0
  95. package/src/tests/http.test.ts +200 -200
  96. package/src/tests/interfaces.test.ts +117 -117
  97. package/src/tests/providers/anthropic.test.ts +118 -0
  98. package/src/tests/providers/google.test.ts +841 -660
  99. package/src/tests/providers/ollama.test.ts +1034 -954
  100. package/src/tests/providers/openai.test.ts +1511 -1122
  101. package/src/tests/router.test.ts +254 -254
  102. package/src/tests/stream-decoder.test.ts +263 -179
  103. package/src/tests/structured-output.test.ts +1450 -1450
  104. package/src/tests/thinking.test.ts +65 -0
  105. package/src/tests/tools.test.ts +175 -175
  106. package/src/thinking.ts +73 -0
  107. package/src/tools.ts +246 -246
  108. package/src/zod-adapter.ts +72 -72
package/src/tools.ts CHANGED
@@ -1,246 +1,246 @@
1
- /**
2
- * Universal LLM Client v3 — Tool Utilities
3
- *
4
- * ToolBuilder: Type-safe tool definition builder with fluent API.
5
- * ToolExecutor: Execution wrappers with timeout and validation.
6
- */
7
-
8
- import type { LLMFunction, LLMToolDefinition, ToolHandler } from './interfaces.js';
9
-
10
- // ============================================================================
11
- // ToolBuilder
12
- // ============================================================================
13
-
14
- /**
15
- * Fluent builder for LLM tool definitions.
16
- *
17
- * Usage:
18
- * const tool = new ToolBuilder('get_weather')
19
- * .description('Get current weather for a location')
20
- * .addParameter('location', 'string', 'City name', true)
21
- * .addParameter('units', 'string', 'Temperature units', false, { enum: ['celsius', 'fahrenheit'] })
22
- * .build();
23
- */
24
- export class ToolBuilder {
25
- private name: string;
26
- private desc: string = '';
27
- private properties: Record<string, unknown> = {};
28
- private required: string[] = [];
29
-
30
- constructor(name: string) {
31
- this.name = name;
32
- }
33
-
34
- description(desc: string): this {
35
- this.desc = desc;
36
- return this;
37
- }
38
-
39
- addParameter(
40
- name: string,
41
- type: string,
42
- description: string,
43
- isRequired: boolean = false,
44
- extra?: Record<string, unknown>,
45
- ): this {
46
- this.properties[name] = {
47
- type,
48
- description,
49
- ...extra,
50
- };
51
- if (isRequired) {
52
- this.required.push(name);
53
- }
54
- return this;
55
- }
56
-
57
- build(): LLMToolDefinition {
58
- return {
59
- type: 'function',
60
- function: {
61
- name: this.name,
62
- description: this.desc,
63
- parameters: {
64
- type: 'object',
65
- properties: this.properties,
66
- required: this.required.length > 0 ? this.required : undefined,
67
- },
68
- },
69
- };
70
- }
71
-
72
- /** Build and return the function definition only */
73
- buildFunction(): LLMFunction {
74
- return this.build().function;
75
- }
76
- }
77
-
78
- // ============================================================================
79
- // ToolExecutor
80
- // ============================================================================
81
-
82
- /**
83
- * Utility wrappers for creating safe tool handlers.
84
- */
85
- export class ToolExecutor {
86
- /**
87
- * Wrap a handler with a timeout.
88
- * Rejects if the handler doesn't complete within the specified ms.
89
- */
90
- static withTimeout(handler: ToolHandler, timeoutMs: number): ToolHandler {
91
- return async (args: unknown) => {
92
- const result = await Promise.race([
93
- Promise.resolve(handler(args)),
94
- new Promise<never>((_, reject) =>
95
- setTimeout(() => reject(new Error(`Tool execution timeout after ${timeoutMs}ms`)), timeoutMs),
96
- ),
97
- ]);
98
- return result;
99
- };
100
- }
101
-
102
- /**
103
- * Wrap a handler to catch errors and return them as strings
104
- * instead of throwing.
105
- */
106
- static safe(handler: ToolHandler): ToolHandler {
107
- return async (args: unknown) => {
108
- try {
109
- return await handler(args);
110
- } catch (error) {
111
- return {
112
- error: error instanceof Error ? error.message : String(error),
113
- };
114
- }
115
- };
116
- }
117
-
118
- /**
119
- * Wrap a handler with argument validation.
120
- * Checks that required fields are present before execution.
121
- */
122
- static withValidation(
123
- handler: ToolHandler,
124
- requiredFields: string[],
125
- ): ToolHandler {
126
- return async (args: unknown) => {
127
- if (!args || typeof args !== 'object') {
128
- throw new Error('Tool arguments must be an object');
129
- }
130
- const obj = args as Record<string, unknown>;
131
- for (const field of requiredFields) {
132
- if (obj[field] === undefined || obj[field] === null) {
133
- throw new Error(`Missing required argument: ${field}`);
134
- }
135
- }
136
- return handler(args);
137
- };
138
- }
139
-
140
- /**
141
- * Create a handler that measures execution time and
142
- * returns both the result and duration.
143
- */
144
- static timed(handler: ToolHandler): ToolHandler {
145
- return async (args: unknown) => {
146
- const start = Date.now();
147
- const result = await handler(args);
148
- return {
149
- result,
150
- duration: Date.now() - start,
151
- };
152
- };
153
- }
154
-
155
- /**
156
- * Compose multiple wrappers around a handler.
157
- * Applied from right to left (innermost to outermost).
158
- */
159
- static compose(
160
- handler: ToolHandler,
161
- ...wrappers: Array<(h: ToolHandler) => ToolHandler>
162
- ): ToolHandler {
163
- return wrappers.reduceRight((h, wrapper) => wrapper(h), handler);
164
- }
165
- }
166
-
167
- // ============================================================================
168
- // Common Tool Definitions
169
- // ============================================================================
170
-
171
- /**
172
- * Create a get_current_time tool definition and handler.
173
- */
174
- export function createTimeTool(): {
175
- name: string;
176
- description: string;
177
- parameters: LLMFunction['parameters'];
178
- handler: ToolHandler;
179
- } {
180
- return {
181
- name: 'get_current_time',
182
- description: 'Get the current date and time',
183
- parameters: {
184
- type: 'object',
185
- properties: {
186
- timezone: {
187
- type: 'string',
188
- description: 'IANA timezone (e.g. "America/New_York"). Defaults to UTC.',
189
- },
190
- },
191
- },
192
- handler: (args: unknown) => {
193
- const { timezone } = (args ?? {}) as { timezone?: string };
194
- const now = new Date();
195
- try {
196
- return {
197
- iso: now.toISOString(),
198
- formatted: now.toLocaleString('en-US', {
199
- timeZone: timezone || 'UTC',
200
- dateStyle: 'full',
201
- timeStyle: 'long',
202
- }),
203
- timezone: timezone || 'UTC',
204
- timestamp: now.getTime(),
205
- };
206
- } catch {
207
- return {
208
- iso: now.toISOString(),
209
- formatted: now.toUTCString(),
210
- timezone: 'UTC',
211
- timestamp: now.getTime(),
212
- };
213
- }
214
- },
215
- };
216
- }
217
-
218
- /**
219
- * Create a random_number tool definition and handler.
220
- */
221
- export function createRandomNumberTool(): {
222
- name: string;
223
- description: string;
224
- parameters: LLMFunction['parameters'];
225
- handler: ToolHandler;
226
- } {
227
- return {
228
- name: 'random_number',
229
- description: 'Generate a random number within a range',
230
- parameters: {
231
- type: 'object',
232
- properties: {
233
- min: { type: 'number', description: 'Minimum value (default 0)' },
234
- max: { type: 'number', description: 'Maximum value (default 100)' },
235
- },
236
- },
237
- handler: (args: unknown) => {
238
- const { min = 0, max = 100 } = (args ?? {}) as { min?: number; max?: number };
239
- return {
240
- value: Math.floor(Math.random() * (max - min + 1)) + min,
241
- min,
242
- max,
243
- };
244
- },
245
- };
246
- }
1
+ /**
2
+ * Universal LLM Client v3 — Tool Utilities
3
+ *
4
+ * ToolBuilder: Type-safe tool definition builder with fluent API.
5
+ * ToolExecutor: Execution wrappers with timeout and validation.
6
+ */
7
+
8
+ import type { LLMFunction, LLMToolDefinition, ToolHandler } from './interfaces.js';
9
+
10
+ // ============================================================================
11
+ // ToolBuilder
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Fluent builder for LLM tool definitions.
16
+ *
17
+ * Usage:
18
+ * const tool = new ToolBuilder('get_weather')
19
+ * .description('Get current weather for a location')
20
+ * .addParameter('location', 'string', 'City name', true)
21
+ * .addParameter('units', 'string', 'Temperature units', false, { enum: ['celsius', 'fahrenheit'] })
22
+ * .build();
23
+ */
24
+ export class ToolBuilder {
25
+ private name: string;
26
+ private desc: string = '';
27
+ private properties: Record<string, unknown> = {};
28
+ private required: string[] = [];
29
+
30
+ constructor(name: string) {
31
+ this.name = name;
32
+ }
33
+
34
+ description(desc: string): this {
35
+ this.desc = desc;
36
+ return this;
37
+ }
38
+
39
+ addParameter(
40
+ name: string,
41
+ type: string,
42
+ description: string,
43
+ isRequired: boolean = false,
44
+ extra?: Record<string, unknown>,
45
+ ): this {
46
+ this.properties[name] = {
47
+ type,
48
+ description,
49
+ ...extra,
50
+ };
51
+ if (isRequired) {
52
+ this.required.push(name);
53
+ }
54
+ return this;
55
+ }
56
+
57
+ build(): LLMToolDefinition {
58
+ return {
59
+ type: 'function',
60
+ function: {
61
+ name: this.name,
62
+ description: this.desc,
63
+ parameters: {
64
+ type: 'object',
65
+ properties: this.properties,
66
+ required: this.required.length > 0 ? this.required : undefined,
67
+ },
68
+ },
69
+ };
70
+ }
71
+
72
+ /** Build and return the function definition only */
73
+ buildFunction(): LLMFunction {
74
+ return this.build().function;
75
+ }
76
+ }
77
+
78
+ // ============================================================================
79
+ // ToolExecutor
80
+ // ============================================================================
81
+
82
+ /**
83
+ * Utility wrappers for creating safe tool handlers.
84
+ */
85
+ export class ToolExecutor {
86
+ /**
87
+ * Wrap a handler with a timeout.
88
+ * Rejects if the handler doesn't complete within the specified ms.
89
+ */
90
+ static withTimeout(handler: ToolHandler, timeoutMs: number): ToolHandler {
91
+ return async (args: unknown) => {
92
+ const result = await Promise.race([
93
+ Promise.resolve(handler(args)),
94
+ new Promise<never>((_, reject) =>
95
+ setTimeout(() => reject(new Error(`Tool execution timeout after ${timeoutMs}ms`)), timeoutMs),
96
+ ),
97
+ ]);
98
+ return result;
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Wrap a handler to catch errors and return them as strings
104
+ * instead of throwing.
105
+ */
106
+ static safe(handler: ToolHandler): ToolHandler {
107
+ return async (args: unknown) => {
108
+ try {
109
+ return await handler(args);
110
+ } catch (error) {
111
+ return {
112
+ error: error instanceof Error ? error.message : String(error),
113
+ };
114
+ }
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Wrap a handler with argument validation.
120
+ * Checks that required fields are present before execution.
121
+ */
122
+ static withValidation(
123
+ handler: ToolHandler,
124
+ requiredFields: string[],
125
+ ): ToolHandler {
126
+ return async (args: unknown) => {
127
+ if (!args || typeof args !== 'object') {
128
+ throw new Error('Tool arguments must be an object');
129
+ }
130
+ const obj = args as Record<string, unknown>;
131
+ for (const field of requiredFields) {
132
+ if (obj[field] === undefined || obj[field] === null) {
133
+ throw new Error(`Missing required argument: ${field}`);
134
+ }
135
+ }
136
+ return handler(args);
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Create a handler that measures execution time and
142
+ * returns both the result and duration.
143
+ */
144
+ static timed(handler: ToolHandler): ToolHandler {
145
+ return async (args: unknown) => {
146
+ const start = Date.now();
147
+ const result = await handler(args);
148
+ return {
149
+ result,
150
+ duration: Date.now() - start,
151
+ };
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Compose multiple wrappers around a handler.
157
+ * Applied from right to left (innermost to outermost).
158
+ */
159
+ static compose(
160
+ handler: ToolHandler,
161
+ ...wrappers: Array<(h: ToolHandler) => ToolHandler>
162
+ ): ToolHandler {
163
+ return wrappers.reduceRight((h, wrapper) => wrapper(h), handler);
164
+ }
165
+ }
166
+
167
+ // ============================================================================
168
+ // Common Tool Definitions
169
+ // ============================================================================
170
+
171
+ /**
172
+ * Create a get_current_time tool definition and handler.
173
+ */
174
+ export function createTimeTool(): {
175
+ name: string;
176
+ description: string;
177
+ parameters: LLMFunction['parameters'];
178
+ handler: ToolHandler;
179
+ } {
180
+ return {
181
+ name: 'get_current_time',
182
+ description: 'Get the current date and time',
183
+ parameters: {
184
+ type: 'object',
185
+ properties: {
186
+ timezone: {
187
+ type: 'string',
188
+ description: 'IANA timezone (e.g. "America/New_York"). Defaults to UTC.',
189
+ },
190
+ },
191
+ },
192
+ handler: (args: unknown) => {
193
+ const { timezone } = (args ?? {}) as { timezone?: string };
194
+ const now = new Date();
195
+ try {
196
+ return {
197
+ iso: now.toISOString(),
198
+ formatted: now.toLocaleString('en-US', {
199
+ timeZone: timezone || 'UTC',
200
+ dateStyle: 'full',
201
+ timeStyle: 'long',
202
+ }),
203
+ timezone: timezone || 'UTC',
204
+ timestamp: now.getTime(),
205
+ };
206
+ } catch {
207
+ return {
208
+ iso: now.toISOString(),
209
+ formatted: now.toUTCString(),
210
+ timezone: 'UTC',
211
+ timestamp: now.getTime(),
212
+ };
213
+ }
214
+ },
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Create a random_number tool definition and handler.
220
+ */
221
+ export function createRandomNumberTool(): {
222
+ name: string;
223
+ description: string;
224
+ parameters: LLMFunction['parameters'];
225
+ handler: ToolHandler;
226
+ } {
227
+ return {
228
+ name: 'random_number',
229
+ description: 'Generate a random number within a range',
230
+ parameters: {
231
+ type: 'object',
232
+ properties: {
233
+ min: { type: 'number', description: 'Minimum value (default 0)' },
234
+ max: { type: 'number', description: 'Maximum value (default 100)' },
235
+ },
236
+ },
237
+ handler: (args: unknown) => {
238
+ const { min = 0, max = 100 } = (args ?? {}) as { min?: number; max?: number };
239
+ return {
240
+ value: Math.floor(Math.random() * (max - min + 1)) + min,
241
+ min,
242
+ max,
243
+ };
244
+ },
245
+ };
246
+ }
@@ -1,72 +1,72 @@
1
- /**
2
- * Zod Adapter for Universal LLM Client
3
- *
4
- * Optional entrypoint for projects that use Zod for schema validation.
5
- * Import from 'universal-llm-client/zod' to use.
6
- *
7
- * @module universal-llm-client/zod
8
- */
9
-
10
- import { z } from 'zod';
11
- import type { SchemaConfig } from './structured-output.js';
12
-
13
- /**
14
- * Create a SchemaConfig from a Zod schema.
15
- *
16
- * This bridges Zod's type-safe schema definitions to the library's
17
- * generic SchemaConfig interface, using Zod 4's native `z.toJSONSchema()`.
18
- *
19
- * @template T The type inferred from the Zod schema
20
- * @param schema The Zod schema
21
- * @param options Optional name and description for LLM guidance
22
- * @returns SchemaConfig ready for use with generateStructured, etc.
23
- *
24
- * @example
25
- * ```typescript
26
- * import { fromZod } from 'universal-llm-client/zod';
27
- * import { z } from 'zod';
28
- *
29
- * const UserSchema = z.object({
30
- * name: z.string(),
31
- * age: z.number(),
32
- * });
33
- *
34
- * const config = fromZod(UserSchema, { name: 'User' });
35
- *
36
- * const user = await model.generateStructured(config, messages);
37
- * // user.name: string, user.age: number (fully typed)
38
- * ```
39
- */
40
- export function fromZod<T>(
41
- schema: z.ZodType<T>,
42
- options?: { name?: string; description?: string },
43
- ): SchemaConfig<T> {
44
- // Convert Zod schema to JSON Schema using Zod 4's native method
45
- const rawJsonSchema = z.toJSONSchema(schema, {
46
- target: 'draft-07',
47
- unrepresentable: 'any',
48
- });
49
-
50
- // Clean up — remove $schema since providers don't need it
51
- const jsonSchema = { ...rawJsonSchema } as Record<string, unknown>;
52
- delete jsonSchema.$schema;
53
-
54
- return {
55
- jsonSchema: jsonSchema as import('./structured-output.js').JSONSchema,
56
- validate: (data: unknown): T => {
57
- const result = schema.safeParse(data);
58
- if (!result.success) {
59
- throw result.error;
60
- }
61
- return result.data;
62
- },
63
- name: options?.name,
64
- description: options?.description,
65
- };
66
- }
67
-
68
- // Re-export z for convenience (users importing from /zod likely want it)
69
- export { z } from 'zod';
70
-
71
- // Re-export SchemaConfig for type usage
72
- export type { SchemaConfig } from './structured-output.js';
1
+ /**
2
+ * Zod Adapter for Universal LLM Client
3
+ *
4
+ * Optional entrypoint for projects that use Zod for schema validation.
5
+ * Import from 'universal-llm-client/zod' to use.
6
+ *
7
+ * @module universal-llm-client/zod
8
+ */
9
+
10
+ import { z } from 'zod';
11
+ import type { SchemaConfig } from './structured-output.js';
12
+
13
+ /**
14
+ * Create a SchemaConfig from a Zod schema.
15
+ *
16
+ * This bridges Zod's type-safe schema definitions to the library's
17
+ * generic SchemaConfig interface, using Zod 4's native `z.toJSONSchema()`.
18
+ *
19
+ * @template T The type inferred from the Zod schema
20
+ * @param schema The Zod schema
21
+ * @param options Optional name and description for LLM guidance
22
+ * @returns SchemaConfig ready for use with generateStructured, etc.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { fromZod } from 'universal-llm-client/zod';
27
+ * import { z } from 'zod';
28
+ *
29
+ * const UserSchema = z.object({
30
+ * name: z.string(),
31
+ * age: z.number(),
32
+ * });
33
+ *
34
+ * const config = fromZod(UserSchema, { name: 'User' });
35
+ *
36
+ * const user = await model.generateStructured(config, messages);
37
+ * // user.name: string, user.age: number (fully typed)
38
+ * ```
39
+ */
40
+ export function fromZod<T>(
41
+ schema: z.ZodType<T>,
42
+ options?: { name?: string; description?: string },
43
+ ): SchemaConfig<T> {
44
+ // Convert Zod schema to JSON Schema using Zod 4's native method
45
+ const rawJsonSchema = z.toJSONSchema(schema, {
46
+ target: 'draft-07',
47
+ unrepresentable: 'any',
48
+ });
49
+
50
+ // Clean up — remove $schema since providers don't need it
51
+ const jsonSchema = { ...rawJsonSchema } as Record<string, unknown>;
52
+ delete jsonSchema.$schema;
53
+
54
+ return {
55
+ jsonSchema: jsonSchema as import('./structured-output.js').JSONSchema,
56
+ validate: (data: unknown): T => {
57
+ const result = schema.safeParse(data);
58
+ if (!result.success) {
59
+ throw result.error;
60
+ }
61
+ return result.data;
62
+ },
63
+ name: options?.name,
64
+ description: options?.description,
65
+ };
66
+ }
67
+
68
+ // Re-export z for convenience (users importing from /zod likely want it)
69
+ export { z } from 'zod';
70
+
71
+ // Re-export SchemaConfig for type usage
72
+ export type { SchemaConfig } from './structured-output.js';