TypeBox Catches LLM Tool-Call Arguments That Are Valid JSON but Wrong Types
Model-generated JSON that parses successfully can still carry string-wrapped numbers, missing required fields, or unexpected prototypes, and those errors land inside file I/O or shell commands where they are harder to diagnose. A strict, schema-driven validation step that shares a single source of truth with TypeScript types stops malformed arguments before any side effects run.
LLM tool calls often produce syntactically correct JSON that still breaks downstream code — a string `"20"` where a number is expected, for instance. This implementation adds a second validation boundary that sits between the event-stream state machine and actual tool execution. It uses TypeBox to define each tool’s parameter schema once, then derives both compile-time TypeScript types and runtime validators from the same definition.
Two entry points handle every path arguments can arrive by: `parseToolArguments` for raw JSON strings and `validateToolArguments` for already-parsed objects. Both paths enforce that the root is a plain object, deep-clone the data to prevent post-validation mutation, and run strict TypeBox checks that reject implicit type coercion. Failures produce a structured `ToolArgumentsValidationError` carrying the tool name and a list of field-level issues, without leaking the original arguments.
The approach keeps unsafe `any` out of the pipeline. `JSON.parse` results are immediately narrowed to `unknown`, and the `Check` method acts as a type guard so that successful branches return `Static<TParameters>` with no cast needed. Invalid schemas — a developer mistake — are left to throw their own errors rather than being swallowed by the validation layer.
Most LLM tool-calling pipelines treat valid JSON as the finish line, but a parsed object can still carry string-wrapped numbers, missing keys, or prototype pollution that crashes downstream code.
Using `structuredClone` before validation isn’t just defensive copying — it also rejects objects containing functions or non-cloneable values that a model should never produce for tool arguments.
Tightening `JSON.parse`’s `any` return to `unknown` before validation is a small, high-leverage move that forces every code path through the schema check instead of trusting the parser.
The deliberate choice not to catch `Compile` errors means a broken schema fails fast during development rather than masquerading as a model argument problem in production logs.