@aeye/core 0.2.0 → 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 +8 -0
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +10 -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 +28 -8
- package/dist/prompt.js.map +1 -1
- package/dist/schema.d.ts +16 -16
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +288 -348
- package/dist/schema.js.map +1 -1
- package/dist/tool.js +1 -1
- package/dist/tool.js.map +1 -1
- package/package.json +54 -54
package/dist/schema.js
CHANGED
|
@@ -153,163 +153,6 @@ function strictifySimple(schema, transform) {
|
|
|
153
153
|
// For all other types (primitives, etc.), return as-is
|
|
154
154
|
return schema;
|
|
155
155
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Modify a JSON Schema property to make it optional (accept null)
|
|
158
|
-
*
|
|
159
|
-
* @param prop - input JSON Schema property
|
|
160
|
-
* @returns
|
|
161
|
-
*/
|
|
162
|
-
const makeOptional = (prop) => {
|
|
163
|
-
if (prop.type) {
|
|
164
|
-
const values = Array.isArray(prop.type) ? prop.type : [prop.type];
|
|
165
|
-
if (!values.includes('null')) {
|
|
166
|
-
prop.type = [...values, 'null'];
|
|
167
|
-
}
|
|
168
|
-
return prop;
|
|
169
|
-
}
|
|
170
|
-
else if (prop.$ref) {
|
|
171
|
-
// If it's a $ref, wrap it in anyOf with null
|
|
172
|
-
return {
|
|
173
|
-
anyOf: [
|
|
174
|
-
{ $ref: prop.$ref },
|
|
175
|
-
{ type: 'null' }
|
|
176
|
-
],
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
else if (prop.allOf) {
|
|
180
|
-
// If it's an allOf with a single entry containing a $ref, extract and wrap the $ref
|
|
181
|
-
// This handles the common case: { allOf: [{ $ref: "..." }] }
|
|
182
|
-
if (prop.allOf.length === 1 && prop.allOf[0].$ref) {
|
|
183
|
-
return {
|
|
184
|
-
anyOf: [
|
|
185
|
-
{ $ref: prop.allOf[0].$ref },
|
|
186
|
-
{ type: 'null' }
|
|
187
|
-
],
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
// Otherwise preserve the allOf structure
|
|
191
|
-
return {
|
|
192
|
-
anyOf: [
|
|
193
|
-
{ allOf: prop.allOf },
|
|
194
|
-
{ type: 'null' }
|
|
195
|
-
],
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
const { title, description, default: defaultValue, ...rest } = prop;
|
|
200
|
-
return {
|
|
201
|
-
title,
|
|
202
|
-
description,
|
|
203
|
-
default: defaultValue,
|
|
204
|
-
anyOf: [
|
|
205
|
-
rest,
|
|
206
|
-
{ type: 'null' }
|
|
207
|
-
],
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
/**
|
|
212
|
-
* Override function to modify JSON Schema generation
|
|
213
|
-
* to make optional fields accept null values.
|
|
214
|
-
*/
|
|
215
|
-
const typeOverride = ({ zodSchema, jsonSchema, path }) => {
|
|
216
|
-
if (jsonSchema.type === 'object' && zodSchema instanceof z.ZodObject) {
|
|
217
|
-
const properties = jsonSchema.properties || {};
|
|
218
|
-
const propertyKeys = Object.keys(properties);
|
|
219
|
-
const required = jsonSchema.required || [];
|
|
220
|
-
const optional = propertyKeys.filter((key) => !required.includes(key));
|
|
221
|
-
// Add null to optional fields (those not in required array)
|
|
222
|
-
for (const key of optional) {
|
|
223
|
-
properties[key] = makeOptional(properties[key]);
|
|
224
|
-
}
|
|
225
|
-
// Check fields that are required - if they use preprocess with optional, add null
|
|
226
|
-
for (const key of required) {
|
|
227
|
-
const fieldSchema = zodSchema.shape[key];
|
|
228
|
-
// Check if this is a ZodPipe (from preprocess) wrapping optional
|
|
229
|
-
if (fieldSchema instanceof z.ZodPipe) {
|
|
230
|
-
const outputSchema = fieldSchema.def.out;
|
|
231
|
-
if (outputSchema instanceof z.ZodOptional) {
|
|
232
|
-
const prop = properties[key];
|
|
233
|
-
// Skip empty objects - they're placeholders for recursive schemas that will be filled in later
|
|
234
|
-
if (Object.keys(prop).length === 0) {
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
properties[key] = makeOptional(prop);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
jsonSchema.required = propertyKeys;
|
|
242
|
-
jsonSchema.additionalProperties = false;
|
|
243
|
-
}
|
|
244
|
-
// Convert allOf with single entry to that entry or anyOf.
|
|
245
|
-
// allOf is not supported by some AI schema parsers.
|
|
246
|
-
if (jsonSchema.allOf?.length) {
|
|
247
|
-
if (jsonSchema.allOf.length === 1) {
|
|
248
|
-
if (Object.keys(jsonSchema).length === 1) {
|
|
249
|
-
// Delete allOf first, THEN assign the content
|
|
250
|
-
const content = jsonSchema.allOf[0];
|
|
251
|
-
delete jsonSchema.allOf;
|
|
252
|
-
Object.assign(jsonSchema, content);
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
jsonSchema.anyOf = jsonSchema.allOf;
|
|
256
|
-
delete jsonSchema.allOf;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
// For anyOf with null, convert to type array
|
|
261
|
-
if (jsonSchema.anyOf?.length === 2) {
|
|
262
|
-
if (jsonSchema.anyOf[1].type === 'null'
|
|
263
|
-
&& Object.keys(jsonSchema.anyOf[1]).length === 1
|
|
264
|
-
&& jsonSchema.anyOf[0].type
|
|
265
|
-
&& !Array.isArray(jsonSchema.anyOf[0].type)) {
|
|
266
|
-
const first = jsonSchema.anyOf[0];
|
|
267
|
-
const nullableType = [first.type, 'null'];
|
|
268
|
-
delete jsonSchema.anyOf;
|
|
269
|
-
Object.assign(jsonSchema, first);
|
|
270
|
-
// Set type after assign to avoid it being overwritten
|
|
271
|
-
jsonSchema.type = nullableType;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
// anyOf in an anyOf - flatten
|
|
275
|
-
// BUT: Don't flatten if it would lose $ref or other important properties
|
|
276
|
-
if (jsonSchema.anyOf?.length) {
|
|
277
|
-
const newAnyOf = [];
|
|
278
|
-
for (const subSchema of jsonSchema.anyOf) {
|
|
279
|
-
// Check if this sub-schema has a nested anyOf that should be flattened
|
|
280
|
-
// Only flatten if:
|
|
281
|
-
// 1. The sub-schema has an anyOf property
|
|
282
|
-
// 2. The sub-schema has no other properties except anyOf (it's a pure wrapper)
|
|
283
|
-
// 3. None of the elements in the nested anyOf are $refs (which should be preserved)
|
|
284
|
-
const isPureAnyOfWrapper = subSchema.anyOf?.length && Object.keys(subSchema).length === 1;
|
|
285
|
-
const hasRefInNested = subSchema.anyOf?.some((nested) => nested.$ref);
|
|
286
|
-
if (isPureAnyOfWrapper && !hasRefInNested) {
|
|
287
|
-
// Safe to flatten
|
|
288
|
-
newAnyOf.push(...subSchema.anyOf);
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
// Keep as-is
|
|
292
|
-
newAnyOf.push(subSchema);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
jsonSchema.anyOf = newAnyOf;
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
/**
|
|
299
|
-
* Converts a Zod schema to JSON Schema that's compatible with AI schemas.
|
|
300
|
-
*
|
|
301
|
-
* @param schema - input Zod schema
|
|
302
|
-
* @returns - JSON Schema object
|
|
303
|
-
*/
|
|
304
|
-
export function toJSONSchema(schema, strict) {
|
|
305
|
-
return z.toJSONSchema(schema, {
|
|
306
|
-
target: 'draft-7',
|
|
307
|
-
reused: 'ref',
|
|
308
|
-
io: 'input',
|
|
309
|
-
unrepresentable: 'any',
|
|
310
|
-
override: strict ? typeOverride : undefined,
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
156
|
/**
|
|
314
157
|
* Converts a Zod schema to JSON Schema V2 with support for different provider formats.
|
|
315
158
|
*
|
|
@@ -332,13 +175,15 @@ export function toJSONSchema(schema, strict) {
|
|
|
332
175
|
* const jsonSchema = toJSONSchemaV2(strictSchema, { strict: true, format: 'openai' });
|
|
333
176
|
* ```
|
|
334
177
|
*/
|
|
335
|
-
export function
|
|
336
|
-
const
|
|
178
|
+
export function toJSONSchema(schema, options) {
|
|
179
|
+
const resolvedOptions = typeof options === 'boolean' ? { strict: options } : options;
|
|
180
|
+
const { strict, format = 'openai' } = resolvedOptions;
|
|
337
181
|
// Currently only OpenAI format is supported
|
|
338
182
|
if (format !== 'openai') {
|
|
339
183
|
throw new Error(`Unsupported format: ${format}. Currently only 'openai' is supported.`);
|
|
340
184
|
}
|
|
341
185
|
const context = {
|
|
186
|
+
root: schema,
|
|
342
187
|
strict,
|
|
343
188
|
format,
|
|
344
189
|
definitions: new Map(),
|
|
@@ -346,7 +191,7 @@ export function toJSONSchemaV2(schema, options) {
|
|
|
346
191
|
refCounter: 0,
|
|
347
192
|
path: [],
|
|
348
193
|
};
|
|
349
|
-
const result =
|
|
194
|
+
const result = convert(schema, context);
|
|
350
195
|
// Add definitions if any were created
|
|
351
196
|
if (context.definitionSchemas.size > 0) {
|
|
352
197
|
result.$defs = Object.fromEntries(context.definitionSchemas);
|
|
@@ -356,16 +201,95 @@ export function toJSONSchemaV2(schema, options) {
|
|
|
356
201
|
/**
|
|
357
202
|
* Main recursive conversion function
|
|
358
203
|
*/
|
|
359
|
-
function
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
+
}
|
|
364
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
|
|
365
289
|
// Handle ZodOptional
|
|
366
290
|
if (schema instanceof z.ZodOptional) {
|
|
367
291
|
const innerSchema = schema.unwrap();
|
|
368
|
-
const innerJson =
|
|
292
|
+
const innerJson = convert(innerSchema, context);
|
|
369
293
|
if (context.strict) {
|
|
370
294
|
// In strict mode, optional fields become nullable
|
|
371
295
|
return makeNullable(innerJson);
|
|
@@ -378,56 +302,19 @@ function convertSchema(schema, context) {
|
|
|
378
302
|
// Handle ZodNullable
|
|
379
303
|
if (schema instanceof z.ZodNullable) {
|
|
380
304
|
const innerSchema = schema.unwrap();
|
|
381
|
-
const innerJson =
|
|
305
|
+
const innerJson = convert(innerSchema, context);
|
|
382
306
|
return makeNullable(innerJson);
|
|
383
307
|
}
|
|
384
308
|
// Handle ZodObject
|
|
385
309
|
if (schema instanceof z.ZodObject) {
|
|
386
|
-
// Check if this object is recursive (contains self-references through getters)
|
|
387
|
-
// If it has an 'aid' in meta, create a definition for it
|
|
388
|
-
const meta = schema.meta();
|
|
389
|
-
if (meta?.aid && !context.definitions.has(schema)) {
|
|
390
|
-
// Register this schema as a definition
|
|
391
|
-
const refName = meta.aid;
|
|
392
|
-
context.definitions.set(schema, refName);
|
|
393
|
-
// Convert the schema
|
|
394
|
-
const properties = {};
|
|
395
|
-
const required = [];
|
|
396
|
-
const shape = schema.shape;
|
|
397
|
-
for (const key in shape) {
|
|
398
|
-
const fieldSchema = shape[key];
|
|
399
|
-
const isOptional = fieldSchema instanceof z.ZodOptional ||
|
|
400
|
-
(fieldSchema instanceof z.ZodPipe && fieldSchema.def.out instanceof z.ZodOptional);
|
|
401
|
-
properties[key] = convertSchema(fieldSchema, context);
|
|
402
|
-
// In strict mode, ALL fields are required (optional ones are nullable)
|
|
403
|
-
// In non-strict mode, only non-optional fields are required
|
|
404
|
-
if (context.strict || !isOptional) {
|
|
405
|
-
required.push(key);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
const schemaJson = {
|
|
409
|
-
type: 'object',
|
|
410
|
-
properties,
|
|
411
|
-
required,
|
|
412
|
-
};
|
|
413
|
-
if (context.strict) {
|
|
414
|
-
schemaJson.additionalProperties = false;
|
|
415
|
-
}
|
|
416
|
-
addMetadata(schemaJson, schema);
|
|
417
|
-
context.definitionSchemas.set(refName, schemaJson);
|
|
418
|
-
return { $ref: `#/$defs/${refName}` };
|
|
419
|
-
}
|
|
420
310
|
const properties = {};
|
|
421
311
|
const required = [];
|
|
422
312
|
const shape = schema.shape;
|
|
423
313
|
for (const key in shape) {
|
|
424
314
|
const fieldSchema = shape[key];
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
// In strict mode, ALL fields are required (optional ones are nullable)
|
|
429
|
-
// In non-strict mode, only non-optional fields are required
|
|
430
|
-
if (context.strict || !isOptional) {
|
|
315
|
+
const isRequired = context.strict || !isOptional(fieldSchema);
|
|
316
|
+
properties[key] = convert(fieldSchema, context);
|
|
317
|
+
if (isRequired) {
|
|
431
318
|
required.push(key);
|
|
432
319
|
}
|
|
433
320
|
}
|
|
@@ -436,35 +323,40 @@ function convertSchema(schema, context) {
|
|
|
436
323
|
properties,
|
|
437
324
|
required,
|
|
438
325
|
};
|
|
439
|
-
if (context.strict) {
|
|
326
|
+
if (context.strict || schema.def.catchall?._zod.def.type === "never") {
|
|
440
327
|
result.additionalProperties = false;
|
|
441
328
|
}
|
|
442
|
-
|
|
329
|
+
else if (schema.def.catchall) {
|
|
330
|
+
result.additionalProperties = convert(schema.def.catchall, context);
|
|
331
|
+
}
|
|
332
|
+
return result;
|
|
443
333
|
}
|
|
444
334
|
// Handle ZodArray
|
|
445
335
|
if (schema instanceof z.ZodArray) {
|
|
446
|
-
const items = convertSchema(schema.element, context);
|
|
447
336
|
const result = {
|
|
448
337
|
type: 'array',
|
|
449
|
-
items,
|
|
338
|
+
items: convert(schema.element, context),
|
|
450
339
|
};
|
|
451
|
-
|
|
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;
|
|
452
348
|
}
|
|
453
349
|
// Handle ZodRecord - convert to array of {key, value} pairs in strict mode
|
|
454
350
|
if (schema instanceof z.ZodRecord) {
|
|
455
|
-
const keySchema = schema.keyType || z.string();
|
|
456
|
-
const valueSchema = schema.valueType;
|
|
457
351
|
if (context.strict) {
|
|
458
352
|
// In strict mode, records become arrays of {key, value} objects
|
|
459
|
-
const keyJson = convertSchema(keySchema, context);
|
|
460
|
-
const valueJson = convertSchema(valueSchema, context);
|
|
461
353
|
return {
|
|
462
354
|
type: 'array',
|
|
463
355
|
items: {
|
|
464
356
|
type: 'object',
|
|
465
357
|
properties: {
|
|
466
|
-
key:
|
|
467
|
-
value:
|
|
358
|
+
key: convert(schema.keyType, context),
|
|
359
|
+
value: convert(schema.valueType, context),
|
|
468
360
|
},
|
|
469
361
|
required: ['key', 'value'],
|
|
470
362
|
additionalProperties: false,
|
|
@@ -473,198 +365,233 @@ function convertSchema(schema, context) {
|
|
|
473
365
|
}
|
|
474
366
|
else {
|
|
475
367
|
// In non-strict mode, use additionalProperties
|
|
476
|
-
const valueJson = convertSchema(valueSchema, context);
|
|
477
368
|
return {
|
|
478
369
|
type: 'object',
|
|
479
|
-
|
|
370
|
+
propertyNames: convert(schema.keyType, context),
|
|
371
|
+
additionalProperties: convert(schema.valueType, context),
|
|
480
372
|
};
|
|
481
373
|
}
|
|
482
374
|
}
|
|
483
375
|
// Handle ZodUnion
|
|
484
376
|
if (schema instanceof z.ZodUnion) {
|
|
485
|
-
const anyOf = schema.options.map(
|
|
377
|
+
const anyOf = schema.options.map(option => convert(option, context));
|
|
486
378
|
return { anyOf };
|
|
487
379
|
}
|
|
488
380
|
// Handle ZodDiscriminatedUnion
|
|
489
381
|
if (schema instanceof z.ZodDiscriminatedUnion) {
|
|
490
|
-
const anyOf = schema.options.map(
|
|
382
|
+
const anyOf = schema.options.map(option => convert(option, context));
|
|
491
383
|
return { anyOf };
|
|
492
384
|
}
|
|
493
385
|
// Handle ZodIntersection - not supported in OpenAI strict mode, convert to anyOf
|
|
494
386
|
if (schema instanceof z.ZodIntersection) {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
// For non-strict, we could try to merge objects, but for now use anyOf
|
|
505
|
-
return {
|
|
506
|
-
anyOf: [
|
|
507
|
-
convertSchema(schema.def.left, context),
|
|
508
|
-
convertSchema(schema.def.right, context),
|
|
509
|
-
],
|
|
510
|
-
};
|
|
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 };
|
|
511
395
|
}
|
|
512
396
|
// Handle ZodTuple
|
|
513
397
|
if (schema instanceof z.ZodTuple) {
|
|
514
398
|
// Tuples in JSON Schema can be represented as arrays with prefixItems
|
|
515
399
|
// For simplicity, we'll use an array type
|
|
516
|
-
const items = schema.def.items.map(
|
|
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
|
+
}
|
|
517
406
|
// If all items are the same type, simplify to a single items schema
|
|
518
407
|
if (items.length > 0 && items.every((item) => JSON.stringify(item) === JSON.stringify(items[0]))) {
|
|
519
|
-
|
|
520
|
-
type: 'array',
|
|
521
|
-
items: items[0],
|
|
522
|
-
};
|
|
408
|
+
result.items = items[0];
|
|
523
409
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
410
|
+
else {
|
|
411
|
+
if (context.strict) {
|
|
412
|
+
result.items = { anyOf: items };
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
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--;
|
|
425
|
+
}
|
|
426
|
+
result.minItems = minItems;
|
|
427
|
+
result.maxItems = items.length;
|
|
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;
|
|
531
437
|
}
|
|
532
438
|
// Handle ZodEnum
|
|
533
439
|
if (schema instanceof z.ZodEnum) {
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
return addMetadata(result, schema);
|
|
539
|
-
}
|
|
540
|
-
// Handle ZodNativeEnum
|
|
541
|
-
if (schema._def?.typeName === 'ZodNativeEnum') {
|
|
542
|
-
const enumValues = Object.values(schema._def.values);
|
|
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);
|
|
543
444
|
return {
|
|
544
|
-
type: typeof
|
|
545
|
-
|
|
445
|
+
type: values.every((v) => typeof v === 'number')
|
|
446
|
+
? 'number'
|
|
447
|
+
: values.every((v) => typeof v === 'string')
|
|
448
|
+
? 'string'
|
|
449
|
+
: undefined,
|
|
450
|
+
enum: values,
|
|
546
451
|
};
|
|
547
452
|
}
|
|
548
453
|
// Handle ZodLiteral
|
|
549
454
|
if (schema instanceof z.ZodLiteral) {
|
|
550
|
-
const
|
|
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)));
|
|
551
457
|
return {
|
|
552
|
-
type:
|
|
553
|
-
const:
|
|
458
|
+
...(types.length === 1 ? { type: types[0] } : {}),
|
|
459
|
+
...(values.length === 1 ? { const: values[0] } : { enum: values }),
|
|
554
460
|
};
|
|
555
461
|
}
|
|
556
462
|
// Handle ZodDefault
|
|
557
463
|
if (schema instanceof z.ZodDefault) {
|
|
558
|
-
const innerJson =
|
|
559
|
-
innerJson.default = schema.def.defaultValue;
|
|
464
|
+
const innerJson = convert(schema.def.innerType, context);
|
|
465
|
+
innerJson.default = JSON.parse(JSON.stringify(schema.def.defaultValue));
|
|
560
466
|
return innerJson;
|
|
561
467
|
}
|
|
562
|
-
// Handle ZodLazy
|
|
468
|
+
// Handle ZodLazy - don't unwrap here, let convert() handle caching
|
|
469
|
+
// Just return empty schema since ZodLazy should be handled at convert() level
|
|
563
470
|
if (schema instanceof z.ZodLazy) {
|
|
564
|
-
//
|
|
565
|
-
|
|
566
|
-
const refName = meta?.aid || `LazySchema${context.refCounter++}`;
|
|
567
|
-
context.definitions.set(schema, refName);
|
|
568
|
-
// Get the actual schema
|
|
569
|
-
const actualSchema = schema.def.getter();
|
|
570
|
-
const schemaJson = convertSchema(actualSchema, context);
|
|
571
|
-
context.definitionSchemas.set(refName, schemaJson);
|
|
572
|
-
return { $ref: `#/$defs/${refName}` };
|
|
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()');
|
|
573
473
|
}
|
|
574
474
|
// Handle ZodCodec
|
|
575
475
|
if (schema instanceof z.ZodCodec) {
|
|
576
476
|
// For codecs, use the input schema for JSON Schema generation
|
|
577
|
-
return
|
|
477
|
+
return convert(schema.def.in, context);
|
|
578
478
|
}
|
|
579
479
|
// Handle ZodPipe (from preprocess)
|
|
580
480
|
if (schema instanceof z.ZodPipe) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (context.strict && schema.def.out instanceof z.ZodOptional) {
|
|
584
|
-
// This is from strictify - the output is optional, so we want nullable
|
|
585
|
-
const innerSchema = schema.def.out.unwrap();
|
|
586
|
-
const innerJson = convertSchema(innerSchema, context);
|
|
587
|
-
return makeNullable(innerJson);
|
|
588
|
-
}
|
|
589
|
-
return convertSchema(schema.def.in, context);
|
|
590
|
-
}
|
|
591
|
-
// Handle ZodTransform (from z.transform())
|
|
592
|
-
if (schema._def?.typeName === 'ZodTransform') {
|
|
593
|
-
// Use the input schema for JSON Schema generation
|
|
594
|
-
return convertSchema(schema._def.schema, context);
|
|
481
|
+
const innerType = schema.def.in._zod.def.type === "transform" ? schema.def.out : schema.def.in;
|
|
482
|
+
return convert(innerType, context);
|
|
595
483
|
}
|
|
596
484
|
// Handle primitive types
|
|
597
485
|
if (schema instanceof z.ZodString) {
|
|
598
486
|
const result = { type: 'string' };
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
if (
|
|
603
|
-
result.
|
|
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;
|
|
604
492
|
}
|
|
605
|
-
|
|
606
|
-
result.
|
|
493
|
+
if (patterns && patterns.size > 0) {
|
|
494
|
+
result.pattern = Array.from(patterns)[0].source;
|
|
607
495
|
}
|
|
608
|
-
|
|
609
|
-
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
if (typeof minimum === 'number') {
|
|
499
|
+
result.minLength = minimum;
|
|
610
500
|
}
|
|
611
|
-
|
|
612
|
-
result.
|
|
501
|
+
if (typeof maximum === 'number') {
|
|
502
|
+
result.maxLength = maximum;
|
|
613
503
|
}
|
|
614
|
-
|
|
615
|
-
result.format =
|
|
504
|
+
if (typeof format === 'string') {
|
|
505
|
+
result.format = format;
|
|
616
506
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
if (schema instanceof z.ZodNumber) {
|
|
621
|
-
const result = { type: 'number' };
|
|
622
|
-
// Add number constraints if present
|
|
623
|
-
const checks = schema._def.checks || [];
|
|
624
|
-
for (const check of checks) {
|
|
625
|
-
if (check.kind === 'min') {
|
|
626
|
-
if (check.inclusive) {
|
|
627
|
-
result.minimum = check.value;
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
630
|
-
result.exclusiveMinimum = check.value;
|
|
631
|
-
}
|
|
507
|
+
if (typeof contentEncoding === 'string') {
|
|
508
|
+
result.contentEncoding = contentEncoding;
|
|
632
509
|
}
|
|
633
|
-
|
|
634
|
-
if (
|
|
635
|
-
result.
|
|
510
|
+
if (patterns) {
|
|
511
|
+
if (patterns.size === 1) {
|
|
512
|
+
result.pattern = Array.from(patterns)[0].source;
|
|
636
513
|
}
|
|
637
514
|
else {
|
|
638
|
-
result.
|
|
515
|
+
result.allOf = Array.from(patterns).map((regex) => ({
|
|
516
|
+
type: 'string',
|
|
517
|
+
pattern: regex.source,
|
|
518
|
+
}));
|
|
639
519
|
}
|
|
640
520
|
}
|
|
641
|
-
else if (check.kind === 'multipleOf') {
|
|
642
|
-
result.multipleOf = check.value;
|
|
643
|
-
}
|
|
644
521
|
}
|
|
645
|
-
return
|
|
522
|
+
return result;
|
|
646
523
|
}
|
|
647
|
-
if (schema instanceof z.
|
|
648
|
-
|
|
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' };
|
|
649
550
|
}
|
|
650
551
|
if (schema instanceof z.ZodBigInt) {
|
|
651
|
-
return
|
|
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' };
|
|
652
565
|
}
|
|
653
|
-
if (schema instanceof z.
|
|
654
|
-
return
|
|
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' };
|
|
655
577
|
}
|
|
656
578
|
if (schema instanceof z.ZodNull) {
|
|
657
579
|
return { type: 'null' };
|
|
658
580
|
}
|
|
581
|
+
if (schema instanceof z.ZodTemplateLiteral) {
|
|
582
|
+
return { type: 'string', pattern: schema._zod.pattern?.source };
|
|
583
|
+
}
|
|
659
584
|
if (schema instanceof z.ZodUndefined) {
|
|
660
585
|
return { type: 'null' }; // Treat undefined as null in JSON
|
|
661
586
|
}
|
|
662
587
|
if (schema instanceof z.ZodAny) {
|
|
663
588
|
return {}; // Any type has no constraints
|
|
664
589
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
|
668
595
|
}
|
|
669
596
|
// Fallback for unknown types
|
|
670
597
|
console.warn(`Unknown Zod schema type: ${schema.constructor.name}`);
|
|
@@ -684,7 +611,30 @@ function makeNullable(schema) {
|
|
|
684
611
|
};
|
|
685
612
|
}
|
|
686
613
|
if (schema.type) {
|
|
687
|
-
//
|
|
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
|
|
688
638
|
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
689
639
|
if (!types.includes('null')) {
|
|
690
640
|
return {
|
|
@@ -713,17 +663,7 @@ function makeNullable(schema) {
|
|
|
713
663
|
],
|
|
714
664
|
};
|
|
715
665
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
*/
|
|
719
|
-
function addMetadata(jsonSchema, zodSchema) {
|
|
720
|
-
if (zodSchema.description) {
|
|
721
|
-
jsonSchema.description = zodSchema.description;
|
|
722
|
-
}
|
|
723
|
-
const meta = zodSchema.meta();
|
|
724
|
-
if (meta?.title) {
|
|
725
|
-
jsonSchema.title = meta.title;
|
|
726
|
-
}
|
|
727
|
-
return jsonSchema;
|
|
666
|
+
function isOptional(schema) {
|
|
667
|
+
return schema._zod.optin !== undefined;
|
|
728
668
|
}
|
|
729
669
|
//# sourceMappingURL=schema.js.map
|