Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/parsing-run-tools.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import OpenAI from 'openai';
import z from 'zod/v3';
import z from 'zod/v4'; // Also works for 'zod/v3'
import { zodFunction } from 'openai/helpers/zod';

const Table = z.enum(['orders', 'customers', 'products']);
Expand Down
2 changes: 1 addition & 1 deletion examples/parsing-stream.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { zodResponseFormat } from 'openai/helpers/zod';
import OpenAI from 'openai/index';
import { z } from 'zod/v3';
import { z } from 'zod/v4'; // Also works for 'zod/v3'

const Step = z.object({
explanation: z.string(),
Expand Down
2 changes: 1 addition & 1 deletion examples/parsing-tools-stream.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { zodFunction } from 'openai/helpers/zod';
import OpenAI from 'openai/index';
import { z } from 'zod/v3';
import { z } from 'zod/v4'; // Also works for 'zod/v3'

const GetWeatherArgs = z.object({
city: z.string(),
Expand Down
2 changes: 1 addition & 1 deletion examples/parsing-tools.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { zodFunction } from 'openai/helpers/zod';
import OpenAI from 'openai/index';
import { z } from 'zod/v3';
import { z } from 'zod/v4'; // Also works for 'zod/v3'

const Table = z.enum(['orders', 'customers', 'products']);

Expand Down
2 changes: 1 addition & 1 deletion examples/parsing.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { zodResponseFormat } from 'openai/helpers/zod';
import OpenAI from 'openai/index';
import { z } from 'zod/v3';
import { z } from 'zod/v4'; // Also works for 'zod/v3'

const Step = z.object({
explanation: z.string(),
Expand Down
2 changes: 1 addition & 1 deletion examples/responses/streaming-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { OpenAI } from 'openai';
import { zodResponsesFunction } from 'openai/helpers/zod';
import { z } from 'zod/v3';
import { z } from 'zod/v4'; // Also works for 'zod/v3'

const Table = z.enum(['orders', 'customers', 'products']);
const Column = z.enum([
Expand Down
2 changes: 1 addition & 1 deletion examples/responses/structured-outputs-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { OpenAI } from 'openai';
import { zodResponsesFunction } from 'openai/helpers/zod';
import { z } from 'zod/v3';
import { z } from 'zod/v4'; // Also works for 'zod/v3'

const Table = z.enum(['orders', 'customers', 'products']);
const Column = z.enum([
Expand Down
2 changes: 1 addition & 1 deletion examples/responses/structured-outputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { OpenAI } from 'openai';
import { zodTextFormat } from 'openai/helpers/zod';
import { z } from 'zod/v3';
import { z } from 'zod/v4'; // Also works for 'zod/v3'

const Step = z.object({
explanation: z.string(),
Expand Down
2 changes: 1 addition & 1 deletion examples/tool-call-helpers-zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import OpenAI from 'openai';
import { zodFunction } from 'openai/helpers/zod';
import { z } from 'zod/v3';
import { z } from 'zod/v4'; // Also works for 'zod/v3'

// gets API Key from environment variable OPENAI_API_KEY
const openai = new OpenAI();
Expand Down
2 changes: 1 addition & 1 deletion examples/ui-generation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import OpenAI from 'openai';
import { z } from 'zod/v3';
import { z } from 'zod/v4'; // Also works for 'zod/v3'
import { zodResponseFormat } from 'openai/helpers/zod';

const openai = new OpenAI();
Expand Down
6 changes: 4 additions & 2 deletions scripts/utils/upload-artifact.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ if [[ "$SIGNED_URL" == "null" ]]; then
exit 1
fi

UPLOAD_RESPONSE=$(tar "${BASE_PATH:+-C$BASE_PATH}" -cz "${ARTIFACT_PATH:-dist}" | curl -v -X PUT \
TARBALL=$(cd dist && npm pack --silent)

UPLOAD_RESPONSE=$(curl -v -X PUT \
-H "Content-Type: application/gzip" \
--data-binary @- "$SIGNED_URL" 2>&1)
--data-binary "@dist/$TARBALL" "$SIGNED_URL" 2>&1)

if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
Expand Down
112 changes: 94 additions & 18 deletions src/helpers/zod.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ResponseFormatJSONSchema } from '../resources/index';
import type { infer as zodInfer, ZodType } from 'zod/v3';
import { z as z3 } from 'zod/v3';
import { z as z4 } from 'zod/v4';
import {
AutoParseableResponseFormat,
AutoParseableTextFormat,
Expand All @@ -11,8 +12,10 @@ import {
import { zodToJsonSchema as _zodToJsonSchema } from '../_vendor/zod-to-json-schema';
import { AutoParseableResponseTool, makeParseableResponseTool } from '../lib/ResponsesParser';
import { type ResponseFormatTextJSONSchemaConfig } from '../resources/responses/responses';
import { toStrictJsonSchema } from '../lib/transform';
import { JSONSchema } from '../lib/jsonschema';

function zodToJsonSchema(schema: ZodType, options: { name: string }): Record<string, unknown> {
function zodV3ToJsonSchema(schema: z3.ZodType, options: { name: string }): Record<string, unknown> {
return _zodToJsonSchema(schema, {
openaiStrictMode: true,
name: options.name,
Expand All @@ -22,6 +25,18 @@ function zodToJsonSchema(schema: ZodType, options: { name: string }): Record<str
});
}

function zodV4ToJsonSchema(schema: z4.ZodType): Record<string, unknown> {
return toStrictJsonSchema(
z4.toJSONSchema(schema, {
target: 'draft-7',
}) as JSONSchema,
) as Record<string, unknown>;
}

function isZodV4(zodObject: z3.ZodType | z4.ZodType): zodObject is z4.ZodType {
return '_zod' in zodObject;
}

/**
* Creates a chat completion `JSONSchema` response format object from
* the given Zod schema.
Expand Down Expand Up @@ -59,37 +74,57 @@ function zodToJsonSchema(schema: ZodType, options: { name: string }): Record<str
* This can be passed directly to the `.create()` method but will not
* result in any automatic parsing, you'll have to parse the response yourself.
*/
export function zodResponseFormat<ZodInput extends ZodType>(
export function zodResponseFormat<ZodInput extends z3.ZodType>(
zodObject: ZodInput,
name: string,
props?: Omit<ResponseFormatJSONSchema.JSONSchema, 'schema' | 'strict' | 'name'>,
): AutoParseableResponseFormat<z3.infer<ZodInput>>;
export function zodResponseFormat<ZodInput extends z4.ZodType>(
zodObject: ZodInput,
name: string,
props?: Omit<ResponseFormatJSONSchema.JSONSchema, 'schema' | 'strict' | 'name'>,
): AutoParseableResponseFormat<z4.infer<ZodInput>>;
export function zodResponseFormat<ZodInput extends z3.ZodType | z4.ZodType>(
zodObject: ZodInput,
name: string,
props?: Omit<ResponseFormatJSONSchema.JSONSchema, 'schema' | 'strict' | 'name'>,
): AutoParseableResponseFormat<zodInfer<ZodInput>> {
): unknown {
return makeParseableResponseFormat(
{
type: 'json_schema',
json_schema: {
...props,
name,
strict: true,
schema: zodToJsonSchema(zodObject, { name }),
schema: isZodV4(zodObject) ? zodV4ToJsonSchema(zodObject) : zodV3ToJsonSchema(zodObject, { name }),
},
},
(content) => zodObject.parse(JSON.parse(content)),
);
}

export function zodTextFormat<ZodInput extends ZodType>(
export function zodTextFormat<ZodInput extends z3.ZodType>(
zodObject: ZodInput,
name: string,
props?: Omit<ResponseFormatTextJSONSchemaConfig, 'schema' | 'type' | 'strict' | 'name'>,
): AutoParseableTextFormat<zodInfer<ZodInput>> {
): AutoParseableTextFormat<z3.infer<ZodInput>>;
export function zodTextFormat<ZodInput extends z4.ZodType>(
zodObject: ZodInput,
name: string,
props?: Omit<ResponseFormatTextJSONSchemaConfig, 'schema' | 'type' | 'strict' | 'name'>,
): AutoParseableTextFormat<z4.infer<ZodInput>>;
export function zodTextFormat<ZodInput extends z3.ZodType | z4.ZodType>(
zodObject: ZodInput,
name: string,
props?: Omit<ResponseFormatTextJSONSchemaConfig, 'schema' | 'type' | 'strict' | 'name'>,
): unknown {
return makeParseableTextFormat(
{
type: 'json_schema',
...props,
name,
strict: true,
schema: zodToJsonSchema(zodObject, { name }),
schema: isZodV4(zodObject) ? zodV4ToJsonSchema(zodObject) : zodV3ToJsonSchema(zodObject, { name }),
},
(content) => zodObject.parse(JSON.parse(content)),
);
Expand All @@ -100,23 +135,41 @@ export function zodTextFormat<ZodInput extends ZodType>(
* automatically by the chat completion `.runTools()` method or automatically
* parsed by `.parse()` / `.stream()`.
*/
export function zodFunction<Parameters extends ZodType>(options: {
export function zodFunction<Parameters extends z3.ZodType>(options: {
name: string;
parameters: Parameters;
function?: ((args: zodInfer<Parameters>) => unknown | Promise<unknown>) | undefined;
function?: ((args: z3.infer<Parameters>) => unknown | Promise<unknown>) | undefined;
description?: string | undefined;
}): AutoParseableTool<{
arguments: Parameters;
name: string;
function: (args: zodInfer<Parameters>) => unknown;
}> {
// @ts-expect-error TODO
function: (args: z3.infer<Parameters>) => unknown;
}>;
export function zodFunction<Parameters extends z4.ZodType>(options: {
name: string;
parameters: Parameters;
function?: ((args: z4.infer<Parameters>) => unknown | Promise<unknown>) | undefined;
description?: string | undefined;
}): AutoParseableTool<{
arguments: Parameters;
name: string;
function: (args: z4.infer<Parameters>) => unknown;
}>;
export function zodFunction<Parameters extends z3.ZodType | z4.ZodType>(options: {
name: string;
parameters: Parameters;
function?: ((args: any) => unknown | Promise<unknown>) | undefined;
description?: string | undefined;
}): unknown {
return makeParseableTool<any>(
{
type: 'function',
function: {
name: options.name,
parameters: zodToJsonSchema(options.parameters, { name: options.name }),
parameters:
isZodV4(options.parameters) ?
zodV4ToJsonSchema(options.parameters)
: zodV3ToJsonSchema(options.parameters, { name: options.name }),
strict: true,
...(options.description ? { description: options.description } : undefined),
},
Expand All @@ -128,21 +181,44 @@ export function zodFunction<Parameters extends ZodType>(options: {
);
}

export function zodResponsesFunction<Parameters extends ZodType>(options: {
export function zodResponsesFunction<Parameters extends z3.ZodType>(options: {
name: string;
parameters: Parameters;
function?: ((args: z3.infer<Parameters>) => unknown | Promise<unknown>) | undefined;
description?: string | undefined;
}): AutoParseableResponseTool<{
arguments: Parameters;
name: string;
function: (args: z3.infer<Parameters>) => unknown;
}>;
export function zodResponsesFunction<Parameters extends z4.ZodType>(options: {
name: string;
parameters: Parameters;
function?: ((args: z4.infer<Parameters>) => unknown | Promise<unknown>) | undefined;
description?: string | undefined;
}): AutoParseableResponseTool<{
arguments: Parameters;
name: string;
function: (args: z4.infer<Parameters>) => unknown;
}>;
export function zodResponsesFunction<Parameters extends z3.ZodType | z4.ZodType>(options: {
name: string;
parameters: Parameters;
function?: ((args: zodInfer<Parameters>) => unknown | Promise<unknown>) | undefined;
function?: ((args: unknown) => unknown | Promise<unknown>) | undefined;
description?: string | undefined;
}): AutoParseableResponseTool<{
arguments: Parameters;
name: string;
function: (args: zodInfer<Parameters>) => unknown;
function: (args: unknown) => unknown;
}> {
return makeParseableResponseTool<any>(
{
type: 'function',
name: options.name,
parameters: zodToJsonSchema(options.parameters, { name: options.name }),
parameters:
isZodV4(options.parameters) ?
zodV4ToJsonSchema(options.parameters)
: zodV3ToJsonSchema(options.parameters, { name: options.name }),
strict: true,
...(options.description ? { description: options.description } : undefined),
},
Expand Down
24 changes: 24 additions & 0 deletions src/lib/jsonschema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,30 @@ export interface JSONSchema {
oneOf?: JSONSchemaDefinition[] | undefined;
not?: JSONSchemaDefinition | undefined;

/**
* @see https://json-schema.org/draft/2020-12/json-schema-core.html#section-8.2.4
*/
$defs?:
| {
[key: string]: JSONSchemaDefinition;
}
| undefined;

/**
* @deprecated Use $defs instead (draft 2019-09+)
* @see https://tools.ietf.org/doc/html/draft-handrews-json-schema-validation-01#page-22
*/
definitions?:
| {
[key: string]: JSONSchemaDefinition;
}
| undefined;

/**
* @see https://json-schema.org/draft/2020-12/json-schema-core#ref
*/
$ref?: string | undefined;

/**
* @see https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-7
*/
Expand Down
Loading