Skip to content

fix: add resolveJsonSchemaRefs utility for recursive Zod schemas#1023

Open
mvanhorn wants to merge 1 commit intocoinbase:mainfrom
mvanhorn:osc/815-recursive-zod-schemas
Open

fix: add resolveJsonSchemaRefs utility for recursive Zod schemas#1023
mvanhorn wants to merge 1 commit intocoinbase:mainfrom
mvanhorn:osc/815-recursive-zod-schemas

Conversation

@mvanhorn
Copy link

Description

Adds a resolveJsonSchemaRefs() utility that inlines $ref pointers in JSON Schema output, fixing the BadRequestError: 400 Invalid schema when using recursive Zod schemas with OpenAI's function-calling API.

Closes #815

Problem

zodToJsonSchema() produces $ref pointers for recursive Zod types (z.lazy). OpenAI's function-calling API rejects schemas containing $ref with "object schema missing properties" (actionProvider.ts:9 stores the raw z.ZodSchema, and framework extensions like LangChain pass it through zodToJsonSchema() to build tool definitions).

Solution

resolveJsonSchemaRefs(schema, maxDepth?) post-processes JSON Schema from zodToJsonSchema():

  • Inlines all $ref references by substituting the definition body
  • Stops at maxDepth (default: 3) to handle recursive types
  • At max depth, replaces with {} (permissive empty schema)
  • Strips $defs/definitions from output

Usage:

import { resolveJsonSchemaRefs } from "@coinbase/agentkit";
import { zodToJsonSchema } from "zod-to-json-schema";

const jsonSchema = zodToJsonSchema(myRecursiveZodSchema);
const flatSchema = resolveJsonSchemaRefs(jsonSchema);
// flatSchema has no $ref - safe for OpenAI function calling

Tests

7 unit tests covering: simple refs, recursive refs with depth limiting, definitions key (not just $defs), stripping definitions from output, union types with refs, and passthrough for schemas without refs.

PASS src/resolveJsonSchemaRefs.test.ts
  resolveJsonSchemaRefs
    ✓ returns schema unchanged when no $ref present
    ✓ inlines a simple $ref
    ✓ inlines recursive $ref up to maxDepth
    ✓ handles definitions key (not just $defs)
    ✓ strips $defs from output
    ✓ handles null and primitive values
    ✓ handles union types with $ref

Lint and format checks pass.

Checklist

  • I have formatted and linted my code
  • All new and existing tests pass
  • My commits are signed (required for merge)
  • I added a changelog fragment for user-facing changes

This contribution was developed with AI assistance (Claude Code).

zodToJsonSchema() produces $ref pointers for recursive Zod types
(z.lazy), which OpenAI and other LLM function-calling APIs reject.

This utility inlines $ref references up to a configurable depth
(default: 3), replacing deeper levels with a permissive empty schema.
Exported from @coinbase/agentkit for use in custom integrations.
@mvanhorn mvanhorn requested a review from murrlincoln as a code owner March 20, 2026 01:28
@cb-heimdall
Copy link

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 1
Sum 2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

Recursive Zod Schemas lead to Invalid Schema in OpenAI

2 participants