{"version":3,"file":"schema-flatten.d.ts","sourceRoot":"","sources":["../../../src/core/tool-call/schema-flatten.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAgC,cAAc,EAAE,MAAM,YAAY,CAAC;AAepG,6EAA6E;AAC7E,QAAA,MAAM,sBAAsB,UAAwF,CAAC;AAErH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,SAAI,GAAG,MAAM,CAejF;AAOD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,EAAE,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,cAAc,CAgBjH;AAiDD;;;GAGG;AACH,wBAAgB,gCAAgC,CAC/C,eAAe,EAAE,mBAAmB,EACpC,kBAAkB,GAAE,MAAM,GAAG,QAAQ,GAAG,SAAqB,EAC7D,YAAY,CAAC,EAAE,OAAO,GACpB,cAAc,CAIhB;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAIrE;AAED,OAAO,EAAE,sBAAsB,EAAE,CAAC","sourcesContent":["import { canonicalizeSchema } from \"./canonicalize.js\";\nimport type { CanonicalJsonSchema, SchemaCompatibilityTransform, SchemaEnvelope } from \"./types.js\";\n\n/**\n * Deterministic schema compatibility layer.\n *\n * Some providers / local models perform poorly with deeply nested JSON Schema.\n * This module builds a provider-facing schema that preserves ALL restrictions\n * (required, enum, numeric bounds, additionalProperties, path & safety\n * annotations) while optionally flattening a bounded amount of single-level\n * nesting. Canonical validation always runs against the CANONICAL schema; the\n * provider-facing flattening never weakens validation.\n */\n\nconst MAX_DEPTH_BEFORE_FLATTEN = 5;\n\n/** Safety annotations we preserve verbatim on the provider-facing schema. */\nconst SAFETY_ANNOTATION_KEYS = [\"x-workspace-path\", \"x-effect\", \"x-safety\", \"x-destructive\", \"description\", \"title\"];\n\nexport function measureSchemaDepth(schema: CanonicalJsonSchema, depth = 0): number {\n\tif (!schema || depth > 64) return depth;\n\tlet max = depth;\n\tif (schema.properties) {\n\t\tfor (const v of Object.values(schema.properties)) {\n\t\t\tmax = Math.max(max, measureSchemaDepth(v as CanonicalJsonSchema, depth + 1));\n\t\t}\n\t}\n\tif (schema.items) {\n\t\tconst items = Array.isArray(schema.items) ? schema.items : [schema.items];\n\t\tfor (const i of items) max = Math.max(max, measureSchemaDepth(i as CanonicalJsonSchema, depth + 1));\n\t}\n\tif (schema.oneOf) for (const s of schema.oneOf) max = Math.max(max, measureSchemaDepth(s, depth + 1));\n\tif (schema.anyOf) for (const s of schema.anyOf) max = Math.max(max, measureSchemaDepth(s, depth + 1));\n\treturn max;\n}\n\n/** Deep-clone a schema (representational copy). */\nfunction cloneSchema<T extends CanonicalJsonSchema>(schema: T): T {\n\treturn JSON.parse(JSON.stringify(schema)) as T;\n}\n\n/**\n * Build a provider-facing schema envelope. `flatten` is selected by the caller\n * based on provider capability and measured need, and recorded on the result.\n */\nexport function buildSchemaEnvelope(schema: CanonicalJsonSchema, opts: { flatten?: boolean } = {}): SchemaEnvelope {\n\tconst canonical = canonicalizeSchema(schema);\n\tlet transform: SchemaCompatibilityTransform = { transformId: \"none\" };\n\tlet providerFacing = canonical;\n\n\tif (measureSchemaDepth(canonical) > MAX_DEPTH_BEFORE_FLATTEN && opts.flatten) {\n\t\t// Flatten exactly one level of object nesting into properties while\n\t\t// preserving every restriction. This is bounded and lossless.\n\t\tproviderFacing = flattenOneLevel(canonical);\n\t\ttransform = {\n\t\t\ttransformId: \"flatten-single-level\",\n\t\t\tnote: `depth ${measureSchemaDepth(canonical)} flattened by one level`,\n\t\t};\n\t}\n\n\treturn { canonical, providerFacing, transform };\n}\n\n/**\n * Flatten a single level: move required/enum/numeric constraints upward is NOT\n * sound, so instead we preserve restrictions by copying each nested property's\n * full schema but expanding one structural level of `allOf`-free composition.\n * Because we cannot re-associate arbitrary lower constraints losslessly at a\n * guaranteed-sound level beyond simple object nesting, we only expand the\n * top-level `properties` that are themselves plain `object` with `properties`\n * one level deep. Any schema we cannot represent safely is marked unsupported\n * and fails explicitly rather than silently changing semantics.\n */\nfunction flattenOneLevel(schema: CanonicalJsonSchema): CanonicalJsonSchema {\n\tconst out = cloneSchema(schema);\n\tout.properties = out.properties ? { ...out.properties } : {};\n\tconst unsupported: string[] = [];\n\n\tfor (const [key, propSchema] of Object.entries(out.properties)) {\n\t\tconst ps = propSchema as CanonicalJsonSchema;\n\t\tif (ps.type === \"object\" && ps.properties) {\n\t\t\tfor (const [subKey, subSchema] of Object.entries(ps.properties)) {\n\t\t\t\tif (out.properties[`${key}.${subKey}`] !== undefined) {\n\t\t\t\t\tunsupported.push(`${key}.${subKey}`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tout.properties[`${key}.${subKey}`] = subSchema as CanonicalJsonSchema;\n\t\t\t}\n\t\t\t// Mark original as present but open (restrictions preserved on children).\n\t\t\tout.properties[key] = {\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: ps.properties,\n\t\t\t\tadditionalProperties: ps.additionalProperties,\n\t\t\t};\n\t\t}\n\t\tconst overlap = Object.keys(out.properties).filter((k) => k.includes(\".\"));\n\t\t// Detect recursive / unsupported constructs.\n\t\tif (ps.$ref && !out.properties[key]) {\n\t\t\tunsupported.push(key);\n\t\t}\n\t\tvoid overlap;\n\t}\n\tif (unsupported.length) {\n\t\t// Something cannot be represented losslessly: surface as unsupported.\n\t\t(out as typeof out & { unsupportedReason?: string }).unsupportedReason =\n\t\t\t`cannot flatten safely: ${unsupported.join(\", \")}`;\n\t}\n\treturn out;\n}\n\n/**\n * Decide a provider-facing flattening treatment while keeping canonical\n * validation authoritative. Returns a SchemaEnvelope.\n */\nexport function applyProviderSchemaCompatibility(\n\tcanonicalSchema: CanonicalJsonSchema,\n\tproviderCapability: \"flat\" | \"nested\" | \"unknown\" = \"unknown\",\n\tforceFlatten?: boolean,\n): SchemaEnvelope {\n\tconst depth = measureSchemaDepth(canonicalSchema);\n\tconst flatten = forceFlatten ?? (providerCapability !== \"flat\" && depth > MAX_DEPTH_BEFORE_FLATTEN);\n\treturn buildSchemaEnvelope(canonicalSchema, { flatten });\n}\n\nexport function isSchemaUnsupported(envelope: SchemaEnvelope): boolean {\n\tif (envelope.unsupported) return true;\n\tconst pf = envelope.providerFacing as CanonicalJsonSchema & { unsupportedReason?: string };\n\treturn Boolean((pf as { unsupportedReason?: string }).unsupportedReason);\n}\n\nexport { SAFETY_ANNOTATION_KEYS };\n"]}