Commit graph

6 commits

Author SHA1 Message Date
Cristina Poncela Cubeiro
55a5c69145 fix(schema): num no longer takes nan and infinity 2026-11-05 14:20:32 +01:00
Cristina Poncela Cubeiro
af79b0c3d7
feat(schema): TypeBox parity for extra types (#11833)
Adds unsafe<T>(), recursive<T>(), Validator.validate() (check plus
errors, no coercion), bigint(), equal(), validation and coercion of
TypeBox's Type.BigInt/Type.Undefined, and Static for TypeBox's Enum,
Tuple, BigInt and Undefined. Also fixes a required property set to
undefined being rejected when its schema accepts undefined, as TypeBox
allows.
2026-11-05 11:36:38 +01:00
Armin Ronacher
20c637b1a5 feat(schema): run const, enum, type list, bounds and pattern checks inline in object and array loops 2026-11-04 18:33:17 +01:00
robopipi[bot]
2bd2c2d0a6
fix(schema): apply a relative root $id only once when resolving references (#11726)
A schema whose root `$id` was a relative path with a directory part,
such as `schemas/tool.json`, could not resolve its local `$ref`s. The
`Resolver` constructor applied the root `$id` to `DEFAULT_BASE` once to
compute `rootBase`. The compiler, the error explainer and the coercer
start from `rootBase` and call `baseFor(root, rootBase)`, which applied
the `$id` a second time. Local references therefore resolved against
`https://json-schema.invalid/schemas/schemas/tool.json`.

The effect was that `compile(schema)` threw `Unresolved $ref
#/$defs/count`. With `unresolvedRefs: "reject"`, which is how tool
arguments are validated (pi-ai's `validateToolArguments`, used by the
agent loop and durable, and durable's `structuredOutput` check), valid
arguments were rejected and the tool never ran. Absolute `$id`s and bare
names like `tool.json` were not affected, because resolving them twice
gives the same URI.

The fix:
- `rootBase` is now `DEFAULT_BASE`; walkers apply the root's own `$id`
through `baseFor`.
- `baseFor` indexes the schema before applying an `$id` and then returns
the indexed base, so a caller whose base already includes the `$id`
cannot apply it again. Without this,
`unevaluatedProperties`/`unevaluatedItems` still failed: `build()`
passes the already-based node to `annotating()`, which applied the `$id`
once more.
- The coercer resolves bases through `baseFor` instead of reading
`baseOf` directly. Otherwise the `rootBase` change broke the first
`parse`/`coerce` of a schema with `$dynamicRef`: the restarted dynamic
compiler is not indexed yet, so a relative `$ref` resolved against
`DEFAULT_BASE` and conversion was silently skipped.
- The explainer drops `$id` from the copy it makes for unevaluated
keywords, and resolves `anyOf`/`oneOf` branch `$ref`s against the
branch's own base (both threw in `errors()` on main for nested resources
with a relative `$id`).

Testing:
- Regression tests in `validator.test.ts` (relative root `$id` with
local `$ref`, with `unevaluatedProperties`/`unevaluatedItems`, error
reporting under nested relative resources) and `coerce.test.ts` (first
coercion of a `$dynamicRef` schema with a relative external `$ref`).
They fail without the change and pass with it.
- Full pi-schema suite: 8712 passed.
- Independently reviewed against edge cases: draft 7 (inert `$id` beside
`$ref`, `#anchor` ids), fragment ids, nested relative chains, anchors,
registered schemas referring back to the root, fresh
`parse`/`coerce`/`errors` after a dynamic restart.

Fixes #11697

Requested by @badlogic

---------

Co-authored-by: triagent <triagent@localhost>
Co-authored-by: Petr Houška <houskape@gmail.com>
2026-11-03 23:14:11 +01:00
Mario Zechner
6536f80129 feat(ai,agent,coding-agent): validate tool arguments with pi-schema, drop TypeBox
pi-schema: opt-in coercion, compile(schema, { coerce: true }) with coerce() and
parse(), the superset of TypeBox's Value.Convert rules and pi-ai's old coercion;
unresolvedRefs option; url and json-pointer-uri-fragment formats.
pi-ai: Tool takes any JSON Schema; validation through pi-schema; Type, Static
and TSchema are no longer exported (breaking). pi-agent-core and pi-durable:
tools on pi-schema, no TypeBox dependency. Coding agent: extensions keep Type,
Static, TSchema and StringEnum through an extension view of pi-ai; built-in
tools stay on TypeBox.
2026-11-01 10:08:42 +00:00
Mario Zechner
fab2c5353e feat(schema): add pi-schema, JSON Schema builders and validation without eval
@earendil-works/pi-schema compiles JSON Schema (drafts 2020-12 and 7) once into
plain closures: no eval or new Function, so it runs under a strict CSP. It
passes 2,222 of the 2,223 core tests of the official JSON-Schema-Test-Suite,
checks Chord-style argument schemas within 1.2x of TypeBox's JIT, and keeps
3,200 compiled schemas in 7.9 MB (TypeBox: 54.7 MB).

- compile(schema, options) returns a Validator: check() is the boolean check
  (a type guard for builder schemas), errors() reports each failure with a
  JSON Pointer path, missing and unknown fields at the field.
- Builders (str, int, obj, opt, union, ...) return plain JSON Schema typed with
  the values it accepts (Static<typeof schema>).
- Formats are an optional entry point (./formats), adapted from TypeBox (MIT).
- Tests run the vendored suite (drafts 2020-12 and 7, with optional and format
  tests); npm run bench compares conformance, speed, setup cost, memory and
  bundle size with TypeBox, @cfworker/json-schema and omptype.
2026-10-31 21:39:34 +00:00