@mochabug/adapt-sdk 0.4.23 → 0.4.24

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/signals/index.ts", "../../src/schema-utils.ts", "../../src/gemini/index.ts"],
4
- "sourcesContent": ["import type {\n JTDSchemaJson,\n SignalDescriptorJson,\n SignalFormatJson\n} from '../api';\nimport { isSchema, isValidSchema, validate as jtdValidate } from 'jtd';\nimport type { Schema, ValidationError } from 'jtd';\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD FUZZER (generate sample values from JTD schemas)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/** Maximum recursion depth for fuzzJTD to prevent infinite loops on self-referencing schemas. */\nconst FUZZ_MAX_DEPTH = 10;\n\n/** Generate a fuzzed (sample) value from a JTD schema. */\nexport function fuzzJTD(\n schema: JTDSchemaJson,\n definitions?: Record<string, JTDSchemaJson>,\n _depth: number = 0\n): unknown {\n if (_depth > FUZZ_MAX_DEPTH) return null;\n\n if (schema.ref) {\n const def = definitions?.[schema.ref];\n if (def) return fuzzJTD(def, definitions, _depth + 1);\n return null;\n }\n\n if (schema.type) {\n switch (schema.type) {\n case 'string':\n return 'Lorem ipsum';\n case 'timestamp':\n return '2024-01-15T10:30:00Z';\n case 'float32':\n case 'float64':\n return 42.5;\n case 'int8':\n case 'uint8':\n case 'int16':\n case 'uint16':\n case 'int32':\n case 'uint32':\n return BigInt(42);\n case 'boolean':\n return true;\n default:\n return null;\n }\n }\n\n if (schema.enum) {\n return schema.enum[0] ?? null;\n }\n\n if (schema.elements) {\n return [\n fuzzJTD(schema.elements, definitions, _depth + 1),\n fuzzJTD(schema.elements, definitions, _depth + 1)\n ];\n }\n\n if (schema.values) {\n return { key1: fuzzJTD(schema.values, definitions, _depth + 1) };\n }\n\n if (schema.properties || schema.optionalProperties) {\n const obj: Record<string, unknown> = {};\n if (schema.properties) {\n for (const [k, v] of Object.entries(schema.properties)) {\n obj[k] = fuzzJTD(v, definitions, _depth + 1);\n }\n }\n if (schema.optionalProperties) {\n for (const [k, v] of Object.entries(schema.optionalProperties)) {\n obj[k] = fuzzJTD(v, definitions, _depth + 1);\n }\n }\n return obj;\n }\n\n if (schema.discriminator && schema.mapping) {\n const firstKey = Object.keys(schema.mapping)[0];\n if (firstKey) {\n const variant = schema.mapping[firstKey]!;\n const obj = fuzzJTD(variant, definitions, _depth + 1) as Record<\n string,\n unknown\n >;\n obj[schema.discriminator] = firstKey;\n return obj;\n }\n return null;\n }\n\n // Empty schema\n return null;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD VALUE VALIDATION (delegates to the `jtd` npm package)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst INTEGER_TYPES = new Set([\n 'int8',\n 'uint8',\n 'int16',\n 'uint16',\n 'int32',\n 'uint32'\n]);\n\nfunction formatInstancePath(instancePath: string[]): string {\n if (instancePath.length === 0) return 'root';\n const joined = instancePath\n .map((p, i) => (/^\\d+$/.test(p) ? `[${p}]` : i === 0 ? p : `.${p}`))\n .join('');\n return `'${joined}'`;\n}\n\nfunction resolveSchemaParent(\n schema: Record<string, unknown>,\n schemaPath: string[]\n): Record<string, unknown> {\n let current: any = schema;\n for (let i = 0; i < schemaPath.length - 1; i++) {\n if (current && typeof current === 'object' && schemaPath[i]! in current) {\n current = current[schemaPath[i]!];\n } else {\n break;\n }\n }\n return current as Record<string, unknown>;\n}\n\nfunction resolveInstanceValue(value: unknown, instancePath: string[]): unknown {\n let current = value;\n for (const key of instancePath) {\n if (current && typeof current === 'object') {\n current = (current as Record<string, unknown>)[key];\n } else {\n return undefined;\n }\n }\n return current;\n}\n\nfunction formatValidationError(\n error: ValidationError,\n schema: Record<string, unknown>,\n value: unknown\n): string {\n const { instancePath, schemaPath } = error;\n const path = formatInstancePath(instancePath);\n const lastKey = schemaPath[schemaPath.length - 1];\n const paramLabel = path === 'root' ? 'root value' : `parameter ${path}`;\n\n if (lastKey === 'type') {\n const parent = resolveSchemaParent(schema, schemaPath);\n const jtdType = parent?.type as string;\n if (jtdType === 'boolean') return `${paramLabel}: expected boolean`;\n if (jtdType === 'string') return `${paramLabel}: expected string`;\n if (jtdType === 'timestamp')\n return `${paramLabel}: expected timestamp string`;\n if (INTEGER_TYPES.has(jtdType)) return `${paramLabel}: expected integer`;\n return `${paramLabel}: expected number`;\n }\n\n if (lastKey === 'enum') {\n const parent = resolveSchemaParent(schema, schemaPath);\n const enumValues = parent?.enum as string[];\n return `${paramLabel}: expected one of [${enumValues.join(', ')}]`;\n }\n\n if (\n schemaPath.length >= 2 &&\n schemaPath[schemaPath.length - 2] === 'properties' &&\n lastKey\n ) {\n if (path === 'root') {\n return `missing required parameter '${lastKey}'`;\n }\n return `missing required parameter ${path}.${lastKey}`;\n }\n\n if (lastKey === 'elements') return `${paramLabel}: expected array`;\n\n if (\n lastKey === 'properties' ||\n lastKey === 'optionalProperties' ||\n lastKey === 'values'\n ) {\n return `${paramLabel}: expected object`;\n }\n\n if (lastKey === 'discriminator') {\n // Find the discriminator field name from the schema\n const parent = resolveSchemaParent(schema, schemaPath);\n const discField = parent?.discriminator as string | undefined;\n if (discField) {\n return `${paramLabel}: parameter '${discField}' must be a string`;\n }\n return `${paramLabel}: expected string discriminator`;\n }\n\n if (lastKey === 'mapping') {\n const discValue = resolveInstanceValue(value, instancePath);\n // Try to list valid variants from the mapping\n const parent = resolveSchemaParent(schema, schemaPath);\n const mapping = parent?.mapping as Record<string, unknown> | undefined;\n const validVariants = mapping ? Object.keys(mapping) : [];\n if (validVariants.length > 0) {\n return `${paramLabel}: unknown variant \"${discValue}\". Valid values: [${validVariants.join(', ')}]`;\n }\n return `${paramLabel}: unknown variant \"${discValue}\"`;\n }\n\n if (schemaPath.length === 0) return `${paramLabel}: unexpected property`;\n\n return `${paramLabel}: validation error at /${schemaPath.join('/')}`;\n}\n\n/**\n * Validate a value against a JTD schema, returning an array of error messages.\n * Delegates to the `jtd` npm package for spec-compliant validation.\n */\nexport function validateJsonAgainstJTD(\n schema: JTDSchemaJson,\n value: unknown,\n definitions?: Record<string, JTDSchemaJson>\n): string[] {\n const defs =\n definitions ??\n (schema.definitions as Record<string, JTDSchemaJson> | undefined);\n\n const schemaForValidation = defs\n ? ({ ...schema, definitions: defs } as Schema)\n : (schema as Schema);\n\n // The jtd lib throws on invalid schemas (e.g., unresolved refs).\n if (!isValidSchema(schemaForValidation)) {\n if (schema.ref) {\n return [`unknown ref \"${schema.ref}\" in schema`];\n }\n return [`invalid schema`];\n }\n\n const errors = jtdValidate(schemaForValidation, value, {\n maxDepth: MAX_VALIDATION_DEPTH,\n maxErrors: MAX_VALIDATION_ERRORS\n });\n\n return errors.map((err) =>\n formatValidationError(\n err,\n schemaForValidation as Record<string, unknown>,\n value\n )\n );\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// SIGNAL-LEVEL VALIDATION (JSON values against JTD formats)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/** Validate a JSON value against a single SignalFormatJson. */\nexport function validateJsonAgainstSignal(\n format: SignalFormatJson,\n jsonValue: unknown\n): string[];\n/** Validate a JSON value against a SignalDescriptorJson (valid if value matches ANY JTD format). */\nexport function validateJsonAgainstSignal(\n descriptor: SignalDescriptorJson,\n jsonValue: unknown\n): string[];\nexport function validateJsonAgainstSignal(\n formatOrDescriptor: SignalFormatJson | SignalDescriptorJson,\n jsonValue: unknown\n): string[] {\n // Descriptor path \u2014 has `formats` array\n if (\n 'formats' in formatOrDescriptor &&\n Array.isArray(formatOrDescriptor.formats)\n ) {\n const jtdFormats = (\n formatOrDescriptor.formats as SignalFormatJson[]\n ).filter((f) => f.jtdSchema);\n if (jtdFormats.length === 0) {\n return ['no JTD formats in signal descriptor'];\n }\n // Valid if value matches ANY JTD format\n for (const format of jtdFormats) {\n const errors = validateJsonAgainstJTD(format.jtdSchema!, jsonValue);\n if (errors.length === 0) return [];\n }\n // None matched \u2014 return errors from last format attempt\n return validateJsonAgainstJTD(\n jtdFormats[jtdFormats.length - 1]!.jtdSchema!,\n jsonValue\n );\n }\n\n // Single format path\n const format = formatOrDescriptor as SignalFormatJson;\n if (!format.jtdSchema) {\n return ['signal format has no jtdSchema'];\n }\n return validateJsonAgainstJTD(format.jtdSchema, jsonValue);\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD \u2192 JSON SCHEMA CONVERTER\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * Convert JTD (JSON Type Definition) schema to JSON Schema.\n *\n * Supports all 8 JTD schema forms per RFC 8927:\n * - Empty form: {}\n * - Ref form: { ref: \"...\" }\n * - Type form: { type: \"...\" }\n * - Enum form: { enum: [...] }\n * - Elements form: { elements: {...} }\n * - Properties form: { properties: {...}, optionalProperties: {...} }\n * - Values form: { values: {...} }\n * - Discriminator form: { discriminator: \"...\", mapping: {...} }\n *\n * JTD `metadata.description` is preserved as JSON Schema `description`.\n * Other metadata fields are appended to the description for context.\n *\n * Top-level `definitions` are emitted as JSON Schema `$defs` and\n * referenced via `$ref` (not inlined), keeping large schemas compact.\n */\nexport function jtdToJsonSchema(\n jtdSchema: JTDSchemaJson,\n definitions?: Record<string, JTDSchemaJson>,\n isRoot = true\n): any {\n if (!jtdSchema || typeof jtdSchema !== 'object') {\n return {};\n }\n\n const isNullable = jtdSchema['nullable'];\n const defs =\n definitions ||\n (jtdSchema['definitions'] as Record<string, JTDSchemaJson> | undefined);\n\n const result: any = {};\n\n if (isRoot) {\n result.$schema = 'http://json-schema.org/draft-07/schema#';\n\n // Emit top-level definitions as $defs (only at root)\n if (defs && Object.keys(defs).length > 0) {\n result.$defs = {};\n for (const [name, defSchema] of Object.entries(defs)) {\n result.$defs[name] = convertNode(defSchema as JTDSchemaJson, defs);\n }\n }\n }\n\n // Build the schema node \u2014 delegate to convertNode for the actual form logic\n const node = convertNode(jtdSchema, defs);\n Object.assign(result, node);\n\n return result;\n}\n\n/** Convert a single JTD node to JSON Schema. Internal recursive worker. */\nfunction convertNode(\n jtdSchema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined\n): any {\n if (!jtdSchema || typeof jtdSchema !== 'object') {\n return {};\n }\n\n const isNullable = jtdSchema['nullable'];\n const result: any = {};\n\n // Extract metadata.description \u2192 JSON Schema description\n applyMetadata(result, jtdSchema);\n\n // \u2500\u2500 Ref form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['ref']) {\n const refName = jtdSchema['ref'] as string;\n if (defs && defs[refName]) {\n const refResult: any = { $ref: `#/$defs/${refName}` };\n applyMetadata(refResult, jtdSchema);\n\n if (isNullable) {\n return {\n anyOf: [refResult, { type: 'null' }],\n ...(refResult.description\n ? { description: refResult.description }\n : {})\n };\n }\n return refResult;\n }\n return {};\n }\n\n // \u2500\u2500 Type form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['type']) {\n const typeMap: Record<string, string> = {\n boolean: 'boolean',\n string: 'string',\n timestamp: 'string',\n float32: 'number',\n float64: 'number',\n int8: 'integer',\n uint8: 'integer',\n int16: 'integer',\n uint16: 'integer',\n int32: 'integer',\n uint32: 'integer'\n };\n const jtdType = jtdSchema['type'] as string;\n const baseType = typeMap[jtdType] || 'string';\n\n result.type = isNullable ? [baseType, 'null'] : baseType;\n\n if (baseType === 'string' && jtdType === 'timestamp') {\n result.format = 'date-time';\n }\n return result;\n }\n\n // \u2500\u2500 Enum form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['enum']) {\n result.type = isNullable ? ['string', 'null'] : 'string';\n result.enum = isNullable\n ? [...(jtdSchema['enum'] as string[]), null]\n : jtdSchema['enum'];\n return result;\n }\n\n // \u2500\u2500 Elements form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['elements']) {\n result.type = isNullable ? ['array', 'null'] : 'array';\n result.items = convertNode(jtdSchema['elements'] as JTDSchemaJson, defs);\n return result;\n }\n\n // \u2500\u2500 Properties form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['properties'] || jtdSchema['optionalProperties']) {\n result.type = isNullable ? ['object', 'null'] : 'object';\n result.properties = {};\n result.required = [];\n\n if (jtdSchema['properties']) {\n for (const [key, value] of Object.entries(jtdSchema['properties'])) {\n result.properties[key] = convertNode(value as JTDSchemaJson, defs);\n result.required.push(key);\n }\n }\n\n if (jtdSchema['optionalProperties']) {\n for (const [key, value] of Object.entries(\n jtdSchema['optionalProperties']\n )) {\n result.properties[key] = convertNode(value as JTDSchemaJson, defs);\n }\n }\n\n // JTD is strict by default\n if (jtdSchema['additionalProperties'] !== true) {\n result.additionalProperties = false;\n }\n\n return result;\n }\n\n // \u2500\u2500 Values form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['values']) {\n result.type = isNullable ? ['object', 'null'] : 'object';\n result.additionalProperties = convertNode(\n jtdSchema['values'] as JTDSchemaJson,\n defs\n );\n return result;\n }\n\n // \u2500\u2500 Discriminator form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['discriminator'] && jtdSchema['mapping']) {\n const variants = [];\n const discriminatorKey = jtdSchema['discriminator'] as string;\n\n for (const [tag, schema] of Object.entries(jtdSchema['mapping'])) {\n const variantSchema = convertNode(schema as JTDSchemaJson, defs);\n\n if (!variantSchema.type) {\n variantSchema.type = 'object';\n } else if (\n variantSchema.type !== 'object' &&\n !Array.isArray(variantSchema.type)\n ) {\n variantSchema.type = 'object';\n }\n\n variantSchema.properties = variantSchema.properties || {};\n variantSchema.properties[discriminatorKey] = { const: tag };\n\n variantSchema.required = variantSchema.required || [];\n if (!variantSchema.required.includes(discriminatorKey)) {\n variantSchema.required.unshift(discriminatorKey);\n }\n\n // JTD discriminator variants are strict by default\n if (variantSchema.additionalProperties === undefined) {\n variantSchema.additionalProperties = false;\n }\n\n variants.push(variantSchema);\n }\n\n if (isNullable) {\n variants.push({ type: 'null' });\n }\n\n result.oneOf = variants;\n return result;\n }\n\n // \u2500\u2500 Empty form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n return result;\n}\n\n/**\n * Extract JTD metadata and apply as JSON Schema `description`.\n *\n * - `metadata.description` becomes `description` directly.\n * - Other metadata fields are appended as \"key: value\" lines.\n */\nfunction applyMetadata(result: any, jtdSchema: JTDSchemaJson): void {\n const metadata = jtdSchema['metadata'] as Record<string, unknown> | undefined;\n if (!metadata || typeof metadata !== 'object') return;\n\n const parts: string[] = [];\n const desc = metadata['description'];\n if (typeof desc === 'string' && desc) {\n parts.push(desc);\n }\n\n // Fold other metadata fields into description\n for (const [key, value] of Object.entries(metadata)) {\n if (key === 'description') continue;\n if (value === undefined || value === null) continue;\n const str = typeof value === 'string' ? value : JSON.stringify(value);\n parts.push(`${key}: ${str}`);\n }\n\n if (parts.length > 0) {\n result.description = parts.join('\\n');\n }\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD SCHEMA VALIDATION (using the `jtd` npm package)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport const MAX_VALIDATION_DEPTH = 32;\nexport const MAX_VALIDATION_ERRORS = 100;\n\n/** Validate that the input is a valid JTD schema. */\nexport function validateJTDSchema(\n schema: unknown\n): { valid: true; schema: JTDSchemaJson } | { valid: false; error: string } {\n if (!isSchema(schema)) {\n return {\n valid: false,\n error: 'Invalid JTD schema: Schema does not match JTD structure'\n };\n }\n\n if (!isValidSchema(schema)) {\n return {\n valid: false,\n error:\n 'Invalid JTD schema: Schema has semantic errors (e.g., circular references, conflicting forms)'\n };\n }\n\n return { valid: true, schema: schema as unknown as JTDSchemaJson };\n}\n\n/** Parse and validate a JSON string. */\nexport function validateJSON(jsonString: string): {\n valid: boolean;\n data?: unknown;\n error?: string;\n} {\n try {\n const data = JSON.parse(jsonString);\n return { valid: true, data };\n } catch (error) {\n return {\n valid: false,\n error: `Invalid JSON: ${error instanceof Error ? error.message : 'Parse error'}`\n };\n }\n}\n\n/** Parse a JSON string and validate it as a JTD schema. */\nexport function validateJTDSchemaString(\n schemaString: string\n): { valid: true; schema: JTDSchemaJson } | { valid: false; error: string } {\n const jsonResult = validateJSON(schemaString);\n if (!jsonResult.valid) return { valid: false, error: jsonResult.error! };\n\n return validateJTDSchema(jsonResult.data);\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// MONACO EDITOR CONFIG FOR JTD SCHEMA EDITING\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/** Monaco editor JSON schema configuration for JTD schema IntelliSense and validation. */\nexport const JTD_SCHEMA_CONFIG = {\n uri: 'https://jsontypedef.com/schemas/jtd-schema.json',\n fileMatch: ['inmemory://jtd-schema/*.json'],\n schema: {\n $schema: 'http://json-schema.org/draft-07/schema#',\n type: 'object',\n properties: {\n type: {\n description: 'Type form - defines a primitive type',\n enum: [\n 'boolean',\n 'string',\n 'timestamp',\n 'float32',\n 'float64',\n 'int8',\n 'uint8',\n 'int16',\n 'uint16',\n 'int32',\n 'uint32'\n ]\n },\n enum: {\n description: 'Enum form - defines a set of string values',\n type: 'array',\n items: { type: 'string' }\n },\n elements: {\n description: 'Elements form - defines an array with typed elements',\n $ref: '#'\n },\n properties: {\n description:\n 'Properties form - defines an object with required properties',\n type: 'object',\n additionalProperties: { $ref: '#' }\n },\n optionalProperties: {\n description: 'Optional properties - defines optional object properties',\n type: 'object',\n additionalProperties: { $ref: '#' }\n },\n values: {\n description: 'Values form - defines a map/dictionary with typed values',\n $ref: '#'\n },\n discriminator: {\n description: 'Discriminator form - defines a tagged union',\n type: 'string'\n },\n mapping: {\n description: 'Mapping for discriminator form',\n type: 'object',\n additionalProperties: { $ref: '#' }\n },\n ref: {\n description: 'Ref form - references a definition',\n type: 'string'\n },\n definitions: {\n description: 'Top-level definitions for reusable schemas',\n type: 'object',\n additionalProperties: { $ref: '#' }\n },\n nullable: {\n description: 'Allow null values',\n type: 'boolean'\n },\n metadata: {\n description: 'Custom metadata for documentation',\n type: 'object'\n },\n additionalProperties: {\n description: 'Allow additional properties (properties form only)',\n type: 'boolean'\n }\n }\n }\n};\n", "/**\n * Shared utilities for JSON Schema providers (OpenAI, Anthropic, Gemini).\n *\n * Contains constants, types, convert output pipeline, definition pooling,\n * and schema inspection helpers used identically across providers.\n */\nimport type {\n JTDSchemaJson,\n SignalDescriptorJson,\n SignalFormatJson\n} from './api';\nimport { validateJsonAgainstJTD } from './signals';\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Constants\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport const MAX_REF_DEPTH = 32;\n\nexport const INTEGER_BOUNDS: Record<\n string,\n { minimum: number; maximum: number }\n> = {\n int8: { minimum: -128, maximum: 127 },\n uint8: { minimum: 0, maximum: 255 },\n int16: { minimum: -32768, maximum: 32767 },\n uint16: { minimum: 0, maximum: 65535 },\n int32: { minimum: -2147483648, maximum: 2147483647 },\n uint32: { minimum: 0, maximum: 4294967295 }\n};\n\nexport const FLOAT32_BOUNDS = { minimum: -3.4028235e38, maximum: 3.4028235e38 };\n\nexport const UNSTRUCTURED_DESC =\n 'Must be a valid JSON value encoded as a string. ' +\n \"For objects: '{\\\"key\\\": \\\"value\\\"}', for arrays: '[1, 2]', for primitives: '\\\"hello\\\"' or '42' or 'true' or 'null'.\";\n\nexport const UNSTRUCTURED_VARIANT_DESC =\n 'The variant data encoded as a valid JSON string ' +\n '(e.g. \\'{\"key\": \"value\"}\\').';\n\nexport const VALUES_ARRAY_DESC =\n 'Array of key-value entries representing a map/dictionary. ' +\n \"Each entry has a string 'key' (the property name) and a 'value' (the property value).\";\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Primitives\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport function nullableType(\n baseType: string,\n nullable: boolean\n): string | string[] {\n return nullable ? [baseType, 'null'] : baseType;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Deep equal\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null || typeof a !== typeof b) return false;\n if (typeof a !== 'object') return false;\n if (Array.isArray(a))\n return (\n Array.isArray(b) &&\n a.length === b.length &&\n a.every((v, i) => deepEqual(v, b[i]))\n );\n if (Array.isArray(b)) return false;\n const ao = a as Record<string, unknown>,\n bo = b as Record<string, unknown>;\n const keys = Object.keys(ao);\n if (keys.length !== Object.keys(bo).length) return false;\n for (const k of keys) {\n if (!(k in bo) || !deepEqual(ao[k], bo[k])) return false;\n }\n return true;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD schema inspection (OpenAI + Anthropic)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport function isUnstructuredSchema(schema: JTDSchemaJson): boolean {\n if (!schema || typeof schema !== 'object') return true;\n\n if (\n schema.type ||\n schema.enum ||\n schema.elements ||\n schema.values ||\n schema.discriminator ||\n schema.ref\n ) {\n return false;\n }\n\n const hasRealProps =\n (schema.properties && Object.keys(schema.properties).length > 0) ||\n (schema.optionalProperties &&\n Object.keys(schema.optionalProperties).length > 0);\n if (hasRealProps) return false;\n\n if (\n (schema.properties !== undefined ||\n schema.optionalProperties !== undefined) &&\n schema.additionalProperties === true\n ) {\n return true;\n }\n\n if (\n schema.properties !== undefined ||\n schema.optionalProperties !== undefined\n ) {\n return false;\n }\n\n return true;\n}\n\nexport function isRootUnstructured(\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined\n): boolean {\n let resolved = schema;\n let depth = 0;\n while (resolved.ref && depth < MAX_REF_DEPTH) {\n const target = defs?.[resolved.ref];\n if (!target) return false;\n resolved = target;\n depth++;\n }\n if (depth >= MAX_REF_DEPTH) return false;\n return isUnstructuredSchema(resolved);\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD definition reachability \u2014 prune unreferenced definitions\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * Walk one or more JTD root schemas and return the set of definition names\n * that are transitively reachable via `ref`. Definitions not in this set\n * are unreferenced and should be pruned from `$defs`.\n */\nexport function findReferencedJTDDefs(\n roots: JTDSchemaJson[],\n defs: Record<string, JTDSchemaJson>\n): Set<string> {\n const visited = new Set<string>();\n const queue: JTDSchemaJson[] = [...roots];\n\n while (queue.length > 0) {\n const schema = queue.pop()!;\n if (!schema || typeof schema !== 'object') continue;\n\n if (schema.ref) {\n if (!visited.has(schema.ref) && defs[schema.ref]) {\n visited.add(schema.ref);\n queue.push(defs[schema.ref]);\n }\n continue; // ref form \u2014 no other structural fields\n }\n\n if (schema.elements) queue.push(schema.elements);\n if (schema.values) queue.push(schema.values);\n if (schema.properties) {\n for (const v of Object.values(schema.properties)) queue.push(v);\n }\n if (schema.optionalProperties) {\n for (const v of Object.values(schema.optionalProperties)) queue.push(v);\n }\n if (schema.mapping) {\n for (const v of Object.values(schema.mapping)) queue.push(v);\n }\n }\n\n return visited;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Convert result types\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport interface ConvertSuccessJson {\n success: true;\n kind: 'json';\n data: unknown;\n}\n\nexport interface ConvertSuccessBinary {\n success: true;\n kind: 'binary';\n mimeType: string;\n data: Uint8Array;\n filename?: string;\n}\n\nexport interface ConvertSuccessSignals {\n success: true;\n kind: 'signals';\n signals: Record<string, unknown>;\n}\n\nexport interface ConvertFailure {\n success: false;\n errors: string[];\n}\n\nexport type ConvertSuccess =\n | ConvertSuccessJson\n | ConvertSuccessBinary\n | ConvertSuccessSignals;\nexport type ConvertResult = ConvertSuccess | ConvertFailure;\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Convert output pipeline (identical across all 3 providers)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport function convertJTDOutput(\n jtdSchema: JTDSchemaJson,\n json: unknown,\n coerce: (\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n ) => unknown\n): ConvertResult {\n const defs = jtdSchema.definitions;\n const coerced = coerce(json, jtdSchema, defs, 0);\n const errors = validateJsonAgainstJTD(jtdSchema, coerced, defs);\n if (errors.length > 0) return { success: false, errors };\n return { success: true, kind: 'json', data: coerced };\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Reference mode: discriminated anyOf schema builder\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * Wrap a signal's value schema in a discriminated anyOf with two variants:\n * - `{ _kind: \"_value\", _value: <valueSchema> }` \u2014 inline data\n * - `{ _kind: \"_ref\", _ref: <string> }` \u2014 file reference\n *\n * Field names are prefixed with `_` to avoid collisions with user-defined\n * signal properties (e.g. a signal named \"data\" or \"kind\").\n * The `_kind` value matches its sibling field name for clarity.\n *\n * The caller resolves references before calling convert.\n *\n * Returns a plain object \u2014 cast to the provider's schema type at the call site.\n *\n * @param valueSchema - The signal's original schema (JTD, MIME, or multi-format anyOf).\n * @param description - Optional signal description to include.\n * @param strict - When false, omits `additionalProperties` (required by GeminiFC).\n */\nexport function buildReferenceAnyOf(\n valueSchema: unknown,\n description?: string,\n strict?: boolean\n): { anyOf: unknown[] } {\n const desc = description ? `${description}. ` : '';\n const valueVariant: Record<string, unknown> = {\n type: 'object',\n properties: {\n _kind: { type: 'string', enum: ['_value'] },\n _value: valueSchema\n },\n required: ['_kind', '_value'],\n description: `${desc}Inline value.`\n };\n const refVariant: Record<string, unknown> = {\n type: 'object',\n properties: {\n _kind: { type: 'string', enum: ['_ref'] },\n _ref: {\n type: 'string',\n description:\n 'A file reference. The referenced file MUST contain data that conforms to the same format described in the inline value variant.'\n }\n },\n required: ['_kind', '_ref'],\n description: `${desc}Reference to a file instead of inline data.`\n };\n if (strict !== false) {\n valueVariant.additionalProperties = false;\n refVariant.additionalProperties = false;\n }\n return { anyOf: [valueVariant, refVariant] };\n}\n\n/**\n * Check if a concrete MIME type matches an accepted MIME pattern.\n *\n * - `*\u200B/*` matches everything\n * - `image/*` matches any `image/...` subtype\n * - Exact string match otherwise\n */\nexport function mimeTypeMatches(concrete: string, accepted: string): boolean {\n const c = concrete.trim().toLowerCase();\n const a = accepted.trim().toLowerCase();\n if (a === '*/*') return true;\n if (a.endsWith('/*')) {\n return c.startsWith(a.slice(0, a.indexOf('/') + 1));\n }\n return c === a;\n}\n\n/**\n * Convert a MIME-format signal value to dispatch-ready binary SignalData.\n *\n * Accepts two data shapes:\n * - **Inline (from LLM):** `{ data: \"<base64>\", mimeType: \"image/png\", filename?: \"...\" }`\n * - **Resolved (from file reference):** `{ data: Uint8Array, mimeType: \"image/png\", filename?: \"...\" }`\n *\n * The input's `mimeType` field is the concrete type and ends up in the result.\n * It is validated against the format's accepted pattern (which may be a wildcard).\n *\n * @param acceptedMimeType - MIME pattern from the signal format declaration.\n * @param value - The signal value to convert.\n */\nexport function convertMimeOutput(\n acceptedMimeType: string,\n value: unknown\n): ConvertResult {\n if (typeof value !== 'object' || value === null) {\n return {\n success: false,\n errors: [\n `expected object with 'data' and 'mimeType' fields, got ${typeof value}`\n ]\n };\n }\n\n const fields = value as Record<string, unknown>;\n\n // Concrete mimeType is required \u2014 this is what goes to dispatch\n if (typeof fields.mimeType !== 'string' || !fields.mimeType.includes('/')) {\n return {\n success: false,\n errors: [\n 'parameter \\'mimeType\\': required concrete MIME type (e.g. \"image/png\", \"application/pdf\")'\n ]\n };\n }\n\n // Validate concrete type against the format's accepted pattern\n if (!mimeTypeMatches(fields.mimeType, acceptedMimeType)) {\n return {\n success: false,\n errors: [\n `parameter 'mimeType': \"${fields.mimeType}\" does not match accepted type \"${acceptedMimeType}\"`\n ]\n };\n }\n\n // Resolve data \u2014 either already Uint8Array or base64-encoded string\n let binaryData: Uint8Array;\n if (fields.data instanceof Uint8Array) {\n binaryData = fields.data;\n } else if (typeof fields.data === 'string') {\n binaryData = new Uint8Array(Buffer.from(fields.data, 'base64'));\n } else {\n return {\n success: false,\n errors: [\n `parameter 'data': expected base64 string or Uint8Array, got ${typeof fields.data}`\n ]\n };\n }\n\n const result: ConvertSuccessBinary = {\n success: true,\n kind: 'binary',\n mimeType: fields.mimeType,\n data: binaryData\n };\n if (typeof fields.filename === 'string' && fields.filename) {\n result.filename = fields.filename;\n }\n return result;\n}\n\n/**\n * Convert a single format's value. Dispatches to JTD or MIME conversion.\n *\n * @param fmt - The signal format (has either `jtdSchema` or `mimeType`).\n * @param value - The signal value. JSON for JTD formats, or `{ data, mimeType, filename }` for MIME.\n * @param coerce - Provider-specific JTD coercion function.\n */\nexport function convertSingleFormatOutput(\n fmt: SignalFormatJson,\n value: unknown,\n coerce: (\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n ) => unknown\n): ConvertResult {\n if (fmt.mimeType) return convertMimeOutput(fmt.mimeType, value);\n if (fmt.jtdSchema) return convertJTDOutput(fmt.jtdSchema, value, coerce);\n return {\n success: false,\n errors: ['Signal format has neither jtdSchema nor mimeType']\n };\n}\n\n/**\n * Convert a signal's value against its declared formats.\n *\n * The value is already resolved \u2014 this function knows nothing about file\n * references. When `referenceMode` is used, the **caller** handles the\n * `_kind` discriminator:\n *\n * - `{ _kind: \"_value\", _value: <inline> }` \u2192 caller extracts `_value`, passes here\n * - `{ _kind: \"_ref\", _ref: \"...\" }` \u2192 caller resolves the ref, passes:\n * - Parsed JSON object (for JTD formats)\n * - `{ data: Uint8Array, mimeType, filename }` (for MIME formats)\n *\n * For **single-format** signals, the value is validated directly.\n * For **multi-format** signals, each format is tried in order (anyOf \u2014 first match wins).\n *\n * @param descriptor - The signal descriptor with one or more formats.\n * @param value - The resolved signal value (JSON for JTD, or `{ data, mimeType, filename }` for MIME).\n * @param coerce - Provider-specific JTD coercion function.\n */\nexport function convertSignalOutput(\n descriptor: SignalDescriptorJson,\n value: unknown,\n coerce: (\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n ) => unknown\n): ConvertResult {\n const formats = descriptor.formats as SignalFormatJson[] | undefined;\n if (!formats || formats.length === 0) {\n return { success: false, errors: ['Signal descriptor has no formats'] };\n }\n\n if (formats.length === 1) {\n return convertSingleFormatOutput(formats[0], value, coerce);\n }\n\n // Multi-format: try each format (anyOf semantics \u2014 first match wins).\n // Each format is an independent option. A MIME format and a JTD format\n // in the same signal are ALTERNATIVE input paths, not mutual constraints.\n // The platform validates signal data against the binding's accepted formats\n // at dispatch time.\n const allErrors: string[] = [];\n for (const fmt of formats) {\n const result = convertSingleFormatOutput(fmt, value, coerce);\n if (result.success) return result;\n allErrors.push(...result.errors);\n }\n\n return {\n success: false,\n errors: [\n `No format matched for signal '${descriptor.name ?? 'unnamed'}'. Errors: ${allErrors.join('; ')}`\n ]\n };\n}\n\n/**\n * Convert multiple signals from a compound output.\n *\n * Expects `input` to be an object keyed by signal name. Each signal's\n * value is passed to {@link convertSignalOutput} \u2014 it can be JSON (for\n * JTD formats) or `{ data, mimeType, filename }` (for MIME formats).\n *\n * When using `referenceMode`, the **caller** must unwrap the `kind`\n * discriminator for each signal before calling this function.\n * See {@link convertSignalOutput} for the full reference-mode contract.\n */\nexport function convertSignalsOutput(\n descriptors: SignalDescriptorJson[],\n input: unknown,\n coerce: (\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n ) => unknown\n): ConvertResult {\n if (typeof input !== 'object' || input === null) {\n return {\n success: false,\n errors: ['/: expected object with signal properties, got ' + typeof input]\n };\n }\n\n const rec = input as Record<string, unknown>;\n const signals: Record<string, unknown> = {};\n const errors: string[] = [];\n\n for (const desc of descriptors) {\n if (!desc.name) continue;\n const value = rec[desc.name];\n if (value === undefined || value === null) continue;\n\n const result = convertSignalOutput(desc, value, coerce);\n if (!result.success) {\n for (const err of result.errors) {\n errors.push(`parameter '${desc.name}': ${err}`);\n }\n } else if (result.kind === 'binary') {\n signals[desc.name] = {\n data: result.data,\n mimeType: result.mimeType,\n filename: result.filename\n };\n } else if (result.kind === 'json') {\n signals[desc.name] = result.data;\n }\n }\n\n if (errors.length > 0) return { success: false, errors };\n return { success: true, kind: 'signals', signals };\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Schema tree walk (for providers that use $ref/$defs)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/** Minimal shape for $ref-bearing schemas (satisfied by OpenAISchema and AnthropicSchema). */\ninterface RefNode {\n properties?: Record<string, RefNode>;\n items?: RefNode;\n anyOf?: RefNode[];\n $defs?: Record<string, RefNode>;\n $ref?: string;\n}\n\nexport function walkSchema<T extends RefNode>(\n schema: T,\n fn: (node: T) => void\n): void {\n fn(schema);\n if (schema.properties)\n for (const v of Object.values(schema.properties)) walkSchema(v as T, fn);\n if (schema.items) walkSchema(schema.items as T, fn);\n if (schema.anyOf) for (const v of schema.anyOf) walkSchema(v as T, fn);\n if (schema.$defs)\n for (const v of Object.values(schema.$defs)) walkSchema(v as T, fn);\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Definition pooling for fromSignals (OpenAI + Anthropic)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/** Recursively rewrite JTD `ref` values in a schema according to a rename map. */\nfunction rewriteJtdRefs(\n schema: JTDSchemaJson,\n renames: Map<string, string>\n): JTDSchemaJson {\n if (!schema || typeof schema !== 'object') return schema;\n\n const result: JTDSchemaJson = { ...schema };\n\n if (result.ref && renames.has(result.ref)) {\n result.ref = renames.get(result.ref)!;\n }\n if (result.properties) {\n result.properties = Object.fromEntries(\n Object.entries(result.properties).map(([k, v]) => [\n k,\n rewriteJtdRefs(v, renames)\n ])\n );\n }\n if (result.optionalProperties) {\n result.optionalProperties = Object.fromEntries(\n Object.entries(result.optionalProperties).map(([k, v]) => [\n k,\n rewriteJtdRefs(v, renames)\n ])\n );\n }\n if (result.elements)\n result.elements = rewriteJtdRefs(result.elements, renames);\n if (result.values) result.values = rewriteJtdRefs(result.values, renames);\n if (result.mapping) {\n result.mapping = Object.fromEntries(\n Object.entries(result.mapping).map(([k, v]) => [\n k,\n rewriteJtdRefs(v, renames)\n ])\n );\n }\n // Definitions are rewritten at pool level, strip from the copy\n if (result.definitions) delete result.definitions;\n\n return result;\n}\n\n/**\n * Pre-scan all signal descriptors, pool their JTD definitions.\n * Deduplicates identical definition SETS; renames conflicts with `_N` suffix.\n *\n * Equivalence is checked per-format: all definitions within a single format's\n * `definitions` block are treated as a unit. Two formats with the same set of\n * definition names and identical schemas (deep-equal) share their defs.\n * If ANY definition name conflicts (same name, different schema), ALL defs\n * from that format get their own namespace to preserve internal cross-references.\n *\n * Returns the merged JTD definitions and signal copies with:\n * - refs rewritten to match merged names\n * - definitions stripped (they live in the merged pool now)\n */\nexport function poolSignalDefinitions(signals: SignalDescriptorJson[]): {\n mergedDefs: Record<string, JTDSchemaJson>;\n signalsCopy: SignalDescriptorJson[];\n} {\n const mergedDefs: Record<string, JTDSchemaJson> = {};\n\n // For each format that has definitions, compute its rename map.\n // Key: `signalIdx:formatIdx`, Value: Map<oldName, newName>\n const renamesByFormat = new Map<string, Map<string, string>>();\n\n for (let si = 0; si < signals.length; si++) {\n const formats = signals[si].formats as SignalFormatJson[] | undefined;\n if (!formats) continue;\n for (let fi = 0; fi < formats.length; fi++) {\n const defs = formats[fi].jtdSchema?.definitions;\n if (!defs || Object.keys(defs).length === 0) continue;\n\n // Check each definition against the merged pool\n const renames = new Map<string, string>();\n let hasConflict = false;\n\n for (const [name, defSchema] of Object.entries(defs)) {\n const existing = mergedDefs[name];\n if (!existing) continue; // new name, no conflict\n if (deepEqual(existing, defSchema)) continue; // identical, dedup\n hasConflict = true;\n break;\n }\n\n if (hasConflict) {\n // This format has at least one conflict. Rename ALL its definitions\n // to avoid broken cross-references between shared and renamed defs.\n for (const [name, defSchema] of Object.entries(defs)) {\n const existing = mergedDefs[name];\n if (existing && deepEqual(existing, defSchema)) {\n // Identical to existing \u2014 can still share\n continue;\n }\n if (existing) {\n // Conflict \u2014 find unique name\n let n = 1;\n while (mergedDefs[`${name}_${n}`]) n++;\n const newName = `${name}_${n}`;\n renames.set(name, newName);\n mergedDefs[newName] = defSchema;\n } else {\n mergedDefs[name] = defSchema;\n }\n }\n } else {\n // No conflicts \u2014 all defs are either new or identical (deduped)\n for (const [name, defSchema] of Object.entries(defs)) {\n if (!mergedDefs[name]) mergedDefs[name] = defSchema;\n }\n }\n\n if (renames.size > 0) renamesByFormat.set(`${si}:${fi}`, renames);\n }\n }\n\n // Rewrite refs inside renamed definitions (they may reference other renamed defs)\n for (const [, renames] of renamesByFormat) {\n for (const [, newName] of renames) {\n mergedDefs[newName] = rewriteJtdRefs(mergedDefs[newName], renames);\n }\n }\n\n // Build signal copies with rewritten refs and stripped definitions\n const signalsCopy: SignalDescriptorJson[] = signals.map((signal, si) => {\n const formats = signal.formats as SignalFormatJson[] | undefined;\n if (!formats) return signal;\n\n let hasChanges = false;\n const newFormats = formats.map((fmt, fi) => {\n if (!fmt.jtdSchema) return fmt;\n const renames = renamesByFormat.get(`${si}:${fi}`);\n const hasDefs =\n fmt.jtdSchema.definitions &&\n Object.keys(fmt.jtdSchema.definitions).length > 0;\n if (!renames && !hasDefs) return fmt;\n\n hasChanges = true;\n const rewritten = renames\n ? rewriteJtdRefs(fmt.jtdSchema, renames)\n : { ...fmt.jtdSchema };\n delete rewritten.definitions;\n return { ...fmt, jtdSchema: rewritten };\n });\n\n return hasChanges ? { ...signal, formats: newFormats } : signal;\n });\n\n // Prune definitions that are never transitively referenced from any signal\n const rootSchemas = signalsCopy.flatMap((s) =>\n ((s.formats as SignalFormatJson[] | undefined) ?? [])\n .filter((f) => f.jtdSchema)\n .map((f) => f.jtdSchema!)\n );\n const referenced = findReferencedJTDDefs(rootSchemas, mergedDefs);\n for (const name of Object.keys(mergedDefs)) {\n if (!referenced.has(name)) delete mergedDefs[name];\n }\n\n return { mergedDefs, signalsCopy };\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Cycle description \u2014 shared by Anthropic and Gemini for recursive ref fallbacks\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nfunction describeJTDType(schema: JTDSchemaJson): string {\n if (!schema || typeof schema !== 'object') return 'any';\n if (schema.ref) return schema.ref;\n if (schema.type) return schema.type;\n if (schema.enum) return `one of: ${schema.enum.join(', ')}`;\n if (schema.elements) return `array of ${describeJTDType(schema.elements)}`;\n if (schema.values) return `map of ${describeJTDType(schema.values)}`;\n if (schema.properties || schema.optionalProperties) return 'object';\n if (schema.discriminator) return `union on '${schema.discriminator}'`;\n return 'any';\n}\n\n/**\n * Build a human-readable description of a JTD definition for use as a\n * fallback at recursive ref break points. Used by both Anthropic and Gemini\n * converters when a cycle is detected.\n */\nexport function describeDefinitionForCycle(\n defName: string,\n def: JTDSchemaJson\n): string {\n const parts: string[] = [\n `Recursive nesting. This field accepts the same object structure as its parent with the following shape:`\n ];\n\n const required: string[] = [];\n const optional: string[] = [];\n\n if (def.properties) {\n for (const [key, prop] of Object.entries(def.properties)) {\n required.push(`${key} (${describeJTDType(prop)})`);\n }\n }\n if (def.optionalProperties) {\n for (const [key, prop] of Object.entries(def.optionalProperties)) {\n optional.push(`${key} (${describeJTDType(prop)})`);\n }\n }\n\n if (required.length > 0) {\n parts.push(`Required: ${required.join(', ')}.`);\n }\n if (optional.length > 0) {\n const shown = optional.slice(0, 8);\n const suffix =\n optional.length > 8 ? `, and ${optional.length - 8} more` : '';\n parts.push(`Optional: ${shown.join(', ')}${suffix}.`);\n }\n\n if (def.elements) {\n parts.push(`Array of ${describeJTDType(def.elements)}.`);\n }\n if (def.values) {\n parts.push(`Map with values of type ${describeJTDType(def.values)}.`);\n }\n\n return parts.join(' ');\n}\n", "// Copyright 2024-2025 Mochabug, LLC. All rights reserved.\n// Licensed under the Apache License, Version 2.0.\n\nimport type {\n JTDSchemaJson,\n SignalDescriptorJson,\n SignalFormatJson\n} from '../api';\nimport {\n buildReferenceAnyOf,\n describeDefinitionForCycle,\n FLOAT32_BOUNDS,\n INTEGER_BOUNDS,\n isRootUnstructured,\n isUnstructuredSchema,\n MAX_REF_DEPTH,\n nullableType,\n convertJTDOutput as sharedConvertJTDOutput,\n convertSignalOutput as sharedConvertSignalOutput,\n convertSignalsOutput as sharedConvertSignalsOutput,\n UNSTRUCTURED_DESC,\n VALUES_ARRAY_DESC,\n type ConvertFailure,\n type ConvertResult,\n type ConvertSuccess,\n type ConvertSuccessBinary,\n type ConvertSuccessJson,\n type ConvertSuccessSignals\n} from '../schema-utils';\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Types\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * JSON Schema subset accepted by the Gemini structured-output API.\n *\n * This is the provider-specific representation produced by {@link GeminiConversion}.\n * It covers the keywords that Gemini 3+ actually inspects when constraining\n * model output: `type`, `properties`, `required`, `enum`, `items`,\n * `additionalProperties`, `format`, `minimum`/`maximum`, `title`, and\n * `description`.\n */\nexport interface GeminiSchema {\n type?: string | string[];\n nullable?: boolean;\n title?: string;\n description?: string;\n properties?: Record<string, GeminiSchema>;\n required?: string[];\n additionalProperties?: boolean | GeminiSchema;\n enum?: (string | number | null)[];\n format?: string;\n items?: GeminiSchema;\n minimum?: number;\n maximum?: number;\n minItems?: number;\n maxItems?: number;\n anyOf?: GeminiSchema[];\n}\n\n/**\n * Schema subset matching the Gemini v1beta Schema protobuf message.\n * Used for function calling `parameters` field in `FunctionDeclaration`.\n *\n * Key differences from {@link GeminiSchema}:\n * - `type` is always a single string (no type arrays)\n * - Uses `nullable: true` instead of `[\"type\", \"null\"]` type arrays\n * - Does NOT include `additionalProperties`\n * - Supports `anyOf` for discriminator unions\n * - Supports `minItems`/`maxItems` for array bounds\n */\nexport interface GeminiFCSchema {\n type?: string;\n nullable?: boolean;\n title?: string;\n description?: string;\n properties?: Record<string, GeminiFCSchema>;\n required?: string[];\n enum?: string[];\n format?: string;\n items?: GeminiFCSchema;\n minimum?: number;\n maximum?: number;\n minItems?: number;\n maxItems?: number;\n anyOf?: GeminiFCSchema[];\n}\n\nexport type {\n ConvertFailure,\n ConvertResult,\n ConvertSuccess,\n ConvertSuccessBinary,\n ConvertSuccessJson,\n ConvertSuccessSignals\n} from '../schema-utils';\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Constants & helpers\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst METADATA_KNOWN_KEYS = new Set(['title', 'label', 'name', 'description']);\n\nfunction applyMetadata(result: GeminiSchema, schema: JTDSchemaJson): void {\n const meta = schema.metadata as Record<string, unknown> | undefined;\n if (!meta) return;\n\n if (typeof meta['description'] === 'string' && meta['description'].trim()) {\n result.description = meta['description'].trim();\n }\n\n const titleCandidate =\n (typeof meta['title'] === 'string' && meta['title'].trim()) ||\n (typeof meta['label'] === 'string' && meta['label'].trim()) ||\n (typeof meta['name'] === 'string' && meta['name'].trim());\n if (titleCandidate) result.title = titleCandidate;\n\n const extras: string[] = [];\n for (const [key, value] of Object.entries(meta)) {\n if (METADATA_KNOWN_KEYS.has(key)) continue;\n if (typeof value === 'string' && value.trim()) {\n extras.push(`[${key}: ${value.trim()}]`);\n } else if (typeof value === 'number' || typeof value === 'boolean') {\n extras.push(`[${key}: ${value}]`);\n }\n }\n if (extras.length > 0) {\n const suffix = extras.join(' ');\n result.description = result.description\n ? `${result.description} ${suffix}`\n : suffix;\n }\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Schema conversion (JTD \u2192 Gemini JSON Schema)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nfunction resolveRef(\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n): JTDSchemaJson {\n if (depth > MAX_REF_DEPTH) {\n throw new Error(\n `Circular or excessively deep ref chain (exceeded ${MAX_REF_DEPTH} levels)`\n );\n }\n if (!schema.ref) return schema;\n const resolved = defs?.[schema.ref];\n if (!resolved) throw new Error(`Unresolved ref \"${schema.ref}\"`);\n return resolveRef(resolved, defs, depth + 1);\n}\n\nfunction convertNode(\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number,\n activeRefs?: Set<string>\n): GeminiSchema {\n if (depth > MAX_REF_DEPTH) {\n throw new Error(`Schema nesting exceeded ${MAX_REF_DEPTH} levels`);\n }\n\n if (!schema || typeof schema !== 'object') {\n return { description: 'Any JSON value' };\n }\n\n if (schema.ref) {\n const visited = activeRefs ?? new Set<string>();\n if (visited.has(schema.ref)) {\n // Cycle detected \u2014 emit a descriptive placeholder instead of recursing\n const refDef = defs?.[schema.ref];\n const result: GeminiSchema = {\n description: refDef\n ? describeDefinitionForCycle(schema.ref, refDef)\n : `Recursive reference to ${schema.ref}. Accepts any valid JSON matching the ${schema.ref} structure.`\n };\n applyMetadata(result, schema);\n return result;\n }\n visited.add(schema.ref);\n const resolved = resolveRef(schema, defs, 0);\n const result = convertNode(resolved, defs, depth + 1, visited);\n visited.delete(schema.ref);\n applyMetadata(result, schema);\n if (schema.nullable) {\n const existing = result.type;\n result.type = Array.isArray(existing)\n ? [...existing, 'null']\n : existing\n ? [existing, 'null']\n : ['null'];\n }\n return result;\n }\n\n const isNullable = schema.nullable === true;\n const result: GeminiSchema = {};\n\n if (schema.type) {\n const intBounds =\n INTEGER_BOUNDS[schema.type as keyof typeof INTEGER_BOUNDS];\n if (intBounds) {\n result.type = nullableType('integer', isNullable);\n result.minimum = intBounds.minimum;\n result.maximum = intBounds.maximum;\n } else\n switch (schema.type) {\n case 'boolean':\n result.type = nullableType('boolean', isNullable);\n break;\n case 'string':\n result.type = nullableType('string', isNullable);\n break;\n case 'timestamp':\n result.type = nullableType('string', isNullable);\n result.format = 'date-time';\n break;\n case 'float32':\n result.type = nullableType('number', isNullable);\n result.minimum = FLOAT32_BOUNDS.minimum;\n result.maximum = FLOAT32_BOUNDS.maximum;\n break;\n case 'float64':\n result.type = nullableType('number', isNullable);\n break;\n default:\n throw new Error(`Unknown JTD type \"${schema.type}\"`);\n }\n applyMetadata(result, schema);\n return result;\n }\n\n if (schema.enum) {\n result.type = nullableType('string', isNullable);\n result.enum = isNullable ? [...schema.enum, null] : schema.enum;\n applyMetadata(result, schema);\n return result;\n }\n\n if (schema.elements) {\n result.type = nullableType('array', isNullable);\n result.items = convertNode(schema.elements, defs, depth + 1, activeRefs);\n applyMetadata(result, schema);\n return result;\n }\n\n if (schema.properties || schema.optionalProperties) {\n result.type = nullableType('object', isNullable);\n result.properties = {};\n const required: string[] = [];\n\n if (schema.properties) {\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n result.properties[key] = convertNode(\n propSchema,\n defs,\n depth + 1,\n activeRefs\n );\n required.push(key);\n }\n }\n if (schema.optionalProperties) {\n for (const [key, propSchema] of Object.entries(\n schema.optionalProperties\n )) {\n result.properties[key] = convertNode(\n propSchema,\n defs,\n depth + 1,\n activeRefs\n );\n }\n }\n if (required.length > 0) result.required = required;\n if (schema.additionalProperties !== true)\n result.additionalProperties = false;\n\n applyMetadata(result, schema);\n return result;\n }\n\n if (schema.values) {\n result.type = nullableType('object', isNullable);\n result.additionalProperties = convertNode(\n schema.values,\n defs,\n depth + 1,\n activeRefs\n );\n applyMetadata(result, schema);\n return result;\n }\n\n if (schema.discriminator && schema.mapping) {\n const discKey = schema.discriminator;\n const tags = Object.keys(schema.mapping);\n\n const variants: GeminiSchema[] = tags.map((tag) => {\n const variantSchema = schema.mapping![tag]!;\n const v = convertNode(variantSchema, defs, depth + 1, activeRefs);\n // Inject the discriminator as a const-like enum property\n v.type = 'object';\n v.properties = {\n [discKey]: {\n type: 'string',\n enum: [tag],\n description: `Must be '${tag}'.`\n },\n ...(v.properties ?? {})\n };\n v.required = [discKey, ...(v.required ?? [])];\n v.additionalProperties = false;\n return v;\n });\n\n if (isNullable) {\n variants.push({ type: 'null' });\n }\n\n if (variants.length === 1) {\n const single = variants[0]!;\n applyMetadata(single, schema);\n return single;\n }\n\n result.anyOf = variants;\n applyMetadata(result, schema);\n return result;\n }\n\n result.description = 'Any JSON value';\n applyMetadata(result, schema);\n return result;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// FC Schema conversion (JTD \u2192 Gemini v1beta Schema protobuf for function calling)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nfunction applyMetadataFC(result: GeminiFCSchema, schema: JTDSchemaJson): void {\n const meta = schema.metadata as Record<string, unknown> | undefined;\n if (!meta) return;\n\n if (typeof meta['description'] === 'string' && meta['description'].trim()) {\n result.description = meta['description'].trim();\n }\n\n const titleCandidate =\n (typeof meta['title'] === 'string' && meta['title'].trim()) ||\n (typeof meta['label'] === 'string' && meta['label'].trim()) ||\n (typeof meta['name'] === 'string' && meta['name'].trim());\n if (titleCandidate) result.title = titleCandidate;\n\n const extras: string[] = [];\n for (const [key, value] of Object.entries(meta)) {\n if (METADATA_KNOWN_KEYS.has(key)) continue;\n if (typeof value === 'string' && value.trim()) {\n extras.push(`[${key}: ${value.trim()}]`);\n } else if (typeof value === 'number' || typeof value === 'boolean') {\n extras.push(`[${key}: ${value}]`);\n }\n }\n if (extras.length > 0) {\n const suffix = extras.join(' ');\n result.description = result.description\n ? `${result.description} ${suffix}`\n : suffix;\n }\n}\n\nfunction convertNodeFC(\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number,\n activeRefs?: Set<string>\n): GeminiFCSchema {\n if (depth > MAX_REF_DEPTH) {\n throw new Error(`Schema nesting exceeded ${MAX_REF_DEPTH} levels`);\n }\n\n if (!schema || typeof schema !== 'object') {\n return { type: 'string', description: UNSTRUCTURED_DESC };\n }\n\n // Unstructured/empty schema \u2192 string-encoded JSON (no additionalProperties in FC)\n if (isRootUnstructured(schema, defs)) {\n const result: GeminiFCSchema = {\n type: 'string',\n description: UNSTRUCTURED_DESC\n };\n if (schema.nullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Ref form \u2014 inline with cycle detection\n if (schema.ref) {\n const visited = activeRefs ?? new Set<string>();\n if (visited.has(schema.ref)) {\n const refDef = defs?.[schema.ref];\n const result: GeminiFCSchema = {\n description: refDef\n ? describeDefinitionForCycle(schema.ref, refDef)\n : `Recursive reference to ${schema.ref}. Accepts any valid JSON matching the ${schema.ref} structure.`\n };\n applyMetadataFC(result, schema);\n return result;\n }\n visited.add(schema.ref);\n const resolved = resolveRef(schema, defs, 0);\n const result = convertNodeFC(resolved, defs, depth + 1, visited);\n visited.delete(schema.ref);\n applyMetadataFC(result, schema);\n if (schema.nullable) result.nullable = true;\n return result;\n }\n\n const isNullable = schema.nullable === true;\n const result: GeminiFCSchema = {};\n\n // Type form\n if (schema.type) {\n const intBounds =\n INTEGER_BOUNDS[schema.type as keyof typeof INTEGER_BOUNDS];\n if (intBounds) {\n result.type = 'integer';\n result.minimum = intBounds.minimum;\n result.maximum = intBounds.maximum;\n } else\n switch (schema.type) {\n case 'boolean':\n result.type = 'boolean';\n break;\n case 'string':\n result.type = 'string';\n break;\n case 'timestamp':\n result.type = 'string';\n result.format = 'date-time';\n break;\n case 'float32':\n result.type = 'number';\n result.minimum = FLOAT32_BOUNDS.minimum;\n result.maximum = FLOAT32_BOUNDS.maximum;\n break;\n case 'float64':\n result.type = 'number';\n break;\n default:\n throw new Error(`Unknown JTD type \"${schema.type}\"`);\n }\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Enum form\n if (schema.enum) {\n result.type = 'string';\n result.enum = [...schema.enum];\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Elements form\n if (schema.elements) {\n result.type = 'array';\n result.items = convertNodeFC(schema.elements, defs, depth + 1, activeRefs);\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Properties form\n if (schema.properties || schema.optionalProperties) {\n result.type = 'object';\n result.properties = {};\n const required: string[] = [];\n\n if (schema.properties) {\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n result.properties[key] = convertNodeFC(\n propSchema,\n defs,\n depth + 1,\n activeRefs\n );\n required.push(key);\n }\n }\n if (schema.optionalProperties) {\n for (const [key, propSchema] of Object.entries(\n schema.optionalProperties\n )) {\n result.properties[key] = convertNodeFC(\n propSchema,\n defs,\n depth + 1,\n activeRefs\n );\n }\n }\n if (required.length > 0) result.required = required;\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Values form \u2192 key-value array pattern (no additionalProperties in FC)\n if (schema.values) {\n const valueSchema = convertNodeFC(\n schema.values,\n defs,\n depth + 1,\n activeRefs\n );\n result.type = 'array';\n result.description = VALUES_ARRAY_DESC;\n result.items = {\n type: 'object',\n properties: {\n key: { type: 'string', description: 'The property name.' },\n value: valueSchema\n },\n required: ['key', 'value']\n };\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Discriminator form \u2192 anyOf with const-like enum for tag\n if (schema.discriminator && schema.mapping) {\n const discKey = schema.discriminator;\n const mapping = schema.mapping;\n const variants: GeminiFCSchema[] = [];\n\n for (const [tag, variantSchema] of Object.entries(mapping)) {\n const variantOut = convertNodeFC(\n variantSchema,\n defs,\n depth + 1,\n activeRefs\n );\n // Per RFC 8927, mapping values are always properties form;\n // the discriminator tag never appears in variant properties.\n variantOut.properties ??= {};\n variantOut.properties[discKey] = {\n type: 'string',\n enum: [tag]\n };\n (variantOut.required ??= []).unshift(discKey);\n applyMetadataFC(variantOut, variantSchema);\n variants.push(variantOut);\n }\n\n if (isNullable) variants.push({ type: 'string', nullable: true });\n\n const anyOfResult: GeminiFCSchema = { anyOf: variants };\n applyMetadataFC(anyOfResult, schema);\n return anyOfResult;\n }\n\n // Fallback \u2014 should not reach here after isRootUnstructured check above,\n // but guard just in case.\n result.type = 'string';\n result.description = UNSTRUCTURED_DESC;\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Coercion (Gemini output \u2192 JTD-conforming JSON)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst INTEGER_TYPE_SET = new Set(Object.keys(INTEGER_BOUNDS));\n\nfunction coerceToJTD(\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n): unknown {\n if (depth > MAX_REF_DEPTH) return value;\n if (value === null || value === undefined) return value;\n\n if (schema.ref) {\n const resolved = defs?.[schema.ref];\n if (resolved) return coerceToJTD(value, resolved, defs, depth + 1);\n return value;\n }\n\n if (schema.type && INTEGER_TYPE_SET.has(schema.type)) {\n if (typeof value === 'number' && !Number.isInteger(value))\n return Math.round(value);\n return value;\n }\n\n if (schema.elements) {\n const el = schema.elements;\n const arr = Array.isArray(value) ? value : [value];\n return arr.map((item) => coerceToJTD(item, el, defs, depth + 1));\n }\n\n if (\n (schema.properties || schema.optionalProperties) &&\n typeof value === 'object' &&\n value !== null\n ) {\n const obj = { ...(value as Record<string, unknown>) };\n if (schema.properties) {\n for (const [k, s] of Object.entries(schema.properties)) {\n if (k in obj) obj[k] = coerceToJTD(obj[k], s, defs, depth + 1);\n }\n }\n if (schema.optionalProperties) {\n for (const [k, s] of Object.entries(schema.optionalProperties)) {\n if (k in obj) obj[k] = coerceToJTD(obj[k], s, defs, depth + 1);\n }\n }\n return obj;\n }\n\n if (schema.values && typeof value === 'object' && value !== null) {\n const obj: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n obj[k] = coerceToJTD(v, schema.values, defs, depth + 1);\n }\n return obj;\n }\n\n if (\n schema.discriminator &&\n schema.mapping &&\n typeof value === 'object' &&\n value !== null\n ) {\n const rec = value as Record<string, unknown>;\n const tag = rec[schema.discriminator];\n if (typeof tag !== 'string') return value;\n const vs = schema.mapping[tag];\n if (!vs) return value;\n // With anyOf, data is flat (same as JTD format). Coerce variant properties in-place.\n const coerced = coerceToJTD(rec, vs, defs, depth + 1) as Record<\n string,\n unknown\n >;\n coerced[schema.discriminator] = tag;\n return coerced;\n }\n\n return value;\n}\n\n/**\n * Coercion for FC schemas. Handles the same forms as coerceToJTD, plus:\n * - Unstructured schemas: parses JSON strings back into objects\n * - Values form: converts {key, value}[] arrays back into Records\n * - Discriminator form: flat object coercion (same as Anthropic anyOf pattern)\n */\nfunction coerceToJTDFC(\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n): unknown {\n if (depth > MAX_REF_DEPTH) return value;\n if (value === null || value === undefined) return value;\n\n // Ref form\n if (schema.ref) {\n const resolved = defs?.[schema.ref];\n if (resolved) return coerceToJTDFC(value, resolved, defs, depth + 1);\n return value;\n }\n\n // Unstructured \u2014 parse JSON string\n if (isUnstructuredSchema(schema)) {\n if (typeof value === 'string') {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n return value;\n }\n\n // Type form \u2014 integer coercion\n if (schema.type && INTEGER_TYPE_SET.has(schema.type)) {\n if (typeof value === 'number' && !Number.isInteger(value))\n return Math.round(value);\n return value;\n }\n\n // Elements form\n if (schema.elements) {\n const el = schema.elements;\n const arr = Array.isArray(value) ? value : [value];\n return arr.map((item) => coerceToJTDFC(item, el, defs, depth + 1));\n }\n\n // Properties form\n if (\n (schema.properties || schema.optionalProperties) &&\n typeof value === 'object' &&\n value !== null\n ) {\n const obj = { ...(value as Record<string, unknown>) };\n if (schema.properties) {\n for (const [k, s] of Object.entries(schema.properties)) {\n if (k in obj) obj[k] = coerceToJTDFC(obj[k], s, defs, depth + 1);\n }\n }\n if (schema.optionalProperties) {\n for (const [k, s] of Object.entries(schema.optionalProperties)) {\n if (k in obj) obj[k] = coerceToJTDFC(obj[k], s, defs, depth + 1);\n }\n }\n return obj;\n }\n\n // Values form \u2014 {key, value}[] array \u2192 Record\n if (schema.values && Array.isArray(value)) {\n const result: Record<string, unknown> = {};\n for (const entry of value) {\n if (typeof entry === 'object' && entry !== null) {\n const rec = entry as Record<string, unknown>;\n const key = rec.key as string;\n result[key] = coerceToJTDFC(rec.value, schema.values, defs, depth + 1);\n }\n }\n return result;\n }\n\n // Discriminator form \u2014 flat object, coerce variant fields\n if (\n schema.discriminator &&\n schema.mapping &&\n typeof value === 'object' &&\n value !== null\n ) {\n const rec = value as Record<string, unknown>;\n const discKey = schema.discriminator;\n const tag = rec[discKey];\n if (typeof tag !== 'string') return value;\n const variantSchema = schema.mapping[tag];\n\n if (!variantSchema) return value;\n\n const result: Record<string, unknown> = { [discKey]: tag };\n\n if (variantSchema.properties) {\n for (const [k, propSchema] of Object.entries(variantSchema.properties)) {\n if (k in rec) {\n result[k] = coerceToJTDFC(rec[k], propSchema, defs, depth + 1);\n }\n }\n }\n\n if (variantSchema.optionalProperties) {\n for (const [k, propSchema] of Object.entries(\n variantSchema.optionalProperties\n )) {\n if (k in rec) {\n result[k] = coerceToJTDFC(rec[k], propSchema, defs, depth + 1);\n }\n }\n }\n\n return result;\n }\n\n return value;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Public API\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * Converts JTD schemas or Adapt signal descriptors into Gemini-compatible\n * JSON Schema, and converts raw LLM output back into validated data.\n *\n * ### JTD form mapping\n * - **type** -- mapped to the corresponding JSON Schema `type` (`string`,\n * `number`, `integer`, `boolean`). Integer sub-types (int8 .. uint32) add\n * `minimum`/`maximum` bounds. `timestamp` becomes `string` with\n * `format: \"date-time\"`.\n * - **enum** -- mapped to `type: \"string\"` with an `enum` array.\n * - **elements** -- mapped to `type: \"array\"` with `items`.\n * - **properties / optionalProperties** -- mapped to `type: \"object\"` with\n * `properties`, `required`, and `additionalProperties: false`. Optional\n * properties are included but not listed in `required`.\n * - **values** -- mapped to `type: \"object\"` with `additionalProperties` set\n * to the value schema (Gemini supports free-form object keys natively).\n * - **discriminator** -- mapped to a wrapper object: a `type: \"string\"` enum\n * property for the discriminator tag, plus one property per variant. The\n * LLM is instructed via `description` to populate only the property\n * matching the chosen tag. (Gemini does not support `anyOf`.)\n *\n * ### MIME formats\n * MIME signal formats become `type: \"string\"` with a description requesting\n * base64-encoded content. On {@link convert}, the base64 string is decoded\n * into a {@link ConvertSuccessBinary} result.\n *\n * ### Serialization\n * `JSON.stringify(conv)` produces a JSON-safe object via {@link toJSON}.\n * Use {@link fromJSON} to restore a fully functional instance from the\n * serialized form (string or parsed object).\n *\n * ```ts\n * const conv = GeminiStructuredOutputConversion.fromJTD(jtdSchema);\n * conv.schema; // Gemini JSON Schema to pass to the LLM\n * conv.convert(output); // coerce + validate -> ConvertResult\n *\n * // Serialize & restore\n * const cached = JSON.stringify(conv);\n * const restored = GeminiStructuredOutputConversion.fromJSON(cached);\n * ```\n */\nexport class GeminiStructuredOutputConversion<\n T extends ConvertSuccess = ConvertSuccess\n> {\n /** The Gemini-specific JSON Schema to pass to the Gemini API as the response schema. */\n readonly schema: GeminiSchema;\n /**\n * The original JTD schema used to build this conversion.\n * Set when created via {@link fromJTD}; `undefined` when created via {@link fromSignal}.\n */\n readonly jtdSchema?: JTDSchemaJson;\n /**\n * The original signal descriptor used to build this conversion.\n * Set when created via {@link fromSignal}; `undefined` when created via {@link fromJTD}.\n */\n readonly descriptor?: SignalDescriptorJson;\n /**\n * The original signal descriptors used to build this conversion.\n * Set when created via {@link fromSignals}; `undefined` otherwise.\n */\n readonly descriptors?: SignalDescriptorJson[];\n /** When true, MIME signals use file-reference schemas instead of base64. */\n readonly referenceMode: boolean;\n\n // \u2500\u2500 Factories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Build a conversion from a JTD schema.\n *\n * Recursively converts every JTD form into the equivalent Gemini JSON Schema\n * node. Definitions (`jtdSchema.definitions`) are resolved inline because\n * Gemini does not support `$ref`.\n *\n * @param jtdSchema - A valid JTD schema (RFC 8927).\n * @returns A new {@link GeminiStructuredOutputConversion} with `jtdSchema` set.\n * @throws If the schema contains an unknown JTD type, an unresolved `ref`,\n * or circular / excessively deep refs (exceeding 32 levels).\n */\n static fromJTD(\n jtdSchema: JTDSchemaJson\n ): GeminiStructuredOutputConversion<ConvertSuccessJson> {\n let schema = convertNode(jtdSchema, jtdSchema.definitions, 0);\n\n // Wrap root discriminated union \u2014 providers require type:\"object\" at\n // the function parameter root; bare anyOf is rejected.\n if (jtdSchema.discriminator && jtdSchema.mapping) {\n schema = {\n type: 'object',\n properties: { input: schema },\n required: ['input']\n };\n }\n\n return new GeminiStructuredOutputConversion(schema, jtdSchema, undefined);\n }\n\n /**\n * Build a conversion from an Adapt signal descriptor.\n *\n * If the descriptor has a single format, its schema is used directly\n * (unwrapped). If it has multiple formats, a wrapper object schema is\n * produced with `format_0`, `format_1`, ... properties -- the LLM must\n * populate exactly one. MIME formats become base64 string schemas.\n *\n * @param descriptor - An Adapt signal descriptor with one or more formats.\n * @returns A new {@link GeminiStructuredOutputConversion} with `descriptor` set.\n * @throws If the descriptor has no formats, or a format has neither\n * `jtdSchema` nor `mimeType`.\n */\n static fromSignal(\n descriptor: SignalDescriptorJson,\n referenceMode?: boolean\n ): GeminiStructuredOutputConversion<\n ConvertSuccessJson | ConvertSuccessBinary\n > {\n const schema = buildSignalSchema(descriptor);\n\n if (referenceMode) {\n const refSchema = buildReferenceAnyOf(\n schema,\n descriptor.description\n ) as GeminiSchema;\n return new GeminiStructuredOutputConversion(\n refSchema,\n undefined,\n descriptor\n );\n }\n\n return new GeminiStructuredOutputConversion(schema, undefined, descriptor);\n }\n\n /**\n * Build a conversion from multiple signal descriptors.\n *\n * Produces an object schema with one property per signal, suitable for\n * use as structured output parameters. Each signal's schema is built via\n * {@link fromSignal} internally.\n *\n * Optional signals are omitted from `required` (Gemini does not use the\n * required+nullable pattern).\n *\n * Receiver/transceiver compatible:\n * ```ts\n * const conv = GeminiStructuredOutputConversion.fromSignals(\n * receiver.signals!,\n * receiver.description,\n * receiver.name\n * );\n * ```\n *\n * @param signals - Array of signal descriptors.\n * @param description - Optional description for the compound schema.\n * @param label - Optional label (mapped to `title` on the schema).\n * @returns A new {@link GeminiStructuredOutputConversion} with `descriptors` set.\n * @throws If any signal has no formats or an invalid format.\n */\n static fromSignals(\n signals: SignalDescriptorJson[],\n description?: string,\n label?: string,\n referenceMode?: boolean\n ): GeminiStructuredOutputConversion<ConvertSuccessSignals> {\n const properties: Record<string, GeminiSchema> = {};\n const required: string[] = [];\n\n for (const signal of signals) {\n if (!signal.name) continue;\n const conv = GeminiStructuredOutputConversion.fromSignal(\n signal,\n referenceMode\n );\n properties[signal.name] = conv.schema;\n if (!signal.optional) required.push(signal.name);\n }\n\n const schema: GeminiSchema = {\n type: 'object',\n properties,\n required: required.length > 0 ? required : undefined,\n additionalProperties: false\n };\n if (description) schema.description = description;\n if (label) schema.title = label;\n\n return new GeminiStructuredOutputConversion(\n schema,\n undefined,\n undefined,\n signals,\n referenceMode\n );\n }\n\n /**\n * Restore a {@link GeminiStructuredOutputConversion} from its serialized form.\n *\n * Accepts either the JSON string produced by `JSON.stringify(conv)` or the\n * already-parsed plain object. The original `jtdSchema` or `descriptor` is\n * re-processed through the corresponding factory, so the restored instance\n * is fully functional.\n *\n * @param data - A JSON string or parsed object previously produced by {@link toJSON}.\n * @returns A fully reconstructed {@link GeminiStructuredOutputConversion}.\n * @throws If the serialized data contains neither `jtdSchema` nor `descriptor`.\n */\n static fromJSON(\n data: string | Record<string, unknown>\n ): GeminiStructuredOutputConversion {\n const parsed = typeof data === 'string' ? JSON.parse(data) : data;\n if (parsed.descriptors) {\n return GeminiStructuredOutputConversion.fromSignals(\n parsed.descriptors as SignalDescriptorJson[],\n parsed.description as string | undefined,\n parsed.label as string | undefined,\n parsed.referenceMode as boolean | undefined\n );\n }\n if (parsed.descriptor) {\n return GeminiStructuredOutputConversion.fromSignal(\n parsed.descriptor as SignalDescriptorJson\n );\n }\n if (parsed.jtdSchema) {\n return GeminiStructuredOutputConversion.fromJTD(\n parsed.jtdSchema as JTDSchemaJson\n );\n }\n throw new Error(\n 'Cannot deserialize: missing jtdSchema, descriptor, or descriptors'\n );\n }\n\n // \u2500\u2500 Constructor (private) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private constructor(\n schema: GeminiSchema,\n jtdSchema: JTDSchemaJson | undefined,\n descriptor: SignalDescriptorJson | undefined,\n descriptors?: SignalDescriptorJson[],\n referenceMode?: boolean\n ) {\n this.schema = schema;\n this.jtdSchema = jtdSchema;\n this.descriptor = descriptor;\n this.descriptors = descriptors;\n this.referenceMode = referenceMode ?? false;\n }\n\n // \u2500\u2500 Convert \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Convert raw LLM output into a validated result.\n *\n * For JTD-based conversions, the value is coerced (e.g. non-integer numbers\n * are rounded for integer types, discriminator wrapper objects are unwrapped\n * into flat tagged unions), then validated against the original JTD schema.\n * Returns {@link ConvertSuccessJson} on success.\n *\n * For MIME-based conversions, the value is expected to be a base64 string\n * which is decoded into raw bytes. Returns {@link ConvertSuccessBinary} on\n * success.\n *\n * For multi-format signals, the first non-null `format_N` property is\n * selected and converted individually.\n *\n * @param value - The value to convert. JSON for JTD, or `{ data, mimeType, filename }` for MIME.\n * @returns A {@link ConvertResult} indicating success or failure with errors.\n */\n convert = (value: unknown): T | ConvertFailure => {\n if (this.descriptors) {\n return sharedConvertSignalsOutput(\n this.descriptors,\n value,\n coerceToJTD\n ) as T | ConvertFailure;\n }\n if (this.jtdSchema) {\n let val = value;\n if (\n this.jtdSchema.discriminator &&\n this.jtdSchema.mapping &&\n typeof val === 'object' &&\n val !== null &&\n 'input' in val\n ) {\n val = (val as Record<string, unknown>).input;\n }\n return sharedConvertJTDOutput(this.jtdSchema, val, coerceToJTD) as\n | T\n | ConvertFailure;\n }\n if (this.descriptor) {\n return sharedConvertSignalOutput(this.descriptor, value, coerceToJTD) as\n | T\n | ConvertFailure;\n }\n return { success: false, errors: ['No source schema available'] };\n };\n\n // \u2500\u2500 Serialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Produce a JSON-serializable representation of this conversion.\n *\n * The output includes the Gemini `schema` and either the original\n * `jtdSchema` or `descriptor` (whichever was used to create this instance),\n * which is sufficient to fully restore the conversion via {@link fromJSON}.\n *\n * Called automatically by `JSON.stringify(conv)`.\n */\n toJSON() {\n if (this.descriptors) {\n const result: Record<string, unknown> = {\n schema: this.schema,\n descriptors: this.descriptors\n };\n if (this.schema.description) result.description = this.schema.description;\n if (this.schema.title) result.label = this.schema.title;\n if (this.referenceMode) result.referenceMode = true;\n return result;\n }\n if (this.descriptor) {\n return { schema: this.schema, descriptor: this.descriptor };\n }\n return { schema: this.schema, jtdSchema: this.jtdSchema };\n }\n}\n\n// \u2500\u2500 Signal schema builder \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildSignalSchema(descriptor: SignalDescriptorJson): GeminiSchema {\n const formats = descriptor.formats as SignalFormatJson[] | undefined;\n if (!formats || formats.length === 0) {\n throw new Error('Signal descriptor has no formats');\n }\n\n if (formats.length === 1) {\n const [fmt] = formats;\n const schema = buildFormatSchema(fmt);\n if (descriptor.label) schema.title = descriptor.label;\n if (descriptor.description) schema.description = descriptor.description;\n return schema;\n }\n\n // Multi-format: use anyOf with one variant per format\n const variants: GeminiSchema[] = [];\n for (const fmt of formats) {\n const fmtSchema = buildFormatSchema(fmt);\n if (fmt.jtdSchema) {\n fmtSchema.description = fmtSchema.description\n ? `Structured JSON data. ${fmtSchema.description}`\n : 'Structured JSON data.';\n }\n variants.push(fmtSchema);\n }\n\n const baseDesc = descriptor.description ?? '';\n const schema: GeminiSchema = {\n anyOf: variants,\n description: baseDesc || undefined\n };\n\n return schema;\n}\n\nfunction buildFormatSchema(fmt: SignalFormatJson): GeminiSchema {\n if (fmt.jtdSchema)\n return convertNode(fmt.jtdSchema, fmt.jtdSchema.definitions, 0);\n if (fmt.mimeType) return convertMimeFormat(fmt.mimeType);\n throw new Error('Signal format has neither jtdSchema nor mimeType');\n}\n\nfunction convertMimeFormat(mimeType: string): GeminiSchema {\n return {\n type: 'object',\n description: `Binary content (accepted MIME type: ${mimeType}).`,\n properties: {\n data: {\n type: 'string',\n description: 'The base64-encoded binary content.'\n },\n filename: {\n type: 'string',\n description:\n 'Filename for the content (e.g. \"report.pdf\", \"image.png\").'\n },\n mimeType: {\n type: 'string',\n description: `The concrete MIME type of the content. Must match: ${mimeType}.`\n }\n },\n required: ['data', 'mimeType', 'filename'],\n additionalProperties: false\n };\n}\n\n// \u2500\u2500 FC Signal schema builders \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildSignalSchemaFC(descriptor: SignalDescriptorJson): GeminiFCSchema {\n const formats = descriptor.formats as SignalFormatJson[] | undefined;\n if (!formats || formats.length === 0) {\n throw new Error('Signal descriptor has no formats');\n }\n\n if (formats.length === 1) {\n const [fmt] = formats;\n const schema = buildFormatSchemaFC(fmt);\n if (descriptor.label) schema.title = descriptor.label;\n if (descriptor.description) schema.description = descriptor.description;\n return schema;\n }\n\n // Multi-format: use anyOf with one variant per format\n const variants: GeminiFCSchema[] = [];\n for (const fmt of formats) {\n const fmtSchema = buildFormatSchemaFC(fmt);\n if (fmt.jtdSchema) {\n fmtSchema.description = fmtSchema.description\n ? `Structured JSON data. ${fmtSchema.description}`\n : 'Structured JSON data.';\n }\n variants.push(fmtSchema);\n }\n\n const baseDesc = descriptor.description ?? '';\n const schema: GeminiFCSchema = {\n anyOf: variants,\n description: baseDesc || undefined\n };\n\n return schema;\n}\n\nfunction buildFormatSchemaFC(fmt: SignalFormatJson): GeminiFCSchema {\n if (fmt.jtdSchema)\n return convertNodeFC(fmt.jtdSchema, fmt.jtdSchema.definitions, 0);\n if (fmt.mimeType) return convertMimeFormatFC(fmt.mimeType);\n throw new Error('Signal format has neither jtdSchema nor mimeType');\n}\n\nfunction convertMimeFormatFC(mimeType: string): GeminiFCSchema {\n return {\n type: 'object',\n description: `Binary content (accepted MIME type: ${mimeType}).`,\n properties: {\n data: {\n type: 'string',\n description: 'The base64-encoded binary content.'\n },\n filename: {\n type: 'string',\n description:\n 'Filename for the content (e.g. \"report.pdf\", \"image.png\").'\n },\n mimeType: {\n type: 'string',\n description: `The concrete MIME type of the content. Must match: ${mimeType}.`\n }\n },\n required: ['data', 'mimeType', 'filename']\n };\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// GeminiFunctionCallingConversion\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * Converts JTD schemas or Adapt signal descriptors into Gemini v1beta Schema\n * protobuf-compatible schemas for use with function calling `parameters`.\n *\n * ### Differences from {@link GeminiStructuredOutputConversion}\n * - Uses `nullable: true` instead of `[\"type\", \"null\"]` type arrays\n * - Does NOT emit `additionalProperties`\n * - Uses `anyOf` for discriminator unions instead of wrapper objects\n * - Uses key-value array pattern for `values` form (maps/dictionaries)\n * - Unstructured/empty schemas are encoded as JSON strings\n *\n * ### JTD form mapping\n * - **type** -- mapped to the corresponding JSON Schema `type` with\n * `minimum`/`maximum` for integers, `format: \"date-time\"` for timestamps.\n * - **enum** -- mapped to `type: \"string\"` with an `enum` array.\n * - **elements** -- mapped to `type: \"array\"` with `items`.\n * - **properties / optionalProperties** -- mapped to `type: \"object\"` with\n * - **values** -- mapped to `type: \"array\"` with items of\n * `{key: string, value: ...}` objects (key-value array pattern).\n * - **discriminator** -- mapped to `anyOf` with single-value `enum` for the\n * discriminator tag on each variant.\n * - **empty / unstructured** -- mapped to `type: \"string\"` with a description\n * requesting JSON-encoded content.\n *\n * ### MIME formats\n * MIME signal formats become `type: \"object\"` with `data` and `filename`\n * properties (same as structured output). On {@link convert}, the base64\n * string is decoded into a {@link ConvertSuccessBinary} result.\n *\n * ### Serialization\n * `JSON.stringify(conv)` produces a JSON-safe object via {@link toJSON}.\n * The serialized form includes `mode: 'fc'` to distinguish it from\n * {@link GeminiStructuredOutputConversion} during deserialization.\n *\n * ```ts\n * const conv = GeminiFunctionCallingConversion.fromJTD(jtdSchema);\n * conv.schema; // FC-compatible schema for FunctionDeclaration.parameters\n * conv.convert(output); // coerce + validate -> ConvertResult\n *\n * // Serialize & restore\n * const cached = JSON.stringify(conv);\n * const restored = GeminiFunctionCallingConversion.fromJSON(cached);\n * ```\n */\nexport class GeminiFunctionCallingConversion<\n T extends ConvertSuccess = ConvertSuccess\n> {\n /** The FC-specific schema to pass as `parameters` in a `FunctionDeclaration`. */\n readonly schema: GeminiFCSchema;\n /**\n * The original JTD schema used to build this conversion.\n * Set when created via {@link fromJTD}; `undefined` when created via {@link fromSignal}.\n */\n readonly jtdSchema?: JTDSchemaJson;\n /**\n * The original signal descriptor used to build this conversion.\n * Set when created via {@link fromSignal}; `undefined` when created via {@link fromJTD}.\n */\n readonly descriptor?: SignalDescriptorJson;\n /**\n * The original signal descriptors used to build this conversion.\n * Set when created via {@link fromSignals}; `undefined` otherwise.\n */\n readonly descriptors?: SignalDescriptorJson[];\n /** When true, MIME signals use file-reference schemas instead of base64. */\n readonly referenceMode: boolean;\n\n // \u2500\u2500 Factories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Build a conversion from a JTD schema.\n *\n * Recursively converts every JTD form into the equivalent Gemini FC schema\n * node. Definitions (`jtdSchema.definitions`) are resolved inline because\n * the FC protobuf schema does not support `$ref`.\n *\n * @param jtdSchema - A valid JTD schema (RFC 8927).\n * @returns A new {@link GeminiFunctionCallingConversion} with `jtdSchema` set.\n * @throws If the schema contains an unknown JTD type, an unresolved `ref`,\n * or circular / excessively deep refs (exceeding 32 levels).\n */\n static fromJTD(\n jtdSchema: JTDSchemaJson\n ): GeminiFunctionCallingConversion<ConvertSuccessJson> {\n let schema = convertNodeFC(jtdSchema, jtdSchema.definitions, 0);\n\n // Wrap root discriminated union \u2014 providers require type:\"object\" at\n // the function parameter root; bare anyOf is rejected.\n if (jtdSchema.discriminator && jtdSchema.mapping) {\n schema = {\n type: 'object',\n properties: { input: schema },\n required: ['input']\n };\n }\n\n return new GeminiFunctionCallingConversion(schema, jtdSchema, undefined);\n }\n\n /**\n * Build a conversion from an Adapt signal descriptor.\n *\n * If the descriptor has a single format, its schema is used directly\n * (unwrapped). If it has multiple formats, a wrapper object schema is\n * produced with `format_0`, `format_1`, ... properties -- the LLM must\n * populate exactly one. MIME formats become base64 string schemas.\n *\n * @param descriptor - An Adapt signal descriptor with one or more formats.\n * @returns A new {@link GeminiFunctionCallingConversion} with `descriptor` set.\n * @throws If the descriptor has no formats, or a format has neither\n * `jtdSchema` nor `mimeType`.\n */\n static fromSignal(\n descriptor: SignalDescriptorJson,\n referenceMode?: boolean\n ): GeminiFunctionCallingConversion<\n ConvertSuccessJson | ConvertSuccessBinary\n > {\n const schema = buildSignalSchemaFC(descriptor);\n\n if (referenceMode) {\n const refSchema = buildReferenceAnyOf(\n schema,\n descriptor.description,\n false\n ) as GeminiFCSchema;\n return new GeminiFunctionCallingConversion(\n refSchema,\n undefined,\n descriptor\n );\n }\n\n return new GeminiFunctionCallingConversion(schema, undefined, descriptor);\n }\n\n /**\n * Build a conversion from multiple signal descriptors.\n *\n * Produces an object schema with one property per signal, suitable for\n * use as function calling parameters. Each signal's schema is built via\n * {@link fromSignal} internally.\n *\n * Optional signals are omitted from `required`.\n *\n * Receiver/transceiver compatible:\n * ```ts\n * const conv = GeminiFunctionCallingConversion.fromSignals(\n * receiver.signals!,\n * receiver.description,\n * receiver.name\n * );\n * ```\n *\n * @param signals - Array of signal descriptors.\n * @param description - Optional description for the compound schema.\n * @param label - Optional label (mapped to `title` on the schema).\n * @returns A new {@link GeminiFunctionCallingConversion} with `descriptors` set.\n * @throws If any signal has no formats or an invalid format.\n */\n static fromSignals(\n signals: SignalDescriptorJson[],\n description?: string,\n label?: string,\n referenceMode?: boolean\n ): GeminiFunctionCallingConversion<ConvertSuccessSignals> {\n const properties: Record<string, GeminiFCSchema> = {};\n const required: string[] = [];\n\n for (const signal of signals) {\n if (!signal.name) continue;\n const conv = GeminiFunctionCallingConversion.fromSignal(\n signal,\n referenceMode\n );\n properties[signal.name] = conv.schema;\n if (!signal.optional) required.push(signal.name);\n }\n\n const schema: GeminiFCSchema = {\n type: 'object',\n properties,\n required: required.length > 0 ? required : undefined\n };\n if (description) schema.description = description;\n if (label) schema.title = label;\n\n return new GeminiFunctionCallingConversion(\n schema,\n undefined,\n undefined,\n signals,\n referenceMode\n );\n }\n\n /**\n * Restore a {@link GeminiFunctionCallingConversion} from its serialized form.\n *\n * Accepts either the JSON string produced by `JSON.stringify(conv)` or the\n * already-parsed plain object. The original `jtdSchema` or `descriptor` is\n * re-processed through the corresponding factory, so the restored instance\n * is fully functional.\n *\n * @param data - A JSON string or parsed object previously produced by {@link toJSON}.\n * @returns A fully reconstructed {@link GeminiFunctionCallingConversion}.\n * @throws If the serialized data contains neither `jtdSchema` nor `descriptor`,\n * or if `mode` is not `'fc'`.\n */\n static fromJSON(\n data: string | Record<string, unknown>\n ): GeminiFunctionCallingConversion {\n const parsed = typeof data === 'string' ? JSON.parse(data) : data;\n if (parsed.mode !== 'fc') {\n throw new Error(\n 'Cannot deserialize as GeminiFunctionCallingConversion: mode is not \"fc\"'\n );\n }\n if (parsed.descriptors) {\n return GeminiFunctionCallingConversion.fromSignals(\n parsed.descriptors as SignalDescriptorJson[],\n parsed.description as string | undefined,\n parsed.label as string | undefined,\n parsed.referenceMode as boolean | undefined\n );\n }\n if (parsed.descriptor) {\n return GeminiFunctionCallingConversion.fromSignal(\n parsed.descriptor as SignalDescriptorJson\n );\n }\n if (parsed.jtdSchema) {\n return GeminiFunctionCallingConversion.fromJTD(\n parsed.jtdSchema as JTDSchemaJson\n );\n }\n throw new Error(\n 'Cannot deserialize: missing jtdSchema, descriptor, or descriptors'\n );\n }\n\n // \u2500\u2500 Constructor (private) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private constructor(\n schema: GeminiFCSchema,\n jtdSchema: JTDSchemaJson | undefined,\n descriptor: SignalDescriptorJson | undefined,\n descriptors?: SignalDescriptorJson[],\n referenceMode?: boolean\n ) {\n this.schema = schema;\n this.jtdSchema = jtdSchema;\n this.descriptor = descriptor;\n this.descriptors = descriptors;\n this.referenceMode = referenceMode ?? false;\n }\n\n // \u2500\u2500 Convert \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Convert raw LLM output into a validated result.\n *\n * For JTD-based conversions, the value is coerced to conform to the original\n * JTD schema:\n * - Non-integer numbers are rounded for integer types.\n * - `{key, value}` arrays from the values form are reassembled into Records.\n * - Unstructured/empty schemas parse the raw JSON string via `JSON.parse`.\n * - Discriminator variants are coerced from flat objects.\n *\n * The coerced value is then validated against the JTD schema. Returns\n * {@link ConvertSuccessJson} on success.\n *\n * For MIME-based conversions, the value is expected to be a base64 string\n * which is decoded into raw bytes. Returns {@link ConvertSuccessBinary} on\n * success.\n *\n * For multi-format signals, the first non-null `format_N` property is\n * selected and converted individually.\n *\n * @param value - The value to convert. JSON for JTD, or `{ data, mimeType, filename }` for MIME.\n * @returns A {@link ConvertResult} indicating success or failure with errors.\n */\n convert = (value: unknown): T | ConvertFailure => {\n if (this.descriptors) {\n return sharedConvertSignalsOutput(\n this.descriptors,\n value,\n coerceToJTDFC\n ) as T | ConvertFailure;\n }\n if (this.jtdSchema) {\n let val = value;\n if (\n this.jtdSchema.discriminator &&\n this.jtdSchema.mapping &&\n typeof val === 'object' &&\n val !== null &&\n 'input' in val\n ) {\n val = (val as Record<string, unknown>).input;\n }\n return sharedConvertJTDOutput(this.jtdSchema, val, coerceToJTDFC) as\n | T\n | ConvertFailure;\n }\n if (this.descriptor) {\n return sharedConvertSignalOutput(\n this.descriptor,\n value,\n coerceToJTDFC\n ) as T | ConvertFailure;\n }\n return { success: false, errors: ['No source schema available'] };\n };\n\n // \u2500\u2500 Serialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Produce a JSON-serializable representation of this conversion.\n *\n * The output includes the FC `schema`, `mode: 'fc'`, and either the\n * original `jtdSchema` or `descriptor` (whichever was used to create this\n * instance), which is sufficient to fully restore the conversion via\n * {@link fromJSON}.\n *\n * Called automatically by `JSON.stringify(conv)`.\n */\n toJSON() {\n if (this.descriptors) {\n const result: Record<string, unknown> = {\n mode: 'fc',\n schema: this.schema,\n descriptors: this.descriptors\n };\n if (this.schema.description) result.description = this.schema.description;\n if (this.schema.title) result.label = this.schema.title;\n if (this.referenceMode) result.referenceMode = true;\n return result;\n }\n if (this.descriptor) {\n return { mode: 'fc', schema: this.schema, descriptor: this.descriptor };\n }\n return { mode: 'fc', schema: this.schema, jtdSchema: this.jtdSchema };\n }\n}\n\n// \u2500\u2500 Backward-compat alias \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** @deprecated Use {@link GeminiStructuredOutputConversion} instead. */\nexport { GeminiStructuredOutputConversion as GeminiConversion };\n\n// \u2500\u2500 Convenience aliases \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Convenience alias for {@link GeminiStructuredOutputConversion.fromJTD}.\n *\n * @param jtdSchema - A valid JTD schema (RFC 8927).\n * @returns A new {@link GeminiStructuredOutputConversion}.\n */\nexport function convertToGeminiSchema(\n jtdSchema: JTDSchemaJson\n): GeminiStructuredOutputConversion<ConvertSuccessJson> {\n return GeminiStructuredOutputConversion.fromJTD(jtdSchema);\n}\n\n/**\n * Convenience alias for {@link GeminiStructuredOutputConversion.fromSignal}.\n *\n * @param descriptor - An Adapt signal descriptor.\n * @returns A new {@link GeminiStructuredOutputConversion}.\n */\nexport function convertSignalToGeminiSchema(\n descriptor: SignalDescriptorJson\n): GeminiStructuredOutputConversion<ConvertSuccessJson | ConvertSuccessBinary> {\n return GeminiStructuredOutputConversion.fromSignal(descriptor);\n}\n\n/**\n * Convenience alias for {@link GeminiFunctionCallingConversion.fromJTD}.\n *\n * @param jtdSchema - A valid JTD schema (RFC 8927).\n * @returns A new {@link GeminiFunctionCallingConversion}.\n */\nexport function convertToGeminiFCSchema(\n jtdSchema: JTDSchemaJson\n): GeminiFunctionCallingConversion<ConvertSuccessJson> {\n return GeminiFunctionCallingConversion.fromJTD(jtdSchema);\n}\n"],
4
+ "sourcesContent": ["import type {\n JTDSchemaJson,\n SignalDescriptorJson,\n SignalFormatJson\n} from '../api';\nimport { isSchema, isValidSchema, validate as jtdValidate } from 'jtd';\nimport type { Schema, ValidationError } from 'jtd';\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD FUZZER (generate sample values from JTD schemas)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/** Maximum recursion depth for fuzzJTD to prevent infinite loops on self-referencing schemas. */\nconst FUZZ_MAX_DEPTH = 10;\n\n/** Generate a fuzzed (sample) value from a JTD schema. */\nexport function fuzzJTD(\n schema: JTDSchemaJson,\n definitions?: Record<string, JTDSchemaJson>,\n _depth: number = 0\n): unknown {\n if (_depth > FUZZ_MAX_DEPTH) return null;\n\n if (schema.ref) {\n const def = definitions?.[schema.ref];\n if (def) return fuzzJTD(def, definitions, _depth + 1);\n return null;\n }\n\n if (schema.type) {\n switch (schema.type) {\n case 'string':\n return 'Lorem ipsum';\n case 'timestamp':\n return '2024-01-15T10:30:00Z';\n case 'float32':\n case 'float64':\n return 42.5;\n case 'int8':\n case 'uint8':\n case 'int16':\n case 'uint16':\n case 'int32':\n case 'uint32':\n return 42;\n case 'boolean':\n return true;\n default:\n return null;\n }\n }\n\n if (schema.enum) {\n return schema.enum[0] ?? null;\n }\n\n if (schema.elements) {\n return [\n fuzzJTD(schema.elements, definitions, _depth + 1),\n fuzzJTD(schema.elements, definitions, _depth + 1)\n ];\n }\n\n if (schema.values) {\n return { key1: fuzzJTD(schema.values, definitions, _depth + 1) };\n }\n\n if (schema.properties || schema.optionalProperties) {\n const obj: Record<string, unknown> = {};\n if (schema.properties) {\n for (const [k, v] of Object.entries(schema.properties)) {\n obj[k] = fuzzJTD(v, definitions, _depth + 1);\n }\n }\n if (schema.optionalProperties) {\n for (const [k, v] of Object.entries(schema.optionalProperties)) {\n obj[k] = fuzzJTD(v, definitions, _depth + 1);\n }\n }\n return obj;\n }\n\n if (schema.discriminator && schema.mapping) {\n const firstKey = Object.keys(schema.mapping)[0];\n if (firstKey) {\n const variant = schema.mapping[firstKey]!;\n const obj = fuzzJTD(variant, definitions, _depth + 1) as Record<\n string,\n unknown\n >;\n obj[schema.discriminator] = firstKey;\n return obj;\n }\n return null;\n }\n\n // Empty schema\n return null;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD VALUE VALIDATION (delegates to the `jtd` npm package)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst INTEGER_TYPES = new Set([\n 'int8',\n 'uint8',\n 'int16',\n 'uint16',\n 'int32',\n 'uint32'\n]);\n\nfunction formatInstancePath(instancePath: string[]): string {\n if (instancePath.length === 0) return 'root';\n const joined = instancePath\n .map((p, i) => (/^\\d+$/.test(p) ? `[${p}]` : i === 0 ? p : `.${p}`))\n .join('');\n return `'${joined}'`;\n}\n\nfunction resolveSchemaParent(\n schema: Record<string, unknown>,\n schemaPath: string[]\n): Record<string, unknown> {\n let current: any = schema;\n for (let i = 0; i < schemaPath.length - 1; i++) {\n if (current && typeof current === 'object' && schemaPath[i]! in current) {\n current = current[schemaPath[i]!];\n } else {\n break;\n }\n }\n return current as Record<string, unknown>;\n}\n\nfunction resolveInstanceValue(value: unknown, instancePath: string[]): unknown {\n let current = value;\n for (const key of instancePath) {\n if (current && typeof current === 'object') {\n current = (current as Record<string, unknown>)[key];\n } else {\n return undefined;\n }\n }\n return current;\n}\n\nfunction formatValidationError(\n error: ValidationError,\n schema: Record<string, unknown>,\n value: unknown\n): string {\n const { instancePath, schemaPath } = error;\n const path = formatInstancePath(instancePath);\n const lastKey = schemaPath[schemaPath.length - 1];\n const paramLabel = path === 'root' ? 'root value' : `parameter ${path}`;\n\n if (lastKey === 'type') {\n const parent = resolveSchemaParent(schema, schemaPath);\n const jtdType = parent?.type as string;\n if (jtdType === 'boolean') return `${paramLabel}: expected boolean`;\n if (jtdType === 'string') return `${paramLabel}: expected string`;\n if (jtdType === 'timestamp')\n return `${paramLabel}: expected timestamp string`;\n if (INTEGER_TYPES.has(jtdType)) return `${paramLabel}: expected integer`;\n return `${paramLabel}: expected number`;\n }\n\n if (lastKey === 'enum') {\n const parent = resolveSchemaParent(schema, schemaPath);\n const enumValues = parent?.enum as string[];\n return `${paramLabel}: expected one of [${enumValues.join(', ')}]`;\n }\n\n if (\n schemaPath.length >= 2 &&\n schemaPath[schemaPath.length - 2] === 'properties' &&\n lastKey\n ) {\n if (path === 'root') {\n return `missing required parameter '${lastKey}'`;\n }\n return `missing required parameter ${path}.${lastKey}`;\n }\n\n if (lastKey === 'elements') return `${paramLabel}: expected array`;\n\n if (\n lastKey === 'properties' ||\n lastKey === 'optionalProperties' ||\n lastKey === 'values'\n ) {\n return `${paramLabel}: expected object`;\n }\n\n if (lastKey === 'discriminator') {\n // Find the discriminator field name from the schema\n const parent = resolveSchemaParent(schema, schemaPath);\n const discField = parent?.discriminator as string | undefined;\n if (discField) {\n return `${paramLabel}: parameter '${discField}' must be a string`;\n }\n return `${paramLabel}: expected string discriminator`;\n }\n\n if (lastKey === 'mapping') {\n const discValue = resolveInstanceValue(value, instancePath);\n // Try to list valid variants from the mapping\n const parent = resolveSchemaParent(schema, schemaPath);\n const mapping = parent?.mapping as Record<string, unknown> | undefined;\n const validVariants = mapping ? Object.keys(mapping) : [];\n if (validVariants.length > 0) {\n return `${paramLabel}: unknown variant \"${discValue}\". Valid values: [${validVariants.join(', ')}]`;\n }\n return `${paramLabel}: unknown variant \"${discValue}\"`;\n }\n\n if (schemaPath.length === 0) return `${paramLabel}: unexpected property`;\n\n return `${paramLabel}: validation error at /${schemaPath.join('/')}`;\n}\n\n/**\n * Validate a value against a JTD schema, returning an array of error messages.\n * Delegates to the `jtd` npm package for spec-compliant validation.\n */\nexport function validateJsonAgainstJTD(\n schema: JTDSchemaJson,\n value: unknown,\n definitions?: Record<string, JTDSchemaJson>\n): string[] {\n const defs =\n definitions ??\n (schema.definitions as Record<string, JTDSchemaJson> | undefined);\n\n const schemaForValidation = defs\n ? ({ ...schema, definitions: defs } as Schema)\n : (schema as Schema);\n\n // The jtd lib throws on invalid schemas (e.g., unresolved refs).\n if (!isValidSchema(schemaForValidation)) {\n if (schema.ref) {\n return [`unknown ref \"${schema.ref}\" in schema`];\n }\n return [`invalid schema`];\n }\n\n const errors = jtdValidate(schemaForValidation, value, {\n maxDepth: MAX_VALIDATION_DEPTH,\n maxErrors: MAX_VALIDATION_ERRORS\n });\n\n return errors.map((err) =>\n formatValidationError(\n err,\n schemaForValidation as Record<string, unknown>,\n value\n )\n );\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// SIGNAL-LEVEL VALIDATION (JSON values against JTD formats)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/** Validate a JSON value against a single SignalFormatJson. */\nexport function validateJsonAgainstSignal(\n format: SignalFormatJson,\n jsonValue: unknown\n): string[];\n/** Validate a JSON value against a SignalDescriptorJson (valid if value matches ANY JTD format). */\nexport function validateJsonAgainstSignal(\n descriptor: SignalDescriptorJson,\n jsonValue: unknown\n): string[];\nexport function validateJsonAgainstSignal(\n formatOrDescriptor: SignalFormatJson | SignalDescriptorJson,\n jsonValue: unknown\n): string[] {\n // Descriptor path \u2014 has `formats` array\n if (\n 'formats' in formatOrDescriptor &&\n Array.isArray(formatOrDescriptor.formats)\n ) {\n const jtdFormats = (\n formatOrDescriptor.formats as SignalFormatJson[]\n ).filter((f) => f.jtdSchema);\n if (jtdFormats.length === 0) {\n return ['no JTD formats in signal descriptor'];\n }\n // Valid if value matches ANY JTD format\n for (const format of jtdFormats) {\n const errors = validateJsonAgainstJTD(format.jtdSchema!, jsonValue);\n if (errors.length === 0) return [];\n }\n // None matched \u2014 return errors from last format attempt\n return validateJsonAgainstJTD(\n jtdFormats[jtdFormats.length - 1]!.jtdSchema!,\n jsonValue\n );\n }\n\n // Single format path\n const format = formatOrDescriptor as SignalFormatJson;\n if (!format.jtdSchema) {\n return ['signal format has no jtdSchema'];\n }\n return validateJsonAgainstJTD(format.jtdSchema, jsonValue);\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD \u2192 JSON SCHEMA CONVERTER\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * Convert JTD (JSON Type Definition) schema to JSON Schema.\n *\n * Supports all 8 JTD schema forms per RFC 8927:\n * - Empty form: {}\n * - Ref form: { ref: \"...\" }\n * - Type form: { type: \"...\" }\n * - Enum form: { enum: [...] }\n * - Elements form: { elements: {...} }\n * - Properties form: { properties: {...}, optionalProperties: {...} }\n * - Values form: { values: {...} }\n * - Discriminator form: { discriminator: \"...\", mapping: {...} }\n *\n * JTD `metadata.description` is preserved as JSON Schema `description`.\n * Other metadata fields are appended to the description for context.\n *\n * Top-level `definitions` are emitted as JSON Schema `$defs` and\n * referenced via `$ref` (not inlined), keeping large schemas compact.\n */\nexport function jtdToJsonSchema(\n jtdSchema: JTDSchemaJson,\n definitions?: Record<string, JTDSchemaJson>,\n isRoot = true\n): any {\n if (!jtdSchema || typeof jtdSchema !== 'object') {\n return {};\n }\n\n const isNullable = jtdSchema['nullable'];\n const defs =\n definitions ||\n (jtdSchema['definitions'] as Record<string, JTDSchemaJson> | undefined);\n\n const result: any = {};\n\n if (isRoot) {\n result.$schema = 'http://json-schema.org/draft-07/schema#';\n\n // Emit top-level definitions as $defs (only at root)\n if (defs && Object.keys(defs).length > 0) {\n result.$defs = {};\n for (const [name, defSchema] of Object.entries(defs)) {\n result.$defs[name] = convertNode(defSchema as JTDSchemaJson, defs);\n }\n }\n }\n\n // Build the schema node \u2014 delegate to convertNode for the actual form logic\n const node = convertNode(jtdSchema, defs);\n Object.assign(result, node);\n\n return result;\n}\n\n/** Convert a single JTD node to JSON Schema. Internal recursive worker. */\nfunction convertNode(\n jtdSchema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined\n): any {\n if (!jtdSchema || typeof jtdSchema !== 'object') {\n return {};\n }\n\n const isNullable = jtdSchema['nullable'];\n const result: any = {};\n\n // Extract metadata.description \u2192 JSON Schema description\n applyMetadata(result, jtdSchema);\n\n // \u2500\u2500 Ref form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['ref']) {\n const refName = jtdSchema['ref'] as string;\n if (defs && defs[refName]) {\n const refResult: any = { $ref: `#/$defs/${refName}` };\n applyMetadata(refResult, jtdSchema);\n\n if (isNullable) {\n return {\n anyOf: [refResult, { type: 'null' }],\n ...(refResult.description\n ? { description: refResult.description }\n : {})\n };\n }\n return refResult;\n }\n return {};\n }\n\n // \u2500\u2500 Type form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['type']) {\n const typeMap: Record<string, string> = {\n boolean: 'boolean',\n string: 'string',\n timestamp: 'string',\n float32: 'number',\n float64: 'number',\n int8: 'integer',\n uint8: 'integer',\n int16: 'integer',\n uint16: 'integer',\n int32: 'integer',\n uint32: 'integer'\n };\n const jtdType = jtdSchema['type'] as string;\n const baseType = typeMap[jtdType] || 'string';\n\n result.type = isNullable ? [baseType, 'null'] : baseType;\n\n if (baseType === 'string' && jtdType === 'timestamp') {\n result.format = 'date-time';\n }\n return result;\n }\n\n // \u2500\u2500 Enum form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['enum']) {\n result.type = isNullable ? ['string', 'null'] : 'string';\n result.enum = isNullable\n ? [...(jtdSchema['enum'] as string[]), null]\n : jtdSchema['enum'];\n return result;\n }\n\n // \u2500\u2500 Elements form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['elements']) {\n result.type = isNullable ? ['array', 'null'] : 'array';\n result.items = convertNode(jtdSchema['elements'] as JTDSchemaJson, defs);\n return result;\n }\n\n // \u2500\u2500 Properties form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['properties'] || jtdSchema['optionalProperties']) {\n result.type = isNullable ? ['object', 'null'] : 'object';\n result.properties = {};\n result.required = [];\n\n if (jtdSchema['properties']) {\n for (const [key, value] of Object.entries(jtdSchema['properties'])) {\n result.properties[key] = convertNode(value as JTDSchemaJson, defs);\n result.required.push(key);\n }\n }\n\n if (jtdSchema['optionalProperties']) {\n for (const [key, value] of Object.entries(\n jtdSchema['optionalProperties']\n )) {\n result.properties[key] = convertNode(value as JTDSchemaJson, defs);\n }\n }\n\n // JTD is strict by default\n if (jtdSchema['additionalProperties'] !== true) {\n result.additionalProperties = false;\n }\n\n return result;\n }\n\n // \u2500\u2500 Values form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['values']) {\n result.type = isNullable ? ['object', 'null'] : 'object';\n result.additionalProperties = convertNode(\n jtdSchema['values'] as JTDSchemaJson,\n defs\n );\n return result;\n }\n\n // \u2500\u2500 Discriminator form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (jtdSchema['discriminator'] && jtdSchema['mapping']) {\n const variants = [];\n const discriminatorKey = jtdSchema['discriminator'] as string;\n\n for (const [tag, schema] of Object.entries(jtdSchema['mapping'])) {\n const variantSchema = convertNode(schema as JTDSchemaJson, defs);\n\n if (!variantSchema.type) {\n variantSchema.type = 'object';\n } else if (\n variantSchema.type !== 'object' &&\n !Array.isArray(variantSchema.type)\n ) {\n variantSchema.type = 'object';\n }\n\n variantSchema.properties = variantSchema.properties || {};\n variantSchema.properties[discriminatorKey] = { const: tag };\n\n variantSchema.required = variantSchema.required || [];\n if (!variantSchema.required.includes(discriminatorKey)) {\n variantSchema.required.unshift(discriminatorKey);\n }\n\n // JTD discriminator variants are strict by default\n if (variantSchema.additionalProperties === undefined) {\n variantSchema.additionalProperties = false;\n }\n\n variants.push(variantSchema);\n }\n\n if (isNullable) {\n variants.push({ type: 'null' });\n }\n\n result.oneOf = variants;\n return result;\n }\n\n // \u2500\u2500 Empty form \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n return result;\n}\n\n/**\n * Extract JTD metadata and apply as JSON Schema `description`.\n *\n * - `metadata.description` becomes `description` directly.\n * - Other metadata fields are appended as \"key: value\" lines.\n */\nfunction applyMetadata(result: any, jtdSchema: JTDSchemaJson): void {\n const metadata = jtdSchema['metadata'] as Record<string, unknown> | undefined;\n if (!metadata || typeof metadata !== 'object') return;\n\n const parts: string[] = [];\n const desc = metadata['description'];\n if (typeof desc === 'string' && desc) {\n parts.push(desc);\n }\n\n // Fold other metadata fields into description\n for (const [key, value] of Object.entries(metadata)) {\n if (key === 'description') continue;\n if (value === undefined || value === null) continue;\n const str = typeof value === 'string' ? value : JSON.stringify(value);\n parts.push(`${key}: ${str}`);\n }\n\n if (parts.length > 0) {\n result.description = parts.join('\\n');\n }\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD SCHEMA VALIDATION (using the `jtd` npm package)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport const MAX_VALIDATION_DEPTH = 32;\nexport const MAX_VALIDATION_ERRORS = 100;\n\n/** Validate that the input is a valid JTD schema. */\nexport function validateJTDSchema(\n schema: unknown\n): { valid: true; schema: JTDSchemaJson } | { valid: false; error: string } {\n if (!isSchema(schema)) {\n return {\n valid: false,\n error: 'Invalid JTD schema: Schema does not match JTD structure'\n };\n }\n\n if (!isValidSchema(schema)) {\n return {\n valid: false,\n error:\n 'Invalid JTD schema: Schema has semantic errors (e.g., circular references, conflicting forms)'\n };\n }\n\n return { valid: true, schema: schema as unknown as JTDSchemaJson };\n}\n\n/** Parse and validate a JSON string. */\nexport function validateJSON(jsonString: string): {\n valid: boolean;\n data?: unknown;\n error?: string;\n} {\n try {\n const data = JSON.parse(jsonString);\n return { valid: true, data };\n } catch (error) {\n return {\n valid: false,\n error: `Invalid JSON: ${error instanceof Error ? error.message : 'Parse error'}`\n };\n }\n}\n\n/** Parse a JSON string and validate it as a JTD schema. */\nexport function validateJTDSchemaString(\n schemaString: string\n): { valid: true; schema: JTDSchemaJson } | { valid: false; error: string } {\n const jsonResult = validateJSON(schemaString);\n if (!jsonResult.valid) return { valid: false, error: jsonResult.error! };\n\n return validateJTDSchema(jsonResult.data);\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// MONACO EDITOR CONFIG FOR JTD SCHEMA EDITING\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/** Monaco editor JSON schema configuration for JTD schema IntelliSense and validation. */\nexport const JTD_SCHEMA_CONFIG = {\n uri: 'https://jsontypedef.com/schemas/jtd-schema.json',\n fileMatch: ['inmemory://jtd-schema/*.json'],\n schema: {\n $schema: 'http://json-schema.org/draft-07/schema#',\n type: 'object',\n properties: {\n type: {\n description: 'Type form - defines a primitive type',\n enum: [\n 'boolean',\n 'string',\n 'timestamp',\n 'float32',\n 'float64',\n 'int8',\n 'uint8',\n 'int16',\n 'uint16',\n 'int32',\n 'uint32'\n ]\n },\n enum: {\n description: 'Enum form - defines a set of string values',\n type: 'array',\n items: { type: 'string' }\n },\n elements: {\n description: 'Elements form - defines an array with typed elements',\n $ref: '#'\n },\n properties: {\n description:\n 'Properties form - defines an object with required properties',\n type: 'object',\n additionalProperties: { $ref: '#' }\n },\n optionalProperties: {\n description: 'Optional properties - defines optional object properties',\n type: 'object',\n additionalProperties: { $ref: '#' }\n },\n values: {\n description: 'Values form - defines a map/dictionary with typed values',\n $ref: '#'\n },\n discriminator: {\n description: 'Discriminator form - defines a tagged union',\n type: 'string'\n },\n mapping: {\n description: 'Mapping for discriminator form',\n type: 'object',\n additionalProperties: { $ref: '#' }\n },\n ref: {\n description: 'Ref form - references a definition',\n type: 'string'\n },\n definitions: {\n description: 'Top-level definitions for reusable schemas',\n type: 'object',\n additionalProperties: { $ref: '#' }\n },\n nullable: {\n description: 'Allow null values',\n type: 'boolean'\n },\n metadata: {\n description: 'Custom metadata for documentation',\n type: 'object'\n },\n additionalProperties: {\n description: 'Allow additional properties (properties form only)',\n type: 'boolean'\n }\n }\n }\n};\n", "/**\n * Shared utilities for JSON Schema providers (OpenAI, Anthropic, Gemini).\n *\n * Contains constants, types, convert output pipeline, definition pooling,\n * and schema inspection helpers used identically across providers.\n */\nimport type {\n JTDSchemaJson,\n SignalDescriptorJson,\n SignalFormatJson\n} from './api';\nimport { validateJsonAgainstJTD } from './signals';\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Constants\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport const MAX_REF_DEPTH = 32;\n\nexport const INTEGER_BOUNDS: Record<\n string,\n { minimum: number; maximum: number }\n> = {\n int8: { minimum: -128, maximum: 127 },\n uint8: { minimum: 0, maximum: 255 },\n int16: { minimum: -32768, maximum: 32767 },\n uint16: { minimum: 0, maximum: 65535 },\n int32: { minimum: -2147483648, maximum: 2147483647 },\n uint32: { minimum: 0, maximum: 4294967295 }\n};\n\nexport const FLOAT32_BOUNDS = { minimum: -3.4028235e38, maximum: 3.4028235e38 };\n\nexport const UNSTRUCTURED_DESC =\n 'Must be a valid JSON value encoded as a string. ' +\n \"For objects: '{\\\"key\\\": \\\"value\\\"}', for arrays: '[1, 2]', for primitives: '\\\"hello\\\"' or '42' or 'true' or 'null'.\";\n\nexport const UNSTRUCTURED_VARIANT_DESC =\n 'The variant data encoded as a valid JSON string ' +\n '(e.g. \\'{\"key\": \"value\"}\\').';\n\nexport const VALUES_ARRAY_DESC =\n 'Array of key-value entries representing a map/dictionary. ' +\n \"Each entry has a string 'key' (the property name) and a 'value' (the property value).\";\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Primitives\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport function nullableType(\n baseType: string,\n nullable: boolean\n): string | string[] {\n return nullable ? [baseType, 'null'] : baseType;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Deep equal\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null || typeof a !== typeof b) return false;\n if (typeof a !== 'object') return false;\n if (Array.isArray(a))\n return (\n Array.isArray(b) &&\n a.length === b.length &&\n a.every((v, i) => deepEqual(v, b[i]))\n );\n if (Array.isArray(b)) return false;\n const ao = a as Record<string, unknown>,\n bo = b as Record<string, unknown>;\n const keys = Object.keys(ao);\n if (keys.length !== Object.keys(bo).length) return false;\n for (const k of keys) {\n if (!(k in bo) || !deepEqual(ao[k], bo[k])) return false;\n }\n return true;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD schema inspection (OpenAI + Anthropic)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport function isUnstructuredSchema(schema: JTDSchemaJson): boolean {\n if (!schema || typeof schema !== 'object') return true;\n\n if (\n schema.type ||\n schema.enum ||\n schema.elements ||\n schema.values ||\n schema.discriminator ||\n schema.ref\n ) {\n return false;\n }\n\n const hasRealProps =\n (schema.properties && Object.keys(schema.properties).length > 0) ||\n (schema.optionalProperties &&\n Object.keys(schema.optionalProperties).length > 0);\n if (hasRealProps) return false;\n\n if (\n (schema.properties !== undefined ||\n schema.optionalProperties !== undefined) &&\n schema.additionalProperties === true\n ) {\n return true;\n }\n\n if (\n schema.properties !== undefined ||\n schema.optionalProperties !== undefined\n ) {\n return false;\n }\n\n return true;\n}\n\nexport function isRootUnstructured(\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined\n): boolean {\n let resolved = schema;\n let depth = 0;\n while (resolved.ref && depth < MAX_REF_DEPTH) {\n const target = defs?.[resolved.ref];\n if (!target) return false;\n resolved = target;\n depth++;\n }\n if (depth >= MAX_REF_DEPTH) return false;\n return isUnstructuredSchema(resolved);\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// JTD definition reachability \u2014 prune unreferenced definitions\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * Walk one or more JTD root schemas and return the set of definition names\n * that are transitively reachable via `ref`. Definitions not in this set\n * are unreferenced and should be pruned from `$defs`.\n */\nexport function findReferencedJTDDefs(\n roots: JTDSchemaJson[],\n defs: Record<string, JTDSchemaJson>\n): Set<string> {\n const visited = new Set<string>();\n const queue: JTDSchemaJson[] = [...roots];\n\n while (queue.length > 0) {\n const schema = queue.pop()!;\n if (!schema || typeof schema !== 'object') continue;\n\n if (schema.ref) {\n if (!visited.has(schema.ref) && defs[schema.ref]) {\n visited.add(schema.ref);\n queue.push(defs[schema.ref]);\n }\n continue; // ref form \u2014 no other structural fields\n }\n\n if (schema.elements) queue.push(schema.elements);\n if (schema.values) queue.push(schema.values);\n if (schema.properties) {\n for (const v of Object.values(schema.properties)) queue.push(v);\n }\n if (schema.optionalProperties) {\n for (const v of Object.values(schema.optionalProperties)) queue.push(v);\n }\n if (schema.mapping) {\n for (const v of Object.values(schema.mapping)) queue.push(v);\n }\n }\n\n return visited;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Convert result types\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport interface ConvertSuccessJson {\n success: true;\n kind: 'json';\n data: unknown;\n}\n\nexport interface ConvertSuccessBinary {\n success: true;\n kind: 'binary';\n mimeType: string;\n data: Uint8Array;\n filename?: string;\n}\n\nexport interface ConvertSuccessSignals {\n success: true;\n kind: 'signals';\n signals: Record<string, unknown>;\n}\n\nexport interface ConvertFailure {\n success: false;\n errors: string[];\n}\n\nexport type ConvertSuccess =\n | ConvertSuccessJson\n | ConvertSuccessBinary\n | ConvertSuccessSignals;\nexport type ConvertResult = ConvertSuccess | ConvertFailure;\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Convert output pipeline (identical across all 3 providers)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nexport function convertJTDOutput(\n jtdSchema: JTDSchemaJson,\n json: unknown,\n coerce: (\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n ) => unknown\n): ConvertResult {\n const defs = jtdSchema.definitions;\n const coerced = coerce(json, jtdSchema, defs, 0);\n const errors = validateJsonAgainstJTD(jtdSchema, coerced, defs);\n if (errors.length > 0) return { success: false, errors };\n return { success: true, kind: 'json', data: coerced };\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Reference mode: discriminated anyOf schema builder\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * Wrap a signal's value schema in a discriminated anyOf with two variants:\n * - `{ _kind: \"_value\", _value: <valueSchema> }` \u2014 inline data\n * - `{ _kind: \"_ref\", _ref: <string> }` \u2014 file reference\n *\n * Field names are prefixed with `_` to avoid collisions with user-defined\n * signal properties (e.g. a signal named \"data\" or \"kind\").\n * The `_kind` value matches its sibling field name for clarity.\n *\n * The caller resolves references before calling convert.\n *\n * Returns a plain object \u2014 cast to the provider's schema type at the call site.\n *\n * @param valueSchema - The signal's original schema (JTD, MIME, or multi-format anyOf).\n * @param description - Optional signal description to include.\n * @param strict - When false, omits `additionalProperties` (required by GeminiFC).\n */\nexport function buildReferenceAnyOf(\n valueSchema: unknown,\n description?: string,\n strict?: boolean\n): { anyOf: unknown[] } {\n const desc = description ? `${description}. ` : '';\n const valueVariant: Record<string, unknown> = {\n type: 'object',\n properties: {\n _kind: { type: 'string', enum: ['_value'] },\n _value: valueSchema\n },\n required: ['_kind', '_value'],\n description: `${desc}Inline value.`\n };\n const refVariant: Record<string, unknown> = {\n type: 'object',\n properties: {\n _kind: { type: 'string', enum: ['_ref'] },\n _ref: {\n type: 'string',\n description:\n 'A file reference. The referenced file MUST contain data that conforms to the same format described in the inline value variant.'\n }\n },\n required: ['_kind', '_ref'],\n description: `${desc}Reference to a file instead of inline data.`\n };\n if (strict !== false) {\n valueVariant.additionalProperties = false;\n refVariant.additionalProperties = false;\n }\n return { anyOf: [valueVariant, refVariant] };\n}\n\n/**\n * Check if a concrete MIME type matches an accepted MIME pattern.\n *\n * - `*\u200B/*` matches everything\n * - `image/*` matches any `image/...` subtype\n * - Exact string match otherwise\n */\nexport function mimeTypeMatches(concrete: string, accepted: string): boolean {\n const c = concrete.trim().toLowerCase();\n const a = accepted.trim().toLowerCase();\n if (a === '*/*') return true;\n if (a.endsWith('/*')) {\n return c.startsWith(a.slice(0, a.indexOf('/') + 1));\n }\n return c === a;\n}\n\n/**\n * Convert a MIME-format signal value to dispatch-ready binary SignalData.\n *\n * Accepts two data shapes:\n * - **Inline (from LLM):** `{ data: \"<base64>\", mimeType: \"image/png\", filename?: \"...\" }`\n * - **Resolved (from file reference):** `{ data: Uint8Array, mimeType: \"image/png\", filename?: \"...\" }`\n *\n * The input's `mimeType` field is the concrete type and ends up in the result.\n * It is validated against the format's accepted pattern (which may be a wildcard).\n *\n * @param acceptedMimeType - MIME pattern from the signal format declaration.\n * @param value - The signal value to convert.\n */\nexport function convertMimeOutput(\n acceptedMimeType: string,\n value: unknown\n): ConvertResult {\n if (typeof value !== 'object' || value === null) {\n return {\n success: false,\n errors: [\n `expected object with 'data' and 'mimeType' fields, got ${typeof value}`\n ]\n };\n }\n\n const fields = value as Record<string, unknown>;\n\n // Concrete mimeType is required \u2014 this is what goes to dispatch\n if (typeof fields.mimeType !== 'string' || !fields.mimeType.includes('/')) {\n return {\n success: false,\n errors: [\n 'parameter \\'mimeType\\': required concrete MIME type (e.g. \"image/png\", \"application/pdf\")'\n ]\n };\n }\n\n // Validate concrete type against the format's accepted pattern\n if (!mimeTypeMatches(fields.mimeType, acceptedMimeType)) {\n return {\n success: false,\n errors: [\n `parameter 'mimeType': \"${fields.mimeType}\" does not match accepted type \"${acceptedMimeType}\"`\n ]\n };\n }\n\n // Resolve data \u2014 either already Uint8Array or base64-encoded string\n let binaryData: Uint8Array;\n if (fields.data instanceof Uint8Array) {\n binaryData = fields.data;\n } else if (typeof fields.data === 'string') {\n binaryData = new Uint8Array(Buffer.from(fields.data, 'base64'));\n } else {\n return {\n success: false,\n errors: [\n `parameter 'data': expected base64 string or Uint8Array, got ${typeof fields.data}`\n ]\n };\n }\n\n const result: ConvertSuccessBinary = {\n success: true,\n kind: 'binary',\n mimeType: fields.mimeType,\n data: binaryData\n };\n if (typeof fields.filename === 'string' && fields.filename) {\n result.filename = fields.filename;\n }\n return result;\n}\n\n/**\n * Convert a single format's value. Dispatches to JTD or MIME conversion.\n *\n * @param fmt - The signal format (has either `jtdSchema` or `mimeType`).\n * @param value - The signal value. JSON for JTD formats, or `{ data, mimeType, filename }` for MIME.\n * @param coerce - Provider-specific JTD coercion function.\n */\nexport function convertSingleFormatOutput(\n fmt: SignalFormatJson,\n value: unknown,\n coerce: (\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n ) => unknown\n): ConvertResult {\n if (fmt.mimeType) return convertMimeOutput(fmt.mimeType, value);\n if (fmt.jtdSchema) return convertJTDOutput(fmt.jtdSchema, value, coerce);\n return {\n success: false,\n errors: ['Signal format has neither jtdSchema nor mimeType']\n };\n}\n\n/**\n * Convert a signal's value against its declared formats.\n *\n * The value is already resolved \u2014 this function knows nothing about file\n * references. When `referenceMode` is used, the **caller** handles the\n * `_kind` discriminator:\n *\n * - `{ _kind: \"_value\", _value: <inline> }` \u2192 caller extracts `_value`, passes here\n * - `{ _kind: \"_ref\", _ref: \"...\" }` \u2192 caller resolves the ref, passes:\n * - Parsed JSON object (for JTD formats)\n * - `{ data: Uint8Array, mimeType, filename }` (for MIME formats)\n *\n * For **single-format** signals, the value is validated directly.\n * For **multi-format** signals, each format is tried in order (anyOf \u2014 first match wins).\n *\n * @param descriptor - The signal descriptor with one or more formats.\n * @param value - The resolved signal value (JSON for JTD, or `{ data, mimeType, filename }` for MIME).\n * @param coerce - Provider-specific JTD coercion function.\n */\nexport function convertSignalOutput(\n descriptor: SignalDescriptorJson,\n value: unknown,\n coerce: (\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n ) => unknown\n): ConvertResult {\n const formats = descriptor.formats as SignalFormatJson[] | undefined;\n if (!formats || formats.length === 0) {\n return { success: false, errors: ['Signal descriptor has no formats'] };\n }\n\n if (formats.length === 1) {\n return convertSingleFormatOutput(formats[0], value, coerce);\n }\n\n // Multi-format: try each format (anyOf semantics \u2014 first match wins).\n // Each format is an independent option. A MIME format and a JTD format\n // in the same signal are ALTERNATIVE input paths, not mutual constraints.\n // The platform validates signal data against the binding's accepted formats\n // at dispatch time.\n const allErrors: string[] = [];\n for (const fmt of formats) {\n const result = convertSingleFormatOutput(fmt, value, coerce);\n if (result.success) return result;\n allErrors.push(...result.errors);\n }\n\n return {\n success: false,\n errors: [\n `No format matched for signal '${descriptor.name ?? 'unnamed'}'. Errors: ${allErrors.join('; ')}`\n ]\n };\n}\n\n/**\n * Convert multiple signals from a compound output.\n *\n * Expects `input` to be an object keyed by signal name. Each signal's\n * value is passed to {@link convertSignalOutput} \u2014 it can be JSON (for\n * JTD formats) or `{ data, mimeType, filename }` (for MIME formats).\n *\n * When using `referenceMode`, the **caller** must unwrap the `kind`\n * discriminator for each signal before calling this function.\n * See {@link convertSignalOutput} for the full reference-mode contract.\n */\nexport function convertSignalsOutput(\n descriptors: SignalDescriptorJson[],\n input: unknown,\n coerce: (\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n ) => unknown\n): ConvertResult {\n if (typeof input !== 'object' || input === null) {\n return {\n success: false,\n errors: ['/: expected object with signal properties, got ' + typeof input]\n };\n }\n\n const rec = input as Record<string, unknown>;\n const signals: Record<string, unknown> = {};\n const errors: string[] = [];\n\n for (const desc of descriptors) {\n if (!desc.name) continue;\n const value = rec[desc.name];\n if (value === undefined || value === null) continue;\n\n const result = convertSignalOutput(desc, value, coerce);\n if (!result.success) {\n for (const err of result.errors) {\n errors.push(`parameter '${desc.name}': ${err}`);\n }\n } else if (result.kind === 'binary') {\n signals[desc.name] = {\n data: result.data,\n mimeType: result.mimeType,\n filename: result.filename\n };\n } else if (result.kind === 'json') {\n signals[desc.name] = result.data;\n }\n }\n\n if (errors.length > 0) return { success: false, errors };\n return { success: true, kind: 'signals', signals };\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Schema tree walk (for providers that use $ref/$defs)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/** Minimal shape for $ref-bearing schemas (satisfied by OpenAISchema and AnthropicSchema). */\ninterface RefNode {\n properties?: Record<string, RefNode>;\n items?: RefNode;\n anyOf?: RefNode[];\n $defs?: Record<string, RefNode>;\n $ref?: string;\n}\n\nexport function walkSchema<T extends RefNode>(\n schema: T,\n fn: (node: T) => void\n): void {\n fn(schema);\n if (schema.properties)\n for (const v of Object.values(schema.properties)) walkSchema(v as T, fn);\n if (schema.items) walkSchema(schema.items as T, fn);\n if (schema.anyOf) for (const v of schema.anyOf) walkSchema(v as T, fn);\n if (schema.$defs)\n for (const v of Object.values(schema.$defs)) walkSchema(v as T, fn);\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Definition pooling for fromSignals (OpenAI + Anthropic)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/** Recursively rewrite JTD `ref` values in a schema according to a rename map. */\nfunction rewriteJtdRefs(\n schema: JTDSchemaJson,\n renames: Map<string, string>\n): JTDSchemaJson {\n if (!schema || typeof schema !== 'object') return schema;\n\n const result: JTDSchemaJson = { ...schema };\n\n if (result.ref && renames.has(result.ref)) {\n result.ref = renames.get(result.ref)!;\n }\n if (result.properties) {\n result.properties = Object.fromEntries(\n Object.entries(result.properties).map(([k, v]) => [\n k,\n rewriteJtdRefs(v, renames)\n ])\n );\n }\n if (result.optionalProperties) {\n result.optionalProperties = Object.fromEntries(\n Object.entries(result.optionalProperties).map(([k, v]) => [\n k,\n rewriteJtdRefs(v, renames)\n ])\n );\n }\n if (result.elements)\n result.elements = rewriteJtdRefs(result.elements, renames);\n if (result.values) result.values = rewriteJtdRefs(result.values, renames);\n if (result.mapping) {\n result.mapping = Object.fromEntries(\n Object.entries(result.mapping).map(([k, v]) => [\n k,\n rewriteJtdRefs(v, renames)\n ])\n );\n }\n // Definitions are rewritten at pool level, strip from the copy\n if (result.definitions) delete result.definitions;\n\n return result;\n}\n\n/**\n * Pre-scan all signal descriptors, pool their JTD definitions.\n * Deduplicates identical definition SETS; renames conflicts with `_N` suffix.\n *\n * Equivalence is checked per-format: all definitions within a single format's\n * `definitions` block are treated as a unit. Two formats with the same set of\n * definition names and identical schemas (deep-equal) share their defs.\n * If ANY definition name conflicts (same name, different schema), ALL defs\n * from that format get their own namespace to preserve internal cross-references.\n *\n * Returns the merged JTD definitions and signal copies with:\n * - refs rewritten to match merged names\n * - definitions stripped (they live in the merged pool now)\n */\nexport function poolSignalDefinitions(signals: SignalDescriptorJson[]): {\n mergedDefs: Record<string, JTDSchemaJson>;\n signalsCopy: SignalDescriptorJson[];\n} {\n const mergedDefs: Record<string, JTDSchemaJson> = {};\n\n // For each format that has definitions, compute its rename map.\n // Key: `signalIdx:formatIdx`, Value: Map<oldName, newName>\n const renamesByFormat = new Map<string, Map<string, string>>();\n\n for (let si = 0; si < signals.length; si++) {\n const formats = signals[si].formats as SignalFormatJson[] | undefined;\n if (!formats) continue;\n for (let fi = 0; fi < formats.length; fi++) {\n const defs = formats[fi].jtdSchema?.definitions;\n if (!defs || Object.keys(defs).length === 0) continue;\n\n // Check each definition against the merged pool\n const renames = new Map<string, string>();\n let hasConflict = false;\n\n for (const [name, defSchema] of Object.entries(defs)) {\n const existing = mergedDefs[name];\n if (!existing) continue; // new name, no conflict\n if (deepEqual(existing, defSchema)) continue; // identical, dedup\n hasConflict = true;\n break;\n }\n\n if (hasConflict) {\n // This format has at least one conflict. Rename ALL its definitions\n // to avoid broken cross-references between shared and renamed defs.\n for (const [name, defSchema] of Object.entries(defs)) {\n const existing = mergedDefs[name];\n if (existing && deepEqual(existing, defSchema)) {\n // Identical to existing \u2014 can still share\n continue;\n }\n if (existing) {\n // Conflict \u2014 find unique name\n let n = 1;\n while (mergedDefs[`${name}_${n}`]) n++;\n const newName = `${name}_${n}`;\n renames.set(name, newName);\n mergedDefs[newName] = defSchema;\n } else {\n mergedDefs[name] = defSchema;\n }\n }\n } else {\n // No conflicts \u2014 all defs are either new or identical (deduped)\n for (const [name, defSchema] of Object.entries(defs)) {\n if (!mergedDefs[name]) mergedDefs[name] = defSchema;\n }\n }\n\n if (renames.size > 0) renamesByFormat.set(`${si}:${fi}`, renames);\n }\n }\n\n // Rewrite refs inside renamed definitions (they may reference other renamed defs)\n for (const [, renames] of renamesByFormat) {\n for (const [, newName] of renames) {\n mergedDefs[newName] = rewriteJtdRefs(mergedDefs[newName], renames);\n }\n }\n\n // Build signal copies with rewritten refs and stripped definitions\n const signalsCopy: SignalDescriptorJson[] = signals.map((signal, si) => {\n const formats = signal.formats as SignalFormatJson[] | undefined;\n if (!formats) return signal;\n\n let hasChanges = false;\n const newFormats = formats.map((fmt, fi) => {\n if (!fmt.jtdSchema) return fmt;\n const renames = renamesByFormat.get(`${si}:${fi}`);\n const hasDefs =\n fmt.jtdSchema.definitions &&\n Object.keys(fmt.jtdSchema.definitions).length > 0;\n if (!renames && !hasDefs) return fmt;\n\n hasChanges = true;\n const rewritten = renames\n ? rewriteJtdRefs(fmt.jtdSchema, renames)\n : { ...fmt.jtdSchema };\n delete rewritten.definitions;\n return { ...fmt, jtdSchema: rewritten };\n });\n\n return hasChanges ? { ...signal, formats: newFormats } : signal;\n });\n\n // Prune definitions that are never transitively referenced from any signal\n const rootSchemas = signalsCopy.flatMap((s) =>\n ((s.formats as SignalFormatJson[] | undefined) ?? [])\n .filter((f) => f.jtdSchema)\n .map((f) => f.jtdSchema!)\n );\n const referenced = findReferencedJTDDefs(rootSchemas, mergedDefs);\n for (const name of Object.keys(mergedDefs)) {\n if (!referenced.has(name)) delete mergedDefs[name];\n }\n\n return { mergedDefs, signalsCopy };\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Cycle description \u2014 shared by Anthropic and Gemini for recursive ref fallbacks\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nfunction describeJTDType(schema: JTDSchemaJson): string {\n if (!schema || typeof schema !== 'object') return 'any';\n if (schema.ref) return schema.ref;\n if (schema.type) return schema.type;\n if (schema.enum) return `one of: ${schema.enum.join(', ')}`;\n if (schema.elements) return `array of ${describeJTDType(schema.elements)}`;\n if (schema.values) return `map of ${describeJTDType(schema.values)}`;\n if (schema.properties || schema.optionalProperties) return 'object';\n if (schema.discriminator) return `union on '${schema.discriminator}'`;\n return 'any';\n}\n\n/**\n * Build a human-readable description of a JTD definition for use as a\n * fallback at recursive ref break points. Used by both Anthropic and Gemini\n * converters when a cycle is detected.\n */\nexport function describeDefinitionForCycle(\n defName: string,\n def: JTDSchemaJson\n): string {\n const parts: string[] = [\n `Recursive nesting. This field accepts the same object structure as its parent with the following shape:`\n ];\n\n const required: string[] = [];\n const optional: string[] = [];\n\n if (def.properties) {\n for (const [key, prop] of Object.entries(def.properties)) {\n required.push(`${key} (${describeJTDType(prop)})`);\n }\n }\n if (def.optionalProperties) {\n for (const [key, prop] of Object.entries(def.optionalProperties)) {\n optional.push(`${key} (${describeJTDType(prop)})`);\n }\n }\n\n if (required.length > 0) {\n parts.push(`Required: ${required.join(', ')}.`);\n }\n if (optional.length > 0) {\n const shown = optional.slice(0, 8);\n const suffix =\n optional.length > 8 ? `, and ${optional.length - 8} more` : '';\n parts.push(`Optional: ${shown.join(', ')}${suffix}.`);\n }\n\n if (def.elements) {\n parts.push(`Array of ${describeJTDType(def.elements)}.`);\n }\n if (def.values) {\n parts.push(`Map with values of type ${describeJTDType(def.values)}.`);\n }\n\n return parts.join(' ');\n}\n", "// Copyright 2024-2025 Mochabug, LLC. All rights reserved.\n// Licensed under the Apache License, Version 2.0.\n\nimport type {\n JTDSchemaJson,\n SignalDescriptorJson,\n SignalFormatJson\n} from '../api';\nimport {\n buildReferenceAnyOf,\n describeDefinitionForCycle,\n FLOAT32_BOUNDS,\n INTEGER_BOUNDS,\n isRootUnstructured,\n isUnstructuredSchema,\n MAX_REF_DEPTH,\n nullableType,\n convertJTDOutput as sharedConvertJTDOutput,\n convertSignalOutput as sharedConvertSignalOutput,\n convertSignalsOutput as sharedConvertSignalsOutput,\n UNSTRUCTURED_DESC,\n VALUES_ARRAY_DESC,\n type ConvertFailure,\n type ConvertResult,\n type ConvertSuccess,\n type ConvertSuccessBinary,\n type ConvertSuccessJson,\n type ConvertSuccessSignals\n} from '../schema-utils';\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Types\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * JSON Schema subset accepted by the Gemini structured-output API.\n *\n * This is the provider-specific representation produced by {@link GeminiConversion}.\n * It covers the keywords that Gemini 3+ actually inspects when constraining\n * model output: `type`, `properties`, `required`, `enum`, `items`,\n * `additionalProperties`, `format`, `minimum`/`maximum`, `title`, and\n * `description`.\n */\nexport interface GeminiSchema {\n type?: string | string[];\n nullable?: boolean;\n title?: string;\n description?: string;\n properties?: Record<string, GeminiSchema>;\n required?: string[];\n additionalProperties?: boolean | GeminiSchema;\n enum?: (string | number | null)[];\n format?: string;\n items?: GeminiSchema;\n minimum?: number;\n maximum?: number;\n minItems?: number;\n maxItems?: number;\n anyOf?: GeminiSchema[];\n}\n\n/**\n * Schema subset matching the Gemini v1beta Schema protobuf message.\n * Used for function calling `parameters` field in `FunctionDeclaration`.\n *\n * Key differences from {@link GeminiSchema}:\n * - `type` is always a single string (no type arrays)\n * - Uses `nullable: true` instead of `[\"type\", \"null\"]` type arrays\n * - Does NOT include `additionalProperties`\n * - Supports `anyOf` for discriminator unions\n * - Supports `minItems`/`maxItems` for array bounds\n */\nexport interface GeminiFCSchema {\n type?: string;\n nullable?: boolean;\n title?: string;\n description?: string;\n properties?: Record<string, GeminiFCSchema>;\n required?: string[];\n enum?: string[];\n format?: string;\n items?: GeminiFCSchema;\n minimum?: number;\n maximum?: number;\n minItems?: number;\n maxItems?: number;\n anyOf?: GeminiFCSchema[];\n}\n\nexport type {\n ConvertFailure,\n ConvertResult,\n ConvertSuccess,\n ConvertSuccessBinary,\n ConvertSuccessJson,\n ConvertSuccessSignals\n} from '../schema-utils';\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Constants & helpers\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst METADATA_KNOWN_KEYS = new Set(['title', 'label', 'name', 'description']);\n\nfunction applyMetadata(result: GeminiSchema, schema: JTDSchemaJson): void {\n const meta = schema.metadata as Record<string, unknown> | undefined;\n if (!meta) return;\n\n if (typeof meta['description'] === 'string' && meta['description'].trim()) {\n result.description = meta['description'].trim();\n }\n\n const titleCandidate =\n (typeof meta['title'] === 'string' && meta['title'].trim()) ||\n (typeof meta['label'] === 'string' && meta['label'].trim()) ||\n (typeof meta['name'] === 'string' && meta['name'].trim());\n if (titleCandidate) result.title = titleCandidate;\n\n const extras: string[] = [];\n for (const [key, value] of Object.entries(meta)) {\n if (METADATA_KNOWN_KEYS.has(key)) continue;\n if (typeof value === 'string' && value.trim()) {\n extras.push(`[${key}: ${value.trim()}]`);\n } else if (typeof value === 'number' || typeof value === 'boolean') {\n extras.push(`[${key}: ${value}]`);\n }\n }\n if (extras.length > 0) {\n const suffix = extras.join(' ');\n result.description = result.description\n ? `${result.description} ${suffix}`\n : suffix;\n }\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Schema conversion (JTD \u2192 Gemini JSON Schema)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nfunction resolveRef(\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n): JTDSchemaJson {\n if (depth > MAX_REF_DEPTH) {\n throw new Error(\n `Circular or excessively deep ref chain (exceeded ${MAX_REF_DEPTH} levels)`\n );\n }\n if (!schema.ref) return schema;\n const resolved = defs?.[schema.ref];\n if (!resolved) throw new Error(`Unresolved ref \"${schema.ref}\"`);\n return resolveRef(resolved, defs, depth + 1);\n}\n\nfunction convertNode(\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number,\n activeRefs?: Set<string>\n): GeminiSchema {\n if (depth > MAX_REF_DEPTH) {\n throw new Error(`Schema nesting exceeded ${MAX_REF_DEPTH} levels`);\n }\n\n if (!schema || typeof schema !== 'object') {\n return { description: 'Any JSON value' };\n }\n\n if (schema.ref) {\n const visited = activeRefs ?? new Set<string>();\n if (visited.has(schema.ref)) {\n // Cycle detected \u2014 emit a descriptive placeholder instead of recursing\n const refDef = defs?.[schema.ref];\n const result: GeminiSchema = {\n description: refDef\n ? describeDefinitionForCycle(schema.ref, refDef)\n : `Recursive reference to ${schema.ref}. Accepts any valid JSON matching the ${schema.ref} structure.`\n };\n applyMetadata(result, schema);\n return result;\n }\n visited.add(schema.ref);\n const resolved = resolveRef(schema, defs, 0);\n const result = convertNode(resolved, defs, depth + 1, visited);\n visited.delete(schema.ref);\n applyMetadata(result, schema);\n if (schema.nullable) {\n const existing = result.type;\n result.type = Array.isArray(existing)\n ? [...existing, 'null']\n : existing\n ? [existing, 'null']\n : ['null'];\n }\n return result;\n }\n\n const isNullable = schema.nullable === true;\n const result: GeminiSchema = {};\n\n if (schema.type) {\n const intBounds =\n INTEGER_BOUNDS[schema.type as keyof typeof INTEGER_BOUNDS];\n if (intBounds) {\n result.type = nullableType('integer', isNullable);\n result.minimum = intBounds.minimum;\n result.maximum = intBounds.maximum;\n } else\n switch (schema.type) {\n case 'boolean':\n result.type = nullableType('boolean', isNullable);\n break;\n case 'string':\n result.type = nullableType('string', isNullable);\n break;\n case 'timestamp':\n result.type = nullableType('string', isNullable);\n result.format = 'date-time';\n break;\n case 'float32':\n result.type = nullableType('number', isNullable);\n result.minimum = FLOAT32_BOUNDS.minimum;\n result.maximum = FLOAT32_BOUNDS.maximum;\n break;\n case 'float64':\n result.type = nullableType('number', isNullable);\n break;\n default:\n throw new Error(`Unknown JTD type \"${schema.type}\"`);\n }\n applyMetadata(result, schema);\n return result;\n }\n\n if (schema.enum) {\n result.type = nullableType('string', isNullable);\n result.enum = isNullable ? [...schema.enum, null] : schema.enum;\n applyMetadata(result, schema);\n return result;\n }\n\n if (schema.elements) {\n result.type = nullableType('array', isNullable);\n result.items = convertNode(schema.elements, defs, depth + 1, activeRefs);\n applyMetadata(result, schema);\n return result;\n }\n\n if (schema.properties || schema.optionalProperties) {\n result.type = nullableType('object', isNullable);\n result.properties = {};\n const required: string[] = [];\n\n if (schema.properties) {\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n result.properties[key] = convertNode(\n propSchema,\n defs,\n depth + 1,\n activeRefs\n );\n required.push(key);\n }\n }\n if (schema.optionalProperties) {\n for (const [key, propSchema] of Object.entries(\n schema.optionalProperties\n )) {\n result.properties[key] = convertNode(\n propSchema,\n defs,\n depth + 1,\n activeRefs\n );\n }\n }\n if (required.length > 0) result.required = required;\n if (schema.additionalProperties !== true)\n result.additionalProperties = false;\n\n applyMetadata(result, schema);\n return result;\n }\n\n if (schema.values) {\n result.type = nullableType('object', isNullable);\n result.additionalProperties = convertNode(\n schema.values,\n defs,\n depth + 1,\n activeRefs\n );\n applyMetadata(result, schema);\n return result;\n }\n\n if (schema.discriminator && schema.mapping) {\n const discKey = schema.discriminator;\n const tags = Object.keys(schema.mapping);\n\n const variants: GeminiSchema[] = tags.map((tag) => {\n const variantSchema = schema.mapping![tag]!;\n const v = convertNode(variantSchema, defs, depth + 1, activeRefs);\n // Inject the discriminator as a const-like enum property\n v.type = 'object';\n v.properties = {\n [discKey]: {\n type: 'string',\n enum: [tag],\n description: `Must be '${tag}'.`\n },\n ...(v.properties ?? {})\n };\n v.required = [discKey, ...(v.required ?? [])];\n v.additionalProperties = false;\n return v;\n });\n\n if (isNullable) {\n variants.push({ type: 'null' });\n }\n\n if (variants.length === 1) {\n const single = variants[0]!;\n applyMetadata(single, schema);\n return single;\n }\n\n result.anyOf = variants;\n applyMetadata(result, schema);\n return result;\n }\n\n result.description = 'Any JSON value';\n applyMetadata(result, schema);\n return result;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// FC Schema conversion (JTD \u2192 Gemini v1beta Schema protobuf for function calling)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nfunction applyMetadataFC(result: GeminiFCSchema, schema: JTDSchemaJson): void {\n const meta = schema.metadata as Record<string, unknown> | undefined;\n if (!meta) return;\n\n if (typeof meta['description'] === 'string' && meta['description'].trim()) {\n result.description = meta['description'].trim();\n }\n\n const titleCandidate =\n (typeof meta['title'] === 'string' && meta['title'].trim()) ||\n (typeof meta['label'] === 'string' && meta['label'].trim()) ||\n (typeof meta['name'] === 'string' && meta['name'].trim());\n if (titleCandidate) result.title = titleCandidate;\n\n const extras: string[] = [];\n for (const [key, value] of Object.entries(meta)) {\n if (METADATA_KNOWN_KEYS.has(key)) continue;\n if (typeof value === 'string' && value.trim()) {\n extras.push(`[${key}: ${value.trim()}]`);\n } else if (typeof value === 'number' || typeof value === 'boolean') {\n extras.push(`[${key}: ${value}]`);\n }\n }\n if (extras.length > 0) {\n const suffix = extras.join(' ');\n result.description = result.description\n ? `${result.description} ${suffix}`\n : suffix;\n }\n}\n\nfunction convertNodeFC(\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number,\n activeRefs?: Set<string>\n): GeminiFCSchema {\n if (depth > MAX_REF_DEPTH) {\n throw new Error(`Schema nesting exceeded ${MAX_REF_DEPTH} levels`);\n }\n\n if (!schema || typeof schema !== 'object') {\n return { type: 'string', description: UNSTRUCTURED_DESC };\n }\n\n // Unstructured/empty schema \u2192 string-encoded JSON (no additionalProperties in FC)\n if (isRootUnstructured(schema, defs)) {\n const result: GeminiFCSchema = {\n type: 'string',\n description: UNSTRUCTURED_DESC\n };\n if (schema.nullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Ref form \u2014 inline with cycle detection\n if (schema.ref) {\n const visited = activeRefs ?? new Set<string>();\n if (visited.has(schema.ref)) {\n const refDef = defs?.[schema.ref];\n const result: GeminiFCSchema = {\n description: refDef\n ? describeDefinitionForCycle(schema.ref, refDef)\n : `Recursive reference to ${schema.ref}. Accepts any valid JSON matching the ${schema.ref} structure.`\n };\n applyMetadataFC(result, schema);\n return result;\n }\n visited.add(schema.ref);\n const resolved = resolveRef(schema, defs, 0);\n const result = convertNodeFC(resolved, defs, depth + 1, visited);\n visited.delete(schema.ref);\n applyMetadataFC(result, schema);\n if (schema.nullable) result.nullable = true;\n return result;\n }\n\n const isNullable = schema.nullable === true;\n const result: GeminiFCSchema = {};\n\n // Type form\n if (schema.type) {\n const intBounds =\n INTEGER_BOUNDS[schema.type as keyof typeof INTEGER_BOUNDS];\n if (intBounds) {\n result.type = 'integer';\n result.minimum = intBounds.minimum;\n result.maximum = intBounds.maximum;\n } else\n switch (schema.type) {\n case 'boolean':\n result.type = 'boolean';\n break;\n case 'string':\n result.type = 'string';\n break;\n case 'timestamp':\n result.type = 'string';\n result.format = 'date-time';\n break;\n case 'float32':\n result.type = 'number';\n result.minimum = FLOAT32_BOUNDS.minimum;\n result.maximum = FLOAT32_BOUNDS.maximum;\n break;\n case 'float64':\n result.type = 'number';\n break;\n default:\n throw new Error(`Unknown JTD type \"${schema.type}\"`);\n }\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Enum form\n if (schema.enum) {\n result.type = 'string';\n result.enum = [...schema.enum];\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Elements form\n if (schema.elements) {\n result.type = 'array';\n result.items = convertNodeFC(schema.elements, defs, depth + 1, activeRefs);\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Properties form\n if (schema.properties || schema.optionalProperties) {\n result.type = 'object';\n result.properties = {};\n const required: string[] = [];\n\n if (schema.properties) {\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n result.properties[key] = convertNodeFC(\n propSchema,\n defs,\n depth + 1,\n activeRefs\n );\n required.push(key);\n }\n }\n if (schema.optionalProperties) {\n for (const [key, propSchema] of Object.entries(\n schema.optionalProperties\n )) {\n result.properties[key] = convertNodeFC(\n propSchema,\n defs,\n depth + 1,\n activeRefs\n );\n }\n }\n if (required.length > 0) result.required = required;\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Values form \u2192 key-value array pattern (no additionalProperties in FC)\n if (schema.values) {\n const valueSchema = convertNodeFC(\n schema.values,\n defs,\n depth + 1,\n activeRefs\n );\n result.type = 'array';\n result.description = VALUES_ARRAY_DESC;\n result.items = {\n type: 'object',\n properties: {\n key: { type: 'string', description: 'The property name.' },\n value: valueSchema\n },\n required: ['key', 'value']\n };\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n }\n\n // Discriminator form \u2192 anyOf with const-like enum for tag\n if (schema.discriminator && schema.mapping) {\n const discKey = schema.discriminator;\n const mapping = schema.mapping;\n const variants: GeminiFCSchema[] = [];\n\n for (const [tag, variantSchema] of Object.entries(mapping)) {\n const variantOut = convertNodeFC(\n variantSchema,\n defs,\n depth + 1,\n activeRefs\n );\n // Per RFC 8927, mapping values are always properties form;\n // the discriminator tag never appears in variant properties.\n variantOut.properties ??= {};\n variantOut.properties[discKey] = {\n type: 'string',\n enum: [tag]\n };\n (variantOut.required ??= []).unshift(discKey);\n applyMetadataFC(variantOut, variantSchema);\n variants.push(variantOut);\n }\n\n if (isNullable) variants.push({ type: 'string', nullable: true });\n\n const anyOfResult: GeminiFCSchema = { anyOf: variants };\n applyMetadataFC(anyOfResult, schema);\n return anyOfResult;\n }\n\n // Fallback \u2014 should not reach here after isRootUnstructured check above,\n // but guard just in case.\n result.type = 'string';\n result.description = UNSTRUCTURED_DESC;\n if (isNullable) result.nullable = true;\n applyMetadataFC(result, schema);\n return result;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Coercion (Gemini output \u2192 JTD-conforming JSON)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst INTEGER_TYPE_SET = new Set(Object.keys(INTEGER_BOUNDS));\n\nfunction coerceToJTD(\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n): unknown {\n if (depth > MAX_REF_DEPTH) return value;\n if (value === null || value === undefined) return value;\n\n if (schema.ref) {\n const resolved = defs?.[schema.ref];\n if (resolved) return coerceToJTD(value, resolved, defs, depth + 1);\n return value;\n }\n\n if (schema.type && INTEGER_TYPE_SET.has(schema.type)) {\n if (typeof value === 'number' && !Number.isInteger(value))\n return Math.round(value);\n return value;\n }\n\n if (schema.elements) {\n const el = schema.elements;\n const arr = Array.isArray(value) ? value : [value];\n return arr.map((item) => coerceToJTD(item, el, defs, depth + 1));\n }\n\n if (\n (schema.properties || schema.optionalProperties) &&\n typeof value === 'object' &&\n value !== null\n ) {\n const obj = { ...(value as Record<string, unknown>) };\n if (schema.properties) {\n for (const [k, s] of Object.entries(schema.properties)) {\n if (k in obj) obj[k] = coerceToJTD(obj[k], s, defs, depth + 1);\n }\n }\n if (schema.optionalProperties) {\n for (const [k, s] of Object.entries(schema.optionalProperties)) {\n if (k in obj) obj[k] = coerceToJTD(obj[k], s, defs, depth + 1);\n }\n }\n return obj;\n }\n\n if (schema.values && typeof value === 'object' && value !== null) {\n const obj: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n obj[k] = coerceToJTD(v, schema.values, defs, depth + 1);\n }\n return obj;\n }\n\n if (\n schema.discriminator &&\n schema.mapping &&\n typeof value === 'object' &&\n value !== null\n ) {\n const rec = value as Record<string, unknown>;\n const tag = rec[schema.discriminator];\n if (typeof tag !== 'string') return value;\n const vs = schema.mapping[tag];\n if (!vs) return value;\n // With anyOf, data is flat (same as JTD format). Coerce variant properties in-place.\n const coerced = coerceToJTD(rec, vs, defs, depth + 1) as Record<\n string,\n unknown\n >;\n coerced[schema.discriminator] = tag;\n return coerced;\n }\n\n return value;\n}\n\n/**\n * Coercion for FC schemas. Handles the same forms as coerceToJTD, plus:\n * - Unstructured schemas: parses JSON strings back into objects\n * - Values form: converts {key, value}[] arrays back into Records\n * - Discriminator form: flat object coercion (same as Anthropic anyOf pattern)\n */\nfunction coerceToJTDFC(\n value: unknown,\n schema: JTDSchemaJson,\n defs: Record<string, JTDSchemaJson> | undefined,\n depth: number\n): unknown {\n if (depth > MAX_REF_DEPTH) return value;\n if (value === null || value === undefined) return value;\n\n // Ref form\n if (schema.ref) {\n const resolved = defs?.[schema.ref];\n if (resolved) return coerceToJTDFC(value, resolved, defs, depth + 1);\n return value;\n }\n\n // Unstructured \u2014 parse JSON string\n if (isUnstructuredSchema(schema)) {\n if (typeof value === 'string') {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n return value;\n }\n\n // Type form \u2014 integer coercion\n if (schema.type && INTEGER_TYPE_SET.has(schema.type)) {\n if (typeof value === 'number' && !Number.isInteger(value))\n return Math.round(value);\n return value;\n }\n\n // Elements form\n if (schema.elements) {\n const el = schema.elements;\n const arr = Array.isArray(value) ? value : [value];\n return arr.map((item) => coerceToJTDFC(item, el, defs, depth + 1));\n }\n\n // Properties form\n if (\n (schema.properties || schema.optionalProperties) &&\n typeof value === 'object' &&\n value !== null\n ) {\n const obj = { ...(value as Record<string, unknown>) };\n if (schema.properties) {\n for (const [k, s] of Object.entries(schema.properties)) {\n if (k in obj) obj[k] = coerceToJTDFC(obj[k], s, defs, depth + 1);\n }\n }\n if (schema.optionalProperties) {\n for (const [k, s] of Object.entries(schema.optionalProperties)) {\n if (k in obj) obj[k] = coerceToJTDFC(obj[k], s, defs, depth + 1);\n }\n }\n return obj;\n }\n\n // Values form \u2014 {key, value}[] array \u2192 Record\n if (schema.values && Array.isArray(value)) {\n const result: Record<string, unknown> = {};\n for (const entry of value) {\n if (typeof entry === 'object' && entry !== null) {\n const rec = entry as Record<string, unknown>;\n const key = rec.key as string;\n result[key] = coerceToJTDFC(rec.value, schema.values, defs, depth + 1);\n }\n }\n return result;\n }\n\n // Discriminator form \u2014 flat object, coerce variant fields\n if (\n schema.discriminator &&\n schema.mapping &&\n typeof value === 'object' &&\n value !== null\n ) {\n const rec = value as Record<string, unknown>;\n const discKey = schema.discriminator;\n const tag = rec[discKey];\n if (typeof tag !== 'string') return value;\n const variantSchema = schema.mapping[tag];\n\n if (!variantSchema) return value;\n\n const result: Record<string, unknown> = { [discKey]: tag };\n\n if (variantSchema.properties) {\n for (const [k, propSchema] of Object.entries(variantSchema.properties)) {\n if (k in rec) {\n result[k] = coerceToJTDFC(rec[k], propSchema, defs, depth + 1);\n }\n }\n }\n\n if (variantSchema.optionalProperties) {\n for (const [k, propSchema] of Object.entries(\n variantSchema.optionalProperties\n )) {\n if (k in rec) {\n result[k] = coerceToJTDFC(rec[k], propSchema, defs, depth + 1);\n }\n }\n }\n\n return result;\n }\n\n return value;\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// Public API\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * Converts JTD schemas or Adapt signal descriptors into Gemini-compatible\n * JSON Schema, and converts raw LLM output back into validated data.\n *\n * ### JTD form mapping\n * - **type** -- mapped to the corresponding JSON Schema `type` (`string`,\n * `number`, `integer`, `boolean`). Integer sub-types (int8 .. uint32) add\n * `minimum`/`maximum` bounds. `timestamp` becomes `string` with\n * `format: \"date-time\"`.\n * - **enum** -- mapped to `type: \"string\"` with an `enum` array.\n * - **elements** -- mapped to `type: \"array\"` with `items`.\n * - **properties / optionalProperties** -- mapped to `type: \"object\"` with\n * `properties`, `required`, and `additionalProperties: false`. Optional\n * properties are included but not listed in `required`.\n * - **values** -- mapped to `type: \"object\"` with `additionalProperties` set\n * to the value schema (Gemini supports free-form object keys natively).\n * - **discriminator** -- mapped to a wrapper object: a `type: \"string\"` enum\n * property for the discriminator tag, plus one property per variant. The\n * LLM is instructed via `description` to populate only the property\n * matching the chosen tag. (Gemini does not support `anyOf`.)\n *\n * ### MIME formats\n * MIME signal formats become `type: \"string\"` with a description requesting\n * base64-encoded content. On {@link convert}, the base64 string is decoded\n * into a {@link ConvertSuccessBinary} result.\n *\n * ### Serialization\n * `JSON.stringify(conv)` produces a JSON-safe object via {@link toJSON}.\n * Use {@link fromJSON} to restore a fully functional instance from the\n * serialized form (string or parsed object).\n *\n * ```ts\n * const conv = GeminiStructuredOutputConversion.fromJTD(jtdSchema);\n * conv.schema; // Gemini JSON Schema to pass to the LLM\n * conv.convert(output); // coerce + validate -> ConvertResult\n *\n * // Serialize & restore\n * const cached = JSON.stringify(conv);\n * const restored = GeminiStructuredOutputConversion.fromJSON(cached);\n * ```\n */\nexport class GeminiStructuredOutputConversion<\n T extends ConvertSuccess = ConvertSuccess\n> {\n /** The Gemini-specific JSON Schema to pass to the Gemini API as the response schema. */\n readonly schema: GeminiSchema;\n /**\n * The original JTD schema used to build this conversion.\n * Set when created via {@link fromJTD}; `undefined` when created via {@link fromSignal}.\n */\n readonly jtdSchema?: JTDSchemaJson;\n /**\n * The original signal descriptor used to build this conversion.\n * Set when created via {@link fromSignal}; `undefined` when created via {@link fromJTD}.\n */\n readonly descriptor?: SignalDescriptorJson;\n /**\n * The original signal descriptors used to build this conversion.\n * Set when created via {@link fromSignals}; `undefined` otherwise.\n */\n readonly descriptors?: SignalDescriptorJson[];\n /** When true, MIME signals use file-reference schemas instead of base64. */\n readonly referenceMode: boolean;\n\n // \u2500\u2500 Factories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Build a conversion from a JTD schema.\n *\n * Recursively converts every JTD form into the equivalent Gemini JSON Schema\n * node. Definitions (`jtdSchema.definitions`) are resolved inline because\n * Gemini does not support `$ref`.\n *\n * @param jtdSchema - A valid JTD schema (RFC 8927).\n * @returns A new {@link GeminiStructuredOutputConversion} with `jtdSchema` set.\n * @throws If the schema contains an unknown JTD type, an unresolved `ref`,\n * or circular / excessively deep refs (exceeding 32 levels).\n */\n static fromJTD(\n jtdSchema: JTDSchemaJson\n ): GeminiStructuredOutputConversion<ConvertSuccessJson> {\n let schema = convertNode(jtdSchema, jtdSchema.definitions, 0);\n\n // Wrap root discriminated union \u2014 providers require type:\"object\" at\n // the function parameter root; bare anyOf is rejected.\n if (jtdSchema.discriminator && jtdSchema.mapping) {\n schema = {\n type: 'object',\n properties: { input: schema },\n required: ['input']\n };\n }\n\n return new GeminiStructuredOutputConversion(schema, jtdSchema, undefined);\n }\n\n /**\n * Build a conversion from an Adapt signal descriptor.\n *\n * If the descriptor has a single format, its schema is used directly\n * (unwrapped). If it has multiple formats, a wrapper object schema is\n * produced with `format_0`, `format_1`, ... properties -- the LLM must\n * populate exactly one. MIME formats become base64 string schemas.\n *\n * @param descriptor - An Adapt signal descriptor with one or more formats.\n * @returns A new {@link GeminiStructuredOutputConversion} with `descriptor` set.\n * @throws If the descriptor has no formats, or a format has neither\n * `jtdSchema` nor `mimeType`.\n */\n static fromSignal(\n descriptor: SignalDescriptorJson,\n referenceMode?: boolean\n ): GeminiStructuredOutputConversion<\n ConvertSuccessJson | ConvertSuccessBinary\n > {\n const schema = buildSignalSchema(descriptor);\n\n if (referenceMode) {\n const refSchema = buildReferenceAnyOf(\n schema,\n descriptor.description\n ) as GeminiSchema;\n return new GeminiStructuredOutputConversion(\n refSchema,\n undefined,\n descriptor\n );\n }\n\n return new GeminiStructuredOutputConversion(schema, undefined, descriptor);\n }\n\n /**\n * Build a conversion from multiple signal descriptors.\n *\n * Produces an object schema with one property per signal, suitable for\n * use as structured output parameters. Each signal's schema is built via\n * {@link fromSignal} internally.\n *\n * Optional signals are omitted from `required` (Gemini does not use the\n * required+nullable pattern).\n *\n * Receiver/transceiver compatible:\n * ```ts\n * const conv = GeminiStructuredOutputConversion.fromSignals(\n * receiver.signals!,\n * receiver.description,\n * receiver.name\n * );\n * ```\n *\n * @param signals - Array of signal descriptors.\n * @param description - Optional description for the compound schema.\n * @param label - Optional label (mapped to `title` on the schema).\n * @returns A new {@link GeminiStructuredOutputConversion} with `descriptors` set.\n * @throws If any signal has no formats or an invalid format.\n */\n static fromSignals(\n signals: SignalDescriptorJson[],\n description?: string,\n label?: string,\n referenceMode?: boolean\n ): GeminiStructuredOutputConversion<ConvertSuccessSignals> {\n const properties: Record<string, GeminiSchema> = {};\n const required: string[] = [];\n\n for (const signal of signals) {\n if (!signal.name) continue;\n const conv = GeminiStructuredOutputConversion.fromSignal(\n signal,\n referenceMode\n );\n properties[signal.name] = conv.schema;\n if (!signal.optional) required.push(signal.name);\n }\n\n const schema: GeminiSchema = {\n type: 'object',\n properties,\n required: required.length > 0 ? required : undefined,\n additionalProperties: false\n };\n if (description) schema.description = description;\n if (label) schema.title = label;\n\n return new GeminiStructuredOutputConversion(\n schema,\n undefined,\n undefined,\n signals,\n referenceMode\n );\n }\n\n /**\n * Restore a {@link GeminiStructuredOutputConversion} from its serialized form.\n *\n * Accepts either the JSON string produced by `JSON.stringify(conv)` or the\n * already-parsed plain object. The original `jtdSchema` or `descriptor` is\n * re-processed through the corresponding factory, so the restored instance\n * is fully functional.\n *\n * @param data - A JSON string or parsed object previously produced by {@link toJSON}.\n * @returns A fully reconstructed {@link GeminiStructuredOutputConversion}.\n * @throws If the serialized data contains neither `jtdSchema` nor `descriptor`.\n */\n static fromJSON(\n data: string | Record<string, unknown>\n ): GeminiStructuredOutputConversion {\n const parsed = typeof data === 'string' ? JSON.parse(data) : data;\n if (parsed.descriptors) {\n return GeminiStructuredOutputConversion.fromSignals(\n parsed.descriptors as SignalDescriptorJson[],\n parsed.description as string | undefined,\n parsed.label as string | undefined,\n parsed.referenceMode as boolean | undefined\n );\n }\n if (parsed.descriptor) {\n return GeminiStructuredOutputConversion.fromSignal(\n parsed.descriptor as SignalDescriptorJson\n );\n }\n if (parsed.jtdSchema) {\n return GeminiStructuredOutputConversion.fromJTD(\n parsed.jtdSchema as JTDSchemaJson\n );\n }\n throw new Error(\n 'Cannot deserialize: missing jtdSchema, descriptor, or descriptors'\n );\n }\n\n // \u2500\u2500 Constructor (private) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private constructor(\n schema: GeminiSchema,\n jtdSchema: JTDSchemaJson | undefined,\n descriptor: SignalDescriptorJson | undefined,\n descriptors?: SignalDescriptorJson[],\n referenceMode?: boolean\n ) {\n this.schema = schema;\n this.jtdSchema = jtdSchema;\n this.descriptor = descriptor;\n this.descriptors = descriptors;\n this.referenceMode = referenceMode ?? false;\n }\n\n // \u2500\u2500 Convert \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Convert raw LLM output into a validated result.\n *\n * For JTD-based conversions, the value is coerced (e.g. non-integer numbers\n * are rounded for integer types, discriminator wrapper objects are unwrapped\n * into flat tagged unions), then validated against the original JTD schema.\n * Returns {@link ConvertSuccessJson} on success.\n *\n * For MIME-based conversions, the value is expected to be a base64 string\n * which is decoded into raw bytes. Returns {@link ConvertSuccessBinary} on\n * success.\n *\n * For multi-format signals, the first non-null `format_N` property is\n * selected and converted individually.\n *\n * @param value - The value to convert. JSON for JTD, or `{ data, mimeType, filename }` for MIME.\n * @returns A {@link ConvertResult} indicating success or failure with errors.\n */\n convert = (value: unknown): T | ConvertFailure => {\n if (this.descriptors) {\n return sharedConvertSignalsOutput(\n this.descriptors,\n value,\n coerceToJTD\n ) as T | ConvertFailure;\n }\n if (this.jtdSchema) {\n let val = value;\n if (\n this.jtdSchema.discriminator &&\n this.jtdSchema.mapping &&\n typeof val === 'object' &&\n val !== null &&\n 'input' in val\n ) {\n val = (val as Record<string, unknown>).input;\n }\n return sharedConvertJTDOutput(this.jtdSchema, val, coerceToJTD) as\n | T\n | ConvertFailure;\n }\n if (this.descriptor) {\n return sharedConvertSignalOutput(this.descriptor, value, coerceToJTD) as\n | T\n | ConvertFailure;\n }\n return { success: false, errors: ['No source schema available'] };\n };\n\n // \u2500\u2500 Serialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Produce a JSON-serializable representation of this conversion.\n *\n * The output includes the Gemini `schema` and either the original\n * `jtdSchema` or `descriptor` (whichever was used to create this instance),\n * which is sufficient to fully restore the conversion via {@link fromJSON}.\n *\n * Called automatically by `JSON.stringify(conv)`.\n */\n toJSON() {\n if (this.descriptors) {\n const result: Record<string, unknown> = {\n schema: this.schema,\n descriptors: this.descriptors\n };\n if (this.schema.description) result.description = this.schema.description;\n if (this.schema.title) result.label = this.schema.title;\n if (this.referenceMode) result.referenceMode = true;\n return result;\n }\n if (this.descriptor) {\n return { schema: this.schema, descriptor: this.descriptor };\n }\n return { schema: this.schema, jtdSchema: this.jtdSchema };\n }\n}\n\n// \u2500\u2500 Signal schema builder \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildSignalSchema(descriptor: SignalDescriptorJson): GeminiSchema {\n const formats = descriptor.formats as SignalFormatJson[] | undefined;\n if (!formats || formats.length === 0) {\n throw new Error('Signal descriptor has no formats');\n }\n\n if (formats.length === 1) {\n const [fmt] = formats;\n const schema = buildFormatSchema(fmt);\n if (descriptor.label) schema.title = descriptor.label;\n if (descriptor.description) schema.description = descriptor.description;\n return schema;\n }\n\n // Multi-format: use anyOf with one variant per format\n const variants: GeminiSchema[] = [];\n for (const fmt of formats) {\n const fmtSchema = buildFormatSchema(fmt);\n if (fmt.jtdSchema) {\n fmtSchema.description = fmtSchema.description\n ? `Structured JSON data. ${fmtSchema.description}`\n : 'Structured JSON data.';\n }\n variants.push(fmtSchema);\n }\n\n const baseDesc = descriptor.description ?? '';\n const schema: GeminiSchema = {\n anyOf: variants,\n description: baseDesc || undefined\n };\n\n return schema;\n}\n\nfunction buildFormatSchema(fmt: SignalFormatJson): GeminiSchema {\n if (fmt.jtdSchema)\n return convertNode(fmt.jtdSchema, fmt.jtdSchema.definitions, 0);\n if (fmt.mimeType) return convertMimeFormat(fmt.mimeType);\n throw new Error('Signal format has neither jtdSchema nor mimeType');\n}\n\nfunction convertMimeFormat(mimeType: string): GeminiSchema {\n return {\n type: 'object',\n description: `Binary content (accepted MIME type: ${mimeType}).`,\n properties: {\n data: {\n type: 'string',\n description: 'The base64-encoded binary content.'\n },\n filename: {\n type: 'string',\n description:\n 'Filename for the content (e.g. \"report.pdf\", \"image.png\").'\n },\n mimeType: {\n type: 'string',\n description: `The concrete MIME type of the content. Must match: ${mimeType}.`\n }\n },\n required: ['data', 'mimeType', 'filename'],\n additionalProperties: false\n };\n}\n\n// \u2500\u2500 FC Signal schema builders \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildSignalSchemaFC(descriptor: SignalDescriptorJson): GeminiFCSchema {\n const formats = descriptor.formats as SignalFormatJson[] | undefined;\n if (!formats || formats.length === 0) {\n throw new Error('Signal descriptor has no formats');\n }\n\n if (formats.length === 1) {\n const [fmt] = formats;\n const schema = buildFormatSchemaFC(fmt);\n if (descriptor.label) schema.title = descriptor.label;\n if (descriptor.description) schema.description = descriptor.description;\n return schema;\n }\n\n // Multi-format: use anyOf with one variant per format\n const variants: GeminiFCSchema[] = [];\n for (const fmt of formats) {\n const fmtSchema = buildFormatSchemaFC(fmt);\n if (fmt.jtdSchema) {\n fmtSchema.description = fmtSchema.description\n ? `Structured JSON data. ${fmtSchema.description}`\n : 'Structured JSON data.';\n }\n variants.push(fmtSchema);\n }\n\n const baseDesc = descriptor.description ?? '';\n const schema: GeminiFCSchema = {\n anyOf: variants,\n description: baseDesc || undefined\n };\n\n return schema;\n}\n\nfunction buildFormatSchemaFC(fmt: SignalFormatJson): GeminiFCSchema {\n if (fmt.jtdSchema)\n return convertNodeFC(fmt.jtdSchema, fmt.jtdSchema.definitions, 0);\n if (fmt.mimeType) return convertMimeFormatFC(fmt.mimeType);\n throw new Error('Signal format has neither jtdSchema nor mimeType');\n}\n\nfunction convertMimeFormatFC(mimeType: string): GeminiFCSchema {\n return {\n type: 'object',\n description: `Binary content (accepted MIME type: ${mimeType}).`,\n properties: {\n data: {\n type: 'string',\n description: 'The base64-encoded binary content.'\n },\n filename: {\n type: 'string',\n description:\n 'Filename for the content (e.g. \"report.pdf\", \"image.png\").'\n },\n mimeType: {\n type: 'string',\n description: `The concrete MIME type of the content. Must match: ${mimeType}.`\n }\n },\n required: ['data', 'mimeType', 'filename']\n };\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// GeminiFunctionCallingConversion\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n/**\n * Converts JTD schemas or Adapt signal descriptors into Gemini v1beta Schema\n * protobuf-compatible schemas for use with function calling `parameters`.\n *\n * ### Differences from {@link GeminiStructuredOutputConversion}\n * - Uses `nullable: true` instead of `[\"type\", \"null\"]` type arrays\n * - Does NOT emit `additionalProperties`\n * - Uses `anyOf` for discriminator unions instead of wrapper objects\n * - Uses key-value array pattern for `values` form (maps/dictionaries)\n * - Unstructured/empty schemas are encoded as JSON strings\n *\n * ### JTD form mapping\n * - **type** -- mapped to the corresponding JSON Schema `type` with\n * `minimum`/`maximum` for integers, `format: \"date-time\"` for timestamps.\n * - **enum** -- mapped to `type: \"string\"` with an `enum` array.\n * - **elements** -- mapped to `type: \"array\"` with `items`.\n * - **properties / optionalProperties** -- mapped to `type: \"object\"` with\n * - **values** -- mapped to `type: \"array\"` with items of\n * `{key: string, value: ...}` objects (key-value array pattern).\n * - **discriminator** -- mapped to `anyOf` with single-value `enum` for the\n * discriminator tag on each variant.\n * - **empty / unstructured** -- mapped to `type: \"string\"` with a description\n * requesting JSON-encoded content.\n *\n * ### MIME formats\n * MIME signal formats become `type: \"object\"` with `data` and `filename`\n * properties (same as structured output). On {@link convert}, the base64\n * string is decoded into a {@link ConvertSuccessBinary} result.\n *\n * ### Serialization\n * `JSON.stringify(conv)` produces a JSON-safe object via {@link toJSON}.\n * The serialized form includes `mode: 'fc'` to distinguish it from\n * {@link GeminiStructuredOutputConversion} during deserialization.\n *\n * ```ts\n * const conv = GeminiFunctionCallingConversion.fromJTD(jtdSchema);\n * conv.schema; // FC-compatible schema for FunctionDeclaration.parameters\n * conv.convert(output); // coerce + validate -> ConvertResult\n *\n * // Serialize & restore\n * const cached = JSON.stringify(conv);\n * const restored = GeminiFunctionCallingConversion.fromJSON(cached);\n * ```\n */\nexport class GeminiFunctionCallingConversion<\n T extends ConvertSuccess = ConvertSuccess\n> {\n /** The FC-specific schema to pass as `parameters` in a `FunctionDeclaration`. */\n readonly schema: GeminiFCSchema;\n /**\n * The original JTD schema used to build this conversion.\n * Set when created via {@link fromJTD}; `undefined` when created via {@link fromSignal}.\n */\n readonly jtdSchema?: JTDSchemaJson;\n /**\n * The original signal descriptor used to build this conversion.\n * Set when created via {@link fromSignal}; `undefined` when created via {@link fromJTD}.\n */\n readonly descriptor?: SignalDescriptorJson;\n /**\n * The original signal descriptors used to build this conversion.\n * Set when created via {@link fromSignals}; `undefined` otherwise.\n */\n readonly descriptors?: SignalDescriptorJson[];\n /** When true, MIME signals use file-reference schemas instead of base64. */\n readonly referenceMode: boolean;\n\n // \u2500\u2500 Factories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Build a conversion from a JTD schema.\n *\n * Recursively converts every JTD form into the equivalent Gemini FC schema\n * node. Definitions (`jtdSchema.definitions`) are resolved inline because\n * the FC protobuf schema does not support `$ref`.\n *\n * @param jtdSchema - A valid JTD schema (RFC 8927).\n * @returns A new {@link GeminiFunctionCallingConversion} with `jtdSchema` set.\n * @throws If the schema contains an unknown JTD type, an unresolved `ref`,\n * or circular / excessively deep refs (exceeding 32 levels).\n */\n static fromJTD(\n jtdSchema: JTDSchemaJson\n ): GeminiFunctionCallingConversion<ConvertSuccessJson> {\n let schema = convertNodeFC(jtdSchema, jtdSchema.definitions, 0);\n\n // Wrap root discriminated union \u2014 providers require type:\"object\" at\n // the function parameter root; bare anyOf is rejected.\n if (jtdSchema.discriminator && jtdSchema.mapping) {\n schema = {\n type: 'object',\n properties: { input: schema },\n required: ['input']\n };\n }\n\n return new GeminiFunctionCallingConversion(schema, jtdSchema, undefined);\n }\n\n /**\n * Build a conversion from an Adapt signal descriptor.\n *\n * If the descriptor has a single format, its schema is used directly\n * (unwrapped). If it has multiple formats, a wrapper object schema is\n * produced with `format_0`, `format_1`, ... properties -- the LLM must\n * populate exactly one. MIME formats become base64 string schemas.\n *\n * @param descriptor - An Adapt signal descriptor with one or more formats.\n * @returns A new {@link GeminiFunctionCallingConversion} with `descriptor` set.\n * @throws If the descriptor has no formats, or a format has neither\n * `jtdSchema` nor `mimeType`.\n */\n static fromSignal(\n descriptor: SignalDescriptorJson,\n referenceMode?: boolean\n ): GeminiFunctionCallingConversion<\n ConvertSuccessJson | ConvertSuccessBinary\n > {\n const schema = buildSignalSchemaFC(descriptor);\n\n if (referenceMode) {\n const refSchema = buildReferenceAnyOf(\n schema,\n descriptor.description,\n false\n ) as GeminiFCSchema;\n return new GeminiFunctionCallingConversion(\n refSchema,\n undefined,\n descriptor\n );\n }\n\n return new GeminiFunctionCallingConversion(schema, undefined, descriptor);\n }\n\n /**\n * Build a conversion from multiple signal descriptors.\n *\n * Produces an object schema with one property per signal, suitable for\n * use as function calling parameters. Each signal's schema is built via\n * {@link fromSignal} internally.\n *\n * Optional signals are omitted from `required`.\n *\n * Receiver/transceiver compatible:\n * ```ts\n * const conv = GeminiFunctionCallingConversion.fromSignals(\n * receiver.signals!,\n * receiver.description,\n * receiver.name\n * );\n * ```\n *\n * @param signals - Array of signal descriptors.\n * @param description - Optional description for the compound schema.\n * @param label - Optional label (mapped to `title` on the schema).\n * @returns A new {@link GeminiFunctionCallingConversion} with `descriptors` set.\n * @throws If any signal has no formats or an invalid format.\n */\n static fromSignals(\n signals: SignalDescriptorJson[],\n description?: string,\n label?: string,\n referenceMode?: boolean\n ): GeminiFunctionCallingConversion<ConvertSuccessSignals> {\n const properties: Record<string, GeminiFCSchema> = {};\n const required: string[] = [];\n\n for (const signal of signals) {\n if (!signal.name) continue;\n const conv = GeminiFunctionCallingConversion.fromSignal(\n signal,\n referenceMode\n );\n properties[signal.name] = conv.schema;\n if (!signal.optional) required.push(signal.name);\n }\n\n const schema: GeminiFCSchema = {\n type: 'object',\n properties,\n required: required.length > 0 ? required : undefined\n };\n if (description) schema.description = description;\n if (label) schema.title = label;\n\n return new GeminiFunctionCallingConversion(\n schema,\n undefined,\n undefined,\n signals,\n referenceMode\n );\n }\n\n /**\n * Restore a {@link GeminiFunctionCallingConversion} from its serialized form.\n *\n * Accepts either the JSON string produced by `JSON.stringify(conv)` or the\n * already-parsed plain object. The original `jtdSchema` or `descriptor` is\n * re-processed through the corresponding factory, so the restored instance\n * is fully functional.\n *\n * @param data - A JSON string or parsed object previously produced by {@link toJSON}.\n * @returns A fully reconstructed {@link GeminiFunctionCallingConversion}.\n * @throws If the serialized data contains neither `jtdSchema` nor `descriptor`,\n * or if `mode` is not `'fc'`.\n */\n static fromJSON(\n data: string | Record<string, unknown>\n ): GeminiFunctionCallingConversion {\n const parsed = typeof data === 'string' ? JSON.parse(data) : data;\n if (parsed.mode !== 'fc') {\n throw new Error(\n 'Cannot deserialize as GeminiFunctionCallingConversion: mode is not \"fc\"'\n );\n }\n if (parsed.descriptors) {\n return GeminiFunctionCallingConversion.fromSignals(\n parsed.descriptors as SignalDescriptorJson[],\n parsed.description as string | undefined,\n parsed.label as string | undefined,\n parsed.referenceMode as boolean | undefined\n );\n }\n if (parsed.descriptor) {\n return GeminiFunctionCallingConversion.fromSignal(\n parsed.descriptor as SignalDescriptorJson\n );\n }\n if (parsed.jtdSchema) {\n return GeminiFunctionCallingConversion.fromJTD(\n parsed.jtdSchema as JTDSchemaJson\n );\n }\n throw new Error(\n 'Cannot deserialize: missing jtdSchema, descriptor, or descriptors'\n );\n }\n\n // \u2500\u2500 Constructor (private) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private constructor(\n schema: GeminiFCSchema,\n jtdSchema: JTDSchemaJson | undefined,\n descriptor: SignalDescriptorJson | undefined,\n descriptors?: SignalDescriptorJson[],\n referenceMode?: boolean\n ) {\n this.schema = schema;\n this.jtdSchema = jtdSchema;\n this.descriptor = descriptor;\n this.descriptors = descriptors;\n this.referenceMode = referenceMode ?? false;\n }\n\n // \u2500\u2500 Convert \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Convert raw LLM output into a validated result.\n *\n * For JTD-based conversions, the value is coerced to conform to the original\n * JTD schema:\n * - Non-integer numbers are rounded for integer types.\n * - `{key, value}` arrays from the values form are reassembled into Records.\n * - Unstructured/empty schemas parse the raw JSON string via `JSON.parse`.\n * - Discriminator variants are coerced from flat objects.\n *\n * The coerced value is then validated against the JTD schema. Returns\n * {@link ConvertSuccessJson} on success.\n *\n * For MIME-based conversions, the value is expected to be a base64 string\n * which is decoded into raw bytes. Returns {@link ConvertSuccessBinary} on\n * success.\n *\n * For multi-format signals, the first non-null `format_N` property is\n * selected and converted individually.\n *\n * @param value - The value to convert. JSON for JTD, or `{ data, mimeType, filename }` for MIME.\n * @returns A {@link ConvertResult} indicating success or failure with errors.\n */\n convert = (value: unknown): T | ConvertFailure => {\n if (this.descriptors) {\n return sharedConvertSignalsOutput(\n this.descriptors,\n value,\n coerceToJTDFC\n ) as T | ConvertFailure;\n }\n if (this.jtdSchema) {\n let val = value;\n if (\n this.jtdSchema.discriminator &&\n this.jtdSchema.mapping &&\n typeof val === 'object' &&\n val !== null &&\n 'input' in val\n ) {\n val = (val as Record<string, unknown>).input;\n }\n return sharedConvertJTDOutput(this.jtdSchema, val, coerceToJTDFC) as\n | T\n | ConvertFailure;\n }\n if (this.descriptor) {\n return sharedConvertSignalOutput(\n this.descriptor,\n value,\n coerceToJTDFC\n ) as T | ConvertFailure;\n }\n return { success: false, errors: ['No source schema available'] };\n };\n\n // \u2500\u2500 Serialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Produce a JSON-serializable representation of this conversion.\n *\n * The output includes the FC `schema`, `mode: 'fc'`, and either the\n * original `jtdSchema` or `descriptor` (whichever was used to create this\n * instance), which is sufficient to fully restore the conversion via\n * {@link fromJSON}.\n *\n * Called automatically by `JSON.stringify(conv)`.\n */\n toJSON() {\n if (this.descriptors) {\n const result: Record<string, unknown> = {\n mode: 'fc',\n schema: this.schema,\n descriptors: this.descriptors\n };\n if (this.schema.description) result.description = this.schema.description;\n if (this.schema.title) result.label = this.schema.title;\n if (this.referenceMode) result.referenceMode = true;\n return result;\n }\n if (this.descriptor) {\n return { mode: 'fc', schema: this.schema, descriptor: this.descriptor };\n }\n return { mode: 'fc', schema: this.schema, jtdSchema: this.jtdSchema };\n }\n}\n\n// \u2500\u2500 Backward-compat alias \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** @deprecated Use {@link GeminiStructuredOutputConversion} instead. */\nexport { GeminiStructuredOutputConversion as GeminiConversion };\n\n// \u2500\u2500 Convenience aliases \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Convenience alias for {@link GeminiStructuredOutputConversion.fromJTD}.\n *\n * @param jtdSchema - A valid JTD schema (RFC 8927).\n * @returns A new {@link GeminiStructuredOutputConversion}.\n */\nexport function convertToGeminiSchema(\n jtdSchema: JTDSchemaJson\n): GeminiStructuredOutputConversion<ConvertSuccessJson> {\n return GeminiStructuredOutputConversion.fromJTD(jtdSchema);\n}\n\n/**\n * Convenience alias for {@link GeminiStructuredOutputConversion.fromSignal}.\n *\n * @param descriptor - An Adapt signal descriptor.\n * @returns A new {@link GeminiStructuredOutputConversion}.\n */\nexport function convertSignalToGeminiSchema(\n descriptor: SignalDescriptorJson\n): GeminiStructuredOutputConversion<ConvertSuccessJson | ConvertSuccessBinary> {\n return GeminiStructuredOutputConversion.fromSignal(descriptor);\n}\n\n/**\n * Convenience alias for {@link GeminiFunctionCallingConversion.fromJTD}.\n *\n * @param jtdSchema - A valid JTD schema (RFC 8927).\n * @returns A new {@link GeminiFunctionCallingConversion}.\n */\nexport function convertToGeminiFCSchema(\n jtdSchema: JTDSchemaJson\n): GeminiFunctionCallingConversion<ConvertSuccessJson> {\n return GeminiFunctionCallingConversion.fromJTD(jtdSchema);\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["isSchema", "isValidSchema", "jtdValidate", "INTEGER_TYPES", "formatInstancePath", "instancePath", "p", "i", "resolveSchemaParent", "schema", "schemaPath", "current", "resolveInstanceValue", "value", "key", "formatValidationError", "error", "path", "lastKey", "paramLabel", "jtdType", "enumValues", "discField", "discValue", "mapping", "validVariants", "validateJsonAgainstJTD", "definitions", "defs", "schemaForValidation", "isValidSchema", "jtdValidate", "MAX_VALIDATION_DEPTH", "MAX_VALIDATION_ERRORS", "err", "MAX_VALIDATION_DEPTH", "MAX_VALIDATION_ERRORS", "MAX_REF_DEPTH", "INTEGER_BOUNDS", "FLOAT32_BOUNDS", "UNSTRUCTURED_DESC", "VALUES_ARRAY_DESC", "nullableType", "baseType", "nullable", "isUnstructuredSchema", "schema", "isRootUnstructured", "defs", "resolved", "depth", "MAX_REF_DEPTH", "target", "convertJTDOutput", "jtdSchema", "json", "coerce", "defs", "coerced", "errors", "validateJsonAgainstJTD", "buildReferenceAnyOf", "valueSchema", "description", "strict", "desc", "valueVariant", "refVariant", "mimeTypeMatches", "concrete", "accepted", "c", "a", "convertMimeOutput", "acceptedMimeType", "value", "fields", "binaryData", "result", "convertSingleFormatOutput", "fmt", "convertSignalOutput", "descriptor", "formats", "allErrors", "convertSignalsOutput", "descriptors", "input", "rec", "signals", "err", "describeJTDType", "schema", "describeDefinitionForCycle", "defName", "def", "parts", "required", "optional", "key", "prop", "shown", "suffix", "METADATA_KNOWN_KEYS", "applyMetadata", "result", "schema", "meta", "titleCandidate", "extras", "key", "value", "suffix", "resolveRef", "defs", "depth", "MAX_REF_DEPTH", "resolved", "convertNode", "activeRefs", "visited", "refDef", "describeDefinitionForCycle", "existing", "isNullable", "intBounds", "INTEGER_BOUNDS", "nullableType", "FLOAT32_BOUNDS", "required", "propSchema", "discKey", "variants", "tag", "variantSchema", "v", "single", "applyMetadataFC", "convertNodeFC", "UNSTRUCTURED_DESC", "isRootUnstructured", "valueSchema", "VALUES_ARRAY_DESC", "mapping", "variantOut", "anyOfResult", "INTEGER_TYPE_SET", "coerceToJTD", "el", "item", "obj", "k", "s", "rec", "vs", "coerced", "coerceToJTDFC", "isUnstructuredSchema", "entry", "GeminiStructuredOutputConversion", "_GeminiStructuredOutputConversion", "jtdSchema", "descriptor", "referenceMode", "buildSignalSchema", "refSchema", "buildReferenceAnyOf", "signals", "description", "label", "properties", "signal", "conv", "data", "parsed", "descriptors", "convertSignalsOutput", "val", "convertJTDOutput", "convertSignalOutput", "formats", "fmt", "buildFormatSchema", "fmtSchema", "baseDesc", "convertMimeFormat", "mimeType", "buildSignalSchemaFC", "buildFormatSchemaFC", "convertMimeFormatFC", "GeminiFunctionCallingConversion", "_GeminiFunctionCallingConversion", "convertToGeminiSchema", "jtdSchema", "GeminiStructuredOutputConversion", "convertSignalToGeminiSchema", "descriptor", "convertToGeminiFCSchema", "GeminiFunctionCallingConversion"]
7
7
  }