Skip to content

fix(ts): support Zod 4 custom tool schemas#3495

Open
jkomyno wants to merge 2 commits into
feat/node-22-esm-onlyfrom
feat/zod-schema-compat
Open

fix(ts): support Zod 4 custom tool schemas#3495
jkomyno wants to merge 2 commits into
feat/node-22-esm-onlyfrom
feat/zod-schema-compat

Conversation

@jkomyno

@jkomyno jkomyno commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Stacked on #3494.

This PR tightens the SDK's Zod boundary now that the TypeScript packages are moving to the newer Node/toolchain baseline. The main issue was asymmetric Zod support: @composio/json-schema-to-zod already supports projects with Zod 3 and Zod 4 by generating schemas through zod/v3, but custom tools converted schemas in the opposite direction through zod-to-json-schema only. With a bare Zod 4 z.object(...), that path degraded to an empty JSON Schema.

What changed

  • Added a shared core helper for Zod schema -> JSON Schema conversion.
  • Supports both zod/v3 schemas and Zod 4 schemas via z.toJSONSchema.
  • Converts custom-tool input schemas in Zod 4 input mode, so defaulted fields stay optional in the generated JSON Schema.
  • Uses the shared helper in both custom-tool entrypoints: createCustomTool(...) and legacy composio.tools.createCustomTool(...).
  • Updates custom-tool types so Zod 4 schemas infer their execute input type while keeping the session context parameter required for context-using tools.
  • Exposes jsonSchemaToZodShape through the narrow @composio/core/utils/json-schema subpath.
  • Switches the Claude Agent SDK provider to use that core subpath instead of depending directly on @composio/json-schema-to-zod or converting to a full Zod object and casting .shape out of it.
  • Adds patch changesets for @composio/core and @composio/claude-agent-sdk.

Review hardening

Follow-up fixes from a deeper pass over the new conversion boundary:

  • Fixes a Zod 3 regression in the v3/v4 discriminator. The detector keyed on _def.type being defined, but Zod 3 stores the element schema there for z.array() / z.promise() — so a Zod 3 array/promise outputParams was misrouted into z.toJSONSchema and crashed with Cannot read properties of undefined (reading 'def'). It now discriminates on _def.type being a string literal (Zod 4) versus a ZodType object (Zod 3), with a regression test.
  • Mirrors input/output mode on the Zod 3 path. The io mode was applied to the Zod 4 converter but dropped on the Zod 3 branch; it now maps onto zod-to-json-schema's pipeStrategy/effectStrategy so .default() / .transform() / .pipe() schemas serialize consistently across both runtimes. Added a v3/v4 input-mode parity test.
  • Fails loudly on a missing named definition instead of returning the wrapper document, which downstream silently degraded into an empty object schema.
  • Replaces broad as never casts in the Claude Agent SDK provider with a single concrete tool() signature — this sidesteps the SDK's deep-instantiation error while keeping the handler's real argument type.
  • Trims now-dead code from the custom-tool schema types and conversion path.

Verification

  • mise exec -- pnpm --filter @composio/core test (933 passing)
  • mise exec -- pnpm --filter @composio/claude-agent-sdk test -- --runInBand (17 passing)
  • mise exec -- pnpm --filter @composio/core build (publint + attw clean, emits @composio/core/utils/json-schema)
  • mise exec -- pnpm --filter @composio/claude-agent-sdk exec tsc --noEmit --skipLibCheck
  • mise exec -- pnpm --filter @composio/claude-agent-sdk build (publint + attw clean)
  • mise exec -- pnpm install --frozen-lockfile
  • git diff --check

Note: mise exec -- pnpm --filter @composio/core typecheck still fails on the existing src/utils/pusher.ts ChannelAuthorizationHandler type mismatch. This PR does not touch that file; the core build and full core unit suite pass.

@vercel

vercel Bot commented Jun 1, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
docs Error Error Jun 8, 2026 9:57pm

Request Review

jkomyno added 2 commits June 9, 2026 01:56
Make isZodV4Schema a type predicate (schema is z4.ZodType) so both
branches of zodSchemaToJsonSchema narrow without casts: the v4 branch
to z4.ZodType, the v3 branch to z3.ZodType. Drop the two as-JsonSchemaObject
casts on the converter outputs — a concrete object type is already
assignable to Record<string, unknown>, so removing them lets the compiler
verify the converters return object-shaped values instead of trusting a
blind assertion. Remove the now-unused z3 import.
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