@aeye/core 0.1.2 → 0.3.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.
- package/dist/common.d.ts +70 -1
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +310 -0
- package/dist/common.js.map +1 -1
- package/dist/prompt.d.ts +13 -2
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +54 -25
- package/dist/prompt.js.map +1 -1
- package/dist/schema.d.ts +73 -5
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +554 -66
- package/dist/schema.js.map +1 -1
- package/dist/tool.js +1 -1
- package/dist/tool.js.map +1 -1
- package/dist/types.d.ts +13 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +54 -54
package/dist/schema.js
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
/**
|
|
3
|
-
* Transfer description and metadata from source Zod schema to target Zod schema
|
|
4
|
-
*
|
|
5
|
-
* @param target - target Zod schema
|
|
6
|
-
* @param source - source Zod schema
|
|
7
|
-
* @returns
|
|
8
|
-
*/
|
|
9
|
-
function transferMetadata(target, source) {
|
|
10
|
-
if (source.description) {
|
|
11
|
-
target = target.describe(source.description);
|
|
12
|
-
}
|
|
13
|
-
const meta = source.meta();
|
|
14
|
-
if (meta) {
|
|
15
|
-
target = target.meta(meta);
|
|
16
|
-
}
|
|
17
|
-
return target;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
3
|
* Recursively transforms a Zod schema to support strict mode.
|
|
21
4
|
*
|
|
22
5
|
* @param schema - input Zod schema
|
|
@@ -43,6 +26,44 @@ export function strictify(schema) {
|
|
|
43
26
|
return transform(schema);
|
|
44
27
|
}
|
|
45
28
|
/**
|
|
29
|
+
* Transfer description and metadata from source Zod schema to target Zod schema
|
|
30
|
+
*
|
|
31
|
+
* @param target - target Zod schema
|
|
32
|
+
* @param source - source Zod schema
|
|
33
|
+
* @returns
|
|
34
|
+
*/
|
|
35
|
+
function transferMetadata(target, source) {
|
|
36
|
+
if (source.description) {
|
|
37
|
+
target = target.describe(source.description);
|
|
38
|
+
}
|
|
39
|
+
const meta = source.meta();
|
|
40
|
+
if (meta) {
|
|
41
|
+
target = target.meta(meta);
|
|
42
|
+
}
|
|
43
|
+
return target;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Extracts the input schema from a transformed schema (codec or preprocess).
|
|
47
|
+
* For preprocess with optional, we need the nullable version for strict mode input.
|
|
48
|
+
* This is needed when building input schemas that contain transformed fields.
|
|
49
|
+
*/
|
|
50
|
+
function getInputSchema(schema) {
|
|
51
|
+
if (schema instanceof z.ZodCodec) {
|
|
52
|
+
return schema.def.in;
|
|
53
|
+
}
|
|
54
|
+
// For ZodPipe (which preprocess creates), check if the output is optional
|
|
55
|
+
// If so, we need to return a nullable version for the input schema
|
|
56
|
+
if (schema instanceof z.ZodPipe) {
|
|
57
|
+
const outputSchema = schema.def.out;
|
|
58
|
+
if (outputSchema instanceof z.ZodOptional) {
|
|
59
|
+
// The input from preprocess is already set up to handle null->undefined conversion
|
|
60
|
+
// So we just need to make sure the base type accepts null
|
|
61
|
+
return z.nullable(outputSchema.unwrap());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return schema;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
46
67
|
* Recursively transforms a Zod schema to support strict mode.
|
|
47
68
|
*
|
|
48
69
|
* @param schema - input Zod schema
|
|
@@ -51,11 +72,14 @@ export function strictify(schema) {
|
|
|
51
72
|
function strictifySimple(schema, transform) {
|
|
52
73
|
// Handle ZodOptional - get the inner schema and make it optional
|
|
53
74
|
if (schema instanceof z.ZodOptional) {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
const innerSchema = schema.unwrap();
|
|
76
|
+
const transformed = transform(innerSchema);
|
|
77
|
+
// Check if the inner schema is nullable
|
|
78
|
+
// If it's nullable, don't convert null to undefined
|
|
79
|
+
const isNullable = innerSchema instanceof z.ZodNullable;
|
|
80
|
+
// Use a preprocess to convert null to undefined only for non-nullable schemas
|
|
81
|
+
// This avoids issues with codec output validation in recursive schemas
|
|
82
|
+
return transferMetadata(z.preprocess((val) => (!isNullable && val === null) ? undefined : val, transformed.optional()), schema);
|
|
59
83
|
}
|
|
60
84
|
// Handle ZodObject
|
|
61
85
|
if (schema instanceof z.ZodObject) {
|
|
@@ -68,8 +92,8 @@ function strictifySimple(schema, transform) {
|
|
|
68
92
|
// Handle ZodCodec
|
|
69
93
|
if (schema instanceof z.ZodCodec) {
|
|
70
94
|
return transferMetadata(z.codec(transform(schema.def.in), transform(schema.def.out), {
|
|
71
|
-
|
|
72
|
-
|
|
95
|
+
decode: schema.def.transform,
|
|
96
|
+
encode: schema.def.reverseTransform,
|
|
73
97
|
}), schema);
|
|
74
98
|
}
|
|
75
99
|
// Handle ZodPipe
|
|
@@ -86,9 +110,12 @@ function strictifySimple(schema, transform) {
|
|
|
86
110
|
}
|
|
87
111
|
// Handle ZodRecord
|
|
88
112
|
if (schema instanceof z.ZodRecord) {
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
113
|
+
const keyTransformed = schema.keyType ? transform(schema.keyType) : z.string();
|
|
114
|
+
const valueTransformed = transform(schema.valueType);
|
|
115
|
+
// For the input schema (array of {key, value}), use the input side of any codecs
|
|
116
|
+
const key = getInputSchema(keyTransformed);
|
|
117
|
+
const value = getInputSchema(valueTransformed);
|
|
118
|
+
return transferMetadata(z.codec(z.array(z.object({ key, value })), z.record(keyTransformed, valueTransformed), {
|
|
92
119
|
decode: (arr) => {
|
|
93
120
|
const record = {};
|
|
94
121
|
for (const { key, value } of arr) {
|
|
@@ -127,55 +154,516 @@ function strictifySimple(schema, transform) {
|
|
|
127
154
|
return schema;
|
|
128
155
|
}
|
|
129
156
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
157
|
+
* Converts a Zod schema to JSON Schema V2 with support for different provider formats.
|
|
158
|
+
*
|
|
159
|
+
* This is a custom implementation that recursively inspects Zod schemas and converts them
|
|
160
|
+
* to JSON Schema following the pattern of strictify/strictifySimple.
|
|
161
|
+
*
|
|
162
|
+
* @param schema - The Zod schema to convert
|
|
163
|
+
* @param options - Configuration options for the conversion
|
|
164
|
+
* @returns JSON Schema object compatible with the specified format
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* const schema = z.object({
|
|
169
|
+
* name: z.string(),
|
|
170
|
+
* age: z.number().optional(),
|
|
171
|
+
* });
|
|
172
|
+
*
|
|
173
|
+
* // For OpenAI strict mode
|
|
174
|
+
* const strictSchema = strictify(schema);
|
|
175
|
+
* const jsonSchema = toJSONSchemaV2(strictSchema, { strict: true, format: 'openai' });
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export function toJSONSchema(schema, options) {
|
|
179
|
+
const resolvedOptions = typeof options === 'boolean' ? { strict: options } : options;
|
|
180
|
+
const { strict, format = 'openai' } = resolvedOptions;
|
|
181
|
+
// Currently only OpenAI format is supported
|
|
182
|
+
if (format !== 'openai') {
|
|
183
|
+
throw new Error(`Unsupported format: ${format}. Currently only 'openai' is supported.`);
|
|
184
|
+
}
|
|
185
|
+
const context = {
|
|
186
|
+
root: schema,
|
|
187
|
+
strict,
|
|
188
|
+
format,
|
|
189
|
+
definitions: new Map(),
|
|
190
|
+
definitionSchemas: new Map(),
|
|
191
|
+
refCounter: 0,
|
|
192
|
+
path: [],
|
|
193
|
+
};
|
|
194
|
+
const result = convert(schema, context);
|
|
195
|
+
// Add definitions if any were created
|
|
196
|
+
if (context.definitionSchemas.size > 0) {
|
|
197
|
+
result.$defs = Object.fromEntries(context.definitionSchemas);
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Main recursive conversion function
|
|
132
203
|
*/
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
204
|
+
function convert(schema, context) {
|
|
205
|
+
// For lazy schemas, we need to check by aid first to handle .describe()/.optional() wrappers
|
|
206
|
+
// that create new objects but share the same lazy getter
|
|
207
|
+
let cacheKey = schema;
|
|
208
|
+
let unwrappedSchema = schema;
|
|
209
|
+
// Check if this is a lazy schema and extract metadata early
|
|
210
|
+
if (schema instanceof z.ZodLazy) {
|
|
211
|
+
const metadata = (schema instanceof z.ZodType) ? schema.meta() : null;
|
|
212
|
+
const aid = metadata?.aid;
|
|
213
|
+
// If there's an aid, check if we've already seen this aid
|
|
214
|
+
if (aid) {
|
|
215
|
+
for (const [cachedSchema, [js, jsId]] of context.definitions.entries()) {
|
|
216
|
+
if (cachedSchema instanceof z.ZodType) {
|
|
217
|
+
const cachedMeta = cachedSchema.meta();
|
|
218
|
+
if (cachedMeta?.aid === aid && jsId && js) {
|
|
219
|
+
// Found a cached version with the same aid - use it
|
|
220
|
+
if (schema === context.root) {
|
|
221
|
+
return { $ref: `#` };
|
|
222
|
+
}
|
|
223
|
+
if (!context.definitionSchemas.has(jsId)) {
|
|
224
|
+
context.definitionSchemas.set(jsId, { ...js });
|
|
225
|
+
for (const prop in js) {
|
|
226
|
+
delete js[prop];
|
|
227
|
+
}
|
|
228
|
+
js.$ref = `#/$defs/${jsId}`;
|
|
229
|
+
}
|
|
230
|
+
return { $ref: `#/$defs/${jsId}` };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
unwrappedSchema = schema.def.getter();
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// Check cache by object identity for non-lazy schemas
|
|
239
|
+
const [js, jsId] = context.definitions.get(schema) || [];
|
|
240
|
+
if (jsId && js) {
|
|
241
|
+
if (schema === context.root) {
|
|
242
|
+
return { $ref: `#` };
|
|
243
|
+
}
|
|
244
|
+
if (!context.definitionSchemas.has(jsId)) {
|
|
245
|
+
context.definitionSchemas.set(jsId, { ...js });
|
|
246
|
+
for (const prop in js) {
|
|
247
|
+
delete js[prop];
|
|
248
|
+
}
|
|
249
|
+
js.$ref = `#/$defs/${jsId}`;
|
|
250
|
+
}
|
|
251
|
+
return { $ref: `#/$defs/${jsId}` };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Capture metadata from the original schema
|
|
255
|
+
const metadata = {};
|
|
256
|
+
if (schema instanceof z.ZodType) {
|
|
257
|
+
Object.assign(metadata, schema.meta() || {});
|
|
258
|
+
if (!metadata.description && schema.description) {
|
|
259
|
+
metadata.description = schema.description;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// If the schema has an 'aid' or 'id' in meta, promote it to a definition
|
|
263
|
+
const id = (metadata.aid ? String(metadata.aid) : 0) || metadata.id || `__schema${context.refCounter++}`;
|
|
264
|
+
const save = !!(metadata.aid || metadata.id) && context.root !== schema;
|
|
265
|
+
// A schema target - will hold either the converted schema or a $ref
|
|
266
|
+
const target = {};
|
|
267
|
+
// Before converting, register this schema to handle recursion
|
|
268
|
+
context.definitions.set(cacheKey, [target, id]);
|
|
269
|
+
// Convert the unwrapped schema
|
|
270
|
+
const result = convertSchema(unwrappedSchema, context);
|
|
271
|
+
Object.assign(result, metadata);
|
|
272
|
+
delete result.aid;
|
|
273
|
+
// Promote it because user requested it or it's recursive
|
|
274
|
+
if (save || context.definitionSchemas.has(id)) {
|
|
275
|
+
context.definitionSchemas.set(id, result);
|
|
276
|
+
target.$ref = `#/$defs/${id}`;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// Inline schema
|
|
280
|
+
Object.assign(target, result);
|
|
281
|
+
}
|
|
282
|
+
return target;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Main conversion function
|
|
286
|
+
*/
|
|
287
|
+
function convertSchema(schema, context) {
|
|
288
|
+
// TODO: Map, Set, File, ReadOnly, Nan, Catch, Prefault, NonOptional, Transform, Function, Promise, Custom
|
|
289
|
+
// Handle ZodOptional
|
|
290
|
+
if (schema instanceof z.ZodOptional) {
|
|
291
|
+
const innerSchema = schema.unwrap();
|
|
292
|
+
const innerJson = convert(innerSchema, context);
|
|
293
|
+
if (context.strict) {
|
|
294
|
+
// In strict mode, optional fields become nullable
|
|
295
|
+
return makeNullable(innerJson);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// In non-strict mode, optional fields are just not required
|
|
299
|
+
return innerJson;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Handle ZodNullable
|
|
303
|
+
if (schema instanceof z.ZodNullable) {
|
|
304
|
+
const innerSchema = schema.unwrap();
|
|
305
|
+
const innerJson = convert(innerSchema, context);
|
|
306
|
+
return makeNullable(innerJson);
|
|
307
|
+
}
|
|
308
|
+
// Handle ZodObject
|
|
309
|
+
if (schema instanceof z.ZodObject) {
|
|
310
|
+
const properties = {};
|
|
311
|
+
const required = [];
|
|
312
|
+
const shape = schema.shape;
|
|
313
|
+
for (const key in shape) {
|
|
314
|
+
const fieldSchema = shape[key];
|
|
315
|
+
const isRequired = context.strict || !isOptional(fieldSchema);
|
|
316
|
+
properties[key] = convert(fieldSchema, context);
|
|
317
|
+
if (isRequired) {
|
|
318
|
+
required.push(key);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const result = {
|
|
322
|
+
type: 'object',
|
|
323
|
+
properties,
|
|
324
|
+
required,
|
|
325
|
+
};
|
|
326
|
+
if (context.strict || schema.def.catchall?._zod.def.type === "never") {
|
|
327
|
+
result.additionalProperties = false;
|
|
328
|
+
}
|
|
329
|
+
else if (schema.def.catchall) {
|
|
330
|
+
result.additionalProperties = convert(schema.def.catchall, context);
|
|
331
|
+
}
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
// Handle ZodArray
|
|
335
|
+
if (schema instanceof z.ZodArray) {
|
|
336
|
+
const result = {
|
|
337
|
+
type: 'array',
|
|
338
|
+
items: convert(schema.element, context),
|
|
339
|
+
};
|
|
340
|
+
const { minimum, maximum } = schema._zod.bag;
|
|
341
|
+
if (typeof minimum === 'number') {
|
|
342
|
+
result.minItems = minimum;
|
|
343
|
+
}
|
|
344
|
+
if (typeof maximum === 'number') {
|
|
345
|
+
result.maxItems = maximum;
|
|
346
|
+
}
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
// Handle ZodRecord - convert to array of {key, value} pairs in strict mode
|
|
350
|
+
if (schema instanceof z.ZodRecord) {
|
|
351
|
+
if (context.strict) {
|
|
352
|
+
// In strict mode, records become arrays of {key, value} objects
|
|
353
|
+
return {
|
|
354
|
+
type: 'array',
|
|
355
|
+
items: {
|
|
356
|
+
type: 'object',
|
|
357
|
+
properties: {
|
|
358
|
+
key: convert(schema.keyType, context),
|
|
359
|
+
value: convert(schema.valueType, context),
|
|
360
|
+
},
|
|
361
|
+
required: ['key', 'value'],
|
|
362
|
+
additionalProperties: false,
|
|
363
|
+
},
|
|
149
364
|
};
|
|
150
365
|
}
|
|
151
|
-
|
|
152
|
-
|
|
366
|
+
else {
|
|
367
|
+
// In non-strict mode, use additionalProperties
|
|
368
|
+
return {
|
|
369
|
+
type: 'object',
|
|
370
|
+
propertyNames: convert(schema.keyType, context),
|
|
371
|
+
additionalProperties: convert(schema.valueType, context),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Handle ZodUnion
|
|
376
|
+
if (schema instanceof z.ZodUnion) {
|
|
377
|
+
const anyOf = schema.options.map(option => convert(option, context));
|
|
378
|
+
return { anyOf };
|
|
153
379
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
380
|
+
// Handle ZodDiscriminatedUnion
|
|
381
|
+
if (schema instanceof z.ZodDiscriminatedUnion) {
|
|
382
|
+
const anyOf = schema.options.map(option => convert(option, context));
|
|
383
|
+
return { anyOf };
|
|
384
|
+
}
|
|
385
|
+
// Handle ZodIntersection - not supported in OpenAI strict mode, convert to anyOf
|
|
386
|
+
if (schema instanceof z.ZodIntersection) {
|
|
387
|
+
const left = convert(schema.def.left, context);
|
|
388
|
+
const right = convert(schema.def.right, context);
|
|
389
|
+
const allOf = [
|
|
390
|
+
...(left.allOf && Object.keys(left).length === 1 ? left.allOf : [left]),
|
|
391
|
+
...(right.allOf && Object.keys(right).length === 1 ? right.allOf : [right]),
|
|
392
|
+
];
|
|
393
|
+
// TODO merge types to support allOf directly for strict mode
|
|
394
|
+
return context.strict ? { anyOf: allOf } : { allOf };
|
|
395
|
+
}
|
|
396
|
+
// Handle ZodTuple
|
|
397
|
+
if (schema instanceof z.ZodTuple) {
|
|
398
|
+
// Tuples in JSON Schema can be represented as arrays with prefixItems
|
|
399
|
+
// For simplicity, we'll use an array type
|
|
400
|
+
const items = schema.def.items.map(item => convert(item, context));
|
|
401
|
+
const rest = schema.def.rest ? convert(schema.def.rest, context) : undefined;
|
|
402
|
+
const result = { type: 'array' };
|
|
403
|
+
if (context.strict && rest) {
|
|
404
|
+
items.push(rest);
|
|
405
|
+
}
|
|
406
|
+
// If all items are the same type, simplify to a single items schema
|
|
407
|
+
if (items.length > 0 && items.every((item) => JSON.stringify(item) === JSON.stringify(items[0]))) {
|
|
408
|
+
result.items = items[0];
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
if (context.strict) {
|
|
412
|
+
result.items = { anyOf: items };
|
|
158
413
|
}
|
|
159
414
|
else {
|
|
160
|
-
|
|
415
|
+
result.prefixItems = items;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (!context.strict && rest) {
|
|
419
|
+
result.additionalItems = rest;
|
|
420
|
+
}
|
|
421
|
+
if (!rest) {
|
|
422
|
+
let minItems = items.length;
|
|
423
|
+
while (minItems > 0 && isOptional(schema.def.items[minItems - 1])) {
|
|
424
|
+
minItems--;
|
|
161
425
|
}
|
|
162
|
-
|
|
426
|
+
result.minItems = minItems;
|
|
427
|
+
result.maxItems = items.length;
|
|
163
428
|
}
|
|
429
|
+
const { minimum, maximum } = schema._zod.bag;
|
|
430
|
+
if (typeof minimum === 'number') {
|
|
431
|
+
result.minItems = minimum;
|
|
432
|
+
}
|
|
433
|
+
if (typeof maximum === 'number') {
|
|
434
|
+
result.maxItems = maximum;
|
|
435
|
+
}
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
// Handle ZodEnum
|
|
439
|
+
if (schema instanceof z.ZodEnum) {
|
|
440
|
+
const numericValues = Object.values(schema.def.entries).filter((v) => typeof v === "number");
|
|
441
|
+
const values = Object.entries(schema.def.entries)
|
|
442
|
+
.filter(([k, _]) => numericValues.indexOf(+k) === -1)
|
|
443
|
+
.map(([_, v]) => v);
|
|
444
|
+
return {
|
|
445
|
+
type: values.every((v) => typeof v === 'number')
|
|
446
|
+
? 'number'
|
|
447
|
+
: values.every((v) => typeof v === 'string')
|
|
448
|
+
? 'string'
|
|
449
|
+
: undefined,
|
|
450
|
+
enum: values,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
// Handle ZodLiteral
|
|
454
|
+
if (schema instanceof z.ZodLiteral) {
|
|
455
|
+
const values = Array.from(schema.values).filter(v => v !== undefined && typeof v !== 'function' && typeof v !== 'symbol' && typeof v !== 'bigint');
|
|
456
|
+
const types = Array.from(new Set(values.map(v => v === null ? 'null' : typeof v)));
|
|
457
|
+
return {
|
|
458
|
+
...(types.length === 1 ? { type: types[0] } : {}),
|
|
459
|
+
...(values.length === 1 ? { const: values[0] } : { enum: values }),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
// Handle ZodDefault
|
|
463
|
+
if (schema instanceof z.ZodDefault) {
|
|
464
|
+
const innerJson = convert(schema.def.innerType, context);
|
|
465
|
+
innerJson.default = JSON.parse(JSON.stringify(schema.def.defaultValue));
|
|
466
|
+
return innerJson;
|
|
467
|
+
}
|
|
468
|
+
// Handle ZodLazy - don't unwrap here, let convert() handle caching
|
|
469
|
+
// Just return empty schema since ZodLazy should be handled at convert() level
|
|
470
|
+
if (schema instanceof z.ZodLazy) {
|
|
471
|
+
// This shouldn't be reached because convert() handles ZodLazy before calling convertSchema
|
|
472
|
+
throw new Error('ZodLazy should be handled in convert(), not convertSchema()');
|
|
473
|
+
}
|
|
474
|
+
// Handle ZodCodec
|
|
475
|
+
if (schema instanceof z.ZodCodec) {
|
|
476
|
+
// For codecs, use the input schema for JSON Schema generation
|
|
477
|
+
return convert(schema.def.in, context);
|
|
478
|
+
}
|
|
479
|
+
// Handle ZodPipe (from preprocess)
|
|
480
|
+
if (schema instanceof z.ZodPipe) {
|
|
481
|
+
const innerType = schema.def.in._zod.def.type === "transform" ? schema.def.out : schema.def.in;
|
|
482
|
+
return convert(innerType, context);
|
|
164
483
|
}
|
|
165
|
-
|
|
484
|
+
// Handle primitive types
|
|
485
|
+
if (schema instanceof z.ZodString) {
|
|
486
|
+
const result = { type: 'string' };
|
|
487
|
+
const { minimum, maximum, format, patterns, contentEncoding } = schema._zod.bag;
|
|
488
|
+
if (context.strict) {
|
|
489
|
+
const strictFormats = ['date-time', 'time', 'date', 'duration', 'email', 'hostname', 'ipv4', 'ipv6', 'uuid'];
|
|
490
|
+
if (typeof format === 'string' && strictFormats.includes(format)) {
|
|
491
|
+
result.format = format;
|
|
492
|
+
}
|
|
493
|
+
if (patterns && patterns.size > 0) {
|
|
494
|
+
result.pattern = Array.from(patterns)[0].source;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
if (typeof minimum === 'number') {
|
|
499
|
+
result.minLength = minimum;
|
|
500
|
+
}
|
|
501
|
+
if (typeof maximum === 'number') {
|
|
502
|
+
result.maxLength = maximum;
|
|
503
|
+
}
|
|
504
|
+
if (typeof format === 'string') {
|
|
505
|
+
result.format = format;
|
|
506
|
+
}
|
|
507
|
+
if (typeof contentEncoding === 'string') {
|
|
508
|
+
result.contentEncoding = contentEncoding;
|
|
509
|
+
}
|
|
510
|
+
if (patterns) {
|
|
511
|
+
if (patterns.size === 1) {
|
|
512
|
+
result.pattern = Array.from(patterns)[0].source;
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
result.allOf = Array.from(patterns).map((regex) => ({
|
|
516
|
+
type: 'string',
|
|
517
|
+
pattern: regex.source,
|
|
518
|
+
}));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return result;
|
|
523
|
+
}
|
|
524
|
+
if (schema instanceof z.ZodNumber) {
|
|
525
|
+
const result = { type: 'number' };
|
|
526
|
+
// Add number constraints if present
|
|
527
|
+
const { minimum, maximum, format, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema._zod.bag;
|
|
528
|
+
if (typeof format === 'string' && format.includes("int")) {
|
|
529
|
+
result.type = 'integer';
|
|
530
|
+
}
|
|
531
|
+
if (typeof exclusiveMinimum === 'number') {
|
|
532
|
+
result.exclusiveMinimum = exclusiveMinimum;
|
|
533
|
+
}
|
|
534
|
+
else if (typeof minimum === 'number') {
|
|
535
|
+
result.minimum = minimum;
|
|
536
|
+
}
|
|
537
|
+
if (typeof exclusiveMaximum === 'number') {
|
|
538
|
+
result.exclusiveMaximum = exclusiveMaximum;
|
|
539
|
+
}
|
|
540
|
+
else if (typeof maximum === 'number') {
|
|
541
|
+
result.maximum = maximum;
|
|
542
|
+
}
|
|
543
|
+
if (typeof multipleOf === 'number') {
|
|
544
|
+
result.multipleOf = multipleOf;
|
|
545
|
+
}
|
|
546
|
+
return result;
|
|
547
|
+
}
|
|
548
|
+
if (schema instanceof z.ZodBoolean || schema instanceof z.ZodSuccess) {
|
|
549
|
+
return { type: 'boolean' };
|
|
550
|
+
}
|
|
551
|
+
if (schema instanceof z.ZodBigInt) {
|
|
552
|
+
return { type: 'integer' };
|
|
553
|
+
}
|
|
554
|
+
if (schema instanceof z.ZodDate || schema instanceof z.ZodISODateTime) {
|
|
555
|
+
return { type: 'string', format: 'date-time' };
|
|
556
|
+
}
|
|
557
|
+
if (schema instanceof z.ZodISODate) {
|
|
558
|
+
return { type: 'string', format: 'date' };
|
|
559
|
+
}
|
|
560
|
+
if (schema instanceof z.ZodISOTime) {
|
|
561
|
+
return { type: 'string', format: 'time' };
|
|
562
|
+
}
|
|
563
|
+
if (schema instanceof z.ZodISODuration) {
|
|
564
|
+
return { type: 'string', format: 'duration' };
|
|
565
|
+
}
|
|
566
|
+
if (schema instanceof z.ZodEmail) {
|
|
567
|
+
return { type: 'string', format: 'email' };
|
|
568
|
+
}
|
|
569
|
+
if (schema instanceof z.ZodIPv4) {
|
|
570
|
+
return { type: 'string', format: 'ipv4' };
|
|
571
|
+
}
|
|
572
|
+
if (schema instanceof z.ZodIPv6) {
|
|
573
|
+
return { type: 'string', format: 'ipv6' };
|
|
574
|
+
}
|
|
575
|
+
if (schema instanceof z.ZodUUID) {
|
|
576
|
+
return { type: 'string', format: 'uuid' };
|
|
577
|
+
}
|
|
578
|
+
if (schema instanceof z.ZodNull) {
|
|
579
|
+
return { type: 'null' };
|
|
580
|
+
}
|
|
581
|
+
if (schema instanceof z.ZodTemplateLiteral) {
|
|
582
|
+
return { type: 'string', pattern: schema._zod.pattern?.source };
|
|
583
|
+
}
|
|
584
|
+
if (schema instanceof z.ZodUndefined) {
|
|
585
|
+
return { type: 'null' }; // Treat undefined as null in JSON
|
|
586
|
+
}
|
|
587
|
+
if (schema instanceof z.ZodAny) {
|
|
588
|
+
return {}; // Any type has no constraints
|
|
589
|
+
}
|
|
590
|
+
if (schema instanceof z.ZodUnknown) {
|
|
591
|
+
return {}; // Unknown type has no constraints
|
|
592
|
+
}
|
|
593
|
+
if (schema instanceof z.ZodTransform) {
|
|
594
|
+
return {}; // Transforms are not represented in JSON Schema
|
|
595
|
+
}
|
|
596
|
+
// Fallback for unknown types
|
|
597
|
+
console.warn(`Unknown Zod schema type: ${schema.constructor.name}`);
|
|
598
|
+
return {};
|
|
599
|
+
}
|
|
166
600
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* @param schema - input Zod schema
|
|
170
|
-
* @returns - JSON Schema object
|
|
601
|
+
* Makes a JSON Schema nullable by adding null to the type
|
|
171
602
|
*/
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
603
|
+
function makeNullable(schema) {
|
|
604
|
+
if (schema.$ref) {
|
|
605
|
+
// If it's a reference, wrap in anyOf with null
|
|
606
|
+
return {
|
|
607
|
+
anyOf: [
|
|
608
|
+
{ $ref: schema.$ref },
|
|
609
|
+
{ type: 'null' },
|
|
610
|
+
],
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
if (schema.type) {
|
|
614
|
+
// Check if schema has type-specific properties that require anyOf structure
|
|
615
|
+
// Arrays have 'items', objects have 'properties', etc.
|
|
616
|
+
const hasTypeSpecificProps = schema.items !== undefined ||
|
|
617
|
+
schema.properties !== undefined ||
|
|
618
|
+
schema.additionalProperties !== undefined ||
|
|
619
|
+
schema.patternProperties !== undefined ||
|
|
620
|
+
schema.minItems !== undefined ||
|
|
621
|
+
schema.maxItems !== undefined ||
|
|
622
|
+
schema.minProperties !== undefined ||
|
|
623
|
+
schema.maxProperties !== undefined;
|
|
624
|
+
// If there are type-specific properties, must use anyOf to separate type schemas
|
|
625
|
+
if (hasTypeSpecificProps) {
|
|
626
|
+
// Extract type-specific properties based on the type
|
|
627
|
+
const { type, description, ...rest } = schema;
|
|
628
|
+
const baseType = Array.isArray(type) ? type[0] : type;
|
|
629
|
+
return {
|
|
630
|
+
anyOf: [
|
|
631
|
+
{ type: baseType, ...rest },
|
|
632
|
+
{ type: 'null' },
|
|
633
|
+
],
|
|
634
|
+
...(description ? { description } : {}),
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
// For simple types without type-specific properties, can use array notation
|
|
638
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
639
|
+
if (!types.includes('null')) {
|
|
640
|
+
return {
|
|
641
|
+
...schema,
|
|
642
|
+
type: [...types, 'null'],
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return schema;
|
|
646
|
+
}
|
|
647
|
+
if (schema.anyOf) {
|
|
648
|
+
// Already has anyOf, add null option if not present
|
|
649
|
+
const hasNull = schema.anyOf.some((s) => s.type === 'null');
|
|
650
|
+
if (!hasNull) {
|
|
651
|
+
return {
|
|
652
|
+
...schema,
|
|
653
|
+
anyOf: [...schema.anyOf, { type: 'null' }],
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
return schema;
|
|
657
|
+
}
|
|
658
|
+
// For complex schemas without type, wrap in anyOf
|
|
659
|
+
return {
|
|
660
|
+
anyOf: [
|
|
661
|
+
schema,
|
|
662
|
+
{ type: 'null' },
|
|
663
|
+
],
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
function isOptional(schema) {
|
|
667
|
+
return schema._zod.optin !== undefined;
|
|
180
668
|
}
|
|
181
669
|
//# sourceMappingURL=schema.js.map
|