@mastra/schema-compat 0.10.2-alpha.2 → 0.10.2-alpha.3
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/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +6 -0
- package/dist/_tsup-dts-rollup.d.cts +55 -26
- package/dist/_tsup-dts-rollup.d.ts +55 -26
- package/dist/index.cjs +178 -240
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +174 -242
- package/package.json +3 -3
- package/src/index.ts +7 -0
- package/src/provider-compats/anthropic.ts +27 -32
- package/src/provider-compats/deepseek.ts +15 -21
- package/src/provider-compats/google.ts +34 -33
- package/src/provider-compats/meta.ts +17 -24
- package/src/provider-compats/openai-reasoning.ts +51 -50
- package/src/provider-compats/openai.ts +28 -33
- package/src/schema-compatibility.test.ts +161 -40
- package/src/schema-compatibility.ts +83 -91
- package/src/utils.test.ts +129 -23
- package/src/utils.ts +8 -21
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { LanguageModelV1 } from 'ai';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ZodTypeAny } from 'zod';
|
|
3
3
|
import type { Targets } from 'zod-to-json-schema';
|
|
4
|
-
import { SchemaCompatLayer } from '../schema-compatibility';
|
|
5
|
-
import type { ShapeValue } from '../schema-compatibility';
|
|
4
|
+
import { SchemaCompatLayer, isArr, isObj, isOptional, isString, isUnion } from '../schema-compatibility';
|
|
6
5
|
|
|
7
6
|
export class DeepSeekSchemaCompatLayer extends SchemaCompatLayer {
|
|
8
7
|
constructor(model: LanguageModelV1) {
|
|
@@ -18,24 +17,19 @@ export class DeepSeekSchemaCompatLayer extends SchemaCompatLayer {
|
|
|
18
17
|
return this.getModel().modelId.includes('deepseek') && !this.getModel().modelId.includes('r1');
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
processZodType
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return this.defaultZodUnionHandler(value);
|
|
33
|
-
}
|
|
34
|
-
case 'ZodString': {
|
|
35
|
-
return this.defaultZodStringHandler(value);
|
|
36
|
-
}
|
|
37
|
-
default:
|
|
38
|
-
return value as ShapeValue<T>;
|
|
20
|
+
processZodType(value: ZodTypeAny): ZodTypeAny {
|
|
21
|
+
if (isOptional(value)) {
|
|
22
|
+
return this.defaultZodOptionalHandler(value, ['ZodObject', 'ZodArray', 'ZodUnion', 'ZodString', 'ZodNumber']);
|
|
23
|
+
} else if (isObj(value)) {
|
|
24
|
+
return this.defaultZodObjectHandler(value);
|
|
25
|
+
} else if (isArr(value)) {
|
|
26
|
+
return this.defaultZodArrayHandler(value, ['min', 'max']);
|
|
27
|
+
} else if (isUnion(value)) {
|
|
28
|
+
return this.defaultZodUnionHandler(value);
|
|
29
|
+
} else if (isString(value)) {
|
|
30
|
+
return this.defaultZodStringHandler(value);
|
|
39
31
|
}
|
|
32
|
+
|
|
33
|
+
return value;
|
|
40
34
|
}
|
|
41
35
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { LanguageModelV1 } from 'ai';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ZodTypeAny } from 'zod';
|
|
3
3
|
import type { Targets } from 'zod-to-json-schema';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
SchemaCompatLayer,
|
|
6
|
+
UNSUPPORTED_ZOD_TYPES,
|
|
7
|
+
isArr,
|
|
8
|
+
isNumber,
|
|
9
|
+
isObj,
|
|
10
|
+
isOptional,
|
|
11
|
+
isString,
|
|
12
|
+
isUnion,
|
|
13
|
+
} from '../schema-compatibility';
|
|
6
14
|
|
|
7
15
|
export class GoogleSchemaCompatLayer extends SchemaCompatLayer {
|
|
8
16
|
constructor(model: LanguageModelV1) {
|
|
@@ -17,38 +25,31 @@ export class GoogleSchemaCompatLayer extends SchemaCompatLayer {
|
|
|
17
25
|
return this.getModel().provider.includes('google') || this.getModel().modelId.includes('google');
|
|
18
26
|
}
|
|
19
27
|
|
|
20
|
-
processZodType
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
case 'ZodUnion': {
|
|
38
|
-
return this.defaultZodUnionHandler(value);
|
|
39
|
-
}
|
|
28
|
+
processZodType(value: ZodTypeAny): ZodTypeAny {
|
|
29
|
+
if (isOptional(value)) {
|
|
30
|
+
return this.defaultZodOptionalHandler(value, [
|
|
31
|
+
'ZodObject',
|
|
32
|
+
'ZodArray',
|
|
33
|
+
'ZodUnion',
|
|
34
|
+
'ZodString',
|
|
35
|
+
'ZodNumber',
|
|
36
|
+
...UNSUPPORTED_ZOD_TYPES,
|
|
37
|
+
]);
|
|
38
|
+
} else if (isObj(value)) {
|
|
39
|
+
return this.defaultZodObjectHandler(value);
|
|
40
|
+
} else if (isArr(value)) {
|
|
41
|
+
return this.defaultZodArrayHandler(value, []);
|
|
42
|
+
} else if (isUnion(value)) {
|
|
43
|
+
return this.defaultZodUnionHandler(value);
|
|
44
|
+
} else if (isString(value)) {
|
|
40
45
|
// Google models support these properties but the model doesn't respect them, but it respects them when they're
|
|
41
46
|
// added to the tool description
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// added to the tool description
|
|
48
|
-
return this.defaultZodNumberHandler(value);
|
|
49
|
-
}
|
|
50
|
-
default:
|
|
51
|
-
return this.defaultUnsupportedZodTypeHandler(value);
|
|
47
|
+
return this.defaultZodStringHandler(value);
|
|
48
|
+
} else if (isNumber(value)) {
|
|
49
|
+
// Google models support these properties but the model doesn't respect them, but it respects them when they're
|
|
50
|
+
// added to the tool description
|
|
51
|
+
return this.defaultZodNumberHandler(value);
|
|
52
52
|
}
|
|
53
|
+
return this.defaultUnsupportedZodTypeHandler(value);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { LanguageModelV1 } from 'ai';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ZodTypeAny } from 'zod';
|
|
3
3
|
import type { Targets } from 'zod-to-json-schema';
|
|
4
|
-
import { SchemaCompatLayer } from '../schema-compatibility';
|
|
5
|
-
import type { ShapeValue } from '../schema-compatibility';
|
|
4
|
+
import { SchemaCompatLayer, isArr, isNumber, isObj, isOptional, isString, isUnion } from '../schema-compatibility';
|
|
6
5
|
|
|
7
6
|
export class MetaSchemaCompatLayer extends SchemaCompatLayer {
|
|
8
7
|
constructor(model: LanguageModelV1) {
|
|
@@ -17,27 +16,21 @@ export class MetaSchemaCompatLayer extends SchemaCompatLayer {
|
|
|
17
16
|
return this.getModel().modelId.includes('meta');
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
processZodType
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
case 'ZodNumber': {
|
|
34
|
-
return this.defaultZodNumberHandler(value);
|
|
35
|
-
}
|
|
36
|
-
case 'ZodString': {
|
|
37
|
-
return this.defaultZodStringHandler(value);
|
|
38
|
-
}
|
|
39
|
-
default:
|
|
40
|
-
return value as ShapeValue<T>;
|
|
19
|
+
processZodType(value: ZodTypeAny): ZodTypeAny {
|
|
20
|
+
if (isOptional(value)) {
|
|
21
|
+
return this.defaultZodOptionalHandler(value, ['ZodObject', 'ZodArray', 'ZodUnion', 'ZodString', 'ZodNumber']);
|
|
22
|
+
} else if (isObj(value)) {
|
|
23
|
+
return this.defaultZodObjectHandler(value);
|
|
24
|
+
} else if (isArr(value)) {
|
|
25
|
+
return this.defaultZodArrayHandler(value, ['min', 'max']);
|
|
26
|
+
} else if (isUnion(value)) {
|
|
27
|
+
return this.defaultZodUnionHandler(value);
|
|
28
|
+
} else if (isNumber(value)) {
|
|
29
|
+
return this.defaultZodNumberHandler(value);
|
|
30
|
+
} else if (isString(value)) {
|
|
31
|
+
return this.defaultZodStringHandler(value);
|
|
41
32
|
}
|
|
33
|
+
|
|
34
|
+
return value;
|
|
42
35
|
}
|
|
43
36
|
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import type { LanguageModelV1 } from 'ai';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import type { ZodTypeAny } from 'zod';
|
|
3
4
|
import type { Targets } from 'zod-to-json-schema';
|
|
4
|
-
import {
|
|
5
|
-
|
|
5
|
+
import {
|
|
6
|
+
SchemaCompatLayer,
|
|
7
|
+
isArr,
|
|
8
|
+
isDate,
|
|
9
|
+
isDefault,
|
|
10
|
+
isNumber,
|
|
11
|
+
isObj,
|
|
12
|
+
isOptional,
|
|
13
|
+
isString,
|
|
14
|
+
isUnion,
|
|
15
|
+
} from '../schema-compatibility';
|
|
6
16
|
|
|
7
17
|
export class OpenAIReasoningSchemaCompatLayer extends SchemaCompatLayer {
|
|
8
18
|
constructor(model: LanguageModelV1) {
|
|
@@ -30,57 +40,48 @@ export class OpenAIReasoningSchemaCompatLayer extends SchemaCompatLayer {
|
|
|
30
40
|
return false;
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
processZodType
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
processZodType(value: ZodTypeAny): ZodTypeAny {
|
|
44
|
+
if (isOptional(value)) {
|
|
45
|
+
const innerZodType = this.processZodType(value._def.innerType);
|
|
46
|
+
return innerZodType.nullable();
|
|
47
|
+
} else if (isObj(value)) {
|
|
48
|
+
return this.defaultZodObjectHandler(value);
|
|
49
|
+
} else if (isArr(value)) {
|
|
50
|
+
return this.defaultZodArrayHandler(value);
|
|
51
|
+
} else if (isUnion(value)) {
|
|
52
|
+
return this.defaultZodUnionHandler(value);
|
|
53
|
+
} else if (isDefault(value)) {
|
|
54
|
+
const defaultDef = value._def;
|
|
55
|
+
const innerType = defaultDef.innerType;
|
|
56
|
+
const defaultValue = defaultDef.defaultValue();
|
|
57
|
+
const constraints: { defaultValue?: unknown } = {};
|
|
58
|
+
if (defaultValue !== undefined) {
|
|
59
|
+
constraints.defaultValue = defaultValue;
|
|
40
60
|
}
|
|
41
|
-
case 'ZodArray': {
|
|
42
|
-
return this.defaultZodArrayHandler(value);
|
|
43
|
-
}
|
|
44
|
-
case 'ZodUnion': {
|
|
45
|
-
return this.defaultZodUnionHandler(value);
|
|
46
|
-
}
|
|
47
|
-
case 'ZodDefault': {
|
|
48
|
-
const defaultDef = (value as z.ZodDefault<any>)._def;
|
|
49
|
-
const innerType = defaultDef.innerType;
|
|
50
|
-
const defaultValue = defaultDef.defaultValue();
|
|
51
|
-
const constraints: { defaultValue?: unknown } = {};
|
|
52
|
-
if (defaultValue !== undefined) {
|
|
53
|
-
constraints.defaultValue = defaultValue;
|
|
54
|
-
}
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
case 'ZodNumber': {
|
|
64
|
-
return this.defaultZodNumberHandler(value);
|
|
65
|
-
}
|
|
66
|
-
case 'ZodString': {
|
|
67
|
-
return this.defaultZodStringHandler(value);
|
|
62
|
+
const description = this.mergeParameterDescription(value.description, constraints);
|
|
63
|
+
let result = this.processZodType(innerType);
|
|
64
|
+
if (description) {
|
|
65
|
+
result = result.describe(description);
|
|
68
66
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
return result;
|
|
68
|
+
} else if (isNumber(value)) {
|
|
69
|
+
return this.defaultZodNumberHandler(value);
|
|
70
|
+
} else if (isString(value)) {
|
|
71
|
+
return this.defaultZodStringHandler(value);
|
|
72
|
+
} else if (isDate(value)) {
|
|
73
|
+
return this.defaultZodDateHandler(value);
|
|
74
|
+
} else if (value._def.typeName === 'ZodAny') {
|
|
75
|
+
// It's bad practice in the tool to use any, it's not reasonable for models that don't support that OOTB, to cast every single possible type
|
|
76
|
+
// in the schema. Usually when it's "any" it could be a json object or a union of specific types.
|
|
77
|
+
return z
|
|
78
|
+
.string()
|
|
79
|
+
.describe(
|
|
80
|
+
(value.description ?? '') +
|
|
81
|
+
`\nArgument was an "any" type, but you (the LLM) do not support "any", so it was cast to a "string" type`,
|
|
82
|
+
);
|
|
84
83
|
}
|
|
84
|
+
|
|
85
|
+
return this.defaultUnsupportedZodTypeHandler(value);
|
|
85
86
|
}
|
|
86
87
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { LanguageModelV1 } from 'ai';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ZodTypeAny } from 'zod';
|
|
3
3
|
import type { Targets } from 'zod-to-json-schema';
|
|
4
|
-
import { SchemaCompatLayer } from '../schema-compatibility';
|
|
5
|
-
import type {
|
|
4
|
+
import { SchemaCompatLayer, isArr, isObj, isOptional, isString, isUnion } from '../schema-compatibility';
|
|
5
|
+
import type { StringCheckType } from '../schema-compatibility';
|
|
6
6
|
|
|
7
7
|
export class OpenAISchemaCompatLayer extends SchemaCompatLayer {
|
|
8
8
|
constructor(model: LanguageModelV1) {
|
|
@@ -24,38 +24,33 @@ export class OpenAISchemaCompatLayer extends SchemaCompatLayer {
|
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
processZodType
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
case 'ZodString': {
|
|
49
|
-
const model = this.getModel();
|
|
50
|
-
const checks: StringCheckType[] = ['emoji'];
|
|
27
|
+
processZodType(value: ZodTypeAny): ZodTypeAny {
|
|
28
|
+
if (isOptional(value)) {
|
|
29
|
+
return this.defaultZodOptionalHandler(value, [
|
|
30
|
+
'ZodObject',
|
|
31
|
+
'ZodArray',
|
|
32
|
+
'ZodUnion',
|
|
33
|
+
'ZodString',
|
|
34
|
+
'ZodNever',
|
|
35
|
+
'ZodUndefined',
|
|
36
|
+
'ZodTuple',
|
|
37
|
+
]);
|
|
38
|
+
} else if (isObj(value)) {
|
|
39
|
+
return this.defaultZodObjectHandler(value);
|
|
40
|
+
} else if (isUnion(value)) {
|
|
41
|
+
return this.defaultZodUnionHandler(value);
|
|
42
|
+
} else if (isArr(value)) {
|
|
43
|
+
return this.defaultZodArrayHandler(value);
|
|
44
|
+
} else if (isString(value)) {
|
|
45
|
+
const model = this.getModel();
|
|
46
|
+
const checks: StringCheckType[] = ['emoji'];
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
return this.defaultZodStringHandler(value, checks);
|
|
48
|
+
if (model.modelId.includes('gpt-4o-mini')) {
|
|
49
|
+
checks.push('regex');
|
|
56
50
|
}
|
|
57
|
-
|
|
58
|
-
return this.defaultUnsupportedZodTypeHandler(value, ['ZodNever', 'ZodUndefined', 'ZodTuple']);
|
|
51
|
+
return this.defaultZodStringHandler(value, checks);
|
|
59
52
|
}
|
|
53
|
+
|
|
54
|
+
return this.defaultUnsupportedZodTypeHandler(value, ['ZodNever', 'ZodUndefined', 'ZodTuple']);
|
|
60
55
|
}
|
|
61
56
|
}
|
|
@@ -2,7 +2,7 @@ import type { LanguageModelV1 } from 'ai';
|
|
|
2
2
|
import { MockLanguageModelV1 } from 'ai/test';
|
|
3
3
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { SchemaCompatLayer } from './schema-compatibility';
|
|
5
|
+
import { isArr, isObj, isOptional, isString, isUnion, SchemaCompatLayer } from './schema-compatibility';
|
|
6
6
|
|
|
7
7
|
class MockSchemaCompatibility extends SchemaCompatLayer {
|
|
8
8
|
constructor(model: LanguageModelV1) {
|
|
@@ -17,8 +17,22 @@ class MockSchemaCompatibility extends SchemaCompatLayer {
|
|
|
17
17
|
return 'jsonSchema7' as const;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
processZodType(value: z.ZodTypeAny):
|
|
21
|
-
|
|
20
|
+
processZodType(value: z.ZodTypeAny): z.ZodTypeAny {
|
|
21
|
+
if (isObj(value)) {
|
|
22
|
+
return this.defaultZodObjectHandler(value);
|
|
23
|
+
} else if (isArr(value)) {
|
|
24
|
+
// For these tests, we will handle all checks by converting them to descriptions.
|
|
25
|
+
return this.defaultZodArrayHandler(value, ['min', 'max', 'length']);
|
|
26
|
+
} else if (isOptional(value)) {
|
|
27
|
+
return this.defaultZodOptionalHandler(value);
|
|
28
|
+
} else if (isUnion(value)) {
|
|
29
|
+
return this.defaultZodUnionHandler(value);
|
|
30
|
+
} else if (isString(value)) {
|
|
31
|
+
// Add a marker to confirm it was processed
|
|
32
|
+
return z.string().describe(`${value.description || 'string'}:processed`);
|
|
33
|
+
} else {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
22
36
|
}
|
|
23
37
|
}
|
|
24
38
|
|
|
@@ -78,16 +92,18 @@ describe('SchemaCompatLayer', () => {
|
|
|
78
92
|
});
|
|
79
93
|
|
|
80
94
|
describe('defaultZodObjectHandler', () => {
|
|
81
|
-
it('should process object shape correctly', () => {
|
|
95
|
+
it('should process object shape correctly and recursively', () => {
|
|
82
96
|
const testSchema = z.object({
|
|
83
|
-
name: z.string(),
|
|
84
|
-
age: z.number(),
|
|
97
|
+
name: z.string().describe('The name'),
|
|
98
|
+
age: z.number(), // not a string, so won't get a description
|
|
85
99
|
});
|
|
86
100
|
|
|
87
|
-
const result = compatibility.defaultZodObjectHandler(testSchema)
|
|
101
|
+
const result = compatibility.defaultZodObjectHandler(testSchema) as z.ZodObject<any, any, any>;
|
|
102
|
+
const newShape = result.shape;
|
|
88
103
|
|
|
89
|
-
expect(
|
|
90
|
-
expect(
|
|
104
|
+
expect(newShape.name).toBeInstanceOf(z.ZodString);
|
|
105
|
+
expect(newShape.name.description).toBe('The name:processed');
|
|
106
|
+
expect(newShape.age.description).toBeUndefined();
|
|
91
107
|
});
|
|
92
108
|
|
|
93
109
|
it('should preserve description', () => {
|
|
@@ -101,6 +117,16 @@ describe('SchemaCompatLayer', () => {
|
|
|
101
117
|
|
|
102
118
|
expect(result.description).toBe('Test object');
|
|
103
119
|
});
|
|
120
|
+
|
|
121
|
+
it('should preserve strictness', () => {
|
|
122
|
+
const strictSchema = z.object({ name: z.string() }).strict();
|
|
123
|
+
const result = compatibility.defaultZodObjectHandler(strictSchema);
|
|
124
|
+
expect(result._def.unknownKeys).toBe('strict');
|
|
125
|
+
|
|
126
|
+
const nonStrictSchema = z.object({ name: z.string() });
|
|
127
|
+
const nonStrictResult = compatibility.defaultZodObjectHandler(nonStrictSchema);
|
|
128
|
+
expect(nonStrictResult._def.unknownKeys).toBe('strip'); // default
|
|
129
|
+
});
|
|
104
130
|
});
|
|
105
131
|
|
|
106
132
|
describe('defaultUnsupportedZodTypeHandler', () => {
|
|
@@ -130,59 +156,38 @@ describe('SchemaCompatLayer', () => {
|
|
|
130
156
|
});
|
|
131
157
|
|
|
132
158
|
describe('defaultZodArrayHandler', () => {
|
|
133
|
-
it('should handle array with constraints', () => {
|
|
159
|
+
it('should handle array with constraints and convert to description', () => {
|
|
134
160
|
const arraySchema = z.array(z.string()).min(2).max(10);
|
|
135
|
-
|
|
136
161
|
const result = compatibility.defaultZodArrayHandler(arraySchema);
|
|
137
|
-
|
|
138
|
-
expect(result).toBeInstanceOf(z.ZodArray);
|
|
139
162
|
expect(result.description).toContain('minLength');
|
|
140
163
|
expect(result.description).toContain('maxLength');
|
|
141
164
|
});
|
|
142
165
|
|
|
143
|
-
it('should
|
|
144
|
-
const arraySchema = z.array(z.string());
|
|
145
|
-
|
|
146
|
-
const result = compatibility.defaultZodArrayHandler(arraySchema);
|
|
166
|
+
it('should preserve constraints not in handleChecks', () => {
|
|
167
|
+
const arraySchema = z.array(z.string()).min(2).max(10);
|
|
168
|
+
// Only handle 'min', so 'max' should be preserved as a validator
|
|
169
|
+
const result = compatibility.defaultZodArrayHandler(arraySchema, ['min']);
|
|
147
170
|
|
|
148
|
-
expect(result).
|
|
171
|
+
expect(result.description).toContain('minLength');
|
|
172
|
+
expect(result.description).not.toContain('maxLength');
|
|
173
|
+
expect(result._def.maxLength?.value).toBe(10); // Preserved
|
|
149
174
|
});
|
|
150
175
|
|
|
151
176
|
it('should handle exact length constraint', () => {
|
|
152
177
|
const arraySchema = z.array(z.string()).length(5);
|
|
153
|
-
|
|
154
178
|
const result = compatibility.defaultZodArrayHandler(arraySchema);
|
|
155
|
-
|
|
156
|
-
expect(result).toBeInstanceOf(z.ZodArray);
|
|
157
179
|
expect(result.description).toContain('exactLength');
|
|
158
180
|
});
|
|
159
181
|
|
|
160
182
|
it('should preserve original description', () => {
|
|
161
|
-
const arraySchema = z.array(z.string()).describe('String array');
|
|
162
|
-
|
|
183
|
+
const arraySchema = z.array(z.string()).describe('String array').min(1);
|
|
163
184
|
const result = compatibility.defaultZodArrayHandler(arraySchema);
|
|
164
|
-
|
|
165
185
|
expect(result.description).toContain('String array');
|
|
186
|
+
expect(result.description).toContain('minLength');
|
|
166
187
|
});
|
|
167
188
|
});
|
|
168
189
|
|
|
169
190
|
describe('defaultZodUnionHandler', () => {
|
|
170
|
-
it('should handle union types', () => {
|
|
171
|
-
const unionSchema = z.union([z.string(), z.number()]);
|
|
172
|
-
|
|
173
|
-
const result = compatibility.defaultZodUnionHandler(unionSchema);
|
|
174
|
-
|
|
175
|
-
expect(result).toBeInstanceOf(z.ZodUnion);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should preserve description', () => {
|
|
179
|
-
const unionSchema = z.union([z.string(), z.number()]).describe('String or number');
|
|
180
|
-
|
|
181
|
-
const result = compatibility.defaultZodUnionHandler(unionSchema);
|
|
182
|
-
|
|
183
|
-
expect(result.description).toBe('String or number');
|
|
184
|
-
});
|
|
185
|
-
|
|
186
191
|
it('should throw error for union with less than 2 options', () => {
|
|
187
192
|
const mockUnion = {
|
|
188
193
|
_def: {
|
|
@@ -195,6 +200,19 @@ describe('SchemaCompatLayer', () => {
|
|
|
195
200
|
compatibility.defaultZodUnionHandler(mockUnion);
|
|
196
201
|
}).toThrow('Union must have at least 2 options');
|
|
197
202
|
});
|
|
203
|
+
|
|
204
|
+
it('should handle union types and process recursively', () => {
|
|
205
|
+
const unionSchema = z.union([z.string().describe('A string'), z.number()]);
|
|
206
|
+
const result = compatibility.defaultZodUnionHandler(unionSchema) as z.ZodUnion<any>;
|
|
207
|
+
const processedString = result.options[0];
|
|
208
|
+
expect(processedString.description).toBe('A string:processed');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should preserve description', () => {
|
|
212
|
+
const unionSchema = z.union([z.string(), z.number()]).describe('String or number');
|
|
213
|
+
const result = compatibility.defaultZodUnionHandler(unionSchema);
|
|
214
|
+
expect(result.description).toBe('String or number');
|
|
215
|
+
});
|
|
198
216
|
});
|
|
199
217
|
|
|
200
218
|
describe('defaultZodStringHandler', () => {
|
|
@@ -347,4 +365,107 @@ describe('SchemaCompatLayer', () => {
|
|
|
347
365
|
expect(result).toBe(optionalNever);
|
|
348
366
|
});
|
|
349
367
|
});
|
|
368
|
+
|
|
369
|
+
describe('Top-level schema processing (processToAISDKSchema)', () => {
|
|
370
|
+
it('should process a simple object schema', () => {
|
|
371
|
+
const objectSchema = z.object({ user: z.string().describe('user name') });
|
|
372
|
+
const result = compatibility.processToAISDKSchema(objectSchema);
|
|
373
|
+
const userProp = result.jsonSchema.properties?.user as any;
|
|
374
|
+
expect(userProp.description).toBe('user name:processed');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should preserve top-level array constraints during processing', () => {
|
|
378
|
+
const arraySchema = z.array(z.string().describe('item')).min(1);
|
|
379
|
+
|
|
380
|
+
// In our mock, 'min' is converted to a description.
|
|
381
|
+
const result = compatibility.processToAISDKSchema(arraySchema);
|
|
382
|
+
|
|
383
|
+
expect(result.jsonSchema.type).toBe('array');
|
|
384
|
+
expect(result.jsonSchema.description).toContain('minLength');
|
|
385
|
+
// The validator itself should be gone
|
|
386
|
+
expect(
|
|
387
|
+
result.validate?.([
|
|
388
|
+
/* empty array */
|
|
389
|
+
]).success,
|
|
390
|
+
).toBe(true);
|
|
391
|
+
|
|
392
|
+
// Now test that a constraint is preserved if not handled
|
|
393
|
+
class PreservingMock extends MockSchemaCompatibility {
|
|
394
|
+
processZodType(value: z.ZodTypeAny): z.ZodTypeAny {
|
|
395
|
+
if (value instanceof z.ZodArray) {
|
|
396
|
+
return this.defaultZodArrayHandler(value as any, [
|
|
397
|
+
/* handle nothing */
|
|
398
|
+
]);
|
|
399
|
+
}
|
|
400
|
+
return super.processZodType(value);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const preservingCompat = new PreservingMock(mockModel);
|
|
404
|
+
const preservingResult = preservingCompat.processToAISDKSchema(arraySchema);
|
|
405
|
+
expect(preservingResult.jsonSchema.description).toBeUndefined();
|
|
406
|
+
expect(
|
|
407
|
+
preservingResult.validate?.([
|
|
408
|
+
/* empty array */
|
|
409
|
+
]).success,
|
|
410
|
+
).toBe(false); // validator preserved
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should preserve top-level object constraints (strict)', () => {
|
|
414
|
+
const strictSchema = z.object({ name: z.string() }).strict();
|
|
415
|
+
const result = compatibility.processToAISDKSchema(strictSchema);
|
|
416
|
+
expect(result.jsonSchema.additionalProperties).toBe(false);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should process array of objects, including nested properties', () => {
|
|
420
|
+
const arraySchema = z.array(
|
|
421
|
+
z.object({
|
|
422
|
+
name: z.string().describe('The name'),
|
|
423
|
+
value: z.number().describe('The value'), // number is not processed in our mock
|
|
424
|
+
}),
|
|
425
|
+
);
|
|
426
|
+
const result = compatibility.processToAISDKSchema(arraySchema);
|
|
427
|
+
const items = result.jsonSchema.items as any;
|
|
428
|
+
expect(items.properties.name.description).toBe('The name:processed');
|
|
429
|
+
expect(items.properties.value.description).toBe('The value');
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should handle optional object schemas', () => {
|
|
433
|
+
const optionalSchema = z
|
|
434
|
+
.object({
|
|
435
|
+
name: z.string(),
|
|
436
|
+
})
|
|
437
|
+
.optional();
|
|
438
|
+
|
|
439
|
+
const result = compatibility.processToAISDKSchema(optionalSchema);
|
|
440
|
+
expect(result.validate!({ name: 'test' }).success).toBe(true);
|
|
441
|
+
expect(result.validate!(undefined).success).toBe(true);
|
|
442
|
+
|
|
443
|
+
const jsonSchema = result.jsonSchema;
|
|
444
|
+
const objectDef = (jsonSchema.anyOf as any[])?.find(def => def.type === 'object');
|
|
445
|
+
expect(objectDef.properties.name.description).toBe('string:processed');
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('should handle optional array schemas', () => {
|
|
449
|
+
const optionalSchema = z.array(z.string()).optional();
|
|
450
|
+
const result = compatibility.processToAISDKSchema(optionalSchema);
|
|
451
|
+
expect(result.validate!(['test']).success).toBe(true);
|
|
452
|
+
expect(result.validate!(undefined).success).toBe(true);
|
|
453
|
+
|
|
454
|
+
const jsonSchema = result.jsonSchema;
|
|
455
|
+
const arrayDef = (jsonSchema.anyOf as any[])?.find(def => def.type === 'array');
|
|
456
|
+
const items = arrayDef.items as any;
|
|
457
|
+
expect(items.description).toBe('string:processed');
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should handle optional scalar schemas', () => {
|
|
461
|
+
const optionalSchema = z.string().optional();
|
|
462
|
+
const result = compatibility.processToAISDKSchema(optionalSchema);
|
|
463
|
+
expect(result.validate!('test').success).toBe(true);
|
|
464
|
+
expect(result.validate!(undefined).success).toBe(true);
|
|
465
|
+
|
|
466
|
+
const jsonSchema = result.jsonSchema;
|
|
467
|
+
const stringDef = (jsonSchema.anyOf as any[])?.find(def => def.type === 'string');
|
|
468
|
+
expect(stringDef.description).toBe('string:processed');
|
|
469
|
+
});
|
|
470
|
+
});
|
|
350
471
|
});
|