perf(mcp): reuse input schemas without losing caller guidance (#1162)

This commit is contained in:
Ysopwnable authored and GitHub committed 2026-10-09 11:38:07 +08:00
1 parent 4fb2f3e6ed
commit e1afbc1914
6 files changed
+599 -158

No files matched your search

+5 -3
View File
@@ -31,9 +31,11 @@ check the complete catalog after SDK conversion and its generated counterpart.
This is REA's local compatibility profile; individual model APIs can impose
additional limits.
Output schemas share repeated definitions through local references while
preserving their complete fields and validation rules. Canonical Zod validation
and the input compatibility profile continue to apply.
Input and output schemas share repeated definitions through schema-local
references while preserving their complete fields and validation rules. Input
properties retain their descriptions and literal examples. Root input unions
keep their inline object presentation to avoid redundant branch nesting.
Canonical Zod validation and the input compatibility profile continue to apply.
For passive `compare_web_captures` inputs, pass each complete
`inspect_web_page` result in `before.inspection` or `after.inspection`, with an
+163
View File
@@ -0,0 +1,163 @@
const schemaMaps = new Set([
"$defs",
"definitions",
"properties",
"patternProperties",
"dependentSchemas",
"dependencies",
]);
const schemaArrays = new Set(["allOf", "anyOf", "oneOf", "prefixItems"]);
const schemaChildren = new Set([
"items",
"additionalItems",
"contains",
"additionalProperties",
"unevaluatedProperties",
"unevaluatedItems",
"propertyNames",
"not",
"if",
"then",
"else",
]);
const isObject = (value: unknown): value is Readonly<Record<string, unknown>> =>
typeof value === "object" && value !== null && !Array.isArray(value);
const resolveReference = (
root: Readonly<Record<string, unknown>>,
reference: string,
): unknown => {
if (!reference.startsWith("#/")) return undefined;
let value: unknown = root;
for (const token of reference.slice(2).split("/")) {
const key = token.replaceAll("~1", "/").replaceAll("~0", "~");
if (!isObject(value) || !Object.hasOwn(value, key)) return undefined;
value = value[key];
}
return value;
};
const referencedDescription = (
root: Readonly<Record<string, unknown>>,
schema: Readonly<Record<string, unknown>>,
): string | undefined => {
let value: unknown = schema;
const active = new Set<string>();
while (isObject(value)) {
if (typeof value.description === "string") return value.description;
if (typeof value.$ref !== "string" || active.has(value.$ref))
return undefined;
active.add(value.$ref);
value = resolveReference(root, value.$ref);
}
return undefined;
};
const describeSchema = (
schema: unknown,
root: Readonly<Record<string, unknown>>,
describeProperty: (property: string) => string,
): unknown => {
if (!isObject(schema)) return schema;
return Object.fromEntries(
Object.entries(schema).map(([key, value]) => {
if (schemaMaps.has(key) && isObject(value))
return [
key,
Object.fromEntries(
Object.entries(value).map(([name, child]) => {
const described = describeSchema(child, root, describeProperty);
return [
name,
key === "properties" && isObject(described)
? {
...described,
description:
referencedDescription(root, described) ??
describeProperty(name),
}
: described,
];
}),
),
];
if (schemaArrays.has(key) && Array.isArray(value))
return [
key,
value.map((child) => describeSchema(child, root, describeProperty)),
];
if (schemaChildren.has(key))
return [
key,
Array.isArray(value)
? value.map((child) =>
describeSchema(child, root, describeProperty),
)
: describeSchema(value, root, describeProperty),
];
// Annotation values and literal const/enum data are not schemas.
return [key, value];
}),
);
};
const flattenRootUnion = (
value: Record<string, unknown>,
describeProperty: (property: string) => string,
): Record<string, unknown> => {
if (!Array.isArray(value.anyOf)) return value;
const branches = value.anyOf;
const objects = branches.filter(isObject);
if (objects.length === 0 || objects.length !== branches.length) return value;
const properties = new Map<string, unknown>();
for (const branch of objects) {
if (!isObject(branch.properties)) return value;
for (const [name, schema] of Object.entries(branch.properties)) {
const previous = properties.get(name);
if (!properties.has(name)) properties.set(name, schema);
else if (JSON.stringify(previous) !== JSON.stringify(schema))
properties.set(name, {
anyOf: [previous, schema],
description: describeProperty(name),
});
}
}
// Invocation still uses the exact canonical union, including branch rules.
const { anyOf: _anyOf, required: _required, ...root } = value;
const requiredByBranch = objects.map((branch) =>
Array.isArray(branch.required)
? branch.required.filter(
(item): item is string => typeof item === "string",
)
: [],
);
const required = requiredByBranch.reduce((common, current) =>
common.filter((name) => current.includes(name)),
);
const minProperties = Math.max(
typeof root.minProperties === "number" ? root.minProperties : 0,
Math.min(...requiredByBranch.map((names) => names.length)),
);
return {
...root,
type: "object",
properties: Object.fromEntries(properties),
...(minProperties > 0 ? { minProperties } : {}),
...(required.length > 0 ? { required } : {}),
...(objects.every((branch) => branch.additionalProperties === false)
? { additionalProperties: false }
: {}),
};
};
/** Preserve property guidance and literal data while advertising object roots. */
export const presentInputJsonSchema = (
root: Record<string, unknown>,
describeProperty: (property: string) => string,
): Record<string, unknown> => {
const described = describeSchema(root, root, describeProperty);
return isObject(described)
? flattenRootUnion(described, describeProperty)
: root;
};
+247
View File
@@ -0,0 +1,247 @@
import { Ajv } from "ajv";
import { Ajv2020 } from "ajv/dist/2020.js";
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { inlineLocalJsonSchemaReferences } from "../../tests/fixtures/localJsonSchemaReferences.js";
import { presentInputJsonSchema } from "./inputSchemaPresentation.js";
import { TOOL_CONTRACTS, toolContract } from "./toolContracts.js";
import { toolInputSchemaWithMetadata } from "./toolSchemaMetadata.js";
describe("advertised input references", () => {
it.each(["draft-2020-12", "draft-07"] as const)(
"preserves production native-selector constraints in %s",
(target) => {
const contract = toolContract("observe_native_calls");
const advertised = toolInputSchemaWithMetadata(contract)[
"~standard"
].jsonSchema.input({ target });
const ajv =
target === "draft-2020-12"
? new Ajv2020({ strict: false })
: new Ajv({ strict: false });
const validate = ajv.compile(advertised);
for (const selector of [
"setFoo:",
"dataTaskWithRequest:completionHandler:",
"",
"bad selector:",
"bad[selector]:",
"set\0Foo:",
"setFoo:\n",
])
for (const extra of [{}, { unexpected: true }]) {
const sample = {
breakpoints: [
{
kind: "objc-method",
class_name: "NSURLSession",
selector,
...extra,
},
],
};
expect(validate(sample)).toBe(
contract.inputSchema.safeParse(sample).success,
);
}
},
);
it.each(["draft-2020-12", "draft-07"] as const)(
"preserves complete input meaning and examples through %s references",
(target) => {
for (const contract of TOOL_CONTRACTS) {
const projection =
toolInputSchemaWithMetadata(contract)["~standard"].jsonSchema.input;
const referenced = projection({ target });
const inline = projection({
target,
libraryOptions: { reused: "inline" },
});
expect(referenced.type, contract.name).toBe("object");
expect(
inlineLocalJsonSchemaReferences(referenced),
contract.name,
).toEqual(inlineLocalJsonSchemaReferences(inline));
expect(referenced.examples, contract.name).toEqual(
contract.examples.map(({ input }) => input),
);
expect(projection({ target })).toBe(referenced);
}
},
);
it.each(["draft-2020-12", "draft-07"] as const)(
"retains specialized descriptions, literal metadata and strict validation in %s",
(target) => {
const literal = {
properties: { target: { $ref: "literal caller data" } },
examples: [{ properties: { path: {} } }],
};
const range = z
.object({
offset: z.number().int().nonnegative().max(65535),
length: z.number().int().min(1),
})
.strict()
.describe("Exact byte range in the selected artifact.");
const canonical = z
.object({
before: range,
after: range,
metadata: z
.record(z.string(), z.unknown())
.meta({ default: literal, examples: [literal] }),
constructor: z.string(),
})
.strict();
const advertised = toolInputSchemaWithMetadata({
...toolContract("open_binary"),
inputSchema: canonical,
})["~standard"].jsonSchema.input({ target });
expect(inlineLocalJsonSchemaReferences(advertised)).toMatchObject({
properties: {
before: { description: "Exact byte range in the selected artifact." },
after: { description: "Exact byte range in the selected artifact." },
metadata: { default: literal, examples: [literal] },
constructor: { description: "Value for constructor." },
},
});
const ajv =
target === "draft-2020-12"
? new Ajv2020({ strict: false })
: new Ajv({ strict: false });
const validate = ajv.compile(advertised);
const valid = {
before: { offset: 0, length: 1 },
after: { offset: 65535, length: 2 },
metadata: literal,
constructor: "constructor data",
};
const samples = [
valid,
...[
{ offset: -1 },
{ offset: 65536 },
{ offset: 0.5 },
{ length: 0 },
{ unexpected: true },
].map((change) => ({
...valid,
before: { ...valid.before, ...change },
})),
{ ...valid, after: null },
{ ...valid, unexpected: true },
];
expect(canonical.safeParse(valid).success).toBe(true);
for (const sample of samples)
expect(validate(sample)).toBe(canonical.safeParse(sample).success);
},
);
});
describe("root input presentation", () => {
it("normalizes repeated string intersections without widening validation", () => {
const root = {
type: "string",
minLength: 1,
description: "Exact selector.",
allOf: [
{ type: "string", pattern: "^[^\\s]+$" },
{ allOf: [{ type: "string", pattern: "^[^\\s]+$" }], minLength: 2 },
],
};
const normalized = inlineLocalJsonSchemaReferences(root);
expect(normalized).toEqual({
type: "string",
minLength: 2,
pattern: "^[^\\s]+$",
description: "Exact selector.",
});
const ajv = new Ajv2020({ strict: false });
const original = ajv.compile(root);
const projected = ajv.compile(
z.record(z.string(), z.unknown()).parse(normalized),
);
for (const sample of ["", "a", "aa", "a a", "a\n", null, {}])
expect(projected(sample)).toBe(original(sample));
});
it("preserves prototype-named properties in object-root presentation", () => {
const fields = Object.fromEntries([
["__proto__", { type: "string" }],
["constructor", { type: "number" }],
]);
const branch = {
type: "object",
properties: fields,
required: ["__proto__"],
additionalProperties: false,
};
const presented = presentInputJsonSchema(
{ anyOf: [branch, branch] },
(property) => `Parameter ${property}`,
);
expect(presented).toMatchObject({
type: "object",
required: ["__proto__"],
minProperties: 1,
additionalProperties: false,
properties: fields,
});
expect(presented.anyOf).toBeUndefined();
expect(Object.getPrototypeOf(presented.properties)).toBe(Object.prototype);
expect(Object.hasOwn(presented.properties ?? {}, "__proto__")).toBe(true);
});
it("retains reference siblings and does not manufacture root branch constraints", () => {
const branch = { $ref: "#/$defs/object", additionalProperties: false };
const root = {
$defs: {
object: { type: "object", properties: { value: { type: "string" } } },
},
anyOf: [branch],
};
const presented = presentInputJsonSchema(root, (property) => property);
expect(presented.anyOf).toEqual([branch]);
const ajv = new Ajv2020({ strict: false });
for (const sample of [{}, { value: "value" }, null])
expect(ajv.compile(presented)(sample)).toBe(ajv.compile(root)(sample));
});
it("preserves specialized guidance across reference chains without following literal references", () => {
const literal = { properties: { literal: {} }, $ref: "#/$defs/missing" };
const presented = presentInputJsonSchema(
{
$defs: {
"a~b": {
type: "string",
description: "Literal URL selected by the caller.",
},
alias: { $ref: "#/$defs/a~0b" },
},
type: "object",
properties: {
url: { $ref: "#/$defs/alias" },
other: { $ref: "#/$defs/alias", description: "Explicit override." },
},
default: literal,
const: literal,
examples: [literal],
enum: [literal],
},
(property) => `Parameter ${property}`,
);
expect(presented).toMatchObject({
properties: {
url: { description: "Literal URL selected by the caller." },
other: { description: "Explicit override." },
},
default: literal,
const: literal,
examples: [literal],
enum: [literal],
});
});
});
+22 -89
View File
@@ -1,6 +1,7 @@
import { z } from "zod";
import type { ToolContract } from "./toolContractTypes.js";
import { presentInputJsonSchema } from "./inputSchemaPresentation.js";
const PROPERTY_DESCRIPTIONS: Readonly<Record<string, string>> = {
addresses: "Ordered provider-normalized procedure addresses to analyze.",
@@ -56,11 +57,25 @@ export const toolInputSchemaWithMetadata = <Contract extends ToolContract>(
withAdvertisedJsonSchema(
contract.inputSchema,
"input",
(project) => (options) =>
flattenRootUnion({
...describeProperties(project(options)),
(project) => (options) => {
const shared = project({
...options,
libraryOptions: { reused: "ref", ...options.libraryOptions },
});
// Root-union flattening compares branch properties before widening them.
// Keep its inline representation: distinct references can name equal
// schemas and otherwise introduce redundant, deeper anyOf properties.
const compatible = Array.isArray(shared.anyOf)
? project({
...options,
libraryOptions: { ...options.libraryOptions, reused: "inline" },
})
: shared;
return {
...presentInputJsonSchema(compatible, fallbackPropertyDescription),
examples: contract.examples.map(({ input }) => input),
}),
};
},
);
/** Share repeated output definitions without changing canonical validation. */
@@ -122,89 +137,10 @@ const memoizeByTarget = (
};
};
/** Keep MCP tool roots object-shaped for clients that reject root unions. */
const flattenRootUnion = (
value: Record<string, unknown>,
): Record<string, unknown> => {
if (!Array.isArray(value.anyOf)) return value;
const branches = value.anyOf.filter(isObject);
if (branches.length === 0 || branches.length !== value.anyOf.length)
return value;
const properties: Record<string, unknown> = {};
for (const branch of branches) {
if (!isObject(branch.properties)) return value;
for (const [name, schema] of Object.entries(branch.properties)) {
const previous = properties[name];
if (previous === undefined) properties[name] = schema;
else if (JSON.stringify(previous) !== JSON.stringify(schema))
properties[name] = {
anyOf: [previous, schema],
description: fallbackPropertyDescription(name),
};
}
}
// The canonical Zod parser still enforces branch validation at invocation.
const { anyOf: _anyOf, required: _required, ...root } = value;
const requiredByBranch = branches.map((branch) =>
Array.isArray(branch.required)
? branch.required.filter(
(item): item is string => typeof item === "string",
)
: [],
);
const required = requiredByBranch.reduce((common, current) =>
common.filter((name) => current.includes(name)),
);
const minProperties = Math.max(
typeof root.minProperties === "number" ? root.minProperties : 0,
Math.min(...requiredByBranch.map((names) => names.length)),
);
return {
...root,
type: "object",
properties,
...(minProperties > 0 ? { minProperties } : {}),
...(required.length > 0 ? { required } : {}),
...(branches.every((branch) => branch.additionalProperties === false)
? { additionalProperties: false }
: {}),
};
};
const describeProperties = (
value: Readonly<Record<string, unknown>>,
): Record<string, unknown> =>
Object.fromEntries(
Object.entries(value).map(([key, child]) => {
if (key !== "properties" || !isObject(child))
return [key, describeValue(child)];
return [
key,
Object.fromEntries(
Object.entries(child).map(([property, propertySchema]) => [
property,
isObject(propertySchema) &&
typeof propertySchema.description !== "string"
? {
...describeProperties(propertySchema),
description: fallbackPropertyDescription(property),
}
: describeValue(propertySchema),
]),
),
];
}),
);
const describeValue = (value: unknown): unknown => {
if (Array.isArray(value)) return value.map(describeValue);
return isObject(value) ? describeProperties(value) : value;
};
const fallbackPropertyDescription = (property: string): string => {
const explicit = PROPERTY_DESCRIPTIONS[property];
const explicit = Object.hasOwn(PROPERTY_DESCRIPTIONS, property)
? PROPERTY_DESCRIPTIONS[property]
: undefined;
if (explicit !== undefined) return explicit;
const words = property.replaceAll("_", " ");
if (property.startsWith("max_"))
@@ -231,6 +167,3 @@ const fallbackPropertyDescription = (property: string): string => {
return `Whether ${words}.`;
return `Value for ${words}.`;
};
const isObject = (value: unknown): value is Readonly<Record<string, unknown>> =>
typeof value === "object" && value !== null && !Array.isArray(value);
+22 -3
View File
@@ -103,25 +103,44 @@ function expectKnownAuthorityHints(tools: readonly ToolSchemas[]): void {
function expectRecursivePropertyDescriptions(
schema: unknown,
path: string,
root: unknown = schema,
active: ReadonlySet<string> = new Set(),
): void {
if (!isRecord(schema)) return;
if (typeof schema.$ref === "string" && !active.has(schema.$ref))
expectRecursivePropertyDescriptions(
resolveReference(root, schema.$ref),
`${path}.${schema.$ref}`,
root,
new Set([...active, schema.$ref]),
);
if (isRecord(schema.properties)) {
for (const [property, child] of Object.entries(schema.properties)) {
expect(child, `${path}.${property}`).toMatchObject({
description: expect.any(String),
});
expectRecursivePropertyDescriptions(child, `${path}.${property}`);
expectRecursivePropertyDescriptions(
child,
`${path}.${property}`,
root,
active,
);
}
}
for (const key of ["items", "additionalProperties"])
if (schema[key] !== undefined)
expectRecursivePropertyDescriptions(schema[key], path);
expectRecursivePropertyDescriptions(schema[key], path, root, active);
for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) {
const children = schema[key];
if (Array.isArray(children))
children.forEach((child: unknown, index: number) =>
expectRecursivePropertyDescriptions(child, `${path}.${key}[${index}]`),
expectRecursivePropertyDescriptions(
child,
`${path}.${key}[${index}]`,
root,
active,
),
);
}
}
+140 -63
View File
@@ -19,6 +19,16 @@ const schemaChildren = new Set([
]);
const lowerBounds = new Set(["minimum", "exclusiveMinimum"]);
const upperBounds = new Set(["maximum", "exclusiveMaximum"]);
const annotations = new Set([
"title",
"description",
"$comment",
"default",
"examples",
"deprecated",
"readOnly",
"writeOnly",
]);
const scopedKeywords = new Set([
"properties",
"patternProperties",
@@ -34,6 +44,102 @@ const scopedKeywords = new Set([
const record = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
const compatibleSibling = (
key: string,
previous: unknown,
value: unknown,
): boolean =>
previous === undefined ||
JSON.stringify(previous) === JSON.stringify(value) ||
annotations.has(key) ||
(typeof previous === "number" &&
typeof value === "number" &&
(lowerBounds.has(key) || upperBounds.has(key)));
const mergeSiblings = (
schema: Record<string, unknown>,
siblings: Record<string, unknown>,
): Record<string, unknown> => {
const merged = { ...schema, ...siblings };
for (const [key, value] of Object.entries(siblings)) {
const previous = schema[key];
if (!compatibleSibling(key, previous, value))
throw new Error(`Conflicting schema reference sibling: ${key}`);
if (typeof previous !== "number" || typeof value !== "number") continue;
if (lowerBounds.has(key)) merged[key] = Math.max(previous, value);
else if (upperBounds.has(key)) merged[key] = Math.min(previous, value);
}
return merged;
};
const normalizeStringIntersection = (
schema: Record<string, unknown>,
): Record<string, unknown> => {
if (
(schema.type !== undefined && schema.type !== "string") ||
!Array.isArray(schema.allOf)
)
return schema;
let requiresString = schema.type === "string";
const validBounds = (value: Record<string, unknown>): boolean =>
["minLength", "maxLength"].every(
(key) =>
!Object.hasOwn(value, key) ||
(typeof value[key] === "number" &&
Number.isInteger(value[key]) &&
value[key] >= 0),
) &&
(!Object.hasOwn(value, "pattern") || typeof value.pattern === "string");
if (!validBounds(schema)) return schema;
const patterns = new Set<string>();
let minimum = typeof schema.minLength === "number" ? schema.minLength : 0;
let maximum =
typeof schema.maxLength === "number" ? schema.maxLength : Infinity;
if (typeof schema.pattern === "string") patterns.add(schema.pattern);
const collect = (child: unknown): boolean => {
if (!record(child) || (child.type !== undefined && child.type !== "string"))
return false;
if (
!validBounds(child) ||
(Object.hasOwn(child, "allOf") && !Array.isArray(child.allOf))
)
return false;
if (child.type === "string") requiresString = true;
if (
Object.keys(child).some(
(key) =>
!["type", "pattern", "minLength", "maxLength", "allOf"].includes(key),
)
)
return false;
if (typeof child.pattern === "string") patterns.add(child.pattern);
if (typeof child.minLength === "number")
minimum = Math.max(minimum, child.minLength);
if (typeof child.maxLength === "number")
maximum = Math.min(maximum, child.maxLength);
return !Array.isArray(child.allOf) || child.allOf.every(collect);
};
if (!schema.allOf.every(collect) || !requiresString) return schema;
const {
allOf: _allOf,
pattern: _pattern,
minLength: _min,
maxLength: _max,
...root
} = schema;
const unique = [...patterns];
return {
...root,
type: "string",
...(minimum > 0 ? { minLength: minimum } : {}),
...(maximum < Infinity ? { maxLength: maximum } : {}),
...(unique.length === 1 ? { pattern: unique[0] } : {}),
...(unique.length > 1
? { allOf: unique.map((pattern) => ({ type: "string", pattern })) }
: {}),
};
};
/** Inline finite local references for representation-independent schema assertions. */
export const inlineLocalJsonSchemaReferences = (
root: Readonly<Record<string, unknown>>,
@@ -68,27 +174,10 @@ export const inlineLocalJsonSchemaReferences = (
visit(siblings, active),
],
};
const merged = { ...referenced, ...siblings };
for (const [key, entry] of Object.entries(siblings)) {
const previous = referenced[key];
if (
previous === undefined ||
JSON.stringify(previous) === JSON.stringify(entry)
)
continue;
if (typeof previous === "number" && typeof entry === "number") {
if (lowerBounds.has(key)) {
merged[key] = Math.max(previous, entry);
continue;
}
if (upperBounds.has(key)) {
merged[key] = Math.min(previous, entry);
continue;
}
}
throw new Error(`Conflicting schema reference sibling: ${key}`);
}
return visit(merged, new Set([...active, reference]));
return visit(
mergeSiblings(referenced, siblings),
new Set([...active, reference]),
);
}
if (Array.isArray(value.allOf) && value.allOf.length === 1) {
const child = visit(value.allOf[0], active);
@@ -96,25 +185,11 @@ export const inlineLocalJsonSchemaReferences = (
if (
record(child) &&
!Object.keys(siblings).some((key) => scopedKeywords.has(key)) &&
Object.entries(siblings).every(
([key, entry]) =>
child[key] === undefined ||
JSON.stringify(child[key]) === JSON.stringify(entry) ||
(typeof child[key] === "number" &&
typeof entry === "number" &&
(lowerBounds.has(key) || upperBounds.has(key))),
Object.entries(siblings).every(([key, entry]) =>
compatibleSibling(key, child[key], entry),
)
) {
const merged = { ...child, ...siblings };
for (const [key, entry] of Object.entries(siblings)) {
const previous = child[key];
if (typeof previous !== "number" || typeof entry !== "number")
continue;
if (lowerBounds.has(key)) merged[key] = Math.max(previous, entry);
else if (upperBounds.has(key))
merged[key] = Math.min(previous, entry);
}
return visit(merged, active);
return visit(mergeSiblings(child, siblings), active);
}
}
const normalized = { ...value };
@@ -139,31 +214,33 @@ export const inlineLocalJsonSchemaReferences = (
normalized.exclusiveMaximum <= normalized.maximum
)
delete normalized.maximum;
return Object.fromEntries(
Object.entries(normalized)
.filter(([key]) => key !== "$defs" && key !== "definitions")
.map(([key, child]) => {
if (schemaMaps.has(key) && record(child))
return [
key,
Object.fromEntries(
Object.entries(child).map(([name, schema]) => [
name,
visit(schema, active),
]),
),
];
if (schemaArrays.has(key) && Array.isArray(child))
return [key, child.map((schema) => visit(schema, active))];
if (schemaChildren.has(key))
return [
key,
Array.isArray(child)
? child.map((schema) => visit(schema, active))
: visit(child, active),
];
return [key, child];
}),
return normalizeStringIntersection(
Object.fromEntries(
Object.entries(normalized)
.filter(([key]) => key !== "$defs" && key !== "definitions")
.map(([key, child]) => {
if (schemaMaps.has(key) && record(child))
return [
key,
Object.fromEntries(
Object.entries(child).map(([name, schema]) => [
name,
visit(schema, active),
]),
),
];
if (schemaArrays.has(key) && Array.isArray(child))
return [key, child.map((schema) => visit(schema, active))];
if (schemaChildren.has(key))
return [
key,
Array.isArray(child)
? child.map((schema) => visit(schema, active))
: visit(child, active),
];
return [key, child];
}),
),
);
};
return visit(root, new Set());