@gajae-code/ai 0.15.6 → 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.
Files changed (39) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/types/adapter-internals/aws-region.d.ts +7 -0
  3. package/dist/types/core.d.ts +1 -0
  4. package/dist/types/index.d.ts +1 -0
  5. package/dist/types/providers/anthropic.d.ts +1 -1
  6. package/dist/types/providers/google-gemini-headers.d.ts +1 -1
  7. package/dist/types/providers/openai-codex-responses.d.ts +6 -0
  8. package/dist/types/utils/schema/normalize.d.ts +0 -5
  9. package/dist/types/utils/sqlite-errors.d.ts +4 -0
  10. package/package.json +3 -3
  11. package/src/adapter-internals/aws-region.d.ts +7 -0
  12. package/src/adapter-internals/aws-region.ts +14 -0
  13. package/src/auth-storage.ts +14 -14
  14. package/src/core.ts +1 -0
  15. package/src/index.ts +1 -0
  16. package/src/model-thinking.ts +8 -0
  17. package/src/models.json +201 -3
  18. package/src/provider-models/openai-compat.ts +61 -0
  19. package/src/providers/amazon-bedrock.ts +5 -1
  20. package/src/providers/anthropic.d.ts +1 -1
  21. package/src/providers/anthropic.ts +1 -1
  22. package/src/providers/aws-credentials.ts +6 -0
  23. package/src/providers/google-gemini-headers.d.ts +1 -1
  24. package/src/providers/google-gemini-headers.ts +1 -1
  25. package/src/providers/kiro-api-key.ts +33 -8
  26. package/src/providers/kiro-codewhisperer.ts +4 -1
  27. package/src/providers/openai-codex-responses.d.ts +6 -0
  28. package/src/providers/openai-codex-responses.ts +17 -2
  29. package/src/providers/pi-native-client.ts +24 -1
  30. package/src/utils/oauth/kiro.ts +91 -22
  31. package/src/utils/schema/dereference.ts +169 -49
  32. package/src/utils/schema/draft.ts +46 -23
  33. package/src/utils/schema/normalize.d.ts +0 -5
  34. package/src/utils/schema/normalize.ts +396 -119
  35. package/src/utils/schema/types.ts +3 -1
  36. package/src/utils/schema/zod-decontaminate.ts +83 -29
  37. package/src/utils/sqlite-errors.d.ts +4 -0
  38. package/src/utils/sqlite-errors.ts +13 -0
  39. package/src/utils/tool-choice-capability.ts +2 -3
@@ -11,69 +11,190 @@
11
11
  */
12
12
  import { isJsonObject, type JsonObject } from "./types";
13
13
 
