@mastra/schema-compat 0.10.2-alpha.2 → 0.10.2

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.
@@ -1,8 +1,7 @@
1
1
  import type { LanguageModelV1 } from 'ai';
2
- import type { z } from 'zod';
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<T extends z.AnyZodObject>(value: z.ZodTypeAny): ShapeValue<T> {
22
- switch (value._def.typeName) {
23
- case 'ZodOptional':
24
- return this.defaultZodOptionalHandler(value, ['ZodObject', 'ZodArray', 'ZodUnion', 'ZodString', 'ZodNumber']);
25
- case 'ZodObject': {
26
- return this.defaultZodObjectHandler(value);
27
- }
28
- case 'ZodArray': {
29
- return this.defaultZodArrayHandler(value, ['min', 'max']);
30
- }
31
- case 'ZodUnion': {
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 { z } from 'zod';
2
+ import type { ZodTypeAny } from 'zod';
3
3
  import type { Targets } from 'zod-to-json-schema';
4
- import { SchemaCompatLayer, UNSUPPORTED_ZOD_TYPES } from '../schema-compatibility';
5
- import type { ShapeValue } from '../schema-compatibility';
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<T extends z.AnyZodObject>(value: z.ZodTypeAny): ShapeValue<T> {
21
- switch (value._def.typeName) {
22
- case 'ZodOptional':
23
- return this.defaultZodOptionalHandler(value, [
24
- 'ZodObject',
25
- 'ZodArray',
26
- 'ZodUnion',
27
- 'ZodString',
28
- 'ZodNumber',
29
- ...UNSUPPORTED_ZOD_TYPES,
30
- ]);
31
- case 'ZodObject': {
32
- return this.defaultZodObjectHandler(value);
33
- }
34
- case 'ZodArray': {
35
- return this.defaultZodArrayHandler(value, []);
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
- case 'ZodString': {
43
- return this.defaultZodStringHandler(value);
44
- }
45
- case 'ZodNumber': {
46
- // Google models support these properties but the model doesn't respect them, but it respects them when they're
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 { z } from 'zod';
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<T extends z.AnyZodObject>(value: z.ZodTypeAny): ShapeValue<T> {
21
- switch (value._def.typeName) {
22
- case 'ZodOptional':
23
- return this.defaultZodOptionalHandler(value, ['ZodObject', 'ZodArray', 'ZodUnion', 'ZodString', 'ZodNumber']);
24
- case 'ZodObject': {
25
- return this.defaultZodObjectHandler(value);
26
- }
27
- case 'ZodArray': {
28
- return this.defaultZodArrayHandler(value, ['min', 'max']);
29
- }
30
- case 'ZodUnion': {
31
- return this.defaultZodUnionHandler(value);
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 { SchemaCompatLayer } from '../schema-compatibility';
5
- import type { ShapeValue } from '../schema-compatibility';
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<T extends z.AnyZodObject>(value: z.ZodTypeAny): ShapeValue<T> {
34
- switch (value._def.typeName) {
35
- case 'ZodOptional':
36
- const innerZodType = this.processZodType(value._def.innerType);
37
- return innerZodType.nullable() as ShapeValue<T>;
38
- case 'ZodObject': {
39
- return this.defaultZodObjectHandler(value);
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
- const description = this.mergeParameterDescription(value.description, constraints);
57
- let result = this.processZodType<T>(innerType);
58
- if (description) {
59
- result = result.describe(description);
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
- case 'ZodDate': {
70
- return this.defaultZodDateHandler(value);
71
- }
72
- case 'ZodAny': {
73
- // 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
74
- // in the schema. Usually when it's "any" it could be a json object or a union of specific types.
75
- return z
76
- .string()
77
- .describe(
78
- (value.description ?? '') +
79
- `\nArgument was an "any" type, but you (the LLM) do not support "any", so it was cast to a "string" type`,
80
- );
81
- }
82
- default:
83
- return this.defaultUnsupportedZodTypeHandler(value);
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 { z } from 'zod';
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, StringCheckType } from '../schema-compatibility';
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<T extends z.AnyZodObject>(value: z.ZodTypeAny): ShapeValue<T> {
28
- switch (value._def.typeName) {
29
- case 'ZodOptional':
30
- return this.defaultZodOptionalHandler(value, [
31
- 'ZodObject',
32
- 'ZodArray',
33
- 'ZodUnion',
34
- 'ZodString',
35
- 'ZodNever',
36
- 'ZodUndefined',
37
- 'ZodTuple',
38
- ]);
39
- case 'ZodObject': {
40
- return this.defaultZodObjectHandler(value);
41
- }
42
- case 'ZodUnion': {
43
- return this.defaultZodUnionHandler(value);
44
- }
45
- case 'ZodArray': {
46
- return this.defaultZodArrayHandler(value);
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
- if (model.modelId.includes('gpt-4o-mini')) {
53
- checks.push('regex');
54
- }
55
- return this.defaultZodStringHandler(value, checks);
48
+ if (model.modelId.includes('gpt-4o-mini')) {
49
+ checks.push('regex');
56
50
  }
57
- default:
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): any {
21
- return value;
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(result).toBeInstanceOf(z.ZodObject);
90
- expect(result._def.typeName).toBe('ZodObject');
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 handle array without constraints', () => {
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).toBeInstanceOf(z.ZodArray);
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
  });