@gajae-code/ai 0.15.5 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/types/adapter-internals/aws-region.d.ts +7 -0
- package/dist/types/auth-broker/client.d.ts +6 -2
- package/dist/types/auth-broker/remote-store.d.ts +14 -2
- package/dist/types/auth-broker/types.d.ts +6 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +19 -0
- package/dist/types/auth-gateway/server.d.ts +39 -5
- package/dist/types/auth-gateway/types.d.ts +16 -2
- package/dist/types/auth-storage.d.ts +92 -24
- package/dist/types/core.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/provider-models/openai-compat.d.ts +1 -0
- package/dist/types/providers/anthropic.d.ts +1 -1
- package/dist/types/providers/google-gemini-headers.d.ts +1 -1
- package/dist/types/providers/openai-codex-responses.d.ts +6 -0
- package/dist/types/providers/register-builtins.d.ts +12 -12
- package/dist/types/stream.d.ts +2 -1
- package/dist/types/types.d.ts +22 -2
- package/dist/types/utils/oauth/api-key-login.d.ts +4 -1
- package/dist/types/utils/oauth/api-key-validation.d.ts +12 -6
- package/dist/types/utils/oauth/commandcode.d.ts +1 -0
- package/dist/types/utils/oauth/types.d.ts +1 -1
- package/dist/types/utils/retry.d.ts +2 -0
- package/dist/types/utils/schema/normalize.d.ts +0 -5
- package/dist/types/utils/sqlite-errors.d.ts +4 -0
- package/package.json +3 -3
- package/src/adapter-internals/aws-region.d.ts +7 -0
- package/src/adapter-internals/aws-region.ts +14 -0
- package/src/auth-broker/client.ts +41 -13
- package/src/auth-broker/redact.ts +25 -1
- package/src/auth-broker/remote-store.ts +374 -115
- package/src/auth-broker/server.ts +131 -91
- package/src/auth-broker/types.ts +6 -0
- package/src/auth-broker/wire-schemas.ts +6 -0
- package/src/auth-gateway/server.ts +447 -79
- package/src/auth-gateway/types.ts +28 -2
- package/src/auth-storage.ts +672 -168
- package/src/cli.ts +1 -0
- package/src/core.ts +1 -0
- package/src/index.ts +1 -0
- package/src/model-thinking.ts +8 -0
- package/src/models.json +1224 -3
- package/src/provider-models/descriptors.ts +3 -1
- package/src/provider-models/openai-compat.ts +102 -1
- package/src/providers/amazon-bedrock.ts +5 -1
- package/src/providers/anthropic.d.ts +1 -1
- package/src/providers/anthropic.ts +8 -2
- package/src/providers/aws-credentials.ts +6 -0
- package/src/providers/azure-openai-responses.ts +4 -1
- package/src/providers/cursor.ts +256 -101
- package/src/providers/gitlab-duo.ts +18 -1
- package/src/providers/google-gemini-cli.ts +3 -0
- package/src/providers/google-gemini-headers.d.ts +1 -1
- package/src/providers/google-gemini-headers.ts +1 -1
- package/src/providers/google-shared.ts +3 -0
- package/src/providers/kiro-api-key.ts +33 -8
- package/src/providers/kiro-codewhisperer.ts +28 -9
- package/src/providers/ollama.ts +3 -0
- package/src/providers/openai-codex-responses.d.ts +6 -0
- package/src/providers/openai-codex-responses.ts +37 -8
- package/src/providers/openai-completions.ts +11 -1
- package/src/providers/openai-responses.ts +10 -1
- package/src/providers/pi-native-client.ts +25 -1
- package/src/providers/pi-native-server.ts +24 -0
- package/src/providers/register-builtins.d.ts +12 -12
- package/src/providers/register-builtins.ts +16 -3
- package/src/stream.d.ts +2 -1
- package/src/stream.ts +175 -67
- package/src/types.d.ts +22 -2
- package/src/types.ts +27 -1
- package/src/utils/oauth/api-key-login.ts +13 -2
- package/src/utils/oauth/api-key-validation.ts +242 -41
- package/src/utils/oauth/commandcode.ts +17 -0
- package/src/utils/oauth/index.ts +20 -5
- package/src/utils/oauth/kiro.ts +91 -22
- package/src/utils/oauth/types.d.ts +1 -1
- package/src/utils/oauth/types.ts +1 -0
- package/src/utils/retry.d.ts +2 -0
- package/src/utils/retry.ts +15 -2
- package/src/utils/schema/dereference.ts +169 -49
- package/src/utils/schema/draft.ts +46 -23
- package/src/utils/schema/normalize.d.ts +0 -5
- package/src/utils/schema/normalize.ts +396 -119
- package/src/utils/schema/types.ts +3 -1
- package/src/utils/schema/zod-decontaminate.ts +83 -29
- package/src/utils/sqlite-errors.d.ts +4 -0
- package/src/utils/sqlite-errors.ts +13 -0
- package/src/utils/tool-choice-capability.ts +2 -3
|
@@ -70,6 +70,14 @@ const SNAKE_TO_CAMEL_RENAMES = new Map<string, string>([
|
|
|
70
70
|
|
|
71
71
|
const JSON_SCHEMA_COMBINERS = ["anyOf", "oneOf"] as const;
|
|
72
72
|
const CCA_FORBIDDEN_COMBINERS = new Set(["anyOf", "oneOf", "allOf"]);
|
|
73
|
+
const JSON_SCHEMA_MAP_KEYS = new Set([
|
|
74
|
+
"properties",
|
|
75
|
+
"patternProperties",
|
|
76
|
+
"dependencies",
|
|
77
|
+
"dependentSchemas",
|
|
78
|
+
"$defs",
|
|
79
|
+
"definitions",
|
|
80
|
+
]);
|
|
73
81
|
|
|
74
82
|
const CLOUD_CODE_ASSIST_CLAUDE_FALLBACK_SCHEMA = {
|
|
75
83
|
type: "object",
|
|
@@ -109,9 +117,9 @@ function applySnakeCaseRenames(obj: JsonObject): JsonObject {
|
|
|
109
117
|
if (!Object.hasOwn(obj, k)) continue;
|
|
110
118
|
const renamed = SNAKE_TO_CAMEL_RENAMES.get(k);
|
|
111
119
|
if (renamed !== undefined) {
|
|
112
|
-
out
|
|
120
|
+
setOwnKey(out, renamed, obj[k]);
|
|
113
121
|
} else if (!outHasOwn(out, k)) {
|
|
114
|
-
out
|
|
122
|
+
setOwnKey(out, k, obj[k]);
|
|
115
123
|
}
|
|
116
124
|
}
|
|
117
125
|
return out;
|
|
@@ -124,21 +132,21 @@ function applySnakeCaseRenames(obj: JsonObject): JsonObject {
|
|
|
124
132
|
* original reference otherwise (zero-allocation fast path).
|
|
125
133
|
*/
|
|
126
134
|
function preHandleNullFields(obj: JsonObject): JsonObject {
|
|
127
|
-
if (obj.type === "null") {
|
|
135
|
+
if (Object.hasOwn(obj, "type") && obj.type === "null") {
|
|
128
136
|
const out: JsonObject = {};
|
|
129
137
|
for (const k in obj) {
|
|
130
138
|
if (!Object.hasOwn(obj, k) || k === "type") continue;
|
|
131
|
-
out
|
|
139
|
+
setOwnKey(out, k, obj[k]);
|
|
132
140
|
}
|
|
133
141
|
out.nullable = true;
|
|
134
142
|
return out;
|
|
135
143
|
}
|
|
136
|
-
if (!Array.isArray(obj.anyOf)) return obj;
|
|
144
|
+
if (!Object.hasOwn(obj, "anyOf") || !Array.isArray(obj.anyOf)) return obj;
|
|
137
145
|
const variants = obj.anyOf as unknown[];
|
|
138
146
|
let sawNull = false;
|
|
139
147
|
const kept: unknown[] = [];
|
|
140
148
|
for (const v of variants) {
|
|
141
|
-
if (isJsonObject(v) && v.type === "null") {
|
|
149
|
+
if (isJsonObject(v) && Object.hasOwn(v, "type") && v.type === "null") {
|
|
142
150
|
sawNull = true;
|
|
143
151
|
continue;
|
|
144
152
|
}
|
|
@@ -147,7 +155,7 @@ function preHandleNullFields(obj: JsonObject): JsonObject {
|
|
|
147
155
|
if (!sawNull) return obj;
|
|
148
156
|
const out: JsonObject = {};
|
|
149
157
|
for (const k in obj) {
|
|
150
|
-
if (Object.hasOwn(obj, k)) out
|
|
158
|
+
if (Object.hasOwn(obj, k)) setOwnKey(out, k, obj[k]);
|
|
151
159
|
}
|
|
152
160
|
out.nullable = true;
|
|
153
161
|
if (kept.length === 0) {
|
|
@@ -156,7 +164,7 @@ function preHandleNullFields(obj: JsonObject): JsonObject {
|
|
|
156
164
|
delete out.anyOf;
|
|
157
165
|
const only = kept[0];
|
|
158
166
|
for (const k in only) {
|
|
159
|
-
if (Object.hasOwn(only, k) && !outHasOwn(out, k)) out
|
|
167
|
+
if (Object.hasOwn(only, k) && !outHasOwn(out, k)) setOwnKey(out, k, only[k]);
|
|
160
168
|
}
|
|
161
169
|
} else {
|
|
162
170
|
out.anyOf = kept;
|
|
@@ -168,6 +176,50 @@ function outHasOwn(obj: JsonObject, key: string): boolean {
|
|
|
168
176
|
return Object.hasOwn(obj, key);
|
|
169
177
|
}
|
|
170
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Setter-independent own-data write. `JSON.parse` produces `__proto__` as an own
|
|
181
|
+
* data property, but plain-object assignment routes it to `Object.prototype`'s
|
|
182
|
+
* `__proto__` setter, so the key would be silently dropped from the copy (and a
|
|
183
|
+
* schema-shaped value would mutate the copy's prototype). Every schema copy site
|
|
184
|
+
* writes through this helper so arbitrary property names round-trip as data.
|
|
185
|
+
*/
|
|
186
|
+
function setOwnKey(target: JsonObject, key: string, value: unknown): void {
|
|
187
|
+
if (key === "__proto__") {
|
|
188
|
+
Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
target[key] = value;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* JSON Schema keywords whose values are literal instance data, not subschemas.
|
|
196
|
+
* The schema walkers must copy them verbatim: recursing into them rewrites user
|
|
197
|
+
* payloads (a `default` of `{type:"object",nullable:true}` is a default value,
|
|
198
|
+
* not a nullable object schema).
|
|
199
|
+
*/
|
|
200
|
+
const JSON_SCHEMA_LITERAL_PAYLOAD_KEYS = new Set(["default", "const", "enum", "examples"]);
|
|
201
|
+
|
|
202
|
+
/** Deep copy of literal payload data; preserves arbitrary keys and breaks aliasing to the caller's input. */
|
|
203
|
+
function cloneJsonLiteral(value: unknown, seen?: WeakMap<object, unknown>): unknown {
|
|
204
|
+
if (!value || typeof value !== "object") return value;
|
|
205
|
+
const cache = seen ?? new WeakMap<object, unknown>();
|
|
206
|
+
const cached = cache.get(value);
|
|
207
|
+
if (cached !== undefined) return cached;
|
|
208
|
+
if (Array.isArray(value)) {
|
|
209
|
+
const out: unknown[] = [];
|
|
210
|
+
cache.set(value, out);
|
|
211
|
+
for (const entry of value) out.push(cloneJsonLiteral(entry, cache));
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
const out: JsonObject = {};
|
|
215
|
+
cache.set(value, out);
|
|
216
|
+
for (const key in value as JsonObject) {
|
|
217
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
218
|
+
setOwnKey(out, key, cloneJsonLiteral((value as JsonObject)[key], cache));
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
|
|
171
223
|
function inferJsonSchemaTypeFromValue(value: unknown): string | undefined {
|
|
172
224
|
if (value === null) return "null";
|
|
173
225
|
if (Array.isArray(value)) return "array";
|
|
@@ -225,6 +277,21 @@ function normalizeSchemaNode(value: unknown, options: NormalizeSchemaWalkOptions
|
|
|
225
277
|
return value;
|
|
226
278
|
}
|
|
227
279
|
if (!once(value, options.epoch)) return {};
|
|
280
|
+
if (options.insideProperties) {
|
|
281
|
+
let changed = false;
|
|
282
|
+
const result: JsonObject = {};
|
|
283
|
+
for (const key in value) {
|
|
284
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
285
|
+
const child = value[key];
|
|
286
|
+
const next = normalizeSchemaNode(child, {
|
|
287
|
+
...options,
|
|
288
|
+
insideProperties: false,
|
|
289
|
+
});
|
|
290
|
+
if (next !== child) changed = true;
|
|
291
|
+
setOwnKey(result, key, next);
|
|
292
|
+
}
|
|
293
|
+
return changed ? result : value;
|
|
294
|
+
}
|
|
228
295
|
let obj = options.normalizeFieldNames && !options.insideProperties ? applySnakeCaseRenames(value) : value;
|
|
229
296
|
if (options.collapseNullFields && !options.insideProperties) {
|
|
230
297
|
obj = preHandleNullFields(obj);
|
|
@@ -232,19 +299,20 @@ function normalizeSchemaNode(value: unknown, options: NormalizeSchemaWalkOptions
|
|
|
232
299
|
const result: JsonObject = {};
|
|
233
300
|
let spill: Array<[string, unknown]> | undefined;
|
|
234
301
|
for (const combiner of JSON_SCHEMA_COMBINERS) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
302
|
+
const variantsRaw = Object.hasOwn(obj, combiner) ? obj[combiner] : undefined;
|
|
303
|
+
if (!Array.isArray(variantsRaw)) continue;
|
|
304
|
+
const variants = variantsRaw as JsonObject[];
|
|
305
|
+
const allHaveConst = variants.every(v => isJsonObject(v) && Object.hasOwn(v, "const"));
|
|
238
306
|
if (!allHaveConst || variants.length === 0) continue;
|
|
239
307
|
|
|
240
308
|
const dedupedEnum: unknown[] = [];
|
|
241
309
|
for (const variant of variants) {
|
|
242
|
-
pushEnumValue(dedupedEnum, variant.const);
|
|
310
|
+
pushEnumValue(dedupedEnum, cloneJsonLiteral(variant.const));
|
|
243
311
|
}
|
|
244
312
|
result.enum = dedupedEnum;
|
|
245
313
|
|
|
246
314
|
const explicitTypes = variants
|
|
247
|
-
.map(variant => variant.type)
|
|
315
|
+
.map(variant => (Object.hasOwn(variant, "type") ? variant.type : undefined))
|
|
248
316
|
.filter((variantType): variantType is string => typeof variantType === "string");
|
|
249
317
|
const allHaveSameExplicitType =
|
|
250
318
|
explicitTypes.length === variants.length &&
|
|
@@ -278,10 +346,18 @@ function normalizeSchemaNode(value: unknown, options: NormalizeSchemaWalkOptions
|
|
|
278
346
|
continue;
|
|
279
347
|
}
|
|
280
348
|
if (options.stripNullableKeyword && key === "nullable") continue;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
349
|
+
if (JSON_SCHEMA_LITERAL_PAYLOAD_KEYS.has(key)) {
|
|
350
|
+
setOwnKey(result, key, cloneJsonLiteral(entry));
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
setOwnKey(
|
|
354
|
+
result,
|
|
355
|
+
key,
|
|
356
|
+
normalizeSchemaNode(entry, {
|
|
357
|
+
...options,
|
|
358
|
+
insideProperties: JSON_SCHEMA_MAP_KEYS.has(key),
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
285
361
|
}
|
|
286
362
|
applyDescriptionSpill(result, spill, options);
|
|
287
363
|
return applyNodePostProcessing(result, options);
|
|
@@ -297,13 +373,21 @@ function normalizeSchemaNode(value: unknown, options: NormalizeSchemaWalkOptions
|
|
|
297
373
|
}
|
|
298
374
|
if (options.stripNullableKeyword && key === "nullable") continue;
|
|
299
375
|
if (key === "const") {
|
|
300
|
-
constValue = entry;
|
|
376
|
+
constValue = cloneJsonLiteral(entry);
|
|
301
377
|
continue;
|
|
302
378
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
379
|
+
if (JSON_SCHEMA_LITERAL_PAYLOAD_KEYS.has(key)) {
|
|
380
|
+
setOwnKey(result, key, cloneJsonLiteral(entry));
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
setOwnKey(
|
|
384
|
+
result,
|
|
385
|
+
key,
|
|
386
|
+
normalizeSchemaNode(entry, {
|
|
387
|
+
...options,
|
|
388
|
+
insideProperties: JSON_SCHEMA_MAP_KEYS.has(key),
|
|
389
|
+
}),
|
|
390
|
+
);
|
|
307
391
|
}
|
|
308
392
|
|
|
309
393
|
if (options.normalizeTypeArrayToNullable && Array.isArray(result.type)) {
|
|
@@ -367,7 +451,7 @@ export function copySchemaWithout(schema: JsonObject, combiner: string): JsonObj
|
|
|
367
451
|
}
|
|
368
452
|
|
|
369
453
|
function mergeObjectCombinerVariants(schema: JsonObject, combiner: "anyOf" | "oneOf"): JsonObject {
|
|
370
|
-
const variantsRaw = schema[combiner];
|
|
454
|
+
const variantsRaw = Object.hasOwn(schema, combiner) ? schema[combiner] : undefined;
|
|
371
455
|
if (!Array.isArray(variantsRaw) || variantsRaw.length === 0) {
|
|
372
456
|
return schema;
|
|
373
457
|
}
|
|
@@ -377,10 +461,10 @@ function mergeObjectCombinerVariants(schema: JsonObject, combiner: "anyOf" | "on
|
|
|
377
461
|
if (!isJsonObject(entry)) {
|
|
378
462
|
return schema;
|
|
379
463
|
}
|
|
380
|
-
const variantType = entry.type;
|
|
464
|
+
const variantType = Object.hasOwn(entry, "type") ? entry.type : undefined;
|
|
381
465
|
const hasObjectShape =
|
|
382
|
-
isJsonObject(entry.properties) ||
|
|
383
|
-
Array.isArray(entry.required) ||
|
|
466
|
+
(Object.hasOwn(entry, "properties") && isJsonObject(entry.properties)) ||
|
|
467
|
+
(Object.hasOwn(entry, "required") && Array.isArray(entry.required)) ||
|
|
384
468
|
Object.hasOwn(entry, "additionalProperties");
|
|
385
469
|
if (variantType === undefined && !hasObjectShape) {
|
|
386
470
|
return schema;
|
|
@@ -388,29 +472,34 @@ function mergeObjectCombinerVariants(schema: JsonObject, combiner: "anyOf" | "on
|
|
|
388
472
|
if (variantType !== undefined && variantType !== "object") {
|
|
389
473
|
return schema;
|
|
390
474
|
}
|
|
391
|
-
if (entry
|
|
475
|
+
if (Object.hasOwn(entry, "properties") && !isJsonObject(entry.properties)) {
|
|
392
476
|
return schema;
|
|
393
477
|
}
|
|
394
|
-
if (entry
|
|
478
|
+
if (Object.hasOwn(entry, "required") && !Array.isArray(entry.required)) {
|
|
395
479
|
return schema;
|
|
396
480
|
}
|
|
397
481
|
variants.push(entry);
|
|
398
482
|
}
|
|
399
483
|
|
|
400
484
|
const mergedProperties: JsonObject = {};
|
|
401
|
-
const ownProperties =
|
|
485
|
+
const ownProperties =
|
|
486
|
+
Object.hasOwn(schema, "properties") && isJsonObject(schema.properties) ? schema.properties : {};
|
|
402
487
|
for (const name in ownProperties) {
|
|
403
|
-
if (Object.hasOwn(ownProperties, name)) mergedProperties
|
|
488
|
+
if (Object.hasOwn(ownProperties, name)) setOwnKey(mergedProperties, name, ownProperties[name]);
|
|
404
489
|
}
|
|
405
490
|
|
|
406
491
|
for (const variant of variants) {
|
|
407
|
-
const properties =
|
|
492
|
+
const properties =
|
|
493
|
+
Object.hasOwn(variant, "properties") && isJsonObject(variant.properties) ? variant.properties : {};
|
|
408
494
|
for (const name in properties) {
|
|
409
495
|
if (!Object.hasOwn(properties, name)) continue;
|
|
410
496
|
const propertySchema = properties[name];
|
|
411
|
-
const existingSchema = mergedProperties[name];
|
|
412
|
-
|
|
413
|
-
|
|
497
|
+
const existingSchema = Object.hasOwn(mergedProperties, name) ? mergedProperties[name] : undefined;
|
|
498
|
+
setOwnKey(
|
|
499
|
+
mergedProperties,
|
|
500
|
+
name,
|
|
501
|
+
existingSchema === undefined ? propertySchema : mergePropertySchemas(existingSchema, propertySchema),
|
|
502
|
+
);
|
|
414
503
|
}
|
|
415
504
|
}
|
|
416
505
|
|
|
@@ -420,9 +509,10 @@ function mergeObjectCombinerVariants(schema: JsonObject, combiner: "anyOf" | "on
|
|
|
420
509
|
|
|
421
510
|
let requiredIntersection: string[] | undefined;
|
|
422
511
|
for (const variant of variants) {
|
|
423
|
-
const variantRequired =
|
|
424
|
-
|
|
425
|
-
|
|
512
|
+
const variantRequired =
|
|
513
|
+
Object.hasOwn(variant, "required") && Array.isArray(variant.required)
|
|
514
|
+
? variant.required.filter((r): r is string => typeof r === "string")
|
|
515
|
+
: [];
|
|
426
516
|
if (requiredIntersection === undefined) {
|
|
427
517
|
requiredIntersection = [...variantRequired];
|
|
428
518
|
} else {
|
|
@@ -430,9 +520,10 @@ function mergeObjectCombinerVariants(schema: JsonObject, combiner: "anyOf" | "on
|
|
|
430
520
|
requiredIntersection = requiredIntersection.filter(r => reqSet.has(r));
|
|
431
521
|
}
|
|
432
522
|
}
|
|
433
|
-
const parentRequired =
|
|
434
|
-
|
|
435
|
-
|
|
523
|
+
const parentRequired =
|
|
524
|
+
Object.hasOwn(schema, "required") && Array.isArray(schema.required)
|
|
525
|
+
? schema.required.filter((r): r is string => typeof r === "string")
|
|
526
|
+
: [];
|
|
436
527
|
const safeRequired = new Set<string>();
|
|
437
528
|
for (const name of requiredIntersection ?? []) {
|
|
438
529
|
if (Object.hasOwn(mergedProperties, name)) safeRequired.add(name);
|
|
@@ -456,7 +547,7 @@ function mergeObjectCombinerVariants(schema: JsonObject, combiner: "anyOf" | "on
|
|
|
456
547
|
}
|
|
457
548
|
|
|
458
549
|
function collapseMixedTypeCombinerVariants(schema: JsonObject, combiner: "anyOf" | "oneOf"): JsonObject {
|
|
459
|
-
const variantsRaw = schema[combiner];
|
|
550
|
+
const variantsRaw = Object.hasOwn(schema, combiner) ? schema[combiner] : undefined;
|
|
460
551
|
if (!Array.isArray(variantsRaw) || variantsRaw.length === 0) {
|
|
461
552
|
return schema;
|
|
462
553
|
}
|
|
@@ -465,7 +556,7 @@ function collapseMixedTypeCombinerVariants(schema: JsonObject, combiner: "anyOf"
|
|
|
465
556
|
const variantTypes: string[] = [];
|
|
466
557
|
const mergedVariantFields: JsonObject = {};
|
|
467
558
|
for (const entry of variantsRaw) {
|
|
468
|
-
if (!isJsonObject(entry) || typeof entry.type !== "string") {
|
|
559
|
+
if (!isJsonObject(entry) || !Object.hasOwn(entry, "type") || typeof entry.type !== "string") {
|
|
469
560
|
return schema;
|
|
470
561
|
}
|
|
471
562
|
|
|
@@ -487,11 +578,11 @@ function collapseMixedTypeCombinerVariants(schema: JsonObject, combiner: "anyOf"
|
|
|
487
578
|
return schema;
|
|
488
579
|
}
|
|
489
580
|
|
|
490
|
-
const existingValue = mergedVariantFields[key];
|
|
581
|
+
const existingValue = Object.hasOwn(mergedVariantFields, key) ? mergedVariantFields[key] : undefined;
|
|
491
582
|
if (existingValue !== undefined && !areJsonValuesEqual(existingValue, variantValue)) {
|
|
492
583
|
return schema;
|
|
493
584
|
}
|
|
494
|
-
mergedVariantFields
|
|
585
|
+
setOwnKey(mergedVariantFields, key, variantValue);
|
|
495
586
|
}
|
|
496
587
|
|
|
497
588
|
seenTypes.add(variantType);
|
|
@@ -508,24 +599,24 @@ function collapseMixedTypeCombinerVariants(schema: JsonObject, combiner: "anyOf"
|
|
|
508
599
|
for (const key in mergedVariantFields) {
|
|
509
600
|
if (!Object.hasOwn(mergedVariantFields, key)) continue;
|
|
510
601
|
const value = mergedVariantFields[key];
|
|
511
|
-
const existingValue = nextSchema[key];
|
|
602
|
+
const existingValue = Object.hasOwn(nextSchema, key) ? nextSchema[key] : undefined;
|
|
512
603
|
if (existingValue !== undefined && !areJsonValuesEqual(existingValue, value)) {
|
|
513
604
|
return schema;
|
|
514
605
|
}
|
|
515
606
|
if (existingValue === undefined) {
|
|
516
|
-
nextSchema
|
|
607
|
+
setOwnKey(nextSchema, key, value);
|
|
517
608
|
}
|
|
518
609
|
}
|
|
519
610
|
return nextSchema;
|
|
520
611
|
}
|
|
521
612
|
|
|
522
613
|
function collapseSameTypeCombinerVariants(schema: JsonObject, combiner: "anyOf" | "oneOf"): JsonObject {
|
|
523
|
-
const variantsRaw = schema[combiner];
|
|
614
|
+
const variantsRaw = Object.hasOwn(schema, combiner) ? schema[combiner] : undefined;
|
|
524
615
|
if (!Array.isArray(variantsRaw) || variantsRaw.length === 0) return schema;
|
|
525
616
|
let commonType: string | undefined;
|
|
526
617
|
let firstEntry: JsonObject | undefined;
|
|
527
618
|
for (const entry of variantsRaw) {
|
|
528
|
-
if (!isJsonObject(entry) || typeof entry.type !== "string") return schema;
|
|
619
|
+
if (!isJsonObject(entry) || !Object.hasOwn(entry, "type") || typeof entry.type !== "string") return schema;
|
|
529
620
|
if (commonType === undefined) {
|
|
530
621
|
commonType = entry.type;
|
|
531
622
|
firstEntry = entry;
|
|
@@ -534,7 +625,7 @@ function collapseSameTypeCombinerVariants(schema: JsonObject, combiner: "anyOf"
|
|
|
534
625
|
if (!firstEntry) return schema;
|
|
535
626
|
const nextSchema = copySchemaWithout(schema, combiner);
|
|
536
627
|
for (const key in firstEntry) {
|
|
537
|
-
if (Object.hasOwn(firstEntry, key) && !outHasOwn(nextSchema, key)) nextSchema
|
|
628
|
+
if (Object.hasOwn(firstEntry, key) && !outHasOwn(nextSchema, key)) setOwnKey(nextSchema, key, firstEntry[key]);
|
|
538
629
|
}
|
|
539
630
|
return nextSchema;
|
|
540
631
|
}
|
|
@@ -544,6 +635,16 @@ function collapseSameTypeCombinerVariants(schema: JsonObject, combiner: "anyOf"
|
|
|
544
635
|
* collapse can handle. This is needed because object-combiner merging can
|
|
545
636
|
* create new anyOf in merged subtrees after child normalization already ran.
|
|
546
637
|
*/
|
|
638
|
+
function stripResidualCombinersMap(schemaMap: JsonObject, epoch: number): JsonObject {
|
|
639
|
+
if (!once(schemaMap, epoch)) return {};
|
|
640
|
+
const result: JsonObject = {};
|
|
641
|
+
for (const key in schemaMap) {
|
|
642
|
+
if (!Object.hasOwn(schemaMap, key)) continue;
|
|
643
|
+
setOwnKey(result, key, stripResidualCombiners(schemaMap[key], epoch));
|
|
644
|
+
}
|
|
645
|
+
return result;
|
|
646
|
+
}
|
|
647
|
+
|
|
547
648
|
export function stripResidualCombiners(value: unknown, epoch: number = epochNext()): unknown {
|
|
548
649
|
if (Array.isArray(value)) {
|
|
549
650
|
if (!once(value, epoch)) return [];
|
|
@@ -553,7 +654,19 @@ export function stripResidualCombiners(value: unknown, epoch: number = epochNext
|
|
|
553
654
|
if (!once(value, epoch)) return {};
|
|
554
655
|
const result: JsonObject = {};
|
|
555
656
|
for (const key in value) {
|
|
556
|
-
if (Object.hasOwn(value, key))
|
|
657
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
658
|
+
if (JSON_SCHEMA_LITERAL_PAYLOAD_KEYS.has(key)) {
|
|
659
|
+
setOwnKey(result, key, value[key]);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
const child = value[key];
|
|
663
|
+
setOwnKey(
|
|
664
|
+
result,
|
|
665
|
+
key,
|
|
666
|
+
JSON_SCHEMA_MAP_KEYS.has(key) && isJsonObject(child)
|
|
667
|
+
? stripResidualCombinersMap(child, epoch)
|
|
668
|
+
: stripResidualCombiners(child, epoch),
|
|
669
|
+
);
|
|
557
670
|
}
|
|
558
671
|
let current: JsonObject = result;
|
|
559
672
|
let changed = true;
|
|
@@ -585,13 +698,13 @@ function extractNullableUnionSchema(schema: unknown): NullableExtractionResult {
|
|
|
585
698
|
return { schema, nullable: false };
|
|
586
699
|
}
|
|
587
700
|
|
|
588
|
-
if (schema.nullable === true) {
|
|
701
|
+
if (Object.hasOwn(schema, "nullable") && schema.nullable === true) {
|
|
589
702
|
const nextSchema = { ...schema };
|
|
590
703
|
delete nextSchema.nullable;
|
|
591
704
|
return { schema: nextSchema, nullable: true };
|
|
592
705
|
}
|
|
593
706
|
|
|
594
|
-
if (Array.isArray(schema.type)) {
|
|
707
|
+
if (Object.hasOwn(schema, "type") && Array.isArray(schema.type)) {
|
|
595
708
|
const typeVariants = schema.type.filter((entry): entry is string => typeof entry === "string");
|
|
596
709
|
const nonNullTypes = typeVariants.filter(entry => entry !== "null");
|
|
597
710
|
if (typeVariants.includes("null") && nonNullTypes.length === 1) {
|
|
@@ -601,13 +714,13 @@ function extractNullableUnionSchema(schema: unknown): NullableExtractionResult {
|
|
|
601
714
|
}
|
|
602
715
|
|
|
603
716
|
for (const combiner of JSON_SCHEMA_COMBINERS) {
|
|
604
|
-
const variantsRaw = schema[combiner];
|
|
717
|
+
const variantsRaw = Object.hasOwn(schema, combiner) ? schema[combiner] : undefined;
|
|
605
718
|
if (!Array.isArray(variantsRaw)) continue;
|
|
606
719
|
|
|
607
720
|
let hasNullVariant = false;
|
|
608
721
|
const nonNullVariants: unknown[] = [];
|
|
609
722
|
for (const variant of variantsRaw) {
|
|
610
|
-
if (isJsonObject(variant) && variant.type === "null") {
|
|
723
|
+
if (isJsonObject(variant) && Object.hasOwn(variant, "type") && variant.type === "null") {
|
|
611
724
|
let keyCount = 0;
|
|
612
725
|
for (const k in variant) {
|
|
613
726
|
if (!Object.hasOwn(variant, k)) continue;
|
|
@@ -630,12 +743,12 @@ function extractNullableUnionSchema(schema: unknown): NullableExtractionResult {
|
|
|
630
743
|
for (const key in nonNullVariant) {
|
|
631
744
|
if (!Object.hasOwn(nonNullVariant, key)) continue;
|
|
632
745
|
const value = nonNullVariant[key];
|
|
633
|
-
const existingValue = nextSchema[key];
|
|
746
|
+
const existingValue = Object.hasOwn(nextSchema, key) ? nextSchema[key] : undefined;
|
|
634
747
|
if (existingValue !== undefined && !areJsonValuesEqual(existingValue, value)) {
|
|
635
748
|
return { schema, nullable: false };
|
|
636
749
|
}
|
|
637
750
|
if (existingValue === undefined) {
|
|
638
|
-
nextSchema
|
|
751
|
+
setOwnKey(nextSchema, key, value);
|
|
639
752
|
}
|
|
640
753
|
}
|
|
641
754
|
return { schema: nextSchema, nullable: true };
|
|
@@ -649,6 +762,23 @@ interface NullableNormalizationResult {
|
|
|
649
762
|
nullable: boolean;
|
|
650
763
|
}
|
|
651
764
|
|
|
765
|
+
function normalizeNullableSchemaMapForCloudCodeAssist(
|
|
766
|
+
value: JsonObject,
|
|
767
|
+
propertyMap: boolean,
|
|
768
|
+
epoch: number,
|
|
769
|
+
): { schema: JsonObject; nullableKeys: Set<string> } {
|
|
770
|
+
if (!once(value, epoch)) return { schema: {}, nullableKeys: new Set() };
|
|
771
|
+
const normalized: JsonObject = {};
|
|
772
|
+
const nullableKeys = new Set<string>();
|
|
773
|
+
for (const key in value) {
|
|
774
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
775
|
+
const child = normalizeNullablePropertiesForCloudCodeAssist(value[key], propertyMap, epoch);
|
|
776
|
+
setOwnKey(normalized, key, child.schema);
|
|
777
|
+
if (child.nullable) nullableKeys.add(key);
|
|
778
|
+
}
|
|
779
|
+
return { schema: normalized, nullableKeys };
|
|
780
|
+
}
|
|
781
|
+
|
|
652
782
|
function normalizeNullablePropertiesForCloudCodeAssist(
|
|
653
783
|
value: unknown,
|
|
654
784
|
isPropertySchema = false,
|
|
@@ -671,29 +801,30 @@ function normalizeNullablePropertiesForCloudCodeAssist(
|
|
|
671
801
|
}
|
|
672
802
|
|
|
673
803
|
const normalized: JsonObject = {};
|
|
804
|
+
let nullablePropertyKeys: Set<string> | undefined;
|
|
674
805
|
for (const key in value) {
|
|
675
|
-
if (Object.hasOwn(value, key))
|
|
676
|
-
|
|
806
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
807
|
+
if (JSON_SCHEMA_LITERAL_PAYLOAD_KEYS.has(key)) {
|
|
808
|
+
setOwnKey(normalized, key, value[key]);
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
if (JSON_SCHEMA_MAP_KEYS.has(key) && isJsonObject(value[key])) {
|
|
812
|
+
const mapped = normalizeNullableSchemaMapForCloudCodeAssist(value[key], key === "properties", epoch);
|
|
813
|
+
setOwnKey(normalized, key, mapped.schema);
|
|
814
|
+
if (key === "properties") nullablePropertyKeys = mapped.nullableKeys;
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
setOwnKey(normalized, key, normalizeNullablePropertiesForCloudCodeAssist(value[key], false, epoch).schema);
|
|
677
818
|
}
|
|
678
819
|
|
|
679
|
-
if (isJsonObject(normalized.properties)) {
|
|
680
|
-
const properties = normalized.properties;
|
|
820
|
+
if (nullablePropertyKeys && isJsonObject(normalized.properties)) {
|
|
681
821
|
const required = new Set(
|
|
682
822
|
Array.isArray(normalized.required)
|
|
683
823
|
? normalized.required.filter((entry): entry is string => typeof entry === "string")
|
|
684
824
|
: [],
|
|
685
825
|
);
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
if (!Object.hasOwn(properties, name)) continue;
|
|
689
|
-
const normalizedProperty = normalizeNullablePropertiesForCloudCodeAssist(properties[name], true, epoch);
|
|
690
|
-
nextProperties[name] = normalizedProperty.schema;
|
|
691
|
-
if (normalizedProperty.nullable) {
|
|
692
|
-
required.delete(name);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
normalized.properties = nextProperties;
|
|
696
|
-
if (Array.isArray(normalized.required)) {
|
|
826
|
+
for (const key of nullablePropertyKeys) required.delete(key);
|
|
827
|
+
if (Object.hasOwn(normalized, "required") && Array.isArray(normalized.required)) {
|
|
697
828
|
normalized.required = Array.from(required);
|
|
698
829
|
}
|
|
699
830
|
}
|
|
@@ -750,23 +881,38 @@ function hasResidualSchemaIncompatibilities(
|
|
|
750
881
|
return false;
|
|
751
882
|
}
|
|
752
883
|
|
|
753
|
-
if (checks.typeArray && Array.isArray(value.type)) return true;
|
|
754
|
-
if (checks.typeNull && value.type === "null") return true;
|
|
884
|
+
if (checks.typeArray && Object.hasOwn(value, "type") && Array.isArray(value.type)) return true;
|
|
885
|
+
if (checks.typeNull && Object.hasOwn(value, "type") && value.type === "null") return true;
|
|
755
886
|
if (checks.nullable && Object.hasOwn(value, "nullable")) return true;
|
|
756
887
|
if (checks.combiners) {
|
|
757
888
|
for (const combiner of CCA_FORBIDDEN_COMBINERS) {
|
|
758
|
-
if (Array.isArray(value[combiner])) return true;
|
|
889
|
+
if (Object.hasOwn(value, combiner) && Array.isArray(value[combiner])) return true;
|
|
759
890
|
}
|
|
760
891
|
}
|
|
761
892
|
for (const k in value) {
|
|
762
893
|
if (!Object.hasOwn(value, k)) continue;
|
|
763
|
-
if (
|
|
894
|
+
if (JSON_SCHEMA_LITERAL_PAYLOAD_KEYS.has(k)) continue;
|
|
895
|
+
const child = value[k];
|
|
896
|
+
const hasResidual =
|
|
897
|
+
JSON_SCHEMA_MAP_KEYS.has(k) && isJsonObject(child)
|
|
898
|
+
? hasResidualSchemaMap(child, checks, epoch)
|
|
899
|
+
: hasResidualSchemaIncompatibilities(child, checks, epoch);
|
|
900
|
+
if (hasResidual) {
|
|
764
901
|
return true;
|
|
765
902
|
}
|
|
766
903
|
}
|
|
767
904
|
return false;
|
|
768
905
|
}
|
|
769
906
|
|
|
907
|
+
function hasResidualSchemaMap(value: JsonObject, checks: ResidualIncompatibilityChecks, epoch: number): boolean {
|
|
908
|
+
if (!once(value, epoch)) return false;
|
|
909
|
+
for (const key in value) {
|
|
910
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
911
|
+
if (hasResidualSchemaIncompatibilities(value[key], checks, epoch)) return true;
|
|
912
|
+
}
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
|
|
770
916
|
export function normalizeSchema(value: unknown, options: NormalizeSchemaOptions): unknown {
|
|
771
917
|
const detoxified = decontaminateZodInstance(value);
|
|
772
918
|
const upgraded = upgradeJsonSchemaTo202012(detoxified);
|
|
@@ -832,8 +978,102 @@ export function normalizeSchemaForCCA(value: unknown): unknown {
|
|
|
832
978
|
});
|
|
833
979
|
}
|
|
834
980
|
|
|
981
|
+
const MCP_SCHEMA_ARRAY_KEYS = new Set(["anyOf", "oneOf", "allOf", "prefixItems"]);
|
|
982
|
+
const MCP_SCHEMA_MAP_KEYS = new Set([
|
|
983
|
+
"properties",
|
|
984
|
+
"patternProperties",
|
|
985
|
+
"dependencies",
|
|
986
|
+
"dependentSchemas",
|
|
987
|
+
"$defs",
|
|
988
|
+
"definitions",
|
|
989
|
+
]);
|
|
990
|
+
const MCP_SCHEMA_VALUE_KEYS = new Set([
|
|
991
|
+
"items",
|
|
992
|
+
"additionalItems",
|
|
993
|
+
"contains",
|
|
994
|
+
"contentSchema",
|
|
995
|
+
"propertyNames",
|
|
996
|
+
"if",
|
|
997
|
+
"then",
|
|
998
|
+
"else",
|
|
999
|
+
"not",
|
|
1000
|
+
"additionalProperties",
|
|
1001
|
+
"unevaluatedItems",
|
|
1002
|
+
"unevaluatedProperties",
|
|
1003
|
+
]);
|
|
1004
|
+
|
|
1005
|
+
function makeImplicitMcpObjectMapsExplicit(value: unknown): unknown {
|
|
1006
|
+
return normalizeMcpObjectMapNode(value, new WeakMap());
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function normalizeMcpObjectMapNode(value: unknown, cache: WeakMap<JsonObject, JsonObject>): unknown {
|
|
1010
|
+
if (!isJsonObject(value)) return value;
|
|
1011
|
+
const cached = cache.get(value);
|
|
1012
|
+
if (cached) return cached;
|
|
1013
|
+
|
|
1014
|
+
const output: JsonObject = {};
|
|
1015
|
+
cache.set(value, output);
|
|
1016
|
+
let changed = false;
|
|
1017
|
+
|
|
1018
|
+
for (const key in value) {
|
|
1019
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
1020
|
+
const child = value[key];
|
|
1021
|
+
let next: unknown = child;
|
|
1022
|
+
if (MCP_SCHEMA_MAP_KEYS.has(key) && isJsonObject(child)) {
|
|
1023
|
+
next = normalizeMcpObjectMap(child, cache);
|
|
1024
|
+
} else if (MCP_SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(child)) {
|
|
1025
|
+
next = normalizeMcpObjectMapArray(child, cache);
|
|
1026
|
+
} else if (MCP_SCHEMA_VALUE_KEYS.has(key)) {
|
|
1027
|
+
next = Array.isArray(child)
|
|
1028
|
+
? normalizeMcpObjectMapArray(child, cache)
|
|
1029
|
+
: normalizeMcpObjectMapNode(child, cache);
|
|
1030
|
+
}
|
|
1031
|
+
if (next !== child) changed = true;
|
|
1032
|
+
setOwnKey(output, key, next);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const schemaType = Object.hasOwn(value, "type") ? value.type : undefined;
|
|
1036
|
+
if (
|
|
1037
|
+
declaresObjectType(schemaType) &&
|
|
1038
|
+
!Object.hasOwn(value, "properties") &&
|
|
1039
|
+
!Object.hasOwn(value, "additionalProperties") &&
|
|
1040
|
+
!Object.hasOwn(value, "patternProperties") &&
|
|
1041
|
+
!Object.hasOwn(value, "unevaluatedProperties")
|
|
1042
|
+
) {
|
|
1043
|
+
output.additionalProperties = true;
|
|
1044
|
+
changed = true;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const result = changed ? output : value;
|
|
1048
|
+
cache.set(value, result);
|
|
1049
|
+
return result;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function normalizeMcpObjectMapArray(value: unknown[], cache: WeakMap<JsonObject, JsonObject>): unknown[] {
|
|
1053
|
+
let changed = false;
|
|
1054
|
+
const output = value.map(item => {
|
|
1055
|
+
const next = normalizeMcpObjectMapNode(item, cache);
|
|
1056
|
+
if (next !== item) changed = true;
|
|
1057
|
+
return next;
|
|
1058
|
+
});
|
|
1059
|
+
return changed ? output : value;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function normalizeMcpObjectMap(schemaMap: JsonObject, cache: WeakMap<JsonObject, JsonObject>): JsonObject {
|
|
1063
|
+
let changed = false;
|
|
1064
|
+
const output: JsonObject = {};
|
|
1065
|
+
for (const key in schemaMap) {
|
|
1066
|
+
if (!Object.hasOwn(schemaMap, key)) continue;
|
|
1067
|
+
const child = schemaMap[key];
|
|
1068
|
+
const next = normalizeMcpObjectMapNode(child, cache);
|
|
1069
|
+
if (next !== child) changed = true;
|
|
1070
|
+
setOwnKey(output, key, next);
|
|
1071
|
+
}
|
|
1072
|
+
return changed ? output : schemaMap;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
835
1075
|
export function normalizeSchemaForMCP(value: unknown): unknown {
|
|
836
|
-
|
|
1076
|
+
const normalized = normalizeSchema(value, {
|
|
837
1077
|
unsupportedFields: isMcpUnsupportedSchemaField,
|
|
838
1078
|
normalizeFieldNames: false,
|
|
839
1079
|
collapseNullFields: false,
|
|
@@ -848,6 +1088,7 @@ export function normalizeSchemaForMCP(value: unknown): unknown {
|
|
|
848
1088
|
stripResidualCombinersFixpoint: false,
|
|
849
1089
|
extractNullableFromUnions: false,
|
|
850
1090
|
});
|
|
1091
|
+
return makeImplicitMcpObjectMapsExplicit(normalized);
|
|
851
1092
|
}
|
|
852
1093
|
|
|
853
1094
|
// ---------------------------------------------------------------------------
|
|
@@ -952,7 +1193,7 @@ function normalizeOpenAIResponsesSchemaNode(value: unknown, cache: WeakMap<JsonO
|
|
|
952
1193
|
}
|
|
953
1194
|
|
|
954
1195
|
if (next !== child) changed = true;
|
|
955
|
-
output
|
|
1196
|
+
setOwnKey(output, key, next);
|
|
956
1197
|
}
|
|
957
1198
|
|
|
958
1199
|
if (Array.isArray(value.oneOf)) {
|
|
@@ -966,7 +1207,7 @@ function normalizeOpenAIResponsesSchemaNode(value: unknown, cache: WeakMap<JsonO
|
|
|
966
1207
|
// Draft 2020-12 lets `type` be an array (e.g. `["object", "null"]`); treat
|
|
967
1208
|
// any variant that includes "object" as an object position for the
|
|
968
1209
|
// properties requirement.
|
|
969
|
-
if (declaresObjectType(value.type) && !Object.hasOwn(value, "properties")) {
|
|
1210
|
+
if (Object.hasOwn(value, "type") && declaresObjectType(value.type) && !Object.hasOwn(value, "properties")) {
|
|
970
1211
|
output.properties = {};
|
|
971
1212
|
changed = true;
|
|
972
1213
|
}
|
|
@@ -1007,7 +1248,7 @@ function normalizeOpenAIResponsesSchemaMap(schemaMap: JsonObject, cache: WeakMap
|
|
|
1007
1248
|
const child = schemaMap[key];
|
|
1008
1249
|
const next = normalizeOpenAIResponsesSchemaNode(child, cache);
|
|
1009
1250
|
if (next !== child) changed = true;
|
|
1010
|
-
output
|
|
1251
|
+
setOwnKey(output, key, next);
|
|
1011
1252
|
}
|
|
1012
1253
|
return changed ? output : schemaMap;
|
|
1013
1254
|
}
|
|
@@ -1089,21 +1330,24 @@ function hasUnrepresentableStrictObjectMap(schema: Record<string, unknown>, epoc
|
|
|
1089
1330
|
if (!once(schema, epoch)) return false;
|
|
1090
1331
|
|
|
1091
1332
|
let hasPatternProperties = false;
|
|
1092
|
-
if (isJsonObject(schema.patternProperties)) {
|
|
1333
|
+
if (Object.hasOwn(schema, "patternProperties") && isJsonObject(schema.patternProperties)) {
|
|
1093
1334
|
for (const _ in schema.patternProperties) {
|
|
1094
1335
|
hasPatternProperties = true;
|
|
1095
1336
|
break;
|
|
1096
1337
|
}
|
|
1097
1338
|
}
|
|
1098
|
-
const additionalPropertiesValue = schema
|
|
1339
|
+
const additionalPropertiesValue = Object.hasOwn(schema, "additionalProperties")
|
|
1340
|
+
? schema.additionalProperties
|
|
1341
|
+
: undefined;
|
|
1099
1342
|
const hasSchemaAdditionalProperties = additionalPropertiesValue === true || isJsonObject(additionalPropertiesValue);
|
|
1100
1343
|
if (hasPatternProperties || hasSchemaAdditionalProperties) {
|
|
1101
1344
|
return true;
|
|
1102
1345
|
}
|
|
1103
1346
|
|
|
1104
|
-
if (isJsonObject(schema.properties)) {
|
|
1347
|
+
if (Object.hasOwn(schema, "properties") && isJsonObject(schema.properties)) {
|
|
1105
1348
|
const properties = schema.properties;
|
|
1106
1349
|
for (const k in properties) {
|
|
1350
|
+
if (!Object.hasOwn(properties, k)) continue;
|
|
1107
1351
|
const propertySchema = properties[k];
|
|
1108
1352
|
if (isJsonObject(propertySchema) && hasUnrepresentableStrictObjectMap(propertySchema, epoch)) {
|
|
1109
1353
|
return true;
|
|
@@ -1111,18 +1355,18 @@ function hasUnrepresentableStrictObjectMap(schema: Record<string, unknown>, epoc
|
|
|
1111
1355
|
}
|
|
1112
1356
|
}
|
|
1113
1357
|
|
|
1114
|
-
if (isJsonObject(schema.items)) {
|
|
1358
|
+
if (Object.hasOwn(schema, "items") && isJsonObject(schema.items)) {
|
|
1115
1359
|
if (hasUnrepresentableStrictObjectMap(schema.items, epoch)) {
|
|
1116
1360
|
return true;
|
|
1117
1361
|
}
|
|
1118
|
-
} else if (Array.isArray(schema.items)) {
|
|
1362
|
+
} else if (Object.hasOwn(schema, "items") && Array.isArray(schema.items)) {
|
|
1119
1363
|
for (const itemSchema of schema.items) {
|
|
1120
1364
|
if (isJsonObject(itemSchema) && hasUnrepresentableStrictObjectMap(itemSchema, epoch)) {
|
|
1121
1365
|
return true;
|
|
1122
1366
|
}
|
|
1123
1367
|
}
|
|
1124
1368
|
}
|
|
1125
|
-
if (Array.isArray(schema.prefixItems)) {
|
|
1369
|
+
if (Object.hasOwn(schema, "prefixItems") && Array.isArray(schema.prefixItems)) {
|
|
1126
1370
|
for (const itemSchema of schema.prefixItems) {
|
|
1127
1371
|
if (isJsonObject(itemSchema) && hasUnrepresentableStrictObjectMap(itemSchema, epoch)) {
|
|
1128
1372
|
return true;
|
|
@@ -1131,7 +1375,7 @@ function hasUnrepresentableStrictObjectMap(schema: Record<string, unknown>, epoc
|
|
|
1131
1375
|
}
|
|
1132
1376
|
|
|
1133
1377
|
for (const key of COMBINATOR_KEYS) {
|
|
1134
|
-
const variants = schema[key];
|
|
1378
|
+
const variants = Object.hasOwn(schema, key) ? schema[key] : undefined;
|
|
1135
1379
|
if (!Array.isArray(variants)) continue;
|
|
1136
1380
|
for (const variant of variants) {
|
|
1137
1381
|
if (isJsonObject(variant) && hasUnrepresentableStrictObjectMap(variant, epoch)) {
|
|
@@ -1141,9 +1385,10 @@ function hasUnrepresentableStrictObjectMap(schema: Record<string, unknown>, epoc
|
|
|
1141
1385
|
}
|
|
1142
1386
|
|
|
1143
1387
|
for (const defsKey of ["$defs", "definitions"] as const) {
|
|
1144
|
-
const defs = schema[defsKey];
|
|
1388
|
+
const defs = Object.hasOwn(schema, defsKey) ? schema[defsKey] : undefined;
|
|
1145
1389
|
if (!isJsonObject(defs)) continue;
|
|
1146
1390
|
for (const k in defs) {
|
|
1391
|
+
if (!Object.hasOwn(defs, k)) continue;
|
|
1147
1392
|
const defSchema = defs[k];
|
|
1148
1393
|
if (isJsonObject(defSchema) && hasUnrepresentableStrictObjectMap(defSchema, epoch)) {
|
|
1149
1394
|
return true;
|
|
@@ -1186,7 +1431,7 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1186
1431
|
// OpenAI strict mode forbids `{$ref, description, ...}`; the SDK resolves
|
|
1187
1432
|
// and merges, with sibling keys taking precedence over the ref'd def.
|
|
1188
1433
|
// Cite: openai-python/src/openai/lib/_pydantic.py:96-110 (`_ensure_strict_json_schema`)
|
|
1189
|
-
if (typeof schema.$ref === "string") {
|
|
1434
|
+
if (Object.hasOwn(schema, "$ref") && typeof schema.$ref === "string") {
|
|
1190
1435
|
let hasSibling = false;
|
|
1191
1436
|
for (const k in schema) {
|
|
1192
1437
|
if (k !== "$ref" && Object.hasOwn(schema, k)) {
|
|
@@ -1201,7 +1446,7 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1201
1446
|
const merged: Record<string, unknown> = { ...resolved };
|
|
1202
1447
|
for (const k in schema) {
|
|
1203
1448
|
if (k === "$ref" || !Object.hasOwn(schema, k)) continue;
|
|
1204
|
-
merged
|
|
1449
|
+
setOwnKey(merged, k, schema[k]);
|
|
1205
1450
|
}
|
|
1206
1451
|
const result = sanitizeSchemaForStrictMode(merged, epoch, cache, root);
|
|
1207
1452
|
cache.set(schema, result);
|
|
@@ -1215,13 +1460,13 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1215
1460
|
// entry's keys WIN over original sibling keys, then `allOf` is dropped.
|
|
1216
1461
|
// Cite: openai-python/src/openai/lib/_pydantic.py:79-83
|
|
1217
1462
|
{
|
|
1218
|
-
const allOf = schema.allOf;
|
|
1463
|
+
const allOf = Object.hasOwn(schema, "allOf") ? schema.allOf : undefined;
|
|
1219
1464
|
if (Array.isArray(allOf) && allOf.length === 1 && isJsonObject(allOf[0])) {
|
|
1220
1465
|
const merged: Record<string, unknown> = { ...schema };
|
|
1221
1466
|
delete merged.allOf;
|
|
1222
1467
|
const sole = allOf[0] as Record<string, unknown>;
|
|
1223
1468
|
for (const k in sole) {
|
|
1224
|
-
if (Object.hasOwn(sole, k)) merged
|
|
1469
|
+
if (Object.hasOwn(sole, k)) setOwnKey(merged, k, sole[k]);
|
|
1225
1470
|
}
|
|
1226
1471
|
const result = sanitizeSchemaForStrictMode(merged, epoch, cache, root);
|
|
1227
1472
|
cache.set(schema, result);
|
|
@@ -1229,7 +1474,7 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1229
1474
|
}
|
|
1230
1475
|
}
|
|
1231
1476
|
|
|
1232
|
-
const typeValue = schema.type;
|
|
1477
|
+
const typeValue = Object.hasOwn(schema, "type") ? schema.type : undefined;
|
|
1233
1478
|
if (Array.isArray(typeValue)) {
|
|
1234
1479
|
const typeVariants = typeValue.filter((entry): entry is string => typeof entry === "string");
|
|
1235
1480
|
const schemaWithoutType = { ...schema };
|
|
@@ -1282,8 +1527,9 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1282
1527
|
const sanitized: Record<string, unknown> = {};
|
|
1283
1528
|
cache.set(schema, sanitized);
|
|
1284
1529
|
for (const key in schema) {
|
|
1530
|
+
if (!Object.hasOwn(schema, key)) continue;
|
|
1285
1531
|
const value = schema[key];
|
|
1286
|
-
if (key
|
|
1532
|
+
if (Object.hasOwn(NON_STRUCTURAL_SCHEMA_KEYS, key) || key === "type" || key === "const" || key === "nullable") {
|
|
1287
1533
|
continue;
|
|
1288
1534
|
}
|
|
1289
1535
|
// `properties` map — recurse into each property schema.
|
|
@@ -1291,10 +1537,15 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1291
1537
|
if (key === "properties" && isJsonObject(value)) {
|
|
1292
1538
|
const properties: Record<string, unknown> = {};
|
|
1293
1539
|
for (const propertyName in value) {
|
|
1540
|
+
if (!Object.hasOwn(value, propertyName)) continue;
|
|
1294
1541
|
const propertySchema = value[propertyName];
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1542
|
+
setOwnKey(
|
|
1543
|
+
properties,
|
|
1544
|
+
propertyName,
|
|
1545
|
+
isJsonObject(propertySchema)
|
|
1546
|
+
? sanitizeSchemaForStrictMode(propertySchema, epoch, cache, root)
|
|
1547
|
+
: propertySchema,
|
|
1548
|
+
);
|
|
1298
1549
|
}
|
|
1299
1550
|
sanitized.properties = properties;
|
|
1300
1551
|
continue;
|
|
@@ -1324,8 +1575,10 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1324
1575
|
// `anyOf`/`oneOf`/`allOf` arrays — recurse into each branch.
|
|
1325
1576
|
|
|
1326
1577
|
if (COMBINATOR_KEYS.includes(key as (typeof COMBINATOR_KEYS)[number]) && Array.isArray(value)) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1578
|
+
setOwnKey(
|
|
1579
|
+
sanitized,
|
|
1580
|
+
key,
|
|
1581
|
+
value.map(entry => (isJsonObject(entry) ? sanitizeSchemaForStrictMode(entry, epoch, cache, root) : entry)),
|
|
1329
1582
|
);
|
|
1330
1583
|
continue;
|
|
1331
1584
|
}
|
|
@@ -1334,12 +1587,17 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1334
1587
|
if ((key === "$defs" || key === "definitions") && isJsonObject(value)) {
|
|
1335
1588
|
const defs: Record<string, unknown> = {};
|
|
1336
1589
|
for (const definitionName in value) {
|
|
1590
|
+
if (!Object.hasOwn(value, definitionName)) continue;
|
|
1337
1591
|
const definitionSchema = value[definitionName];
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1592
|
+
setOwnKey(
|
|
1593
|
+
defs,
|
|
1594
|
+
definitionName,
|
|
1595
|
+
isJsonObject(definitionSchema)
|
|
1596
|
+
? sanitizeSchemaForStrictMode(definitionSchema, epoch, cache, root)
|
|
1597
|
+
: definitionSchema,
|
|
1598
|
+
);
|
|
1341
1599
|
}
|
|
1342
|
-
sanitized
|
|
1600
|
+
setOwnKey(sanitized, key, defs);
|
|
1343
1601
|
continue;
|
|
1344
1602
|
}
|
|
1345
1603
|
// `additionalProperties` is owned by `enforceStrictSchema`, which sets it to false.
|
|
@@ -1348,7 +1606,12 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1348
1606
|
continue;
|
|
1349
1607
|
}
|
|
1350
1608
|
|
|
1351
|
-
if (
|
|
1609
|
+
if (
|
|
1610
|
+
key === "description" &&
|
|
1611
|
+
typeof value === "string" &&
|
|
1612
|
+
Object.hasOwn(schema, "default") &&
|
|
1613
|
+
schema.default !== undefined
|
|
1614
|
+
) {
|
|
1352
1615
|
// Preserve `default:` info for strict-mode providers that strip the keyword.
|
|
1353
1616
|
// Inline as `(default: X)` text in the description, matching the convention for
|
|
1354
1617
|
// runtime-placeholder defaults (e.g. `cwd`) that cannot live in the keyword form.
|
|
@@ -1358,7 +1621,7 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1358
1621
|
continue;
|
|
1359
1622
|
}
|
|
1360
1623
|
|
|
1361
|
-
sanitized
|
|
1624
|
+
setOwnKey(sanitized, key, value);
|
|
1362
1625
|
}
|
|
1363
1626
|
// Post-pass: re-derive `type` and turn dropped keywords into a representable shape.
|
|
1364
1627
|
|
|
@@ -1394,7 +1657,7 @@ export function sanitizeSchemaForStrictMode(
|
|
|
1394
1657
|
// `description` hoists to the wrapper so both branches share it without
|
|
1395
1658
|
// duplication — matches the optional-property wrap in `enforceStrictSchema`
|
|
1396
1659
|
// and the typical OpenAI strict-mode "description on the union" shape.
|
|
1397
|
-
if (schema.nullable === true) {
|
|
1660
|
+
if (Object.hasOwn(schema, "nullable") && schema.nullable === true) {
|
|
1398
1661
|
const { nullable: _, description, ...withoutNullable } = sanitized;
|
|
1399
1662
|
const wrapper: JsonObject = { anyOf: [withoutNullable, { type: "null" }] };
|
|
1400
1663
|
if (description !== undefined) wrapper.description = description;
|
|
@@ -1456,6 +1719,7 @@ function enforceStrictSchemaBody(
|
|
|
1456
1719
|
);
|
|
1457
1720
|
const strictProperties: Record<string, unknown> = {};
|
|
1458
1721
|
for (const key in props) {
|
|
1722
|
+
if (!Object.hasOwn(props, key)) continue;
|
|
1459
1723
|
const value = props[key];
|
|
1460
1724
|
const processed =
|
|
1461
1725
|
value != null && typeof value === "object" && !Array.isArray(value)
|
|
@@ -1467,20 +1731,20 @@ function enforceStrictSchemaBody(
|
|
|
1467
1731
|
if (
|
|
1468
1732
|
isJsonObject(processed) &&
|
|
1469
1733
|
Array.isArray(processed.anyOf) &&
|
|
1470
|
-
processed.anyOf.some(v => isJsonObject(v) && v.type === "null")
|
|
1734
|
+
processed.anyOf.some(v => isJsonObject(v) && Object.hasOwn(v, "type") && v.type === "null")
|
|
1471
1735
|
) {
|
|
1472
|
-
strictProperties
|
|
1736
|
+
setOwnKey(strictProperties, key, processed);
|
|
1473
1737
|
continue;
|
|
1474
1738
|
}
|
|
1475
1739
|
if (isJsonObject(processed) && typeof processed.description === "string") {
|
|
1476
1740
|
const { description, ...withoutDescription } = processed;
|
|
1477
|
-
strictProperties
|
|
1741
|
+
setOwnKey(strictProperties, key, { anyOf: [withoutDescription, { type: "null" }], description });
|
|
1478
1742
|
continue;
|
|
1479
1743
|
}
|
|
1480
|
-
strictProperties
|
|
1744
|
+
setOwnKey(strictProperties, key, { anyOf: [processed, { type: "null" }] });
|
|
1481
1745
|
continue;
|
|
1482
1746
|
}
|
|
1483
|
-
strictProperties
|
|
1747
|
+
setOwnKey(strictProperties, key, processed);
|
|
1484
1748
|
}
|
|
1485
1749
|
result.properties = strictProperties;
|
|
1486
1750
|
result.required = Object.keys(strictProperties);
|
|
@@ -1504,26 +1768,39 @@ function enforceStrictSchemaBody(
|
|
|
1504
1768
|
);
|
|
1505
1769
|
}
|
|
1506
1770
|
for (const key of COMBINATOR_KEYS) {
|
|
1507
|
-
if (Array.isArray(result[key])) {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1771
|
+
if (Object.hasOwn(result, key) && Array.isArray(result[key])) {
|
|
1772
|
+
setOwnKey(
|
|
1773
|
+
result,
|
|
1774
|
+
key,
|
|
1775
|
+
(result[key] as unknown[]).map(entry =>
|
|
1776
|
+
entry != null && typeof entry === "object" && !Array.isArray(entry)
|
|
1777
|
+
? enforceStrictSchema(entry as Record<string, unknown>, cache)
|
|
1778
|
+
: entry,
|
|
1779
|
+
),
|
|
1512
1780
|
);
|
|
1513
1781
|
}
|
|
1514
1782
|
}
|
|
1515
1783
|
for (const defsKey of ["$defs", "definitions"] as const) {
|
|
1516
|
-
if (
|
|
1784
|
+
if (
|
|
1785
|
+
Object.hasOwn(result, defsKey) &&
|
|
1786
|
+
result[defsKey] != null &&
|
|
1787
|
+
typeof result[defsKey] === "object" &&
|
|
1788
|
+
!Array.isArray(result[defsKey])
|
|
1789
|
+
) {
|
|
1517
1790
|
const defs = result[defsKey] as Record<string, unknown>;
|
|
1518
1791
|
const nextDefs: Record<string, unknown> = {};
|
|
1519
1792
|
for (const name in defs) {
|
|
1793
|
+
if (!Object.hasOwn(defs, name)) continue;
|
|
1520
1794
|
const def = defs[name];
|
|
1521
|
-
|
|
1795
|
+
setOwnKey(
|
|
1796
|
+
nextDefs,
|
|
1797
|
+
name,
|
|
1522
1798
|
def != null && typeof def === "object" && !Array.isArray(def)
|
|
1523
1799
|
? enforceStrictSchema(def as Record<string, unknown>, cache)
|
|
1524
|
-
: def
|
|
1800
|
+
: def,
|
|
1801
|
+
);
|
|
1525
1802
|
}
|
|
1526
|
-
result
|
|
1803
|
+
setOwnKey(result, defsKey, nextDefs);
|
|
1527
1804
|
}
|
|
1528
1805
|
}
|
|
1529
1806
|
// Strict mode requires every schema node to declare a concrete type (or
|