Skip to content

feat(core)!: extract zod from @ag-ui/core, ship @ag-ui/core/schemas subpath#1637

Open
AlemTuzlak wants to merge 37 commits into
mainfrom
alem/extract-zod-from-core
Open

feat(core)!: extract zod from @ag-ui/core, ship @ag-ui/core/schemas subpath#1637
AlemTuzlak wants to merge 37 commits into
mainfrom
alem/extract-zod-from-core

Conversation

@AlemTuzlak
Copy link
Copy Markdown
Contributor

@AlemTuzlak AlemTuzlak commented May 6, 2026

Summary

@ag-ui/core no longer depends on zod. Upstream consumers were repeatedly hitting v3/v4 dual-installation conflicts because core pinned zod 3.x as a hard runtime dep. This PR:

  • Moves all *Schema exports to a new opt-in subpath: @ag-ui/core/schemas.
  • Makes zod an optional peer dependency of the schemas subpath, accepting ^3.24.0 || ^4.0.0 — consumers pick exactly one zod version.
  • Keeps the main @ag-ui/core entry type-only (TypeScript types + EventType enum + AGUIError + event factories). Zero runtime dependencies.
  • @ag-ui/client and @ag-ui/proto import EventSchemas from @ag-ui/core/schemas and call EventSchemas.parse(...) directly, preserving prior runtime-validation behavior with no caller changes.
  • Bumps @ag-ui/core to 0.1.0 with a CHANGELOG entry.

Migration tooling

  • Migration guide at docs/sdk/js/core/migration-0-1-0.mdx (journey format with decision tree, before/after diffs, FAQ).
  • jscodeshift codemod at codemods/0.1.0-schemas-to-subpath.ts (repo root). Splits combined imports, redirects every *Schema import to the subpath, preserves import type semantics + per-specifier type modifiers, dedupes by (imported, local) so aliased duplicates survive, warns on namespace imports, idempotent. Run via:
npx jscodeshift -t https://raw.githubusercontent.com/ag-ui-protocol/ag-ui/main/codemods/0.1.0-schemas-to-subpath.ts \
  --parser=tsx --extensions=ts,tsx src/

Breaking change

@ag-ui/core no longer exports any *Schema constants from its main entry. The two existing internal consumers (adk-middleware, README example) have been migrated.

// Before
import { UserMessageSchema } from "@ag-ui/core";

// After (option A — most users)
import { UserMessageSchema } from "@ag-ui/core/schemas";

// After (option B — BYO validator)
import type { UserMessage } from "@ag-ui/core";
// validate with valibot/arktype/your own zod schemas...

Tool.parameters, RunAgentInput.state, and RunAgentInput.forwardedProps are now optional (?: any) to match what zod's addQuestionMarks infers from z.any() fields. Non-breaking widening (extra | undefined allowed).

Test plan

  • pnpm -C sdks/typescript/packages/core test — 182/182
  • pnpm -C sdks/typescript/packages/proto test — 92/92
  • pnpm -C sdks/typescript/packages/client test — 482/483 (1 pre-existing Windows-only ESM-loader failure on esm-interop.test.ts, unrelated)
  • pnpm -C integrations/adk-middleware/typescript test — 8/8
  • pnpm -C sdks/typescript build — clean across @ag-ui/core, @ag-ui/client, @ag-ui/proto, @ag-ui/encoder, @ag-ui/adk, @ag-ui/claude-agent-sdk
  • All CI checks green (build, typescript, all 22 dojo deploys, check-binaries, Vercel)
  • Codemod smoke test against fixtures (node codemods/test.ts)
  • Verify on a fresh consumer running zod v4 — no version conflict

Notable internal changes

  • BaseEvent interface gains [k: string]: unknown to match the runtime .passthrough() semantics that the prior z.infer types provided. This restores compatibility with consumers (notably claude-agent-sdk) that pass extra fields like threadId on event constructors.
  • BinaryInputContentSchema runtime check moved from superRefine to .refine() to work cleanly across both zod 3 and zod 4 majors.

