@discomedia/utils 1.0.61 → 1.0.63
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/index-frontend.cjs +3124 -235
- package/dist/index-frontend.cjs.map +1 -1
- package/dist/index-frontend.mjs +3124 -235
- package/dist/index-frontend.mjs.map +1 -1
- package/dist/index.cjs +3120 -231
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +3120 -231
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +3 -2
- package/dist/test.js +8944 -349
- package/dist/test.js.map +1 -1
- package/dist/types/llm-config.d.ts +3 -3
- package/dist/types/llm-config.d.ts.map +1 -1
- package/dist/types/llm-images.d.ts.map +1 -1
- package/dist/types/llm-openai.d.ts +6 -2
- package/dist/types/llm-openai.d.ts.map +1 -1
- package/dist/types/types/llm-types.d.ts +11 -18
- package/dist/types/types/llm-types.d.ts.map +1 -1
- package/dist/types-frontend/llm-config.d.ts +3 -3
- package/dist/types-frontend/llm-config.d.ts.map +1 -1
- package/dist/types-frontend/llm-images.d.ts.map +1 -1
- package/dist/types-frontend/llm-openai.d.ts +6 -2
- package/dist/types-frontend/llm-openai.d.ts.map +1 -1
- package/dist/types-frontend/types/llm-types.d.ts +11 -18
- package/dist/types-frontend/types/llm-types.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/index-frontend.mjs
CHANGED
|
@@ -7,6 +7,8 @@ function isOpenRouterModel(model) {
|
|
|
7
7
|
'openai/gpt-5-mini',
|
|
8
8
|
'openai/gpt-5-nano',
|
|
9
9
|
'openai/gpt-5.1',
|
|
10
|
+
'openai/gpt-5.4',
|
|
11
|
+
'openai/gpt-5.4-pro',
|
|
10
12
|
'openai/gpt-5.2',
|
|
11
13
|
'openai/gpt-5.2-pro',
|
|
12
14
|
'openai/gpt-5.1-codex',
|
|
@@ -223,7 +225,7 @@ function maybeObj(x) {
|
|
|
223
225
|
return x ?? {};
|
|
224
226
|
}
|
|
225
227
|
// https://stackoverflow.com/a/34491287
|
|
226
|
-
function isEmptyObj(obj) {
|
|
228
|
+
function isEmptyObj$1(obj) {
|
|
227
229
|
if (!obj)
|
|
228
230
|
return true;
|
|
229
231
|
for (const _k in obj)
|
|
@@ -1962,6 +1964,20 @@ let Messages$1 = class Messages extends APIResource {
|
|
|
1962
1964
|
function isChatCompletionFunctionTool(tool) {
|
|
1963
1965
|
return tool !== undefined && 'function' in tool && tool.function !== undefined;
|
|
1964
1966
|
}
|
|
1967
|
+
function makeParseableTextFormat(response_format, parser) {
|
|
1968
|
+
const obj = { ...response_format };
|
|
1969
|
+
Object.defineProperties(obj, {
|
|
1970
|
+
$brand: {
|
|
1971
|
+
value: 'auto-parseable-response-format',
|
|
1972
|
+
enumerable: false,
|
|
1973
|
+
},
|
|
1974
|
+
$parseRaw: {
|
|
1975
|
+
value: parser,
|
|
1976
|
+
enumerable: false,
|
|
1977
|
+
},
|
|
1978
|
+
});
|
|
1979
|
+
return obj;
|
|
1980
|
+
}
|
|
1965
1981
|
function isAutoParsableResponseFormat(response_format) {
|
|
1966
1982
|
return response_format?.['$brand'] === 'auto-parseable-response-format';
|
|
1967
1983
|
}
|
|
@@ -3974,7 +3990,7 @@ const readEnv = (env) => {
|
|
|
3974
3990
|
return undefined;
|
|
3975
3991
|
};
|
|
3976
3992
|
|
|
3977
|
-
var _AssistantStream_instances, _a$
|
|
3993
|
+
var _AssistantStream_instances, _a$2, _AssistantStream_events, _AssistantStream_runStepSnapshots, _AssistantStream_messageSnapshots, _AssistantStream_messageSnapshot, _AssistantStream_finalRun, _AssistantStream_currentContentIndex, _AssistantStream_currentContent, _AssistantStream_currentToolCallIndex, _AssistantStream_currentToolCall, _AssistantStream_currentEvent, _AssistantStream_currentRunSnapshot, _AssistantStream_currentRunStepSnapshot, _AssistantStream_addEvent, _AssistantStream_endRequest, _AssistantStream_handleMessage, _AssistantStream_handleRunStep, _AssistantStream_handleEvent, _AssistantStream_accumulateRunStep, _AssistantStream_accumulateMessage, _AssistantStream_accumulateContent, _AssistantStream_handleRun;
|
|
3978
3994
|
class AssistantStream extends EventStream {
|
|
3979
3995
|
constructor() {
|
|
3980
3996
|
super(...arguments);
|
|
@@ -4049,7 +4065,7 @@ class AssistantStream extends EventStream {
|
|
|
4049
4065
|
};
|
|
4050
4066
|
}
|
|
4051
4067
|
static fromReadableStream(stream) {
|
|
4052
|
-
const runner = new _a$
|
|
4068
|
+
const runner = new _a$2();
|
|
4053
4069
|
runner._run(() => runner._fromReadableStream(stream));
|
|
4054
4070
|
return runner;
|
|
4055
4071
|
}
|
|
@@ -4075,7 +4091,7 @@ class AssistantStream extends EventStream {
|
|
|
4075
4091
|
return stream.toReadableStream();
|
|
4076
4092
|
}
|
|
4077
4093
|
static createToolAssistantStream(runId, runs, params, options) {
|
|
4078
|
-
const runner = new _a$
|
|
4094
|
+
const runner = new _a$2();
|
|
4079
4095
|
runner._run(() => runner._runToolAssistantStream(runId, runs, params, {
|
|
4080
4096
|
...options,
|
|
4081
4097
|
headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' },
|
|
@@ -4104,7 +4120,7 @@ class AssistantStream extends EventStream {
|
|
|
4104
4120
|
return this._addRun(__classPrivateFieldGet(this, _AssistantStream_instances, "m", _AssistantStream_endRequest).call(this));
|
|
4105
4121
|
}
|
|
4106
4122
|
static createThreadAssistantStream(params, thread, options) {
|
|
4107
|
-
const runner = new _a$
|
|
4123
|
+
const runner = new _a$2();
|
|
4108
4124
|
runner._run(() => runner._threadAssistantStream(params, thread, {
|
|
4109
4125
|
...options,
|
|
4110
4126
|
headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' },
|
|
@@ -4112,7 +4128,7 @@ class AssistantStream extends EventStream {
|
|
|
4112
4128
|
return runner;
|
|
4113
4129
|
}
|
|
4114
4130
|
static createAssistantStream(threadId, runs, params, options) {
|
|
4115
|
-
const runner = new _a$
|
|
4131
|
+
const runner = new _a$2();
|
|
4116
4132
|
runner._run(() => runner._runAssistantStream(threadId, runs, params, {
|
|
4117
4133
|
...options,
|
|
4118
4134
|
headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' },
|
|
@@ -4254,7 +4270,7 @@ class AssistantStream extends EventStream {
|
|
|
4254
4270
|
return await this._createToolAssistantStream(runs, runId, params, options);
|
|
4255
4271
|
}
|
|
4256
4272
|
}
|
|
4257
|
-
_a$
|
|
4273
|
+
_a$2 = AssistantStream, _AssistantStream_addEvent = function _AssistantStream_addEvent(event) {
|
|
4258
4274
|
if (this.ended)
|
|
4259
4275
|
return;
|
|
4260
4276
|
__classPrivateFieldSet(this, _AssistantStream_currentEvent, event);
|
|
@@ -4432,7 +4448,7 @@ _a$1 = AssistantStream, _AssistantStream_addEvent = function _AssistantStream_ad
|
|
|
4432
4448
|
}
|
|
4433
4449
|
let data = event.data;
|
|
4434
4450
|
if (data.delta) {
|
|
4435
|
-
const accumulated = _a$
|
|
4451
|
+
const accumulated = _a$2.accumulateDelta(snapshot, data.delta);
|
|
4436
4452
|
__classPrivateFieldGet(this, _AssistantStream_runStepSnapshots, "f")[event.data.id] = accumulated;
|
|
4437
4453
|
}
|
|
4438
4454
|
return __classPrivateFieldGet(this, _AssistantStream_runStepSnapshots, "f")[event.data.id];
|
|
@@ -4486,7 +4502,7 @@ _a$1 = AssistantStream, _AssistantStream_addEvent = function _AssistantStream_ad
|
|
|
4486
4502
|
}
|
|
4487
4503
|
throw Error('Tried to accumulate a non-message event');
|
|
4488
4504
|
}, _AssistantStream_accumulateContent = function _AssistantStream_accumulateContent(contentElement, currentContent) {
|
|
4489
|
-
return _a$
|
|
4505
|
+
return _a$2.accumulateDelta(currentContent, contentElement);
|
|
4490
4506
|
}, _AssistantStream_handleRun = function _AssistantStream_handleRun(event) {
|
|
4491
4507
|
__classPrivateFieldSet(this, _AssistantStream_currentRunSnapshot, event.data);
|
|
4492
4508
|
switch (event.event) {
|
|
@@ -6904,7 +6920,7 @@ _Webhooks_instances = new WeakSet(), _Webhooks_validateSecret = function _Webhoo
|
|
|
6904
6920
|
};
|
|
6905
6921
|
|
|
6906
6922
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
6907
|
-
var _OpenAI_instances, _a, _OpenAI_encoder, _OpenAI_baseURLOverridden;
|
|
6923
|
+
var _OpenAI_instances, _a$1, _OpenAI_encoder, _OpenAI_baseURLOverridden;
|
|
6908
6924
|
/**
|
|
6909
6925
|
* API Client for interfacing with the OpenAI API.
|
|
6910
6926
|
*/
|
|
@@ -6995,7 +7011,7 @@ class OpenAI {
|
|
|
6995
7011
|
throw new OpenAIError("It looks like you're running in a browser-like environment.\n\nThis is disabled by default, as it risks exposing your secret API credentials to attackers.\nIf you understand the risks and have appropriate mitigations in place,\nyou can set the `dangerouslyAllowBrowser` option to `true`, e.g.,\n\nnew OpenAI({ apiKey, dangerouslyAllowBrowser: true });\n\nhttps://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety\n");
|
|
6996
7012
|
}
|
|
6997
7013
|
this.baseURL = options.baseURL;
|
|
6998
|
-
this.timeout = options.timeout ?? _a.DEFAULT_TIMEOUT /* 10 minutes */;
|
|
7014
|
+
this.timeout = options.timeout ?? _a$1.DEFAULT_TIMEOUT /* 10 minutes */;
|
|
6999
7015
|
this.logger = options.logger ?? console;
|
|
7000
7016
|
const defaultLogLevel = 'warn';
|
|
7001
7017
|
// Set default logLevel early so that we can log a warning in parseLogLevel.
|
|
@@ -7083,7 +7099,7 @@ class OpenAI {
|
|
|
7083
7099
|
new URL(path)
|
|
7084
7100
|
: new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path));
|
|
7085
7101
|
const defaultQuery = this.defaultQuery();
|
|
7086
|
-
if (!isEmptyObj(defaultQuery)) {
|
|
7102
|
+
if (!isEmptyObj$1(defaultQuery)) {
|
|
7087
7103
|
query = { ...defaultQuery, ...query };
|
|
7088
7104
|
}
|
|
7089
7105
|
if (typeof query === 'object' && query && !Array.isArray(query)) {
|
|
@@ -7425,10 +7441,10 @@ class OpenAI {
|
|
|
7425
7441
|
}
|
|
7426
7442
|
}
|
|
7427
7443
|
}
|
|
7428
|
-
_a = OpenAI, _OpenAI_encoder = new WeakMap(), _OpenAI_instances = new WeakSet(), _OpenAI_baseURLOverridden = function _OpenAI_baseURLOverridden() {
|
|
7444
|
+
_a$1 = OpenAI, _OpenAI_encoder = new WeakMap(), _OpenAI_instances = new WeakSet(), _OpenAI_baseURLOverridden = function _OpenAI_baseURLOverridden() {
|
|
7429
7445
|
return this.baseURL !== 'https://api.openai.com/v1';
|
|
7430
7446
|
};
|
|
7431
|
-
OpenAI.OpenAI = _a;
|
|
7447
|
+
OpenAI.OpenAI = _a$1;
|
|
7432
7448
|
OpenAI.DEFAULT_TIMEOUT = 600000; // 10 minutes
|
|
7433
7449
|
OpenAI.OpenAIError = OpenAIError;
|
|
7434
7450
|
OpenAI.APIError = APIError;
|
|
@@ -7468,240 +7484,3052 @@ OpenAI.Containers = Containers;
|
|
|
7468
7484
|
OpenAI.Skills = Skills;
|
|
7469
7485
|
OpenAI.Videos = Videos;
|
|
7470
7486
|
|
|
7471
|
-
//
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
const
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
},
|
|
7479
|
-
'gpt-4o-mini': {
|
|
7480
|
-
inputCost: 0.15 / 1_000_000,
|
|
7481
|
-
outputCost: 0.6 / 1_000_000,
|
|
7482
|
-
},
|
|
7483
|
-
'o1-mini': {
|
|
7484
|
-
inputCost: 1.1 / 1_000_000,
|
|
7485
|
-
outputCost: 4.4 / 1_000_000,
|
|
7486
|
-
},
|
|
7487
|
-
'o1': {
|
|
7488
|
-
inputCost: 15 / 1_000_000,
|
|
7489
|
-
outputCost: 60 / 1_000_000,
|
|
7490
|
-
},
|
|
7491
|
-
'o3-mini': {
|
|
7492
|
-
inputCost: 1.1 / 1_000_000,
|
|
7493
|
-
outputCost: 4.4 / 1_000_000,
|
|
7494
|
-
},
|
|
7495
|
-
'o3': {
|
|
7496
|
-
inputCost: 2 / 1_000_000,
|
|
7497
|
-
outputCost: 8 / 1_000_000,
|
|
7498
|
-
},
|
|
7499
|
-
'gpt-4.1': {
|
|
7500
|
-
inputCost: 2 / 1_000_000,
|
|
7501
|
-
outputCost: 8 / 1_000_000,
|
|
7502
|
-
},
|
|
7503
|
-
'gpt-4.1-mini': {
|
|
7504
|
-
inputCost: 0.4 / 1_000_000,
|
|
7505
|
-
outputCost: 1.6 / 1_000_000,
|
|
7506
|
-
},
|
|
7507
|
-
'gpt-4.1-nano': {
|
|
7508
|
-
inputCost: 0.1 / 1_000_000,
|
|
7509
|
-
outputCost: 0.4 / 1_000_000,
|
|
7510
|
-
},
|
|
7511
|
-
'gpt-5': {
|
|
7512
|
-
inputCost: 1.25 / 1_000_000,
|
|
7513
|
-
outputCost: 10 / 1_000_000,
|
|
7514
|
-
},
|
|
7515
|
-
'gpt-5-mini': {
|
|
7516
|
-
inputCost: 0.25 / 1_000_000,
|
|
7517
|
-
outputCost: 2 / 1_000_000,
|
|
7518
|
-
},
|
|
7519
|
-
'gpt-5-nano': {
|
|
7520
|
-
inputCost: 0.05 / 1_000_000,
|
|
7521
|
-
outputCost: 0.4 / 1_000_000,
|
|
7522
|
-
},
|
|
7523
|
-
'gpt-5.1': {
|
|
7524
|
-
inputCost: 1.25 / 1_000_000,
|
|
7525
|
-
outputCost: 10 / 1_000_000,
|
|
7526
|
-
},
|
|
7527
|
-
'gpt-5.2': {
|
|
7528
|
-
inputCost: 1.5 / 1_000_000,
|
|
7529
|
-
outputCost: 12 / 1_000_000,
|
|
7530
|
-
},
|
|
7531
|
-
'gpt-5.2-pro': {
|
|
7532
|
-
inputCost: 3 / 1_000_000,
|
|
7533
|
-
outputCost: 24 / 1_000_000,
|
|
7534
|
-
},
|
|
7535
|
-
'gpt-5.1-codex': {
|
|
7536
|
-
inputCost: 1.1 / 1_000_000,
|
|
7537
|
-
outputCost: 8.8 / 1_000_000,
|
|
7538
|
-
},
|
|
7539
|
-
'gpt-5.1-codex-max': {
|
|
7540
|
-
inputCost: 1.8 / 1_000_000,
|
|
7541
|
-
outputCost: 14.4 / 1_000_000,
|
|
7542
|
-
},
|
|
7543
|
-
'o4-mini': {
|
|
7544
|
-
inputCost: 1.1 / 1_000_000,
|
|
7545
|
-
outputCost: 4.4 / 1_000_000,
|
|
7546
|
-
},
|
|
7547
|
-
};
|
|
7548
|
-
const deepseekModelCosts = {
|
|
7549
|
-
'deepseek-chat': {
|
|
7550
|
-
inputCost: 0.27 / 1_000_000, // $0.27 per 1M tokens (Cache miss price)
|
|
7551
|
-
cacheHitCost: 0.07 / 1_000_000, // $0.07 per 1M tokens (Cache hit price)
|
|
7552
|
-
outputCost: 1.1 / 1_000_000, // $1.10 per 1M tokens
|
|
7553
|
-
},
|
|
7554
|
-
'deepseek-reasoner': {
|
|
7555
|
-
inputCost: 0.55 / 1_000_000, // $0.55 per 1M tokens (Cache miss price)
|
|
7556
|
-
cacheHitCost: 0.14 / 1_000_000, // $0.14 per 1M tokens (Cache hit price)
|
|
7557
|
-
outputCost: 2.19 / 1_000_000, // $2.19 per 1M tokens
|
|
7558
|
-
},
|
|
7559
|
-
};
|
|
7560
|
-
/** Image generation costs in USD per image. Based on OpenAI pricing as of Feb 2025. */
|
|
7561
|
-
const openAiImageCosts = {
|
|
7562
|
-
'gpt-image-1': 0.0075, // $0.0075 per image for gpt-image-1
|
|
7563
|
-
'gpt-image-1.5': 0.0075, // Assumes parity pricing with gpt-image-1 until OpenAI publishes updated rates
|
|
7564
|
-
};
|
|
7565
|
-
/**
|
|
7566
|
-
* Calculates the cost of generating images using OpenAI's Images API.
|
|
7567
|
-
*
|
|
7568
|
-
* @param model The image generation model name.
|
|
7569
|
-
* @param imageCount The number of images generated.
|
|
7570
|
-
* @returns The cost of generating the images in USD.
|
|
7571
|
-
*/
|
|
7572
|
-
function calculateImageCost(model, imageCount) {
|
|
7573
|
-
if (typeof model !== 'string' || typeof imageCount !== 'number' || imageCount <= 0) {
|
|
7574
|
-
return 0;
|
|
7575
|
-
}
|
|
7576
|
-
const costPerImage = openAiImageCosts[model];
|
|
7577
|
-
if (!costPerImage)
|
|
7578
|
-
return 0;
|
|
7579
|
-
return imageCount * costPerImage;
|
|
7487
|
+
// functions
|
|
7488
|
+
function getEnumValues(entries) {
|
|
7489
|
+
const numericValues = Object.values(entries).filter((v) => typeof v === "number");
|
|
7490
|
+
const values = Object.entries(entries)
|
|
7491
|
+
.filter(([k, _]) => numericValues.indexOf(+k) === -1)
|
|
7492
|
+
.map(([_, v]) => v);
|
|
7493
|
+
return values;
|
|
7580
7494
|
}
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
* @param outputTokens The number of output tokens generated by the language model.
|
|
7588
|
-
* @param reasoningTokens The number of output tokens generated by the language model for reasoning. This is only used for Deepseek models.
|
|
7589
|
-
* @param cacheHitTokens The number of input tokens that were cache hits for Deepseek models.
|
|
7590
|
-
* @returns The cost of calling the language model in USD.
|
|
7591
|
-
*/
|
|
7592
|
-
function calculateCost(provider, model, inputTokens, outputTokens, reasoningTokens, cacheHitTokens) {
|
|
7593
|
-
if (typeof provider !== 'string' ||
|
|
7594
|
-
typeof model !== 'string' ||
|
|
7595
|
-
typeof inputTokens !== 'number' ||
|
|
7596
|
-
typeof outputTokens !== 'number' ||
|
|
7597
|
-
(reasoningTokens !== undefined && typeof reasoningTokens !== 'number') ||
|
|
7598
|
-
(cacheHitTokens !== undefined && typeof cacheHitTokens !== 'number')) {
|
|
7599
|
-
return 0;
|
|
7495
|
+
|
|
7496
|
+
var _a;
|
|
7497
|
+
class $ZodRegistry {
|
|
7498
|
+
constructor() {
|
|
7499
|
+
this._map = new WeakMap();
|
|
7500
|
+
this._idmap = new Map();
|
|
7600
7501
|
}
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7502
|
+
add(schema, ..._meta) {
|
|
7503
|
+
const meta = _meta[0];
|
|
7504
|
+
this._map.set(schema, meta);
|
|
7505
|
+
if (meta && typeof meta === "object" && "id" in meta) {
|
|
7506
|
+
this._idmap.set(meta.id, schema);
|
|
7507
|
+
}
|
|
7508
|
+
return this;
|
|
7509
|
+
}
|
|
7510
|
+
clear() {
|
|
7511
|
+
this._map = new WeakMap();
|
|
7512
|
+
this._idmap = new Map();
|
|
7513
|
+
return this;
|
|
7514
|
+
}
|
|
7515
|
+
remove(schema) {
|
|
7516
|
+
const meta = this._map.get(schema);
|
|
7517
|
+
if (meta && typeof meta === "object" && "id" in meta) {
|
|
7518
|
+
this._idmap.delete(meta.id);
|
|
7519
|
+
}
|
|
7520
|
+
this._map.delete(schema);
|
|
7521
|
+
return this;
|
|
7522
|
+
}
|
|
7523
|
+
get(schema) {
|
|
7524
|
+
// return this._map.get(schema) as any;
|
|
7525
|
+
// inherit metadata
|
|
7526
|
+
const p = schema._zod.parent;
|
|
7527
|
+
if (p) {
|
|
7528
|
+
const pm = { ...(this.get(p) ?? {}) };
|
|
7529
|
+
delete pm.id; // do not inherit id
|
|
7530
|
+
const f = { ...pm, ...this._map.get(schema) };
|
|
7531
|
+
return Object.keys(f).length ? f : undefined;
|
|
7532
|
+
}
|
|
7533
|
+
return this._map.get(schema);
|
|
7534
|
+
}
|
|
7535
|
+
has(schema) {
|
|
7536
|
+
return this._map.has(schema);
|
|
7537
|
+
}
|
|
7538
|
+
}
|
|
7539
|
+
// registries
|
|
7540
|
+
function registry() {
|
|
7541
|
+
return new $ZodRegistry();
|
|
7611
7542
|
}
|
|
7543
|
+
(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
|
|
7544
|
+
const globalRegistry = globalThis.__zod_globalRegistry;
|
|
7612
7545
|
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
}
|
|
7634
|
-
|
|
7546
|
+
// function initializeContext<T extends schemas.$ZodType>(inputs: JSONSchemaGeneratorParams<T>): ToJSONSchemaContext<T> {
|
|
7547
|
+
// return {
|
|
7548
|
+
// processor: inputs.processor,
|
|
7549
|
+
// metadataRegistry: inputs.metadata ?? globalRegistry,
|
|
7550
|
+
// target: inputs.target ?? "draft-2020-12",
|
|
7551
|
+
// unrepresentable: inputs.unrepresentable ?? "throw",
|
|
7552
|
+
// };
|
|
7553
|
+
// }
|
|
7554
|
+
function initializeContext(params) {
|
|
7555
|
+
// Normalize target: convert old non-hyphenated versions to hyphenated versions
|
|
7556
|
+
let target = params?.target ?? "draft-2020-12";
|
|
7557
|
+
if (target === "draft-4")
|
|
7558
|
+
target = "draft-04";
|
|
7559
|
+
if (target === "draft-7")
|
|
7560
|
+
target = "draft-07";
|
|
7561
|
+
return {
|
|
7562
|
+
processors: params.processors ?? {},
|
|
7563
|
+
metadataRegistry: params?.metadata ?? globalRegistry,
|
|
7564
|
+
target,
|
|
7565
|
+
unrepresentable: params?.unrepresentable ?? "throw",
|
|
7566
|
+
override: params?.override ?? (() => { }),
|
|
7567
|
+
io: params?.io ?? "output",
|
|
7568
|
+
counter: 0,
|
|
7569
|
+
seen: new Map(),
|
|
7570
|
+
cycles: params?.cycles ?? "ref",
|
|
7571
|
+
reused: params?.reused ?? "inline",
|
|
7572
|
+
external: params?.external ?? undefined,
|
|
7573
|
+
};
|
|
7574
|
+
}
|
|
7575
|
+
function process$1(schema, ctx, _params = { path: [], schemaPath: [] }) {
|
|
7576
|
+
var _a;
|
|
7577
|
+
const def = schema._zod.def;
|
|
7578
|
+
// check for schema in seens
|
|
7579
|
+
const seen = ctx.seen.get(schema);
|
|
7580
|
+
if (seen) {
|
|
7581
|
+
seen.count++;
|
|
7582
|
+
// check if cycle
|
|
7583
|
+
const isCycle = _params.schemaPath.includes(schema);
|
|
7584
|
+
if (isCycle) {
|
|
7585
|
+
seen.cycle = _params.path;
|
|
7586
|
+
}
|
|
7587
|
+
return seen.schema;
|
|
7588
|
+
}
|
|
7589
|
+
// initialize
|
|
7590
|
+
const result = { schema: {}, count: 1, cycle: undefined, path: _params.path };
|
|
7591
|
+
ctx.seen.set(schema, result);
|
|
7592
|
+
// custom method overrides default behavior
|
|
7593
|
+
const overrideSchema = schema._zod.toJSONSchema?.();
|
|
7594
|
+
if (overrideSchema) {
|
|
7595
|
+
result.schema = overrideSchema;
|
|
7635
7596
|
}
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
return parseArray();
|
|
7645
|
-
if (char === '"' || char === "'")
|
|
7646
|
-
return parseString();
|
|
7647
|
-
if (char === 't' && jsonStr.slice(index, index + 4).toLowerCase() === 'true') {
|
|
7648
|
-
index += 4;
|
|
7649
|
-
return true;
|
|
7597
|
+
else {
|
|
7598
|
+
const params = {
|
|
7599
|
+
..._params,
|
|
7600
|
+
schemaPath: [..._params.schemaPath, schema],
|
|
7601
|
+
path: _params.path,
|
|
7602
|
+
};
|
|
7603
|
+
if (schema._zod.processJSONSchema) {
|
|
7604
|
+
schema._zod.processJSONSchema(ctx, result.schema, params);
|
|
7650
7605
|
}
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7606
|
+
else {
|
|
7607
|
+
const _json = result.schema;
|
|
7608
|
+
const processor = ctx.processors[def.type];
|
|
7609
|
+
if (!processor) {
|
|
7610
|
+
throw new Error(`[toJSONSchema]: Non-representable type encountered: ${def.type}`);
|
|
7611
|
+
}
|
|
7612
|
+
processor(schema, ctx, _json, params);
|
|
7613
|
+
}
|
|
7614
|
+
const parent = schema._zod.parent;
|
|
7615
|
+
if (parent) {
|
|
7616
|
+
// Also set ref if processor didn't (for inheritance)
|
|
7617
|
+
if (!result.ref)
|
|
7618
|
+
result.ref = parent;
|
|
7619
|
+
process$1(parent, ctx, params);
|
|
7620
|
+
ctx.seen.get(parent).isParent = true;
|
|
7621
|
+
}
|
|
7622
|
+
}
|
|
7623
|
+
// metadata
|
|
7624
|
+
const meta = ctx.metadataRegistry.get(schema);
|
|
7625
|
+
if (meta)
|
|
7626
|
+
Object.assign(result.schema, meta);
|
|
7627
|
+
if (ctx.io === "input" && isTransforming(schema)) {
|
|
7628
|
+
// examples/defaults only apply to output type of pipe
|
|
7629
|
+
delete result.schema.examples;
|
|
7630
|
+
delete result.schema.default;
|
|
7631
|
+
}
|
|
7632
|
+
// set prefault as default
|
|
7633
|
+
if (ctx.io === "input" && result.schema._prefault)
|
|
7634
|
+
(_a = result.schema).default ?? (_a.default = result.schema._prefault);
|
|
7635
|
+
delete result.schema._prefault;
|
|
7636
|
+
// pulling fresh from ctx.seen in case it was overwritten
|
|
7637
|
+
const _result = ctx.seen.get(schema);
|
|
7638
|
+
return _result.schema;
|
|
7639
|
+
}
|
|
7640
|
+
function extractDefs(ctx, schema
|
|
7641
|
+
// params: EmitParams
|
|
7642
|
+
) {
|
|
7643
|
+
// iterate over seen map;
|
|
7644
|
+
const root = ctx.seen.get(schema);
|
|
7645
|
+
if (!root)
|
|
7646
|
+
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
7647
|
+
// Track ids to detect duplicates across different schemas
|
|
7648
|
+
const idToSchema = new Map();
|
|
7649
|
+
for (const entry of ctx.seen.entries()) {
|
|
7650
|
+
const id = ctx.metadataRegistry.get(entry[0])?.id;
|
|
7651
|
+
if (id) {
|
|
7652
|
+
const existing = idToSchema.get(id);
|
|
7653
|
+
if (existing && existing !== entry[0]) {
|
|
7654
|
+
throw new Error(`Duplicate schema id "${id}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);
|
|
7655
|
+
}
|
|
7656
|
+
idToSchema.set(id, entry[0]);
|
|
7657
|
+
}
|
|
7658
|
+
}
|
|
7659
|
+
// returns a ref to the schema
|
|
7660
|
+
// defId will be empty if the ref points to an external schema (or #)
|
|
7661
|
+
const makeURI = (entry) => {
|
|
7662
|
+
// comparing the seen objects because sometimes
|
|
7663
|
+
// multiple schemas map to the same seen object.
|
|
7664
|
+
// e.g. lazy
|
|
7665
|
+
// external is configured
|
|
7666
|
+
const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
|
|
7667
|
+
if (ctx.external) {
|
|
7668
|
+
const externalId = ctx.external.registry.get(entry[0])?.id; // ?? "__shared";// `__schema${ctx.counter++}`;
|
|
7669
|
+
// check if schema is in the external registry
|
|
7670
|
+
const uriGenerator = ctx.external.uri ?? ((id) => id);
|
|
7671
|
+
if (externalId) {
|
|
7672
|
+
return { ref: uriGenerator(externalId) };
|
|
7673
|
+
}
|
|
7674
|
+
// otherwise, add to __shared
|
|
7675
|
+
const id = entry[1].defId ?? entry[1].schema.id ?? `schema${ctx.counter++}`;
|
|
7676
|
+
entry[1].defId = id; // set defId so it will be reused if needed
|
|
7677
|
+
return { defId: id, ref: `${uriGenerator("__shared")}#/${defsSegment}/${id}` };
|
|
7678
|
+
}
|
|
7679
|
+
if (entry[1] === root) {
|
|
7680
|
+
return { ref: "#" };
|
|
7681
|
+
}
|
|
7682
|
+
// self-contained schema
|
|
7683
|
+
const uriPrefix = `#`;
|
|
7684
|
+
const defUriPrefix = `${uriPrefix}/${defsSegment}/`;
|
|
7685
|
+
const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;
|
|
7686
|
+
return { defId, ref: defUriPrefix + defId };
|
|
7687
|
+
};
|
|
7688
|
+
// stored cached version in `def` property
|
|
7689
|
+
// remove all properties, set $ref
|
|
7690
|
+
const extractToDef = (entry) => {
|
|
7691
|
+
// if the schema is already a reference, do not extract it
|
|
7692
|
+
if (entry[1].schema.$ref) {
|
|
7693
|
+
return;
|
|
7654
7694
|
}
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7695
|
+
const seen = entry[1];
|
|
7696
|
+
const { ref, defId } = makeURI(entry);
|
|
7697
|
+
seen.def = { ...seen.schema };
|
|
7698
|
+
// defId won't be set if the schema is a reference to an external schema
|
|
7699
|
+
// or if the schema is the root schema
|
|
7700
|
+
if (defId)
|
|
7701
|
+
seen.defId = defId;
|
|
7702
|
+
// wipe away all properties except $ref
|
|
7703
|
+
const schema = seen.schema;
|
|
7704
|
+
for (const key in schema) {
|
|
7705
|
+
delete schema[key];
|
|
7706
|
+
}
|
|
7707
|
+
schema.$ref = ref;
|
|
7708
|
+
};
|
|
7709
|
+
// throw on cycles
|
|
7710
|
+
// break cycles
|
|
7711
|
+
if (ctx.cycles === "throw") {
|
|
7712
|
+
for (const entry of ctx.seen.entries()) {
|
|
7713
|
+
const seen = entry[1];
|
|
7714
|
+
if (seen.cycle) {
|
|
7715
|
+
throw new Error("Cycle detected: " +
|
|
7716
|
+
`#/${seen.cycle?.join("/")}/<root>` +
|
|
7717
|
+
'\n\nSet the `cycles` parameter to `"ref"` to resolve cyclical schemas with defs.');
|
|
7718
|
+
}
|
|
7719
|
+
}
|
|
7720
|
+
}
|
|
7721
|
+
// extract schemas into $defs
|
|
7722
|
+
for (const entry of ctx.seen.entries()) {
|
|
7723
|
+
const seen = entry[1];
|
|
7724
|
+
// convert root schema to # $ref
|
|
7725
|
+
if (schema === entry[0]) {
|
|
7726
|
+
extractToDef(entry); // this has special handling for the root schema
|
|
7727
|
+
continue;
|
|
7658
7728
|
}
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
}
|
|
7665
|
-
function parseObject() {
|
|
7666
|
-
const obj = {};
|
|
7667
|
-
index++; // Skip opening brace
|
|
7668
|
-
skipWhitespace();
|
|
7669
|
-
while (index < jsonStr.length && getChar() !== '}') {
|
|
7670
|
-
skipWhitespace();
|
|
7671
|
-
const key = parseString();
|
|
7672
|
-
if (key === undefined) {
|
|
7673
|
-
console.warn(`Expected key at position ${index}`);
|
|
7674
|
-
index++;
|
|
7729
|
+
// extract schemas that are in the external registry
|
|
7730
|
+
if (ctx.external) {
|
|
7731
|
+
const ext = ctx.external.registry.get(entry[0])?.id;
|
|
7732
|
+
if (schema !== entry[0] && ext) {
|
|
7733
|
+
extractToDef(entry);
|
|
7675
7734
|
continue;
|
|
7676
7735
|
}
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7736
|
+
}
|
|
7737
|
+
// extract schemas with `id` meta
|
|
7738
|
+
const id = ctx.metadataRegistry.get(entry[0])?.id;
|
|
7739
|
+
if (id) {
|
|
7740
|
+
extractToDef(entry);
|
|
7741
|
+
continue;
|
|
7742
|
+
}
|
|
7743
|
+
// break cycles
|
|
7744
|
+
if (seen.cycle) {
|
|
7745
|
+
// any
|
|
7746
|
+
extractToDef(entry);
|
|
7747
|
+
continue;
|
|
7748
|
+
}
|
|
7749
|
+
// extract reused schemas
|
|
7750
|
+
if (seen.count > 1) {
|
|
7751
|
+
if (ctx.reused === "ref") {
|
|
7752
|
+
extractToDef(entry);
|
|
7753
|
+
// biome-ignore lint:
|
|
7754
|
+
continue;
|
|
7755
|
+
}
|
|
7756
|
+
}
|
|
7757
|
+
}
|
|
7758
|
+
}
|
|
7759
|
+
function finalize(ctx, schema) {
|
|
7760
|
+
const root = ctx.seen.get(schema);
|
|
7761
|
+
if (!root)
|
|
7762
|
+
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
7763
|
+
// flatten refs - inherit properties from parent schemas
|
|
7764
|
+
const flattenRef = (zodSchema) => {
|
|
7765
|
+
const seen = ctx.seen.get(zodSchema);
|
|
7766
|
+
// already processed
|
|
7767
|
+
if (seen.ref === null)
|
|
7768
|
+
return;
|
|
7769
|
+
const schema = seen.def ?? seen.schema;
|
|
7770
|
+
const _cached = { ...schema };
|
|
7771
|
+
const ref = seen.ref;
|
|
7772
|
+
seen.ref = null; // prevent infinite recursion
|
|
7773
|
+
if (ref) {
|
|
7774
|
+
flattenRef(ref);
|
|
7775
|
+
const refSeen = ctx.seen.get(ref);
|
|
7776
|
+
const refSchema = refSeen.schema;
|
|
7777
|
+
// merge referenced schema into current
|
|
7778
|
+
if (refSchema.$ref && (ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0")) {
|
|
7779
|
+
// older drafts can't combine $ref with other properties
|
|
7780
|
+
schema.allOf = schema.allOf ?? [];
|
|
7781
|
+
schema.allOf.push(refSchema);
|
|
7680
7782
|
}
|
|
7681
7783
|
else {
|
|
7682
|
-
|
|
7784
|
+
Object.assign(schema, refSchema);
|
|
7785
|
+
}
|
|
7786
|
+
// restore child's own properties (child wins)
|
|
7787
|
+
Object.assign(schema, _cached);
|
|
7788
|
+
const isParentRef = zodSchema._zod.parent === ref;
|
|
7789
|
+
// For parent chain, child is a refinement - remove parent-only properties
|
|
7790
|
+
if (isParentRef) {
|
|
7791
|
+
for (const key in schema) {
|
|
7792
|
+
if (key === "$ref" || key === "allOf")
|
|
7793
|
+
continue;
|
|
7794
|
+
if (!(key in _cached)) {
|
|
7795
|
+
delete schema[key];
|
|
7796
|
+
}
|
|
7797
|
+
}
|
|
7683
7798
|
}
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7799
|
+
// When ref was extracted to $defs, remove properties that match the definition
|
|
7800
|
+
if (refSchema.$ref && refSeen.def) {
|
|
7801
|
+
for (const key in schema) {
|
|
7802
|
+
if (key === "$ref" || key === "allOf")
|
|
7803
|
+
continue;
|
|
7804
|
+
if (key in refSeen.def && JSON.stringify(schema[key]) === JSON.stringify(refSeen.def[key])) {
|
|
7805
|
+
delete schema[key];
|
|
7806
|
+
}
|
|
7807
|
+
}
|
|
7690
7808
|
}
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7809
|
+
}
|
|
7810
|
+
// If parent was extracted (has $ref), propagate $ref to this schema
|
|
7811
|
+
// This handles cases like: readonly().meta({id}).describe()
|
|
7812
|
+
// where processor sets ref to innerType but parent should be referenced
|
|
7813
|
+
const parent = zodSchema._zod.parent;
|
|
7814
|
+
if (parent && parent !== ref) {
|
|
7815
|
+
// Ensure parent is processed first so its def has inherited properties
|
|
7816
|
+
flattenRef(parent);
|
|
7817
|
+
const parentSeen = ctx.seen.get(parent);
|
|
7818
|
+
if (parentSeen?.schema.$ref) {
|
|
7819
|
+
schema.$ref = parentSeen.schema.$ref;
|
|
7820
|
+
// De-duplicate with parent's definition
|
|
7821
|
+
if (parentSeen.def) {
|
|
7822
|
+
for (const key in schema) {
|
|
7823
|
+
if (key === "$ref" || key === "allOf")
|
|
7824
|
+
continue;
|
|
7825
|
+
if (key in parentSeen.def && JSON.stringify(schema[key]) === JSON.stringify(parentSeen.def[key])) {
|
|
7826
|
+
delete schema[key];
|
|
7827
|
+
}
|
|
7828
|
+
}
|
|
7829
|
+
}
|
|
7830
|
+
}
|
|
7831
|
+
}
|
|
7832
|
+
// execute overrides
|
|
7833
|
+
ctx.override({
|
|
7834
|
+
zodSchema: zodSchema,
|
|
7835
|
+
jsonSchema: schema,
|
|
7836
|
+
path: seen.path ?? [],
|
|
7837
|
+
});
|
|
7838
|
+
};
|
|
7839
|
+
for (const entry of [...ctx.seen.entries()].reverse()) {
|
|
7840
|
+
flattenRef(entry[0]);
|
|
7841
|
+
}
|
|
7842
|
+
const result = {};
|
|
7843
|
+
if (ctx.target === "draft-2020-12") {
|
|
7844
|
+
result.$schema = "https://json-schema.org/draft/2020-12/schema";
|
|
7845
|
+
}
|
|
7846
|
+
else if (ctx.target === "draft-07") {
|
|
7847
|
+
result.$schema = "http://json-schema.org/draft-07/schema#";
|
|
7848
|
+
}
|
|
7849
|
+
else if (ctx.target === "draft-04") {
|
|
7850
|
+
result.$schema = "http://json-schema.org/draft-04/schema#";
|
|
7851
|
+
}
|
|
7852
|
+
else if (ctx.target === "openapi-3.0") ;
|
|
7853
|
+
else ;
|
|
7854
|
+
if (ctx.external?.uri) {
|
|
7855
|
+
const id = ctx.external.registry.get(schema)?.id;
|
|
7856
|
+
if (!id)
|
|
7857
|
+
throw new Error("Schema is missing an `id` property");
|
|
7858
|
+
result.$id = ctx.external.uri(id);
|
|
7859
|
+
}
|
|
7860
|
+
Object.assign(result, root.def ?? root.schema);
|
|
7861
|
+
// build defs object
|
|
7862
|
+
const defs = ctx.external?.defs ?? {};
|
|
7863
|
+
for (const entry of ctx.seen.entries()) {
|
|
7864
|
+
const seen = entry[1];
|
|
7865
|
+
if (seen.def && seen.defId) {
|
|
7866
|
+
defs[seen.defId] = seen.def;
|
|
7867
|
+
}
|
|
7868
|
+
}
|
|
7869
|
+
// set definitions in result
|
|
7870
|
+
if (ctx.external) ;
|
|
7871
|
+
else {
|
|
7872
|
+
if (Object.keys(defs).length > 0) {
|
|
7873
|
+
if (ctx.target === "draft-2020-12") {
|
|
7874
|
+
result.$defs = defs;
|
|
7695
7875
|
}
|
|
7696
7876
|
else {
|
|
7697
|
-
|
|
7877
|
+
result.definitions = defs;
|
|
7698
7878
|
}
|
|
7699
|
-
skipWhitespace();
|
|
7700
7879
|
}
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7880
|
+
}
|
|
7881
|
+
try {
|
|
7882
|
+
// this "finalizes" this schema and ensures all cycles are removed
|
|
7883
|
+
// each call to finalize() is functionally independent
|
|
7884
|
+
// though the seen map is shared
|
|
7885
|
+
const finalized = JSON.parse(JSON.stringify(result));
|
|
7886
|
+
Object.defineProperty(finalized, "~standard", {
|
|
7887
|
+
value: {
|
|
7888
|
+
...schema["~standard"],
|
|
7889
|
+
jsonSchema: {
|
|
7890
|
+
input: createStandardJSONSchemaMethod(schema, "input", ctx.processors),
|
|
7891
|
+
output: createStandardJSONSchemaMethod(schema, "output", ctx.processors),
|
|
7892
|
+
},
|
|
7893
|
+
},
|
|
7894
|
+
enumerable: false,
|
|
7895
|
+
writable: false,
|
|
7896
|
+
});
|
|
7897
|
+
return finalized;
|
|
7898
|
+
}
|
|
7899
|
+
catch (_err) {
|
|
7900
|
+
throw new Error("Error converting schema to JSON.");
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
function isTransforming(_schema, _ctx) {
|
|
7904
|
+
const ctx = _ctx ?? { seen: new Set() };
|
|
7905
|
+
if (ctx.seen.has(_schema))
|
|
7906
|
+
return false;
|
|
7907
|
+
ctx.seen.add(_schema);
|
|
7908
|
+
const def = _schema._zod.def;
|
|
7909
|
+
if (def.type === "transform")
|
|
7910
|
+
return true;
|
|
7911
|
+
if (def.type === "array")
|
|
7912
|
+
return isTransforming(def.element, ctx);
|
|
7913
|
+
if (def.type === "set")
|
|
7914
|
+
return isTransforming(def.valueType, ctx);
|
|
7915
|
+
if (def.type === "lazy")
|
|
7916
|
+
return isTransforming(def.getter(), ctx);
|
|
7917
|
+
if (def.type === "promise" ||
|
|
7918
|
+
def.type === "optional" ||
|
|
7919
|
+
def.type === "nonoptional" ||
|
|
7920
|
+
def.type === "nullable" ||
|
|
7921
|
+
def.type === "readonly" ||
|
|
7922
|
+
def.type === "default" ||
|
|
7923
|
+
def.type === "prefault") {
|
|
7924
|
+
return isTransforming(def.innerType, ctx);
|
|
7925
|
+
}
|
|
7926
|
+
if (def.type === "intersection") {
|
|
7927
|
+
return isTransforming(def.left, ctx) || isTransforming(def.right, ctx);
|
|
7928
|
+
}
|
|
7929
|
+
if (def.type === "record" || def.type === "map") {
|
|
7930
|
+
return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx);
|
|
7931
|
+
}
|
|
7932
|
+
if (def.type === "pipe") {
|
|
7933
|
+
return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);
|
|
7934
|
+
}
|
|
7935
|
+
if (def.type === "object") {
|
|
7936
|
+
for (const key in def.shape) {
|
|
7937
|
+
if (isTransforming(def.shape[key], ctx))
|
|
7938
|
+
return true;
|
|
7939
|
+
}
|
|
7940
|
+
return false;
|
|
7941
|
+
}
|
|
7942
|
+
if (def.type === "union") {
|
|
7943
|
+
for (const option of def.options) {
|
|
7944
|
+
if (isTransforming(option, ctx))
|
|
7945
|
+
return true;
|
|
7946
|
+
}
|
|
7947
|
+
return false;
|
|
7948
|
+
}
|
|
7949
|
+
if (def.type === "tuple") {
|
|
7950
|
+
for (const item of def.items) {
|
|
7951
|
+
if (isTransforming(item, ctx))
|
|
7952
|
+
return true;
|
|
7953
|
+
}
|
|
7954
|
+
if (def.rest && isTransforming(def.rest, ctx))
|
|
7955
|
+
return true;
|
|
7956
|
+
return false;
|
|
7957
|
+
}
|
|
7958
|
+
return false;
|
|
7959
|
+
}
|
|
7960
|
+
const createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
|
|
7961
|
+
const { libraryOptions, target } = params ?? {};
|
|
7962
|
+
const ctx = initializeContext({ ...(libraryOptions ?? {}), target, io, processors });
|
|
7963
|
+
process$1(schema, ctx);
|
|
7964
|
+
extractDefs(ctx, schema);
|
|
7965
|
+
return finalize(ctx, schema);
|
|
7966
|
+
};
|
|
7967
|
+
|
|
7968
|
+
const formatMap = {
|
|
7969
|
+
guid: "uuid",
|
|
7970
|
+
url: "uri",
|
|
7971
|
+
datetime: "date-time",
|
|
7972
|
+
json_string: "json-string",
|
|
7973
|
+
regex: "", // do not set
|
|
7974
|
+
};
|
|
7975
|
+
// ==================== SIMPLE TYPE PROCESSORS ====================
|
|
7976
|
+
const stringProcessor = (schema, ctx, _json, _params) => {
|
|
7977
|
+
const json = _json;
|
|
7978
|
+
json.type = "string";
|
|
7979
|
+
const { minimum, maximum, format, patterns, contentEncoding } = schema._zod
|
|
7980
|
+
.bag;
|
|
7981
|
+
if (typeof minimum === "number")
|
|
7982
|
+
json.minLength = minimum;
|
|
7983
|
+
if (typeof maximum === "number")
|
|
7984
|
+
json.maxLength = maximum;
|
|
7985
|
+
// custom pattern overrides format
|
|
7986
|
+
if (format) {
|
|
7987
|
+
json.format = formatMap[format] ?? format;
|
|
7988
|
+
if (json.format === "")
|
|
7989
|
+
delete json.format; // empty format is not valid
|
|
7990
|
+
// JSON Schema format: "time" requires a full time with offset or Z
|
|
7991
|
+
// z.iso.time() does not include timezone information, so format: "time" should never be used
|
|
7992
|
+
if (format === "time") {
|
|
7993
|
+
delete json.format;
|
|
7994
|
+
}
|
|
7995
|
+
}
|
|
7996
|
+
if (contentEncoding)
|
|
7997
|
+
json.contentEncoding = contentEncoding;
|
|
7998
|
+
if (patterns && patterns.size > 0) {
|
|
7999
|
+
const regexes = [...patterns];
|
|
8000
|
+
if (regexes.length === 1)
|
|
8001
|
+
json.pattern = regexes[0].source;
|
|
8002
|
+
else if (regexes.length > 1) {
|
|
8003
|
+
json.allOf = [
|
|
8004
|
+
...regexes.map((regex) => ({
|
|
8005
|
+
...(ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0"
|
|
8006
|
+
? { type: "string" }
|
|
8007
|
+
: {}),
|
|
8008
|
+
pattern: regex.source,
|
|
8009
|
+
})),
|
|
8010
|
+
];
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
};
|
|
8014
|
+
const numberProcessor = (schema, ctx, _json, _params) => {
|
|
8015
|
+
const json = _json;
|
|
8016
|
+
const { minimum, maximum, format, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema._zod.bag;
|
|
8017
|
+
if (typeof format === "string" && format.includes("int"))
|
|
8018
|
+
json.type = "integer";
|
|
8019
|
+
else
|
|
8020
|
+
json.type = "number";
|
|
8021
|
+
if (typeof exclusiveMinimum === "number") {
|
|
8022
|
+
if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
|
|
8023
|
+
json.minimum = exclusiveMinimum;
|
|
8024
|
+
json.exclusiveMinimum = true;
|
|
8025
|
+
}
|
|
8026
|
+
else {
|
|
8027
|
+
json.exclusiveMinimum = exclusiveMinimum;
|
|
8028
|
+
}
|
|
8029
|
+
}
|
|
8030
|
+
if (typeof minimum === "number") {
|
|
8031
|
+
json.minimum = minimum;
|
|
8032
|
+
if (typeof exclusiveMinimum === "number" && ctx.target !== "draft-04") {
|
|
8033
|
+
if (exclusiveMinimum >= minimum)
|
|
8034
|
+
delete json.minimum;
|
|
8035
|
+
else
|
|
8036
|
+
delete json.exclusiveMinimum;
|
|
8037
|
+
}
|
|
8038
|
+
}
|
|
8039
|
+
if (typeof exclusiveMaximum === "number") {
|
|
8040
|
+
if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
|
|
8041
|
+
json.maximum = exclusiveMaximum;
|
|
8042
|
+
json.exclusiveMaximum = true;
|
|
8043
|
+
}
|
|
8044
|
+
else {
|
|
8045
|
+
json.exclusiveMaximum = exclusiveMaximum;
|
|
8046
|
+
}
|
|
8047
|
+
}
|
|
8048
|
+
if (typeof maximum === "number") {
|
|
8049
|
+
json.maximum = maximum;
|
|
8050
|
+
if (typeof exclusiveMaximum === "number" && ctx.target !== "draft-04") {
|
|
8051
|
+
if (exclusiveMaximum <= maximum)
|
|
8052
|
+
delete json.maximum;
|
|
8053
|
+
else
|
|
8054
|
+
delete json.exclusiveMaximum;
|
|
8055
|
+
}
|
|
8056
|
+
}
|
|
8057
|
+
if (typeof multipleOf === "number")
|
|
8058
|
+
json.multipleOf = multipleOf;
|
|
8059
|
+
};
|
|
8060
|
+
const booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
8061
|
+
json.type = "boolean";
|
|
8062
|
+
};
|
|
8063
|
+
const bigintProcessor = (_schema, ctx, _json, _params) => {
|
|
8064
|
+
if (ctx.unrepresentable === "throw") {
|
|
8065
|
+
throw new Error("BigInt cannot be represented in JSON Schema");
|
|
8066
|
+
}
|
|
8067
|
+
};
|
|
8068
|
+
const symbolProcessor = (_schema, ctx, _json, _params) => {
|
|
8069
|
+
if (ctx.unrepresentable === "throw") {
|
|
8070
|
+
throw new Error("Symbols cannot be represented in JSON Schema");
|
|
8071
|
+
}
|
|
8072
|
+
};
|
|
8073
|
+
const nullProcessor = (_schema, ctx, json, _params) => {
|
|
8074
|
+
if (ctx.target === "openapi-3.0") {
|
|
8075
|
+
json.type = "string";
|
|
8076
|
+
json.nullable = true;
|
|
8077
|
+
json.enum = [null];
|
|
8078
|
+
}
|
|
8079
|
+
else {
|
|
8080
|
+
json.type = "null";
|
|
8081
|
+
}
|
|
8082
|
+
};
|
|
8083
|
+
const undefinedProcessor = (_schema, ctx, _json, _params) => {
|
|
8084
|
+
if (ctx.unrepresentable === "throw") {
|
|
8085
|
+
throw new Error("Undefined cannot be represented in JSON Schema");
|
|
8086
|
+
}
|
|
8087
|
+
};
|
|
8088
|
+
const voidProcessor = (_schema, ctx, _json, _params) => {
|
|
8089
|
+
if (ctx.unrepresentable === "throw") {
|
|
8090
|
+
throw new Error("Void cannot be represented in JSON Schema");
|
|
8091
|
+
}
|
|
8092
|
+
};
|
|
8093
|
+
const neverProcessor = (_schema, _ctx, json, _params) => {
|
|
8094
|
+
json.not = {};
|
|
8095
|
+
};
|
|
8096
|
+
const anyProcessor = (_schema, _ctx, _json, _params) => {
|
|
8097
|
+
// empty schema accepts anything
|
|
8098
|
+
};
|
|
8099
|
+
const unknownProcessor = (_schema, _ctx, _json, _params) => {
|
|
8100
|
+
// empty schema accepts anything
|
|
8101
|
+
};
|
|
8102
|
+
const dateProcessor = (_schema, ctx, _json, _params) => {
|
|
8103
|
+
if (ctx.unrepresentable === "throw") {
|
|
8104
|
+
throw new Error("Date cannot be represented in JSON Schema");
|
|
8105
|
+
}
|
|
8106
|
+
};
|
|
8107
|
+
const enumProcessor = (schema, _ctx, json, _params) => {
|
|
8108
|
+
const def = schema._zod.def;
|
|
8109
|
+
const values = getEnumValues(def.entries);
|
|
8110
|
+
// Number enums can have both string and number values
|
|
8111
|
+
if (values.every((v) => typeof v === "number"))
|
|
8112
|
+
json.type = "number";
|
|
8113
|
+
if (values.every((v) => typeof v === "string"))
|
|
8114
|
+
json.type = "string";
|
|
8115
|
+
json.enum = values;
|
|
8116
|
+
};
|
|
8117
|
+
const literalProcessor = (schema, ctx, json, _params) => {
|
|
8118
|
+
const def = schema._zod.def;
|
|
8119
|
+
const vals = [];
|
|
8120
|
+
for (const val of def.values) {
|
|
8121
|
+
if (val === undefined) {
|
|
8122
|
+
if (ctx.unrepresentable === "throw") {
|
|
8123
|
+
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
8124
|
+
}
|
|
8125
|
+
}
|
|
8126
|
+
else if (typeof val === "bigint") {
|
|
8127
|
+
if (ctx.unrepresentable === "throw") {
|
|
8128
|
+
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
8129
|
+
}
|
|
8130
|
+
else {
|
|
8131
|
+
vals.push(Number(val));
|
|
8132
|
+
}
|
|
8133
|
+
}
|
|
8134
|
+
else {
|
|
8135
|
+
vals.push(val);
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
8138
|
+
if (vals.length === 0) ;
|
|
8139
|
+
else if (vals.length === 1) {
|
|
8140
|
+
const val = vals[0];
|
|
8141
|
+
json.type = val === null ? "null" : typeof val;
|
|
8142
|
+
if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
|
|
8143
|
+
json.enum = [val];
|
|
8144
|
+
}
|
|
8145
|
+
else {
|
|
8146
|
+
json.const = val;
|
|
8147
|
+
}
|
|
8148
|
+
}
|
|
8149
|
+
else {
|
|
8150
|
+
if (vals.every((v) => typeof v === "number"))
|
|
8151
|
+
json.type = "number";
|
|
8152
|
+
if (vals.every((v) => typeof v === "string"))
|
|
8153
|
+
json.type = "string";
|
|
8154
|
+
if (vals.every((v) => typeof v === "boolean"))
|
|
8155
|
+
json.type = "boolean";
|
|
8156
|
+
if (vals.every((v) => v === null))
|
|
8157
|
+
json.type = "null";
|
|
8158
|
+
json.enum = vals;
|
|
8159
|
+
}
|
|
8160
|
+
};
|
|
8161
|
+
const nanProcessor = (_schema, ctx, _json, _params) => {
|
|
8162
|
+
if (ctx.unrepresentable === "throw") {
|
|
8163
|
+
throw new Error("NaN cannot be represented in JSON Schema");
|
|
8164
|
+
}
|
|
8165
|
+
};
|
|
8166
|
+
const templateLiteralProcessor = (schema, _ctx, json, _params) => {
|
|
8167
|
+
const _json = json;
|
|
8168
|
+
const pattern = schema._zod.pattern;
|
|
8169
|
+
if (!pattern)
|
|
8170
|
+
throw new Error("Pattern not found in template literal");
|
|
8171
|
+
_json.type = "string";
|
|
8172
|
+
_json.pattern = pattern.source;
|
|
8173
|
+
};
|
|
8174
|
+
const fileProcessor = (schema, _ctx, json, _params) => {
|
|
8175
|
+
const _json = json;
|
|
8176
|
+
const file = {
|
|
8177
|
+
type: "string",
|
|
8178
|
+
format: "binary",
|
|
8179
|
+
contentEncoding: "binary",
|
|
8180
|
+
};
|
|
8181
|
+
const { minimum, maximum, mime } = schema._zod.bag;
|
|
8182
|
+
if (minimum !== undefined)
|
|
8183
|
+
file.minLength = minimum;
|
|
8184
|
+
if (maximum !== undefined)
|
|
8185
|
+
file.maxLength = maximum;
|
|
8186
|
+
if (mime) {
|
|
8187
|
+
if (mime.length === 1) {
|
|
8188
|
+
file.contentMediaType = mime[0];
|
|
8189
|
+
Object.assign(_json, file);
|
|
8190
|
+
}
|
|
8191
|
+
else {
|
|
8192
|
+
Object.assign(_json, file); // shared props at root
|
|
8193
|
+
_json.anyOf = mime.map((m) => ({ contentMediaType: m })); // only contentMediaType differs
|
|
8194
|
+
}
|
|
8195
|
+
}
|
|
8196
|
+
else {
|
|
8197
|
+
Object.assign(_json, file);
|
|
8198
|
+
}
|
|
8199
|
+
};
|
|
8200
|
+
const successProcessor = (_schema, _ctx, json, _params) => {
|
|
8201
|
+
json.type = "boolean";
|
|
8202
|
+
};
|
|
8203
|
+
const customProcessor = (_schema, ctx, _json, _params) => {
|
|
8204
|
+
if (ctx.unrepresentable === "throw") {
|
|
8205
|
+
throw new Error("Custom types cannot be represented in JSON Schema");
|
|
8206
|
+
}
|
|
8207
|
+
};
|
|
8208
|
+
const functionProcessor = (_schema, ctx, _json, _params) => {
|
|
8209
|
+
if (ctx.unrepresentable === "throw") {
|
|
8210
|
+
throw new Error("Function types cannot be represented in JSON Schema");
|
|
8211
|
+
}
|
|
8212
|
+
};
|
|
8213
|
+
const transformProcessor = (_schema, ctx, _json, _params) => {
|
|
8214
|
+
if (ctx.unrepresentable === "throw") {
|
|
8215
|
+
throw new Error("Transforms cannot be represented in JSON Schema");
|
|
8216
|
+
}
|
|
8217
|
+
};
|
|
8218
|
+
const mapProcessor = (_schema, ctx, _json, _params) => {
|
|
8219
|
+
if (ctx.unrepresentable === "throw") {
|
|
8220
|
+
throw new Error("Map cannot be represented in JSON Schema");
|
|
8221
|
+
}
|
|
8222
|
+
};
|
|
8223
|
+
const setProcessor = (_schema, ctx, _json, _params) => {
|
|
8224
|
+
if (ctx.unrepresentable === "throw") {
|
|
8225
|
+
throw new Error("Set cannot be represented in JSON Schema");
|
|
8226
|
+
}
|
|
8227
|
+
};
|
|
8228
|
+
// ==================== COMPOSITE TYPE PROCESSORS ====================
|
|
8229
|
+
const arrayProcessor = (schema, ctx, _json, params) => {
|
|
8230
|
+
const json = _json;
|
|
8231
|
+
const def = schema._zod.def;
|
|
8232
|
+
const { minimum, maximum } = schema._zod.bag;
|
|
8233
|
+
if (typeof minimum === "number")
|
|
8234
|
+
json.minItems = minimum;
|
|
8235
|
+
if (typeof maximum === "number")
|
|
8236
|
+
json.maxItems = maximum;
|
|
8237
|
+
json.type = "array";
|
|
8238
|
+
json.items = process$1(def.element, ctx, { ...params, path: [...params.path, "items"] });
|
|
8239
|
+
};
|
|
8240
|
+
const objectProcessor = (schema, ctx, _json, params) => {
|
|
8241
|
+
const json = _json;
|
|
8242
|
+
const def = schema._zod.def;
|
|
8243
|
+
json.type = "object";
|
|
8244
|
+
json.properties = {};
|
|
8245
|
+
const shape = def.shape;
|
|
8246
|
+
for (const key in shape) {
|
|
8247
|
+
json.properties[key] = process$1(shape[key], ctx, {
|
|
8248
|
+
...params,
|
|
8249
|
+
path: [...params.path, "properties", key],
|
|
8250
|
+
});
|
|
8251
|
+
}
|
|
8252
|
+
// required keys
|
|
8253
|
+
const allKeys = new Set(Object.keys(shape));
|
|
8254
|
+
const requiredKeys = new Set([...allKeys].filter((key) => {
|
|
8255
|
+
const v = def.shape[key]._zod;
|
|
8256
|
+
if (ctx.io === "input") {
|
|
8257
|
+
return v.optin === undefined;
|
|
8258
|
+
}
|
|
8259
|
+
else {
|
|
8260
|
+
return v.optout === undefined;
|
|
8261
|
+
}
|
|
8262
|
+
}));
|
|
8263
|
+
if (requiredKeys.size > 0) {
|
|
8264
|
+
json.required = Array.from(requiredKeys);
|
|
8265
|
+
}
|
|
8266
|
+
// catchall
|
|
8267
|
+
if (def.catchall?._zod.def.type === "never") {
|
|
8268
|
+
// strict
|
|
8269
|
+
json.additionalProperties = false;
|
|
8270
|
+
}
|
|
8271
|
+
else if (!def.catchall) {
|
|
8272
|
+
// regular
|
|
8273
|
+
if (ctx.io === "output")
|
|
8274
|
+
json.additionalProperties = false;
|
|
8275
|
+
}
|
|
8276
|
+
else if (def.catchall) {
|
|
8277
|
+
json.additionalProperties = process$1(def.catchall, ctx, {
|
|
8278
|
+
...params,
|
|
8279
|
+
path: [...params.path, "additionalProperties"],
|
|
8280
|
+
});
|
|
8281
|
+
}
|
|
8282
|
+
};
|
|
8283
|
+
const unionProcessor = (schema, ctx, json, params) => {
|
|
8284
|
+
const def = schema._zod.def;
|
|
8285
|
+
// Exclusive unions (inclusive === false) use oneOf (exactly one match) instead of anyOf (one or more matches)
|
|
8286
|
+
// This includes both z.xor() and discriminated unions
|
|
8287
|
+
const isExclusive = def.inclusive === false;
|
|
8288
|
+
const options = def.options.map((x, i) => process$1(x, ctx, {
|
|
8289
|
+
...params,
|
|
8290
|
+
path: [...params.path, isExclusive ? "oneOf" : "anyOf", i],
|
|
8291
|
+
}));
|
|
8292
|
+
if (isExclusive) {
|
|
8293
|
+
json.oneOf = options;
|
|
8294
|
+
}
|
|
8295
|
+
else {
|
|
8296
|
+
json.anyOf = options;
|
|
8297
|
+
}
|
|
8298
|
+
};
|
|
8299
|
+
const intersectionProcessor = (schema, ctx, json, params) => {
|
|
8300
|
+
const def = schema._zod.def;
|
|
8301
|
+
const a = process$1(def.left, ctx, {
|
|
8302
|
+
...params,
|
|
8303
|
+
path: [...params.path, "allOf", 0],
|
|
8304
|
+
});
|
|
8305
|
+
const b = process$1(def.right, ctx, {
|
|
8306
|
+
...params,
|
|
8307
|
+
path: [...params.path, "allOf", 1],
|
|
8308
|
+
});
|
|
8309
|
+
const isSimpleIntersection = (val) => "allOf" in val && Object.keys(val).length === 1;
|
|
8310
|
+
const allOf = [
|
|
8311
|
+
...(isSimpleIntersection(a) ? a.allOf : [a]),
|
|
8312
|
+
...(isSimpleIntersection(b) ? b.allOf : [b]),
|
|
8313
|
+
];
|
|
8314
|
+
json.allOf = allOf;
|
|
8315
|
+
};
|
|
8316
|
+
const tupleProcessor = (schema, ctx, _json, params) => {
|
|
8317
|
+
const json = _json;
|
|
8318
|
+
const def = schema._zod.def;
|
|
8319
|
+
json.type = "array";
|
|
8320
|
+
const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
8321
|
+
const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
8322
|
+
const prefixItems = def.items.map((x, i) => process$1(x, ctx, {
|
|
8323
|
+
...params,
|
|
8324
|
+
path: [...params.path, prefixPath, i],
|
|
8325
|
+
}));
|
|
8326
|
+
const rest = def.rest
|
|
8327
|
+
? process$1(def.rest, ctx, {
|
|
8328
|
+
...params,
|
|
8329
|
+
path: [...params.path, restPath, ...(ctx.target === "openapi-3.0" ? [def.items.length] : [])],
|
|
8330
|
+
})
|
|
8331
|
+
: null;
|
|
8332
|
+
if (ctx.target === "draft-2020-12") {
|
|
8333
|
+
json.prefixItems = prefixItems;
|
|
8334
|
+
if (rest) {
|
|
8335
|
+
json.items = rest;
|
|
8336
|
+
}
|
|
8337
|
+
}
|
|
8338
|
+
else if (ctx.target === "openapi-3.0") {
|
|
8339
|
+
json.items = {
|
|
8340
|
+
anyOf: prefixItems,
|
|
8341
|
+
};
|
|
8342
|
+
if (rest) {
|
|
8343
|
+
json.items.anyOf.push(rest);
|
|
8344
|
+
}
|
|
8345
|
+
json.minItems = prefixItems.length;
|
|
8346
|
+
if (!rest) {
|
|
8347
|
+
json.maxItems = prefixItems.length;
|
|
8348
|
+
}
|
|
8349
|
+
}
|
|
8350
|
+
else {
|
|
8351
|
+
json.items = prefixItems;
|
|
8352
|
+
if (rest) {
|
|
8353
|
+
json.additionalItems = rest;
|
|
8354
|
+
}
|
|
8355
|
+
}
|
|
8356
|
+
// length
|
|
8357
|
+
const { minimum, maximum } = schema._zod.bag;
|
|
8358
|
+
if (typeof minimum === "number")
|
|
8359
|
+
json.minItems = minimum;
|
|
8360
|
+
if (typeof maximum === "number")
|
|
8361
|
+
json.maxItems = maximum;
|
|
8362
|
+
};
|
|
8363
|
+
const recordProcessor = (schema, ctx, _json, params) => {
|
|
8364
|
+
const json = _json;
|
|
8365
|
+
const def = schema._zod.def;
|
|
8366
|
+
json.type = "object";
|
|
8367
|
+
// For looseRecord with regex patterns, use patternProperties
|
|
8368
|
+
// This correctly represents "only validate keys matching the pattern" semantics
|
|
8369
|
+
// and composes well with allOf (intersections)
|
|
8370
|
+
const keyType = def.keyType;
|
|
8371
|
+
const keyBag = keyType._zod.bag;
|
|
8372
|
+
const patterns = keyBag?.patterns;
|
|
8373
|
+
if (def.mode === "loose" && patterns && patterns.size > 0) {
|
|
8374
|
+
// Use patternProperties for looseRecord with regex patterns
|
|
8375
|
+
const valueSchema = process$1(def.valueType, ctx, {
|
|
8376
|
+
...params,
|
|
8377
|
+
path: [...params.path, "patternProperties", "*"],
|
|
8378
|
+
});
|
|
8379
|
+
json.patternProperties = {};
|
|
8380
|
+
for (const pattern of patterns) {
|
|
8381
|
+
json.patternProperties[pattern.source] = valueSchema;
|
|
8382
|
+
}
|
|
8383
|
+
}
|
|
8384
|
+
else {
|
|
8385
|
+
// Default behavior: use propertyNames + additionalProperties
|
|
8386
|
+
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
|
|
8387
|
+
json.propertyNames = process$1(def.keyType, ctx, {
|
|
8388
|
+
...params,
|
|
8389
|
+
path: [...params.path, "propertyNames"],
|
|
8390
|
+
});
|
|
8391
|
+
}
|
|
8392
|
+
json.additionalProperties = process$1(def.valueType, ctx, {
|
|
8393
|
+
...params,
|
|
8394
|
+
path: [...params.path, "additionalProperties"],
|
|
8395
|
+
});
|
|
8396
|
+
}
|
|
8397
|
+
// Add required for keys with discrete values (enum, literal, etc.)
|
|
8398
|
+
const keyValues = keyType._zod.values;
|
|
8399
|
+
if (keyValues) {
|
|
8400
|
+
const validKeyValues = [...keyValues].filter((v) => typeof v === "string" || typeof v === "number");
|
|
8401
|
+
if (validKeyValues.length > 0) {
|
|
8402
|
+
json.required = validKeyValues;
|
|
8403
|
+
}
|
|
8404
|
+
}
|
|
8405
|
+
};
|
|
8406
|
+
const nullableProcessor = (schema, ctx, json, params) => {
|
|
8407
|
+
const def = schema._zod.def;
|
|
8408
|
+
const inner = process$1(def.innerType, ctx, params);
|
|
8409
|
+
const seen = ctx.seen.get(schema);
|
|
8410
|
+
if (ctx.target === "openapi-3.0") {
|
|
8411
|
+
seen.ref = def.innerType;
|
|
8412
|
+
json.nullable = true;
|
|
8413
|
+
}
|
|
8414
|
+
else {
|
|
8415
|
+
json.anyOf = [inner, { type: "null" }];
|
|
8416
|
+
}
|
|
8417
|
+
};
|
|
8418
|
+
const nonoptionalProcessor = (schema, ctx, _json, params) => {
|
|
8419
|
+
const def = schema._zod.def;
|
|
8420
|
+
process$1(def.innerType, ctx, params);
|
|
8421
|
+
const seen = ctx.seen.get(schema);
|
|
8422
|
+
seen.ref = def.innerType;
|
|
8423
|
+
};
|
|
8424
|
+
const defaultProcessor = (schema, ctx, json, params) => {
|
|
8425
|
+
const def = schema._zod.def;
|
|
8426
|
+
process$1(def.innerType, ctx, params);
|
|
8427
|
+
const seen = ctx.seen.get(schema);
|
|
8428
|
+
seen.ref = def.innerType;
|
|
8429
|
+
json.default = JSON.parse(JSON.stringify(def.defaultValue));
|
|
8430
|
+
};
|
|
8431
|
+
const prefaultProcessor = (schema, ctx, json, params) => {
|
|
8432
|
+
const def = schema._zod.def;
|
|
8433
|
+
process$1(def.innerType, ctx, params);
|
|
8434
|
+
const seen = ctx.seen.get(schema);
|
|
8435
|
+
seen.ref = def.innerType;
|
|
8436
|
+
if (ctx.io === "input")
|
|
8437
|
+
json._prefault = JSON.parse(JSON.stringify(def.defaultValue));
|
|
8438
|
+
};
|
|
8439
|
+
const catchProcessor = (schema, ctx, json, params) => {
|
|
8440
|
+
const def = schema._zod.def;
|
|
8441
|
+
process$1(def.innerType, ctx, params);
|
|
8442
|
+
const seen = ctx.seen.get(schema);
|
|
8443
|
+
seen.ref = def.innerType;
|
|
8444
|
+
let catchValue;
|
|
8445
|
+
try {
|
|
8446
|
+
catchValue = def.catchValue(undefined);
|
|
8447
|
+
}
|
|
8448
|
+
catch {
|
|
8449
|
+
throw new Error("Dynamic catch values are not supported in JSON Schema");
|
|
8450
|
+
}
|
|
8451
|
+
json.default = catchValue;
|
|
8452
|
+
};
|
|
8453
|
+
const pipeProcessor = (schema, ctx, _json, params) => {
|
|
8454
|
+
const def = schema._zod.def;
|
|
8455
|
+
const innerType = ctx.io === "input" ? (def.in._zod.def.type === "transform" ? def.out : def.in) : def.out;
|
|
8456
|
+
process$1(innerType, ctx, params);
|
|
8457
|
+
const seen = ctx.seen.get(schema);
|
|
8458
|
+
seen.ref = innerType;
|
|
8459
|
+
};
|
|
8460
|
+
const readonlyProcessor = (schema, ctx, json, params) => {
|
|
8461
|
+
const def = schema._zod.def;
|
|
8462
|
+
process$1(def.innerType, ctx, params);
|
|
8463
|
+
const seen = ctx.seen.get(schema);
|
|
8464
|
+
seen.ref = def.innerType;
|
|
8465
|
+
json.readOnly = true;
|
|
8466
|
+
};
|
|
8467
|
+
const promiseProcessor = (schema, ctx, _json, params) => {
|
|
8468
|
+
const def = schema._zod.def;
|
|
8469
|
+
process$1(def.innerType, ctx, params);
|
|
8470
|
+
const seen = ctx.seen.get(schema);
|
|
8471
|
+
seen.ref = def.innerType;
|
|
8472
|
+
};
|
|
8473
|
+
const optionalProcessor = (schema, ctx, _json, params) => {
|
|
8474
|
+
const def = schema._zod.def;
|
|
8475
|
+
process$1(def.innerType, ctx, params);
|
|
8476
|
+
const seen = ctx.seen.get(schema);
|
|
8477
|
+
seen.ref = def.innerType;
|
|
8478
|
+
};
|
|
8479
|
+
const lazyProcessor = (schema, ctx, _json, params) => {
|
|
8480
|
+
const innerType = schema._zod.innerType;
|
|
8481
|
+
process$1(innerType, ctx, params);
|
|
8482
|
+
const seen = ctx.seen.get(schema);
|
|
8483
|
+
seen.ref = innerType;
|
|
8484
|
+
};
|
|
8485
|
+
// ==================== ALL PROCESSORS ====================
|
|
8486
|
+
const allProcessors = {
|
|
8487
|
+
string: stringProcessor,
|
|
8488
|
+
number: numberProcessor,
|
|
8489
|
+
boolean: booleanProcessor,
|
|
8490
|
+
bigint: bigintProcessor,
|
|
8491
|
+
symbol: symbolProcessor,
|
|
8492
|
+
null: nullProcessor,
|
|
8493
|
+
undefined: undefinedProcessor,
|
|
8494
|
+
void: voidProcessor,
|
|
8495
|
+
never: neverProcessor,
|
|
8496
|
+
any: anyProcessor,
|
|
8497
|
+
unknown: unknownProcessor,
|
|
8498
|
+
date: dateProcessor,
|
|
8499
|
+
enum: enumProcessor,
|
|
8500
|
+
literal: literalProcessor,
|
|
8501
|
+
nan: nanProcessor,
|
|
8502
|
+
template_literal: templateLiteralProcessor,
|
|
8503
|
+
file: fileProcessor,
|
|
8504
|
+
success: successProcessor,
|
|
8505
|
+
custom: customProcessor,
|
|
8506
|
+
function: functionProcessor,
|
|
8507
|
+
transform: transformProcessor,
|
|
8508
|
+
map: mapProcessor,
|
|
8509
|
+
set: setProcessor,
|
|
8510
|
+
array: arrayProcessor,
|
|
8511
|
+
object: objectProcessor,
|
|
8512
|
+
union: unionProcessor,
|
|
8513
|
+
intersection: intersectionProcessor,
|
|
8514
|
+
tuple: tupleProcessor,
|
|
8515
|
+
record: recordProcessor,
|
|
8516
|
+
nullable: nullableProcessor,
|
|
8517
|
+
nonoptional: nonoptionalProcessor,
|
|
8518
|
+
default: defaultProcessor,
|
|
8519
|
+
prefault: prefaultProcessor,
|
|
8520
|
+
catch: catchProcessor,
|
|
8521
|
+
pipe: pipeProcessor,
|
|
8522
|
+
readonly: readonlyProcessor,
|
|
8523
|
+
promise: promiseProcessor,
|
|
8524
|
+
optional: optionalProcessor,
|
|
8525
|
+
lazy: lazyProcessor,
|
|
8526
|
+
};
|
|
8527
|
+
function toJSONSchema(input, params) {
|
|
8528
|
+
if ("_idmap" in input) {
|
|
8529
|
+
// Registry case
|
|
8530
|
+
const registry = input;
|
|
8531
|
+
const ctx = initializeContext({ ...params, processors: allProcessors });
|
|
8532
|
+
const defs = {};
|
|
8533
|
+
// First pass: process all schemas to build the seen map
|
|
8534
|
+
for (const entry of registry._idmap.entries()) {
|
|
8535
|
+
const [_, schema] = entry;
|
|
8536
|
+
process$1(schema, ctx);
|
|
8537
|
+
}
|
|
8538
|
+
const schemas = {};
|
|
8539
|
+
const external = {
|
|
8540
|
+
registry,
|
|
8541
|
+
uri: params?.uri,
|
|
8542
|
+
defs,
|
|
8543
|
+
};
|
|
8544
|
+
// Update the context with external configuration
|
|
8545
|
+
ctx.external = external;
|
|
8546
|
+
// Second pass: emit each schema
|
|
8547
|
+
for (const entry of registry._idmap.entries()) {
|
|
8548
|
+
const [key, schema] = entry;
|
|
8549
|
+
extractDefs(ctx, schema);
|
|
8550
|
+
schemas[key] = finalize(ctx, schema);
|
|
8551
|
+
}
|
|
8552
|
+
if (Object.keys(defs).length > 0) {
|
|
8553
|
+
const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
|
|
8554
|
+
schemas.__shared = {
|
|
8555
|
+
[defsSegment]: defs,
|
|
8556
|
+
};
|
|
8557
|
+
}
|
|
8558
|
+
return { schemas };
|
|
8559
|
+
}
|
|
8560
|
+
// Single schema case
|
|
8561
|
+
const ctx = initializeContext({ ...params, processors: allProcessors });
|
|
8562
|
+
process$1(input, ctx);
|
|
8563
|
+
extractDefs(ctx, input);
|
|
8564
|
+
return finalize(ctx, input);
|
|
8565
|
+
}
|
|
8566
|
+
|
|
8567
|
+
const ignoreOverride = Symbol('Let zodToJsonSchema decide on which parser to use');
|
|
8568
|
+
const defaultOptions = {
|
|
8569
|
+
name: undefined,
|
|
8570
|
+
$refStrategy: 'root',
|
|
8571
|
+
effectStrategy: 'input',
|
|
8572
|
+
pipeStrategy: 'all',
|
|
8573
|
+
dateStrategy: 'format:date-time',
|
|
8574
|
+
mapStrategy: 'entries',
|
|
8575
|
+
nullableStrategy: 'from-target',
|
|
8576
|
+
removeAdditionalStrategy: 'passthrough',
|
|
8577
|
+
definitionPath: 'definitions',
|
|
8578
|
+
target: 'jsonSchema7',
|
|
8579
|
+
strictUnions: false,
|
|
8580
|
+
errorMessages: false,
|
|
8581
|
+
markdownDescription: false,
|
|
8582
|
+
patternStrategy: 'escape',
|
|
8583
|
+
applyRegexFlags: false,
|
|
8584
|
+
emailStrategy: 'format:email',
|
|
8585
|
+
base64Strategy: 'contentEncoding:base64',
|
|
8586
|
+
nameStrategy: 'ref',
|
|
8587
|
+
};
|
|
8588
|
+
const getDefaultOptions = (options) => {
|
|
8589
|
+
// We need to add `definitions` here as we may mutate it
|
|
8590
|
+
return (typeof options === 'string' ?
|
|
8591
|
+
{
|
|
8592
|
+
...defaultOptions,
|
|
8593
|
+
basePath: ['#'],
|
|
8594
|
+
definitions: {},
|
|
8595
|
+
name: options,
|
|
8596
|
+
}
|
|
8597
|
+
: {
|
|
8598
|
+
...defaultOptions,
|
|
8599
|
+
basePath: ['#'],
|
|
8600
|
+
definitions: {},
|
|
8601
|
+
...options,
|
|
8602
|
+
});
|
|
8603
|
+
};
|
|
8604
|
+
|
|
8605
|
+
const zodDef = (zodSchema) => {
|
|
8606
|
+
return '_def' in zodSchema ? zodSchema._def : zodSchema;
|
|
8607
|
+
};
|
|
8608
|
+
function isEmptyObj(obj) {
|
|
8609
|
+
if (!obj)
|
|
8610
|
+
return true;
|
|
8611
|
+
for (const _k in obj)
|
|
8612
|
+
return false;
|
|
8613
|
+
return true;
|
|
8614
|
+
}
|
|
8615
|
+
|
|
8616
|
+
const getRefs = (options) => {
|
|
8617
|
+
const _options = getDefaultOptions(options);
|
|
8618
|
+
const currentPath = _options.name !== undefined ?
|
|
8619
|
+
[..._options.basePath, _options.definitionPath, _options.name]
|
|
8620
|
+
: _options.basePath;
|
|
8621
|
+
return {
|
|
8622
|
+
..._options,
|
|
8623
|
+
currentPath: currentPath,
|
|
8624
|
+
propertyPath: undefined,
|
|
8625
|
+
seenRefs: new Set(),
|
|
8626
|
+
seen: new Map(Object.entries(_options.definitions).map(([name, def]) => [
|
|
8627
|
+
zodDef(def),
|
|
8628
|
+
{
|
|
8629
|
+
def: zodDef(def),
|
|
8630
|
+
path: [..._options.basePath, _options.definitionPath, name],
|
|
8631
|
+
// Resolution of references will be forced even though seen, so it's ok that the schema is undefined here for now.
|
|
8632
|
+
jsonSchema: undefined,
|
|
8633
|
+
},
|
|
8634
|
+
])),
|
|
8635
|
+
};
|
|
8636
|
+
};
|
|
8637
|
+
|
|
8638
|
+
function addErrorMessage(res, key, errorMessage, refs) {
|
|
8639
|
+
if (!refs?.errorMessages)
|
|
8640
|
+
return;
|
|
8641
|
+
if (errorMessage) {
|
|
8642
|
+
res.errorMessage = {
|
|
8643
|
+
...res.errorMessage,
|
|
8644
|
+
[key]: errorMessage,
|
|
8645
|
+
};
|
|
8646
|
+
}
|
|
8647
|
+
}
|
|
8648
|
+
function setResponseValueAndErrors(res, key, value, errorMessage, refs) {
|
|
8649
|
+
res[key] = value;
|
|
8650
|
+
addErrorMessage(res, key, errorMessage, refs);
|
|
8651
|
+
}
|
|
8652
|
+
|
|
8653
|
+
var util;
|
|
8654
|
+
(function (util) {
|
|
8655
|
+
util.assertEqual = (_) => { };
|
|
8656
|
+
function assertIs(_arg) { }
|
|
8657
|
+
util.assertIs = assertIs;
|
|
8658
|
+
function assertNever(_x) {
|
|
8659
|
+
throw new Error();
|
|
8660
|
+
}
|
|
8661
|
+
util.assertNever = assertNever;
|
|
8662
|
+
util.arrayToEnum = (items) => {
|
|
8663
|
+
const obj = {};
|
|
8664
|
+
for (const item of items) {
|
|
8665
|
+
obj[item] = item;
|
|
8666
|
+
}
|
|
8667
|
+
return obj;
|
|
8668
|
+
};
|
|
8669
|
+
util.getValidEnumValues = (obj) => {
|
|
8670
|
+
const validKeys = util.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number");
|
|
8671
|
+
const filtered = {};
|
|
8672
|
+
for (const k of validKeys) {
|
|
8673
|
+
filtered[k] = obj[k];
|
|
8674
|
+
}
|
|
8675
|
+
return util.objectValues(filtered);
|
|
8676
|
+
};
|
|
8677
|
+
util.objectValues = (obj) => {
|
|
8678
|
+
return util.objectKeys(obj).map(function (e) {
|
|
8679
|
+
return obj[e];
|
|
8680
|
+
});
|
|
8681
|
+
};
|
|
8682
|
+
util.objectKeys = typeof Object.keys === "function" // eslint-disable-line ban/ban
|
|
8683
|
+
? (obj) => Object.keys(obj) // eslint-disable-line ban/ban
|
|
8684
|
+
: (object) => {
|
|
8685
|
+
const keys = [];
|
|
8686
|
+
for (const key in object) {
|
|
8687
|
+
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
|
8688
|
+
keys.push(key);
|
|
8689
|
+
}
|
|
8690
|
+
}
|
|
8691
|
+
return keys;
|
|
8692
|
+
};
|
|
8693
|
+
util.find = (arr, checker) => {
|
|
8694
|
+
for (const item of arr) {
|
|
8695
|
+
if (checker(item))
|
|
8696
|
+
return item;
|
|
8697
|
+
}
|
|
8698
|
+
return undefined;
|
|
8699
|
+
};
|
|
8700
|
+
util.isInteger = typeof Number.isInteger === "function"
|
|
8701
|
+
? (val) => Number.isInteger(val) // eslint-disable-line ban/ban
|
|
8702
|
+
: (val) => typeof val === "number" && Number.isFinite(val) && Math.floor(val) === val;
|
|
8703
|
+
function joinValues(array, separator = " | ") {
|
|
8704
|
+
return array.map((val) => (typeof val === "string" ? `'${val}'` : val)).join(separator);
|
|
8705
|
+
}
|
|
8706
|
+
util.joinValues = joinValues;
|
|
8707
|
+
util.jsonStringifyReplacer = (_, value) => {
|
|
8708
|
+
if (typeof value === "bigint") {
|
|
8709
|
+
return value.toString();
|
|
8710
|
+
}
|
|
8711
|
+
return value;
|
|
8712
|
+
};
|
|
8713
|
+
})(util || (util = {}));
|
|
8714
|
+
var objectUtil;
|
|
8715
|
+
(function (objectUtil) {
|
|
8716
|
+
objectUtil.mergeShapes = (first, second) => {
|
|
8717
|
+
return {
|
|
8718
|
+
...first,
|
|
8719
|
+
...second, // second overwrites first
|
|
8720
|
+
};
|
|
8721
|
+
};
|
|
8722
|
+
})(objectUtil || (objectUtil = {}));
|
|
8723
|
+
util.arrayToEnum([
|
|
8724
|
+
"string",
|
|
8725
|
+
"nan",
|
|
8726
|
+
"number",
|
|
8727
|
+
"integer",
|
|
8728
|
+
"float",
|
|
8729
|
+
"boolean",
|
|
8730
|
+
"date",
|
|
8731
|
+
"bigint",
|
|
8732
|
+
"symbol",
|
|
8733
|
+
"function",
|
|
8734
|
+
"undefined",
|
|
8735
|
+
"null",
|
|
8736
|
+
"array",
|
|
8737
|
+
"object",
|
|
8738
|
+
"unknown",
|
|
8739
|
+
"promise",
|
|
8740
|
+
"void",
|
|
8741
|
+
"never",
|
|
8742
|
+
"map",
|
|
8743
|
+
"set",
|
|
8744
|
+
]);
|
|
8745
|
+
|
|
8746
|
+
util.arrayToEnum([
|
|
8747
|
+
"invalid_type",
|
|
8748
|
+
"invalid_literal",
|
|
8749
|
+
"custom",
|
|
8750
|
+
"invalid_union",
|
|
8751
|
+
"invalid_union_discriminator",
|
|
8752
|
+
"invalid_enum_value",
|
|
8753
|
+
"unrecognized_keys",
|
|
8754
|
+
"invalid_arguments",
|
|
8755
|
+
"invalid_return_type",
|
|
8756
|
+
"invalid_date",
|
|
8757
|
+
"invalid_string",
|
|
8758
|
+
"too_small",
|
|
8759
|
+
"too_big",
|
|
8760
|
+
"invalid_intersection_types",
|
|
8761
|
+
"not_multiple_of",
|
|
8762
|
+
"not_finite",
|
|
8763
|
+
]);
|
|
8764
|
+
class ZodError extends Error {
|
|
8765
|
+
get errors() {
|
|
8766
|
+
return this.issues;
|
|
8767
|
+
}
|
|
8768
|
+
constructor(issues) {
|
|
8769
|
+
super();
|
|
8770
|
+
this.issues = [];
|
|
8771
|
+
this.addIssue = (sub) => {
|
|
8772
|
+
this.issues = [...this.issues, sub];
|
|
8773
|
+
};
|
|
8774
|
+
this.addIssues = (subs = []) => {
|
|
8775
|
+
this.issues = [...this.issues, ...subs];
|
|
8776
|
+
};
|
|
8777
|
+
const actualProto = new.target.prototype;
|
|
8778
|
+
if (Object.setPrototypeOf) {
|
|
8779
|
+
// eslint-disable-next-line ban/ban
|
|
8780
|
+
Object.setPrototypeOf(this, actualProto);
|
|
8781
|
+
}
|
|
8782
|
+
else {
|
|
8783
|
+
this.__proto__ = actualProto;
|
|
8784
|
+
}
|
|
8785
|
+
this.name = "ZodError";
|
|
8786
|
+
this.issues = issues;
|
|
8787
|
+
}
|
|
8788
|
+
format(_mapper) {
|
|
8789
|
+
const mapper = _mapper ||
|
|
8790
|
+
function (issue) {
|
|
8791
|
+
return issue.message;
|
|
8792
|
+
};
|
|
8793
|
+
const fieldErrors = { _errors: [] };
|
|
8794
|
+
const processError = (error) => {
|
|
8795
|
+
for (const issue of error.issues) {
|
|
8796
|
+
if (issue.code === "invalid_union") {
|
|
8797
|
+
issue.unionErrors.map(processError);
|
|
8798
|
+
}
|
|
8799
|
+
else if (issue.code === "invalid_return_type") {
|
|
8800
|
+
processError(issue.returnTypeError);
|
|
8801
|
+
}
|
|
8802
|
+
else if (issue.code === "invalid_arguments") {
|
|
8803
|
+
processError(issue.argumentsError);
|
|
8804
|
+
}
|
|
8805
|
+
else if (issue.path.length === 0) {
|
|
8806
|
+
fieldErrors._errors.push(mapper(issue));
|
|
8807
|
+
}
|
|
8808
|
+
else {
|
|
8809
|
+
let curr = fieldErrors;
|
|
8810
|
+
let i = 0;
|
|
8811
|
+
while (i < issue.path.length) {
|
|
8812
|
+
const el = issue.path[i];
|
|
8813
|
+
const terminal = i === issue.path.length - 1;
|
|
8814
|
+
if (!terminal) {
|
|
8815
|
+
curr[el] = curr[el] || { _errors: [] };
|
|
8816
|
+
// if (typeof el === "string") {
|
|
8817
|
+
// curr[el] = curr[el] || { _errors: [] };
|
|
8818
|
+
// } else if (typeof el === "number") {
|
|
8819
|
+
// const errorArray: any = [];
|
|
8820
|
+
// errorArray._errors = [];
|
|
8821
|
+
// curr[el] = curr[el] || errorArray;
|
|
8822
|
+
// }
|
|
8823
|
+
}
|
|
8824
|
+
else {
|
|
8825
|
+
curr[el] = curr[el] || { _errors: [] };
|
|
8826
|
+
curr[el]._errors.push(mapper(issue));
|
|
8827
|
+
}
|
|
8828
|
+
curr = curr[el];
|
|
8829
|
+
i++;
|
|
8830
|
+
}
|
|
8831
|
+
}
|
|
8832
|
+
}
|
|
8833
|
+
};
|
|
8834
|
+
processError(this);
|
|
8835
|
+
return fieldErrors;
|
|
8836
|
+
}
|
|
8837
|
+
static assert(value) {
|
|
8838
|
+
if (!(value instanceof ZodError)) {
|
|
8839
|
+
throw new Error(`Not a ZodError: ${value}`);
|
|
8840
|
+
}
|
|
8841
|
+
}
|
|
8842
|
+
toString() {
|
|
8843
|
+
return this.message;
|
|
8844
|
+
}
|
|
8845
|
+
get message() {
|
|
8846
|
+
return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2);
|
|
8847
|
+
}
|
|
8848
|
+
get isEmpty() {
|
|
8849
|
+
return this.issues.length === 0;
|
|
8850
|
+
}
|
|
8851
|
+
flatten(mapper = (issue) => issue.message) {
|
|
8852
|
+
const fieldErrors = Object.create(null);
|
|
8853
|
+
const formErrors = [];
|
|
8854
|
+
for (const sub of this.issues) {
|
|
8855
|
+
if (sub.path.length > 0) {
|
|
8856
|
+
const firstEl = sub.path[0];
|
|
8857
|
+
fieldErrors[firstEl] = fieldErrors[firstEl] || [];
|
|
8858
|
+
fieldErrors[firstEl].push(mapper(sub));
|
|
8859
|
+
}
|
|
8860
|
+
else {
|
|
8861
|
+
formErrors.push(mapper(sub));
|
|
8862
|
+
}
|
|
8863
|
+
}
|
|
8864
|
+
return { formErrors, fieldErrors };
|
|
8865
|
+
}
|
|
8866
|
+
get formErrors() {
|
|
8867
|
+
return this.flatten();
|
|
8868
|
+
}
|
|
8869
|
+
}
|
|
8870
|
+
ZodError.create = (issues) => {
|
|
8871
|
+
const error = new ZodError(issues);
|
|
8872
|
+
return error;
|
|
8873
|
+
};
|
|
8874
|
+
|
|
8875
|
+
var errorUtil;
|
|
8876
|
+
(function (errorUtil) {
|
|
8877
|
+
errorUtil.errToObj = (message) => typeof message === "string" ? { message } : message || {};
|
|
8878
|
+
// biome-ignore lint:
|
|
8879
|
+
errorUtil.toString = (message) => typeof message === "string" ? message : message?.message;
|
|
8880
|
+
})(errorUtil || (errorUtil = {}));
|
|
8881
|
+
|
|
8882
|
+
var ZodFirstPartyTypeKind;
|
|
8883
|
+
(function (ZodFirstPartyTypeKind) {
|
|
8884
|
+
ZodFirstPartyTypeKind["ZodString"] = "ZodString";
|
|
8885
|
+
ZodFirstPartyTypeKind["ZodNumber"] = "ZodNumber";
|
|
8886
|
+
ZodFirstPartyTypeKind["ZodNaN"] = "ZodNaN";
|
|
8887
|
+
ZodFirstPartyTypeKind["ZodBigInt"] = "ZodBigInt";
|
|
8888
|
+
ZodFirstPartyTypeKind["ZodBoolean"] = "ZodBoolean";
|
|
8889
|
+
ZodFirstPartyTypeKind["ZodDate"] = "ZodDate";
|
|
8890
|
+
ZodFirstPartyTypeKind["ZodSymbol"] = "ZodSymbol";
|
|
8891
|
+
ZodFirstPartyTypeKind["ZodUndefined"] = "ZodUndefined";
|
|
8892
|
+
ZodFirstPartyTypeKind["ZodNull"] = "ZodNull";
|
|
8893
|
+
ZodFirstPartyTypeKind["ZodAny"] = "ZodAny";
|
|
8894
|
+
ZodFirstPartyTypeKind["ZodUnknown"] = "ZodUnknown";
|
|
8895
|
+
ZodFirstPartyTypeKind["ZodNever"] = "ZodNever";
|
|
8896
|
+
ZodFirstPartyTypeKind["ZodVoid"] = "ZodVoid";
|
|
8897
|
+
ZodFirstPartyTypeKind["ZodArray"] = "ZodArray";
|
|
8898
|
+
ZodFirstPartyTypeKind["ZodObject"] = "ZodObject";
|
|
8899
|
+
ZodFirstPartyTypeKind["ZodUnion"] = "ZodUnion";
|
|
8900
|
+
ZodFirstPartyTypeKind["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion";
|
|
8901
|
+
ZodFirstPartyTypeKind["ZodIntersection"] = "ZodIntersection";
|
|
8902
|
+
ZodFirstPartyTypeKind["ZodTuple"] = "ZodTuple";
|
|
8903
|
+
ZodFirstPartyTypeKind["ZodRecord"] = "ZodRecord";
|
|
8904
|
+
ZodFirstPartyTypeKind["ZodMap"] = "ZodMap";
|
|
8905
|
+
ZodFirstPartyTypeKind["ZodSet"] = "ZodSet";
|
|
8906
|
+
ZodFirstPartyTypeKind["ZodFunction"] = "ZodFunction";
|
|
8907
|
+
ZodFirstPartyTypeKind["ZodLazy"] = "ZodLazy";
|
|
8908
|
+
ZodFirstPartyTypeKind["ZodLiteral"] = "ZodLiteral";
|
|
8909
|
+
ZodFirstPartyTypeKind["ZodEnum"] = "ZodEnum";
|
|
8910
|
+
ZodFirstPartyTypeKind["ZodEffects"] = "ZodEffects";
|
|
8911
|
+
ZodFirstPartyTypeKind["ZodNativeEnum"] = "ZodNativeEnum";
|
|
8912
|
+
ZodFirstPartyTypeKind["ZodOptional"] = "ZodOptional";
|
|
8913
|
+
ZodFirstPartyTypeKind["ZodNullable"] = "ZodNullable";
|
|
8914
|
+
ZodFirstPartyTypeKind["ZodDefault"] = "ZodDefault";
|
|
8915
|
+
ZodFirstPartyTypeKind["ZodCatch"] = "ZodCatch";
|
|
8916
|
+
ZodFirstPartyTypeKind["ZodPromise"] = "ZodPromise";
|
|
8917
|
+
ZodFirstPartyTypeKind["ZodBranded"] = "ZodBranded";
|
|
8918
|
+
ZodFirstPartyTypeKind["ZodPipeline"] = "ZodPipeline";
|
|
8919
|
+
ZodFirstPartyTypeKind["ZodReadonly"] = "ZodReadonly";
|
|
8920
|
+
})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
|
|
8921
|
+
|
|
8922
|
+
function parseAnyDef() {
|
|
8923
|
+
return {};
|
|
8924
|
+
}
|
|
8925
|
+
|
|
8926
|
+
function parseArrayDef(def, refs) {
|
|
8927
|
+
const res = {
|
|
8928
|
+
type: 'array',
|
|
8929
|
+
};
|
|
8930
|
+
if (def.type?._def?.typeName !== ZodFirstPartyTypeKind.ZodAny) {
|
|
8931
|
+
res.items = parseDef(def.type._def, {
|
|
8932
|
+
...refs,
|
|
8933
|
+
currentPath: [...refs.currentPath, 'items'],
|
|
8934
|
+
});
|
|
8935
|
+
}
|
|
8936
|
+
if (def.minLength) {
|
|
8937
|
+
setResponseValueAndErrors(res, 'minItems', def.minLength.value, def.minLength.message, refs);
|
|
8938
|
+
}
|
|
8939
|
+
if (def.maxLength) {
|
|
8940
|
+
setResponseValueAndErrors(res, 'maxItems', def.maxLength.value, def.maxLength.message, refs);
|
|
8941
|
+
}
|
|
8942
|
+
if (def.exactLength) {
|
|
8943
|
+
setResponseValueAndErrors(res, 'minItems', def.exactLength.value, def.exactLength.message, refs);
|
|
8944
|
+
setResponseValueAndErrors(res, 'maxItems', def.exactLength.value, def.exactLength.message, refs);
|
|
8945
|
+
}
|
|
8946
|
+
return res;
|
|
8947
|
+
}
|
|
8948
|
+
|
|
8949
|
+
function parseBigintDef(def, refs) {
|
|
8950
|
+
const res = {
|
|
8951
|
+
type: 'integer',
|
|
8952
|
+
format: 'int64',
|
|
8953
|
+
};
|
|
8954
|
+
if (!def.checks)
|
|
8955
|
+
return res;
|
|
8956
|
+
for (const check of def.checks) {
|
|
8957
|
+
switch (check.kind) {
|
|
8958
|
+
case 'min':
|
|
8959
|
+
if (refs.target === 'jsonSchema7') {
|
|
8960
|
+
if (check.inclusive) {
|
|
8961
|
+
setResponseValueAndErrors(res, 'minimum', check.value, check.message, refs);
|
|
8962
|
+
}
|
|
8963
|
+
else {
|
|
8964
|
+
setResponseValueAndErrors(res, 'exclusiveMinimum', check.value, check.message, refs);
|
|
8965
|
+
}
|
|
8966
|
+
}
|
|
8967
|
+
else {
|
|
8968
|
+
if (!check.inclusive) {
|
|
8969
|
+
res.exclusiveMinimum = true;
|
|
8970
|
+
}
|
|
8971
|
+
setResponseValueAndErrors(res, 'minimum', check.value, check.message, refs);
|
|
8972
|
+
}
|
|
8973
|
+
break;
|
|
8974
|
+
case 'max':
|
|
8975
|
+
if (refs.target === 'jsonSchema7') {
|
|
8976
|
+
if (check.inclusive) {
|
|
8977
|
+
setResponseValueAndErrors(res, 'maximum', check.value, check.message, refs);
|
|
8978
|
+
}
|
|
8979
|
+
else {
|
|
8980
|
+
setResponseValueAndErrors(res, 'exclusiveMaximum', check.value, check.message, refs);
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8983
|
+
else {
|
|
8984
|
+
if (!check.inclusive) {
|
|
8985
|
+
res.exclusiveMaximum = true;
|
|
8986
|
+
}
|
|
8987
|
+
setResponseValueAndErrors(res, 'maximum', check.value, check.message, refs);
|
|
8988
|
+
}
|
|
8989
|
+
break;
|
|
8990
|
+
case 'multipleOf':
|
|
8991
|
+
setResponseValueAndErrors(res, 'multipleOf', check.value, check.message, refs);
|
|
8992
|
+
break;
|
|
8993
|
+
}
|
|
8994
|
+
}
|
|
8995
|
+
return res;
|
|
8996
|
+
}
|
|
8997
|
+
|
|
8998
|
+
function parseBooleanDef() {
|
|
8999
|
+
return {
|
|
9000
|
+
type: 'boolean',
|
|
9001
|
+
};
|
|
9002
|
+
}
|
|
9003
|
+
|
|
9004
|
+
function parseBrandedDef(_def, refs) {
|
|
9005
|
+
return parseDef(_def.type._def, refs);
|
|
9006
|
+
}
|
|
9007
|
+
|
|
9008
|
+
const parseCatchDef = (def, refs) => {
|
|
9009
|
+
return parseDef(def.innerType._def, refs);
|
|
9010
|
+
};
|
|
9011
|
+
|
|
9012
|
+
function parseDateDef(def, refs, overrideDateStrategy) {
|
|
9013
|
+
const strategy = overrideDateStrategy ?? refs.dateStrategy;
|
|
9014
|
+
if (Array.isArray(strategy)) {
|
|
9015
|
+
return {
|
|
9016
|
+
anyOf: strategy.map((item, i) => parseDateDef(def, refs, item)),
|
|
9017
|
+
};
|
|
9018
|
+
}
|
|
9019
|
+
switch (strategy) {
|
|
9020
|
+
case 'string':
|
|
9021
|
+
case 'format:date-time':
|
|
9022
|
+
return {
|
|
9023
|
+
type: 'string',
|
|
9024
|
+
format: 'date-time',
|
|
9025
|
+
};
|
|
9026
|
+
case 'format:date':
|
|
9027
|
+
return {
|
|
9028
|
+
type: 'string',
|
|
9029
|
+
format: 'date',
|
|
9030
|
+
};
|
|
9031
|
+
case 'integer':
|
|
9032
|
+
return integerDateParser(def, refs);
|
|
9033
|
+
}
|
|
9034
|
+
}
|
|
9035
|
+
const integerDateParser = (def, refs) => {
|
|
9036
|
+
const res = {
|
|
9037
|
+
type: 'integer',
|
|
9038
|
+
format: 'unix-time',
|
|
9039
|
+
};
|
|
9040
|
+
if (refs.target === 'openApi3') {
|
|
9041
|
+
return res;
|
|
9042
|
+
}
|
|
9043
|
+
for (const check of def.checks) {
|
|
9044
|
+
switch (check.kind) {
|
|
9045
|
+
case 'min':
|
|
9046
|
+
setResponseValueAndErrors(res, 'minimum', check.value, // This is in milliseconds
|
|
9047
|
+
check.message, refs);
|
|
9048
|
+
break;
|
|
9049
|
+
case 'max':
|
|
9050
|
+
setResponseValueAndErrors(res, 'maximum', check.value, // This is in milliseconds
|
|
9051
|
+
check.message, refs);
|
|
9052
|
+
break;
|
|
9053
|
+
}
|
|
9054
|
+
}
|
|
9055
|
+
return res;
|
|
9056
|
+
};
|
|
9057
|
+
|
|
9058
|
+
function parseDefaultDef(_def, refs) {
|
|
9059
|
+
return {
|
|
9060
|
+
...parseDef(_def.innerType._def, refs),
|
|
9061
|
+
default: _def.defaultValue(),
|
|
9062
|
+
};
|
|
9063
|
+
}
|
|
9064
|
+
|
|
9065
|
+
function parseEffectsDef(_def, refs, forceResolution) {
|
|
9066
|
+
return refs.effectStrategy === 'input' ? parseDef(_def.schema._def, refs, forceResolution) : {};
|
|
9067
|
+
}
|
|
9068
|
+
|
|
9069
|
+
function parseEnumDef(def) {
|
|
9070
|
+
return {
|
|
9071
|
+
type: 'string',
|
|
9072
|
+
enum: [...def.values],
|
|
9073
|
+
};
|
|
9074
|
+
}
|
|
9075
|
+
|
|
9076
|
+
const isJsonSchema7AllOfType = (type) => {
|
|
9077
|
+
if ('type' in type && type.type === 'string')
|
|
9078
|
+
return false;
|
|
9079
|
+
return 'allOf' in type;
|
|
9080
|
+
};
|
|
9081
|
+
function parseIntersectionDef(def, refs) {
|
|
9082
|
+
const allOf = [
|
|
9083
|
+
parseDef(def.left._def, {
|
|
9084
|
+
...refs,
|
|
9085
|
+
currentPath: [...refs.currentPath, 'allOf', '0'],
|
|
9086
|
+
}),
|
|
9087
|
+
parseDef(def.right._def, {
|
|
9088
|
+
...refs,
|
|
9089
|
+
currentPath: [...refs.currentPath, 'allOf', '1'],
|
|
9090
|
+
}),
|
|
9091
|
+
].filter((x) => !!x);
|
|
9092
|
+
let unevaluatedProperties = refs.target === 'jsonSchema2019-09' ? { unevaluatedProperties: false } : undefined;
|
|
9093
|
+
const mergedAllOf = [];
|
|
9094
|
+
// If either of the schemas is an allOf, merge them into a single allOf
|
|
9095
|
+
allOf.forEach((schema) => {
|
|
9096
|
+
if (isJsonSchema7AllOfType(schema)) {
|
|
9097
|
+
mergedAllOf.push(...schema.allOf);
|
|
9098
|
+
if (schema.unevaluatedProperties === undefined) {
|
|
9099
|
+
// If one of the schemas has no unevaluatedProperties set,
|
|
9100
|
+
// the merged schema should also have no unevaluatedProperties set
|
|
9101
|
+
unevaluatedProperties = undefined;
|
|
9102
|
+
}
|
|
9103
|
+
}
|
|
9104
|
+
else {
|
|
9105
|
+
let nestedSchema = schema;
|
|
9106
|
+
if ('additionalProperties' in schema && schema.additionalProperties === false) {
|
|
9107
|
+
const { additionalProperties, ...rest } = schema;
|
|
9108
|
+
nestedSchema = rest;
|
|
9109
|
+
}
|
|
9110
|
+
else {
|
|
9111
|
+
// As soon as one of the schemas has additionalProperties set not to false, we allow unevaluatedProperties
|
|
9112
|
+
unevaluatedProperties = undefined;
|
|
9113
|
+
}
|
|
9114
|
+
mergedAllOf.push(nestedSchema);
|
|
9115
|
+
}
|
|
9116
|
+
});
|
|
9117
|
+
return mergedAllOf.length ?
|
|
9118
|
+
{
|
|
9119
|
+
allOf: mergedAllOf,
|
|
9120
|
+
...unevaluatedProperties,
|
|
9121
|
+
}
|
|
9122
|
+
: undefined;
|
|
9123
|
+
}
|
|
9124
|
+
|
|
9125
|
+
function parseLiteralDef(def, refs) {
|
|
9126
|
+
const parsedType = typeof def.value;
|
|
9127
|
+
if (parsedType !== 'bigint' &&
|
|
9128
|
+
parsedType !== 'number' &&
|
|
9129
|
+
parsedType !== 'boolean' &&
|
|
9130
|
+
parsedType !== 'string') {
|
|
9131
|
+
return {
|
|
9132
|
+
type: Array.isArray(def.value) ? 'array' : 'object',
|
|
9133
|
+
};
|
|
9134
|
+
}
|
|
9135
|
+
if (refs.target === 'openApi3') {
|
|
9136
|
+
return {
|
|
9137
|
+
type: parsedType === 'bigint' ? 'integer' : parsedType,
|
|
9138
|
+
enum: [def.value],
|
|
9139
|
+
};
|
|
9140
|
+
}
|
|
9141
|
+
return {
|
|
9142
|
+
type: parsedType === 'bigint' ? 'integer' : parsedType,
|
|
9143
|
+
const: def.value,
|
|
9144
|
+
};
|
|
9145
|
+
}
|
|
9146
|
+
|
|
9147
|
+
let emojiRegex;
|
|
9148
|
+
/**
|
|
9149
|
+
* Generated from the regular expressions found here as of 2024-05-22:
|
|
9150
|
+
* https://github.com/colinhacks/zod/blob/master/src/types.ts.
|
|
9151
|
+
*
|
|
9152
|
+
* Expressions with /i flag have been changed accordingly.
|
|
9153
|
+
*/
|
|
9154
|
+
const zodPatterns = {
|
|
9155
|
+
/**
|
|
9156
|
+
* `c` was changed to `[cC]` to replicate /i flag
|
|
9157
|
+
*/
|
|
9158
|
+
cuid: /^[cC][^\s-]{8,}$/,
|
|
9159
|
+
cuid2: /^[0-9a-z]+$/,
|
|
9160
|
+
ulid: /^[0-9A-HJKMNP-TV-Z]{26}$/,
|
|
9161
|
+
/**
|
|
9162
|
+
* `a-z` was added to replicate /i flag
|
|
9163
|
+
*/
|
|
9164
|
+
email: /^(?!\.)(?!.*\.\.)([a-zA-Z0-9_'+\-\.]*)[a-zA-Z0-9_+-]@([a-zA-Z0-9][a-zA-Z0-9\-]*\.)+[a-zA-Z]{2,}$/,
|
|
9165
|
+
/**
|
|
9166
|
+
* Constructed a valid Unicode RegExp
|
|
9167
|
+
*
|
|
9168
|
+
* Lazily instantiate since this type of regex isn't supported
|
|
9169
|
+
* in all envs (e.g. React Native).
|
|
9170
|
+
*
|
|
9171
|
+
* See:
|
|
9172
|
+
* https://github.com/colinhacks/zod/issues/2433
|
|
9173
|
+
* Fix in Zod:
|
|
9174
|
+
* https://github.com/colinhacks/zod/commit/9340fd51e48576a75adc919bff65dbc4a5d4c99b
|
|
9175
|
+
*/
|
|
9176
|
+
emoji: () => {
|
|
9177
|
+
if (emojiRegex === undefined) {
|
|
9178
|
+
emojiRegex = RegExp('^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$', 'u');
|
|
9179
|
+
}
|
|
9180
|
+
return emojiRegex;
|
|
9181
|
+
},
|
|
9182
|
+
base64: /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/,
|
|
9183
|
+
nanoid: /^[a-zA-Z0-9_-]{21}$/,
|
|
9184
|
+
};
|
|
9185
|
+
function parseStringDef(def, refs) {
|
|
9186
|
+
const res = {
|
|
9187
|
+
type: 'string',
|
|
9188
|
+
};
|
|
9189
|
+
function processPattern(value) {
|
|
9190
|
+
return refs.patternStrategy === 'escape' ? escapeNonAlphaNumeric(value) : value;
|
|
9191
|
+
}
|
|
9192
|
+
if (def.checks) {
|
|
9193
|
+
for (const check of def.checks) {
|
|
9194
|
+
switch (check.kind) {
|
|
9195
|
+
case 'min':
|
|
9196
|
+
setResponseValueAndErrors(res, 'minLength', typeof res.minLength === 'number' ? Math.max(res.minLength, check.value) : check.value, check.message, refs);
|
|
9197
|
+
break;
|
|
9198
|
+
case 'max':
|
|
9199
|
+
setResponseValueAndErrors(res, 'maxLength', typeof res.maxLength === 'number' ? Math.min(res.maxLength, check.value) : check.value, check.message, refs);
|
|
9200
|
+
break;
|
|
9201
|
+
case 'email':
|
|
9202
|
+
switch (refs.emailStrategy) {
|
|
9203
|
+
case 'format:email':
|
|
9204
|
+
addFormat(res, 'email', check.message, refs);
|
|
9205
|
+
break;
|
|
9206
|
+
case 'format:idn-email':
|
|
9207
|
+
addFormat(res, 'idn-email', check.message, refs);
|
|
9208
|
+
break;
|
|
9209
|
+
case 'pattern:zod':
|
|
9210
|
+
addPattern(res, zodPatterns.email, check.message, refs);
|
|
9211
|
+
break;
|
|
9212
|
+
}
|
|
9213
|
+
break;
|
|
9214
|
+
case 'url':
|
|
9215
|
+
addFormat(res, 'uri', check.message, refs);
|
|
9216
|
+
break;
|
|
9217
|
+
case 'uuid':
|
|
9218
|
+
addFormat(res, 'uuid', check.message, refs);
|
|
9219
|
+
break;
|
|
9220
|
+
case 'regex':
|
|
9221
|
+
addPattern(res, check.regex, check.message, refs);
|
|
9222
|
+
break;
|
|
9223
|
+
case 'cuid':
|
|
9224
|
+
addPattern(res, zodPatterns.cuid, check.message, refs);
|
|
9225
|
+
break;
|
|
9226
|
+
case 'cuid2':
|
|
9227
|
+
addPattern(res, zodPatterns.cuid2, check.message, refs);
|
|
9228
|
+
break;
|
|
9229
|
+
case 'startsWith':
|
|
9230
|
+
addPattern(res, RegExp(`^${processPattern(check.value)}`), check.message, refs);
|
|
9231
|
+
break;
|
|
9232
|
+
case 'endsWith':
|
|
9233
|
+
addPattern(res, RegExp(`${processPattern(check.value)}$`), check.message, refs);
|
|
9234
|
+
break;
|
|
9235
|
+
case 'datetime':
|
|
9236
|
+
addFormat(res, 'date-time', check.message, refs);
|
|
9237
|
+
break;
|
|
9238
|
+
case 'date':
|
|
9239
|
+
addFormat(res, 'date', check.message, refs);
|
|
9240
|
+
break;
|
|
9241
|
+
case 'time':
|
|
9242
|
+
addFormat(res, 'time', check.message, refs);
|
|
9243
|
+
break;
|
|
9244
|
+
case 'duration':
|
|
9245
|
+
addFormat(res, 'duration', check.message, refs);
|
|
9246
|
+
break;
|
|
9247
|
+
case 'length':
|
|
9248
|
+
setResponseValueAndErrors(res, 'minLength', typeof res.minLength === 'number' ? Math.max(res.minLength, check.value) : check.value, check.message, refs);
|
|
9249
|
+
setResponseValueAndErrors(res, 'maxLength', typeof res.maxLength === 'number' ? Math.min(res.maxLength, check.value) : check.value, check.message, refs);
|
|
9250
|
+
break;
|
|
9251
|
+
case 'includes': {
|
|
9252
|
+
addPattern(res, RegExp(processPattern(check.value)), check.message, refs);
|
|
9253
|
+
break;
|
|
9254
|
+
}
|
|
9255
|
+
case 'ip': {
|
|
9256
|
+
if (check.version !== 'v6') {
|
|
9257
|
+
addFormat(res, 'ipv4', check.message, refs);
|
|
9258
|
+
}
|
|
9259
|
+
if (check.version !== 'v4') {
|
|
9260
|
+
addFormat(res, 'ipv6', check.message, refs);
|
|
9261
|
+
}
|
|
9262
|
+
break;
|
|
9263
|
+
}
|
|
9264
|
+
case 'emoji':
|
|
9265
|
+
addPattern(res, zodPatterns.emoji, check.message, refs);
|
|
9266
|
+
break;
|
|
9267
|
+
case 'ulid': {
|
|
9268
|
+
addPattern(res, zodPatterns.ulid, check.message, refs);
|
|
9269
|
+
break;
|
|
9270
|
+
}
|
|
9271
|
+
case 'base64': {
|
|
9272
|
+
switch (refs.base64Strategy) {
|
|
9273
|
+
case 'format:binary': {
|
|
9274
|
+
addFormat(res, 'binary', check.message, refs);
|
|
9275
|
+
break;
|
|
9276
|
+
}
|
|
9277
|
+
case 'contentEncoding:base64': {
|
|
9278
|
+
setResponseValueAndErrors(res, 'contentEncoding', 'base64', check.message, refs);
|
|
9279
|
+
break;
|
|
9280
|
+
}
|
|
9281
|
+
case 'pattern:zod': {
|
|
9282
|
+
addPattern(res, zodPatterns.base64, check.message, refs);
|
|
9283
|
+
break;
|
|
9284
|
+
}
|
|
9285
|
+
}
|
|
9286
|
+
break;
|
|
9287
|
+
}
|
|
9288
|
+
case 'nanoid': {
|
|
9289
|
+
addPattern(res, zodPatterns.nanoid, check.message, refs);
|
|
9290
|
+
}
|
|
9291
|
+
}
|
|
9292
|
+
}
|
|
9293
|
+
}
|
|
9294
|
+
return res;
|
|
9295
|
+
}
|
|
9296
|
+
const escapeNonAlphaNumeric = (value) => Array.from(value)
|
|
9297
|
+
.map((c) => (/[a-zA-Z0-9]/.test(c) ? c : `\\${c}`))
|
|
9298
|
+
.join('');
|
|
9299
|
+
const addFormat = (schema, value, message, refs) => {
|
|
9300
|
+
if (schema.format || schema.anyOf?.some((x) => x.format)) {
|
|
9301
|
+
if (!schema.anyOf) {
|
|
9302
|
+
schema.anyOf = [];
|
|
9303
|
+
}
|
|
9304
|
+
if (schema.format) {
|
|
9305
|
+
schema.anyOf.push({
|
|
9306
|
+
format: schema.format,
|
|
9307
|
+
...(schema.errorMessage &&
|
|
9308
|
+
refs.errorMessages && {
|
|
9309
|
+
errorMessage: { format: schema.errorMessage.format },
|
|
9310
|
+
}),
|
|
9311
|
+
});
|
|
9312
|
+
delete schema.format;
|
|
9313
|
+
if (schema.errorMessage) {
|
|
9314
|
+
delete schema.errorMessage.format;
|
|
9315
|
+
if (Object.keys(schema.errorMessage).length === 0) {
|
|
9316
|
+
delete schema.errorMessage;
|
|
9317
|
+
}
|
|
9318
|
+
}
|
|
9319
|
+
}
|
|
9320
|
+
schema.anyOf.push({
|
|
9321
|
+
format: value,
|
|
9322
|
+
...(message && refs.errorMessages && { errorMessage: { format: message } }),
|
|
9323
|
+
});
|
|
9324
|
+
}
|
|
9325
|
+
else {
|
|
9326
|
+
setResponseValueAndErrors(schema, 'format', value, message, refs);
|
|
9327
|
+
}
|
|
9328
|
+
};
|
|
9329
|
+
const addPattern = (schema, regex, message, refs) => {
|
|
9330
|
+
if (schema.pattern || schema.allOf?.some((x) => x.pattern)) {
|
|
9331
|
+
if (!schema.allOf) {
|
|
9332
|
+
schema.allOf = [];
|
|
9333
|
+
}
|
|
9334
|
+
if (schema.pattern) {
|
|
9335
|
+
schema.allOf.push({
|
|
9336
|
+
pattern: schema.pattern,
|
|
9337
|
+
...(schema.errorMessage &&
|
|
9338
|
+
refs.errorMessages && {
|
|
9339
|
+
errorMessage: { pattern: schema.errorMessage.pattern },
|
|
9340
|
+
}),
|
|
9341
|
+
});
|
|
9342
|
+
delete schema.pattern;
|
|
9343
|
+
if (schema.errorMessage) {
|
|
9344
|
+
delete schema.errorMessage.pattern;
|
|
9345
|
+
if (Object.keys(schema.errorMessage).length === 0) {
|
|
9346
|
+
delete schema.errorMessage;
|
|
9347
|
+
}
|
|
9348
|
+
}
|
|
9349
|
+
}
|
|
9350
|
+
schema.allOf.push({
|
|
9351
|
+
pattern: processRegExp(regex, refs),
|
|
9352
|
+
...(message && refs.errorMessages && { errorMessage: { pattern: message } }),
|
|
9353
|
+
});
|
|
9354
|
+
}
|
|
9355
|
+
else {
|
|
9356
|
+
setResponseValueAndErrors(schema, 'pattern', processRegExp(regex, refs), message, refs);
|
|
9357
|
+
}
|
|
9358
|
+
};
|
|
9359
|
+
// Mutate z.string.regex() in a best attempt to accommodate for regex flags when applyRegexFlags is true
|
|
9360
|
+
const processRegExp = (regexOrFunction, refs) => {
|
|
9361
|
+
const regex = typeof regexOrFunction === 'function' ? regexOrFunction() : regexOrFunction;
|
|
9362
|
+
if (!refs.applyRegexFlags || !regex.flags)
|
|
9363
|
+
return regex.source;
|
|
9364
|
+
// Currently handled flags
|
|
9365
|
+
const flags = {
|
|
9366
|
+
i: regex.flags.includes('i'), // Case-insensitive
|
|
9367
|
+
m: regex.flags.includes('m'), // `^` and `$` matches adjacent to newline characters
|
|
9368
|
+
s: regex.flags.includes('s'), // `.` matches newlines
|
|
9369
|
+
};
|
|
9370
|
+
// The general principle here is to step through each character, one at a time, applying mutations as flags require. We keep track when the current character is escaped, and when it's inside a group /like [this]/ or (also) a range like /[a-z]/. The following is fairly brittle imperative code; edit at your peril!
|
|
9371
|
+
const source = flags.i ? regex.source.toLowerCase() : regex.source;
|
|
9372
|
+
let pattern = '';
|
|
9373
|
+
let isEscaped = false;
|
|
9374
|
+
let inCharGroup = false;
|
|
9375
|
+
let inCharRange = false;
|
|
9376
|
+
for (let i = 0; i < source.length; i++) {
|
|
9377
|
+
if (isEscaped) {
|
|
9378
|
+
pattern += source[i];
|
|
9379
|
+
isEscaped = false;
|
|
9380
|
+
continue;
|
|
9381
|
+
}
|
|
9382
|
+
if (flags.i) {
|
|
9383
|
+
if (inCharGroup) {
|
|
9384
|
+
if (source[i].match(/[a-z]/)) {
|
|
9385
|
+
if (inCharRange) {
|
|
9386
|
+
pattern += source[i];
|
|
9387
|
+
pattern += `${source[i - 2]}-${source[i]}`.toUpperCase();
|
|
9388
|
+
inCharRange = false;
|
|
9389
|
+
}
|
|
9390
|
+
else if (source[i + 1] === '-' && source[i + 2]?.match(/[a-z]/)) {
|
|
9391
|
+
pattern += source[i];
|
|
9392
|
+
inCharRange = true;
|
|
9393
|
+
}
|
|
9394
|
+
else {
|
|
9395
|
+
pattern += `${source[i]}${source[i].toUpperCase()}`;
|
|
9396
|
+
}
|
|
9397
|
+
continue;
|
|
9398
|
+
}
|
|
9399
|
+
}
|
|
9400
|
+
else if (source[i].match(/[a-z]/)) {
|
|
9401
|
+
pattern += `[${source[i]}${source[i].toUpperCase()}]`;
|
|
9402
|
+
continue;
|
|
9403
|
+
}
|
|
9404
|
+
}
|
|
9405
|
+
if (flags.m) {
|
|
9406
|
+
if (source[i] === '^') {
|
|
9407
|
+
pattern += `(^|(?<=[\r\n]))`;
|
|
9408
|
+
continue;
|
|
9409
|
+
}
|
|
9410
|
+
else if (source[i] === '$') {
|
|
9411
|
+
pattern += `($|(?=[\r\n]))`;
|
|
9412
|
+
continue;
|
|
9413
|
+
}
|
|
9414
|
+
}
|
|
9415
|
+
if (flags.s && source[i] === '.') {
|
|
9416
|
+
pattern += inCharGroup ? `${source[i]}\r\n` : `[${source[i]}\r\n]`;
|
|
9417
|
+
continue;
|
|
9418
|
+
}
|
|
9419
|
+
pattern += source[i];
|
|
9420
|
+
if (source[i] === '\\') {
|
|
9421
|
+
isEscaped = true;
|
|
9422
|
+
}
|
|
9423
|
+
else if (inCharGroup && source[i] === ']') {
|
|
9424
|
+
inCharGroup = false;
|
|
9425
|
+
}
|
|
9426
|
+
else if (!inCharGroup && source[i] === '[') {
|
|
9427
|
+
inCharGroup = true;
|
|
9428
|
+
}
|
|
9429
|
+
}
|
|
9430
|
+
try {
|
|
9431
|
+
const regexTest = new RegExp(pattern);
|
|
9432
|
+
}
|
|
9433
|
+
catch {
|
|
9434
|
+
console.warn(`Could not convert regex pattern at ${refs.currentPath.join('/')} to a flag-independent form! Falling back to the flag-ignorant source`);
|
|
9435
|
+
return regex.source;
|
|
9436
|
+
}
|
|
9437
|
+
return pattern;
|
|
9438
|
+
};
|
|
9439
|
+
|
|
9440
|
+
function parseRecordDef(def, refs) {
|
|
9441
|
+
if (refs.target === 'openApi3' && def.keyType?._def.typeName === ZodFirstPartyTypeKind.ZodEnum) {
|
|
9442
|
+
return {
|
|
9443
|
+
type: 'object',
|
|
9444
|
+
required: def.keyType._def.values,
|
|
9445
|
+
properties: def.keyType._def.values.reduce((acc, key) => ({
|
|
9446
|
+
...acc,
|
|
9447
|
+
[key]: parseDef(def.valueType._def, {
|
|
9448
|
+
...refs,
|
|
9449
|
+
currentPath: [...refs.currentPath, 'properties', key],
|
|
9450
|
+
}) ?? {},
|
|
9451
|
+
}), {}),
|
|
9452
|
+
additionalProperties: false,
|
|
9453
|
+
};
|
|
9454
|
+
}
|
|
9455
|
+
const schema = {
|
|
9456
|
+
type: 'object',
|
|
9457
|
+
additionalProperties: parseDef(def.valueType._def, {
|
|
9458
|
+
...refs,
|
|
9459
|
+
currentPath: [...refs.currentPath, 'additionalProperties'],
|
|
9460
|
+
}) ?? {},
|
|
9461
|
+
};
|
|
9462
|
+
if (refs.target === 'openApi3') {
|
|
9463
|
+
return schema;
|
|
9464
|
+
}
|
|
9465
|
+
if (def.keyType?._def.typeName === ZodFirstPartyTypeKind.ZodString && def.keyType._def.checks?.length) {
|
|
9466
|
+
const keyType = Object.entries(parseStringDef(def.keyType._def, refs)).reduce((acc, [key, value]) => (key === 'type' ? acc : { ...acc, [key]: value }), {});
|
|
9467
|
+
return {
|
|
9468
|
+
...schema,
|
|
9469
|
+
propertyNames: keyType,
|
|
9470
|
+
};
|
|
9471
|
+
}
|
|
9472
|
+
else if (def.keyType?._def.typeName === ZodFirstPartyTypeKind.ZodEnum) {
|
|
9473
|
+
return {
|
|
9474
|
+
...schema,
|
|
9475
|
+
propertyNames: {
|
|
9476
|
+
enum: def.keyType._def.values,
|
|
9477
|
+
},
|
|
9478
|
+
};
|
|
9479
|
+
}
|
|
9480
|
+
return schema;
|
|
9481
|
+
}
|
|
9482
|
+
|
|
9483
|
+
function parseMapDef(def, refs) {
|
|
9484
|
+
if (refs.mapStrategy === 'record') {
|
|
9485
|
+
return parseRecordDef(def, refs);
|
|
9486
|
+
}
|
|
9487
|
+
const keys = parseDef(def.keyType._def, {
|
|
9488
|
+
...refs,
|
|
9489
|
+
currentPath: [...refs.currentPath, 'items', 'items', '0'],
|
|
9490
|
+
}) || {};
|
|
9491
|
+
const values = parseDef(def.valueType._def, {
|
|
9492
|
+
...refs,
|
|
9493
|
+
currentPath: [...refs.currentPath, 'items', 'items', '1'],
|
|
9494
|
+
}) || {};
|
|
9495
|
+
return {
|
|
9496
|
+
type: 'array',
|
|
9497
|
+
maxItems: 125,
|
|
9498
|
+
items: {
|
|
9499
|
+
type: 'array',
|
|
9500
|
+
items: [keys, values],
|
|
9501
|
+
minItems: 2,
|
|
9502
|
+
maxItems: 2,
|
|
9503
|
+
},
|
|
9504
|
+
};
|
|
9505
|
+
}
|
|
9506
|
+
|
|
9507
|
+
function parseNativeEnumDef(def) {
|
|
9508
|
+
const object = def.values;
|
|
9509
|
+
const actualKeys = Object.keys(def.values).filter((key) => {
|
|
9510
|
+
return typeof object[object[key]] !== 'number';
|
|
9511
|
+
});
|
|
9512
|
+
const actualValues = actualKeys.map((key) => object[key]);
|
|
9513
|
+
const parsedTypes = Array.from(new Set(actualValues.map((values) => typeof values)));
|
|
9514
|
+
return {
|
|
9515
|
+
type: parsedTypes.length === 1 ?
|
|
9516
|
+
parsedTypes[0] === 'string' ?
|
|
9517
|
+
'string'
|
|
9518
|
+
: 'number'
|
|
9519
|
+
: ['string', 'number'],
|
|
9520
|
+
enum: actualValues,
|
|
9521
|
+
};
|
|
9522
|
+
}
|
|
9523
|
+
|
|
9524
|
+
function parseNeverDef() {
|
|
9525
|
+
return {
|
|
9526
|
+
not: {},
|
|
9527
|
+
};
|
|
9528
|
+
}
|
|
9529
|
+
|
|
9530
|
+
function parseNullDef(refs) {
|
|
9531
|
+
return refs.target === 'openApi3' ?
|
|
9532
|
+
{
|
|
9533
|
+
enum: ['null'],
|
|
9534
|
+
nullable: true,
|
|
9535
|
+
}
|
|
9536
|
+
: {
|
|
9537
|
+
type: 'null',
|
|
9538
|
+
};
|
|
9539
|
+
}
|
|
9540
|
+
|
|
9541
|
+
const primitiveMappings = {
|
|
9542
|
+
ZodString: 'string',
|
|
9543
|
+
ZodNumber: 'number',
|
|
9544
|
+
ZodBigInt: 'integer',
|
|
9545
|
+
ZodBoolean: 'boolean',
|
|
9546
|
+
ZodNull: 'null',
|
|
9547
|
+
};
|
|
9548
|
+
function parseUnionDef(def, refs) {
|
|
9549
|
+
if (refs.target === 'openApi3')
|
|
9550
|
+
return asAnyOf(def, refs);
|
|
9551
|
+
const options = def.options instanceof Map ? Array.from(def.options.values()) : def.options;
|
|
9552
|
+
// This blocks tries to look ahead a bit to produce nicer looking schemas with type array instead of anyOf.
|
|
9553
|
+
if (options.every((x) => x._def.typeName in primitiveMappings && (!x._def.checks || !x._def.checks.length))) {
|
|
9554
|
+
// all types in union are primitive and lack checks, so might as well squash into {type: [...]}
|
|
9555
|
+
const types = options.reduce((types, x) => {
|
|
9556
|
+
const type = primitiveMappings[x._def.typeName]; //Can be safely casted due to row 43
|
|
9557
|
+
return type && !types.includes(type) ? [...types, type] : types;
|
|
9558
|
+
}, []);
|
|
9559
|
+
return {
|
|
9560
|
+
type: types.length > 1 ? types : types[0],
|
|
9561
|
+
};
|
|
9562
|
+
}
|
|
9563
|
+
else if (options.every((x) => x._def.typeName === 'ZodLiteral' && !x.description)) {
|
|
9564
|
+
// all options literals
|
|
9565
|
+
const types = options.reduce((acc, x) => {
|
|
9566
|
+
const type = typeof x._def.value;
|
|
9567
|
+
switch (type) {
|
|
9568
|
+
case 'string':
|
|
9569
|
+
case 'number':
|
|
9570
|
+
case 'boolean':
|
|
9571
|
+
return [...acc, type];
|
|
9572
|
+
case 'bigint':
|
|
9573
|
+
return [...acc, 'integer'];
|
|
9574
|
+
case 'object':
|
|
9575
|
+
if (x._def.value === null)
|
|
9576
|
+
return [...acc, 'null'];
|
|
9577
|
+
case 'symbol':
|
|
9578
|
+
case 'undefined':
|
|
9579
|
+
case 'function':
|
|
9580
|
+
default:
|
|
9581
|
+
return acc;
|
|
9582
|
+
}
|
|
9583
|
+
}, []);
|
|
9584
|
+
if (types.length === options.length) {
|
|
9585
|
+
// all the literals are primitive, as far as null can be considered primitive
|
|
9586
|
+
const uniqueTypes = types.filter((x, i, a) => a.indexOf(x) === i);
|
|
9587
|
+
return {
|
|
9588
|
+
type: uniqueTypes.length > 1 ? uniqueTypes : uniqueTypes[0],
|
|
9589
|
+
enum: options.reduce((acc, x) => {
|
|
9590
|
+
return acc.includes(x._def.value) ? acc : [...acc, x._def.value];
|
|
9591
|
+
}, []),
|
|
9592
|
+
};
|
|
9593
|
+
}
|
|
9594
|
+
}
|
|
9595
|
+
else if (options.every((x) => x._def.typeName === 'ZodEnum')) {
|
|
9596
|
+
return {
|
|
9597
|
+
type: 'string',
|
|
9598
|
+
enum: options.reduce((acc, x) => [...acc, ...x._def.values.filter((x) => !acc.includes(x))], []),
|
|
9599
|
+
};
|
|
9600
|
+
}
|
|
9601
|
+
return asAnyOf(def, refs);
|
|
9602
|
+
}
|
|
9603
|
+
const asAnyOf = (def, refs) => {
|
|
9604
|
+
const anyOf = (def.options instanceof Map ? Array.from(def.options.values()) : def.options)
|
|
9605
|
+
.map((x, i) => parseDef(x._def, {
|
|
9606
|
+
...refs,
|
|
9607
|
+
currentPath: [...refs.currentPath, 'anyOf', `${i}`],
|
|
9608
|
+
}))
|
|
9609
|
+
.filter((x) => !!x && (!refs.strictUnions || (typeof x === 'object' && Object.keys(x).length > 0)));
|
|
9610
|
+
return anyOf.length ? { anyOf } : undefined;
|
|
9611
|
+
};
|
|
9612
|
+
|
|
9613
|
+
function parseNullableDef(def, refs) {
|
|
9614
|
+
if (['ZodString', 'ZodNumber', 'ZodBigInt', 'ZodBoolean', 'ZodNull'].includes(def.innerType._def.typeName) &&
|
|
9615
|
+
(!def.innerType._def.checks || !def.innerType._def.checks.length)) {
|
|
9616
|
+
if (refs.target === 'openApi3' || refs.nullableStrategy === 'property') {
|
|
9617
|
+
return {
|
|
9618
|
+
type: primitiveMappings[def.innerType._def.typeName],
|
|
9619
|
+
nullable: true,
|
|
9620
|
+
};
|
|
9621
|
+
}
|
|
9622
|
+
return {
|
|
9623
|
+
type: [primitiveMappings[def.innerType._def.typeName], 'null'],
|
|
9624
|
+
};
|
|
9625
|
+
}
|
|
9626
|
+
if (refs.target === 'openApi3') {
|
|
9627
|
+
const base = parseDef(def.innerType._def, {
|
|
9628
|
+
...refs,
|
|
9629
|
+
currentPath: [...refs.currentPath],
|
|
9630
|
+
});
|
|
9631
|
+
if (base && '$ref' in base)
|
|
9632
|
+
return { allOf: [base], nullable: true };
|
|
9633
|
+
return base && { ...base, nullable: true };
|
|
9634
|
+
}
|
|
9635
|
+
const base = parseDef(def.innerType._def, {
|
|
9636
|
+
...refs,
|
|
9637
|
+
currentPath: [...refs.currentPath, 'anyOf', '0'],
|
|
9638
|
+
});
|
|
9639
|
+
return base && { anyOf: [base, { type: 'null' }] };
|
|
9640
|
+
}
|
|
9641
|
+
|
|
9642
|
+
function parseNumberDef(def, refs) {
|
|
9643
|
+
const res = {
|
|
9644
|
+
type: 'number',
|
|
9645
|
+
};
|
|
9646
|
+
if (!def.checks)
|
|
9647
|
+
return res;
|
|
9648
|
+
for (const check of def.checks) {
|
|
9649
|
+
switch (check.kind) {
|
|
9650
|
+
case 'int':
|
|
9651
|
+
res.type = 'integer';
|
|
9652
|
+
addErrorMessage(res, 'type', check.message, refs);
|
|
9653
|
+
break;
|
|
9654
|
+
case 'min':
|
|
9655
|
+
if (refs.target === 'jsonSchema7') {
|
|
9656
|
+
if (check.inclusive) {
|
|
9657
|
+
setResponseValueAndErrors(res, 'minimum', check.value, check.message, refs);
|
|
9658
|
+
}
|
|
9659
|
+
else {
|
|
9660
|
+
setResponseValueAndErrors(res, 'exclusiveMinimum', check.value, check.message, refs);
|
|
9661
|
+
}
|
|
9662
|
+
}
|
|
9663
|
+
else {
|
|
9664
|
+
if (!check.inclusive) {
|
|
9665
|
+
res.exclusiveMinimum = true;
|
|
9666
|
+
}
|
|
9667
|
+
setResponseValueAndErrors(res, 'minimum', check.value, check.message, refs);
|
|
9668
|
+
}
|
|
9669
|
+
break;
|
|
9670
|
+
case 'max':
|
|
9671
|
+
if (refs.target === 'jsonSchema7') {
|
|
9672
|
+
if (check.inclusive) {
|
|
9673
|
+
setResponseValueAndErrors(res, 'maximum', check.value, check.message, refs);
|
|
9674
|
+
}
|
|
9675
|
+
else {
|
|
9676
|
+
setResponseValueAndErrors(res, 'exclusiveMaximum', check.value, check.message, refs);
|
|
9677
|
+
}
|
|
9678
|
+
}
|
|
9679
|
+
else {
|
|
9680
|
+
if (!check.inclusive) {
|
|
9681
|
+
res.exclusiveMaximum = true;
|
|
9682
|
+
}
|
|
9683
|
+
setResponseValueAndErrors(res, 'maximum', check.value, check.message, refs);
|
|
9684
|
+
}
|
|
9685
|
+
break;
|
|
9686
|
+
case 'multipleOf':
|
|
9687
|
+
setResponseValueAndErrors(res, 'multipleOf', check.value, check.message, refs);
|
|
9688
|
+
break;
|
|
9689
|
+
}
|
|
9690
|
+
}
|
|
9691
|
+
return res;
|
|
9692
|
+
}
|
|
9693
|
+
|
|
9694
|
+
function decideAdditionalProperties(def, refs) {
|
|
9695
|
+
if (refs.removeAdditionalStrategy === 'strict') {
|
|
9696
|
+
return def.catchall._def.typeName === 'ZodNever' ?
|
|
9697
|
+
def.unknownKeys !== 'strict'
|
|
9698
|
+
: parseDef(def.catchall._def, {
|
|
9699
|
+
...refs,
|
|
9700
|
+
currentPath: [...refs.currentPath, 'additionalProperties'],
|
|
9701
|
+
}) ?? true;
|
|
9702
|
+
}
|
|
9703
|
+
else {
|
|
9704
|
+
return def.catchall._def.typeName === 'ZodNever' ?
|
|
9705
|
+
def.unknownKeys === 'passthrough'
|
|
9706
|
+
: parseDef(def.catchall._def, {
|
|
9707
|
+
...refs,
|
|
9708
|
+
currentPath: [...refs.currentPath, 'additionalProperties'],
|
|
9709
|
+
}) ?? true;
|
|
9710
|
+
}
|
|
9711
|
+
}
|
|
9712
|
+
function parseObjectDef(def, refs) {
|
|
9713
|
+
const result = {
|
|
9714
|
+
type: 'object',
|
|
9715
|
+
...Object.entries(def.shape()).reduce((acc, [propName, propDef]) => {
|
|
9716
|
+
if (propDef === undefined || propDef._def === undefined)
|
|
9717
|
+
return acc;
|
|
9718
|
+
const propertyPath = [...refs.currentPath, 'properties', propName];
|
|
9719
|
+
const parsedDef = parseDef(propDef._def, {
|
|
9720
|
+
...refs,
|
|
9721
|
+
currentPath: propertyPath,
|
|
9722
|
+
propertyPath,
|
|
9723
|
+
});
|
|
9724
|
+
if (parsedDef === undefined)
|
|
9725
|
+
return acc;
|
|
9726
|
+
if (refs.openaiStrictMode &&
|
|
9727
|
+
propDef.isOptional() &&
|
|
9728
|
+
!propDef.isNullable() &&
|
|
9729
|
+
typeof propDef._def?.defaultValue === 'undefined') {
|
|
9730
|
+
throw new Error(`Zod field at \`${propertyPath.join('/')}\` uses \`.optional()\` without \`.nullable()\` which is not supported by the API. See: https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#all-fields-must-be-required`);
|
|
9731
|
+
}
|
|
9732
|
+
return {
|
|
9733
|
+
properties: {
|
|
9734
|
+
...acc.properties,
|
|
9735
|
+
[propName]: parsedDef,
|
|
9736
|
+
},
|
|
9737
|
+
required: propDef.isOptional() && !refs.openaiStrictMode ? acc.required : [...acc.required, propName],
|
|
9738
|
+
};
|
|
9739
|
+
}, { properties: {}, required: [] }),
|
|
9740
|
+
additionalProperties: decideAdditionalProperties(def, refs),
|
|
9741
|
+
};
|
|
9742
|
+
if (!result.required.length)
|
|
9743
|
+
delete result.required;
|
|
9744
|
+
return result;
|
|
9745
|
+
}
|
|
9746
|
+
|
|
9747
|
+
const parseOptionalDef = (def, refs) => {
|
|
9748
|
+
if (refs.propertyPath &&
|
|
9749
|
+
refs.currentPath.slice(0, refs.propertyPath.length).toString() === refs.propertyPath.toString()) {
|
|
9750
|
+
return parseDef(def.innerType._def, { ...refs, currentPath: refs.currentPath });
|
|
9751
|
+
}
|
|
9752
|
+
const innerSchema = parseDef(def.innerType._def, {
|
|
9753
|
+
...refs,
|
|
9754
|
+
currentPath: [...refs.currentPath, 'anyOf', '1'],
|
|
9755
|
+
});
|
|
9756
|
+
return innerSchema ?
|
|
9757
|
+
{
|
|
9758
|
+
anyOf: [
|
|
9759
|
+
{
|
|
9760
|
+
not: {},
|
|
9761
|
+
},
|
|
9762
|
+
innerSchema,
|
|
9763
|
+
],
|
|
9764
|
+
}
|
|
9765
|
+
: {};
|
|
9766
|
+
};
|
|
9767
|
+
|
|
9768
|
+
const parsePipelineDef = (def, refs) => {
|
|
9769
|
+
if (refs.pipeStrategy === 'input') {
|
|
9770
|
+
return parseDef(def.in._def, refs);
|
|
9771
|
+
}
|
|
9772
|
+
else if (refs.pipeStrategy === 'output') {
|
|
9773
|
+
return parseDef(def.out._def, refs);
|
|
9774
|
+
}
|
|
9775
|
+
const a = parseDef(def.in._def, {
|
|
9776
|
+
...refs,
|
|
9777
|
+
currentPath: [...refs.currentPath, 'allOf', '0'],
|
|
9778
|
+
});
|
|
9779
|
+
const b = parseDef(def.out._def, {
|
|
9780
|
+
...refs,
|
|
9781
|
+
currentPath: [...refs.currentPath, 'allOf', a ? '1' : '0'],
|
|
9782
|
+
});
|
|
9783
|
+
return {
|
|
9784
|
+
allOf: [a, b].filter((x) => x !== undefined),
|
|
9785
|
+
};
|
|
9786
|
+
};
|
|
9787
|
+
|
|
9788
|
+
function parsePromiseDef(def, refs) {
|
|
9789
|
+
return parseDef(def.type._def, refs);
|
|
9790
|
+
}
|
|
9791
|
+
|
|
9792
|
+
function parseSetDef(def, refs) {
|
|
9793
|
+
const items = parseDef(def.valueType._def, {
|
|
9794
|
+
...refs,
|
|
9795
|
+
currentPath: [...refs.currentPath, 'items'],
|
|
9796
|
+
});
|
|
9797
|
+
const schema = {
|
|
9798
|
+
type: 'array',
|
|
9799
|
+
uniqueItems: true,
|
|
9800
|
+
items,
|
|
9801
|
+
};
|
|
9802
|
+
if (def.minSize) {
|
|
9803
|
+
setResponseValueAndErrors(schema, 'minItems', def.minSize.value, def.minSize.message, refs);
|
|
9804
|
+
}
|
|
9805
|
+
if (def.maxSize) {
|
|
9806
|
+
setResponseValueAndErrors(schema, 'maxItems', def.maxSize.value, def.maxSize.message, refs);
|
|
9807
|
+
}
|
|
9808
|
+
return schema;
|
|
9809
|
+
}
|
|
9810
|
+
|
|
9811
|
+
function parseTupleDef(def, refs) {
|
|
9812
|
+
if (def.rest) {
|
|
9813
|
+
return {
|
|
9814
|
+
type: 'array',
|
|
9815
|
+
minItems: def.items.length,
|
|
9816
|
+
items: def.items
|
|
9817
|
+
.map((x, i) => parseDef(x._def, {
|
|
9818
|
+
...refs,
|
|
9819
|
+
currentPath: [...refs.currentPath, 'items', `${i}`],
|
|
9820
|
+
}))
|
|
9821
|
+
.reduce((acc, x) => (x === undefined ? acc : [...acc, x]), []),
|
|
9822
|
+
additionalItems: parseDef(def.rest._def, {
|
|
9823
|
+
...refs,
|
|
9824
|
+
currentPath: [...refs.currentPath, 'additionalItems'],
|
|
9825
|
+
}),
|
|
9826
|
+
};
|
|
9827
|
+
}
|
|
9828
|
+
else {
|
|
9829
|
+
return {
|
|
9830
|
+
type: 'array',
|
|
9831
|
+
minItems: def.items.length,
|
|
9832
|
+
maxItems: def.items.length,
|
|
9833
|
+
items: def.items
|
|
9834
|
+
.map((x, i) => parseDef(x._def, {
|
|
9835
|
+
...refs,
|
|
9836
|
+
currentPath: [...refs.currentPath, 'items', `${i}`],
|
|
9837
|
+
}))
|
|
9838
|
+
.reduce((acc, x) => (x === undefined ? acc : [...acc, x]), []),
|
|
9839
|
+
};
|
|
9840
|
+
}
|
|
9841
|
+
}
|
|
9842
|
+
|
|
9843
|
+
function parseUndefinedDef() {
|
|
9844
|
+
return {
|
|
9845
|
+
not: {},
|
|
9846
|
+
};
|
|
9847
|
+
}
|
|
9848
|
+
|
|
9849
|
+
function parseUnknownDef() {
|
|
9850
|
+
return {};
|
|
9851
|
+
}
|
|
9852
|
+
|
|
9853
|
+
const parseReadonlyDef = (def, refs) => {
|
|
9854
|
+
return parseDef(def.innerType._def, refs);
|
|
9855
|
+
};
|
|
9856
|
+
|
|
9857
|
+
function parseDef(def, refs, forceResolution = false) {
|
|
9858
|
+
const seenItem = refs.seen.get(def);
|
|
9859
|
+
if (refs.override) {
|
|
9860
|
+
const overrideResult = refs.override?.(def, refs, seenItem, forceResolution);
|
|
9861
|
+
if (overrideResult !== ignoreOverride) {
|
|
9862
|
+
return overrideResult;
|
|
9863
|
+
}
|
|
9864
|
+
}
|
|
9865
|
+
if (seenItem && !forceResolution) {
|
|
9866
|
+
const seenSchema = get$ref(seenItem, refs);
|
|
9867
|
+
if (seenSchema !== undefined) {
|
|
9868
|
+
if ('$ref' in seenSchema) {
|
|
9869
|
+
refs.seenRefs.add(seenSchema.$ref);
|
|
9870
|
+
}
|
|
9871
|
+
return seenSchema;
|
|
9872
|
+
}
|
|
9873
|
+
}
|
|
9874
|
+
const newItem = { def, path: refs.currentPath, jsonSchema: undefined };
|
|
9875
|
+
refs.seen.set(def, newItem);
|
|
9876
|
+
const jsonSchema = selectParser(def, def.typeName, refs, forceResolution);
|
|
9877
|
+
if (jsonSchema) {
|
|
9878
|
+
addMeta(def, refs, jsonSchema);
|
|
9879
|
+
}
|
|
9880
|
+
newItem.jsonSchema = jsonSchema;
|
|
9881
|
+
return jsonSchema;
|
|
9882
|
+
}
|
|
9883
|
+
const get$ref = (item, refs) => {
|
|
9884
|
+
switch (refs.$refStrategy) {
|
|
9885
|
+
case 'root':
|
|
9886
|
+
return { $ref: item.path.join('/') };
|
|
9887
|
+
// this case is needed as OpenAI strict mode doesn't support top-level `$ref`s, i.e.
|
|
9888
|
+
// the top-level schema *must* be `{"type": "object", "properties": {...}}` but if we ever
|
|
9889
|
+
// need to define a `$ref`, relative `$ref`s aren't supported, so we need to extract
|
|
9890
|
+
// the schema to `#/definitions/` and reference that.
|
|
9891
|
+
//
|
|
9892
|
+
// e.g. if we need to reference a schema at
|
|
9893
|
+
// `["#","definitions","contactPerson","properties","person1","properties","name"]`
|
|
9894
|
+
// then we'll extract it out to `contactPerson_properties_person1_properties_name`
|
|
9895
|
+
case 'extract-to-root':
|
|
9896
|
+
const name = item.path.slice(refs.basePath.length + 1).join('_');
|
|
9897
|
+
// we don't need to extract the root schema in this case, as it's already
|
|
9898
|
+
// been added to the definitions
|
|
9899
|
+
if (name !== refs.name && refs.nameStrategy === 'duplicate-ref') {
|
|
9900
|
+
refs.definitions[name] = item.def;
|
|
9901
|
+
}
|
|
9902
|
+
return { $ref: [...refs.basePath, refs.definitionPath, name].join('/') };
|
|
9903
|
+
case 'relative':
|
|
9904
|
+
return { $ref: getRelativePath(refs.currentPath, item.path) };
|
|
9905
|
+
case 'none':
|
|
9906
|
+
case 'seen': {
|
|
9907
|
+
if (item.path.length < refs.currentPath.length &&
|
|
9908
|
+
item.path.every((value, index) => refs.currentPath[index] === value)) {
|
|
9909
|
+
console.warn(`Recursive reference detected at ${refs.currentPath.join('/')}! Defaulting to any`);
|
|
9910
|
+
return {};
|
|
9911
|
+
}
|
|
9912
|
+
return refs.$refStrategy === 'seen' ? {} : undefined;
|
|
9913
|
+
}
|
|
9914
|
+
}
|
|
9915
|
+
};
|
|
9916
|
+
const getRelativePath = (pathA, pathB) => {
|
|
9917
|
+
let i = 0;
|
|
9918
|
+
for (; i < pathA.length && i < pathB.length; i++) {
|
|
9919
|
+
if (pathA[i] !== pathB[i])
|
|
9920
|
+
break;
|
|
9921
|
+
}
|
|
9922
|
+
return [(pathA.length - i).toString(), ...pathB.slice(i)].join('/');
|
|
9923
|
+
};
|
|
9924
|
+
const selectParser = (def, typeName, refs, forceResolution) => {
|
|
9925
|
+
switch (typeName) {
|
|
9926
|
+
case ZodFirstPartyTypeKind.ZodString:
|
|
9927
|
+
return parseStringDef(def, refs);
|
|
9928
|
+
case ZodFirstPartyTypeKind.ZodNumber:
|
|
9929
|
+
return parseNumberDef(def, refs);
|
|
9930
|
+
case ZodFirstPartyTypeKind.ZodObject:
|
|
9931
|
+
return parseObjectDef(def, refs);
|
|
9932
|
+
case ZodFirstPartyTypeKind.ZodBigInt:
|
|
9933
|
+
return parseBigintDef(def, refs);
|
|
9934
|
+
case ZodFirstPartyTypeKind.ZodBoolean:
|
|
9935
|
+
return parseBooleanDef();
|
|
9936
|
+
case ZodFirstPartyTypeKind.ZodDate:
|
|
9937
|
+
return parseDateDef(def, refs);
|
|
9938
|
+
case ZodFirstPartyTypeKind.ZodUndefined:
|
|
9939
|
+
return parseUndefinedDef();
|
|
9940
|
+
case ZodFirstPartyTypeKind.ZodNull:
|
|
9941
|
+
return parseNullDef(refs);
|
|
9942
|
+
case ZodFirstPartyTypeKind.ZodArray:
|
|
9943
|
+
return parseArrayDef(def, refs);
|
|
9944
|
+
case ZodFirstPartyTypeKind.ZodUnion:
|
|
9945
|
+
case ZodFirstPartyTypeKind.ZodDiscriminatedUnion:
|
|
9946
|
+
return parseUnionDef(def, refs);
|
|
9947
|
+
case ZodFirstPartyTypeKind.ZodIntersection:
|
|
9948
|
+
return parseIntersectionDef(def, refs);
|
|
9949
|
+
case ZodFirstPartyTypeKind.ZodTuple:
|
|
9950
|
+
return parseTupleDef(def, refs);
|
|
9951
|
+
case ZodFirstPartyTypeKind.ZodRecord:
|
|
9952
|
+
return parseRecordDef(def, refs);
|
|
9953
|
+
case ZodFirstPartyTypeKind.ZodLiteral:
|
|
9954
|
+
return parseLiteralDef(def, refs);
|
|
9955
|
+
case ZodFirstPartyTypeKind.ZodEnum:
|
|
9956
|
+
return parseEnumDef(def);
|
|
9957
|
+
case ZodFirstPartyTypeKind.ZodNativeEnum:
|
|
9958
|
+
return parseNativeEnumDef(def);
|
|
9959
|
+
case ZodFirstPartyTypeKind.ZodNullable:
|
|
9960
|
+
return parseNullableDef(def, refs);
|
|
9961
|
+
case ZodFirstPartyTypeKind.ZodOptional:
|
|
9962
|
+
return parseOptionalDef(def, refs);
|
|
9963
|
+
case ZodFirstPartyTypeKind.ZodMap:
|
|
9964
|
+
return parseMapDef(def, refs);
|
|
9965
|
+
case ZodFirstPartyTypeKind.ZodSet:
|
|
9966
|
+
return parseSetDef(def, refs);
|
|
9967
|
+
case ZodFirstPartyTypeKind.ZodLazy:
|
|
9968
|
+
return parseDef(def.getter()._def, refs);
|
|
9969
|
+
case ZodFirstPartyTypeKind.ZodPromise:
|
|
9970
|
+
return parsePromiseDef(def, refs);
|
|
9971
|
+
case ZodFirstPartyTypeKind.ZodNaN:
|
|
9972
|
+
case ZodFirstPartyTypeKind.ZodNever:
|
|
9973
|
+
return parseNeverDef();
|
|
9974
|
+
case ZodFirstPartyTypeKind.ZodEffects:
|
|
9975
|
+
return parseEffectsDef(def, refs, forceResolution);
|
|
9976
|
+
case ZodFirstPartyTypeKind.ZodAny:
|
|
9977
|
+
return parseAnyDef();
|
|
9978
|
+
case ZodFirstPartyTypeKind.ZodUnknown:
|
|
9979
|
+
return parseUnknownDef();
|
|
9980
|
+
case ZodFirstPartyTypeKind.ZodDefault:
|
|
9981
|
+
return parseDefaultDef(def, refs);
|
|
9982
|
+
case ZodFirstPartyTypeKind.ZodBranded:
|
|
9983
|
+
return parseBrandedDef(def, refs);
|
|
9984
|
+
case ZodFirstPartyTypeKind.ZodReadonly:
|
|
9985
|
+
return parseReadonlyDef(def, refs);
|
|
9986
|
+
case ZodFirstPartyTypeKind.ZodCatch:
|
|
9987
|
+
return parseCatchDef(def, refs);
|
|
9988
|
+
case ZodFirstPartyTypeKind.ZodPipeline:
|
|
9989
|
+
return parsePipelineDef(def, refs);
|
|
9990
|
+
case ZodFirstPartyTypeKind.ZodFunction:
|
|
9991
|
+
case ZodFirstPartyTypeKind.ZodVoid:
|
|
9992
|
+
case ZodFirstPartyTypeKind.ZodSymbol:
|
|
9993
|
+
return undefined;
|
|
9994
|
+
default:
|
|
9995
|
+
return ((_) => undefined)();
|
|
9996
|
+
}
|
|
9997
|
+
};
|
|
9998
|
+
const addMeta = (def, refs, jsonSchema) => {
|
|
9999
|
+
if (def.description) {
|
|
10000
|
+
jsonSchema.description = def.description;
|
|
10001
|
+
if (refs.markdownDescription) {
|
|
10002
|
+
jsonSchema.markdownDescription = def.description;
|
|
10003
|
+
}
|
|
10004
|
+
}
|
|
10005
|
+
return jsonSchema;
|
|
10006
|
+
};
|
|
10007
|
+
|
|
10008
|
+
const zodToJsonSchema = (schema, options) => {
|
|
10009
|
+
const refs = getRefs(options);
|
|
10010
|
+
const name = typeof options === 'string' ? options
|
|
10011
|
+
: options?.nameStrategy === 'title' ? undefined
|
|
10012
|
+
: options?.name;
|
|
10013
|
+
const main = parseDef(schema._def, name === undefined ? refs : ({
|
|
10014
|
+
...refs,
|
|
10015
|
+
currentPath: [...refs.basePath, refs.definitionPath, name],
|
|
10016
|
+
}), false) ?? {};
|
|
10017
|
+
const title = typeof options === 'object' && options.name !== undefined && options.nameStrategy === 'title' ?
|
|
10018
|
+
options.name
|
|
10019
|
+
: undefined;
|
|
10020
|
+
if (title !== undefined) {
|
|
10021
|
+
main.title = title;
|
|
10022
|
+
}
|
|
10023
|
+
const definitions = (() => {
|
|
10024
|
+
if (isEmptyObj(refs.definitions)) {
|
|
10025
|
+
return undefined;
|
|
10026
|
+
}
|
|
10027
|
+
const definitions = {};
|
|
10028
|
+
const processedDefinitions = new Set();
|
|
10029
|
+
// the call to `parseDef()` here might itself add more entries to `.definitions`
|
|
10030
|
+
// so we need to continually evaluate definitions until we've resolved all of them
|
|
10031
|
+
//
|
|
10032
|
+
// we have a generous iteration limit here to avoid blowing up the stack if there
|
|
10033
|
+
// are any bugs that would otherwise result in us iterating indefinitely
|
|
10034
|
+
for (let i = 0; i < 500; i++) {
|
|
10035
|
+
const newDefinitions = Object.entries(refs.definitions).filter(([key]) => !processedDefinitions.has(key));
|
|
10036
|
+
if (newDefinitions.length === 0)
|
|
10037
|
+
break;
|
|
10038
|
+
for (const [key, schema] of newDefinitions) {
|
|
10039
|
+
definitions[key] =
|
|
10040
|
+
parseDef(zodDef(schema), { ...refs, currentPath: [...refs.basePath, refs.definitionPath, key] }, true) ?? {};
|
|
10041
|
+
processedDefinitions.add(key);
|
|
10042
|
+
}
|
|
10043
|
+
}
|
|
10044
|
+
return definitions;
|
|
10045
|
+
})();
|
|
10046
|
+
const combined = name === undefined ?
|
|
10047
|
+
definitions ?
|
|
10048
|
+
{
|
|
10049
|
+
...main,
|
|
10050
|
+
[refs.definitionPath]: definitions,
|
|
10051
|
+
}
|
|
10052
|
+
: main
|
|
10053
|
+
: refs.nameStrategy === 'duplicate-ref' ?
|
|
10054
|
+
{
|
|
10055
|
+
...main,
|
|
10056
|
+
...(definitions || refs.seenRefs.size ?
|
|
10057
|
+
{
|
|
10058
|
+
[refs.definitionPath]: {
|
|
10059
|
+
...definitions,
|
|
10060
|
+
// only actually duplicate the schema definition if it was ever referenced
|
|
10061
|
+
// otherwise the duplication is completely pointless
|
|
10062
|
+
...(refs.seenRefs.size ? { [name]: main } : undefined),
|
|
10063
|
+
},
|
|
10064
|
+
}
|
|
10065
|
+
: undefined),
|
|
10066
|
+
}
|
|
10067
|
+
: {
|
|
10068
|
+
$ref: [...(refs.$refStrategy === 'relative' ? [] : refs.basePath), refs.definitionPath, name].join('/'),
|
|
10069
|
+
[refs.definitionPath]: {
|
|
10070
|
+
...definitions,
|
|
10071
|
+
[name]: main,
|
|
10072
|
+
},
|
|
10073
|
+
};
|
|
10074
|
+
if (refs.target === 'jsonSchema7') {
|
|
10075
|
+
combined.$schema = 'http://json-schema.org/draft-07/schema#';
|
|
10076
|
+
}
|
|
10077
|
+
else if (refs.target === 'jsonSchema2019-09') {
|
|
10078
|
+
combined.$schema = 'https://json-schema.org/draft/2019-09/schema#';
|
|
10079
|
+
}
|
|
10080
|
+
return combined;
|
|
10081
|
+
};
|
|
10082
|
+
|
|
10083
|
+
function toStrictJsonSchema(schema) {
|
|
10084
|
+
if (schema.type !== 'object') {
|
|
10085
|
+
throw new Error(`Root schema must have type: 'object' but got type: ${schema.type ? `'${schema.type}'` : 'undefined'}`);
|
|
10086
|
+
}
|
|
10087
|
+
const schemaCopy = structuredClone(schema);
|
|
10088
|
+
return ensureStrictJsonSchema(schemaCopy, [], schemaCopy);
|
|
10089
|
+
}
|
|
10090
|
+
function isNullable(schema) {
|
|
10091
|
+
if (typeof schema === 'boolean') {
|
|
10092
|
+
return false;
|
|
10093
|
+
}
|
|
10094
|
+
if (schema.type === 'null') {
|
|
10095
|
+
return true;
|
|
10096
|
+
}
|
|
10097
|
+
for (const oneOfVariant of schema.oneOf ?? []) {
|
|
10098
|
+
if (isNullable(oneOfVariant)) {
|
|
10099
|
+
return true;
|
|
10100
|
+
}
|
|
10101
|
+
}
|
|
10102
|
+
for (const allOfVariant of schema.anyOf ?? []) {
|
|
10103
|
+
if (isNullable(allOfVariant)) {
|
|
10104
|
+
return true;
|
|
10105
|
+
}
|
|
10106
|
+
}
|
|
10107
|
+
return false;
|
|
10108
|
+
}
|
|
10109
|
+
/**
|
|
10110
|
+
* Mutates the given JSON schema to ensure it conforms to the `strict` standard
|
|
10111
|
+
* that the API expects.
|
|
10112
|
+
*/
|
|
10113
|
+
function ensureStrictJsonSchema(jsonSchema, path, root) {
|
|
10114
|
+
if (typeof jsonSchema === 'boolean') {
|
|
10115
|
+
throw new TypeError(`Expected object schema but got boolean; path=${path.join('/')}`);
|
|
10116
|
+
}
|
|
10117
|
+
if (!isObject(jsonSchema)) {
|
|
10118
|
+
throw new TypeError(`Expected ${JSON.stringify(jsonSchema)} to be an object; path=${path.join('/')}`);
|
|
10119
|
+
}
|
|
10120
|
+
// Handle $defs (non-standard but sometimes used)
|
|
10121
|
+
const defs = jsonSchema.$defs;
|
|
10122
|
+
if (isObject(defs)) {
|
|
10123
|
+
for (const [defName, defSchema] of Object.entries(defs)) {
|
|
10124
|
+
ensureStrictJsonSchema(defSchema, [...path, '$defs', defName], root);
|
|
10125
|
+
}
|
|
10126
|
+
}
|
|
10127
|
+
// Handle definitions (draft-04 style, deprecated in draft-07 but still used)
|
|
10128
|
+
const definitions = jsonSchema.definitions;
|
|
10129
|
+
if (isObject(definitions)) {
|
|
10130
|
+
for (const [definitionName, definitionSchema] of Object.entries(definitions)) {
|
|
10131
|
+
ensureStrictJsonSchema(definitionSchema, [...path, 'definitions', definitionName], root);
|
|
10132
|
+
}
|
|
10133
|
+
}
|
|
10134
|
+
// Add additionalProperties: false to object types
|
|
10135
|
+
const typ = jsonSchema.type;
|
|
10136
|
+
if (typ === 'object' && !('additionalProperties' in jsonSchema)) {
|
|
10137
|
+
jsonSchema.additionalProperties = false;
|
|
10138
|
+
}
|
|
10139
|
+
const required = jsonSchema.required ?? [];
|
|
10140
|
+
// Handle object properties
|
|
10141
|
+
const properties = jsonSchema.properties;
|
|
10142
|
+
if (isObject(properties)) {
|
|
10143
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
10144
|
+
if (!isNullable(value) && !required.includes(key)) {
|
|
10145
|
+
throw new Error(`Zod field at \`${[...path, 'properties', key].join('/')}\` uses \`.optional()\` without \`.nullable()\` which is not supported by the API. See: https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#all-fields-must-be-required`);
|
|
10146
|
+
}
|
|
10147
|
+
}
|
|
10148
|
+
jsonSchema.required = Object.keys(properties);
|
|
10149
|
+
jsonSchema.properties = Object.fromEntries(Object.entries(properties).map(([key, propSchema]) => [
|
|
10150
|
+
key,
|
|
10151
|
+
ensureStrictJsonSchema(propSchema, [...path, 'properties', key], root),
|
|
10152
|
+
]));
|
|
10153
|
+
}
|
|
10154
|
+
// Handle arrays
|
|
10155
|
+
const items = jsonSchema.items;
|
|
10156
|
+
if (isObject(items)) {
|
|
10157
|
+
jsonSchema.items = ensureStrictJsonSchema(items, [...path, 'items'], root);
|
|
10158
|
+
}
|
|
10159
|
+
// Handle unions (anyOf)
|
|
10160
|
+
const anyOf = jsonSchema.anyOf;
|
|
10161
|
+
if (Array.isArray(anyOf)) {
|
|
10162
|
+
jsonSchema.anyOf = anyOf.map((variant, i) => ensureStrictJsonSchema(variant, [...path, 'anyOf', String(i)], root));
|
|
10163
|
+
}
|
|
10164
|
+
// Handle intersections (allOf)
|
|
10165
|
+
const allOf = jsonSchema.allOf;
|
|
10166
|
+
if (Array.isArray(allOf)) {
|
|
10167
|
+
if (allOf.length === 1) {
|
|
10168
|
+
const resolved = ensureStrictJsonSchema(allOf[0], [...path, 'allOf', '0'], root);
|
|
10169
|
+
Object.assign(jsonSchema, resolved);
|
|
10170
|
+
delete jsonSchema.allOf;
|
|
10171
|
+
}
|
|
10172
|
+
else {
|
|
10173
|
+
jsonSchema.allOf = allOf.map((entry, i) => ensureStrictJsonSchema(entry, [...path, 'allOf', String(i)], root));
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
10176
|
+
// Strip `null` defaults as there's no meaningful distinction
|
|
10177
|
+
if (jsonSchema.default === null) {
|
|
10178
|
+
delete jsonSchema.default;
|
|
10179
|
+
}
|
|
10180
|
+
// Handle $ref with additional properties
|
|
10181
|
+
const ref = jsonSchema.$ref;
|
|
10182
|
+
if (ref && hasMoreThanNKeys(jsonSchema, 1)) {
|
|
10183
|
+
if (typeof ref !== 'string') {
|
|
10184
|
+
throw new TypeError(`Received non-string $ref - ${ref}; path=${path.join('/')}`);
|
|
10185
|
+
}
|
|
10186
|
+
const resolved = resolveRef(root, ref);
|
|
10187
|
+
if (typeof resolved === 'boolean') {
|
|
10188
|
+
throw new Error(`Expected \`$ref: ${ref}\` to resolve to an object schema but got boolean`);
|
|
10189
|
+
}
|
|
10190
|
+
if (!isObject(resolved)) {
|
|
10191
|
+
throw new Error(`Expected \`$ref: ${ref}\` to resolve to an object but got ${JSON.stringify(resolved)}`);
|
|
10192
|
+
}
|
|
10193
|
+
// Properties from the json schema take priority over the ones on the `$ref`
|
|
10194
|
+
Object.assign(jsonSchema, { ...resolved, ...jsonSchema });
|
|
10195
|
+
delete jsonSchema.$ref;
|
|
10196
|
+
// Since the schema expanded from `$ref` might not have `additionalProperties: false` applied,
|
|
10197
|
+
// we call `ensureStrictJsonSchema` again to fix the inlined schema and ensure it's valid.
|
|
10198
|
+
return ensureStrictJsonSchema(jsonSchema, path, root);
|
|
10199
|
+
}
|
|
10200
|
+
return jsonSchema;
|
|
10201
|
+
}
|
|
10202
|
+
function resolveRef(root, ref) {
|
|
10203
|
+
if (!ref.startsWith('#/')) {
|
|
10204
|
+
throw new Error(`Unexpected $ref format ${JSON.stringify(ref)}; Does not start with #/`);
|
|
10205
|
+
}
|
|
10206
|
+
const pathParts = ref.slice(2).split('/');
|
|
10207
|
+
let resolved = root;
|
|
10208
|
+
for (const key of pathParts) {
|
|
10209
|
+
if (!isObject(resolved)) {
|
|
10210
|
+
throw new Error(`encountered non-object entry while resolving ${ref} - ${JSON.stringify(resolved)}`);
|
|
10211
|
+
}
|
|
10212
|
+
const value = resolved[key];
|
|
10213
|
+
if (value === undefined) {
|
|
10214
|
+
throw new Error(`Key ${key} not found while resolving ${ref}`);
|
|
10215
|
+
}
|
|
10216
|
+
resolved = value;
|
|
10217
|
+
}
|
|
10218
|
+
return resolved;
|
|
10219
|
+
}
|
|
10220
|
+
function isObject(obj) {
|
|
10221
|
+
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
|
|
10222
|
+
}
|
|
10223
|
+
function hasMoreThanNKeys(obj, n) {
|
|
10224
|
+
let i = 0;
|
|
10225
|
+
for (const _ in obj) {
|
|
10226
|
+
i++;
|
|
10227
|
+
if (i > n) {
|
|
10228
|
+
return true;
|
|
10229
|
+
}
|
|
10230
|
+
}
|
|
10231
|
+
return false;
|
|
10232
|
+
}
|
|
10233
|
+
|
|
10234
|
+
function zodV3ToJsonSchema(schema, options) {
|
|
10235
|
+
return zodToJsonSchema(schema, {
|
|
10236
|
+
openaiStrictMode: true,
|
|
10237
|
+
name: options.name,
|
|
10238
|
+
nameStrategy: 'duplicate-ref',
|
|
10239
|
+
$refStrategy: 'extract-to-root',
|
|
10240
|
+
nullableStrategy: 'property',
|
|
10241
|
+
});
|
|
10242
|
+
}
|
|
10243
|
+
function zodV4ToJsonSchema(schema) {
|
|
10244
|
+
return toStrictJsonSchema(toJSONSchema(schema, {
|
|
10245
|
+
target: 'draft-7',
|
|
10246
|
+
}));
|
|
10247
|
+
}
|
|
10248
|
+
function isZodV4(zodObject) {
|
|
10249
|
+
return '_zod' in zodObject;
|
|
10250
|
+
}
|
|
10251
|
+
function zodTextFormat(zodObject, name, props) {
|
|
10252
|
+
return makeParseableTextFormat({
|
|
10253
|
+
type: 'json_schema',
|
|
10254
|
+
...props,
|
|
10255
|
+
name,
|
|
10256
|
+
strict: true,
|
|
10257
|
+
schema: isZodV4(zodObject) ? zodV4ToJsonSchema(zodObject) : zodV3ToJsonSchema(zodObject, { name }),
|
|
10258
|
+
}, (content) => zodObject.parse(JSON.parse(content)));
|
|
10259
|
+
}
|
|
10260
|
+
|
|
10261
|
+
// llm-openai-config.ts
|
|
10262
|
+
const DEFAULT_MODEL = 'gpt-4.1-mini';
|
|
10263
|
+
const GPT_5_4_HIGH_CONTEXT_THRESHOLD_TOKENS = 272_000;
|
|
10264
|
+
const GPT_5_4_HIGH_CONTEXT_INPUT_MULTIPLIER = 2;
|
|
10265
|
+
const GPT_5_4_HIGH_CONTEXT_OUTPUT_MULTIPLIER = 1.5;
|
|
10266
|
+
/** Token costs in USD per 1M tokens. Last updated Mar 2026. */
|
|
10267
|
+
const openAiModelCosts = {
|
|
10268
|
+
'gpt-4o': {
|
|
10269
|
+
inputCost: 2.5 / 1_000_000,
|
|
10270
|
+
cacheHitCost: 1.25 / 1_000_000,
|
|
10271
|
+
outputCost: 10 / 1_000_000,
|
|
10272
|
+
},
|
|
10273
|
+
'gpt-4o-mini': {
|
|
10274
|
+
inputCost: 0.15 / 1_000_000,
|
|
10275
|
+
cacheHitCost: 0.075 / 1_000_000,
|
|
10276
|
+
outputCost: 0.6 / 1_000_000,
|
|
10277
|
+
},
|
|
10278
|
+
'o1-mini': {
|
|
10279
|
+
inputCost: 1.1 / 1_000_000,
|
|
10280
|
+
cacheHitCost: 0.55 / 1_000_000,
|
|
10281
|
+
outputCost: 4.4 / 1_000_000,
|
|
10282
|
+
},
|
|
10283
|
+
'o1': {
|
|
10284
|
+
inputCost: 15 / 1_000_000,
|
|
10285
|
+
cacheHitCost: 7.5 / 1_000_000,
|
|
10286
|
+
outputCost: 60 / 1_000_000,
|
|
10287
|
+
},
|
|
10288
|
+
'o3-mini': {
|
|
10289
|
+
inputCost: 1.1 / 1_000_000,
|
|
10290
|
+
cacheHitCost: 0.55 / 1_000_000,
|
|
10291
|
+
outputCost: 4.4 / 1_000_000,
|
|
10292
|
+
},
|
|
10293
|
+
'o3': {
|
|
10294
|
+
inputCost: 2 / 1_000_000,
|
|
10295
|
+
cacheHitCost: 0.5 / 1_000_000,
|
|
10296
|
+
outputCost: 8 / 1_000_000,
|
|
10297
|
+
},
|
|
10298
|
+
'gpt-4.1': {
|
|
10299
|
+
inputCost: 2 / 1_000_000,
|
|
10300
|
+
cacheHitCost: 0.5 / 1_000_000,
|
|
10301
|
+
outputCost: 8 / 1_000_000,
|
|
10302
|
+
},
|
|
10303
|
+
'gpt-4.1-mini': {
|
|
10304
|
+
inputCost: 0.4 / 1_000_000,
|
|
10305
|
+
cacheHitCost: 0.1 / 1_000_000,
|
|
10306
|
+
outputCost: 1.6 / 1_000_000,
|
|
10307
|
+
},
|
|
10308
|
+
'gpt-4.1-nano': {
|
|
10309
|
+
inputCost: 0.1 / 1_000_000,
|
|
10310
|
+
cacheHitCost: 0.025 / 1_000_000,
|
|
10311
|
+
outputCost: 0.4 / 1_000_000,
|
|
10312
|
+
},
|
|
10313
|
+
'gpt-5': {
|
|
10314
|
+
inputCost: 1.25 / 1_000_000,
|
|
10315
|
+
cacheHitCost: 0.125 / 1_000_000,
|
|
10316
|
+
outputCost: 10 / 1_000_000,
|
|
10317
|
+
},
|
|
10318
|
+
'gpt-5-mini': {
|
|
10319
|
+
inputCost: 0.25 / 1_000_000,
|
|
10320
|
+
cacheHitCost: 0.025 / 1_000_000,
|
|
10321
|
+
outputCost: 2 / 1_000_000,
|
|
10322
|
+
},
|
|
10323
|
+
'gpt-5-nano': {
|
|
10324
|
+
inputCost: 0.05 / 1_000_000,
|
|
10325
|
+
cacheHitCost: 0.005 / 1_000_000,
|
|
10326
|
+
outputCost: 0.4 / 1_000_000,
|
|
10327
|
+
},
|
|
10328
|
+
'gpt-5.1': {
|
|
10329
|
+
inputCost: 1.25 / 1_000_000,
|
|
10330
|
+
cacheHitCost: 0.125 / 1_000_000,
|
|
10331
|
+
outputCost: 10 / 1_000_000,
|
|
10332
|
+
},
|
|
10333
|
+
'gpt-5.4': {
|
|
10334
|
+
inputCost: 2.5 / 1_000_000,
|
|
10335
|
+
cacheHitCost: 0.25 / 1_000_000,
|
|
10336
|
+
outputCost: 15 / 1_000_000,
|
|
10337
|
+
},
|
|
10338
|
+
'gpt-5.4-pro': {
|
|
10339
|
+
inputCost: 30 / 1_000_000,
|
|
10340
|
+
outputCost: 180 / 1_000_000,
|
|
10341
|
+
},
|
|
10342
|
+
'gpt-5.2': {
|
|
10343
|
+
inputCost: 1.75 / 1_000_000,
|
|
10344
|
+
cacheHitCost: 0.175 / 1_000_000,
|
|
10345
|
+
outputCost: 14 / 1_000_000,
|
|
10346
|
+
},
|
|
10347
|
+
'gpt-5.2-pro': {
|
|
10348
|
+
inputCost: 21 / 1_000_000,
|
|
10349
|
+
outputCost: 168 / 1_000_000,
|
|
10350
|
+
},
|
|
10351
|
+
'gpt-5.1-codex': {
|
|
10352
|
+
inputCost: 1.25 / 1_000_000,
|
|
10353
|
+
cacheHitCost: 0.125 / 1_000_000,
|
|
10354
|
+
outputCost: 10 / 1_000_000,
|
|
10355
|
+
},
|
|
10356
|
+
'gpt-5.1-codex-max': {
|
|
10357
|
+
inputCost: 1.25 / 1_000_000,
|
|
10358
|
+
cacheHitCost: 0.125 / 1_000_000,
|
|
10359
|
+
outputCost: 10 / 1_000_000,
|
|
10360
|
+
},
|
|
10361
|
+
'o4-mini': {
|
|
10362
|
+
inputCost: 1.1 / 1_000_000,
|
|
10363
|
+
cacheHitCost: 0.275 / 1_000_000,
|
|
10364
|
+
outputCost: 4.4 / 1_000_000,
|
|
10365
|
+
},
|
|
10366
|
+
};
|
|
10367
|
+
const deepseekModelCosts = {
|
|
10368
|
+
'deepseek-chat': {
|
|
10369
|
+
inputCost: 0.27 / 1_000_000, // $0.27 per 1M tokens (Cache miss price)
|
|
10370
|
+
cacheHitCost: 0.07 / 1_000_000, // $0.07 per 1M tokens (Cache hit price)
|
|
10371
|
+
outputCost: 1.1 / 1_000_000, // $1.10 per 1M tokens
|
|
10372
|
+
},
|
|
10373
|
+
'deepseek-reasoner': {
|
|
10374
|
+
inputCost: 0.55 / 1_000_000, // $0.55 per 1M tokens (Cache miss price)
|
|
10375
|
+
cacheHitCost: 0.14 / 1_000_000, // $0.14 per 1M tokens (Cache hit price)
|
|
10376
|
+
outputCost: 2.19 / 1_000_000, // $2.19 per 1M tokens
|
|
10377
|
+
},
|
|
10378
|
+
};
|
|
10379
|
+
function shouldUseGPT54HighContextPricing(model, inputTokens) {
|
|
10380
|
+
return (model === 'gpt-5.4' || model === 'gpt-5.4-pro') && inputTokens > GPT_5_4_HIGH_CONTEXT_THRESHOLD_TOKENS;
|
|
10381
|
+
}
|
|
10382
|
+
/** Image generation costs in USD per image. Based on OpenAI pricing as of Feb 2025. */
|
|
10383
|
+
const openAiImageCosts = {
|
|
10384
|
+
'gpt-image-1': 0.0075, // $0.0075 per image for gpt-image-1
|
|
10385
|
+
'gpt-image-1.5': 0.0075, // Assumes parity pricing with gpt-image-1 until OpenAI publishes updated rates
|
|
10386
|
+
};
|
|
10387
|
+
/**
|
|
10388
|
+
* Calculates the cost of generating images using OpenAI's Images API.
|
|
10389
|
+
*
|
|
10390
|
+
* @param model The image generation model name.
|
|
10391
|
+
* @param imageCount The number of images generated.
|
|
10392
|
+
* @returns The cost of generating the images in USD.
|
|
10393
|
+
*/
|
|
10394
|
+
function calculateImageCost(model, imageCount) {
|
|
10395
|
+
if (typeof model !== 'string' || typeof imageCount !== 'number' || imageCount <= 0) {
|
|
10396
|
+
return 0;
|
|
10397
|
+
}
|
|
10398
|
+
const costPerImage = openAiImageCosts[model];
|
|
10399
|
+
if (!costPerImage)
|
|
10400
|
+
return 0;
|
|
10401
|
+
return imageCount * costPerImage;
|
|
10402
|
+
}
|
|
10403
|
+
/**
|
|
10404
|
+
* Calculates the cost of calling a language model in USD based on the provider and model, tokens, and given costs per 1M tokens.
|
|
10405
|
+
*
|
|
10406
|
+
* @param provider The provider of the language model. Supported providers are 'openai' and 'deepseek'.
|
|
10407
|
+
* @param model The name of the language model. Supported models are listed in the `openAiModelCosts` and `deepseekModelCosts` objects.
|
|
10408
|
+
* @param inputTokens The number of input tokens passed to the language model.
|
|
10409
|
+
* @param outputTokens The number of output tokens generated by the language model.
|
|
10410
|
+
* @param reasoningTokens The number of output tokens generated by the language model for reasoning.
|
|
10411
|
+
* @param cacheHitTokens The number of input tokens billed at cached-input rates.
|
|
10412
|
+
* @returns The cost of calling the language model in USD.
|
|
10413
|
+
*/
|
|
10414
|
+
function calculateCost(provider, model, inputTokens, outputTokens, reasoningTokens, cacheHitTokens) {
|
|
10415
|
+
if (typeof provider !== 'string' ||
|
|
10416
|
+
typeof model !== 'string' ||
|
|
10417
|
+
typeof inputTokens !== 'number' ||
|
|
10418
|
+
typeof outputTokens !== 'number' ||
|
|
10419
|
+
(reasoningTokens !== undefined && typeof reasoningTokens !== 'number') ||
|
|
10420
|
+
(cacheHitTokens !== undefined && typeof cacheHitTokens !== 'number')) {
|
|
10421
|
+
return 0;
|
|
10422
|
+
}
|
|
10423
|
+
const modelCosts = provider === 'deepseek' ? deepseekModelCosts[model] : openAiModelCosts[model];
|
|
10424
|
+
if (!modelCosts)
|
|
10425
|
+
return 0;
|
|
10426
|
+
const boundedCacheHitTokens = Math.min(Math.max(cacheHitTokens || 0, 0), inputTokens);
|
|
10427
|
+
let inputCost = inputTokens * modelCosts.inputCost;
|
|
10428
|
+
if (typeof modelCosts.cacheHitCost === 'number' && boundedCacheHitTokens > 0) {
|
|
10429
|
+
inputCost = boundedCacheHitTokens * modelCosts.cacheHitCost + (inputTokens - boundedCacheHitTokens) * modelCosts.inputCost;
|
|
10430
|
+
}
|
|
10431
|
+
let outputCost = outputTokens * modelCosts.outputCost;
|
|
10432
|
+
let reasoningCost = (reasoningTokens || 0) * modelCosts.outputCost;
|
|
10433
|
+
if (provider === 'openai' && shouldUseGPT54HighContextPricing(model, inputTokens)) {
|
|
10434
|
+
inputCost *= GPT_5_4_HIGH_CONTEXT_INPUT_MULTIPLIER;
|
|
10435
|
+
outputCost *= GPT_5_4_HIGH_CONTEXT_OUTPUT_MULTIPLIER;
|
|
10436
|
+
reasoningCost *= GPT_5_4_HIGH_CONTEXT_OUTPUT_MULTIPLIER;
|
|
10437
|
+
}
|
|
10438
|
+
return inputCost + outputCost + reasoningCost;
|
|
10439
|
+
}
|
|
10440
|
+
|
|
10441
|
+
/**
|
|
10442
|
+
* Fix a broken JSON string by attempting to extract and parse valid JSON content. This function is very lenient and will attempt to fix many types of JSON errors, including unbalanced brackets, missing or extra commas, improperly escaped $ signs, unquoted strings, trailing commas, missing closing brackets or braces, etc.
|
|
10443
|
+
* @param {string} jsonStr - The broken JSON string to fix
|
|
10444
|
+
* @returns {JsonValue} - The parsed JSON value
|
|
10445
|
+
*/
|
|
10446
|
+
function fixBrokenJson(jsonStr) {
|
|
10447
|
+
// Pre-process: Fix improperly escaped $ signs
|
|
10448
|
+
jsonStr = jsonStr.replace(/\\\$/g, '$');
|
|
10449
|
+
let index = 0;
|
|
10450
|
+
function parse() {
|
|
10451
|
+
const results = [];
|
|
10452
|
+
while (index < jsonStr.length) {
|
|
10453
|
+
skipWhitespace();
|
|
10454
|
+
const value = parseValue();
|
|
10455
|
+
if (value !== undefined) {
|
|
10456
|
+
results.push(value);
|
|
10457
|
+
}
|
|
10458
|
+
else {
|
|
10459
|
+
index++; // Skip invalid character
|
|
10460
|
+
}
|
|
10461
|
+
}
|
|
10462
|
+
return results.length === 1 ? results[0] : results;
|
|
10463
|
+
}
|
|
10464
|
+
function parseValue() {
|
|
10465
|
+
skipWhitespace();
|
|
10466
|
+
const char = getChar();
|
|
10467
|
+
if (!char)
|
|
10468
|
+
return undefined;
|
|
10469
|
+
if (char === '{')
|
|
10470
|
+
return parseObject();
|
|
10471
|
+
if (char === '[')
|
|
10472
|
+
return parseArray();
|
|
10473
|
+
if (char === '"' || char === "'")
|
|
10474
|
+
return parseString();
|
|
10475
|
+
if (char === 't' && jsonStr.slice(index, index + 4).toLowerCase() === 'true') {
|
|
10476
|
+
index += 4;
|
|
10477
|
+
return true;
|
|
10478
|
+
}
|
|
10479
|
+
if (char === 'f' && jsonStr.slice(index, index + 5).toLowerCase() === 'false') {
|
|
10480
|
+
index += 5;
|
|
10481
|
+
return false;
|
|
10482
|
+
}
|
|
10483
|
+
if (char === 'n' && jsonStr.slice(index, index + 4).toLowerCase() === 'null') {
|
|
10484
|
+
index += 4;
|
|
10485
|
+
return null;
|
|
10486
|
+
}
|
|
10487
|
+
if (/[a-zA-Z]/.test(char))
|
|
10488
|
+
return parseString(); // Unquoted string
|
|
10489
|
+
if (char === '-' || char === '.' || /\d/.test(char))
|
|
10490
|
+
return parseNumber();
|
|
10491
|
+
return undefined; // Unknown character
|
|
10492
|
+
}
|
|
10493
|
+
function parseObject() {
|
|
10494
|
+
const obj = {};
|
|
10495
|
+
index++; // Skip opening brace
|
|
10496
|
+
skipWhitespace();
|
|
10497
|
+
while (index < jsonStr.length && getChar() !== '}') {
|
|
10498
|
+
skipWhitespace();
|
|
10499
|
+
const key = parseString();
|
|
10500
|
+
if (key === undefined) {
|
|
10501
|
+
console.warn(`Expected key at position ${index}`);
|
|
10502
|
+
index++;
|
|
10503
|
+
continue;
|
|
10504
|
+
}
|
|
10505
|
+
skipWhitespace();
|
|
10506
|
+
if (getChar() === ':') {
|
|
10507
|
+
index++; // Skip colon
|
|
10508
|
+
}
|
|
10509
|
+
else {
|
|
10510
|
+
console.warn(`Missing colon after key "${key}" at position ${index}`);
|
|
10511
|
+
}
|
|
10512
|
+
skipWhitespace();
|
|
10513
|
+
const value = parseValue();
|
|
10514
|
+
if (value === undefined) {
|
|
10515
|
+
console.warn(`Expected value for key "${key}" at position ${index}`);
|
|
10516
|
+
index++;
|
|
10517
|
+
continue;
|
|
10518
|
+
}
|
|
10519
|
+
obj[key] = value;
|
|
10520
|
+
skipWhitespace();
|
|
10521
|
+
if (getChar() === ',') {
|
|
10522
|
+
index++; // Skip comma
|
|
10523
|
+
}
|
|
10524
|
+
else {
|
|
10525
|
+
break;
|
|
10526
|
+
}
|
|
10527
|
+
skipWhitespace();
|
|
10528
|
+
}
|
|
10529
|
+
if (getChar() === '}') {
|
|
10530
|
+
index++; // Skip closing brace
|
|
10531
|
+
}
|
|
10532
|
+
else {
|
|
7705
10533
|
// Add a closing brace if it's missing
|
|
7706
10534
|
jsonStr += '}';
|
|
7707
10535
|
}
|
|
@@ -8004,6 +10832,8 @@ const isSupportedModel = (model) => {
|
|
|
8004
10832
|
'gpt-5-mini',
|
|
8005
10833
|
'gpt-5-nano',
|
|
8006
10834
|
'gpt-5.1',
|
|
10835
|
+
'gpt-5.4',
|
|
10836
|
+
'gpt-5.4-pro',
|
|
8007
10837
|
'gpt-5.2',
|
|
8008
10838
|
'gpt-5.2-pro',
|
|
8009
10839
|
'gpt-5.1-codex',
|
|
@@ -8030,6 +10860,8 @@ function supportsTemperature(model) {
|
|
|
8030
10860
|
'gpt-5-mini',
|
|
8031
10861
|
'gpt-5-nano',
|
|
8032
10862
|
'gpt-5.1',
|
|
10863
|
+
'gpt-5.4',
|
|
10864
|
+
'gpt-5.4-pro',
|
|
8033
10865
|
'gpt-5.2',
|
|
8034
10866
|
'gpt-5.2-pro',
|
|
8035
10867
|
'gpt-5.1-codex',
|
|
@@ -8057,6 +10889,8 @@ function isGPT5Model(model) {
|
|
|
8057
10889
|
'gpt-5-mini',
|
|
8058
10890
|
'gpt-5-nano',
|
|
8059
10891
|
'gpt-5.1',
|
|
10892
|
+
'gpt-5.4',
|
|
10893
|
+
'gpt-5.4-pro',
|
|
8060
10894
|
'gpt-5.2',
|
|
8061
10895
|
'gpt-5.2-pro',
|
|
8062
10896
|
'gpt-5.1-codex',
|
|
@@ -8064,6 +10898,38 @@ function isGPT5Model(model) {
|
|
|
8064
10898
|
];
|
|
8065
10899
|
return gpt5Models.includes(model);
|
|
8066
10900
|
}
|
|
10901
|
+
function supportsStructuredOutputs(model) {
|
|
10902
|
+
return normalizeModelName(model) !== 'gpt-5.4-pro';
|
|
10903
|
+
}
|
|
10904
|
+
function supportsDistillation(model) {
|
|
10905
|
+
return normalizeModelName(model) !== 'gpt-5.4-pro';
|
|
10906
|
+
}
|
|
10907
|
+
function isZodSchema(schema) {
|
|
10908
|
+
return typeof schema === 'object' && schema !== null && 'safeParse' in schema;
|
|
10909
|
+
}
|
|
10910
|
+
function buildJsonSchemaFormat(schema, schemaName, schemaDescription, schemaStrict) {
|
|
10911
|
+
if (isZodSchema(schema)) {
|
|
10912
|
+
const zodFormat = zodTextFormat(schema, schemaName, schemaDescription
|
|
10913
|
+
? {
|
|
10914
|
+
description: schemaDescription,
|
|
10915
|
+
}
|
|
10916
|
+
: undefined);
|
|
10917
|
+
return {
|
|
10918
|
+
type: 'json_schema',
|
|
10919
|
+
name: zodFormat.name,
|
|
10920
|
+
schema: zodFormat.schema,
|
|
10921
|
+
...(zodFormat.description ? { description: zodFormat.description } : {}),
|
|
10922
|
+
...(schemaStrict !== undefined ? { strict: schemaStrict } : { strict: zodFormat.strict }),
|
|
10923
|
+
};
|
|
10924
|
+
}
|
|
10925
|
+
return {
|
|
10926
|
+
type: 'json_schema',
|
|
10927
|
+
name: schemaName,
|
|
10928
|
+
schema,
|
|
10929
|
+
...(schemaDescription ? { description: schemaDescription } : {}),
|
|
10930
|
+
...(schemaStrict !== undefined ? { strict: schemaStrict } : {}),
|
|
10931
|
+
};
|
|
10932
|
+
}
|
|
8067
10933
|
/**
|
|
8068
10934
|
* Makes a call to OpenAI's Responses API for more advanced use cases with built-in tools.
|
|
8069
10935
|
*
|
|
@@ -8106,8 +10972,15 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
8106
10972
|
input,
|
|
8107
10973
|
...cleanOptions,
|
|
8108
10974
|
};
|
|
10975
|
+
if (requestBody.text?.format?.type === 'json_schema' && !supportsStructuredOutputs(normalizedModel)) {
|
|
10976
|
+
throw new Error(`Model ${normalizedModel} does not support structured outputs`);
|
|
10977
|
+
}
|
|
10978
|
+
if (requestBody.store && !supportsDistillation(normalizedModel)) {
|
|
10979
|
+
throw new Error(`Model ${normalizedModel} does not support distillation`);
|
|
10980
|
+
}
|
|
8109
10981
|
// Make the API call to the Responses endpoint
|
|
8110
10982
|
const response = await openai.responses.create(requestBody);
|
|
10983
|
+
const cacheHitTokens = response.usage?.input_tokens_details?.cached_tokens || 0;
|
|
8111
10984
|
// Extract tool calls from the output
|
|
8112
10985
|
const toolCalls = response.output
|
|
8113
10986
|
?.filter((item) => item.type === 'function_call')
|
|
@@ -8147,7 +11020,8 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
8147
11020
|
reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0,
|
|
8148
11021
|
provider: 'openai',
|
|
8149
11022
|
model: normalizedModel,
|
|
8150
|
-
|
|
11023
|
+
cache_hit_tokens: cacheHitTokens,
|
|
11024
|
+
cost: calculateCost('openai', normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, cacheHitTokens),
|
|
8151
11025
|
},
|
|
8152
11026
|
tool_calls: toolCalls,
|
|
8153
11027
|
...(codeInterpreterOutputs ? { code_interpreter_outputs: codeInterpreterOutputs } : {}),
|
|
@@ -8163,9 +11037,13 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
8163
11037
|
.join('') || '';
|
|
8164
11038
|
// Determine the format for parsing the response
|
|
8165
11039
|
let parsingFormat = 'text';
|
|
8166
|
-
|
|
11040
|
+
const requestedFormat = requestBody.text?.format;
|
|
11041
|
+
if (requestedFormat?.type === 'json_object') {
|
|
8167
11042
|
parsingFormat = 'json';
|
|
8168
11043
|
}
|
|
11044
|
+
else if (requestedFormat?.type === 'json_schema') {
|
|
11045
|
+
parsingFormat = requestedFormat;
|
|
11046
|
+
}
|
|
8169
11047
|
// Handle regular responses
|
|
8170
11048
|
const parsedResponse = await parseResponse(textContent, parsingFormat);
|
|
8171
11049
|
if (parsedResponse === null) {
|
|
@@ -8179,7 +11057,8 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
8179
11057
|
reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0,
|
|
8180
11058
|
provider: 'openai',
|
|
8181
11059
|
model: normalizedModel,
|
|
8182
|
-
|
|
11060
|
+
cache_hit_tokens: cacheHitTokens,
|
|
11061
|
+
cost: calculateCost('openai', normalizedModel, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, response.usage?.output_tokens_details?.reasoning_tokens || 0, cacheHitTokens),
|
|
8183
11062
|
},
|
|
8184
11063
|
tool_calls: toolCalls,
|
|
8185
11064
|
...(codeInterpreterOutputs ? { code_interpreter_outputs: codeInterpreterOutputs } : {}),
|
|
@@ -8202,7 +11081,7 @@ const makeResponsesAPICall = async (input, options = {}) => {
|
|
|
8202
11081
|
* });
|
|
8203
11082
|
*/
|
|
8204
11083
|
async function makeLLMCall(input, options = {}) {
|
|
8205
|
-
const { apiKey, model = DEFAULT_MODEL, responseFormat = 'text', tools, useCodeInterpreter = false, useWebSearch = false, imageBase64, imageDetail = 'high', context, } = options;
|
|
11084
|
+
const { apiKey, model = DEFAULT_MODEL, responseFormat = 'text', schema, schemaName = 'response', schemaDescription, schemaStrict, tools, useCodeInterpreter = false, useWebSearch = false, imageBase64, imageDetail = 'high', context, } = options;
|
|
8206
11085
|
// Validate model
|
|
8207
11086
|
const normalizedModel = normalizeModelName(model);
|
|
8208
11087
|
if (!isSupportedModel(normalizedModel)) {
|
|
@@ -8280,9 +11159,17 @@ async function makeLLMCall(input, options = {}) {
|
|
|
8280
11159
|
responsesOptions.temperature = 0.2;
|
|
8281
11160
|
}
|
|
8282
11161
|
// Configure response format
|
|
8283
|
-
if (
|
|
11162
|
+
if (schema) {
|
|
11163
|
+
responsesOptions.text = {
|
|
11164
|
+
format: buildJsonSchemaFormat(schema, schemaName, schemaDescription, schemaStrict),
|
|
11165
|
+
};
|
|
11166
|
+
}
|
|
11167
|
+
else if (responseFormat === 'json') {
|
|
8284
11168
|
responsesOptions.text = { format: { type: 'json_object' } };
|
|
8285
11169
|
}
|
|
11170
|
+
else if (typeof responseFormat === 'object' && responseFormat.type === 'json_schema') {
|
|
11171
|
+
responsesOptions.text = { format: responseFormat };
|
|
11172
|
+
}
|
|
8286
11173
|
// Configure built-in tools
|
|
8287
11174
|
if (useCodeInterpreter) {
|
|
8288
11175
|
responsesOptions.tools = [{ type: 'code_interpreter', container: { type: 'auto' } }];
|
|
@@ -8311,6 +11198,8 @@ const MULTIMODAL_VISION_MODELS = new Set([
|
|
|
8311
11198
|
'gpt-5-mini',
|
|
8312
11199
|
'gpt-5-nano',
|
|
8313
11200
|
'gpt-5.1',
|
|
11201
|
+
'gpt-5.4',
|
|
11202
|
+
'gpt-5.4-pro',
|
|
8314
11203
|
'gpt-5.2',
|
|
8315
11204
|
'gpt-5.2-pro',
|
|
8316
11205
|
'gpt-5.1-codex',
|