Repository navigation
fix(client): take zod as a peer dependency - #3200
Conversation
@ag-ui/client and @ag-ui/proto declared zod ^3.25.76 as a direct dependency, so an application pinning zod >= 4.6.6 (the release with the CVE-2023-54404 size-gate fix) still got a nested zod 3.25.76 beneath them. Make zod a required peer (^3.25.18 || ^4.0.0, matching @ag-ui/core) in client, proto and encoder, so every package uses the application's zod. The zod matrix now asserts every @ag-ui/* package resolves the consumer's zod rather than a nested copy. Closes #3129 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ag-ui/a2a-middleware
@ag-ui/a2ui-middleware
@ag-ui/event-throttle-middleware
@ag-ui/mcp-apps-middleware
@ag-ui/mcp-middleware
@ag-ui/a2a
@ag-ui/adk-js
@ag-ui/adk
@ag-ui/ag2
@ag-ui/agno
@ag-ui/antigravity
@ag-ui/aws-strands
@ag-ui/claude-agent-sdk
@ag-ui/claude-managed-agents
@ag-ui/crewai
@ag-ui/langchain
@ag-ui/langgraph
@ag-ui/llamaindex
@ag-ui/mastra
@ag-ui/pydantic-ai
@ag-ui/vercel-ai-sdk
@ag-ui/watsonx
@ag-ui/a2ui-toolkit
create-ag-ui-app
@ag-ui/client
@ag-ui/core
@ag-ui/encoder
@ag-ui/proto
commit: |
|
A small matrix-strengthening suggestion, verified against The current peer-resolution assertion compares only Zod version strings. A nested installation with the same version passes even though the SDK package does not resolve the consumer's physical installation. Comparing canonical Reproduction on the actual matrix: immediately after each consumer install, copy its
Node 22.23.1, pinned pnpm 10.33.4, lifecycle scripts disabled; no consumer overrides. Also pass:
This asserts physical package resolution in the npm consumer matrix, not universal module identity across formats/bundlers/package managers. In particular, the existing Zod v4 cross-copy tests show Please feel free to fold this incremental patch into this PR. Happy to coordinate a branch-targeted contribution if preferred; no competing PR opened. Codex assisted with implementation and verification. diff --git a/sdks/typescript/packages/core/scripts/verify-zod-matrix.mjs b/sdks/typescript/packages/core/scripts/verify-zod-matrix.mjs
index 2b0c472c..268e331f 100644
--- a/sdks/typescript/packages/core/scripts/verify-zod-matrix.mjs
+++ b/sdks/typescript/packages/core/scripts/verify-zod-matrix.mjs
@@ -6,7 +6,7 @@
// pinned zod (an npm override forces the package's own dependency onto that
// major) and runs the same smoke through require() and import.
import { execFileSync } from "node:child_process";
-import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
+import { mkdtempSync, writeFileSync, rmSync, realpathSync } from "node:fs";
import { tmpdir } from "node:os";
import { basename, dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
@@ -147,6 +147,10 @@ try {
}
// The peer guarantee: every package resolves the consumer's zod, so an application
// that pins a patched zod gets no older copy nested beneath @ag-ui/*.
+ const consumerZodPath = realpathSync(execFileSync(
+ "node", ["-p", "require.resolve('zod/package.json')"],
+ { cwd: consumer, encoding: "utf8" },
+ ).trim());
for (const name of ["core", ...SIBLINGS]) {
const resolved = execFileSync(
"node",
@@ -159,6 +163,14 @@ try {
if (resolved !== installed) {
throw new Error(`@ag-ui/${name} resolved zod ${resolved}, not the consumer's ${installed}`);
}
+ const resolvedPath = realpathSync(execFileSync(
+ "node",
+ ["-p", `require.resolve("zod/package.json", { paths: [require.resolve("@ag-ui/${name}/package.json")] })`],
+ { cwd: consumer, encoding: "utf8" },
+ ).trim());
+ if (resolvedPath !== consumerZodPath) {
+ throw new Error(`@ag-ui/${name} resolved a different zod installation: ${resolvedPath}, not ${consumerZodPath}`);
+ }
}
console.log(`\n== zod@${installed} ==`);
run("node", ["smoke.cjs"], consumer); |
Closes #3129.
Problem
@ag-ui/clientand@ag-ui/protolistedzod: ^3.25.76as a direct dependency. An app that pinszod@4.6.6+(the release that carries the CVE-2023-54404 size-gate fix) still ended up with a nestedzod@3.25.76under client and proto. Installing published 1.0.3 next tozod@4.6.7:Change
@ag-ui/client,@ag-ui/protoand@ag-ui/encodernow take zod as a required peer (^3.25.18 || ^4.0.0, the same range as@ag-ui/core), so they use the app's zod. zod stays a dev dependency for the workspace. The encoder needs the peer because it loads proto, which validates with core's schemas at runtime.core/scripts/verify-zod-matrix.mjs: each cell now checks that every@ag-ui/*package uses the consumer's zod, not a nested copy.docs/migrating-to-1-0.mdxnow addzod.pnpm-lock.yamlregenerated. Besides the zod entries moving, pnpm worked out peer dependencies again for some unrelated packages. Notably, in the Dojo,@langchain/openai's@langchain/corepeer now resolves to the 1.1.40 already in the lockfile, and in adk-middleware/js,@google/adk's optional MCP SDK now resolves against zod 3.25.76 instead of 4.4.3.No source changes: the client already only uses the
zod/v4API, which both zod 3.25+ and 4.x export.Compatibility
--legacy-peer-deps, projects without zod will get a missing-peer warning or error and needzodinstalled. This needs a changelog note at release.Scope
Upgrading zod does not limit how much work AG-UI does on a very large payload. The upstream patch only short-circuits containers that declare a maximum size, and AG-UI's generated arrays have none. That follow-up is tracked in #3199.
Verification
nx run-many -t build,test,typecheck,lintfor core, client, proto, encoder and adk-js: all pass.node core/scripts/verify-zod-matrix.mjs: passes with zod 3.25.18, 3.25.76 and 4.6.7, plus the no-zod case, including the new single-copy check.pnpm install --frozen-lockfile: passes.🤖 Generated with Claude Code