@typia/utils 12.0.0-dev.20260309 → 12.0.0-dev.20260311

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/lib/converters/LlmSchemaConverter.d.ts +0 -1
  2. package/lib/converters/LlmSchemaConverter.js +4 -31
  3. package/lib/converters/LlmSchemaConverter.js.map +1 -1
  4. package/lib/converters/LlmSchemaConverter.mjs +2 -32
  5. package/lib/converters/LlmSchemaConverter.mjs.map +1 -1
  6. package/lib/http/HttpLlm.js +4 -5
  7. package/lib/http/HttpLlm.js.map +1 -1
  8. package/lib/http/HttpLlm.mjs +0 -1
  9. package/lib/http/HttpLlm.mjs.map +1 -1
  10. package/lib/http/internal/HttpLlmApplicationComposer.js +3 -4
  11. package/lib/http/internal/HttpLlmApplicationComposer.js.map +1 -1
  12. package/lib/http/internal/HttpLlmApplicationComposer.mjs +5 -2
  13. package/lib/http/internal/HttpLlmApplicationComposer.mjs.map +1 -1
  14. package/lib/index.mjs +9 -9
  15. package/lib/utils/LlmJson.mjs +9 -2
  16. package/lib/utils/LlmJson.mjs.map +1 -1
  17. package/lib/utils/internal/stringifyValidationFailure.js +17 -15
  18. package/lib/utils/internal/stringifyValidationFailure.js.map +1 -1
  19. package/lib/utils/internal/stringifyValidationFailure.mjs +17 -15
  20. package/lib/utils/internal/stringifyValidationFailure.mjs.map +1 -1
  21. package/lib/validators/internal/OpenApiOneOfValidator.mjs +5 -1
  22. package/lib/validators/internal/OpenApiOneOfValidator.mjs.map +1 -1
  23. package/package.json +2 -2
  24. package/src/converters/LlmSchemaConverter.ts +617 -647
  25. package/src/converters/OpenApiConverter.ts +285 -285
  26. package/src/converters/index.ts +5 -5
  27. package/src/converters/internal/LlmDescriptionInverter.ts +178 -178
  28. package/src/converters/internal/LlmParametersComposer.ts +52 -52
  29. package/src/converters/internal/OpenApiConstraintShifter.ts +154 -154
  30. package/src/converters/internal/OpenApiExclusiveEmender.ts +46 -46
  31. package/src/converters/internal/OpenApiV3Downgrader.ts +355 -355
  32. package/src/converters/internal/OpenApiV3Upgrader.ts +470 -470
  33. package/src/converters/internal/OpenApiV3_1Upgrader.ts +685 -685
  34. package/src/converters/internal/SwaggerV2Downgrader.ts +424 -424
  35. package/src/converters/internal/SwaggerV2Upgrader.ts +523 -523
  36. package/src/http/HttpError.ts +107 -107
  37. package/src/http/HttpLlm.ts +166 -167
  38. package/src/http/HttpMigration.ts +92 -92
  39. package/src/http/index.ts +3 -3
  40. package/src/http/internal/HttpLlmApplicationComposer.ts +360 -361
  41. package/src/http/internal/HttpLlmFunctionFetcher.ts +37 -37
  42. package/src/http/internal/HttpMigrateApplicationComposer.ts +56 -56
  43. package/src/http/internal/HttpMigrateRouteAccessor.ts +135 -135
  44. package/src/http/internal/HttpMigrateRouteComposer.ts +505 -505
  45. package/src/http/internal/HttpMigrateRouteFetcher.ts +203 -203
  46. package/src/index.ts +4 -4
  47. package/src/utils/ArrayUtil.ts +42 -42
  48. package/src/utils/LlmJson.ts +141 -141
  49. package/src/utils/MapUtil.ts +15 -15
  50. package/src/utils/NamingConvention.ts +205 -205
  51. package/src/utils/Singleton.ts +17 -17
  52. package/src/utils/StringUtil.ts +14 -14
  53. package/src/utils/dedent.ts +57 -57
  54. package/src/utils/index.ts +8 -8
  55. package/src/utils/internal/EndpointUtil.ts +44 -44
  56. package/src/utils/internal/JsonDescriptor.ts +70 -70
  57. package/src/utils/internal/OpenApiTypeCheckerBase.ts +822 -822
  58. package/src/utils/internal/coerceLlmArguments.ts +314 -314
  59. package/src/utils/internal/parseLenientJson.ts +894 -894
  60. package/src/utils/internal/stringifyValidationFailure.ts +415 -411
  61. package/src/validators/LlmTypeChecker.ts +402 -402
  62. package/src/validators/OpenApiTypeChecker.ts +297 -297
  63. package/src/validators/OpenApiV3TypeChecker.ts +70 -70
  64. package/src/validators/OpenApiV3_1TypeChecker.ts +86 -86
  65. package/src/validators/OpenApiValidator.ts +94 -94
  66. package/src/validators/SwaggerV2TypeChecker.ts +71 -71
  67. package/src/validators/functional/_isBigintString.ts +8 -8
  68. package/src/validators/functional/_isFormatByte.ts +7 -7
  69. package/src/validators/functional/_isFormatDate.ts +3 -3
  70. package/src/validators/functional/_isFormatDateTime.ts +4 -4
  71. package/src/validators/functional/_isFormatDuration.ts +4 -4
  72. package/src/validators/functional/_isFormatEmail.ts +4 -4
  73. package/src/validators/functional/_isFormatHostname.ts +4 -4
  74. package/src/validators/functional/_isFormatIdnEmail.ts +4 -4
  75. package/src/validators/functional/_isFormatIdnHostname.ts +4 -4
  76. package/src/validators/functional/_isFormatIpv4.ts +4 -4
  77. package/src/validators/functional/_isFormatIpv6.ts +4 -4
  78. package/src/validators/functional/_isFormatIri.ts +3 -3
  79. package/src/validators/functional/_isFormatIriReference.ts +4 -4
  80. package/src/validators/functional/_isFormatJsonPointer.ts +3 -3
  81. package/src/validators/functional/_isFormatPassword.ts +1 -1
  82. package/src/validators/functional/_isFormatRegex.ts +8 -8
  83. package/src/validators/functional/_isFormatRelativeJsonPointer.ts +4 -4
  84. package/src/validators/functional/_isFormatTime.ts +4 -4
  85. package/src/validators/functional/_isFormatUri.ts +6 -6
  86. package/src/validators/functional/_isFormatUriReference.ts +5 -5
  87. package/src/validators/functional/_isFormatUriTemplate.ts +4 -4
  88. package/src/validators/functional/_isFormatUrl.ts +4 -4
  89. package/src/validators/functional/_isFormatUuid.ts +3 -3
  90. package/src/validators/functional/_isUniqueItems.ts +159 -159
  91. package/src/validators/index.ts +14 -14
  92. package/src/validators/internal/IOpenApiValidatorContext.ts +17 -17
  93. package/src/validators/internal/OpenApiArrayValidator.ts +49 -49
  94. package/src/validators/internal/OpenApiBooleanValidator.ts +11 -11
  95. package/src/validators/internal/OpenApiConstantValidator.ts +11 -11
  96. package/src/validators/internal/OpenApiIntegerValidator.ts +49 -49
  97. package/src/validators/internal/OpenApiNumberValidator.ts +48 -48
  98. package/src/validators/internal/OpenApiObjectValidator.ts +83 -83
  99. package/src/validators/internal/OpenApiOneOfValidator.ts +309 -309
  100. package/src/validators/internal/OpenApiSchemaNamingRule.ts +124 -124
  101. package/src/validators/internal/OpenApiStationValidator.ts +115 -115
  102. package/src/validators/internal/OpenApiStringValidator.ts +88 -88
  103. package/src/validators/internal/OpenApiTupleValidator.ts +55 -55
