Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/gold-baboons-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ai-sdk/provider-utils": patch
---

Ensure the default empty tool input schema includes `type: "object"` for OpenAI-compatible providers that require object schemas.
12 changes: 12 additions & 0 deletions packages/provider-utils/src/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ import * as z4 from 'zod/v4';
import { safeParseJSON } from './parse-json';
import { zodSchema } from './zod-schema';

describe('asSchema', () => {
it('should create an object schema when no schema is provided', async () => {
const schema = asSchema(undefined);

expect(await schema.jsonSchema).toStrictEqual({
type: 'object',
properties: {},
additionalProperties: false,
});
});
});

describe('zodSchema', () => {
describe('zod/v4', () => {
describe('json schema conversion', () => {
Expand Down
155 changes: 155 additions & 0 deletions packages/provider-utils/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,158 @@
validate,
};
}
<<<<<<< HEAD

Check failure on line 102 in packages/provider-utils/src/schema.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
=======

Check failure on line 103 in packages/provider-utils/src/schema.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.

function isSchema(value: unknown): value is Schema {
return (
typeof value === 'object' &&
value !== null &&
schemaSymbol in value &&
value[schemaSymbol] === true &&
'jsonSchema' in value &&
'validate' in value
);
}

export function asSchema<OBJECT>(
schema: FlexibleSchema<OBJECT> | undefined,
): Schema<OBJECT> {
return schema == null
? jsonSchema({
type: 'object',
properties: {},
additionalProperties: false,
})
: isSchema(schema)
? schema
: '~standard' in schema
? schema['~standard'].vendor === 'zod'
? zodSchema(schema as ZodSchema<OBJECT>)
: standardSchema(schema as StandardSchema<OBJECT>)
: schema();
}

function standardSchema<OBJECT>(
standardSchema: StandardSchema<OBJECT>,
): Schema<OBJECT> {
return jsonSchema(
() =>
addAdditionalPropertiesToJsonSchema(
standardSchema['~standard'].jsonSchema.input({
target: 'draft-07',
}) as JSONSchema7,
),
{
validate: async value => {
const result = await standardSchema['~standard'].validate(value);
return 'value' in result
? { success: true, value: result.value }
: {
success: false,
error: new TypeValidationError({
value,
cause: result.issues,
}),
};
},
},
);
}

export function zod3Schema<OBJECT>(
zodSchema: z3.Schema<OBJECT, z3.ZodTypeDef, any>,
options?: {
/**
* Enables support for references in the schema.
* This is required for recursive schemas, e.g. with `z.lazy`.
* However, not all language models and providers support such references.
* Defaults to `false`.
*/
useReferences?: boolean;
},
): Schema<OBJECT> {
// default to no references (to support openapi conversion for google)
const useReferences = options?.useReferences ?? false;

return jsonSchema(
// defer json schema creation to avoid unnecessary computation when only validation is needed
() =>
zod3ToJsonSchema(zodSchema, {
$refStrategy: useReferences ? 'root' : 'none',
}) as JSONSchema7,
{
validate: async value => {
const result = await zodSchema.safeParseAsync(value);
return result.success
? { success: true, value: result.data }
: { success: false, error: result.error };
},
},
);
}

export function zod4Schema<OBJECT>(
zodSchema: z4.core.$ZodType<OBJECT, any>,
options?: {
/**
* Enables support for references in the schema.
* This is required for recursive schemas, e.g. with `z.lazy`.
* However, not all language models and providers support such references.
* Defaults to `false`.
*/
useReferences?: boolean;
},
): Schema<OBJECT> {
// default to no references (to support openapi conversion for google)
const useReferences = options?.useReferences ?? false;

return jsonSchema(
// defer json schema creation to avoid unnecessary computation when only validation is needed
() =>
addAdditionalPropertiesToJsonSchema(
z4.toJSONSchema(zodSchema, {
target: 'draft-7',
io: 'input',
reused: useReferences ? 'ref' : 'inline',
}) as JSONSchema7,
),
{
validate: async value => {
const result = await z4.safeParseAsync(zodSchema, value);
return result.success
? { success: true, value: result.data }
: { success: false, error: result.error };
},
},
);
}

export function isZod4Schema(
zodSchema: z4.core.$ZodType<any, any> | z3.Schema<any, z3.ZodTypeDef, any>,
): zodSchema is z4.core.$ZodType<any, any> {
// https://zod.dev/library-authors?id=how-to-support-zod-3-and-zod-4-simultaneously
return '_zod' in zodSchema;
}

export function zodSchema<OBJECT>(
zodSchema:
| z4.core.$ZodType<OBJECT, any>
| z3.Schema<OBJECT, z3.ZodTypeDef, any>,
options?: {
/**
* Enables support for references in the schema.
* This is required for recursive schemas, e.g. with `z.lazy`.
* However, not all language models and providers support such references.
* Defaults to `false`.
*/
useReferences?: boolean;
},
): Schema<OBJECT> {
if (isZod4Schema(zodSchema)) {
return zod4Schema(zodSchema, options);
} else {
return zod3Schema(zodSchema, options);
}
}
>>>>>>> d559de90d2 ([v6.0] fix: include object type in default tool schema (#16799))

Check failure on line 256 in packages/provider-utils/src/schema.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
Loading