Skip to content

[shared] Move to zod 4 and AG-UI 1.0 - #101

Merged
robinbowes merged 1 commit into
mainfrom
deps/zod4-agui1
Nov 2, 2026
Merged

robinbowes merged 1 commit into
mainfrom
deps/zod4-agui1

Conversation

@robinbowes

Copy link
Copy Markdown
Collaborator

Summary

This moves the kit to zod 4 (^3.25.76 → ^4.6.7) and AG-UI 1.0 (0.0.59 → 1.0.3, exact pins) in one PR, because neither type-checks without the other. AG-UI 1.0's validators are zod 4 schemas, and kitAguiEventSchema is a z.discriminatedUnion over them:

packages/shared/src/ai/agui.ts
  import { EventType, type Interrupt } from '@ag-ui/core'          # main entry: types only in 1.0
  import { …EventSchema, InterruptSchema, … } from '@ag-ui/core/schemas'
  kitAguiEventSchema = z.discriminatedUnion('type', [ …AG-UI schemas ])   # one zod, the host's (AG-UI takes it as a peer)

Behaviour changes:

Change Why
Protobuf now carries TOOL_CALL_RESULT (PROTO_UNSUPPORTED_EVENTS = []) @ag-ui/proto@1.0.3 has the message. A new test round-trips the whole event, not just its type.
example-feature's PATCH no longer fills body: '' zod 4 applies .default() under .partial(), so PATCH { title } would have wiped the body. The update schema is rebuilt without the default. An audit of every .partial() found no other affected schema.
zod-to-json-schema removed; toolInputSchema() uses z.toJSONSchema That package is zod 3 only. The options are draft-7, input side (so defaulted fields are optional) and lenient on unrepresentable types, the same as before.

Type-level changes:

  • ZodTypeDef is gone, so the UI's api-client.ts inferred every response as {} until its signature became z.ZodType<T, unknown>.
  • z.record needs a key schema.
  • invalid_type_error is now error.
  • ZodObject generics changed.
  • TOOL_CALL_RESULT.content is string | ContentPart[]. The kit still emits strings, and its readers now narrow.

Plugin API (docs/plugin-api.md, regenerated):

  • JobVariant is now ZodObject<{ type: ZodTypeAny } & ZodRawShape>.
  • JobTypeOf reads the variant's shape.type.
  • Exported schema types print with zod 4's names.

The type test pinning JobTypeOf<Orders> to 'orders.sync' still passes.

Docs: docs/CONCEPTS.md §9's known gaps drop "protobuf drops TOOL_CALL_RESULT" and "@ag-ui/core is 0.0.x", and add that a cancelled run doesn't emit AG-UI 1.0's cancelled RUN_FINISHED outcome.

Left for later:

  • Deprecated zod 4 forms (.uuid(), .email(), .passthrough(), .strict()) still work, and no tool reports them. Moving to z.uuid() and the like would be about 200 lines of churn, so it's left for a separate PR.
  • The protobuf drop check in createAguiEncoder never fires while the list is empty. I kept it because the contract test compares the list with a real round-trip, so the next upstream gap fails loudly.

Evidence

  • Before: with the bumps alone, pnpm typecheck gives 12 errors in shared, then 325 in web (pages inferring {}).
    After: pnpm gate passes on Node 26.13.0: api 638, the other web projects 1807 + 5 skipped (2 new tests), driver 13, cli 58, evals 25.
  • The PATCH bug, test first: the new test failed with + "body": "" before the fix and passes after.
  • The analytics plugin under zod 4: in a throwaway worktree I reproduced kit.yml's plugins job (plugin add …@3.4.2, db:generate, migrate, GATE_SUITE_DRIVER=neon pnpm gate). Typecheck, build, api 685 and the rest passed. The one failure was local only: my global gitignore hides .claude/, and with the skill files staged it passes.
  • The review gate approved with 0 findings.

Merge Danger

Door: two-way

Blast Radius: plugins

Plugins compile against the host's zod, so a plugin with zod 3–only code (ZodTypeDef, single-argument z.record, .partial() over defaults) breaks on a host that takes this. The porting note's steps 27–34 cover the migration, including the .partial() audit.

🤖 Generated with Claude Code

The two travel together: AG-UI 1.0's validators are zod 4 schemas (and
take zod as a peer), and kitAguiEventSchema is a z.discriminatedUnion
over them, so neither upgrade type-checks without the other.

AG-UI 0.0.59 -> 1.0.3 (exact): validators come from @ag-ui/core/schemas;
TOOL_CALL_RESULT.content is typed string | parts, which tests and the
eval transcript now narrow. @ag-ui/proto 1.0.3 carries TOOL_CALL_RESULT,
so the protobuf drop list is empty and a test round-trips the event.

zod 3 -> 4: ZodTypeDef is gone (z.ZodType<Output, Input>), z.record
needs a key schema, invalid_type_error is error, ZodObject generics
changed. JobVariant/JobTypeOf in the plugin API are restated for zod 4.
zod-to-json-schema is replaced by z.toJSONSchema (draft-7, input side).

zod 4 fills a .default() field under .partial(): example-feature's
update schema turned PATCH { title } into { title, body: '' }. It is
rebuilt without the default, with a test; no kit schema had the shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@robinbowes
robinbowes merged commit 2166f3e into main Nov 2, 2026
4 checks passed
@robinbowes
robinbowes deleted the deps/zod4-agui1 branch November 2, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant