feat(helpers): add standard schema support#1997
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c4080eb882
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…en/issue-1945-standard-schema-helpers
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 32d335f217
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc85ea2ca2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 60486eefdd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5385043dac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/lib/transform.ts:1301
- The allOf-merge failure message claims the schema "cannot be merged without changing Draft 7 validation", but this branch is also reached for structural mismatches that don’t actually change Draft 7 validation semantics (e.g., enum array ordering differences). That makes the error misleading for callers.
Consider either canonicalizing order-insensitive arrays (at least enum/required) before equality checks, or adjust the message to describe that the SDK cannot merge the schemas exactly (without rewriting/canonicalizing) for strict Structured Outputs.
const fail = (): never => {
throw new Error(
`Object allOf at \`${
path.join('/') || '<root>'
}\` cannot be merged without changing Draft 7 validation.`,
);
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 20418aaac0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 49772b4dbd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 489a430261
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ebbe7a9b3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
jbeckwith-oai
left a comment
There was a problem hiding this comment.
I found one blocking issue in the public type contract. Standard Schema declares ~standard.types as optional, so the current output-inference conditional returns unknown for ordinary vendor schemas. I reproduced this with both a spec-shaped StandardSchemaV1<Input, Output> and actual Zod v4: standardResponseFormat(schema, ...).__output and standardFunction({ parameters: schema, function(args) { ... } }) both lose the schema output type. Please fix the conditional and add a regression test using an official/vendor schema whose types property has the standard optional shape.
The runtime conversion/parsing behavior otherwise looked sound in my review. I ran the focused standard-schema/transform/Zod/ResponsesParser suites (270 tests), the full suite (118 passed, 1 skipped; 1,420 tests), lint, build, TypeScript, package/export checks, and additional runtime boundary probes.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e5c4c1869c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2fe00db58e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const hasOwnObjectConstraints = Object.keys(jsonSchema).some((keyword) => | ||
| JSON_SCHEMA_OBJECT_KEYWORDS.has(keyword), | ||
| ); | ||
| if ( | ||
| isRedundantUnionWrapperType(jsonSchema.type, 'object') && | ||
| !hasOwnObjectConstraints && | ||
| Array.isArray(jsonSchema.anyOf) && | ||
| jsonSchema.anyOf.every((branch) => isObjectOnlySchema(branch, root)) | ||
| ) { |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 345f5274a2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| const { branch, index: branchIndex } = realBranches[0]!; | ||
| if (typeof branch === 'boolean' || !isObject(branch) || !isObjectOnlySchema(branch, schema)) { |
There was a problem hiding this comment.
Promote nullable singleton root unions under the object wrapper
When the root has type: 'object' and its sole real anyOf branch has type: ['object', 'null'], this predicate rejects the branch even though the outer type already excludes null. The schema is exactly equivalent to promoting the branch while retaining the root's scalar object type, but all Standard Schema helpers instead reach the root-anyOf error.
Useful? React with 👍 / 👎.
| jsonSchema.allOf = allOf.map((entry, i) => | ||
| ensureStrictJsonSchema(entry, [...path, 'allOf', String(i)], root), | ||
| ); | ||
| if (allOf.length === 1 && hasOnlyAnnotationSiblings(jsonSchema, 'allOf')) { |
There was a problem hiding this comment.
Collapse parent constraints duplicated by singleton allOf
When a converter emits a scalar schema such as { type: 'string', allOf: [{ type: 'string' }] }, the sole branch merely duplicates the parent constraint, but the parent's type makes this guard fail and the later unsupported-keyword check rejects the schema. The new branch deduplication only compares entries inside allOf, so this exactly representable parent/branch duplicate remains unhandled.
Useful? React with 👍 / 👎.
| if (!parentHasObjectShape && objectBranches.length === 0) { | ||
| return false; |
There was a problem hiding this comment.
Discard empty-schema allOf branches before classification
For a scalar intersection such as allOf: [{}, { type: 'string' }], neither the parent nor a branch has an object shape, so this return bypasses normalization and the remaining two-branch allOf is rejected later. An empty schema object is validation-equivalent to true and is therefore a neutral intersection branch, so it should be removed alongside the existing boolean true handling.
Useful? React with 👍 / 👎.
| const possibleBranches = record['oneOf'].filter((branch) => branch !== false); | ||
| if (!areOneOfBranchesMutuallyExclusive(possibleBranches, normalizedSchema)) { |
There was a problem hiding this comment.
Account for the enclosing type in oneOf exclusivity proofs
When an object-typed oneOf wrapper contains untyped object branches with distinct required literal discriminators, such as { type: 'object', oneOf: [{ properties: { kind: { const: 'a' } }, required: ['kind'] }, ...] }, the branches overlap only on non-object values, which the enclosing type: 'object' already rejects. This context therefore makes rewriting to anyOf exact, but the branch-only proof returns false because neither branch independently declares an object type, causing all four Standard Schema helpers to reject valid converter output.
Useful? React with 👍 / 👎.
| for (const keyword of Object.keys(schemaRecord)) { | ||
| if (schemaRecord[keyword] === undefined) { | ||
| delete schemaRecord[keyword]; |
There was a problem hiding this comment.
Remove undefined entries from schema maps
When a converter leaves an undefined map member such as properties: { nickname: undefined } or $defs: { Unused: undefined }, JSON serialization would omit that entry, but this pass only deletes undefined keywords from schema objects; the map container itself is never cleaned. The undefined value therefore survives until recursive strictification, where the properties path calls isNullable(undefined, ...) and crashes while reading $ref instead of treating the omitted entry consistently with the existing undefined-placeholder handling.
Useful? React with 👍 / 👎.
Summary
standardResponseFormat(),standardTextFormat(),standardFunction(), andstandardResponsesFunction()underopenai/helpers/standard-schemaMotivation
Fixes #1945.
The SDK currently has Zod-specific helpers, while #1021 and the open Valibot-specific PR #1923 show the pressure to add another helper family for each schema library. Standard Schema provides a vendor-neutral validation interface, and Standard JSON Schema provides a vendor-neutral JSON Schema conversion interface, so the SDK can support Zod, Valibot, ArkType, and future libraries without adding runtime dependencies or per-library adapters.
Behavior
The helpers use
schema["~standard"].jsonSchema.input({ target: "draft-07" })for the model-facing schema, then pass that schema throughtoStrictJsonSchema(). Callers whose validator only implements Standard Schema validation can supply aschemaoverride instead.Parsed response content and tool arguments flow through
schema["~standard"].validate(), preserving the schema’s inferred output type for parsed content and callback arguments.Validation is intentionally synchronous. The SDK’s existing
$parseRawhooks are synchronous, so Promise-returning Standard Schema validators fail with an actionable error instead of introducing hidden async behavior into parse helpers.Compatibility
This is additive and introduces no new runtime or peer dependencies. Existing Zod helpers are unchanged. The only existing behavior change is clearer shared-transform error wording for non-Zod schemas.
Validation
pnpm exec jest tests/helpers/standard-schema.test.ts tests/helpers/zod.test.ts tests/lib/transform.test.ts --runInBandpnpm exec tsc --noEmitpnpm exec prettier --check src/helpers/standard-schema.ts src/lib/transform.ts tests/helpers/standard-schema.test.ts tests/helpers/zod.test.ts tests/lib/transform.test.ts jsr.jsongit diff --checkpnpm run lint