@ai-sdk/provider-utils 4.0.4 → 4.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/src/__snapshots__/schema.test.ts.snap +346 -0
- package/src/add-additional-properties-to-json-schema.test.ts +289 -0
- package/src/add-additional-properties-to-json-schema.ts +53 -0
- package/src/combine-headers.ts +11 -0
- package/src/convert-async-iterator-to-readable-stream.test.ts +78 -0
- package/src/convert-async-iterator-to-readable-stream.ts +47 -0
- package/src/convert-image-model-file-to-data-uri.test.ts +85 -0
- package/src/convert-image-model-file-to-data-uri.ts +19 -0
- package/src/convert-to-form-data.test.ts +167 -0
- package/src/convert-to-form-data.ts +61 -0
- package/src/create-tool-name-mapping.test.ts +163 -0
- package/src/create-tool-name-mapping.ts +66 -0
- package/src/delay.test.ts +212 -0
- package/src/delay.ts +47 -0
- package/src/delayed-promise.test.ts +132 -0
- package/src/delayed-promise.ts +61 -0
- package/src/download-blob.test.ts +145 -0
- package/src/download-blob.ts +31 -0
- package/src/download-error.ts +39 -0
- package/src/extract-response-headers.ts +9 -0
- package/src/fetch-function.ts +4 -0
- package/src/generate-id.test.ts +31 -0
- package/src/generate-id.ts +57 -0
- package/src/get-error-message.ts +15 -0
- package/src/get-from-api.test.ts +199 -0
- package/src/get-from-api.ts +97 -0
- package/src/get-runtime-environment-user-agent.test.ts +47 -0
- package/src/get-runtime-environment-user-agent.ts +24 -0
- package/src/handle-fetch-error.ts +39 -0
- package/src/index.ts +67 -0
- package/src/inject-json-instruction.test.ts +404 -0
- package/src/inject-json-instruction.ts +63 -0
- package/src/is-abort-error.ts +8 -0
- package/src/is-async-iterable.ts +3 -0
- package/src/is-non-nullable.ts +12 -0
- package/src/is-url-supported.test.ts +282 -0
- package/src/is-url-supported.ts +40 -0
- package/src/load-api-key.ts +45 -0
- package/src/load-optional-setting.ts +30 -0
- package/src/load-setting.ts +62 -0
- package/src/maybe-promise-like.ts +3 -0
- package/src/media-type-to-extension.test.ts +26 -0
- package/src/media-type-to-extension.ts +22 -0
- package/src/normalize-headers.test.ts +64 -0
- package/src/normalize-headers.ts +38 -0
- package/src/parse-json-event-stream.ts +33 -0
- package/src/parse-json.test.ts +191 -0
- package/src/parse-json.ts +122 -0
- package/src/parse-provider-options.ts +32 -0
- package/src/post-to-api.ts +166 -0
- package/src/provider-tool-factory.ts +125 -0
- package/src/remove-undefined-entries.test.ts +57 -0
- package/src/remove-undefined-entries.ts +12 -0
- package/src/resolve.test.ts +125 -0
- package/src/resolve.ts +17 -0
- package/src/response-handler.test.ts +89 -0
- package/src/response-handler.ts +187 -0
- package/src/schema.test-d.ts +11 -0
- package/src/schema.test.ts +502 -0
- package/src/schema.ts +267 -0
- package/src/secure-json-parse.test.ts +59 -0
- package/src/secure-json-parse.ts +92 -0
- package/src/test/convert-array-to-async-iterable.ts +9 -0
- package/src/test/convert-array-to-readable-stream.ts +15 -0
- package/src/test/convert-async-iterable-to-array.ts +9 -0
- package/src/test/convert-readable-stream-to-array.ts +14 -0
- package/src/test/convert-response-stream-to-array.ts +9 -0
- package/src/test/index.ts +7 -0
- package/src/test/is-node-version.ts +4 -0
- package/src/test/mock-id.ts +8 -0
- package/src/to-json-schema/zod3-to-json-schema/LICENSE +16 -0
- package/src/to-json-schema/zod3-to-json-schema/README.md +24 -0
- package/src/to-json-schema/zod3-to-json-schema/get-relative-path.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/index.ts +1 -0
- package/src/to-json-schema/zod3-to-json-schema/options.ts +98 -0
- package/src/to-json-schema/zod3-to-json-schema/parse-def.test.ts +224 -0
- package/src/to-json-schema/zod3-to-json-schema/parse-def.ts +109 -0
- package/src/to-json-schema/zod3-to-json-schema/parse-types.ts +57 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/any.ts +5 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/array.test.ts +98 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/array.ts +38 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.test.ts +51 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.ts +44 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/boolean.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/branded.test.ts +16 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/branded.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/catch.test.ts +15 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/catch.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/date.test.ts +97 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/date.ts +64 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/default.test.ts +54 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/default.ts +14 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/effects.test.ts +41 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/effects.ts +14 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/enum.ts +13 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.test.ts +92 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.ts +52 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/literal.ts +29 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/map.test.ts +48 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/map.ts +47 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.test.ts +102 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.ts +31 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/never.ts +9 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/null.ts +9 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.test.ts +67 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.ts +42 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/number.test.ts +65 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/number.ts +44 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/object.test.ts +149 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/object.ts +88 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/optional.test.ts +147 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/optional.ts +23 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/pipe.test.ts +35 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/pipeline.ts +29 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/promise.test.ts +15 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/promise.ts +11 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.test.ts +20 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/record.test.ts +108 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/record.ts +71 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/set.test.ts +20 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/set.ts +35 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/string.test.ts +438 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/string.ts +426 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.test.ts +33 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.ts +61 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/undefined.ts +11 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/union.test.ts +226 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/union.ts +144 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/unknown.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/refs.test.ts +919 -0
- package/src/to-json-schema/zod3-to-json-schema/refs.ts +39 -0
- package/src/to-json-schema/zod3-to-json-schema/select-parser.ts +115 -0
- package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.test.ts +862 -0
- package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.ts +93 -0
- package/src/types/assistant-model-message.ts +39 -0
- package/src/types/content-part.ts +379 -0
- package/src/types/data-content.ts +4 -0
- package/src/types/execute-tool.ts +27 -0
- package/src/types/index.ts +40 -0
- package/src/types/model-message.ts +14 -0
- package/src/types/provider-options.ts +9 -0
- package/src/types/system-model-message.ts +20 -0
- package/src/types/tool-approval-request.ts +16 -0
- package/src/types/tool-approval-response.ts +27 -0
- package/src/types/tool-call.ts +31 -0
- package/src/types/tool-model-message.ts +23 -0
- package/src/types/tool-result.ts +35 -0
- package/src/types/tool.test-d.ts +193 -0
- package/src/types/tool.ts +324 -0
- package/src/types/user-model-message.ts +22 -0
- package/src/uint8-utils.ts +26 -0
- package/src/validate-types.test.ts +105 -0
- package/src/validate-types.ts +81 -0
- package/src/version.ts +6 -0
- package/src/with-user-agent-suffix.test.ts +84 -0
- package/src/with-user-agent-suffix.ts +27 -0
- package/src/without-trailing-slash.ts +3 -0
- package/LICENSE +0 -13
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ZodNativeEnumDef } from 'zod/v3';
|
|
2
|
+
|
|
3
|
+
export type JsonSchema7NativeEnumType = {
|
|
4
|
+
type: 'string' | 'number' | ['string', 'number'];
|
|
5
|
+
enum: (string | number)[];
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function parseNativeEnumDef(
|
|
9
|
+
def: ZodNativeEnumDef,
|
|
10
|
+
): JsonSchema7NativeEnumType {
|
|
11
|
+
const object = def.values;
|
|
12
|
+
const actualKeys = Object.keys(def.values).filter((key: string) => {
|
|
13
|
+
return typeof object[object[key]] !== 'number';
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const actualValues = actualKeys.map((key: string) => object[key]);
|
|
17
|
+
|
|
18
|
+
const parsedTypes = Array.from(
|
|
19
|
+
new Set(actualValues.map((values: string | number) => typeof values)),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
type:
|
|
24
|
+
parsedTypes.length === 1
|
|
25
|
+
? parsedTypes[0] === 'string'
|
|
26
|
+
? 'string'
|
|
27
|
+
: 'number'
|
|
28
|
+
: ['string', 'number'],
|
|
29
|
+
enum: actualValues,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { z } from 'zod/v3';
|
|
3
|
+
import { parseObjectDef } from './object';
|
|
4
|
+
import { getRefs } from '../refs';
|
|
5
|
+
import { JSONSchema7 } from '@ai-sdk/provider';
|
|
6
|
+
|
|
7
|
+
describe('nullable', () => {
|
|
8
|
+
it('should be possible to properly reference nested nullable primitives', () => {
|
|
9
|
+
const nullablePrimitive = z.string().nullable();
|
|
10
|
+
|
|
11
|
+
const schema = z.object({
|
|
12
|
+
one: nullablePrimitive,
|
|
13
|
+
two: nullablePrimitive,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const jsonSchema: any = parseObjectDef(schema._def, getRefs());
|
|
17
|
+
|
|
18
|
+
expect(jsonSchema).toStrictEqual({
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
one: { type: ['string', 'null'] },
|
|
23
|
+
two: { $ref: '#/properties/one' },
|
|
24
|
+
},
|
|
25
|
+
required: ['one', 'two'],
|
|
26
|
+
} satisfies JSONSchema7);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should be possible to properly reference nested nullable primitives', () => {
|
|
30
|
+
const three = z.string();
|
|
31
|
+
|
|
32
|
+
const nullableObject = z
|
|
33
|
+
.object({
|
|
34
|
+
three,
|
|
35
|
+
})
|
|
36
|
+
.nullable();
|
|
37
|
+
|
|
38
|
+
const schema = z.object({
|
|
39
|
+
one: nullableObject,
|
|
40
|
+
two: nullableObject,
|
|
41
|
+
three,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const jsonSchema: any = parseObjectDef(schema._def, getRefs());
|
|
45
|
+
|
|
46
|
+
expect(jsonSchema).toStrictEqual({
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
one: {
|
|
50
|
+
anyOf: [
|
|
51
|
+
{
|
|
52
|
+
type: 'object',
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
required: ['three'],
|
|
55
|
+
properties: { three: { type: 'string' } },
|
|
56
|
+
},
|
|
57
|
+
{ type: 'null' },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
two: { $ref: '#/properties/one' },
|
|
61
|
+
three: { $ref: '#/properties/one/anyOf/0/properties/three' },
|
|
62
|
+
},
|
|
63
|
+
required: ['one', 'two', 'three'],
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
} satisfies JSONSchema7);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ZodNullableDef } from 'zod/v3';
|
|
2
|
+
import { parseDef } from '../parse-def';
|
|
3
|
+
import { JsonSchema7Type } from '../parse-types';
|
|
4
|
+
import { Refs } from '../refs';
|
|
5
|
+
import { JsonSchema7NullType } from './null';
|
|
6
|
+
import { primitiveMappings } from './union';
|
|
7
|
+
|
|
8
|
+
export type JsonSchema7NullableType =
|
|
9
|
+
| {
|
|
10
|
+
anyOf: [JsonSchema7Type, JsonSchema7NullType];
|
|
11
|
+
}
|
|
12
|
+
| {
|
|
13
|
+
type: [string, 'null'];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function parseNullableDef(
|
|
17
|
+
def: ZodNullableDef,
|
|
18
|
+
refs: Refs,
|
|
19
|
+
): JsonSchema7NullableType | undefined {
|
|
20
|
+
if (
|
|
21
|
+
['ZodString', 'ZodNumber', 'ZodBigInt', 'ZodBoolean', 'ZodNull'].includes(
|
|
22
|
+
def.innerType._def.typeName,
|
|
23
|
+
) &&
|
|
24
|
+
(!def.innerType._def.checks || !def.innerType._def.checks.length)
|
|
25
|
+
) {
|
|
26
|
+
return {
|
|
27
|
+
type: [
|
|
28
|
+
primitiveMappings[
|
|
29
|
+
def.innerType._def.typeName as keyof typeof primitiveMappings
|
|
30
|
+
],
|
|
31
|
+
'null',
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const base = parseDef(def.innerType._def, {
|
|
37
|
+
...refs,
|
|
38
|
+
currentPath: [...refs.currentPath, 'anyOf', '0'],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return base && { anyOf: [base, { type: 'null' }] };
|
|
42
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { JSONSchema7 } from '@ai-sdk/provider';
|
|
3
|
+
import { z } from 'zod/v3';
|
|
4
|
+
import { parseNumberDef } from './number';
|
|
5
|
+
|
|
6
|
+
describe('number', () => {
|
|
7
|
+
it('should be possible to describe minimum number', () => {
|
|
8
|
+
const parsedSchema = parseNumberDef(z.number().min(5)._def);
|
|
9
|
+
|
|
10
|
+
expect(parsedSchema).toStrictEqual({
|
|
11
|
+
type: 'number',
|
|
12
|
+
minimum: 5,
|
|
13
|
+
} satisfies JSONSchema7);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should be possible to describe maximum number', () => {
|
|
17
|
+
const parsedSchema = parseNumberDef(z.number().max(5)._def);
|
|
18
|
+
|
|
19
|
+
expect(parsedSchema).toStrictEqual({
|
|
20
|
+
type: 'number',
|
|
21
|
+
maximum: 5,
|
|
22
|
+
} satisfies JSONSchema7);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should be possible to describe both minimum and maximum number', () => {
|
|
26
|
+
const parsedSchema = parseNumberDef(z.number().min(5).max(5)._def);
|
|
27
|
+
|
|
28
|
+
expect(parsedSchema).toStrictEqual({
|
|
29
|
+
type: 'number',
|
|
30
|
+
minimum: 5,
|
|
31
|
+
maximum: 5,
|
|
32
|
+
} satisfies JSONSchema7);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should be possible to describe an integer', () => {
|
|
36
|
+
const parsedSchema = parseNumberDef(z.number().int()._def);
|
|
37
|
+
|
|
38
|
+
expect(parsedSchema).toStrictEqual({
|
|
39
|
+
type: 'integer',
|
|
40
|
+
} satisfies JSONSchema7);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should be possible to describe multiples of n', () => {
|
|
44
|
+
const parsedSchema = parseNumberDef(z.number().multipleOf(2)._def);
|
|
45
|
+
|
|
46
|
+
expect(parsedSchema).toStrictEqual({
|
|
47
|
+
type: 'number',
|
|
48
|
+
multipleOf: 2,
|
|
49
|
+
} satisfies JSONSchema7);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should be possible to describe positive, negative, nonpositive and nonnegative numbers', () => {
|
|
53
|
+
const parsedSchema = parseNumberDef(
|
|
54
|
+
z.number().positive().negative().nonpositive().nonnegative()._def,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(parsedSchema).toStrictEqual({
|
|
58
|
+
type: 'number',
|
|
59
|
+
minimum: 0,
|
|
60
|
+
maximum: 0,
|
|
61
|
+
exclusiveMaximum: 0,
|
|
62
|
+
exclusiveMinimum: 0,
|
|
63
|
+
} satisfies JSONSchema7);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ZodNumberDef } from 'zod/v3';
|
|
2
|
+
|
|
3
|
+
export type JsonSchema7NumberType = {
|
|
4
|
+
type: 'number' | 'integer';
|
|
5
|
+
minimum?: number;
|
|
6
|
+
exclusiveMinimum?: number;
|
|
7
|
+
maximum?: number;
|
|
8
|
+
exclusiveMaximum?: number;
|
|
9
|
+
multipleOf?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function parseNumberDef(def: ZodNumberDef): JsonSchema7NumberType {
|
|
13
|
+
const res: JsonSchema7NumberType = {
|
|
14
|
+
type: 'number',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
if (!def.checks) return res;
|
|
18
|
+
|
|
19
|
+
for (const check of def.checks) {
|
|
20
|
+
switch (check.kind) {
|
|
21
|
+
case 'int':
|
|
22
|
+
res.type = 'integer';
|
|
23
|
+
break;
|
|
24
|
+
case 'min':
|
|
25
|
+
if (check.inclusive) {
|
|
26
|
+
res.minimum = check.value;
|
|
27
|
+
} else {
|
|
28
|
+
res.exclusiveMinimum = check.value;
|
|
29
|
+
}
|
|
30
|
+
break;
|
|
31
|
+
case 'max':
|
|
32
|
+
if (check.inclusive) {
|
|
33
|
+
res.maximum = check.value;
|
|
34
|
+
} else {
|
|
35
|
+
res.exclusiveMaximum = check.value;
|
|
36
|
+
}
|
|
37
|
+
break;
|
|
38
|
+
case 'multipleOf':
|
|
39
|
+
res.multipleOf = check.value;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return res;
|
|
44
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { z } from 'zod/v3';
|
|
3
|
+
import { parseObjectDef } from './object';
|
|
4
|
+
import { getRefs } from '../refs';
|
|
5
|
+
import { JSONSchema7 } from '@ai-sdk/provider';
|
|
6
|
+
|
|
7
|
+
describe('object', () => {
|
|
8
|
+
it('should be possible to describe catchAll schema', () => {
|
|
9
|
+
const schema = z
|
|
10
|
+
.object({ normalProperty: z.string() })
|
|
11
|
+
.catchall(z.boolean());
|
|
12
|
+
|
|
13
|
+
const parsedSchema = parseObjectDef(schema._def, getRefs());
|
|
14
|
+
|
|
15
|
+
expect(parsedSchema).toStrictEqual({
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
normalProperty: { type: 'string' },
|
|
19
|
+
},
|
|
20
|
+
required: ['normalProperty'],
|
|
21
|
+
additionalProperties: {
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
},
|
|
24
|
+
} satisfies JSONSchema7);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should be possible to use selective partial', () => {
|
|
28
|
+
const schema = z
|
|
29
|
+
.object({ foo: z.boolean(), bar: z.number() })
|
|
30
|
+
.partial({ foo: true });
|
|
31
|
+
|
|
32
|
+
const parsedSchema = parseObjectDef(schema._def, getRefs());
|
|
33
|
+
|
|
34
|
+
expect(parsedSchema).toStrictEqual({
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
foo: { type: 'boolean' },
|
|
38
|
+
bar: { type: 'number' },
|
|
39
|
+
},
|
|
40
|
+
required: ['bar'],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
} satisfies JSONSchema7);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should allow additional properties unless strict when removeAdditionalStrategy is strict', () => {
|
|
46
|
+
const schema = z.object({ foo: z.boolean(), bar: z.number() });
|
|
47
|
+
|
|
48
|
+
const parsedSchema = parseObjectDef(
|
|
49
|
+
schema._def,
|
|
50
|
+
getRefs({ removeAdditionalStrategy: 'strict' }),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(parsedSchema).toStrictEqual({
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
foo: { type: 'boolean' },
|
|
57
|
+
bar: { type: 'number' },
|
|
58
|
+
},
|
|
59
|
+
required: ['foo', 'bar'],
|
|
60
|
+
additionalProperties: true,
|
|
61
|
+
} satisfies JSONSchema7);
|
|
62
|
+
|
|
63
|
+
const strictSchema = z
|
|
64
|
+
.object({ foo: z.boolean(), bar: z.number() })
|
|
65
|
+
.strict();
|
|
66
|
+
|
|
67
|
+
const parsedStrictSchema = parseObjectDef(
|
|
68
|
+
strictSchema._def,
|
|
69
|
+
getRefs({ removeAdditionalStrategy: 'strict' }),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(parsedStrictSchema).toStrictEqual({
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
foo: { type: 'boolean' },
|
|
76
|
+
bar: { type: 'number' },
|
|
77
|
+
},
|
|
78
|
+
required: ['foo', 'bar'],
|
|
79
|
+
additionalProperties: false,
|
|
80
|
+
} satisfies JSONSchema7);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should allow additional properties with catchall when removeAdditionalStrategy is strict', () => {
|
|
84
|
+
const schema = z
|
|
85
|
+
.object({ foo: z.boolean(), bar: z.number() })
|
|
86
|
+
.catchall(z.boolean());
|
|
87
|
+
|
|
88
|
+
const parsedSchema = parseObjectDef(
|
|
89
|
+
schema._def,
|
|
90
|
+
getRefs({ removeAdditionalStrategy: 'strict' }),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(parsedSchema).toStrictEqual({
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
foo: { type: 'boolean' },
|
|
97
|
+
bar: { type: 'number' },
|
|
98
|
+
},
|
|
99
|
+
required: ['foo', 'bar'],
|
|
100
|
+
additionalProperties: {
|
|
101
|
+
type: 'boolean',
|
|
102
|
+
},
|
|
103
|
+
} satisfies JSONSchema7);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should be possible to not set additionalProperties at all when allowed', () => {
|
|
107
|
+
const schema = z
|
|
108
|
+
.object({ foo: z.boolean(), bar: z.number() })
|
|
109
|
+
.passthrough();
|
|
110
|
+
|
|
111
|
+
const parsedSchema = parseObjectDef(
|
|
112
|
+
schema._def,
|
|
113
|
+
getRefs({
|
|
114
|
+
removeAdditionalStrategy: 'passthrough',
|
|
115
|
+
allowedAdditionalProperties: undefined,
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(parsedSchema).toStrictEqual({
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
foo: { type: 'boolean' },
|
|
123
|
+
bar: { type: 'number' },
|
|
124
|
+
},
|
|
125
|
+
required: ['foo', 'bar'],
|
|
126
|
+
} satisfies JSONSchema7);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should be possible to not set additionalProperties at all when rejected', () => {
|
|
130
|
+
const schema = z.object({ foo: z.boolean(), bar: z.number() }).strict();
|
|
131
|
+
|
|
132
|
+
const parsedSchema = parseObjectDef(
|
|
133
|
+
schema._def,
|
|
134
|
+
getRefs({
|
|
135
|
+
removeAdditionalStrategy: 'passthrough',
|
|
136
|
+
rejectedAdditionalProperties: undefined,
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(parsedSchema).toStrictEqual({
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: {
|
|
143
|
+
foo: { type: 'boolean' },
|
|
144
|
+
bar: { type: 'number' },
|
|
145
|
+
},
|
|
146
|
+
required: ['foo', 'bar'],
|
|
147
|
+
} satisfies JSONSchema7);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ZodObjectDef, ZodTypeAny } from 'zod/v3';
|
|
2
|
+
import { parseDef } from '../parse-def';
|
|
3
|
+
import { JsonSchema7Type } from '../parse-types';
|
|
4
|
+
import { Refs } from '../refs';
|
|
5
|
+
|
|
6
|
+
export type JsonSchema7ObjectType = {
|
|
7
|
+
type: 'object';
|
|
8
|
+
properties: Record<string, JsonSchema7Type>;
|
|
9
|
+
additionalProperties?: boolean | JsonSchema7Type;
|
|
10
|
+
required?: string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function parseObjectDef(def: ZodObjectDef, refs: Refs) {
|
|
14
|
+
const result: JsonSchema7ObjectType = {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const required: string[] = [];
|
|
20
|
+
|
|
21
|
+
const shape = def.shape();
|
|
22
|
+
|
|
23
|
+
for (const propName in shape) {
|
|
24
|
+
let propDef = shape[propName];
|
|
25
|
+
|
|
26
|
+
if (propDef === undefined || propDef._def === undefined) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const propOptional = safeIsOptional(propDef);
|
|
31
|
+
|
|
32
|
+
const parsedDef = parseDef(propDef._def, {
|
|
33
|
+
...refs,
|
|
34
|
+
currentPath: [...refs.currentPath, 'properties', propName],
|
|
35
|
+
propertyPath: [...refs.currentPath, 'properties', propName],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (parsedDef === undefined) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
result.properties[propName] = parsedDef;
|
|
43
|
+
|
|
44
|
+
if (!propOptional) {
|
|
45
|
+
required.push(propName);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (required.length) {
|
|
50
|
+
result.required = required;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const additionalProperties = decideAdditionalProperties(def, refs);
|
|
54
|
+
|
|
55
|
+
if (additionalProperties !== undefined) {
|
|
56
|
+
result.additionalProperties = additionalProperties;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function decideAdditionalProperties(def: ZodObjectDef, refs: Refs) {
|
|
63
|
+
if (def.catchall._def.typeName !== 'ZodNever') {
|
|
64
|
+
return parseDef(def.catchall._def, {
|
|
65
|
+
...refs,
|
|
66
|
+
currentPath: [...refs.currentPath, 'additionalProperties'],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
switch (def.unknownKeys) {
|
|
71
|
+
case 'passthrough':
|
|
72
|
+
return refs.allowedAdditionalProperties;
|
|
73
|
+
case 'strict':
|
|
74
|
+
return refs.rejectedAdditionalProperties;
|
|
75
|
+
case 'strip':
|
|
76
|
+
return refs.removeAdditionalStrategy === 'strict'
|
|
77
|
+
? refs.allowedAdditionalProperties
|
|
78
|
+
: refs.rejectedAdditionalProperties;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function safeIsOptional(schema: ZodTypeAny): boolean {
|
|
83
|
+
try {
|
|
84
|
+
return schema.isOptional();
|
|
85
|
+
} catch {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { z } from 'zod/v3';
|
|
3
|
+
import { parseDef } from '../parse-def';
|
|
4
|
+
import { getRefs } from '../refs';
|
|
5
|
+
import { JSONSchema7 } from '@ai-sdk/provider';
|
|
6
|
+
|
|
7
|
+
describe('Standalone optionals', () => {
|
|
8
|
+
it('should work as unions with undefined', () => {
|
|
9
|
+
const parsedSchema = parseDef(z.string().optional()._def, getRefs());
|
|
10
|
+
|
|
11
|
+
expect(parsedSchema).toStrictEqual({
|
|
12
|
+
anyOf: [
|
|
13
|
+
{
|
|
14
|
+
not: {},
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
type: 'string',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
} satisfies JSONSchema7);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should work as unions with void', () => {
|
|
24
|
+
const parsedSchema = parseDef(z.void().optional()._def, getRefs());
|
|
25
|
+
|
|
26
|
+
expect(parsedSchema).toStrictEqual({} satisfies JSONSchema7);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should not affect object properties', () => {
|
|
30
|
+
const parsedSchema = parseDef(
|
|
31
|
+
z.object({ myProperty: z.string().optional() })._def,
|
|
32
|
+
getRefs(),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(parsedSchema).toStrictEqual({
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
myProperty: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
additionalProperties: false,
|
|
43
|
+
} satisfies JSONSchema7);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should work with nested properties', () => {
|
|
47
|
+
const parsedSchema = parseDef(
|
|
48
|
+
z.object({ myProperty: z.string().optional().array() })._def,
|
|
49
|
+
getRefs(),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(parsedSchema).toStrictEqual({
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
myProperty: {
|
|
56
|
+
type: 'array',
|
|
57
|
+
items: {
|
|
58
|
+
anyOf: [{ not: {} }, { type: 'string' }],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ['myProperty'],
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
} satisfies JSONSchema7);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should work with nested properties as object properties', () => {
|
|
68
|
+
const parsedSchema = parseDef(
|
|
69
|
+
z.object({
|
|
70
|
+
myProperty: z.object({ myInnerProperty: z.string().optional() }),
|
|
71
|
+
})._def,
|
|
72
|
+
getRefs(),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(parsedSchema).toStrictEqual({
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
myProperty: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
myInnerProperty: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
required: ['myProperty'],
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
} satisfies JSONSchema7);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should work with nested properties with nested object property parents', () => {
|
|
94
|
+
const parsedSchema = parseDef(
|
|
95
|
+
z.object({
|
|
96
|
+
myProperty: z.object({
|
|
97
|
+
myInnerProperty: z.string().optional().array(),
|
|
98
|
+
}),
|
|
99
|
+
})._def,
|
|
100
|
+
getRefs(),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(parsedSchema).toStrictEqual({
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
myProperty: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
myInnerProperty: {
|
|
110
|
+
type: 'array',
|
|
111
|
+
items: {
|
|
112
|
+
anyOf: [
|
|
113
|
+
{ not: {} },
|
|
114
|
+
{
|
|
115
|
+
type: 'string',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
required: ['myInnerProperty'],
|
|
122
|
+
additionalProperties: false,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ['myProperty'],
|
|
126
|
+
additionalProperties: false,
|
|
127
|
+
} satisfies JSONSchema7);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should work with ref pathing', () => {
|
|
131
|
+
const recurring = z.string();
|
|
132
|
+
|
|
133
|
+
const schema = z.tuple([recurring.optional(), recurring]);
|
|
134
|
+
|
|
135
|
+
const parsedSchema = parseDef(schema._def, getRefs());
|
|
136
|
+
|
|
137
|
+
expect(parsedSchema).toStrictEqual({
|
|
138
|
+
type: 'array',
|
|
139
|
+
minItems: 2,
|
|
140
|
+
maxItems: 2,
|
|
141
|
+
items: [
|
|
142
|
+
{ anyOf: [{ not: {} }, { type: 'string' }] },
|
|
143
|
+
{ $ref: '#/items/0/anyOf/1' },
|
|
144
|
+
],
|
|
145
|
+
} satisfies JSONSchema7);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ZodOptionalDef } from 'zod/v3';
|
|
2
|
+
import { parseDef } from '../parse-def';
|
|
3
|
+
import { JsonSchema7Type } from '../parse-types';
|
|
4
|
+
import { Refs } from '../refs';
|
|
5
|
+
import { parseAnyDef } from './any';
|
|
6
|
+
|
|
7
|
+
export const parseOptionalDef = (
|
|
8
|
+
def: ZodOptionalDef,
|
|
9
|
+
refs: Refs,
|
|
10
|
+
): JsonSchema7Type | undefined => {
|
|
11
|
+
if (refs.currentPath.toString() === refs.propertyPath?.toString()) {
|
|
12
|
+
return parseDef(def.innerType._def, refs);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const innerSchema = parseDef(def.innerType._def, {
|
|
16
|
+
...refs,
|
|
17
|
+
currentPath: [...refs.currentPath, 'anyOf', '1'],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return innerSchema
|
|
21
|
+
? { anyOf: [{ not: parseAnyDef() }, innerSchema] }
|
|
22
|
+
: parseAnyDef();
|
|
23
|
+
};
|