Skip to content

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

Open
AlemTuzlak wants to merge 43 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 43 commits into
mainfrom
alem/extract-zod-from-core

Conversation

@AlemTuzlak

@AlemTuzlak AlemTuzlak commented May 6, 2026

Copy link
Copy Markdown
Contributor

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

vercel Bot commented May 6, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
ag-ui-dojo Ready Ready Preview, Comment Jun 26, 2026 1:26pm

Request Review

@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Python Preview Packages

Version 0.0.0.dev1784161336 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.dev1784161336' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1784161336' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1784161336' --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.dev1784161336' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1784161336' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1784161336' --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.dev1784161336

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


Commit: ad18446

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.
tombeckenham added a commit to TanStack/ai that referenced this pull request Jun 10, 2026
…520)

@ag-ui/core@0.0.x infers its public types from Zod 3 schemas and carries
zod ^3.22 as a hard runtime dependency, dragging Zod 3 into every
consumer's tree and type graph. Apps on Zod 4 (e.g. @hookform/resolvers@5)
hit zod/v4/core version-mismatch errors (TS2769) when their lockfile
resolves the resolver's zod/v4 import to the Zod 3 shim.

@ag-ui/core@0.1.0 makes the main entry type-only with zero runtime deps
and moves schemas to an opt-in /schemas subpath (zod ^3.24 || ^4 optional
peer). This change:

- bumps @ag-ui/core to ^0.1.0
- loads RunAgentInputSchema lazily from @ag-ui/core/schemas inside
  chatParamsFromRequestBody, typed against a local structural interface
  so zod never appears in this package's compilation or public .d.ts
- declares zod (^3.24.0 || ^4.0.0) as an optional peer dependency — only
  server code calling chatParamsFromRequest* needs zod installed; calling
  without it rejects with an actionable AGUIError

Verified against a packed build: a zod-4 + @hookform/resolvers@5 consumer
gets exactly one zod in its tree and tsc --noEmit passes with
skipLibCheck: false; a zod-free consumer imports @tanstack/ai fine.

Blocked on the @ag-ui/core@0.1.0 npm release
(ag-ui-protocol/ag-ui#1637, conflict resolution in
ag-ui-protocol/ag-ui#1918). Lockfile to be regenerated once published.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-core

# Conflicts:
#	pnpm-lock.yaml
#	sdks/typescript/packages/core/package.json
@mintlify

mintlify Bot commented Jun 24, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
tawkitai 🟢 Ready View Preview Jun 24, 2026, 12:52 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

The endpoint validation handler imported RunAgentInputSchema from the
bare @ag-ui/core entry, which no longer exports *Schema constants after
the 0.1.0 schema-subpath split. At runtime the symbol was undefined, so
safeParse() threw and the endpoint returned 500 instead of 400/200 —
breaking 10 endpoint-validation tests after merging main.
This PR is scoped to the zod-extraction minor (0.1.0). The deprecated-event
removals (THINKING, BinaryInputContent) are split into a stacked major PR.
contextablemark and others added 2 commits July 15, 2026 23:57
Brings the zod-extraction branch up to current main (206 commits) so #1637
is mergeable again. Only one semantic conflict, in core/src/events.ts.

main's sole core change since this branch last merged main (Jun 24) was
2fa33a0 "fix(core): accept null parentMessageId on tool-call events", which
edited the zod schemas that this branch relocated from events.ts to schemas.ts.
Resolved so the fix is preserved rather than dropped:

- events.ts: kept the branch's type-only interfaces (the extraction).
- schemas.ts: ported the null-acceptance onto ToolCallStartEventSchema and
  ToolCallChunkEventSchema (.nullable().optional().transform(v => v ?? undefined)).
  Output type stays `string | undefined`; schema-type-equality still passes.
- tool-call-events.test.ts (added by 2fa33a0): repointed schema imports to
  ../schemas and expressed the input-widening contract via
  z.input<typeof ToolCallStartEventSchema> (the branch's hand-written *Props
  type stays string | undefined by design).

Verified: core 188, client 499, proto green; tsc --noEmit core; build of
core/client/proto/encoder; publint --strict + attw --pack (root and /schemas
subpath resolve on node10 / node16 CJS+ESM / bundler).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…surface

#1637 makes @ag-ui/core type-only (zod schemas move to the @ag-ui/core/schemas
subpath). Because @ag-ui/client does `export * from "@ag-ui/core"`, it
transitively stopped re-exporting the schema values — breaking consumers that
import `*Schema` / `EventSchemas` from @ag-ui/client:

- integrations/mastra interrupt-bridge.test.ts: RunFinishedEventSchema.parse(...)
  threw "Cannot read properties of undefined (reading 'parse')" — caught by CI.
- middlewares/a2a-middleware/src/index.ts: same import in source — a latent
  runtime `undefined`, not covered by a test.

Both usages were added on main after #1637's last migration sweep, so the PR
never saw them. Restore the pre-0.1.0 client surface by also re-exporting the
schemas subpath. client already depends on zod, so this adds no new coupling and
core stays zod-free. Safe as `export *`: core exports only non-Schema names,
schemas exports only *Schema / EventSchemas — no collisions.

Verified: nx test @ag-ui/{core,client,mastra,a2a-middleware} — core 188,
client 499, mastra 300 (was 299 | 1 failed), a2a-middleware green; client builds
with no ambiguous re-export.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

3 participants