AlemTuzlak added 21 commits May 6, 2026 11:55
…ests

- Reject array inputs in defaultEventValidator (typeof [] === 'object' footgun)
- Treat empty issues[] as success in fromStandardSchema (Standard Schema compliance)
- Add tests for fromStandardSchema and array rejection
zod's .min(1) is a runtime-only check; it does not produce a non-empty tuple
type at the inference level. The static type was too narrow. Adds direct
equality assertions for both RunFinishedSuccessOutcome and
RunFinishedInterruptOutcome to catch any future regression.
Replace schema.parse()-based factory helpers with plain object construction.
Inline the three coercion behaviors previously supplied by Zod schemas:
- TEXT_MESSAGE_START: role defaults to "assistant" when omitted
- ACTIVITY_SNAPSHOT: replace defaults to true when omitted
- RUN_FINISHED: outcome:null/undefined normalized to field omission

Add explicit guard in createRunFinishedInterruptEvent to throw on empty
interrupts array, preserving the .min(1) validation previously done by Zod.
…interface

BREAKING CHANGE: `@ag-ui/core` no longer exports any `*Schema` constants. zod is
no longer used to define types. Consumers using runtime validation should use the
`AgentValidator` interface or write their own schemas with the validation library
of their choice.
…erance

Replaces coverage from the deleted backwards-compatibility.test.ts: the
protocol contract is that AG-UI events tolerate unknown future fields,
and the new validator must continue to accept them.
The package no longer uses zod for type definitions, schema validation, or
any other purpose. Consumers needing runtime schema validation should plug
in their own AgentValidator implementation.
@ag-ui/core no longer exports *Schema constants. Three external consumers
(adk-middleware, TypeScript SDK README, and core README example) now use
type-only imports or locally-defined validation instead.
…r architecture

The Event Schemas section described the deleted zod schemas. Replace with
guidance for defaultEventValidator + Standard Schema customization. Also
tighten the multimodal example to use import type for UserMessage.
@AlemTuzlak AlemTuzlak requested review from a team and contextablemark as code owners May 6, 2026 12:20
@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ag-ui-dojo Ready Ready Preview, Comment May 6, 2026 5:37pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Python Preview Packages

Version 0.0.0.dev1778088798 published to TestPyPI.

Warning: These packages are built from contributor code that may not yet have been vetted for correctness or security. Install at your own risk and do not use in production.

Install with uv

Add the TestPyPI index to your pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = true

Then install the packages you need:

# Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1778088798' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1778088798' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1778088798' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1778088798' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1778088798' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1778088798' --index testpypi

Install with pip

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  ag-ui-protocol==0.0.0.dev1778088798

Use --extra-index-url https://pypi.org/simple/ so pip can resolve
transitive dependencies (pydantic, fastapi, etc.) from real PyPI.


Commit: dabd28b

AlemTuzlak added 3 commits May 6, 2026 14:43
The *-static.ts files were a migration scaffold used to prove static
types matched z.infer types via expectTypeOf assertions. After zod was
removed, the scaffolding became dead-end indirection. Move the type
definitions directly into types.ts / events.ts / capabilities.ts where
they belong and delete the static files.
…tValidator

- Schemas come back at a new opt-in subpath; zod becomes an optional peer
  dependency so consumers control the version (the original v3/v4 conflict fix).
- defaultEventValidator removed; @ag-ui/client and @ag-ui/proto default to
  zodValidator from the subpath, preserving prior runtime-validation behavior.
- adk-middleware reverts its local validator and imports
  AgentCapabilitiesSchema from @ag-ui/core/schemas.
- BinaryInputContent runtime check rewritten as .refine() (loses precise
  error path; boolean check unchanged).