@@ -1,314 +1,314 @@
1
- import { IJsonParseResult, ILlmSchema } from "@typia/interface";
2
-
3
- import { LlmTypeChecker } from "../../validators/LlmTypeChecker";
4
- import { parseLenientJson } from "./parseLenientJson";
5
-
6
- /**
7
- * Coerce LLM arguments by parsing double-stringified JSON values.
8
- *
9
- * When LLM produces stringified JSON for non-string schema types, this function
10
- * attempts to parse them using the lenient JSON parser.
11
- *
12
- * Only applies coercion when value is string but schema expects non-string.
13
- * Type validation is handled separately by `ILlmFunction.validate`.
14
- *
15
- * @param value Parsed JSON value (potentially with stringified nested values)
16
- * @param parameters LLM parameters schema
17
- * @returns Coerced value with double-stringified JSON parsed
18
- * @internal
19
- */
20
- export function coerceLlmArguments<T = unknown>(
21
- value: unknown,
22
- parameters: ILlmSchema.IParameters,
23
- ): T {
24
- return coerceValue(value, parameters, parameters.$defs) as T;
25
- }
26
-
27
- function coerceValue(
28
- value: unknown,
29
- schema: ILlmSchema,
30
- $defs: Record<string, ILlmSchema> | undefined,
31
- ): unknown {
32
- // Resolve reference
33
- if (LlmTypeChecker.isReference(schema)) {
34
- const key: string = schema.$ref.replace("#/$defs/", "");
35
- const resolved: ILlmSchema | undefined = $defs?.[key];
36
- if (resolved !== undefined) {
37
- return coerceValue(value, resolved, $defs);
38
- }
39
- return value;
40
- }
41
-
42
- // Handle anyOf
43
- if (LlmTypeChecker.isAnyOf(schema)) {
44
- // Value is string
45
- if (typeof value === "string") {
46
- // If string is in union, don't parse - it's valid as-is
47
- const hasString: boolean = schema.anyOf.some((s: ILlmSchema): boolean =>
48
- LlmTypeChecker.isString(resolveSchema(s, $defs)),
49
- );
50
- if (hasString) {
51
- return value;
52
- }
53
- // String value but no string in union - try to parse
54
- const parsed: IJsonParseResult<unknown> = parseLenientJson(value);
55
- if (parsed.success) {
56
- // Find uniquely matching schema via type + x-discriminator
57
- const matched: ILlmSchema | undefined = findMatchingAnyOfSchema(
58
- parsed.data,
59
- schema,
60
- $defs,
61
- );
62
- if (matched !== undefined) {
63
- return coerceValue(parsed.data, matched, $defs);
64
- }
65
- return parsed.data;
66
- }
67
- // JSON parse failed - try "n" -> false or null
68
- if (value.length === 1 && value.toLowerCase() === "n") {
69
- const hasBoolean: boolean = schema.anyOf.some(
70
- (s: ILlmSchema): boolean =>
71
- LlmTypeChecker.isBoolean(resolveSchema(s, $defs)),
72
- );
73
- const hasNull: boolean = schema.anyOf.some((s: ILlmSchema): boolean =>
74
- LlmTypeChecker.isNull(resolveSchema(s, $defs)),
75
- );
76
- if (hasBoolean && !hasNull) return false;
77
- else if (hasNull && !hasBoolean) return null;
78
- return value;
79
- }
80
- }
81
- // Value is object - find matching schema via discriminated union
82
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
83
- const matched: ILlmSchema | undefined = findMatchingObjectInAnyOf(
84
- value as Record<string, unknown>,
85
- schema,
86
- $defs,
87
- );
88
- if (matched !== undefined) {
89
- return coerceValue(value, matched, $defs);
90
- }
91
- return value;
92
- }
93
- // Value is array - find matching array schema (only if unambiguous)
94
- if (Array.isArray(value)) {
95
- const arraySchemas: ILlmSchema[] = schema.anyOf.filter(
96
- (s: ILlmSchema): boolean =>
97
- LlmTypeChecker.isArray(resolveSchema(s, $defs)),
98
- );
99
- if (arraySchemas.length === 1) {
100
- return coerceValue(value, arraySchemas[0]!, $defs);
101
- }
102
- // Multiple or no array schemas - skip coercion
103
- return value;
104
- }
105
- // Non-string primitive or no matching schema - return as-is
106
- return value;
107
- }
108
-
109
- // String schema - no coercion needed (value stays as-is)
110
- if (LlmTypeChecker.isString(schema)) {
111
- return value;
112
- }
113
-
114
- // Unknown schema - no coercion needed
115
- if (LlmTypeChecker.isUnknown(schema)) {
116
- return value;
117
- }
118
-
119
- // Value is string but schema is non-string - try to parse
120
- if (typeof value === "string") {
121
- const parsed: IJsonParseResult<unknown> = parseLenientJson(value);
122
- if (parsed.success) {
123
- // Continue coercion on the parsed value (for nested stringified values)
124
- return coerceValue(parsed.data, schema, $defs);
125
- }
126
- // JSON parse failed - try "n" -> false for boolean schema
127
- if (value.length === 1 && value.toLowerCase() === "n") {
128
- if (LlmTypeChecker.isNull(schema)) return null;
129
- if (LlmTypeChecker.isBoolean(schema)) return false;
130
- }
131
- // Parse failed, return original - validate will catch type error
132
- return value;
133
- }
134
-
135
- // Value is array and schema is array - recurse into items
136
- if (Array.isArray(value) && LlmTypeChecker.isArray(schema)) {
137
- return value.map((item: unknown): unknown =>
138
- coerceValue(item, schema.items, $defs),
139
- );
140
- }
141
-
142
- // Value is object and schema is object - recurse into properties
143
- if (
144
- typeof value === "object" &&
145
- value !== null &&
146
- !Array.isArray(value) &&
147
- LlmTypeChecker.isObject(schema)
148
- ) {
149
- return coerceObject(value as Record<string, unknown>, schema, $defs);
150
- }
151
-
152
- // Everything else (null, boolean, number, integer) - return as-is
153
- return value;
154
- }
155
-
156
- function coerceObject(
157
- value: Record<string, unknown>,
158
- schema: ILlmSchema.IObject,
159
- $defs: Record<string, ILlmSchema> | undefined,
160
- ): Record<string, unknown> {
161
- const result: Record<string, unknown> = {};
162
-
163
- // Coerce known properties
164
- for (const [key, propSchema] of Object.entries(schema.properties)) {
165
- if (key in value) {
166
- result[key] = coerceValue(value[key], propSchema, $defs);
167
- }
168
- }
169
-
170
- // Preserve additional properties - let validation handle rejection
171
- const additionalSchema: ILlmSchema | undefined =
172
- typeof schema.additionalProperties === "object"
173
- ? schema.additionalProperties
174
- : undefined;
175
-
176
- for (const key of Object.keys(value)) {
177
- if (!(key in schema.properties)) {
178
- result[key] = additionalSchema
179
- ? coerceValue(value[key], additionalSchema, $defs)
180
- : value[key];
181
- }
182
- }
183
-
184
- return result;
185
- }
186
-
187
- function resolveSchema(
188
- schema: ILlmSchema,
189
- $defs: Record<string, ILlmSchema> | undefined,
190
- ): ILlmSchema {
191
- if (LlmTypeChecker.isReference(schema)) {
192
- const key: string = schema.$ref.replace("#/$defs/", "");
193
- const resolved: ILlmSchema | undefined = $defs?.[key];
194
- if (resolved !== undefined) {
195
- return resolveSchema(resolved, $defs);
196
- }
197
- }
198
- return schema;
199
- }
200
-
201
- /**
202
- * Check if value roughly matches the expected schema type. Used for anyOf
203
- * matching after parsing.
204
- */
205
- function matchesSchemaType(
206
- value: unknown,
207
- schema: ILlmSchema,
208
- $defs: Record<string, ILlmSchema> | undefined,
209
- ): boolean {
210
- if (LlmTypeChecker.isReference(schema)) {
211
- const key: string = schema.$ref.replace("#/$defs/", "");
212
- const resolved: ILlmSchema | undefined = $defs?.[key];
213
- if (resolved) return matchesSchemaType(value, resolved, $defs);
214
- return false;
215
- }
216
- if (LlmTypeChecker.isNull(schema)) return value === null;
217
- if (LlmTypeChecker.isBoolean(schema)) return typeof value === "boolean";
218
- if (LlmTypeChecker.isInteger(schema))
219
- return typeof value === "number" && Number.isInteger(value);
220
- if (LlmTypeChecker.isNumber(schema)) return typeof value === "number";
221
- if (LlmTypeChecker.isString(schema)) return typeof value === "string";
222
- if (LlmTypeChecker.isArray(schema)) return Array.isArray(value);
223
- if (LlmTypeChecker.isObject(schema))
224
- return typeof value === "object" && value !== null && !Array.isArray(value);
225
- if (LlmTypeChecker.isUnknown(schema)) return true;
226
- if (LlmTypeChecker.isAnyOf(schema))
227
- return schema.anyOf.some((s) => matchesSchemaType(value, s, $defs));
228
- return false;
229
- }
230
-
231
- /**
232
- * Find the uniquely matching schema for a value among anyOf alternatives. Uses
233
- * `x-discriminator` for object disambiguation. Returns undefined if no unique
234
- * match can be determined.
235
- */
236
- function findMatchingAnyOfSchema(
237
- value: unknown,
238
- schema: ILlmSchema.IAnyOf,
239
- $defs: Record<string, ILlmSchema> | undefined,
240
- ): ILlmSchema | undefined {
241
- const matching: ILlmSchema[] = schema.anyOf.filter((s: ILlmSchema): boolean =>
242
- matchesSchemaType(value, s, $defs),
243
- );
244
- if (matching.length === 1) return matching[0];
245
- if (matching.length === 0) return undefined;
246
- // Multiple type matches - try x-discriminator for objects
247
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
248
- return findMatchingObjectInAnyOf(
249
- value as Record<string, unknown>,
250
- schema,
251
- $defs,
252
- );
253
- }
254
- return undefined;
255
- }
256
-
257
- /**
258
- * Find the matching object schema among anyOf using `x-discriminator`. If only
259
- * one object schema exists, returns it directly. If multiple exist but no
260
- * x-discriminator, gives up.
261
- */
262
- function findMatchingObjectInAnyOf(
263
- value: Record<string, unknown>,
264
- schema: ILlmSchema.IAnyOf,
265
- $defs: Record<string, ILlmSchema> | undefined,
266
- ): ILlmSchema | undefined {
267
- const objectSchemas: ILlmSchema[] = schema.anyOf.filter(
268
- (s: ILlmSchema): boolean =>
269
- LlmTypeChecker.isObject(resolveSchema(s, $defs)),
270
- );
271
- if (objectSchemas.length === 0) return undefined;
272
- if (objectSchemas.length === 1) return objectSchemas[0];
273
-
274
- // Multiple object schemas - require x-discriminator
275
- const discriminator: ILlmSchema.IAnyOf.IDiscriminator | undefined =
276
- schema["x-discriminator"];
277
- if (discriminator === undefined) return undefined;
278
-
279
- const key: string = discriminator.propertyName;
280
- const discriminatorValue: unknown = value[key];
281
-
282
- // Use mapping for direct $ref lookup
283
- if (
284
- discriminator.mapping !== undefined &&
285
- typeof discriminatorValue === "string"
286
- ) {
287
- const ref: string | undefined = discriminator.mapping[discriminatorValue];
288
- if (ref !== undefined) {
289
- for (const s of schema.anyOf) {
290
- if (LlmTypeChecker.isReference(s) && s.$ref === ref) {
291
- return s;
292
- }
293
- }
294
- }
295
- return undefined;
296
- }
297
-
298
- // No mapping - match by enum values on the discriminator property
299
- for (const s of objectSchemas) {
300
- const resolved: ILlmSchema = resolveSchema(s, $defs);
301
- if (!LlmTypeChecker.isObject(resolved)) continue;
302
- const propSchema: ILlmSchema | undefined = resolved.properties?.[key];
303
- if (propSchema === undefined) continue;
304
- const resolvedProp: ILlmSchema = resolveSchema(propSchema, $defs);
305
- if (
306
- LlmTypeChecker.isString(resolvedProp) &&
307
- resolvedProp.enum?.includes(discriminatorValue as string)
308
- ) {
309
- return s;
310
- }
311
- }
312
-
313
- return undefined;
314
- }
1
+ import { IJsonParseResult, ILlmSchema } from "@typia/interface";
2
+
3
+ import { LlmTypeChecker } from "../../validators/LlmTypeChecker";
4
+ import { parseLenientJson } from "./parseLenientJson";
5
+
6
+ /**
7
+ * Coerce LLM arguments by parsing double-stringified JSON values.
8
+ *
9
+ * When LLM produces stringified JSON for non-string schema types, this function
10
+ * attempts to parse them using the lenient JSON parser.
11
+ *
12
+ * Only applies coercion when value is string but schema expects non-string.
13
+ * Type validation is handled separately by `ILlmFunction.validate`.
14
+ *
15
+ * @param value Parsed JSON value (potentially with stringified nested values)
16
+ * @param parameters LLM parameters schema
17
+ * @returns Coerced value with double-stringified JSON parsed
18
+ * @internal
19
+ */
20
+ export function coerceLlmArguments<T = unknown>(
21
+ value: unknown,
22
+ parameters: ILlmSchema.IParameters,
23
+ ): T {
24
+ return coerceValue(value, parameters, parameters.$defs) as T;
25
+ }
26
+
27
+ function coerceValue(
28
+ value: unknown,
29
+ schema: ILlmSchema,
30
+ $defs: Record<string, ILlmSchema> | undefined,
31
+ ): unknown {
32
+ // Resolve reference
33
+ if (LlmTypeChecker.isReference(schema)) {
34
+ const key: string = schema.$ref.replace("#/$defs/", "");
35
+ const resolved: ILlmSchema | undefined = $defs?.[key];
36
+ if (resolved !== undefined) {
37
+ return coerceValue(value, resolved, $defs);
38
+ }
39
+ return value;
40
+ }
41
+
42
+ // Handle anyOf
43
+ if (LlmTypeChecker.isAnyOf(schema)) {
44
+ // Value is string
45
+ if (typeof value === "string") {
46
+ // If string is in union, don't parse - it's valid as-is
47
+ const hasString: boolean = schema.anyOf.some((s: ILlmSchema): boolean =>
48
+ LlmTypeChecker.isString(resolveSchema(s, $defs)),
49
+ );
50
+ if (hasString) {
51
+ return value;
52
+ }
53
+ // String value but no string in union - try to parse
54
+ const parsed: IJsonParseResult<unknown> = parseLenientJson(value);
55
+ if (parsed.success) {
56
+ // Find uniquely matching schema via type + x-discriminator
57
+ const matched: ILlmSchema | undefined = findMatchingAnyOfSchema(
58
+ parsed.data,
59
+ schema,
60
+ $defs,
61
+ );
62
+ if (matched !== undefined) {
63
+ return coerceValue(parsed.data, matched, $defs);
64
+ }
65
+ return parsed.data;
66
+ }
67
+ // JSON parse failed - try "n" -> false or null
68
+ if (value.length === 1 && value.toLowerCase() === "n") {
69
+ const hasBoolean: boolean = schema.anyOf.some(
70
+ (s: ILlmSchema): boolean =>
71
+ LlmTypeChecker.isBoolean(resolveSchema(s, $defs)),
72
+ );
73
+ const hasNull: boolean = schema.anyOf.some((s: ILlmSchema): boolean =>
74
+ LlmTypeChecker.isNull(resolveSchema(s, $defs)),
75
+ );
76
+ if (hasBoolean && !hasNull) return false;
77
+ else if (hasNull && !hasBoolean) return null;
78
+ return value;
79
+ }
80
+ }
81
+ // Value is object - find matching schema via discriminated union
82
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
83
+ const matched: ILlmSchema | undefined = findMatchingObjectInAnyOf(
84
+ value as Record<string, unknown>,
85
+ schema,
86
+ $defs,
87
+ );
88
+ if (matched !== undefined) {
89
+ return coerceValue(value, matched, $defs);
90
+ }
91
+ return value;
92
+ }
93
+ // Value is array - find matching array schema (only if unambiguous)
94
+ if (Array.isArray(value)) {
95
+ const arraySchemas: ILlmSchema[] = schema.anyOf.filter(
96
+ (s: ILlmSchema): boolean =>
97
+ LlmTypeChecker.isArray(resolveSchema(s, $defs)),
98
+ );
99
+ if (arraySchemas.length === 1) {
100
+ return coerceValue(value, arraySchemas[0]!, $defs);
101
+ }
102
+ // Multiple or no array schemas - skip coercion
103
+ return value;
104
+ }
105
+ // Non-string primitive or no matching schema - return as-is
106
+ return value;
107
+ }
108
+
109
+ // String schema - no coercion needed (value stays as-is)
110
+ if (LlmTypeChecker.isString(schema)) {
111
+ return value;
112
+ }
113
+
114
+ // Unknown schema - no coercion needed
115
+ if (LlmTypeChecker.isUnknown(schema)) {
116
+ return value;
117
+ }
118
+
119
+ // Value is string but schema is non-string - try to parse
120
+ if (typeof value === "string") {
121
+ const parsed: IJsonParseResult<unknown> = parseLenientJson(value);
122
+ if (parsed.success) {
123
+ // Continue coercion on the parsed value (for nested stringified values)
124
+ return coerceValue(parsed.data, schema, $defs);
125
+ }
126
+ // JSON parse failed - try "n" -> false for boolean schema
127
+ if (value.length === 1 && value.toLowerCase() === "n") {
128
+ if (LlmTypeChecker.isNull(schema)) return null;
129
+ if (LlmTypeChecker.isBoolean(schema)) return false;
130
+ }
131
+ // Parse failed, return original - validate will catch type error
132
+ return value;
133
+ }
134
+
135
+ // Value is array and schema is array - recurse into items
136
+ if (Array.isArray(value) && LlmTypeChecker.isArray(schema)) {
137
+ return value.map((item: unknown): unknown =>
138
+ coerceValue(item, schema.items, $defs),
139
+ );
140
+ }
141
+
142
+ // Value is object and schema is object - recurse into properties
143
+ if (
144
+ typeof value === "object" &&
145
+ value !== null &&
146
+ !Array.isArray(value) &&
147
+ LlmTypeChecker.isObject(schema)
148
+ ) {
149
+ return coerceObject(value as Record<string, unknown>, schema, $defs);
150
+ }
151
+
152
+ // Everything else (null, boolean, number, integer) - return as-is
153
+ return value;
154
+ }
155
+
156
+ function coerceObject(
157
+ value: Record<string, unknown>,
158
+ schema: ILlmSchema.IObject,
159
+ $defs: Record<string, ILlmSchema> | undefined,
160
+ ): Record<string, unknown> {
161
+ const result: Record<string, unknown> = {};
162
+
163
+ // Coerce known properties
164
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
165
+ if (key in value) {
166
+ result[key] = coerceValue(value[key], propSchema, $defs);
167
+ }
168
+ }
169
+
170
+ // Preserve additional properties - let validation handle rejection
171
+ const additionalSchema: ILlmSchema | undefined =
172
+ typeof schema.additionalProperties === "object"
173
+ ? schema.additionalProperties
174
+ : undefined;
175
+
176
+ for (const key of Object.keys(value)) {
177
+ if (!(key in schema.properties)) {
178
+ result[key] = additionalSchema
179
+ ? coerceValue(value[key], additionalSchema, $defs)
180
+ : value[key];
181
+ }
182
+ }
183
+
184
+ return result;
185
+ }
186
+
187
+ function resolveSchema(
188
+ schema: ILlmSchema,
189
+ $defs: Record<string, ILlmSchema> | undefined,
190
+ ): ILlmSchema {
191
+ if (LlmTypeChecker.isReference(schema)) {
192
+ const key: string = schema.$ref.replace("#/$defs/", "");
193
+ const resolved: ILlmSchema | undefined = $defs?.[key];
194
+ if (resolved !== undefined) {
195
+ return resolveSchema(resolved, $defs);
196
+ }
197
+ }
198
+ return schema;
199
+ }
200
+
201
+ /**
202
+ * Check if value roughly matches the expected schema type. Used for anyOf
203
+ * matching after parsing.
204
+ */
205
+ function matchesSchemaType(
206
+ value: unknown,
207
+ schema: ILlmSchema,
208
+ $defs: Record<string, ILlmSchema> | undefined,
209
+ ): boolean {
210
+ if (LlmTypeChecker.isReference(schema)) {
211
+ const key: string = schema.$ref.replace("#/$defs/", "");
212
+ const resolved: ILlmSchema | undefined = $defs?.[key];
213
+ if (resolved) return matchesSchemaType(value, resolved, $defs);
214
+ return false;
215
+ }
216
+ if (LlmTypeChecker.isNull(schema)) return value === null;
217
+ if (LlmTypeChecker.isBoolean(schema)) return typeof value === "boolean";
218
+ if (LlmTypeChecker.isInteger(schema))
219
+ return typeof value === "number" && Number.isInteger(value);
220
+ if (LlmTypeChecker.isNumber(schema)) return typeof value === "number";
221
+ if (LlmTypeChecker.isString(schema)) return typeof value === "string";
222
+ if (LlmTypeChecker.isArray(schema)) return Array.isArray(value);
223
+ if (LlmTypeChecker.isObject(schema))
224
+ return typeof value === "object" && value !== null && !Array.isArray(value);
225
+ if (LlmTypeChecker.isUnknown(schema)) return true;
226
+ if (LlmTypeChecker.isAnyOf(schema))
227
+ return schema.anyOf.some((s) => matchesSchemaType(value, s, $defs));
228
+ return false;
229
+ }
230
+
231
+ /**
232
+ * Find the uniquely matching schema for a value among anyOf alternatives. Uses
233
+ * `x-discriminator` for object disambiguation. Returns undefined if no unique
234
+ * match can be determined.
235
+ */
236
+ function findMatchingAnyOfSchema(
237
+ value: unknown,
238
+ schema: ILlmSchema.IAnyOf,
239
+ $defs: Record<string, ILlmSchema> | undefined,
240
+ ): ILlmSchema | undefined {
241
+ const matching: ILlmSchema[] = schema.anyOf.filter((s: ILlmSchema): boolean =>
242
+ matchesSchemaType(value, s, $defs),
243
+ );
244
+ if (matching.length === 1) return matching[0];
245
+ if (matching.length === 0) return undefined;
246
+ // Multiple type matches - try x-discriminator for objects
247
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
248
+ return findMatchingObjectInAnyOf(
249
+ value as Record<string, unknown>,
250
+ schema,
251
+ $defs,
252
+ );
253
+ }
254
+ return undefined;
255
+ }
256
+
257
+ /**
258
+ * Find the matching object schema among anyOf using `x-discriminator`. If only
259
+ * one object schema exists, returns it directly. If multiple exist but no
260
+ * x-discriminator, gives up.
261
+ */
262
+ function findMatchingObjectInAnyOf(
263
+ value: Record<string, unknown>,
264
+ schema: ILlmSchema.IAnyOf,
265
+ $defs: Record<string, ILlmSchema> | undefined,
266
+ ): ILlmSchema | undefined {
267
+ const objectSchemas: ILlmSchema[] = schema.anyOf.filter(
268
+ (s: ILlmSchema): boolean =>
269
+ LlmTypeChecker.isObject(resolveSchema(s, $defs)),
270
+ );
271
+ if (objectSchemas.length === 0) return undefined;
272
+ if (objectSchemas.length === 1) return objectSchemas[0];
273
+
274
+ // Multiple object schemas - require x-discriminator
275
+ const discriminator: ILlmSchema.IAnyOf.IDiscriminator | undefined =
276
+ schema["x-discriminator"];
277
+ if (discriminator === undefined) return undefined;
278
+
279
+ const key: string = discriminator.propertyName;
280
+ const discriminatorValue: unknown = value[key];
281
+
282
+ // Use mapping for direct $ref lookup
283
+ if (
284
+ discriminator.mapping !== undefined &&
285
+ typeof discriminatorValue === "string"
286
+ ) {
287
+ const ref: string | undefined = discriminator.mapping[discriminatorValue];
288
+ if (ref !== undefined) {
289
+ for (const s of schema.anyOf) {
290
+ if (LlmTypeChecker.isReference(s) && s.$ref === ref) {
291
+ return s;
292
+ }
293
+ }
294
+ }
295
+ return undefined;
296
+ }
297
+
298
+ // No mapping - match by enum values on the discriminator property
299
+ for (const s of objectSchemas) {
300
+ const resolved: ILlmSchema = resolveSchema(s, $defs);
301
+ if (!LlmTypeChecker.isObject(resolved)) continue;
302
+ const propSchema: ILlmSchema | undefined = resolved.properties?.[key];
303
+ if (propSchema === undefined) continue;
304
+ const resolvedProp: ILlmSchema = resolveSchema(propSchema, $defs);
305
+ if (
306
+ LlmTypeChecker.isString(resolvedProp) &&
307
+ resolvedProp.enum?.includes(discriminatorValue as string)
308
+ ) {
309
+ return s;
310
+ }
311
+ }
312
+
313
+ return undefined;
314
+ }