openai 6.47.0 → 6.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/azure.d.mts.map +1 -1
  3. package/azure.d.ts.map +1 -1
  4. package/azure.js +5 -1
  5. package/azure.js.map +1 -1
  6. package/azure.mjs +5 -1
  7. package/azure.mjs.map +1 -1
  8. package/helpers/standard-schema.d.mts +104 -0
  9. package/helpers/standard-schema.d.mts.map +1 -0
  10. package/helpers/standard-schema.d.ts +104 -0
  11. package/helpers/standard-schema.d.ts.map +1 -0
  12. package/helpers/standard-schema.js +320 -0
  13. package/helpers/standard-schema.js.map +1 -0
  14. package/helpers/standard-schema.mjs +314 -0
  15. package/helpers/standard-schema.mjs.map +1 -0
  16. package/helpers/zod.d.mts +26 -3
  17. package/helpers/zod.d.mts.map +1 -1
  18. package/helpers/zod.d.ts +26 -3
  19. package/helpers/zod.d.ts.map +1 -1
  20. package/helpers/zod.js +97 -7
  21. package/helpers/zod.js.map +1 -1
  22. package/helpers/zod.mjs +96 -7
  23. package/helpers/zod.mjs.map +1 -1
  24. package/internal/headers.d.mts.map +1 -1
  25. package/internal/headers.d.ts.map +1 -1
  26. package/internal/headers.js +7 -3
  27. package/internal/headers.js.map +1 -1
  28. package/internal/headers.mjs +7 -3
  29. package/internal/headers.mjs.map +1 -1
  30. package/lib/AbstractChatCompletionRunner.d.mts.map +1 -1
  31. package/lib/AbstractChatCompletionRunner.d.ts.map +1 -1
  32. package/lib/AbstractChatCompletionRunner.js +43 -1
  33. package/lib/AbstractChatCompletionRunner.js.map +1 -1
  34. package/lib/AbstractChatCompletionRunner.mjs +43 -1
  35. package/lib/AbstractChatCompletionRunner.mjs.map +1 -1
  36. package/lib/ChatCompletionStream.d.mts +2 -1
  37. package/lib/ChatCompletionStream.d.mts.map +1 -1
  38. package/lib/ChatCompletionStream.d.ts +2 -1
  39. package/lib/ChatCompletionStream.d.ts.map +1 -1
  40. package/lib/ChatCompletionStream.js +53 -14
  41. package/lib/ChatCompletionStream.js.map +1 -1
  42. package/lib/ChatCompletionStream.mjs +53 -14
  43. package/lib/ChatCompletionStream.mjs.map +1 -1
  44. package/lib/ResponsesParser.d.mts.map +1 -1
  45. package/lib/ResponsesParser.d.ts.map +1 -1
  46. package/lib/ResponsesParser.js +3 -5
  47. package/lib/ResponsesParser.js.map +1 -1
  48. package/lib/ResponsesParser.mjs +3 -5
  49. package/lib/ResponsesParser.mjs.map +1 -1
  50. package/lib/jsonschema.d.mts +3 -0
  51. package/lib/jsonschema.d.mts.map +1 -1
  52. package/lib/jsonschema.d.ts +3 -0
  53. package/lib/jsonschema.d.ts.map +1 -1
  54. package/lib/transform.d.mts +25 -1
  55. package/lib/transform.d.mts.map +1 -1
  56. package/lib/transform.d.ts +25 -1
  57. package/lib/transform.d.ts.map +1 -1
  58. package/lib/transform.js +1521 -82
  59. package/lib/transform.js.map +1 -1
  60. package/lib/transform.mjs +1515 -82
  61. package/lib/transform.mjs.map +1 -1
  62. package/package.json +1 -1
  63. package/resources/admin/organization/audit-logs.d.mts +2 -2
  64. package/resources/admin/organization/audit-logs.d.mts.map +1 -1
  65. package/resources/admin/organization/audit-logs.d.ts +2 -2
  66. package/resources/admin/organization/audit-logs.d.ts.map +1 -1
  67. package/resources/admin/organization/index.d.mts +1 -0
  68. package/resources/admin/organization/index.d.mts.map +1 -1
  69. package/resources/admin/organization/index.d.ts +1 -0
  70. package/resources/admin/organization/index.d.ts.map +1 -1
  71. package/resources/admin/organization/index.js +3 -1
  72. package/resources/admin/organization/index.js.map +1 -1
  73. package/resources/admin/organization/index.mjs +1 -0
  74. package/resources/admin/organization/index.mjs.map +1 -1
  75. package/resources/admin/organization/organization.d.mts +4 -0
  76. package/resources/admin/organization/organization.d.mts.map +1 -1
  77. package/resources/admin/organization/organization.d.ts +4 -0
  78. package/resources/admin/organization/organization.d.ts.map +1 -1
  79. package/resources/admin/organization/organization.js +4 -0
  80. package/resources/admin/organization/organization.js.map +1 -1
  81. package/resources/admin/organization/organization.mjs +4 -0
  82. package/resources/admin/organization/organization.mjs.map +1 -1
  83. package/resources/admin/organization/projects/index.d.mts +2 -1
  84. package/resources/admin/organization/projects/index.d.mts.map +1 -1
  85. package/resources/admin/organization/projects/index.d.ts +2 -1
  86. package/resources/admin/organization/projects/index.d.ts.map +1 -1
  87. package/resources/admin/organization/projects/index.js +7 -5
  88. package/resources/admin/organization/projects/index.js.map +1 -1
  89. package/resources/admin/organization/projects/index.mjs +2 -1
  90. package/resources/admin/organization/projects/index.mjs.map +1 -1
  91. package/resources/admin/organization/projects/projects.d.mts +6 -2
  92. package/resources/admin/organization/projects/projects.d.mts.map +1 -1
  93. package/resources/admin/organization/projects/projects.d.ts +6 -2
  94. package/resources/admin/organization/projects/projects.d.ts.map +1 -1
  95. package/resources/admin/organization/projects/projects.js +6 -2
  96. package/resources/admin/organization/projects/projects.js.map +1 -1
  97. package/resources/admin/organization/projects/projects.mjs +6 -2
  98. package/resources/admin/organization/projects/projects.mjs.map +1 -1
  99. package/resources/admin/organization/projects/service-accounts/api-keys.d.mts +58 -0
  100. package/resources/admin/organization/projects/service-accounts/api-keys.d.mts.map +1 -0
  101. package/resources/admin/organization/projects/service-accounts/api-keys.d.ts +58 -0
  102. package/resources/admin/organization/projects/service-accounts/api-keys.d.ts.map +1 -0
  103. package/resources/admin/organization/projects/service-accounts/api-keys.js +26 -0
  104. package/resources/admin/organization/projects/service-accounts/api-keys.js.map +1 -0
  105. package/resources/admin/organization/projects/service-accounts/api-keys.mjs +22 -0
  106. package/resources/admin/organization/projects/service-accounts/api-keys.mjs.map +1 -0
  107. package/resources/admin/organization/projects/service-accounts/index.d.mts +3 -0
  108. package/resources/admin/organization/projects/service-accounts/index.d.mts.map +1 -0
  109. package/resources/admin/organization/projects/service-accounts/index.d.ts +3 -0
  110. package/resources/admin/organization/projects/service-accounts/index.d.ts.map +1 -0
  111. package/resources/admin/organization/projects/service-accounts/index.js +9 -0
  112. package/resources/admin/organization/projects/service-accounts/index.js.map +1 -0
  113. package/resources/admin/organization/projects/service-accounts/index.mjs +4 -0
  114. package/resources/admin/organization/projects/service-accounts/index.mjs.map +1 -0
  115. package/resources/admin/organization/projects/service-accounts/service-accounts.d.mts +177 -0
  116. package/resources/admin/organization/projects/service-accounts/service-accounts.d.mts.map +1 -0
  117. package/resources/admin/organization/projects/service-accounts/service-accounts.d.ts +177 -0
  118. package/resources/admin/organization/projects/service-accounts/service-accounts.d.ts.map +1 -0
  119. package/resources/admin/organization/projects/service-accounts/service-accounts.js +109 -0
  120. package/resources/admin/organization/projects/service-accounts/service-accounts.js.map +1 -0
  121. package/resources/admin/organization/projects/service-accounts/service-accounts.mjs +104 -0
  122. package/resources/admin/organization/projects/service-accounts/service-accounts.mjs.map +1 -0
  123. package/resources/admin/organization/projects/service-accounts.d.mts +1 -167
  124. package/resources/admin/organization/projects/service-accounts.d.mts.map +1 -1
  125. package/resources/admin/organization/projects/service-accounts.d.ts +1 -167
  126. package/resources/admin/organization/projects/service-accounts.d.ts.map +1 -1
  127. package/resources/admin/organization/projects/service-accounts.js +2 -97
  128. package/resources/admin/organization/projects/service-accounts.js.map +1 -1
  129. package/resources/admin/organization/projects/service-accounts.mjs +1 -95
  130. package/resources/admin/organization/projects/service-accounts.mjs.map +1 -1
  131. package/resources/admin/organization/projects/spend-limit.d.mts +115 -0
  132. package/resources/admin/organization/projects/spend-limit.d.mts.map +1 -0
  133. package/resources/admin/organization/projects/spend-limit.d.ts +115 -0
  134. package/resources/admin/organization/projects/spend-limit.d.ts.map +1 -0
  135. package/resources/admin/organization/projects/spend-limit.js +67 -0
  136. package/resources/admin/organization/projects/spend-limit.js.map +1 -0
  137. package/resources/admin/organization/projects/spend-limit.mjs +63 -0
  138. package/resources/admin/organization/projects/spend-limit.mjs.map +1 -0
  139. package/resources/admin/organization/spend-limit.d.mts +108 -0
  140. package/resources/admin/organization/spend-limit.d.mts.map +1 -0
  141. package/resources/admin/organization/spend-limit.d.ts +108 -0
  142. package/resources/admin/organization/spend-limit.d.ts.map +1 -0
  143. package/resources/admin/organization/spend-limit.js +59 -0
  144. package/resources/admin/organization/spend-limit.js.map +1 -0
  145. package/resources/admin/organization/spend-limit.mjs +55 -0
  146. package/resources/admin/organization/spend-limit.mjs.map +1 -0
  147. package/resources/admin/organization/usage.d.mts +517 -88
  148. package/resources/admin/organization/usage.d.mts.map +1 -1
  149. package/resources/admin/organization/usage.d.ts +517 -88
  150. package/resources/admin/organization/usage.d.ts.map +1 -1
  151. package/resources/beta/responses/input-tokens.d.mts +6 -2
  152. package/resources/beta/responses/input-tokens.d.mts.map +1 -1
  153. package/resources/beta/responses/input-tokens.d.ts +6 -2
  154. package/resources/beta/responses/input-tokens.d.ts.map +1 -1
  155. package/resources/beta/responses/responses.d.mts +25 -15
  156. package/resources/beta/responses/responses.d.mts.map +1 -1
  157. package/resources/beta/responses/responses.d.ts +25 -15
  158. package/resources/beta/responses/responses.d.ts.map +1 -1
  159. package/resources/beta/responses/responses.js.map +1 -1
  160. package/resources/beta/responses/responses.mjs.map +1 -1
  161. package/resources/chat/completions/completions.d.mts +4 -3
  162. package/resources/chat/completions/completions.d.mts.map +1 -1
  163. package/resources/chat/completions/completions.d.ts +4 -3
  164. package/resources/chat/completions/completions.d.ts.map +1 -1
  165. package/resources/chat/completions/completions.js.map +1 -1
  166. package/resources/chat/completions/completions.mjs.map +1 -1
  167. package/resources/responses/input-tokens.d.mts +2 -1
  168. package/resources/responses/input-tokens.d.mts.map +1 -1
  169. package/resources/responses/input-tokens.d.ts +2 -1
  170. package/resources/responses/input-tokens.d.ts.map +1 -1
  171. package/resources/responses/responses.d.mts +9 -8
  172. package/resources/responses/responses.d.mts.map +1 -1
  173. package/resources/responses/responses.d.ts +9 -8
  174. package/resources/responses/responses.d.ts.map +1 -1
  175. package/resources/responses/responses.js.map +1 -1
  176. package/resources/responses/responses.mjs.map +1 -1
  177. package/resources/shared.d.mts +4 -1
  178. package/resources/shared.d.mts.map +1 -1
  179. package/resources/shared.d.ts +4 -1
  180. package/resources/shared.d.ts.map +1 -1
  181. package/src/azure.ts +5 -1
  182. package/src/helpers/standard-schema.ts +570 -0
  183. package/src/helpers/zod.ts +158 -29
  184. package/src/internal/headers.ts +7 -3
  185. package/src/lib/AbstractChatCompletionRunner.ts +43 -1
  186. package/src/lib/ChatCompletionStream.ts +56 -8
  187. package/src/lib/ResponsesParser.ts +7 -5
  188. package/src/lib/jsonschema.ts +3 -0
  189. package/src/lib/transform.ts +1930 -103
  190. package/src/resources/admin/organization/audit-logs.ts +169 -1
  191. package/src/resources/admin/organization/index.ts +6 -0
  192. package/src/resources/admin/organization/organization.ts +16 -0
  193. package/src/resources/admin/organization/projects/index.ts +7 -1
  194. package/src/resources/admin/organization/projects/projects.ts +29 -13
  195. package/src/resources/admin/organization/projects/service-accounts/api-keys.ts +80 -0
  196. package/src/resources/admin/organization/projects/service-accounts/index.ts +15 -0
  197. package/src/resources/admin/organization/projects/service-accounts/service-accounts.ts +284 -0
  198. package/src/resources/admin/organization/projects/service-accounts.ts +1 -264
  199. package/src/resources/admin/organization/projects/spend-limit.ts +156 -0
  200. package/src/resources/admin/organization/spend-limit.ts +144 -0
  201. package/src/resources/admin/organization/usage.ts +616 -88
  202. package/src/resources/beta/responses/input-tokens.ts +6 -2
  203. package/src/resources/beta/responses/responses.ts +25 -14
  204. package/src/resources/chat/completions/completions.ts +4 -3
  205. package/src/resources/responses/input-tokens.ts +2 -1
  206. package/src/resources/responses/responses.ts +9 -7
  207. package/src/resources/shared.ts +4 -1
  208. package/src/version.ts +1 -1
  209. package/version.d.mts +1 -1
  210. package/version.d.ts +1 -1
  211. package/version.js +1 -1
  212. package/version.mjs +1 -1