- CHANGELOG and docs updated to reflect the actual final architecture.
- zodValidator uses Standard Schema (zod 3.24+); the peer range was
  too permissive (3.22.4 would crash at import).
- Add types conditions to exports map so node16/nodenext module
  resolution can locate .d.ts files for both entries.
…ubpath

The 8 schema test files deleted in 908f9d5 were exercising real
protocol-validation behavior, not just zod mechanics. Now that the
schemas live at the @ag-ui/core/schemas subpath, the tests can come
back with their imports redirected.

types-static.test.ts is renamed and reframed as an equality check
between the hand-written types and z.infer<typeof XxxSchema>, providing
drift protection against future schema/type divergence.
contextablemark
contextablemark previously approved these changes May 6, 2026
Copy link
Copy Markdown
Collaborator

@contextablemark contextablemark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Rewrite schemas.ts to use zod API that's stable across both majors:
- z.nativeEnum(EventType) -> z.enum([...EventType values as tuple...])
- z.record(X) -> z.record(z.string(), X) (two-arg form is required in zod 4)

Existing tests continue to pass against the resolved zod version. Standard
Schema works in both zod 3.24+ and zod 4, so zodValidator (built via
fromStandardSchema(EventSchemas)) is unchanged.
The AgentValidator/fromStandardSchema/zodValidator abstraction was
introduced to make runtime validation pluggable. With both internal
consumers (transformHttpEventStream and the protobuf encoder/decoder)
reverted to calling EventSchemas.parse directly, the abstraction has no
in-tree users and earns no public-API complexity. Removed entirely.

@ag-ui/client and @ag-ui/proto now import EventSchemas from
@ag-ui/core/schemas and declare zod as a regular dependency.
@ag-ui/core's main entry stays type-only with no runtime deps.
AlemTuzlak added 2 commits May 6, 2026 17:01
Adds a journey-format migration guide at sdk/js/core/migration-0-1-0
documenting the schemas-to-subpath move, plus an automated jscodeshift
codemod that splits combined imports and redirects every *Schema import
to @ag-ui/core/schemas. Cross-links the migration guide from the
existing core overview and events pages.
The previous code called .at(0).node which returns a Collection, not a
path. Use .paths()[0].node instead. Verified against the input fixture:
output matches expected; running twice is a no-op (idempotent).

Also updated the expected fixture to reflect actual jscodeshift output
(header comments attached to a removed node are dropped; multiline
formatting for the merged import). Updated test.ts to use execFileSync
subprocess with os.tmpdir() for cross-platform compatibility.
…e resolution

Problem 1 – @ag-ui/core/schemas module not found in consumers using legacy
moduleResolution: add `types` conditions to the exports map in package.json and
bump moduleResolution from node to bundler in the four affected tsconfigs
(sdks/typescript, packages/core, packages/client, integrations/adk-middleware).

Problem 2 – schema-type-equality.test.ts compile errors caused by three TypeScript
limitations:
  - BaseEventSchema uses z.enum([...]) with plain string literals, inferring a string
    union instead of the EventType enum type. Assertions use `AsObject<HandWrittenEvent>`
    (interface→mapped-type conversion) + reverse toExtend<_ISchema>() to work around
    TypeScript's interface/index-signature restriction.
  - Events with required z.any() fields (StateSnapshotEvent.snapshot, RawEvent.event,
    CustomEvent.value, RunStartedEvent nested RunAgentInput) have those fields inferred
    as optional by zod 3's addQuestionMarks. Same AsObject + reverse-toExtend pattern.
  - BaseEventSchema.passthrough() adds [x: string]: unknown to all inferred event
    types; forward toExtend<HandWrittenEvent>() fails for events where the above quirks
    also apply.

