@hoangvu12/yomi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +191 -0
  2. package/dist/coerce.d.ts +8 -0
  3. package/dist/coerce.d.ts.map +1 -0
  4. package/dist/coerce.js +207 -0
  5. package/dist/coerce.js.map +1 -0
  6. package/dist/coercers/array.d.ts +20 -0
  7. package/dist/coercers/array.d.ts.map +1 -0
  8. package/dist/coercers/array.js +77 -0
  9. package/dist/coercers/array.js.map +1 -0
  10. package/dist/coercers/enum.d.ts +12 -0
  11. package/dist/coercers/enum.d.ts.map +1 -0
  12. package/dist/coercers/enum.js +62 -0
  13. package/dist/coercers/enum.js.map +1 -0
  14. package/dist/coercers/object.d.ts +23 -0
  15. package/dist/coercers/object.d.ts.map +1 -0
  16. package/dist/coercers/object.js +75 -0
  17. package/dist/coercers/object.js.map +1 -0
  18. package/dist/coercers/primitive.d.ts +33 -0
  19. package/dist/coercers/primitive.d.ts.map +1 -0
  20. package/dist/coercers/primitive.js +160 -0
  21. package/dist/coercers/primitive.js.map +1 -0
  22. package/dist/coercers/union.d.ts +35 -0
  23. package/dist/coercers/union.d.ts.map +1 -0
  24. package/dist/coercers/union.js +82 -0
  25. package/dist/coercers/union.js.map +1 -0
  26. package/dist/flags.d.ts +44 -0
  27. package/dist/flags.d.ts.map +1 -0
  28. package/dist/flags.js +42 -0
  29. package/dist/flags.js.map +1 -0
  30. package/dist/index.d.ts +81 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +125 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/parse.d.ts +17 -0
  35. package/dist/parse.d.ts.map +1 -0
  36. package/dist/parse.js +157 -0
  37. package/dist/parse.js.map +1 -0
  38. package/dist/types.d.ts +64 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +48 -0
  41. package/dist/types.js.map +1 -0
  42. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # Yomi (読み)