package/lib/transform.mjs CHANGED
@@ -1,28 +1,512 @@
1
- export function toStrictJsonSchema(schema) {
2
- if (schema.type !== 'object') {
3
- throw new Error(`Root schema must have type: 'object' but got type: ${schema.type ? `'${schema.type}'` : 'undefined'}`);
1
+ const JSON_SCHEMA_ANNOTATION_KEYWORDS = new Set([
2
+ '$comment',
3
+ 'default',
4
+ 'description',
5
+ 'examples',
6
+ 'readOnly',
7
+ 'title',
8
+ 'writeOnly',
9
+ ]);
10
+ const JSON_SCHEMA_ROOT_METADATA_KEYWORDS = new Set(['$id', '$schema']);
11
+ const JSON_SCHEMA_OBJECT_KEYWORDS = new Set([
12
+ 'additionalProperties',
13
+ 'dependencies',
14
+ 'maxProperties',
15
+ 'minProperties',
16
+ 'patternProperties',
17
+ 'properties',
18
+ 'propertyNames',
19
+ 'required',
20
+ ]);
21
+ const JSON_SCHEMA_SINGLE_SCHEMA_KEYWORDS = [
22
+ 'additionalItems',
23
+ 'additionalProperties',
24
+ 'contains',
25
+ 'contentSchema',
26
+ 'else',
27
+ 'if',
28
+ 'not',
29
+ 'propertyNames',
30
+ 'then',
31
+ 'unevaluatedItems',
32
+ 'unevaluatedProperties',
33
+ ];
34
+ const JSON_SCHEMA_ARRAY_SCHEMA_KEYWORDS = ['allOf', 'anyOf', 'items', 'oneOf', 'prefixItems'];
35
+ const JSON_SCHEMA_MAP_SCHEMA_KEYWORDS = [
36
+ '$defs',
37
+ 'definitions',
38
+ 'dependentSchemas',
39
+ 'dependencies',
40
+ 'patternProperties',
41
+ 'properties',
42
+ ];
43
+ const JSON_SCHEMA_UNSUPPORTED_SCHEMA_KEYWORDS = new Set([
44
+ '$anchor',
45
+ '$dynamicAnchor',
46
+ '$dynamicRef',
47
+ '$recursiveAnchor',
48
+ '$recursiveRef',
49
+ 'allOf',
50
+ 'contains',
51
+ 'contentEncoding',
52
+ 'contentMediaType',
53
+ 'contentSchema',
54
+ 'dependentRequired',
55
+ 'dependentSchemas',
56
+ 'dependencies',
57
+ 'else',
58
+ 'if',
59
+ 'maxContains',
60
+ 'maxProperties',
61
+ 'minContains',
62
+ 'minProperties',
63
+ 'not',
64
+ 'patternProperties',
65
+ 'prefixItems',
66
+ 'propertyNames',
67
+ 'then',
68
+ 'unevaluatedItems',
69
+ 'unevaluatedProperties',
70
+ 'uniqueItems',
71
+ ]);
72
+ const MERGEABLE_OBJECT_ALL_OF_KEYWORDS = new Set([
73
+ ...JSON_SCHEMA_ANNOTATION_KEYWORDS,
74
+ 'additionalProperties',
75
+ 'properties',
76
+ 'required',
77
+ 'type',
78
+ ]);
79
+ /**
80
+ * Visits only values carried by JSON Schema keywords that contain schemas.
81
+ * Literal payloads such as enum, const, and default deliberately do not
82
+ * participate.
83
+ */
84
+ export function forEachJSONSchemaChild(schema, path, visit) {
85
+ const record = schema;
86
+ for (const keyword of JSON_SCHEMA_SINGLE_SCHEMA_KEYWORDS) {
87
+ if (keyword in record) {
88
+ visit(record[keyword], [...path, keyword], keyword);
89
+ }
4
90
  }
91
+ for (const keyword of JSON_SCHEMA_ARRAY_SCHEMA_KEYWORDS) {
92
+ const children = record[keyword];
93
+ if (Array.isArray(children)) {
94
+ for (const [index, child] of children.entries()) {
95
+ visit(child, [...path, keyword, String(index)], keyword);
96
+ }
97
+ }
98
+ else if (children !== undefined) {
99
+ visit(children, [...path, keyword], keyword);
100
+ }
101
+ }
102
+ for (const keyword of JSON_SCHEMA_MAP_SCHEMA_KEYWORDS) {
103
+ const children = record[keyword];
104
+ if (!isObject(children))
105
+ continue;
106
+ for (const [key, child] of Object.entries(children)) {
107
+ // Draft 7 dependencies also permits property dependency arrays. They
108
+ // are not schemas and must not be traversed as literal JSON payloads.
109
+ if (keyword === 'dependencies' && !isSchemaDefinition(child))
110
+ continue;
111
+ visit(child, [...path, keyword, key], keyword);
112
+ }
113
+ }
114
+ }
115
+ export function toStrictJsonSchema(schema) {
5
116
  const schemaCopy = structuredClone(schema);
6
- return ensureStrictJsonSchema(schemaCopy, [], schemaCopy);
117
+ // JSON serialization omits undefined object properties. Drop optional
118
+ // placeholders before any structural checks so they cannot accidentally
119
+ // look like validation-bearing schema keywords.
120
+ stripUndefinedSchemaKeywords(schemaCopy);
121
+ normalizeSingletonTypeArrays(schemaCopy);
122
+ // Root ref/allOf normalization can promote a nested branch into the root.
123
+ // Reject separate resource scopes while their original nesting is still
124
+ // visible so branch-local definitions cannot be rebound at the root.
125
+ assertNoNestedSchemaIds(schemaCopy);
126
+ normalizeRootRefAndAllOf(schemaCopy);
127
+ if (schemaCopy.type !== 'object') {
128
+ throw new Error(`Root schema must have type: 'object' but got type: ${schemaCopy.type ? `'${schemaCopy.type}'` : 'undefined'}`);
129
+ }
130
+ if (schemaCopy.anyOf !== undefined) {
131
+ throw new Error('Root schema must not use `anyOf` because strict Structured Outputs requires a root object without a union.');
132
+ }
133
+ validateRefSchemas(schemaCopy, [], schemaCopy);
134
+ preserveAllOfRefTargets(schemaCopy);
135
+ validateRefSchemas(schemaCopy, [], schemaCopy);
136
+ rewriteLocalRefsIntoFilteredAnyOfBranches(schemaCopy);
137
+ // Resolve representable object intersections before recursive
138
+ // strictification closes referenced definitions. Otherwise a definition
139
+ // reached through an allOf ref would be closed in isolation before its
140
+ // sibling object properties can be merged.
141
+ normalizeObjectAllOfBranches(schemaCopy, [], schemaCopy);
142
+ const strictSchema = ensureStrictJsonSchema(schemaCopy, [], schemaCopy);
143
+ validateRefSchemas(strictSchema, [], strictSchema);
144
+ return strictSchema;
145
+ }
146
+ function stripUndefinedSchemaKeywords(schema, visited = new Set()) {
147
+ if (typeof schema === 'boolean' || !isObject(schema) || visited.has(schema)) {
148
+ return;
149
+ }
150
+ visited.add(schema);
151
+ const schemaRecord = schema;
152
+ for (const keyword of Object.keys(schemaRecord)) {
153
+ if (schemaRecord[keyword] === undefined) {
154
+ delete schemaRecord[keyword];
155
+ }
156
+ }
157
+ forEachJSONSchemaChild(schema, [], (child) => {
158
+ stripUndefinedSchemaKeywords(child, visited);
159
+ });
160
+ }
161
+ /**
162
+ * Root ref inlining and singleton allOf flattening can expose each other.
163
+ * Iterate until flattening no longer produces another root ref so every
164
+ * exactly representable chain reaches its final object form before the root
165
+ * type check runs.
166
+ */
167
+ function normalizeRootRefAndAllOf(schema) {
168
+ const seenRefs = new Set();
169
+ while (true) {
170
+ if (typeof schema.$ref === 'string') {
171
+ if (seenRefs.has(schema.$ref)) {
172
+ throw new Error('Cyclic local $ref at `<root>` is not supported: ' + JSON.stringify(schema.$ref));
173
+ }
174
+ seenRefs.add(schema.$ref);
175
+ }
176
+ inlineRootRefObject(schema);
177
+ preserveAllOfRefTargets(schema, true);
178
+ normalizeRootAllOf(schema);
179
+ const normalizedAnyOf = normalizeRootAnyOf(schema);
180
+ if (schema.$ref === undefined && !normalizedAnyOf) {
181
+ return;
182
+ }
183
+ }
184
+ }
185
+ /**
186
+ * Some Standard Schema converters emit the root object through a local ref,
187
+ * with the referenced schema stored in a root definition map. Structured
188
+ * Outputs requires the root itself to be an object, so inline that safe,
189
+ * definition-only form while keeping the root maps available for every local
190
+ * pointer in the schema.
191
+ */
192
+ function inlineRootRefObject(schema) {
193
+ let ref = schema.$ref;
194
+ if (ref === undefined) {
195
+ return;
196
+ }
197
+ assertLocalRootRef(ref);
198
+ if (!hasOnlyRootRefAndDefinitions(schema)) {
199
+ throw new Error('Schema $ref at `<root>` has non-metadata siblings that Draft 7 ignores and cannot be represented in strict Structured Outputs.');
200
+ }
201
+ const seenRefs = new Set();
202
+ // Ref siblings are annotations in Draft 7, so keep them while following
203
+ // aliases. Add outer annotations first so they win over inner aliases and
204
+ // the final target when the effective root is assembled.
205
+ const inheritedAnnotations = Object.fromEntries(Object.entries(schema).filter(([keyword]) => JSON_SCHEMA_ANNOTATION_KEYWORDS.has(keyword)));
206
+ let resolved;
207
+ while (true) {
208
+ if (seenRefs.has(ref)) {
209
+ throw new Error('Cyclic local $ref at `<root>` is not supported: ' + JSON.stringify(ref));
210
+ }
211
+ seenRefs.add(ref);
212
+ const target = resolveLocalRef(schema, ref);
213
+ if (target === undefined) {
214
+ throw new Error('Local $ref at `<root>` does not resolve to an object or boolean schema: ' + JSON.stringify(ref));
215
+ }
216
+ if (typeof target === 'boolean') {
217
+ throw new TypeError('Expected object schema but got boolean; path=<root>');
218
+ }
219
+ const nextRef = target.$ref;
220
+ if (nextRef === undefined) {
221
+ resolved = target;
222
+ break;
223
+ }
224
+ assertLocalRootRef(nextRef);
225
+ if (seenRefs.has(nextRef)) {
226
+ throw new Error('Cyclic local $ref at `<root>` is not supported: ' + JSON.stringify(nextRef));
227
+ }
228
+ if (!hasOnlyRefAndAnnotations(target)) {
229
+ throw new Error('Schema $ref in root chain has non-annotation siblings that Draft 7 ignores and cannot be represented in strict Structured Outputs.');
230
+ }
231
+ for (const keyword of JSON_SCHEMA_ANNOTATION_KEYWORDS) {
232
+ if (!(keyword in inheritedAnnotations) && keyword in target) {
233
+ inheritedAnnotations[keyword] = target[keyword];
234
+ }
235
+ }
236
+ ref = nextRef;
237
+ }
238
+ const rootDefinitions = schema.$defs;
239
+ const legacyDefinitions = schema.definitions;
240
+ const rootMetadata = Object.fromEntries(Object.entries(schema).filter(([keyword]) => JSON_SCHEMA_ANNOTATION_KEYWORDS.has(keyword) || JSON_SCHEMA_ROOT_METADATA_KEYWORDS.has(keyword)));
241
+ const inlined = structuredClone(resolved);
242
+ // A target's definition map lives below the target's original pointer, not
243
+ // at the document root. Keep the outer document map at the root so existing
244
+ // absolute refs retain their meaning; the retained outer map also keeps the
245
+ // target's nested map reachable at its original pointer.
246
+ for (const keyword of ['$defs', 'definitions']) {
247
+ if (schema[keyword] !== undefined && inlined[keyword] !== undefined) {
248
+ delete inlined[keyword];
249
+ }
250
+ }
251
+ const schemaRecord = schema;
252
+ for (const keyword of Object.keys(schema)) {
253
+ delete schemaRecord[keyword];
254
+ }
255
+ Object.assign(schema, inlined, inheritedAnnotations, rootMetadata);
256
+ if (rootDefinitions !== undefined) {
257
+ schema.$defs = rootDefinitions;
258
+ }
259
+ if (legacyDefinitions !== undefined) {
260
+ schema.definitions = legacyDefinitions;
261
+ }
262
+ }
263
+ /**
264
+ * Root object validation runs before recursive strictification, so normalize
265
+ * the same exactly representable allOf forms here that the recursive pass
266
+ * handles for nested schemas.
267
+ */
268
+ function normalizeRootAllOf(schema) {
269
+ while (schema.allOf !== undefined) {
270
+ // Root normalization runs before recursive strictification so the root
271
+ // type check can see an inlined object. Normalize nested intersections in
272
+ // its branches first for the same associative allOf case handled by the
273
+ // later recursive pass.
274
+ if (Array.isArray(schema.allOf)) {
275
+ for (const [index, branch] of schema.allOf.entries()) {
276
+ normalizeObjectAllOfBranches(branch, ['allOf', String(index)], schema);
277
+ }
278
+ }
279
+ if (mergeObjectAllOf(schema, [], schema)) {
280
+ // Removing neutral true branches can leave a singleton allOf behind;
281
+ // keep normalizing until the root reaches its final object form.
282
+ continue;
283
+ }
284
+ const allOf = schema.allOf;
285
+ if (!Array.isArray(allOf) || allOf.length !== 1 || !hasOnlyRootAllOfMetadataSiblings(schema)) {
286
+ return;
287
+ }
288
+ const branch = allOf[0];
289
+ if (typeof branch === 'boolean' || !isObject(branch)) {
290
+ return;
291
+ }
292
+ const rootMetadata = { ...schema };
293
+ delete rootMetadata.allOf;
294
+ const normalized = structuredClone(branch);
295
+ const schemaRecord = schema;
296
+ for (const keyword of Object.keys(schema)) {
297
+ delete schemaRecord[keyword];
298
+ }
299
+ Object.assign(schema, normalized, rootMetadata);
300
+ }
301
+ }
302
+ /**
303
+ * A singleton root anyOf with no validating siblings is equivalent to its
304
+ * only branch. Flatten it before the root-union check so converters that
305
+ * retain a redundant object wrapper can still produce a strict root object.
306
+ */
307
+ function normalizeRootAnyOf(schema) {
308
+ const anyOf = schema.anyOf;
309
+ if (!Array.isArray(anyOf) || !hasOnlyRootAnyOfMetadataSiblings(schema)) {
310
+ return false;
311
+ }
312
+ // `false` contributes no instances to a union. Keep the original array in
313
+ // place until promotion so refs into the surviving branch can be rewritten
314
+ // from their original index, while refs into removed false branches become
315
+ // dangling and remain fail closed after the wrapper disappears.
316
+ const realBranches = anyOf
317
+ .map((branch, index) => ({ branch, index }))
318
+ .filter(({ branch }) => branch !== false);
319
+ if (realBranches.length !== 1) {
320
+ return false;
321
+ }
322
+ const { branch, index: branchIndex } = realBranches[0];
323
+ if (typeof branch === 'boolean' || !isObject(branch) || !isObjectOnlySchema(branch, schema)) {
324
+ return false;
325
+ }
326
+ const definitionRenames = planPromotedRootAnyOfDefinitionRenames(schema, branch);
327
+ rewriteLocalRefsIntoPromotedRootAnyOfBranch(schema, branchIndex, definitionRenames);
328
+ const rootMetadata = { ...schema };
329
+ delete rootMetadata.anyOf;
330
+ const normalized = structuredClone(branch);
331
+ for (const keyword of ['$defs', 'definitions']) {
332
+ const rootDefinitions = schema[keyword];
333
+ const branchDefinitions = normalized[keyword];
334
+ if (!isObject(rootDefinitions) || !isObject(branchDefinitions)) {
335
+ continue;
336
+ }
337
+ const renames = definitionRenames.get(keyword);
338
+ const mergedDefinitions = { ...rootDefinitions };
339
+ for (const [name, definition] of Object.entries(branchDefinitions)) {
340
+ mergedDefinitions[renames?.get(name) ?? name] = definition;
341
+ }
342
+ normalized[keyword] = mergedDefinitions;
343
+ delete rootMetadata[keyword];
344
+ }
345
+ const schemaRecord = schema;
346
+ for (const keyword of Object.keys(schema)) {
347
+ delete schemaRecord[keyword];
348
+ }
349
+ Object.assign(schema, normalized, rootMetadata);
350
+ return true;
351
+ }
352
+ /**
353
+ * Root and promoted branch definition maps occupy the same pointer after
354
+ * promotion. Give conflicting branch definitions stable aliases before refs
355
+ * are rewritten so neither original target is rebound.
356
+ */
357
+ function planPromotedRootAnyOfDefinitionRenames(root, branch) {
358
+ const renames = new Map();
359
+ for (const keyword of ['$defs', 'definitions']) {
360
+ const rootDefinitions = root[keyword];
361
+ const branchDefinitions = branch[keyword];
362
+ if (!isObject(rootDefinitions) || !isObject(branchDefinitions)) {
363
+ continue;
364
+ }
365
+ const usedNames = new Set([...Object.keys(rootDefinitions), ...Object.keys(branchDefinitions)]);
366
+ const keywordRenames = new Map();
367
+ let aliasIndex = 0;
368
+ for (const [name, definition] of Object.entries(branchDefinitions)) {
369
+ if (!Object.prototype.hasOwnProperty.call(rootDefinitions, name) ||
370
+ schemasEqual(rootDefinitions[name], definition)) {
371
+ continue;
372
+ }
373
+ let alias = '__openai_strict_anyOf_definition_' + aliasIndex++;
374
+ while (usedNames.has(alias)) {
375
+ alias = '__openai_strict_anyOf_definition_' + aliasIndex++;
376
+ }
377
+ usedNames.add(alias);
378
+ keywordRenames.set(name, alias);
379
+ }
380
+ if (keywordRenames.size > 0) {
381
+ renames.set(keyword, keywordRenames);
382
+ }
383
+ }
384
+ return renames;
385
+ }
386
+ /**
387
+ * Promoting a singleton root anyOf branch removes the original anyOf/index
388
+ * pointer prefix. Rewrite refs through that prefix while the old tree still
389
+ * exists so the promoted schema keeps naming the same targets.
390
+ */
391
+ function rewriteLocalRefsIntoPromotedRootAnyOfBranch(root, branchIndex, definitionRenames) {
392
+ const rewriteRef = (ref) => {
393
+ const parts = parseLocalRef(ref);
394
+ if (parts === undefined || parts[0] !== 'anyOf' || parts[1] !== String(branchIndex)) {
395
+ return ref;
396
+ }
397
+ const promotedParts = parts.slice(2);
398
+ const definitionKeyword = promotedParts[0];
399
+ if (promotedParts.length > 1 && (definitionKeyword === '$defs' || definitionKeyword === 'definitions')) {
400
+ const renamed = definitionRenames.get(definitionKeyword)?.get(promotedParts[1]);
401
+ if (renamed !== undefined) {
402
+ promotedParts[1] = renamed;
403
+ }
404
+ }
405
+ return promotedParts.length === 0 ?
406
+ '#'
407
+ : '#/' + promotedParts.map(encodeJSONPointerTokenForURIFragment).join('/');
408
+ };
409
+ const rewriteRefs = (value) => {
410
+ if (typeof value === 'boolean' || !isObject(value)) {
411
+ return;
412
+ }
413
+ if (typeof value.$ref === 'string') {
414
+ value.$ref = rewriteRef(value.$ref);
415
+ }
416
+ forEachJSONSchemaChild(value, [], (child) => {
417
+ rewriteRefs(child);
418
+ });
419
+ };
420
+ rewriteRefs(root);
421
+ }
422
+ function assertLocalRootRef(ref) {
423
+ if (typeof ref !== 'string') {
424
+ throw new TypeError('Received non-string $ref - ' + String(ref) + '; path=<root>');
425
+ }
426
+ if (!ref.startsWith('#')) {
427
+ throw new Error('External $ref at `<root>` is not supported in strict Structured Outputs: ' + JSON.stringify(ref));
428
+ }
7
429
  }
8
- function isNullable(schema) {
430
+ function hasOnlyRootRefAndDefinitions(schema) {
431
+ return Object.keys(schema).every((keyword) => keyword === '$ref' ||
432
+ keyword === '$defs' ||
433
+ keyword === 'definitions' ||
434
+ JSON_SCHEMA_ROOT_METADATA_KEYWORDS.has(keyword) ||
435
+ JSON_SCHEMA_ANNOTATION_KEYWORDS.has(keyword));
436
+ }
437
+ /**
438
+ * Draft 7 permits `type` to be either a string or an array of strings. A
439
+ * singleton array has exactly the same validation semantics as its scalar
440
+ * form, so canonicalize it before root validation and recursive strictifying.
441
+ * Multi-type arrays carry real union semantics and must remain unchanged.
442
+ */
443
+ function normalizeSingletonTypeArrays(schema) {
444
+ if (typeof schema === 'boolean' || !isObject(schema)) {
445
+ return;
446
+ }
447
+ if (Array.isArray(schema.type) && schema.type.length === 1) {
448
+ schema.type = schema.type[0];
449
+ }
450
+ forEachJSONSchemaChild(schema, [], (child) => {
451
+ normalizeSingletonTypeArrays(child);
452
+ });
453
+ }
454
+ function isNullable(schema, root, seenRefs = new Set()) {
9
455
  if (typeof schema === 'boolean') {
456
+ return schema;
457
+ }
458
+ const ref = schema.$ref;
459
+ if (ref !== undefined) {
460
+ // Annotation keywords do not constrain validation, so they are safe beside
461
+ // a local ref. Keep the proof conservative for every other sibling because
462
+ // resolving those correctly would require intersecting the referenced
463
+ // schema and its sibling constraints.
464
+ if (typeof ref !== 'string' || !hasOnlyRefAndAnnotations(schema) || seenRefs.has(ref)) {
465
+ return false;
466
+ }
467
+ const resolved = resolveLocalRef(root, ref);
468
+ if (resolved === undefined) {
469
+ return false;
470
+ }
471
+ return isNullable(resolved, root, new Set([...seenRefs, ref]));
472
+ }
473
+ if (schema.type !== undefined &&
474
+ schema.type !== 'null' &&
475
+ !(Array.isArray(schema.type) && schema.type.includes('null'))) {
10
476
  return false;
11
477
  }
12
- if (schema.type === 'null') {
13
- return true;
478
+ if ('const' in schema && schema.const !== null) {
479
+ return false;
14
480
  }
15
- for (const oneOfVariant of schema.oneOf ?? []) {
16
- if (isNullable(oneOfVariant)) {
17
- return true;
481
+ if (schema.enum !== undefined && (!Array.isArray(schema.enum) || !schema.enum.includes(null))) {
482
+ return false;
483
+ }
484
+ if (schema.allOf !== undefined) {
485
+ if (!Array.isArray(schema.allOf) || !schema.allOf.every((variant) => isNullable(variant, root))) {
486
+ return false;
18
487
  }
19
488
  }
20
- for (const allOfVariant of schema.anyOf ?? []) {
21
- if (isNullable(allOfVariant)) {
22
- return true;
489
+ if (schema.anyOf !== undefined) {
490
+ if (!Array.isArray(schema.anyOf) || !schema.anyOf.some((variant) => isNullable(variant, root))) {
491
+ return false;
23
492
  }
24
493
  }
25
- return false;
494
+ if (schema.oneOf !== undefined) {
495
+ if (!Array.isArray(schema.oneOf) ||
496
+ schema.oneOf.filter((variant) => isNullable(variant, root)).length !== 1) {
497
+ return false;
498
+ }
499
+ }
500
+ // Conditional and negated schemas need a full JSON Schema evaluator to prove
501
+ // that null is allowed. Treat them conservatively instead of accepting an
502
+ // optional field that may not actually accept null.
503
+ if (schema.not !== undefined ||
504
+ schema.if !== undefined ||
505
+ schema.then !== undefined ||
506
+ schema.else !== undefined) {
507
+ return false;
508
+ }
509
+ return true;
26
510
  }
27
511
  /**
28
512
  * Mutates the given JSON schema to ensure it conforms to the `strict` standard
@@ -35,117 +519,1066 @@ function ensureStrictJsonSchema(jsonSchema, path, root) {
35
519
  if (!isObject(jsonSchema)) {
36
520
  throw new TypeError(`Expected ${JSON.stringify(jsonSchema)} to be an object; path=${path.join('/')}`);
37
521
  }
38
- // Handle $defs (non-standard but sometimes used)
39
- const defs = jsonSchema.$defs;
40
- if (isObject(defs)) {
41
- for (const [defName, defSchema] of Object.entries(defs)) {
42
- ensureStrictJsonSchema(defSchema, [...path, '$defs', defName], root);
43
- }
522
+ // Closing each object branch in an allOf independently changes the
523
+ // intersection: sibling branches' properties become forbidden extras. Merge
524
+ // the small object-intersection subset that can be represented exactly before
525
+ // applying strict object closure, and fail closed for the rest.
526
+ if (mergeObjectAllOf(jsonSchema, path, root)) {
527
+ return ensureStrictJsonSchema(jsonSchema, path, root);
44
528
  }
45
- // Handle definitions (draft-04 style, deprecated in draft-07 but still used)
46
- const definitions = jsonSchema.definitions;
47
- if (isObject(definitions)) {
48
- for (const [definitionName, definitionSchema] of Object.entries(definitions)) {
49
- ensureStrictJsonSchema(definitionSchema, [...path, 'definitions', definitionName], root);
529
+ // false is the identity element for a union. Remove it before proving
530
+ // whether an outer object/array wrapper is redundant so an impossible
531
+ // alternative cannot hide the shape of every real branch. Keep all-false
532
+ // and boolean-only unions intact so the existing boolean-schema rejection
533
+ // remains fail closed.
534
+ normalizeAnyOfFalseBranches(jsonSchema);
535
+ // Closing a type: object wrapper around object union branches without any
536
+ // own properties would turn it into an empty object and forbid every branch
537
+ // property. A bare object type is redundant when every branch already proves
538
+ // the value is an object, so remove only that exact constraint; fail closed
539
+ // for wrappers whose object constraints cannot be preserved mechanically.
540
+ normalizeObjectUnionWrapper(jsonSchema, path, root);
541
+ // Add additionalProperties: false to object schemas. Draft 7 permits object
542
+ // keywords without an explicit type, so those implicit object shapes need
543
+ // the same strict handling as type: 'object'. Explicitly open object schemas
544
+ // cannot be represented in Structured Outputs strict mode.
545
+ if (hasObjectShape(jsonSchema)) {
546
+ if (!('additionalProperties' in jsonSchema)) {
547
+ jsonSchema.additionalProperties = false;
548
+ }
549
+ else if (jsonSchema.additionalProperties !== false) {
550
+ throw new Error(`Object schema at \`${path.join('/') || '<root>'}\` must set \`additionalProperties: false\` to be compatible with strict Structured Outputs.`);
50
551
  }
51
- }
52
- // Add additionalProperties: false to object types
53
- const typ = jsonSchema.type;
54
- if (typ === 'object' && !('additionalProperties' in jsonSchema)) {
55
- jsonSchema.additionalProperties = false;
56
552
  }
57
553
  const required = jsonSchema.required ?? [];
554
+ if (!Array.isArray(required) || required.some((key) => typeof key !== 'string')) {
555
+ throw new TypeError(`Expected \`required\` to be an array of strings; path=${path.join('/') || '<root>'}`);
556
+ }
58
557
  // Handle object properties
59
558
  const properties = jsonSchema.properties;
559
+ if (hasObjectShape(jsonSchema)) {
560
+ for (const key of required) {
561
+ if (!isObject(properties) || !Object.prototype.hasOwnProperty.call(properties, key)) {
562
+ throw new Error(`Object schema at \`${path.join('/') || '<root>'}\` requires property \`${key}\` but does not declare it in \`properties\`.`);
563
+ }
564
+ }
565
+ }
60
566
  if (isObject(properties)) {
61
567
  for (const [key, value] of Object.entries(properties)) {
62
- if (!isNullable(value) && !required.includes(key)) {
63
- 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`);
568
+ if (!isNullable(value, root) && !required.includes(key)) {
569
+ throw new Error(`Schema 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`);
64
570
  }
65
571
  }
66
572
  jsonSchema.required = Object.keys(properties);
67
- jsonSchema.properties = Object.fromEntries(Object.entries(properties).map(([key, propSchema]) => [
68
- key,
69
- ensureStrictJsonSchema(propSchema, [...path, 'properties', key], root),
70
- ]));
71
573
  }
72
- // Handle arrays
574
+ // Structured Outputs accepts one schema for every array item and does not
575
+ // support Draft 7 tuples or additionalItems. Reject both forms rather than
576
+ // advertising a schema whose array validation the API cannot preserve.
73
577
  const items = jsonSchema.items;
74
- if (isObject(items)) {
75
- jsonSchema.items = ensureStrictJsonSchema(items, [...path, 'items'], root);
578
+ const additionalItems = jsonSchema.additionalItems;
579
+ if (Array.isArray(items)) {
580
+ throw new Error(`Schema at \`${path.join('/') || '<root>'}\` uses tuple-form \`items\`, which cannot be represented in strict Structured Outputs.`);
76
581
  }
77
- // Handle unions (anyOf)
78
- const anyOf = jsonSchema.anyOf;
79
- if (Array.isArray(anyOf)) {
80
- jsonSchema.anyOf = anyOf.map((variant, i) => ensureStrictJsonSchema(variant, [...path, 'anyOf', String(i)], root));
582
+ if (additionalItems !== undefined) {
583
+ throw new Error(`Schema at \`${path.join('/') || '<root>'}\` uses unsupported keyword \`additionalItems\` and cannot be represented in strict Structured Outputs.`);
81
584
  }
82
585
  // Handle intersections (allOf)
83
586
  const allOf = jsonSchema.allOf;
84
587
  if (Array.isArray(allOf)) {
85
- if (allOf.length === 1) {
86
- const resolved = ensureStrictJsonSchema(allOf[0], [...path, 'allOf', '0'], root);
87
- Object.assign(jsonSchema, resolved);
88
- delete jsonSchema.allOf;
588
+ if (allOf.length === 1 && hasOnlyAnnotationSiblings(jsonSchema, 'allOf')) {
589
+ const branch = allOf[0];
590
+ if (branch === false) {
591
+ throw new Error(`Schema at \`${path.join('/') || '<root>'}\` uses \`allOf: [false]\`, which cannot be represented in strict Structured Outputs.`);
592
+ }
593
+ if (branch === true) {
594
+ // true is the neutral schema for an intersection, so removing this
595
+ // branch preserves validation while retaining the parent annotations.
596
+ delete jsonSchema.allOf;
597
+ }
598
+ else {
599
+ const resolved = ensureStrictJsonSchema(branch, [...path, 'allOf', '0'], root);
600
+ const annotations = { ...jsonSchema };
601
+ delete annotations.allOf;
602
+ Object.assign(jsonSchema, resolved, annotations);
603
+ delete jsonSchema.allOf;
604
+ }
89
605
  }
90
- else {
91
- jsonSchema.allOf = allOf.map((entry, i) => ensureStrictJsonSchema(entry, [...path, 'allOf', String(i)], root));
606
+ }
607
+ normalizeArrayUnionWrapper(jsonSchema, root);
608
+ const schemaRecord = jsonSchema;
609
+ for (const keyword of JSON_SCHEMA_UNSUPPORTED_SCHEMA_KEYWORDS) {
610
+ // Optional converter output often keeps undefined placeholders on the
611
+ // JavaScript object even though JSON serialization omits them. They carry
612
+ // no validation semantics, so treat only undefined as absent; every
613
+ // defined value remains unsupported.
614
+ if (schemaRecord[keyword] !== undefined) {
615
+ throw new Error(`Schema at \`${path.join('/') || '<root>'}\` uses unsupported keyword \`${keyword}\` and cannot be represented in strict Structured Outputs.`);
92
616
  }
617
+ delete schemaRecord[keyword];
618
+ }
619
+ const type = jsonSchema.type;
620
+ const currentItems = jsonSchema.items;
621
+ if ((type === 'array' || (Array.isArray(type) && type.includes('array'))) && currentItems === undefined) {
622
+ throw new Error(`Schema at \`${path.join('/') || '<root>'}\` declares an array without \`items\`, which cannot be represented in strict Structured Outputs.`);
93
623
  }
624
+ forEachJSONSchemaChild(jsonSchema, path, (child, childPath, keyword) => {
625
+ // These boolean forms are already handled as parent-keyword semantics:
626
+ // additionalProperties: false closes objects, while boolean
627
+ // additionalItems does not contain a nested schema to strictify.
628
+ if (typeof child === 'boolean' && (keyword === 'additionalProperties' || keyword === 'additionalItems')) {
629
+ return;
630
+ }
631
+ ensureStrictJsonSchema(child, childPath, root);
632
+ });
94
633
  // Strip `null` defaults as there's no meaningful distinction
95
634
  if (jsonSchema.default === null) {
96
635
  delete jsonSchema.default;
97
636
  }
98
- // Handle $ref with additional properties
99
- const ref = jsonSchema.$ref;
100
- if (ref && hasMoreThanNKeys(jsonSchema, 1)) {
101
- if (typeof ref !== 'string') {
102
- throw new TypeError(`Received non-string $ref - ${ref}; path=${path.join('/')}`);
637
+ return jsonSchema;
638
+ }
639
+ function parseLocalRef(ref) {
640
+ if (!ref.startsWith('#')) {
641
+ return undefined;
642
+ }
643
+ let pointer;
644
+ try {
645
+ // A local $ref is a URI fragment containing a JSON Pointer. RFC 6901
646
+ // decodes the complete fragment before tokenizing the pointer, so an
647
+ // encoded slash is a separator rather than part of a literal key.
648
+ pointer = decodeURIComponent(ref.slice(1));
649
+ }
650
+ catch {
651
+ return undefined;
652
+ }
653
+ if (pointer === '') {
654
+ return [];
655
+ }
656
+ if (!pointer.startsWith('/')) {
657
+ return undefined;
658
+ }
659
+ const parts = [];
660
+ for (const encodedPart of pointer.slice(1).split('/')) {
661
+ // JSON Pointer only defines ~0 and ~1 escapes. Reject malformed escape
662
+ // sequences instead of looking up a different literal key.
663
+ if (/~(?:[^01]|$)/.test(encodedPart)) {
664
+ return undefined;
103
665
  }
104
- const resolved = resolveRef(root, ref);
105
- if (typeof resolved === 'boolean') {
106
- throw new Error(`Expected \`$ref: ${ref}\` to resolve to an object schema but got boolean`);
666
+ parts.push(encodedPart.replace(/~1/g, '/').replace(/~0/g, '~'));
667
+ }
668
+ return parts;
669
+ }
670
+ function resolvePointerPart(resolved, part) {
671
+ if (Array.isArray(resolved)) {
672
+ if (!/^(?:0|[1-9]\d*)$/.test(part)) {
673
+ return undefined;
107
674
  }
108
- if (!isObject(resolved)) {
109
- throw new Error(`Expected \`$ref: ${ref}\` to resolve to an object but got ${JSON.stringify(resolved)}`);
675
+ const index = Number(part);
676
+ if (!Object.prototype.hasOwnProperty.call(resolved, index)) {
677
+ return undefined;
110
678
  }
111
- // Properties from the json schema take priority over the ones on the `$ref`
112
- Object.assign(jsonSchema, { ...resolved, ...jsonSchema });
113
- delete jsonSchema.$ref;
114
- // Since the schema expanded from `$ref` might not have `additionalProperties: false` applied,
115
- // we call `ensureStrictJsonSchema` again to fix the inlined schema and ensure it's valid.
116
- return ensureStrictJsonSchema(jsonSchema, path, root);
679
+ return resolved[index];
117
680
  }
118
- return jsonSchema;
681
+ if (!isObject(resolved) || !Object.prototype.hasOwnProperty.call(resolved, part)) {
682
+ return undefined;
683
+ }
684
+ return resolved[part];
119
685
  }
120
- function resolveRef(root, ref) {
121
- if (!ref.startsWith('#/')) {
122
- throw new Error(`Unexpected $ref format ${JSON.stringify(ref)}; Does not start with #/`);
686
+ export function resolveLocalRef(root, ref) {
687
+ const parts = parseLocalRef(ref);
688
+ if (parts === undefined) {
689
+ return undefined;
123
690
  }
124
- const pathParts = ref.slice(2).split('/');
691
+ // Literal payloads such as `default`, `enum`, and `const` can contain
692
+ // object-shaped values, but they are not schemas and are never traversed by
693
+ // strictification. Resolve only the schema-bearing locations visited by
694
+ // forEachJSONSchemaChild so every accepted target is normalized before we
695
+ // advertise the result as strict.
125
696
  let resolved = root;
126
- for (const key of pathParts) {
697
+ for (let index = 0; index < parts.length;) {
127
698
  if (!isObject(resolved)) {
128
- throw new Error(`encountered non-object entry while resolving ${ref} - ${JSON.stringify(resolved)}`);
699
+ return undefined;
700
+ }
701
+ const keyword = parts[index];
702
+ if (JSON_SCHEMA_SINGLE_SCHEMA_KEYWORDS.includes(keyword)) {
703
+ resolved = resolvePointerPart(resolved, keyword);
704
+ index += 1;
705
+ continue;
706
+ }
707
+ if (JSON_SCHEMA_ARRAY_SCHEMA_KEYWORDS.includes(keyword)) {
708
+ resolved = resolvePointerPart(resolved, keyword);
709
+ index += 1;
710
+ if (Array.isArray(resolved)) {
711
+ if (index >= parts.length) {
712
+ return undefined;
713
+ }
714
+ resolved = resolvePointerPart(resolved, parts[index]);
715
+ index += 1;
716
+ }
717
+ continue;
129
718
  }
130
- const value = resolved[key];
131
- if (value === undefined) {
132
- throw new Error(`Key ${key} not found while resolving ${ref}`);
719
+ if (JSON_SCHEMA_MAP_SCHEMA_KEYWORDS.includes(keyword)) {
720
+ const children = resolvePointerPart(resolved, keyword);
721
+ index += 1;
722
+ if (!isObject(children) || index >= parts.length) {
723
+ return undefined;
724
+ }
725
+ resolved = resolvePointerPart(children, parts[index]);
726
+ if (keyword === 'dependencies' && !isSchemaDefinition(resolved)) {
727
+ return undefined;
728
+ }
729
+ index += 1;
730
+ continue;
133
731
  }
134
- resolved = value;
732
+ return undefined;
135
733
  }
136
- return resolved;
734
+ return isSchemaDefinition(resolved) ? resolved : undefined;
137
735
  }
138
736
  function isObject(obj) {
139
737
  return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
140
738
  }
141
- function hasMoreThanNKeys(obj, n) {
142
- let i = 0;
143
- for (const _ in obj) {
144
- i++;
145
- if (i > n) {
739
+ function isSchemaDefinition(value) {
740
+ return typeof value === 'boolean' || isObject(value);
741
+ }
742
+ function isObjectOnlySchema(schema, root, seenRefs = new Set()) {
743
+ if (typeof schema === 'boolean' || !isObject(schema)) {
744
+ return false;
745
+ }
746
+ if (schema.$ref !== undefined) {
747
+ if (typeof schema.$ref !== 'string' || !hasOnlyRefAndAnnotations(schema) || seenRefs.has(schema.$ref)) {
748
+ return false;
749
+ }
750
+ const resolved = resolveLocalRef(root, schema.$ref);
751
+ if (resolved === undefined) {
752
+ return false;
753
+ }
754
+ return isObjectOnlySchema(resolved, root, new Set([...seenRefs, schema.$ref]));
755
+ }
756
+ if (schema.allOf !== undefined) {
757
+ if (!Array.isArray(schema.allOf) ||
758
+ schema.allOf.length !== 1 ||
759
+ !hasOnlyAnnotationSiblings(schema, 'allOf')) {
760
+ return false;
761
+ }
762
+ const branch = schema.allOf[0];
763
+ return branch !== undefined && branch !== true && branch !== false ?
764
+ isObjectOnlySchema(branch, root, seenRefs)
765
+ : false;
766
+ }
767
+ return (schema.type === 'object' ||
768
+ (Array.isArray(schema.type) && schema.type.length === 1 && schema.type[0] === 'object'));
769
+ }
770
+ function isArrayOnlySchema(schema, root, seenRefs = new Set()) {
771
+ if (typeof schema === 'boolean' || !isObject(schema)) {
772
+ return false;
773
+ }
774
+ if (schema.$ref !== undefined) {
775
+ if (typeof schema.$ref !== 'string' || !hasOnlyRefAndAnnotations(schema) || seenRefs.has(schema.$ref)) {
776
+ return false;
777
+ }
778
+ const resolved = resolveLocalRef(root, schema.$ref);
779
+ if (resolved === undefined) {
780
+ return false;
781
+ }
782
+ return isArrayOnlySchema(resolved, root, new Set([...seenRefs, schema.$ref]));
783
+ }
784
+ if (schema.allOf !== undefined) {
785
+ if (!Array.isArray(schema.allOf) ||
786
+ schema.allOf.length !== 1 ||
787
+ !hasOnlyAnnotationSiblings(schema, 'allOf')) {
788
+ return false;
789
+ }
790
+ const branch = schema.allOf[0];
791
+ return branch !== undefined && branch !== true && branch !== false ?
792
+ isArrayOnlySchema(branch, root, seenRefs)
793
+ : false;
794
+ }
795
+ return (schema.type === 'array' ||
796
+ (Array.isArray(schema.type) && schema.type.length === 1 && schema.type[0] === 'array'));
797
+ }
798
+ export function hasOnlyRefAndAnnotations(schema) {
799
+ return Object.keys(schema).every(
800
+ // Definition maps do not add sibling validation constraints, and keeping
801
+ // them in place preserves local pointers into a nested alias's scope.
802
+ (keyword) => keyword === '$ref' ||
803
+ keyword === '$defs' ||
804
+ keyword === 'definitions' ||
805
+ JSON_SCHEMA_ANNOTATION_KEYWORDS.has(keyword));
806
+ }
807
+ function hasOnlyAnnotationSiblings(schema, keyword) {
808
+ const schemaRecord = schema;
809
+ return Object.keys(schema).every(
810
+ // Definition maps do not add sibling validation constraints. Keep them
811
+ // beside a flattened singleton allOf so refs into this nested scope stay
812
+ // reachable at their original pointers.
813
+ (schemaKeyword) => schemaKeyword === keyword ||
814
+ ((schemaKeyword === '$defs' || schemaKeyword === 'definitions') &&
815
+ isObject(schemaRecord[schemaKeyword])) ||
816
+ JSON_SCHEMA_ANNOTATION_KEYWORDS.has(schemaKeyword));
817
+ }
818
+ function hasOnlyRootAllOfMetadataSiblings(schema) {
819
+ return Object.keys(schema).every((keyword) => keyword === 'allOf' ||
820
+ keyword === '$defs' ||
821
+ keyword === 'definitions' ||
822
+ JSON_SCHEMA_ROOT_METADATA_KEYWORDS.has(keyword) ||
823
+ JSON_SCHEMA_ANNOTATION_KEYWORDS.has(keyword));
824
+ }
825
+ function hasOnlyRootAnyOfMetadataSiblings(schema) {
826
+ return Object.keys(schema).every((keyword) => keyword === 'anyOf' ||
827
+ keyword === '$defs' ||
828
+ keyword === 'definitions' ||
829
+ (keyword === 'type' && schema.type === 'object') ||
830
+ JSON_SCHEMA_ROOT_METADATA_KEYWORDS.has(keyword) ||
831
+ JSON_SCHEMA_ANNOTATION_KEYWORDS.has(keyword));
832
+ }
833
+ function hasObjectKeywords(schema) {
834
+ return Object.keys(schema).some((keyword) => JSON_SCHEMA_OBJECT_KEYWORDS.has(keyword));
835
+ }
836
+ function hasObjectShape(schema) {
837
+ const typ = schema.type;
838
+ return (typ === 'object' ||
839
+ (Array.isArray(typ) && typ.includes('object')) ||
840
+ (typ === undefined && hasObjectKeywords(schema)));
841
+ }
842
+ function isRedundantUnionWrapperType(type, branchType) {
843
+ return (type === branchType ||
844
+ (Array.isArray(type) && type.length === 2 && type.includes(branchType) && type.includes('null')));
845
+ }
846
+ function normalizeObjectUnionWrapper(jsonSchema, path, root) {
847
+ if (jsonSchema.anyOf === undefined) {
848
+ return;
849
+ }
850
+ const hasEmptyProperties = isObject(jsonSchema.properties) && Object.keys(jsonSchema.properties).length === 0;
851
+ const hasEmptyRequired = Array.isArray(jsonSchema.required) && jsonSchema.required.length === 0;
852
+ if (hasEmptyProperties) {
853
+ delete jsonSchema.properties;
854
+ }
855
+ if (hasEmptyRequired) {
856
+ delete jsonSchema.required;
857
+ }
858
+ if (!hasObjectShape(jsonSchema)) {
859
+ return;
860
+ }
861
+ const hasOwnObjectConstraints = Object.keys(jsonSchema).some((keyword) => JSON_SCHEMA_OBJECT_KEYWORDS.has(keyword));
862
+ if (isRedundantUnionWrapperType(jsonSchema.type, 'object') &&
863
+ !hasOwnObjectConstraints &&
864
+ Array.isArray(jsonSchema.anyOf) &&
865
+ jsonSchema.anyOf.every((branch) => isObjectOnlySchema(branch, root))) {
866
+ // The union already excludes null and every non-object value, so both a
867
+ // scalar object wrapper and a nullable object wrapper are redundant under
868
+ // Draft 7's conjunctive keyword semantics.
869
+ delete jsonSchema.type;
870
+ return;
871
+ }
872
+ throw new Error('Object anyOf schema at `' +
873
+ (path.join('/') || '<root>') +
874
+ '` cannot be represented in strict Structured Outputs without changing Draft 7 validation.');
875
+ }
876
+ function normalizeArrayUnionWrapper(jsonSchema, root) {
877
+ if (isRedundantUnionWrapperType(jsonSchema.type, 'array') &&
878
+ jsonSchema.items === undefined &&
879
+ Array.isArray(jsonSchema.anyOf) &&
880
+ jsonSchema.anyOf.every((branch) => isArrayOnlySchema(branch, root))) {
881
+ // Every union branch already proves the value is an array and excludes
882
+ // null. Keeping either redundant wrapper would require an outer items
883
+ // schema that does not contribute any Draft 7 validation.
884
+ delete jsonSchema.type;
885
+ }
886
+ }
887
+ function normalizeAnyOfFalseBranches(jsonSchema) {
888
+ if (!Array.isArray(jsonSchema.anyOf)) {
889
+ return;
890
+ }
891
+ const realBranches = jsonSchema.anyOf.filter((branch) => branch !== false);
892
+ if (realBranches.length > 0 && realBranches.length !== jsonSchema.anyOf.length) {
893
+ jsonSchema.anyOf = realBranches;
894
+ }
895
+ }
896
+ export function assertNoNestedSchemaIds(schema) {
897
+ const visit = (value, path) => {
898
+ if (typeof value === 'boolean' || !isObject(value)) {
899
+ return;
900
+ }
901
+ if (path.length > 0 && value.$id !== undefined) {
902
+ throw new Error('Nested $id at ' +
903
+ JSON.stringify(path.join('/')) +
904
+ ' establishes a separate JSON Schema resource scope and cannot be represented in strict Structured Outputs.');
905
+ }
906
+ forEachJSONSchemaChild(value, path, (child, childPath) => {
907
+ visit(child, childPath);
908
+ });
909
+ };
910
+ visit(schema, []);
911
+ }
912
+ function refTargetsAllOfBranch(root, ref) {
913
+ const parts = parseLocalRef(ref);
914
+ if (parts === undefined) {
915
+ return false;
916
+ }
917
+ let resolved = root;
918
+ for (const [index, part] of parts.entries()) {
919
+ if (part === 'allOf' &&
920
+ isObject(resolved) &&
921
+ Array.isArray(resolved['allOf']) &&
922
+ index < parts.length - 1) {
146
923
  return true;
147
924
  }
925
+ resolved = resolvePointerPart(resolved, part);
926
+ if (resolved === undefined) {
927
+ return false;
928
+ }
148
929
  }
149
930
  return false;
150
931
  }
932
+ function escapeJSONPointerToken(token) {
933
+ return token.replace(/~/g, '~0').replace(/\//g, '~1');
934
+ }
935
+ function encodeJSONPointerTokenForURIFragment(token) {
936
+ // `$` is a valid URI fragment sub-delimiter and keeping it readable retains
937
+ // the conventional `#/$defs/...` spelling. Everything else that could
938
+ // invalidate or retokenize the fragment (notably `%` and spaces) is encoded
939
+ // after JSON Pointer escaping.
940
+ return encodeURIComponent(escapeJSONPointerToken(token)).replace(/%24/g, '$');
941
+ }
942
+ /**
943
+ * Standard Schema normalization moves representable oneOf branches to anyOf.
944
+ * Rewrite only pointers that traverse an actual oneOf schema array while the
945
+ * original tree is still intact, preserving escaped tokens for every other
946
+ * path segment.
947
+ */
948
+ export function rewriteLocalRefsIntoMovedOneOfBranches(root) {
949
+ const rewriteRef = (ref) => {
950
+ const parts = parseLocalRef(ref);
951
+ if (parts === undefined || parts.length === 0) {
952
+ return ref;
953
+ }
954
+ let resolved = root;
955
+ let changed = false;
956
+ for (const [index, part] of parts.entries()) {
957
+ if (part === 'oneOf' &&
958
+ index < parts.length - 1 &&
959
+ isObject(resolved) &&
960
+ Array.isArray(resolved['oneOf'])) {
961
+ parts[index] = 'anyOf';
962
+ changed = true;
963
+ }
964
+ resolved = resolvePointerPart(resolved, part);
965
+ if (resolved === undefined) {
966
+ return ref;
967
+ }
968
+ }
969
+ return changed ? '#/' + parts.map(encodeJSONPointerTokenForURIFragment).join('/') : ref;
970
+ };
971
+ const rewriteRefs = (value) => {
972
+ if (typeof value === 'boolean' || !isObject(value)) {
973
+ return;
974
+ }
975
+ if (typeof value.$ref === 'string') {
976
+ value.$ref = rewriteRef(value.$ref);
977
+ }
978
+ forEachJSONSchemaChild(value, [], (child) => {
979
+ rewriteRefs(child);
980
+ });
981
+ };
982
+ rewriteRefs(root);
983
+ }
984
+ /**
985
+ * Strictification removes false anyOf alternatives. Rewrite pointers into
986
+ * surviving alternatives before that filtering happens so each local ref
987
+ * still names the same schema after earlier indices disappear.
988
+ */
989
+ function rewriteLocalRefsIntoFilteredAnyOfBranches(root) {
990
+ const rewriteRef = (ref) => {
991
+ const originalParts = parseLocalRef(ref);
992
+ if (originalParts === undefined || originalParts.length === 0) {
993
+ return ref;
994
+ }
995
+ const rewrittenParts = [...originalParts];
996
+ let resolved = root;
997
+ let changed = false;
998
+ for (const [index, part] of originalParts.entries()) {
999
+ const resolvedRecord = typeof resolved === 'object' && resolved !== null && !Array.isArray(resolved) ?
1000
+ resolved
1001
+ : undefined;
1002
+ if (part === 'anyOf' &&
1003
+ index < originalParts.length - 1 &&
1004
+ resolvedRecord !== undefined &&
1005
+ Array.isArray(resolvedRecord['anyOf'])) {
1006
+ const branches = resolvedRecord['anyOf'];
1007
+ const branchIndexPart = originalParts[index + 1];
1008
+ if (!/^(?:0|[1-9]\d*)$/.test(branchIndexPart)) {
1009
+ return ref;
1010
+ }
1011
+ const branchIndex = Number(branchIndexPart);
1012
+ if (!Object.prototype.hasOwnProperty.call(branches, branchIndex)) {
1013
+ return ref;
1014
+ }
1015
+ const realBranches = branches.filter((branch) => branch !== false);
1016
+ if (realBranches.length > 0 && realBranches.length !== branches.length) {
1017
+ // A ref to a removed false schema is already rejected by the first
1018
+ // ref-validation pass. Leave it untouched so it remains fail closed.
1019
+ if (branches[branchIndex] === false) {
1020
+ return ref;
1021
+ }
1022
+ const rewrittenIndex = branches.slice(0, branchIndex).filter((branch) => branch !== false).length;
1023
+ if (rewrittenIndex !== branchIndex) {
1024
+ rewrittenParts[index + 1] = String(rewrittenIndex);
1025
+ changed = true;
1026
+ }
1027
+ }
1028
+ }
1029
+ // Resolve through the original pointer, not the rewritten one, so a
1030
+ // nested anyOf can also be remapped after its parent index shifts.
1031
+ resolved = resolvePointerPart(resolved, part);
1032
+ if (resolved === undefined) {
1033
+ return ref;
1034
+ }
1035
+ }
1036
+ return changed ? '#/' + rewrittenParts.map(encodeJSONPointerTokenForURIFragment).join('/') : ref;
1037
+ };
1038
+ const rewriteRefs = (value) => {
1039
+ if (typeof value === 'boolean' || !isObject(value)) {
1040
+ return;
1041
+ }
1042
+ if (typeof value.$ref === 'string') {
1043
+ value.$ref = rewriteRef(value.$ref);
1044
+ }
1045
+ forEachJSONSchemaChild(value, [], (child) => {
1046
+ rewriteRefs(child);
1047
+ });
1048
+ };
1049
+ rewriteRefs(root);
1050
+ }
1051
+ /**
1052
+ * Strictification removes every representable allOf. Preserve any schema
1053
+ * referenced through an allOf branch under a stable root definition first so
1054
+ * structural flattening cannot leave a dangling local pointer behind.
1055
+ */
1056
+ function preserveAllOfRefTargets(root, rootOnly = false) {
1057
+ const refsToPreserve = new Set();
1058
+ const collectRefs = (value) => {
1059
+ if (typeof value === 'boolean' || !isObject(value)) {
1060
+ return;
1061
+ }
1062
+ if (typeof value.$ref === 'string' && refTargetsAllOfBranch(root, value.$ref)) {
1063
+ const pointerParts = parseLocalRef(value.$ref);
1064
+ if (!rootOnly || pointerParts?.[0] === 'allOf') {
1065
+ refsToPreserve.add(value.$ref);
1066
+ }
1067
+ }
1068
+ forEachJSONSchemaChild(value, [], (child) => {
1069
+ collectRefs(child);
1070
+ });
1071
+ };
1072
+ collectRefs(root);
1073
+ if (refsToPreserve.size === 0) {
1074
+ return;
1075
+ }
1076
+ if (root.$defs !== undefined && !isObject(root.$defs)) {
1077
+ throw new Error('Root schema has invalid $defs and cannot preserve local allOf references.');
1078
+ }
1079
+ const definitions = (root.$defs ?? (root.$defs = {}));
1080
+ const rewrittenRefs = new Map();
1081
+ let aliasIndex = 0;
1082
+ for (const ref of refsToPreserve) {
1083
+ const target = resolveLocalRef(root, ref);
1084
+ if (!isSchemaDefinition(target)) {
1085
+ if (rootOnly) {
1086
+ continue;
1087
+ }
1088
+ throw new Error('Local $ref cannot be preserved before allOf flattening: ' + JSON.stringify(ref));
1089
+ }
1090
+ let alias = '__openai_strict_allOf_ref_' + aliasIndex++;
1091
+ while (Object.prototype.hasOwnProperty.call(definitions, alias)) {
1092
+ alias = '__openai_strict_allOf_ref_' + aliasIndex++;
1093
+ }
1094
+ definitions[alias] = structuredClone(target);
1095
+ rewrittenRefs.set(ref, '#/$defs/' + escapeJSONPointerToken(alias));
1096
+ }
1097
+ const rewriteRefs = (value) => {
1098
+ if (typeof value === 'boolean' || !isObject(value)) {
1099
+ return;
1100
+ }
1101
+ if (typeof value.$ref === 'string') {
1102
+ value.$ref = rewrittenRefs.get(value.$ref) ?? value.$ref;
1103
+ }
1104
+ forEachJSONSchemaChild(value, [], (child) => {
1105
+ rewriteRefs(child);
1106
+ });
1107
+ };
1108
+ rewriteRefs(root);
1109
+ }
1110
+ /**
1111
+ * Closed allOf merges can discard optional property declarations. Preserve
1112
+ * only local refs into declarations that are about to disappear, then rewrite
1113
+ * those refs to stable root definitions before the merge removes their paths.
1114
+ */
1115
+ function preserveDiscardedAllOfPropertyRefTargets(root, discardedPaths) {
1116
+ if (discardedPaths.length === 0) {
1117
+ return;
1118
+ }
1119
+ const refsToPreserve = new Set();
1120
+ const collectRefs = (value) => {
1121
+ if (typeof value === 'boolean' || !isObject(value)) {
1122
+ return;
1123
+ }
1124
+ if (typeof value.$ref === 'string') {
1125
+ const parts = parseLocalRef(value.$ref);
1126
+ if (parts !== undefined &&
1127
+ discardedPaths.some((discardedPath) => parts.length >= discardedPath.length &&
1128
+ discardedPath.every((part, index) => parts[index] === part))) {
1129
+ refsToPreserve.add(value.$ref);
1130
+ }
1131
+ }
1132
+ forEachJSONSchemaChild(value, [], (child) => {
1133
+ collectRefs(child);
1134
+ });
1135
+ };
1136
+ collectRefs(root);
1137
+ if (refsToPreserve.size === 0) {
1138
+ return;
1139
+ }
1140
+ if (root.$defs !== undefined && !isObject(root.$defs)) {
1141
+ throw new Error('Root schema has invalid $defs and cannot preserve discarded allOf properties.');
1142
+ }
1143
+ const definitions = (root.$defs ?? (root.$defs = {}));
1144
+ const rewrittenRefs = new Map();
1145
+ let aliasIndex = 0;
1146
+ for (const ref of refsToPreserve) {
1147
+ const target = resolveLocalRef(root, ref);
1148
+ if (!isSchemaDefinition(target)) {
1149
+ throw new Error('Local $ref cannot be preserved before allOf property removal: ' + JSON.stringify(ref));
1150
+ }
1151
+ let alias = '__openai_strict_allOf_property_ref_' + aliasIndex++;
1152
+ while (Object.prototype.hasOwnProperty.call(definitions, alias)) {
1153
+ alias = '__openai_strict_allOf_property_ref_' + aliasIndex++;
1154
+ }
1155
+ definitions[alias] = structuredClone(target);
1156
+ rewrittenRefs.set(ref, '#/$defs/' + escapeJSONPointerToken(alias));
1157
+ }
1158
+ const rewriteRefs = (value) => {
1159
+ if (typeof value === 'boolean' || !isObject(value)) {
1160
+ return;
1161
+ }
1162
+ if (typeof value.$ref === 'string') {
1163
+ value.$ref = rewrittenRefs.get(value.$ref) ?? value.$ref;
1164
+ }
1165
+ forEachJSONSchemaChild(value, [], (child) => {
1166
+ rewriteRefs(child);
1167
+ });
1168
+ };
1169
+ rewriteRefs(root);
1170
+ }
1171
+ function validateRefSchemas(schema, path, root) {
1172
+ if (typeof schema === 'boolean' || !isObject(schema)) {
1173
+ return;
1174
+ }
1175
+ const ref = schema.$ref;
1176
+ if (ref !== undefined) {
1177
+ if (typeof ref !== 'string') {
1178
+ throw new TypeError(`Received non-string $ref - ${ref}; path=${path.join('/')}`);
1179
+ }
1180
+ if (!ref.startsWith('#')) {
1181
+ throw new Error(`External $ref at \`${path.join('/') || '<root>'}\` is not supported in strict Structured Outputs: ${JSON.stringify(ref)}`);
1182
+ }
1183
+ const resolved = resolveLocalRef(root, ref);
1184
+ if (resolved === undefined || !isSchemaDefinition(resolved)) {
1185
+ throw new Error(`Local $ref at \`${path.join('/') || '<root>'}\` does not resolve to an object or boolean schema: ${JSON.stringify(ref)}`);
1186
+ }
1187
+ if (typeof resolved === 'boolean') {
1188
+ throw new TypeError(`Expected object schema but got boolean; path=${path.join('/')}`);
1189
+ }
1190
+ if (!hasOnlyRefAndAnnotations(schema)) {
1191
+ throw new Error(`Schema $ref at \`${path.join('/') || '<root>'}\` has non-annotation siblings that Draft 7 ignores and cannot be represented in strict Structured Outputs.`);
1192
+ }
1193
+ }
1194
+ forEachJSONSchemaChild(schema, path, (child, childPath) => {
1195
+ validateRefSchemas(child, childPath, root);
1196
+ });
1197
+ }
1198
+ /**
1199
+ * Resolve only local aliases whose siblings carry no validation semantics.
1200
+ * Keeping this separate from general ref validation lets allOf merging inspect
1201
+ * the effective object shape without broadening which refs or sibling
1202
+ * constraints are accepted.
1203
+ */
1204
+ function resolveObjectAllOfBranch(schema, root, normalizing) {
1205
+ const refChain = [schema];
1206
+ const seenRefs = new Set();
1207
+ let resolved = schema;
1208
+ let resolvedPath = [];
1209
+ while (true) {
1210
+ while (resolved.$ref !== undefined) {
1211
+ const ref = resolved.$ref;
1212
+ if (typeof ref !== 'string' || !hasOnlyRefAndAnnotations(resolved) || seenRefs.has(ref)) {
1213
+ return undefined;
1214
+ }
1215
+ seenRefs.add(ref);
1216
+ const target = resolveLocalRef(root, ref);
1217
+ if (typeof target === 'boolean' || !isObject(target)) {
1218
+ return undefined;
1219
+ }
1220
+ const targetPath = parseLocalRef(ref);
1221
+ if (targetPath === undefined) {
1222
+ return undefined;
1223
+ }
1224
+ resolved = target;
1225
+ resolvedPath = targetPath;
1226
+ refChain.push(resolved);
1227
+ }
1228
+ // A ref can point forward to a target whose own mergeable allOf has not
1229
+ // been visited yet. Normalize that target before its consumer classifies
1230
+ // the resolved shape, making property order irrelevant. An active target
1231
+ // is a ref/allOf cycle; leave it wrapped so the caller still fails closed.
1232
+ if (resolved.allOf !== undefined && !normalizing.has(resolved)) {
1233
+ const previousAllOf = resolved.allOf;
1234
+ normalizeObjectAllOfBranches(resolved, resolvedPath, root, normalizing);
1235
+ // Singleton flattening can expose another local ref. Follow it through
1236
+ // the same guarded loop before returning the effective branch.
1237
+ if (resolved.$ref !== undefined || resolved.allOf !== previousAllOf) {
1238
+ continue;
1239
+ }
1240
+ }
1241
+ return { schema: resolved, refChain };
1242
+ }
1243
+ }
1244
+ function normalizeObjectAllOfBranches(schema, path, root, normalizing = new Set()) {
1245
+ if (typeof schema === 'boolean' || !isObject(schema)) {
1246
+ return;
1247
+ }
1248
+ if (normalizing.has(schema)) {
1249
+ return;
1250
+ }
1251
+ normalizing.add(schema);
1252
+ try {
1253
+ while (true) {
1254
+ forEachJSONSchemaChild(schema, path, (child, childPath) => {
1255
+ normalizeObjectAllOfBranches(child, childPath, root, normalizing);
1256
+ });
1257
+ // Intersections are associative, so normalize nested object
1258
+ // intersections before classifying their parents. Otherwise an inner
1259
+ // allOf wrapper has no directly visible object shape and makes an
1260
+ // exactly mergeable outer allOf fail closed.
1261
+ if (!mergeObjectAllOf(schema, path, root, normalizing)) {
1262
+ return;
1263
+ }
1264
+ }
1265
+ }
1266
+ finally {
1267
+ normalizing.delete(schema);
1268
+ }
1269
+ }
1270
+ /**
1271
+ * Standard Schema needs to prove oneOf branches exclusive before it rewrites
1272
+ * them to anyOf. Inspect a cloned branch through the same conservative object
1273
+ * allOf merger without mutating the caller's schema or broadening failures.
1274
+ */
1275
+ export function normalizeObjectAllOfForExclusivity(schema, root) {
1276
+ if (schema.allOf === undefined) {
1277
+ return undefined;
1278
+ }
1279
+ const normalized = structuredClone(schema);
1280
+ try {
1281
+ // Removing neutral branches or flattening a singleton can expose another
1282
+ // mergeable allOf wrapper. Keep applying the recursive merger and safe
1283
+ // singleton flattening until the proof sees the branch's final shape.
1284
+ while (normalized.allOf !== undefined) {
1285
+ normalizeObjectAllOfBranches(normalized, [], root);
1286
+ if (normalized.allOf === undefined) {
1287
+ break;
1288
+ }
1289
+ const allOf = normalized.allOf;
1290
+ if (!Array.isArray(allOf) || allOf.length !== 1 || !hasOnlyAnnotationSiblings(normalized, 'allOf')) {
1291
+ return undefined;
1292
+ }
1293
+ const branch = allOf[0];
1294
+ if (typeof branch === 'boolean' || !isObject(branch)) {
1295
+ return undefined;
1296
+ }
1297
+ const siblings = { ...normalized };
1298
+ delete siblings.allOf;
1299
+ const flattened = structuredClone(branch);
1300
+ for (const keyword of Object.keys(normalized)) {
1301
+ delete normalized[keyword];
1302
+ }
1303
+ Object.assign(normalized, flattened, siblings);
1304
+ }
1305
+ return normalized;
1306
+ }
1307
+ catch {
1308
+ return undefined;
1309
+ }
1310
+ }
1311
+ function mergeObjectAllOf(jsonSchema, path, root, normalizing = new Set()) {
1312
+ const allOf = jsonSchema.allOf;
1313
+ if (!Array.isArray(allOf) || allOf.length === 0) {
1314
+ return false;
1315
+ }
1316
+ // Intersections are idempotent. Collapse structurally identical branches
1317
+ // before object-shape classification so duplicate scalar and array schemas
1318
+ // can reach the existing safe singleton-flattening path.
1319
+ const uniqueBranches = allOf.filter((branch, index) => !allOf.slice(0, index).some((candidate) => schemasEqual(candidate, branch)));
1320
+ if (uniqueBranches.length !== allOf.length) {
1321
+ jsonSchema.allOf = uniqueBranches;
1322
+ return true;
1323
+ }
1324
+ // `true` is the identity element for Draft 7 intersections. Remove it
1325
+ // before deciding whether the remaining branches are object-mergeable so a
1326
+ // neutral branch cannot make an otherwise exact merge fail closed.
1327
+ const nonNeutralBranches = allOf.filter((entry) => entry !== true);
1328
+ if (nonNeutralBranches.length !== allOf.length) {
1329
+ if (nonNeutralBranches.length === 0) {
1330
+ delete jsonSchema.allOf;
1331
+ }
1332
+ else {
1333
+ jsonSchema.allOf = nonNeutralBranches;
1334
+ }
1335
+ return true;
1336
+ }
1337
+ const parentHasObjectShape = hasObjectShapeWithoutAllOf(jsonSchema);
1338
+ const resolvedEntries = allOf.map((entry) => isObject(entry) ? resolveObjectAllOfBranch(entry, root, normalizing) : undefined);
1339
+ const objectBranches = resolvedEntries
1340
+ .map((entry) => entry?.schema)
1341
+ .filter((entry) => entry !== undefined && hasObjectShapeWithoutAllOf(entry));
1342
+ if (!parentHasObjectShape && objectBranches.length === 0) {
1343
+ return false;
1344
+ }
1345
+ // A lone object branch with no object-valued parent is handled by the
1346
+ // existing safe single-allOf flattening path below.
1347
+ if (!parentHasObjectShape && allOf.length === 1) {
1348
+ return false;
1349
+ }
1350
+ const fail = () => {
1351
+ throw new Error(`Object allOf at \`${path.join('/') || '<root>'}\` cannot be merged without changing Draft 7 validation.`);
1352
+ };
1353
+ if (!parentHasObjectShape &&
1354
+ ['additionalProperties', 'properties', 'required', 'type'].some((keyword) => keyword in jsonSchema)) {
1355
+ fail();
1356
+ }
1357
+ for (const keyword of Object.keys(jsonSchema)) {
1358
+ if (keyword !== 'allOf' &&
1359
+ keyword !== '$defs' &&
1360
+ keyword !== 'definitions' &&
1361
+ !(path.length === 0 && JSON_SCHEMA_ROOT_METADATA_KEYWORDS.has(keyword)) &&
1362
+ !MERGEABLE_OBJECT_ALL_OF_KEYWORDS.has(keyword)) {
1363
+ fail();
1364
+ }
1365
+ }
1366
+ const branches = [];
1367
+ if (parentHasObjectShape) {
1368
+ branches.push({ schema: jsonSchema, sourcePath: path });
1369
+ }
1370
+ for (const [index, entry] of allOf.entries()) {
1371
+ if (!isObject(entry)) {
1372
+ fail();
1373
+ }
1374
+ const resolvedEntry = resolvedEntries[index];
1375
+ if (resolvedEntry === undefined) {
1376
+ return fail();
1377
+ }
1378
+ const branch = resolvedEntry.schema;
1379
+ // Definition maps do not constrain instances. Any refs into an allOf
1380
+ // branch were preserved under stable root definitions before this merge,
1381
+ // so a valid definitions-only branch can now be discarded like an
1382
+ // annotation-only branch.
1383
+ if (hasObjectShapeWithoutAllOf(branch)) {
1384
+ branches.push({
1385
+ schema: branch,
1386
+ sourcePath: branch === entry ? [...path, 'allOf', String(index)] : undefined,
1387
+ });
1388
+ }
1389
+ else if (!hasOnlyNeutralAllOfBranchKeywords(branch)) {
1390
+ fail();
1391
+ }
1392
+ }
1393
+ const merged = {};
1394
+ for (const keyword of ['$defs', 'definitions']) {
1395
+ if (jsonSchema[keyword] !== undefined) {
1396
+ merged[keyword] = jsonSchema[keyword];
1397
+ }
1398
+ }
1399
+ if (path.length === 0) {
1400
+ for (const keyword of JSON_SCHEMA_ROOT_METADATA_KEYWORDS) {
1401
+ if (keyword in jsonSchema) {
1402
+ merged[keyword] = jsonSchema[keyword];
1403
+ }
1404
+ }
1405
+ }
1406
+ const mergedProperties = Object.create(null);
1407
+ const mergedRequired = new Set();
1408
+ const closedPropertySets = [];
1409
+ const propertyEntries = [];
1410
+ let sawProperties = false;
1411
+ let sawRequired = false;
1412
+ let hasExplicitObjectType = false;
1413
+ let hasExplicitNullableObjectType = false;
1414
+ const mergeAnnotations = (schema) => {
1415
+ for (const keyword of JSON_SCHEMA_ANNOTATION_KEYWORDS) {
1416
+ if (!(keyword in schema))
1417
+ continue;
1418
+ // Annotation keywords do not affect Draft 7 validation. Preserve the
1419
+ // first value (the outer schema, then earlier branches) instead of
1420
+ // rejecting an otherwise exactly mergeable intersection.
1421
+ if (!(keyword in merged)) {
1422
+ merged[keyword] = schema[keyword];
1423
+ }
1424
+ }
1425
+ };
1426
+ mergeAnnotations(jsonSchema);
1427
+ for (const resolvedEntry of resolvedEntries) {
1428
+ if (resolvedEntry === undefined)
1429
+ continue;
1430
+ for (const entry of resolvedEntry.refChain) {
1431
+ mergeAnnotations(entry);
1432
+ }
1433
+ }
1434
+ for (const { schema: branch, sourcePath } of branches) {
1435
+ for (const keyword of Object.keys(branch)) {
1436
+ if (keyword === 'allOf' && branch === jsonSchema)
1437
+ continue;
1438
+ if ((keyword === '$defs' || keyword === 'definitions') &&
1439
+ isObject(branch[keyword])) {
1440
+ continue;
1441
+ }
1442
+ if (branch === jsonSchema && path.length === 0 && JSON_SCHEMA_ROOT_METADATA_KEYWORDS.has(keyword)) {
1443
+ continue;
1444
+ }
1445
+ if (!MERGEABLE_OBJECT_ALL_OF_KEYWORDS.has(keyword)) {
1446
+ fail();
1447
+ }
1448
+ }
1449
+ if (branch.type !== undefined) {
1450
+ if (!isMergeableObjectType(branch.type)) {
1451
+ fail();
1452
+ }
1453
+ if (branch.type === 'object') {
1454
+ hasExplicitObjectType = true;
1455
+ }
1456
+ else {
1457
+ hasExplicitNullableObjectType = true;
1458
+ }
1459
+ }
1460
+ if (branch.properties !== undefined) {
1461
+ if (!isObject(branch.properties)) {
1462
+ fail();
1463
+ }
1464
+ sawProperties = true;
1465
+ for (const [key, propertySchema] of Object.entries(branch.properties)) {
1466
+ propertyEntries.push({
1467
+ key,
1468
+ propertySchema,
1469
+ sourcePath: sourcePath === undefined ? undefined : [...sourcePath, 'properties', key],
1470
+ });
1471
+ }
1472
+ }
1473
+ if (branch.required !== undefined) {
1474
+ if (!Array.isArray(branch.required) || branch.required.some((key) => typeof key !== 'string')) {
1475
+ fail();
1476
+ }
1477
+ sawRequired = true;
1478
+ for (const key of branch.required)
1479
+ mergedRequired.add(key);
1480
+ }
1481
+ if ('additionalProperties' in branch) {
1482
+ if (branch.additionalProperties !== false) {
1483
+ fail();
1484
+ }
1485
+ closedPropertySets.push(new Set(Object.keys(branch.properties ?? {})));
1486
+ }
1487
+ }
1488
+ // A closed branch forbids every property it does not declare. Intersect all
1489
+ // closed property sets before merging schemas so optional declarations
1490
+ // excluded by another closed branch are discarded, while required excluded
1491
+ // properties remain unrepresentable and fail closed.
1492
+ const allowedClosedProperties = closedPropertySets.length === 0 ?
1493
+ undefined
1494
+ : closedPropertySets
1495
+ .slice(1)
1496
+ .reduce((allowed, keys) => new Set([...allowed].filter((key) => keys.has(key))), new Set(closedPropertySets[0]));
1497
+ const excludesRequiredProperty = allowedClosedProperties !== undefined &&
1498
+ [...mergedRequired].some((key) => !allowedClosedProperties.has(key));
1499
+ const collapsesToNull = excludesRequiredProperty && !hasExplicitObjectType && hasExplicitNullableObjectType;
1500
+ const discardedPropertyPaths = propertyEntries
1501
+ .filter(({ key, sourcePath }) => sourcePath !== undefined &&
1502
+ (collapsesToNull || (allowedClosedProperties !== undefined && !allowedClosedProperties.has(key))))
1503
+ .map(({ sourcePath }) => sourcePath);
1504
+ preserveDiscardedAllOfPropertyRefTargets(root, discardedPropertyPaths);
1505
+ if (jsonSchema === root && root.$defs !== undefined) {
1506
+ merged.$defs = root.$defs;
1507
+ }
1508
+ if (excludesRequiredProperty) {
1509
+ // Object keywords do not constrain null. If every explicit object type
1510
+ // also admits null, the object portion is contradictory but null remains
1511
+ // an exact representation of the intersection.
1512
+ if (collapsesToNull) {
1513
+ merged.type = 'null';
1514
+ for (const keyword of Object.keys(jsonSchema)) {
1515
+ delete jsonSchema[keyword];
1516
+ }
1517
+ Object.assign(jsonSchema, merged);
1518
+ return true;
1519
+ }
1520
+ fail();
1521
+ }
1522
+ for (const { key, propertySchema } of propertyEntries) {
1523
+ if (allowedClosedProperties !== undefined && !allowedClosedProperties.has(key)) {
1524
+ continue;
1525
+ }
1526
+ if (Object.prototype.hasOwnProperty.call(mergedProperties, key) &&
1527
+ !schemasEqual(mergedProperties[key], propertySchema)) {
1528
+ fail();
1529
+ }
1530
+ mergedProperties[key] = propertySchema;
1531
+ }
1532
+ if (hasExplicitObjectType || hasExplicitNullableObjectType) {
1533
+ merged.type = hasExplicitObjectType ? 'object' : ['object', 'null'];
1534
+ }
1535
+ if (sawProperties)
1536
+ merged.properties = Object.fromEntries(Object.entries(mergedProperties));
1537
+ if (sawRequired)
1538
+ merged.required = [...mergedRequired];
1539
+ if (closedPropertySets.length > 0)
1540
+ merged.additionalProperties = false;
1541
+ for (const keyword of Object.keys(jsonSchema)) {
1542
+ delete jsonSchema[keyword];
1543
+ }
1544
+ Object.assign(jsonSchema, merged);
1545
+ return true;
1546
+ }
1547
+ function hasObjectShapeWithoutAllOf(schema) {
1548
+ if (schema.type !== undefined) {
1549
+ return isMergeableObjectType(schema.type);
1550
+ }
1551
+ return Object.keys(schema).some((keyword) => JSON_SCHEMA_OBJECT_KEYWORDS.has(keyword));
1552
+ }
1553
+ function hasOnlyNeutralAllOfBranchKeywords(schema) {
1554
+ const schemaRecord = schema;
1555
+ return Object.keys(schema).every((keyword) => JSON_SCHEMA_ANNOTATION_KEYWORDS.has(keyword) ||
1556
+ ((keyword === '$defs' || keyword === 'definitions') && isObject(schemaRecord[keyword])));
1557
+ }
1558
+ function isMergeableObjectType(type) {
1559
+ return (type === 'object' ||
1560
+ (Array.isArray(type) && type.length === 2 && type.includes('object') && type.includes('null')));
1561
+ }
1562
+ function schemasEqual(left, right) {
1563
+ if (left === right) {
1564
+ return true;
1565
+ }
1566
+ if (Array.isArray(left) || Array.isArray(right)) {
1567
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
1568
+ return false;
1569
+ }
1570
+ return left.every((value, index) => schemasEqual(value, right[index]));
1571
+ }
1572
+ if (typeof left !== 'object' || left === null || typeof right !== 'object' || right === null) {
1573
+ return false;
1574
+ }
1575
+ const leftRecord = left;
1576
+ const rightRecord = right;
1577
+ const leftKeys = Object.keys(leftRecord).sort();
1578
+ const rightKeys = Object.keys(rightRecord).sort();
1579
+ if (leftKeys.length !== rightKeys.length) {
1580
+ return false;
1581
+ }
1582
+ return leftKeys.every((key, index) => key === rightKeys[index] && schemasEqual(leftRecord[key], rightRecord[key]));
1583
+ }
151
1584
  //# sourceMappingURL=transform.mjs.map