14
- /**
15
- * Resolve a JSON-pointer-style `$ref` against the root schema's `$defs`
16
- * or `definitions` block. Returns `undefined` for external or unresolvable refs.
17
- */
18
- function resolveLocalRef(ref: string, root: JsonObject): JsonObject | undefined {
19
- // Only handle local refs: #/$defs/Name or #/definitions/Name
20
- const match = /^#\/(\$defs|definitions)\/(.+)$/.exec(ref);
21
- if (!match) return undefined;
14
+ const JSON_SCHEMA_LITERAL_PAYLOAD_KEYS = new Set(["default", "const", "enum", "examples"]);
15
+ const JSON_SCHEMA_MAP_KEYS = new Set([
16
+ "properties",
17
+ "patternProperties",
18
+ "dependencies",
19
+ "dependentSchemas",
20
+ "$defs",
21
+ "definitions",
22
+ ]);
23
+
24
+ function setOwnKey(target: JsonObject, key: string, value: unknown): void {
25
+ if (key === "__proto__") {
26
+ Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
27
+ return;
28
+ }
29
+ target[key] = value;
30
+ }
31
+
32
+ type DereferenceState = {
33
+ unresolvedRef: boolean;
34
+ };
35
+
36
+ /** Resolve any local JSON Pointer, including escaped names and boolean schemas. */
37
+ function resolveLocalRef(ref: string, root: JsonObject): unknown | undefined {
38
+ if (!ref.startsWith("#")) return undefined;
39
+ let pointer: string;
40
+ try {
41
+ pointer = decodeURIComponent(ref.slice(1));
42
+ } catch {
43
+ return undefined;
44
+ }
45
+ if (pointer === "") return root;
46
+ if (!pointer.startsWith("/")) return undefined;
22
47
 
23
- const [, defsKey, name] = match;
24
- const defs = root[defsKey!];
25
- if (!isJsonObject(defs)) return undefined;
48
+ let current: unknown = root;
49
+ for (const encodedSegment of pointer.slice(1).split("/")) {
50
+ const segment = encodedSegment.replaceAll("~1", "/").replaceAll("~0", "~");
51
+ if (Array.isArray(current)) {
52
+ if (!/^(0|[1-9]\d*)$/.test(segment) || !Object.hasOwn(current, segment)) return undefined;
53
+ current = current[Number(segment)];
54
+ } else if (isJsonObject(current)) {
55
+ if (!Object.hasOwn(current, segment)) return undefined;
56
+ current = current[segment];
57
+ } else {
58
+ return undefined;
59
+ }
60
+ }
61
+ return current;
62
+ }
26
63
 
27
- const resolved = defs[name!];
28
- return isJsonObject(resolved) ? resolved : undefined;
64
+ /** Find definition maps anywhere in a schema graph, excluding literal instance data. */
65
+ function hasDefinitionMapDeep(value: unknown, seen: Set<object>, inSchemaMap = false): boolean {
66
+ if (Array.isArray(value)) {
67
+ if (seen.has(value)) return false;
68
+ seen.add(value);
69
+ return value.some(entry => hasDefinitionMapDeep(entry, seen, false));
70
+ }
71
+ if (!isJsonObject(value) || seen.has(value)) return false;
72
+ seen.add(value);
73
+ for (const key in value) {
74
+ if (!Object.hasOwn(value, key)) continue;
75
+ if (!inSchemaMap && (key === "$defs" || key === "definitions")) return true;
76
+ if (!inSchemaMap && JSON_SCHEMA_LITERAL_PAYLOAD_KEYS.has(key)) continue;
77
+ const childInSchemaMap = !inSchemaMap && JSON_SCHEMA_MAP_KEYS.has(key);
78
+ if (hasDefinitionMapDeep(value[key], seen, childInSchemaMap)) return true;
79
+ }
80
+ return false;
81
+ }
82
+
83
+ function dereferenceSchemaMap(
84
+ schemaMap: JsonObject,
85
+ root: JsonObject,
86
+ visitingRefs: Set<string>,
87
+ visitingNodes: Set<object>,
88
+ state: DereferenceState,
89
+ ): JsonObject {
90
+ const result: JsonObject = {};
91
+ for (const key in schemaMap) {
92
+ if (!Object.hasOwn(schemaMap, key)) continue;
93
+ setOwnKey(result, key, dereferenceNode(schemaMap[key], root, visitingRefs, visitingNodes, state));
94
+ }
95
+ return result;
29
96
  }
30
97
 
31
98
  /**
32
99
  * Recursively dereference a JSON Schema node, inlining all local `$ref` pointers.
100
+ * Object and array path tracking preserves the existing `{}` cycle boundary.
33
101
  */
34
- function dereferenceNode(node: unknown, root: JsonObject, visiting: Set<string>): unknown {
35
- if (!isJsonObject(node)) return node;
36
- if (Array.isArray(node)) return node.map(item => dereferenceNode(item, root, visiting));
102
+ function dereferenceNode(
103
+ node: unknown,
104
+ root: JsonObject,
105
+ visitingRefs: Set<string>,
106
+ visitingNodes: Set<object>,
107
+ state: DereferenceState,
108
+ inSchemaMap = false,
109
+ ): unknown {
110
+ if (!node || typeof node !== "object") return node;
111
+ if (visitingNodes.has(node)) return {};
112
+ visitingNodes.add(node);
113
+
114
+ if (Array.isArray(node)) {
115
+ const result = node.map(item => dereferenceNode(item, root, visitingRefs, visitingNodes, state));
116
+ visitingNodes.delete(node);
117
+ return result;
118
+ }
37
119
 
38
- const ref = node.$ref;
120
+ const schemaNode = node as JsonObject;
121
+ const ref = Object.hasOwn(schemaNode, "$ref") ? schemaNode.$ref : undefined;
39
122
  if (typeof ref === "string") {
40
- // Break circular references
41
- if (visiting.has(ref)) return {};
123
+ if (visitingRefs.has(ref)) {
124
+ visitingNodes.delete(node);
125
+ return {};
126
+ }
42
127
  const resolved = resolveLocalRef(ref, root);
43
- if (!resolved) return node; // External ref — leave as-is
44
- visiting.add(ref);
45
- const inlined = dereferenceNode(resolved, root, visiting);
46
- visiting.delete(ref);
128
+ if (resolved !== undefined) {
129
+ visitingRefs.add(ref);
130
+ const inlined = dereferenceNode(resolved, root, visitingRefs, visitingNodes, state);
131
+ visitingRefs.delete(ref);
47
132
 
48
- // Merge sibling keywords (e.g. description, default) from the
49
- // referencing node. In draft 2020-12 these are valid alongside $ref.
50
- let hasSiblings = false;
51
- for (const k in node) {
52
- if (k !== "$ref") {
53
- hasSiblings = true;
54
- break;
133
+ // Merge sibling keywords (e.g. description, default) from the
134
+ // referencing node. In draft 2020-12 these are valid alongside `$ref`.
135
+ let hasSiblings = false;
136
+ for (const key in schemaNode) {
137
+ if (Object.hasOwn(schemaNode, key) && key !== "$ref") {
138
+ hasSiblings = true;
139
+ break;
140
+ }
141
+ }
142
+ if (inlined === false) {
143
+ visitingNodes.delete(node);
144
+ return false;
145
+ }
146
+ if (!hasSiblings || (!isJsonObject(inlined) && inlined !== true)) {
147
+ visitingNodes.delete(node);
148
+ return inlined;
55
149
  }
150
+ const merged: JsonObject = {};
151
+ if (isJsonObject(inlined)) {
152
+ for (const key in inlined) {
153
+ if (Object.hasOwn(inlined, key)) setOwnKey(merged, key, inlined[key]);
154
+ }
155
+ }
156
+ for (const key in schemaNode) {
157
+ if (!Object.hasOwn(schemaNode, key) || key === "$ref") continue;
158
+ const siblingValue = schemaNode[key];
159
+ const sibling =
160
+ !inSchemaMap && JSON_SCHEMA_LITERAL_PAYLOAD_KEYS.has(key)
161
+ ? schemaNode[key]
162
+ : !inSchemaMap && JSON_SCHEMA_MAP_KEYS.has(key) && isJsonObject(siblingValue)
163
+ ? dereferenceSchemaMap(siblingValue, root, visitingRefs, visitingNodes, state)
164
+ : dereferenceNode(siblingValue, root, visitingRefs, visitingNodes, state);
165
+ setOwnKey(merged, key, sibling);
166
+ }
167
+ if (!state.unresolvedRef) {
168
+ delete merged.$defs;
169
+ delete merged.definitions;
170
+ }
171
+ visitingNodes.delete(node);
172
+ return merged;
56
173
  }
57
- if (!hasSiblings || !isJsonObject(inlined)) return inlined;
58
- const merged: JsonObject = { ...inlined, ...node };
59
- delete merged.$ref;
60
- return merged;
174
+ state.unresolvedRef = true;
61
175
  }
62
176
 
63
177
  const result: JsonObject = {};
64
- for (const key in node) {
65
- const value = node[key];
66
- // Skip $defs/definitions — they get inlined into consumers
67
- if (key === "$defs" || key === "definitions") continue;
68
-
69
- if (Array.isArray(value)) {
70
- result[key] = value.map(item => dereferenceNode(item, root, visiting));
71
- } else if (isJsonObject(value)) {
72
- result[key] = dereferenceNode(value, root, visiting);
73
- } else {
74
- result[key] = value;
178
+ for (const key in schemaNode) {
179
+ if (!Object.hasOwn(schemaNode, key)) continue;
180
+ const value = schemaNode[key];
181
+ // Literal instance data is not schema-shaped, even when it contains an
182
+ // object that happens to use schema-looking keys or a local `$ref`.
183
+ if (!inSchemaMap && JSON_SCHEMA_LITERAL_PAYLOAD_KEYS.has(key)) {
184
+ setOwnKey(result, key, value);
185
+ continue;
186
+ }
187
+ if (!inSchemaMap && JSON_SCHEMA_MAP_KEYS.has(key) && isJsonObject(value)) {
188
+ setOwnKey(result, key, dereferenceSchemaMap(value, root, visitingRefs, visitingNodes, state));
189
+ continue;
75
190
  }
191
+ setOwnKey(result, key, dereferenceNode(value, root, visitingRefs, visitingNodes, state));
192
+ }
193
+ if (!state.unresolvedRef) {
194
+ delete result.$defs;
195
+ delete result.definitions;
76
196
  }
197
+ visitingNodes.delete(node);
77
198
  return result;
78
199
  }
79
200
 
@@ -90,9 +211,8 @@ function dereferenceNode(node: unknown, root: JsonObject, visiting: Set<string>)
90
211
  export function dereferenceJsonSchema(schema: unknown): unknown {
91
212
  if (!isJsonObject(schema)) return schema;
92
213
 
93
- // Fast path: nothing to dereference
94
- const hasDefs = schema.$defs !== undefined || schema.definitions !== undefined;
95
- if (!hasDefs) return schema;
214
+ // Fast path: nothing to dereference anywhere in the schema graph
215
+ if (!hasDefinitionMapDeep(schema, new Set())) return schema;
96
216
 
97
- return dereferenceNode(schema, schema, new Set());
217
+ return dereferenceNode(schema, schema, new Set(), new Set(), { unresolvedRef: false });
98
218
  }
@@ -17,7 +17,7 @@ const DRAFT_07_SCHEMA_URIS: Record<string, true> = {
17
17
  * entry rather than the map object itself so legacy `definitions`-style refs
18
18
  * inside property schemas get rewritten.
19
19
  */
20
- const SCHEMA_MAP_KEYS: Record<string, true> = { properties: true, patternProperties: true, dependentSchemas: true };
20
+ const SCHEMA_MAP_KEYS = new Set(["properties", "patternProperties", "dependentSchemas"]);
21
21
  /**
22
22
  * Keys whose values are JSON-Schema *values*, not nested schemas. The upgrade
23
23
  * walker must NOT descend into these — `type: ["string","null"]` is not a
@@ -34,6 +34,14 @@ const NON_SCHEMA_VALUE_KEYS: Record<string, true> = {
34
34
  type: true,
35
35
  };
36
36
 
37
+ function setOwnKey(target: JsonObject, key: string, value: unknown): void {
38
+ if (key === "__proto__") {
39
+ Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
40
+ return;
41
+ }
42
+ target[key] = value;
43
+ }
44
+
37
45
  /** Rewrite draft-07's `#/definitions/Foo` ref form to draft 2020-12's `#/$defs/Foo`. External refs (`http://…`) pass through. */
38
46
  function convertRef(value: string): string {
39
47
  return value.startsWith("#/definitions/") ? `#/$defs/${value.slice("#/definitions/".length)}` : value;
@@ -41,10 +49,10 @@ function convertRef(value: string): string {
41
49
 
42
50
  /** Get-or-create a child object map on `target[key]`. Used to lazily build up `$defs`/`dependentRequired`/`dependentSchemas` during conversion. */
43
51
  function getObjectMap(target: JsonObject, key: string): JsonObject {
44
- const existing = target[key];
52
+ const existing = Object.hasOwn(target, key) ? target[key] : undefined;
45
53
  if (isJsonObject(existing)) return existing;
46
54
  const next: JsonObject = {};
47
- target[key] = next;
55
+ setOwnKey(target, key, next);
48
56
  return next;
49
57
  }
50
58
 
@@ -52,13 +60,14 @@ function getObjectMap(target: JsonObject, key: string): JsonObject {
52
60
  function mergeSchemaMap(target: JsonObject, key: string, value: JsonObject, cache: WeakMap<object, unknown>): void {
53
61
  const map = getObjectMap(target, key);
54
62
  for (const name in value) {
55
- map[name] = upgradeJsonSchemaTo202012Impl(value[name], cache);
63
+ if (!Object.hasOwn(value, name)) continue;
64
+ setOwnKey(map, name, upgradeJsonSchemaTo202012Impl(value[name], cache));
56
65
  }
57
66
  }
58
67
  /** Copy a schema-map field with upgrade; non-object values are passed through verbatim. */
59
68
  function copySchemaMap(target: JsonObject, key: string, value: unknown, cache: WeakMap<object, unknown>): void {
60
69
  if (!isJsonObject(value)) {
61
- target[key] = value;
70
+ setOwnKey(target, key, value);
62
71
  return;
63
72
  }
64
73
  mergeSchemaMap(target, key, value, cache);
@@ -107,20 +116,21 @@ function mergePrefixItems(existing: unknown, convertedItems: unknown[]): unknown
107
116
  /** Record `key → deps` in `dependentRequired`, unioning with any existing array. */
108
117
  function mergeDependentRequired(target: JsonObject, key: string, deps: unknown[]): void {
109
118
  const dependentRequired = getObjectMap(target, "dependentRequired");
110
- const existing = dependentRequired[key];
119
+ const existing = Object.hasOwn(dependentRequired, key) ? dependentRequired[key] : undefined;
111
120
  if (existing === undefined) {
112
- dependentRequired[key] = deps;
121
+ setOwnKey(dependentRequired, key, deps);
113
122
  return;
114
123
  }
115
124
  if (Array.isArray(existing)) {
116
- dependentRequired[key] = mergeArrayValues(existing, deps);
125
+ setOwnKey(dependentRequired, key, mergeArrayValues(existing, deps));
117
126
  }
118
127
  }
119
128
 
120
129
  /** Record `key → schema` in `dependentSchemas`, intersecting with any existing entry. */
121
130
  function mergeDependentSchema(target: JsonObject, key: string, schema: unknown): void {
122
131
  const dependentSchemas = getObjectMap(target, "dependentSchemas");
123
- dependentSchemas[key] = combineSchemas(dependentSchemas[key], schema);
132
+ const existing = Object.hasOwn(dependentSchemas, key) ? dependentSchemas[key] : undefined;
133
+ setOwnKey(dependentSchemas, key, combineSchemas(existing, schema));
124
134
  }
125
135
 
126
136
  /**
@@ -130,9 +140,10 @@ function mergeDependentSchema(target: JsonObject, key: string, schema: unknown):
130
140
  * - schema value → `dependentSchemas`
131
141
  */
132
142
  function convertDependencies(source: JsonObject, target: JsonObject, cache: WeakMap<object, unknown>): void {
133
- const dependencies = source.dependencies;
143
+ const dependencies = Object.hasOwn(source, "dependencies") ? source.dependencies : undefined;
134
144
  if (!isJsonObject(dependencies)) return;
135
145
  for (const key in dependencies) {
146
+ if (!Object.hasOwn(dependencies, key)) continue;
136
147
  const dependency = dependencies[key];
137
148
  const converted = upgradeJsonSchemaTo202012Impl(dependency, cache);
138
149
  if (Array.isArray(converted)) {
@@ -150,7 +161,9 @@ function hasNullType(type: unknown): boolean {
150
161
 
151
162
  /** True if any variant in `anyOf` declares (only) a null type. Used to avoid double-adding `{type:"null"}`. */
152
163
  function hasNullVariant(variants: unknown[]): boolean {
153
- return variants.some(variant => isJsonObject(variant) && hasNullType(variant.type));
164
+ return variants.some(
165
+ variant => isJsonObject(variant) && Object.hasOwn(variant, "type") && hasNullType(variant.type),
166
+ );
154
167
  }
155
168
 
156
169
  /**
@@ -182,6 +195,7 @@ function makeNullable(schema: JsonObject): JsonObject {
182
195
  function schemaMapNeedsDraft202012Upgrade(value: unknown, epoch: number): boolean {
183
196
  if (!isJsonObject(value)) return false;
184
197
  for (const k in value) {
198
+ if (!Object.hasOwn(value, k)) continue;
185
199
  if (schemaNeedsDraft202012UpgradeImpl(value[k], epoch)) return true;
186
200
  }
187
201
  return false;
@@ -203,9 +217,10 @@ function schemaNeedsDraft202012UpgradeImpl(value: unknown, epoch: number): boole
203
217
  if (!once(value, epoch)) return false;
204
218
 
205
219
  for (const key in value) {
220
+ if (!Object.hasOwn(value, key)) continue;
206
221
  const entry = value[key];
207
222
  if (key === "$schema") {
208
- if (typeof entry === "string" && entry in DRAFT_07_SCHEMA_URIS) return true;
223
+ if (typeof entry === "string" && Object.hasOwn(DRAFT_07_SCHEMA_URIS, entry)) return true;
209
224
  continue;
210
225
  }
211
226
  if (key === "definitions" || key === "dependencies" || key === "additionalItems" || key === "nullable") {
@@ -216,11 +231,11 @@ function schemaNeedsDraft202012UpgradeImpl(value: unknown, epoch: number): boole
216
231
  continue;
217
232
  }
218
233
  if (key === "items" && Array.isArray(entry)) return true;
219
- if (key === "$defs" || key in SCHEMA_MAP_KEYS) {
234
+ if (key === "$defs" || SCHEMA_MAP_KEYS.has(key)) {
220
235
  if (schemaMapNeedsDraft202012Upgrade(entry, epoch)) return true;
221
236
  continue;
222
237
  }
223
- if (key in NON_SCHEMA_VALUE_KEYS) continue;
238
+ if (Object.hasOwn(NON_SCHEMA_VALUE_KEYS, key)) continue;
224
239
  if (schemaNeedsDraft202012UpgradeImpl(entry, epoch)) return true;
225
240
  }
226
241
 
@@ -253,6 +268,7 @@ function upgradeJsonSchemaTo202012Impl(value: unknown, cache: WeakMap<object, un
253
268
  // Seed cache before recursion so back-edges in cyclic graphs resolve.
254
269
  cache.set(value, result);
255
270
  for (const key in value) {
271
+ if (!Object.hasOwn(value, key)) continue;
256
272
  const entry = value[key];
257
273
  // `definitions` is the draft-07 name; merge under the canonical `$defs`.
258
274
  // `$defs` may appear pre-upgraded — still walk entries to upgrade their bodies.
@@ -261,13 +277,13 @@ function upgradeJsonSchemaTo202012Impl(value: unknown, cache: WeakMap<object, un
261
277
  continue;
262
278
  }
263
279
  // Recurse into each entry; the map shape itself is preserved.
264
- if (key in SCHEMA_MAP_KEYS) {
280
+ if (SCHEMA_MAP_KEYS.has(key)) {
265
281
  copySchemaMap(result, key, entry, cache);
266
282
  continue;
267
283
  }
268
284
  // JSON-Schema *value* keywords — copy verbatim.
269
- if (key in NON_SCHEMA_VALUE_KEYS) {
270
- result[key] = entry;
285
+ if (Object.hasOwn(NON_SCHEMA_VALUE_KEYS, key)) {
286
+ setOwnKey(result, key, entry);
271
287
  continue;
272
288
  }
273
289
  // Draft-07-only keywords with no draft 2020-12 spelling — drop entirely.
@@ -278,7 +294,9 @@ function upgradeJsonSchemaTo202012Impl(value: unknown, cache: WeakMap<object, un
278
294
  // Rewrite `$schema` URI to the 2020-12 form; non-draft-07 URIs pass through.
279
295
  if (key === "$schema") {
280
296
  result.$schema =
281
- typeof entry === "string" && entry in DRAFT_07_SCHEMA_URIS ? JSON_SCHEMA_DRAFT_2020_12_URI : entry;
297
+ typeof entry === "string" && Object.hasOwn(DRAFT_07_SCHEMA_URIS, entry)
298
+ ? JSON_SCHEMA_DRAFT_2020_12_URI
299
+ : entry;
282
300
  continue;
283
301
  }
284
302
  // `#/definitions/Foo` → `#/$defs/Foo`.
@@ -290,15 +308,20 @@ function upgradeJsonSchemaTo202012Impl(value: unknown, cache: WeakMap<object, un
290
308
  if (key === "items" && Array.isArray(entry)) {
291
309
  continue;
292
310
  }
293
- result[key] = upgradeJsonSchemaTo202012Impl(entry, cache);
311
+ setOwnKey(result, key, upgradeJsonSchemaTo202012Impl(entry, cache));
294
312
  }
295
313
 
296
314
  // Draft-07 tuple form: `items: [a, b]` (+ optional `additionalItems`) becomes
297
315
  // draft 2020-12 `prefixItems: [a, b]` (+ optional `items` for the rest).
298
- if (Array.isArray(value.items)) {
299
- const convertedItems = upgradeJsonSchemaTo202012Impl(value.items, cache) as unknown[];
316
+ const tupleItems = Object.hasOwn(value, "items") && Array.isArray(value.items) ? value.items : undefined;
317
+ if (tupleItems !== undefined) {
318
+ const convertedItems = upgradeJsonSchemaTo202012Impl(tupleItems, cache) as unknown[];
300
319
  result.prefixItems = mergePrefixItems(result.prefixItems, convertedItems);
301
- if (value.additionalItems !== undefined && value.additionalItems !== true) {
320
+ if (
321
+ Object.hasOwn(value, "additionalItems") &&
322
+ value.additionalItems !== undefined &&
323
+ value.additionalItems !== true
324
+ ) {
302
325
  result.items = upgradeJsonSchemaTo202012Impl(value.additionalItems, cache);
303
326
  } else {
304
327
  // `additionalItems: true` (or absent) in draft-07 == no `items` in 2020-12.
@@ -311,7 +334,7 @@ function upgradeJsonSchemaTo202012Impl(value: unknown, cache: WeakMap<object, un
311
334
  // OpenAPI 3.0 `nullable: true` → 2020-12 nullability. `makeNullable` may
312
335
  // return a fresh wrapper object, in which case update the cache so callers
313
336
  // referring to the same input see the wrapper instead of the inner result.
314
- if (value.nullable === true) {
337
+ if (Object.hasOwn(value, "nullable") && value.nullable === true) {
315
338
  const nullable = makeNullable(result);
316
339
  if (nullable !== result) cache.set(value, nullable);
317
340
  return nullable;
@@ -25,11 +25,6 @@ export interface NormalizeSchemaOptions {
25
25
  }
26
26
  /** Copy all keys from a schema except the specified combiner key. */
27
27
  export declare function copySchemaWithout(schema: JsonObject, combiner: string): JsonObject;
28
- /**
29
- * Recursively strip any remaining anyOf/oneOf that same-type or mixed-type
30
- * collapse can handle. This is needed because object-combiner merging can
31
- * create new anyOf in merged subtrees after child normalization already ran.
32
- */
33
28
  export declare function stripResidualCombiners(value: unknown, epoch?: number): unknown;
34
29
  export declare function normalizeSchema(value: unknown, options: NormalizeSchemaOptions): unknown;
35
30
  export declare function normalizeSchemaForGoogle(value: unknown): unknown;