2
+
3
+ **Yomi** (pronounced "yoh-mee", 読み) means "reading" or "interpretation" in Japanese. This library interprets messy LLM output and coerces it to match your Zod schemas.
4
+
5
+ ## The Problem
6
+
7
+ LLMs don't return perfect JSON. They return:
8
+
9
+ ```json
10
+ {name: "John", age: "25",} // unquoted keys, trailing comma
11
+ ```
12
+
13
+ ```
14
+ Here's the user data: {"name": "John", "age": 25} // wrapped in text
15
+ ```
16
+
17
+ ```json
18
+ {"name": "John", "age": "25", "active": "yes"} // wrong types everywhere
19
+ ```
20
+
21
+ `JSON.parse()` fails. Even if it succeeds, your types are wrong.
22
+
23
+ ## The Solution
24
+
25
+ Yomi uses a two-phase approach inspired by [BAML](https://docs.boundaryml.com/)'s schema-aligned parsing:
26
+
27
+ 1. **Flexible JSON parsing** - Fix malformed JSON, extract from markdown/text
28
+ 2. **Schema-aligned coercion** - Walk your Zod schema and coerce values to match
29
+
30
+ ```ts
31
+ import { z } from "zod";
32
+ import { parse } from "@hoangvu12/yomi";
33
+
34
+ const User = z.object({
35
+ name: z.string(),
36
+ age: z.number(),
37
+ active: z.boolean(),
38
+ });
39
+
40
+ const result = parse(User, `{name: "John", age: "25", active: "yes"}`);
41
+
42
+ // result.success === true
43
+ // result.data.value === { name: "John", age: 25, active: true }
44
+ // result.data.flags === ["json_repaired", "string_to_number", "string_to_bool"]
45
+ ```
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ npm install @hoangvu12/yomi zod
51
+ # or
52
+ bun add @hoangvu12/yomi zod
53
+ ```
54
+
55
+ ## API
56
+
57
+ ### `parse(schema, input)`
58
+
59
+ Parse and coerce input to match schema. Returns a result object.
60
+
61
+ ```ts
62
+ const result = parse(UserSchema, rawInput);
63
+
64
+ if (result.success) {
65
+ console.log(result.data.value); // typed as z.infer<typeof UserSchema>
66
+ console.log(result.data.flags); // what transformations happened
67
+ } else {
68
+ console.log(result.error); // what went wrong
69
+ }
70
+ ```
71
+
72
+ ### `parseOrThrow(schema, input)`
73
+
74
+ Same as `parse`, but throws on failure. Returns the coerced value directly.
75
+
76
+ ```ts
77
+ const user = parseOrThrow(UserSchema, rawInput);
78
+ // user is typed as z.infer<typeof UserSchema>
79
+ ```
80
+
81
+ ### `coerce(schema, value)` / `coerceOrThrow(schema, value)`
82
+
83
+ Skip JSON parsing, just do schema coercion on an already-parsed value.
84
+
85
+ ```ts
86
+ const result = coerce(UserSchema, { name: "John", age: "25" });
87
+ ```
88
+
89
+ ## What It Fixes
90
+
91
+ ### JSON Parsing
92
+
93
+ | Input | Fixed |
94
+ |-------|-------|
95
+ | `{name: "John"}` | Unquoted keys |
96
+ | `{"name": "John",}` | Trailing commas |
97
+ | `// comment` | Comments |
98
+ | `'single quotes'` | Single quotes |
99
+ | `` ```json {...}``` `` | Markdown code blocks |
100
+ | `Here's the data: {...}` | Surrounding text |
101
+
102
+ ### Type Coercion
103
+
104
+ | From | To | Example |
105
+ |------|----|---------|
106
+ | `"123"` | `number` | `"25"` → `25` |
107
+ | `"12.5"` | `int` | `"12.5"` → `13` (rounded) |
108
+ | `123` | `string` | `123` → `"123"` |
109
+ | `"true"`, `"yes"`, `"1"` | `boolean` | → `true` |
110
+ | `"false"`, `"no"`, `"0"` | `boolean` | → `false` |
111
+ | `value` | `array` | `"x"` → `["x"]` |
112
+ | `[value]` | `single` | `["x"]` → `"x"` |
113
+ | `"PENDING"` | `enum` | Case-insensitive match |
114
+ | `null` | `undefined` | For optional fields |
115
+ | `{extra: ...}` | `object` | Extra keys ignored |
116
+
117
+ ## Flags
118
+
119
+ Every transformation is tracked. Use flags to:
120
+ - Log when coercion happens in production
121
+ - Detect if defaults were used vs explicit values
122
+ - Debug why parsing succeeded unexpectedly
123
+
124
+ ```ts
125
+ const result = parse(Schema, input);
126
+ if (result.success) {
127
+ for (const flag of result.data.flags) {
128
+ console.log(flag);
129
+ // { flag: "string_to_number" }
130
+ // { flag: "extra_keys_ignored", keys: ["confidence"] }
131
+ // { flag: "float_to_int", original: 12.5, rounded: 13 }
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### Available Flags
137
+
138
+ | Flag | Meaning |
139
+ |------|---------|
140
+ | `json_repaired` | jsonrepair fixed the JSON |
141
+ | `extracted_from_markdown` | Extracted from `` ```json ``` `` block |
142
+ | `extracted_from_text` | Extracted JSON from surrounding text |
143
+ | `string_to_number` | `"123"` → `123` |
144
+ | `string_to_bool` | `"true"` → `true` |
145
+ | `number_to_string` | `123` → `"123"` |
146
+ | `bool_to_string` | `true` → `"true"` |
147
+ | `float_to_int` | `12.5` → `13` |
148
+ | `single_to_array` | `x` → `[x]` |
149
+ | `array_to_single` | `[x]` → `x` |
150
+ | `null_to_undefined` | `null` → `undefined` |
151
+ | `extra_keys_ignored` | Object had extra properties |
152
+ | `missing_optional_key` | Optional field was missing |
153
+ | `default_used` | Used schema's default value |
154
+ | `enum_case_insensitive` | `"PENDING"` matched `"pending"` |
155
+
156
+ ## Supported Zod Types
157
+
158
+ - Primitives: `string`, `number`, `boolean`, `null`, `undefined`, `literal`
159
+ - Objects: `object`, `record`
160
+ - Arrays: `array`, `tuple`
161
+ - Unions: `union`, `discriminatedUnion`, `optional`, `nullable`
162
+ - Enums: `enum`, `nativeEnum`
163
+ - Modifiers: `default`, `catch`
164
+ - Passthrough: `any`, `unknown`
165
+
166
+ ## How It Works
167
+
168
+ ```
169
+ LLM Output (string)
170
+
171
+
172
+ ┌─────────────────┐
173
+ │ Flexible JSON │ ← jsonrepair + markdown extraction
174
+ │ Parser │
175
+ └────────┬────────┘
176
+ │ unknown
177
+
178
+ ┌─────────────────┐
179
+ │ Schema-Aligned │ ← walks Zod schema tree
180
+ │ Coercer │
181
+ └────────┬────────┘
182
+ │ { value: T, flags: Flag[] }
183
+
184
+ Result<T>
185
+ ```
186
+
187
+ The coercer recursively walks your Zod schema using `schema._def`, dispatching to type-specific coercion functions. Each coercer tries to interpret the input value as the expected type, recording flags when transformations occur.
188
+
189
+ ## License
190
+
191
+ MIT
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ import { type CoerceContext, type CoerceResult } from "./types.js";
3
+ /**
4
+ * Coerce a value to match a Zod schema.
5
+ * This is the main entry point for schema-aligned coercion.
6
+ */
7
+ export declare function coerceToSchema<T extends z.ZodTypeAny>(schema: T, value: unknown, ctx?: CoerceContext): CoerceResult<z.infer<T>>;
8
+ //# sourceMappingURL=coerce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coerce.d.ts","sourceRoot":"","sources":["../src/coerce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EAIlB,MAAM,YAAY,CAAC;AAcpB;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACnD,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,OAAO,EACd,GAAG,CAAC,EAAE,aAAa,GAClB,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAG1B"}
package/dist/coerce.js ADDED
@@ -0,0 +1,207 @@
1
+ import { z } from "zod";
2
+ import { createContext, failure, describeType, } from "./types.js";
3
+ import { coerceString, coerceNumber, coerceInt, coerceBoolean, coerceNull, coerceLiteral, } from "./coercers/primitive.js";
4
+ import { coerceArray, coerceTuple } from "./coercers/array.js";
5
+ import { coerceObject, coerceRecord } from "./coercers/object.js";
6
+ import { coerceUnion, coerceOptional, coerceNullable, coerceDefault } from "./coercers/union.js";
7
+ import { coerceEnum, coerceNativeEnum } from "./coercers/enum.js";
8
+ /**
9
+ * Coerce a value to match a Zod schema.
10
+ * This is the main entry point for schema-aligned coercion.
11
+ */
12
+ export function coerceToSchema(schema, value, ctx) {
13
+ const context = ctx ?? createContext();
14
+ return coerceZodType(schema, value, context);
15
+ }
16
+ /**
17
+ * Internal dispatcher for Zod types.
18
+ */
19
+ function coerceZodType(schema, value, ctx) {
20
+ const def = schema._def;
21
+ const typeName = def.typeName;
22
+ switch (typeName) {
23
+ // Primitives
24
+ case z.ZodFirstPartyTypeKind.ZodString:
25
+ return coerceString(value, ctx);
26
+ case z.ZodFirstPartyTypeKind.ZodNumber:
27
+ return coerceNumber(value, ctx);
28
+ case z.ZodFirstPartyTypeKind.ZodBigInt:
29
+ return coerceInt(value, ctx); // Use int coercion for bigint
30
+ case z.ZodFirstPartyTypeKind.ZodBoolean:
31
+ return coerceBoolean(value, ctx);
32
+ case z.ZodFirstPartyTypeKind.ZodNull:
33
+ return coerceNull(value, ctx);
34
+ case z.ZodFirstPartyTypeKind.ZodUndefined:
35
+ if (value === undefined || value === null) {
36
+ return { success: true, value: undefined, flags: ctx.flags };
37
+ }
38
+ return failure("Expected undefined", ctx, "undefined", describeType(value));
39
+ case z.ZodFirstPartyTypeKind.ZodVoid:
40
+ return { success: true, value: undefined, flags: ctx.flags };
41
+ case z.ZodFirstPartyTypeKind.ZodAny:
42
+ case z.ZodFirstPartyTypeKind.ZodUnknown:
43
+ return { success: true, value, flags: ctx.flags };
44
+ case z.ZodFirstPartyTypeKind.ZodNever:
45
+ return failure("ZodNever cannot match any value", ctx, "never", describeType(value));
46
+ // Literals
47
+ case z.ZodFirstPartyTypeKind.ZodLiteral:
48
+ return coerceLiteral(value, def.value, ctx);
49
+ // Arrays
50
+ case z.ZodFirstPartyTypeKind.ZodArray:
51
+ return coerceArray(value, (v, c) => coerceZodType(def.type, v, c), ctx);
52
+ // Tuples
53
+ case z.ZodFirstPartyTypeKind.ZodTuple: {
54
+ const items = def.items;
55
+ const coercers = items.map((item) => (v, c) => coerceZodType(item, v, c));
56
+ return coerceTuple(value, coercers, ctx);
57
+ }
58
+ // Objects
59
+ case z.ZodFirstPartyTypeKind.ZodObject: {
60
+ const shape = def.shape();
61
+ const objectSchema = {};
62
+ for (const [key, propSchema] of Object.entries(shape)) {
63
+ const isOptional = propSchema.isOptional();
64
+ const hasDefault = propSchema._def.typeName === z.ZodFirstPartyTypeKind.ZodDefault;
65
+ objectSchema[key] = {
66
+ coercer: (v, c) => coerceZodType(propSchema, v, c),
67
+ optional: isOptional,
68
+ ...(hasDefault ? { default: propSchema._def.defaultValue() } : {}),
69
+ };
70
+ }
71
+ return coerceObject(value, objectSchema, ctx);
72
+ }
73
+ // Records
74
+ case z.ZodFirstPartyTypeKind.ZodRecord:
75
+ return coerceRecord(value, (v, c) => coerceZodType(def.valueType, v, c), ctx);
76
+ // Maps
77
+ case z.ZodFirstPartyTypeKind.ZodMap:
78
+ // Treat as record for now
79
+ return coerceRecord(value, (v, c) => coerceZodType(def.valueType, v, c), ctx);
80
+ // Unions
81
+ case z.ZodFirstPartyTypeKind.ZodUnion: {
82
+ const options = def.options;
83
+ const coercers = options.map((opt) => (v, c) => coerceZodType(opt, v, c));
84
+ return coerceUnion(value, coercers, ctx);
85
+ }
86
+ case z.ZodFirstPartyTypeKind.ZodDiscriminatedUnion: {
87
+ const options = def.options;
88
+ const coercers = options.map((opt) => (v, c) => coerceZodType(opt, v, c));
89
+ return coerceUnion(value, coercers, ctx);
90
+ }
91
+ // Optionals and Nullables
92
+ case z.ZodFirstPartyTypeKind.ZodOptional:
93
+ return coerceOptional(value, (v, c) => coerceZodType(def.innerType, v, c), ctx);
94
+ case z.ZodFirstPartyTypeKind.ZodNullable:
95
+ return coerceNullable(value, (v, c) => coerceZodType(def.innerType, v, c), ctx);
96
+ // Defaults
97
+ case z.ZodFirstPartyTypeKind.ZodDefault:
98
+ return coerceDefault(value, (v, c) => coerceZodType(def.innerType, v, c), def.defaultValue(), ctx);
99
+ // Catch
100
+ case z.ZodFirstPartyTypeKind.ZodCatch: {
101
+ const result = coerceZodType(def.innerType, value, ctx);
102
+ if (result.success)
103
+ return result;
104
+ return { success: true, value: def.catchValue({ error: result.error, input: value }), flags: ctx.flags };
105
+ }
106
+ // Enums
107
+ case z.ZodFirstPartyTypeKind.ZodEnum:
108
+ return coerceEnum(value, def.values, ctx);
109
+ case z.ZodFirstPartyTypeKind.ZodNativeEnum:
110
+ return coerceNativeEnum(value, def.values, ctx);
111
+ // Effects/Transforms
112
+ case z.ZodFirstPartyTypeKind.ZodEffects: {
113
+ // First coerce the inner type
114
+ const innerResult = coerceZodType(def.schema, value, ctx);
115
+ if (!innerResult.success)
116
+ return innerResult;
117
+ // Then apply the effect
118
+ // Note: We only handle refinements here, not transforms
119
+ // Transforms would need async support
120
+ if (def.effect.type === "refinement") {
121
+ const refinement = def.effect.refinement;
122
+ try {
123
+ const isValid = refinement(innerResult.value);
124
+ if (!isValid) {
125
+ return failure("Refinement failed", ctx, "refined value", describeType(innerResult.value));
126
+ }
127
+ }
128
+ catch (e) {
129
+ return failure(`Refinement error: ${e instanceof Error ? e.message : String(e)}`, ctx, "refined value", describeType(innerResult.value));
130
+ }
131
+ }
132
+ return innerResult;
133
+ }
134
+ // Lazy
135
+ case z.ZodFirstPartyTypeKind.ZodLazy:
136
+ return coerceZodType(def.getter(), value, ctx);
137
+ // Branded
138
+ case z.ZodFirstPartyTypeKind.ZodBranded:
139
+ return coerceZodType(def.type, value, ctx);
140
+ // Pipeline
141
+ case z.ZodFirstPartyTypeKind.ZodPipeline: {
142
+ const inResult = coerceZodType(def.in, value, ctx);
143
+ if (!inResult.success)
144
+ return inResult;
145
+ return coerceZodType(def.out, inResult.value, ctx);
146
+ }
147
+ // Readonly
148
+ case z.ZodFirstPartyTypeKind.ZodReadonly:
149
+ return coerceZodType(def.innerType, value, ctx);
150
+ // Date - special handling
151
+ case z.ZodFirstPartyTypeKind.ZodDate: {
152
+ if (value instanceof Date) {
153
+ if (isNaN(value.getTime())) {
154
+ return failure("Invalid Date", ctx, "Date", "Invalid Date");
155
+ }
156
+ return { success: true, value, flags: ctx.flags };
157
+ }
158
+ if (typeof value === "string" || typeof value === "number") {
159
+ const date = new Date(value);
160
+ if (isNaN(date.getTime())) {
161
+ return failure(`Cannot parse "${value}" as Date`, ctx, "Date", describeType(value));
162
+ }
163
+ return { success: true, value: date, flags: ctx.flags };
164
+ }
165
+ return failure("Expected Date", ctx, "Date", describeType(value));
166
+ }
167
+ // Intersection - coerce to both types
168
+ case z.ZodFirstPartyTypeKind.ZodIntersection: {
169
+ const leftResult = coerceZodType(def.left, value, ctx);
170
+ if (!leftResult.success)
171
+ return leftResult;
172
+ const rightResult = coerceZodType(def.right, leftResult.value, ctx);
173
+ if (!rightResult.success)
174
+ return rightResult;
175
+ // Merge results for objects
176
+ if (typeof leftResult.value === "object" && typeof rightResult.value === "object") {
177
+ return { success: true, value: { ...leftResult.value, ...rightResult.value }, flags: ctx.flags };
178
+ }
179
+ return rightResult;
180
+ }
181
+ // Set
182
+ case z.ZodFirstPartyTypeKind.ZodSet: {
183
+ const arrayResult = coerceArray(value, (v, c) => coerceZodType(def.valueType, v, c), ctx);
184
+ if (!arrayResult.success)
185
+ return arrayResult;
186
+ return { success: true, value: new Set(arrayResult.value), flags: ctx.flags };
187
+ }
188
+ // Promise - just pass through
189
+ case z.ZodFirstPartyTypeKind.ZodPromise:
190
+ return { success: true, value, flags: ctx.flags };
191
+ // Function - just pass through
192
+ case z.ZodFirstPartyTypeKind.ZodFunction:
193
+ if (typeof value === "function") {
194
+ return { success: true, value, flags: ctx.flags };
195
+ }
196
+ return failure("Expected function", ctx, "function", describeType(value));
197
+ // Symbol
198
+ case z.ZodFirstPartyTypeKind.ZodSymbol:
199
+ if (typeof value === "symbol") {
200
+ return { success: true, value, flags: ctx.flags };
201
+ }
202
+ return failure("Expected symbol", ctx, "symbol", describeType(value));
203
+ default:
204
+ return failure(`Unsupported Zod type: ${typeName}`, ctx, typeName, describeType(value));
205
+ }
206
+ }
207
+ //# sourceMappingURL=coerce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coerce.js","sourceRoot":"","sources":["../src/coerce.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type CoerceContext, type CoerceResult } from "../types.js";
2
+ export type ElementCoercer<T> = (value: unknown, ctx: CoerceContext) => CoerceResult<T>;
3
+ /**
4
+ * LLMs often return a single item when we expect an array (especially when
5
+ * there's only one result). Rather than failing, we wrap it in an array.
6
+ */
7
+ export declare function coerceArray<T>(value: unknown, elementCoercer: ElementCoercer<T>, ctx: CoerceContext): CoerceResult<T[]>;
8
+ /**
9
+ * The reverse case: schema expects single value but LLM returned [value].
10
+ * If the array has exactly one element, unwrap it.
11
+ */
12
+ export declare function coerceArrayToSingle<T>(value: unknown, elementCoercer: ElementCoercer<T>, ctx: CoerceContext): CoerceResult<T>;
13
+ /**
14
+ * Tuples are fixed-length arrays where each position has a specific type.
15
+ * We validate length matches and coerce each element to its expected type.
16
+ */
17
+ export declare function coerceTuple<T extends unknown[]>(value: unknown, elementCoercers: {
18
+ [K in keyof T]: ElementCoercer<T[K]>;
19
+ }, ctx: CoerceContext): CoerceResult<T>;
20
+ //# sourceMappingURL=array.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"array.d.ts","sourceRoot":"","sources":["../../src/coercers/array.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EAKlB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC;AAExF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,KAAK,EAAE,OAAO,EACd,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC,EACjC,GAAG,EAAE,aAAa,GACjB,YAAY,CAAC,CAAC,EAAE,CAAC,CAiCnB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,KAAK,EAAE,OAAO,EACd,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC,EACjC,GAAG,EAAE,aAAa,GACjB,YAAY,CAAC,CAAC,CAAC,CAqBjB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,EAAE,EAC7C,KAAK,EAAE,OAAO,EACd,eAAe,EAAE;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,EACzD,GAAG,EAAE,aAAa,GACjB,YAAY,CAAC,CAAC,CAAC,CAgCjB"}
@@ -0,0 +1,77 @@
1
+ import { Flag } from "../flags.js";
2
+ import { childContext, success, failure, describeType, } from "../types.js";
3
+ /**
4
+ * LLMs often return a single item when we expect an array (especially when
5
+ * there's only one result). Rather than failing, we wrap it in an array.
6
+ */
7
+ export function coerceArray(value, elementCoercer, ctx) {
8
+ if (value === null || value === undefined) {
9
+ return failure("Expected array, got null/undefined", ctx, "array", describeType(value));
10
+ }
11
+ if (Array.isArray(value)) {
12
+ const results = [];
13
+ for (let i = 0; i < value.length; i++) {
14
+ // Each element gets its own path context for error reporting
15
+ const elementCtx = childContext(ctx, i);
16
+ const result = elementCoercer(value[i], elementCtx);
17
+ if (!result.success) {
18
+ return result;
19
+ }
20
+ results.push(result.value);
21
+ }
22
+ return success(results, ctx);
23
+ }
24
+ // Single value → wrap in array (common LLM behavior)
25
+ ctx.flags.push({ flag: Flag.SingleToArray });
26
+ const elementCtx = childContext(ctx, 0);
27
+ const result = elementCoercer(value, elementCtx);
28
+ if (!result.success) {
29
+ return result;
30
+ }
31
+ return success([result.value], ctx);
32
+ }
33
+ /**
34
+ * The reverse case: schema expects single value but LLM returned [value].
35
+ * If the array has exactly one element, unwrap it.
36
+ */
37
+ export function coerceArrayToSingle(value, elementCoercer, ctx) {
38
+ if (!Array.isArray(value)) {
39
+ return elementCoercer(value, ctx);
40
+ }
41
+ if (value.length === 0) {
42
+ return failure("Expected single value, got empty array", ctx, "single value", "empty array");
43
+ }
44
+ if (value.length > 1) {
45
+ return failure(`Expected single value, got array with ${value.length} elements`, ctx, "single value", `array[${value.length}]`);
46
+ }
47
+ ctx.flags.push({ flag: Flag.ArrayToSingle });
48
+ const elementCtx = childContext(ctx, 0);
49
+ return elementCoercer(value[0], elementCtx);
50
+ }
51
+ /**
52
+ * Tuples are fixed-length arrays where each position has a specific type.
53
+ * We validate length matches and coerce each element to its expected type.
54
+ */
55
+ export function coerceTuple(value, elementCoercers, ctx) {
56
+ if (!Array.isArray(value)) {
57
+ return failure("Expected tuple (array), got " + describeType(value), ctx, "tuple", describeType(value));
58
+ }
59
+ if (value.length !== elementCoercers.length) {
60
+ return failure(`Expected tuple of length ${elementCoercers.length}, got length ${value.length}`, ctx, `tuple[${elementCoercers.length}]`, `array[${value.length}]`);
61
+ }
62
+ const results = [];
63
+ for (let i = 0; i < elementCoercers.length; i++) {
64
+ const elementCtx = childContext(ctx, i);
65
+ const coercer = elementCoercers[i];
66
+ if (!coercer) {
67
+ return failure(`Missing coercer for index ${i}`, ctx, "coercer", "undefined");
68
+ }
69
+ const result = coercer(value[i], elementCtx);
70
+ if (!result.success) {
71
+ return result;
72
+ }
73
+ results.push(result.value);
74
+ }
75
+ return success(results, ctx);
76
+ }
77
+ //# sourceMappingURL=array.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"array.js","sourceRoot":"","sources":["../../src/coercers/array.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAGL,YAAY,EACZ,OAAO,EACP,OAAO,EACP,YAAY,GACb,MAAM,aAAa,CAAC;AAIrB;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,KAAc,EACd,cAAiC,EACjC,GAAkB;IAElB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,OAAO,CAAC,oCAAoC,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1F,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,OAAO,GAAQ,EAAE,CAAC;QAExB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,6DAA6D;YAC7D,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACxC,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;YAEpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QAED,OAAO,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,qDAAqD;IACrD,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAEjD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAc,EACd,cAAiC,EACjC,GAAkB;IAElB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,OAAO,CAAC,wCAAwC,EAAE,GAAG,EAAE,cAAc,EAAE,aAAa,CAAC,CAAC;IAC/F,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,OAAO,CACZ,yCAAyC,KAAK,CAAC,MAAM,WAAW,EAChE,GAAG,EACH,cAAc,EACd,SAAS,KAAK,CAAC,MAAM,GAAG,CACzB,CAAC;IACJ,CAAC;IAED,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACxC,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,KAAc,EACd,eAAyD,EACzD,GAAkB;IAElB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,8BAA8B,GAAG,YAAY,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1G,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe,CAAC,MAAM,EAAE,CAAC;QAC5C,OAAO,OAAO,CACZ,4BAA4B,eAAe,CAAC,MAAM,gBAAgB,KAAK,CAAC,MAAM,EAAE,EAChF,GAAG,EACH,SAAS,eAAe,CAAC,MAAM,GAAG,EAClC,SAAS,KAAK,CAAC,MAAM,GAAG,CACzB,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAc,EAAE,CAAC;IAE9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChD,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,6BAA6B,CAAC,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QAChF,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QAE7C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,OAAO,CAAC,OAAY,EAAE,GAAG,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type CoerceContext, type CoerceResult } from "../types.js";
2
+ /**
3
+ * LLMs often return enum values with wrong casing - "PENDING" instead of "pending".
4
+ * Rather than failing, we do case-insensitive matching and track when it happens.
5
+ */
6
+ export declare function coerceEnum<T extends string>(value: unknown, allowedValues: readonly T[], ctx: CoerceContext): CoerceResult<T>;
7
+ /**
8
+ * TypeScript enums have reverse mappings for numeric values.
9
+ * We filter those out and match against both keys and values.
10
+ */
11
+ export declare function coerceNativeEnum<T extends Record<string, string | number>>(value: unknown, enumObject: T, ctx: CoerceContext): CoerceResult<T[keyof T]>;
12
+ //# sourceMappingURL=enum.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enum.d.ts","sourceRoot":"","sources":["../../src/coercers/enum.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EAIlB,MAAM,aAAa,CAAC;AAErB;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EACzC,KAAK,EAAE,OAAO,EACd,aAAa,EAAE,SAAS,CAAC,EAAE,EAC3B,GAAG,EAAE,aAAa,GACjB,YAAY,CAAC,CAAC,CAAC,CA8BjB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EACxE,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,CAAC,EACb,GAAG,EAAE,aAAa,GACjB,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CA4C1B"}
@@ -0,0 +1,62 @@
1
+ import { Flag } from "../flags.js";
2
+ import { success, failure, describeType, } from "../types.js";
3
+ /**
4
+ * LLMs often return enum values with wrong casing - "PENDING" instead of "pending".
5
+ * Rather than failing, we do case-insensitive matching and track when it happens.
6
+ */
7
+ export function coerceEnum(value, allowedValues, ctx) {
8
+ if (typeof value !== "string") {
9
+ return failure(`Expected enum value, got ${describeType(value)}`, ctx, allowedValues.join(" | "), describeType(value));
10
+ }
11
+ // Fast path: exact match
12
+ if (allowedValues.includes(value)) {
13
+ return success(value, ctx);
14
+ }
15
+ // Fallback: case-insensitive match
16
+ const lowerValue = value.toLowerCase();
17
+ const match = allowedValues.find((v) => v.toLowerCase() === lowerValue);
18
+ if (match) {
19
+ ctx.flags.push({ flag: Flag.EnumCaseInsensitive, input: value, matched: match });
20
+ return success(match, ctx);
21
+ }
22
+ return failure(`Value "${value}" is not a valid enum value`, ctx, allowedValues.join(" | "), `"${value}"`);
23
+ }
24
+ /**
25
+ * TypeScript enums have reverse mappings for numeric values.
26
+ * We filter those out and match against both keys and values.
27
+ */
28
+ export function coerceNativeEnum(value, enumObject, ctx) {
29
+ // Filter out numeric keys (reverse mappings from numeric enums)
30
+ const enumValues = Object.entries(enumObject)
31
+ .filter(([key]) => isNaN(Number(key)))
32
+ .map(([_, val]) => val);
33
+ if (enumValues.includes(value)) {
34
+ return success(value, ctx);
35
+ }
36
+ // Try matching by key or value name, case-insensitive
37
+ if (typeof value === "string") {
38
+ const lowerValue = value.toLowerCase();
39
+ for (const [key, enumValue] of Object.entries(enumObject)) {
40
+ if (isNaN(Number(key))) {
41
+ // Match against enum key name
42
+ if (key.toLowerCase() === lowerValue) {
43
+ ctx.flags.push({ flag: Flag.EnumCaseInsensitive, input: value, matched: key });
44
+ return success(enumValue, ctx);
45
+ }
46
+ // Match against string enum value
47
+ if (typeof enumValue === "string" && enumValue.toLowerCase() === lowerValue) {
48
+ ctx.flags.push({ flag: Flag.EnumCaseInsensitive, input: value, matched: enumValue });
49
+ return success(enumValue, ctx);
50
+ }
51
+ }
52
+ }
53
+ }
54
+ // For numeric enums, also accept the number directly
55
+ if (typeof value === "number") {
56
+ if (enumValues.includes(value)) {
57
+ return success(value, ctx);
58
+ }
59
+ }
60
+ return failure(`Value "${String(value)}" is not a valid enum value`, ctx, enumValues.map((v) => String(v)).join(" | "), describeType(value));
61
+ }
62
+ //# sourceMappingURL=enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enum.js","sourceRoot":"","sources":["../../src/coercers/enum.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type CoerceContext, type CoerceResult } from "../types.js";
2
+ export type PropertyCoercer<T> = (value: unknown, ctx: CoerceContext) => CoerceResult<T>;
3
+ export interface ObjectSchema {
4
+ [key: string]: {
5
+ coercer: PropertyCoercer<unknown>;
6
+ optional: boolean;
7
+ default?: unknown;
8
+ };
9
+ }
10
+ /**
11
+ * LLMs often add extra fields we didn't ask for (explanations, confidence scores,
12
+ * reasoning). We ignore these rather than failing, but track them in flags.
13
+ *
14
+ * Missing optional fields are also tracked - callers can detect when defaults
15
+ * were used vs when the LLM explicitly provided values.
16
+ */
17
+ export declare function coerceObject<T extends Record<string, unknown>>(value: unknown, schema: ObjectSchema, ctx: CoerceContext): CoerceResult<T>;
18
+ /**
19
+ * For z.record() - a map with string keys and uniform value types.
20
+ * Unlike objects, we don't have a fixed schema - just coerce each value.
21
+ */
22
+ export declare function coerceRecord<T>(value: unknown, valueCoercer: PropertyCoercer<T>, ctx: CoerceContext): CoerceResult<Record<string, T>>;
23
+ //# sourceMappingURL=object.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../../src/coercers/object.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EAKlB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC;AAEzF,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG;QACb,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;QAClC,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAMD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,aAAa,GACjB,YAAY,CAAC,CAAC,CAAC,CAoDjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAC5B,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,EAChC,GAAG,EAAE,aAAa,GACjB,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAwBjC"}