Also fix backwards-compatibility.test.ts discriminated-union access (toolCalls requires
role narrowing) and add a type cast in adk-middleware index.ts to bridge the z.any()
optionality gap in AgentCapabilities parsing.
- codemod test.ts: select npx.cmd on Windows so the smoke test runs
- codemod transform: preserve import-type semantics when moving schema
  specifiers (don't force schemas subpath to load at runtime)
- codemod transform: warn on namespace imports of @ag-ui/core that
  cannot be automatically rewritten
- codemod README: move 'Aliased imports' out of 'Known limitations'
  (it's correct behavior, not a limitation)
- client / proto / adk-middleware package.json: add types condition
  to exports map so node16/nodenext consumers resolve .d.ts files
AlemTuzlak added 2 commits May 6, 2026 18:28
- Dedup by (imported, local) pair so 'Foo' and 'Foo as Bar' both survive.
- When the existing @ag-ui/core/schemas declaration is type-only,
  don't merge value specs into it (and vice versa) — emit separate
  declarations to keep value imports loadable at runtime.
- Fixtures: add cases for double-aliased import and pre-existing
  type-only schemas declaration; reflect the corrected behavior.
…stness

Node 24 + Windows: execFileSync no longer performs PATHEXT resolution
for .cmd shims, so the platform-conditional 'npx.cmd' fix from a prior
commit fails with EINVAL. Switching to shell:true with the literal 'npx'
delegates resolution to the shell on every platform. Args are static
literals so shell injection is not a concern.

Also use mkdtempSync per invocation so concurrent runs don't race on a
shared temp file.
- Tsconfig moduleResolution bumps were tangential to the zod migration.
  Revert them. Add typesVersions to @ag-ui/core's package.json so the
  schemas subpath types still resolve under legacy moduleResolution.
- Move codemods from sdks/typescript/codemods/ to repo root codemods/
  for discoverability. Update the migration-guide npx invocation URL
  to point at the new path.
Earlier commits removed @standard-schema/spec from @ag-ui/core's
package.json when the validator was dropped, but did not regenerate
the lockfile. CI uses --frozen-lockfile and fails install.
AlemTuzlak added 2 commits May 6, 2026 19:23
The previous z.infer<typeof BaseEventSchema> type included a
[k: string]: unknown index signature inherited from .passthrough(),
which silently accepted arbitrary extra fields like 'threadId' on
downstream event constructors. The hand-written BaseEvent was closed,
breaking integrations (notably claude-agent-sdk) that relied on this
type-level passthrough. Restore the index signature so inferred and
hand-written types match exactly and the runtime passthrough behavior
is reflected at the type level.

Also add `as XxxEvent` type assertions to event-factories.ts to work
around a TypeScript limitation where object spread of Omit<T, K> types
with index signatures does not preserve named property requirements in
the inferred object literal type.
…with zod inference

- Tool.parameters, RunAgentInput.state, RunAgentInput.forwardedProps:
  made optional in the hand-written types to match what zod's
  addQuestionMarks produces from z.any() fields. Schema-type-equality
  can now use stricter assertions.
- adk-middleware: dropped 'as AgentCapabilities' cast that was working
  around the previous drift.
- backwards-compatibility test: dropped role-narrowing pattern in
  favor of the simpler direct-access form (vitest doesn't strict-typecheck).
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 6, 2026

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@1637

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@1637

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@1637

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@1637

@ag-ui/middleware-starter

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/middleware-starter@1637

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@1637

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@1637

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@1637

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@1637

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@1637

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@1637

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@1637

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@1637

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@1637

@ag-ui/langroid

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langroid@1637

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@1637

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@1637

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@1637

@ag-ui/server-starter

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/server-starter@1637

@ag-ui/server-starter-all-features

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/server-starter-all-features@1637

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@1637

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@1637

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@1637

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@1637

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@1637

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@1637

commit: b6b7ccf

@AlemTuzlak AlemTuzlak changed the title feat(core)!: extract zod from @ag-ui/core, ship pluggable AgentValidator feat(core)!: extract zod from @ag-ui/core, ship @ag-ui/core/schemas subpath May 6, 2026
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.

2 participants