π File detail
utils/semanticNumber.ts
π§© .tsπ 37 linesπΎ 1,486 bytesπ text
β Back to All Filesπ― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes semanticNumber β mainly functions, hooks, or classes. Dependencies touch schema validation.
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import { z } from 'zod/v4' /** * Number that also accepts numeric string literals like "30", "-5", "3.14". *
π€ Exports (heuristic)
semanticNumber
π External import roots
Package roots from from "β¦" (relative paths omitted).
zod
π₯οΈ Source preview
import { z } from 'zod/v4'
/**
* Number that also accepts numeric string literals like "30", "-5", "3.14".
*
* Tool inputs arrive as model-generated JSON. The model occasionally quotes
* numbers β `"head_limit":"30"` instead of `"head_limit":30` β and z.number()
* rejects that with a type error. z.coerce.number() is the wrong fix: it
* accepts values like "" or null by converting them via JS Number(), masking
* bugs rather than surfacing them.
*
* Only strings that are valid decimal number literals (matching /^-?\d+(\.\d+)?$/)
* are coerced. Anything else passes through and is rejected by the inner schema.
*
* z.preprocess emits {"type":"number"} to the API schema, so the model is
* still told this is a number β the string tolerance is invisible client-side
* coercion, not an advertised input shape.
*
* .optional()/.default() go INSIDE (on the inner schema), not chained after:
* chaining them onto ZodPipe widens z.output<> to unknown in Zod v4.
*
* semanticNumber() β number
* semanticNumber(z.number().optional()) β number | undefined
* semanticNumber(z.number().default(0)) β number
*/
export function semanticNumber<T extends z.ZodType>(
inner: T = z.number() as unknown as T,
) {
return z.preprocess((v: unknown) => {
if (typeof v === 'string' && /^-?\d+(\.\d+)?$/.test(v)) {
const n = Number(v)
if (Number.isFinite(n)) return n
}
return v
}, inner)
}