@collective-intelligence/extensions-sdk-backend-zod
Optional Zod 4 authoring adapter for @collective-intelligence/extensions-sdk.
The core SDK remains provider-neutral; this package owns Zod and exposes one
public adapter.
zod (>=4.4.2 <5) is a peer dependency. Install it next to the adapter so
the adapter and your schemas share one Zod instance:
bun add @collective-intelligence/extensions-sdk \
@collective-intelligence/extensions-sdk-backend-zod zod
import type { PackageCodec } from "@collective-intelligence/extensions-sdk"
import { zodSchema } from "@collective-intelligence/extensions-sdk-backend-zod"
import { z } from "zod"
const identifier = zodSchema(
z.codec(z.string().regex(/^item:[0-9]+$/), z.number().int(), {
decode: (wire) => Number(wire.slice(5)),
encode: (value) => `item:${value}`
})
)
const outputSchema: PackageCodec<string, number> = identifier
zodSchema(...) infers Zod input as the wire type and Zod output as the
implementation value. Validation uses safeParseAsync; encoding uses
safeEncodeAsync, so asynchronous refinements and codecs retain their native
behavior.
The adapter also creates deterministic input and output JSON Schema Draft
2020-12 projections for interchange consumers only. Runtime validation and
encoding always call Zod directly. Both directions describe Zod's input
wire type: input validation consumes that wire type, and output encoding
produces it. The Zod
output type remains the in-process domain value and is validated by native
reverse encoding rather than advertised as transported JSON. Codecs,
transforms, preprocessors, success schemas, coercion, catches, overwrites, and
custom refinements are conservatively reported as structural fidelity
because their native callbacks or semantics can be stricter than the portable
schema. Unsupported JSON Schema constructs fail when the adapter is created
instead of being silently widened.
One-way transforms
Zod transforms and preprocessors do not provide reverse encoding. Their adapter result is therefore decoder-only and has no output projection:
const parsedNumber = zodSchema(z.string().transform(Number))
await parsedNumber.validate("42") // { value: 42 }
// parsedNumber does not satisfy PackageCodec<string, number>
This is recursive for nested objects, arrays, unions, tuples, records, exact
optionals, promises, and other wrappers. z.success(...) is also decoder-only.
Use an explicit z.codec(inputSchema, outputSchema, { decode, encode }) when
the schema must be accepted in an output position. Lazy schemas are treated as
decoder-only conservatively because their recursive type cannot prove encoder
capability statically.
The provider string (zod/4) is diagnostic metadata only. Hosts dispatch on
the structural validate and encode capabilities; project(...) is only
for portable consumers. There is no registry or provider switch.
Packed boundary
Run bun run test:pack to verify the published boundary. It builds and packs
this package and @collective-intelligence/extensions-sdk, installs the packed
artifacts and the peer zod into a temporary copy of test/consumer, then
type-checks and runs that consumer. The build requires the base SDK
declarations, so build @collective-intelligence/extensions-sdk first.