librechat-data-provider 0.7.4 → 0.7.7
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/check_updates.sh +1 -0
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react-query/index.es.js +1 -1
- package/dist/react-query/index.es.js.map +1 -1
- package/dist/react-query/package.json +1 -1
- package/package.json +6 -6
- package/react-query/package.json +1 -1
- package/server-rollup.config.js +3 -3
- package/specs/actions.spec.ts +700 -36
- package/specs/azure.spec.ts +8 -5
- package/specs/filetypes.spec.ts +1 -7
- package/specs/mcp.spec.ts +52 -0
- package/specs/openapiSpecs.ts +127 -0
- package/specs/utils.spec.ts +129 -0
- package/src/actions.ts +311 -101
- package/src/api-endpoints.ts +70 -13
- package/src/artifacts.ts +3104 -0
- package/src/azure.ts +40 -33
- package/src/bedrock.ts +227 -0
- package/src/config.ts +344 -78
- package/src/createPayload.ts +3 -1
- package/src/data-service.ts +353 -90
- package/src/file-config.ts +13 -2
- package/src/generate.ts +31 -2
- package/src/index.ts +12 -4
- package/src/keys.ts +17 -0
- package/src/mcp.ts +87 -0
- package/src/models.ts +1 -1
- package/src/parsers.ts +118 -60
- package/src/react-query/react-query-service.ts +54 -115
- package/src/request.ts +31 -7
- package/src/roles.ts +91 -2
- package/src/schemas.ts +513 -340
- package/src/types/agents.ts +276 -0
- package/src/types/assistants.ts +181 -27
- package/src/types/files.ts +6 -0
- package/src/types/mutations.ts +170 -7
- package/src/types/queries.ts +43 -21
- package/src/types/runs.ts +23 -0
- package/src/types.ts +132 -67
- package/src/utils.ts +44 -0
- package/src/zod.spec.ts +526 -0
- package/src/zod.ts +86 -0
- package/tsconfig.json +1 -2
- package/specs/parsers.spec.ts +0 -48
- package/src/sse.js +0 -242
package/src/utils.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const envVarRegex = /^\${(.+)}$/;
|
|
2
|
+
|
|
3
|
+
/** Extracts the value of an environment variable from a string. */
|
|
4
|
+
export function extractEnvVariable(value: string) {
|
|
5
|
+
if (!value) {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Trim the input
|
|
10
|
+
const trimmed = value.trim();
|
|
11
|
+
|
|
12
|
+
// Special case: if it's just a single environment variable
|
|
13
|
+
const singleMatch = trimmed.match(envVarRegex);
|
|
14
|
+
if (singleMatch) {
|
|
15
|
+
const varName = singleMatch[1];
|
|
16
|
+
return process.env[varName] || trimmed;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// For multiple variables, process them using a regex loop
|
|
20
|
+
const regex = /\${([^}]+)}/g;
|
|
21
|
+
let result = trimmed;
|
|
22
|
+
|
|
23
|
+
// First collect all matches and their positions
|
|
24
|
+
const matches = [];
|
|
25
|
+
let match;
|
|
26
|
+
while ((match = regex.exec(trimmed)) !== null) {
|
|
27
|
+
matches.push({
|
|
28
|
+
fullMatch: match[0],
|
|
29
|
+
varName: match[1],
|
|
30
|
+
index: match.index,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Process matches in reverse order to avoid position shifts
|
|
35
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
36
|
+
const { fullMatch, varName, index } = matches[i];
|
|
37
|
+
const envValue = process.env[varName] || fullMatch;
|
|
38
|
+
|
|
39
|
+
// Replace at exact position
|
|
40
|
+
result = result.substring(0, index) + envValue + result.substring(index + fullMatch.length);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
}
|
package/src/zod.spec.ts
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/* eslint-disable jest/no-conditional-expect */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
// zod.spec.ts
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { convertJsonSchemaToZod } from './zod';
|
|
6
|
+
import type { JsonSchemaType } from './zod';
|
|
7
|
+
|
|
8
|
+
describe('convertJsonSchemaToZod', () => {
|
|
9
|
+
describe('primitive types', () => {
|
|
10
|
+
it('should convert string schema', () => {
|
|
11
|
+
const schema: JsonSchemaType = {
|
|
12
|
+
type: 'string',
|
|
13
|
+
};
|
|
14
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
15
|
+
|
|
16
|
+
expect(zodSchema?.parse('test')).toBe('test');
|
|
17
|
+
expect(() => zodSchema?.parse(123)).toThrow();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should convert string enum schema', () => {
|
|
21
|
+
const schema: JsonSchemaType = {
|
|
22
|
+
type: 'string',
|
|
23
|
+
enum: ['foo', 'bar', 'baz'],
|
|
24
|
+
};
|
|
25
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
26
|
+
|
|
27
|
+
expect(zodSchema?.parse('foo')).toBe('foo');
|
|
28
|
+
expect(() => zodSchema?.parse('invalid')).toThrow();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should convert number schema', () => {
|
|
32
|
+
const schema: JsonSchemaType = {
|
|
33
|
+
type: 'number',
|
|
34
|
+
};
|
|
35
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
36
|
+
|
|
37
|
+
expect(zodSchema?.parse(123)).toBe(123);
|
|
38
|
+
expect(() => zodSchema?.parse('123')).toThrow();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should convert boolean schema', () => {
|
|
42
|
+
const schema: JsonSchemaType = {
|
|
43
|
+
type: 'boolean',
|
|
44
|
+
};
|
|
45
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
46
|
+
|
|
47
|
+
expect(zodSchema?.parse(true)).toBe(true);
|
|
48
|
+
expect(() => zodSchema?.parse('true')).toThrow();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('array types', () => {
|
|
53
|
+
it('should convert array of strings schema', () => {
|
|
54
|
+
const schema: JsonSchemaType = {
|
|
55
|
+
type: 'array',
|
|
56
|
+
items: { type: 'string' },
|
|
57
|
+
};
|
|
58
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
59
|
+
|
|
60
|
+
expect(zodSchema?.parse(['a', 'b', 'c'])).toEqual(['a', 'b', 'c']);
|
|
61
|
+
expect(() => zodSchema?.parse(['a', 123, 'c'])).toThrow();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should convert array of numbers schema', () => {
|
|
65
|
+
const schema: JsonSchemaType = {
|
|
66
|
+
type: 'array',
|
|
67
|
+
items: { type: 'number' },
|
|
68
|
+
};
|
|
69
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
70
|
+
|
|
71
|
+
expect(zodSchema?.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
72
|
+
expect(() => zodSchema?.parse([1, '2', 3])).toThrow();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('object types', () => {
|
|
77
|
+
it('should convert simple object schema', () => {
|
|
78
|
+
const schema: JsonSchemaType = {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
name: { type: 'string' },
|
|
82
|
+
age: { type: 'number' },
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
86
|
+
|
|
87
|
+
expect(zodSchema?.parse({ name: 'John', age: 30 })).toEqual({ name: 'John', age: 30 });
|
|
88
|
+
expect(() => zodSchema?.parse({ name: 123, age: 30 })).toThrow();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should handle required fields', () => {
|
|
92
|
+
const schema: JsonSchemaType = {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
name: { type: 'string' },
|
|
96
|
+
age: { type: 'number' },
|
|
97
|
+
},
|
|
98
|
+
required: ['name'],
|
|
99
|
+
};
|
|
100
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
101
|
+
|
|
102
|
+
expect(zodSchema?.parse({ name: 'John' })).toEqual({ name: 'John' });
|
|
103
|
+
expect(() => zodSchema?.parse({})).toThrow();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle nested objects', () => {
|
|
107
|
+
const schema: JsonSchemaType = {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
user: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {
|
|
113
|
+
name: { type: 'string' },
|
|
114
|
+
age: { type: 'number' },
|
|
115
|
+
},
|
|
116
|
+
required: ['name'],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
required: ['user'],
|
|
120
|
+
};
|
|
121
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
122
|
+
|
|
123
|
+
expect(zodSchema?.parse({ user: { name: 'John', age: 30 } })).toEqual({
|
|
124
|
+
user: { name: 'John', age: 30 },
|
|
125
|
+
});
|
|
126
|
+
expect(() => zodSchema?.parse({ user: { age: 30 } })).toThrow();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle objects with arrays', () => {
|
|
130
|
+
const schema: JsonSchemaType = {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
names: {
|
|
134
|
+
type: 'array',
|
|
135
|
+
items: { type: 'string' },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
140
|
+
|
|
141
|
+
expect(zodSchema?.parse({ names: ['John', 'Jane'] })).toEqual({ names: ['John', 'Jane'] });
|
|
142
|
+
expect(() => zodSchema?.parse({ names: ['John', 123] })).toThrow();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('edge cases', () => {
|
|
147
|
+
it('should handle empty object schema', () => {
|
|
148
|
+
const schema: JsonSchemaType = {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {},
|
|
151
|
+
};
|
|
152
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
153
|
+
|
|
154
|
+
expect(zodSchema?.parse({})).toEqual({});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should handle unknown types as unknown', () => {
|
|
158
|
+
const schema = {
|
|
159
|
+
type: 'invalid',
|
|
160
|
+
} as unknown as JsonSchemaType;
|
|
161
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
162
|
+
|
|
163
|
+
expect(zodSchema?.parse('anything')).toBe('anything');
|
|
164
|
+
expect(zodSchema?.parse(123)).toBe(123);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should handle empty enum arrays as regular strings', () => {
|
|
168
|
+
const schema: JsonSchemaType = {
|
|
169
|
+
type: 'string',
|
|
170
|
+
enum: [],
|
|
171
|
+
};
|
|
172
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
173
|
+
|
|
174
|
+
expect(zodSchema?.parse('test')).toBe('test');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('complex schemas', () => {
|
|
179
|
+
it('should handle complex nested schema', () => {
|
|
180
|
+
const schema: JsonSchemaType = {
|
|
181
|
+
type: 'object',
|
|
182
|
+
properties: {
|
|
183
|
+
id: { type: 'number' },
|
|
184
|
+
user: {
|
|
185
|
+
type: 'object',
|
|
186
|
+
properties: {
|
|
187
|
+
name: { type: 'string' },
|
|
188
|
+
roles: {
|
|
189
|
+
type: 'array',
|
|
190
|
+
items: {
|
|
191
|
+
type: 'object',
|
|
192
|
+
properties: {
|
|
193
|
+
name: { type: 'string' },
|
|
194
|
+
permissions: {
|
|
195
|
+
type: 'array',
|
|
196
|
+
items: {
|
|
197
|
+
type: 'string',
|
|
198
|
+
enum: ['read', 'write', 'admin'],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
required: ['name', 'permissions'],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
required: ['name', 'roles'],
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
required: ['id', 'user'],
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
213
|
+
|
|
214
|
+
const validData = {
|
|
215
|
+
id: 1,
|
|
216
|
+
user: {
|
|
217
|
+
name: 'John',
|
|
218
|
+
roles: [
|
|
219
|
+
{
|
|
220
|
+
name: 'moderator',
|
|
221
|
+
permissions: ['read', 'write'],
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
if (zodSchema == null) {
|
|
227
|
+
throw new Error('Zod schema is null');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
expect(zodSchema.parse(validData)).toEqual(validData);
|
|
231
|
+
expect(() =>
|
|
232
|
+
zodSchema.parse({
|
|
233
|
+
id: 1,
|
|
234
|
+
user: {
|
|
235
|
+
name: 'John',
|
|
236
|
+
roles: [
|
|
237
|
+
{
|
|
238
|
+
name: 'moderator',
|
|
239
|
+
permissions: ['invalid'],
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
}),
|
|
244
|
+
).toThrow();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// zod.spec.ts
|
|
249
|
+
describe('schema descriptions', () => {
|
|
250
|
+
it('should preserve top-level description', () => {
|
|
251
|
+
const schema: JsonSchemaType = {
|
|
252
|
+
type: 'object',
|
|
253
|
+
description: 'A test schema description',
|
|
254
|
+
properties: {
|
|
255
|
+
name: { type: 'string' },
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
259
|
+
expect(zodSchema?.description).toBe('A test schema description');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should preserve field descriptions', () => {
|
|
263
|
+
const schema: JsonSchemaType = {
|
|
264
|
+
type: 'object',
|
|
265
|
+
properties: {
|
|
266
|
+
name: {
|
|
267
|
+
type: 'string',
|
|
268
|
+
description: 'The user\'s name',
|
|
269
|
+
},
|
|
270
|
+
age: {
|
|
271
|
+
type: 'number',
|
|
272
|
+
description: 'The user\'s age',
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
277
|
+
|
|
278
|
+
const shape = (zodSchema as z.ZodObject<any>).shape;
|
|
279
|
+
expect(shape.name.description).toBe('The user\'s name');
|
|
280
|
+
expect(shape.age.description).toBe('The user\'s age');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should preserve descriptions in nested objects', () => {
|
|
284
|
+
const schema: JsonSchemaType = {
|
|
285
|
+
type: 'object',
|
|
286
|
+
description: 'User record',
|
|
287
|
+
properties: {
|
|
288
|
+
user: {
|
|
289
|
+
type: 'object',
|
|
290
|
+
description: 'User details',
|
|
291
|
+
properties: {
|
|
292
|
+
name: {
|
|
293
|
+
type: 'string',
|
|
294
|
+
description: 'The user\'s name',
|
|
295
|
+
},
|
|
296
|
+
settings: {
|
|
297
|
+
type: 'object',
|
|
298
|
+
description: 'User preferences',
|
|
299
|
+
properties: {
|
|
300
|
+
theme: {
|
|
301
|
+
type: 'string',
|
|
302
|
+
description: 'UI theme preference',
|
|
303
|
+
enum: ['light', 'dark'],
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
312
|
+
|
|
313
|
+
// Type assertions for better type safety
|
|
314
|
+
const shape = zodSchema instanceof z.ZodObject ? zodSchema.shape : {};
|
|
315
|
+
expect(zodSchema?.description).toBe('User record');
|
|
316
|
+
|
|
317
|
+
if ('user' in shape) {
|
|
318
|
+
expect(shape.user.description).toBe('User details');
|
|
319
|
+
|
|
320
|
+
const userShape = shape.user instanceof z.ZodObject ? shape.user.shape : {};
|
|
321
|
+
if ('name' in userShape && 'settings' in userShape) {
|
|
322
|
+
expect(userShape.name.description).toBe('The user\'s name');
|
|
323
|
+
expect(userShape.settings.description).toBe('User preferences');
|
|
324
|
+
|
|
325
|
+
const settingsShape =
|
|
326
|
+
userShape.settings instanceof z.ZodObject ? userShape.settings.shape : {};
|
|
327
|
+
if ('theme' in settingsShape) {
|
|
328
|
+
expect(settingsShape.theme.description).toBe('UI theme preference');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('should preserve descriptions in arrays', () => {
|
|
335
|
+
const schema: JsonSchemaType = {
|
|
336
|
+
type: 'object',
|
|
337
|
+
properties: {
|
|
338
|
+
tags: {
|
|
339
|
+
type: 'array',
|
|
340
|
+
description: 'User tags',
|
|
341
|
+
items: {
|
|
342
|
+
type: 'string',
|
|
343
|
+
description: 'Individual tag',
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
scores: {
|
|
347
|
+
type: 'array',
|
|
348
|
+
description: 'Test scores',
|
|
349
|
+
items: {
|
|
350
|
+
type: 'number',
|
|
351
|
+
description: 'Individual score',
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
357
|
+
|
|
358
|
+
const shape = (zodSchema as z.ZodObject<any>).shape;
|
|
359
|
+
expect(shape.tags.description).toBe('User tags');
|
|
360
|
+
expect(shape.scores.description).toBe('Test scores');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('should preserve descriptions in enums', () => {
|
|
364
|
+
const schema: JsonSchemaType = {
|
|
365
|
+
type: 'object',
|
|
366
|
+
properties: {
|
|
367
|
+
role: {
|
|
368
|
+
type: 'string',
|
|
369
|
+
description: 'User role in the system',
|
|
370
|
+
enum: ['admin', 'user', 'guest'],
|
|
371
|
+
},
|
|
372
|
+
status: {
|
|
373
|
+
type: 'string',
|
|
374
|
+
description: 'Account status',
|
|
375
|
+
enum: ['active', 'suspended', 'deleted'],
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
380
|
+
|
|
381
|
+
const shape = (zodSchema as z.ZodObject<any>).shape;
|
|
382
|
+
expect(shape.role.description).toBe('User role in the system');
|
|
383
|
+
expect(shape.status.description).toBe('Account status');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should preserve descriptions in a complex schema', () => {
|
|
387
|
+
const schema: JsonSchemaType = {
|
|
388
|
+
type: 'object',
|
|
389
|
+
description: 'User profile configuration',
|
|
390
|
+
properties: {
|
|
391
|
+
basicInfo: {
|
|
392
|
+
type: 'object',
|
|
393
|
+
description: 'Basic user information',
|
|
394
|
+
properties: {
|
|
395
|
+
name: {
|
|
396
|
+
type: 'string',
|
|
397
|
+
description: 'Full name of the user',
|
|
398
|
+
},
|
|
399
|
+
age: {
|
|
400
|
+
type: 'number',
|
|
401
|
+
description: 'User age in years',
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
required: ['name'],
|
|
405
|
+
},
|
|
406
|
+
preferences: {
|
|
407
|
+
type: 'object',
|
|
408
|
+
description: 'User preferences',
|
|
409
|
+
properties: {
|
|
410
|
+
notifications: {
|
|
411
|
+
type: 'array',
|
|
412
|
+
description: 'Notification settings',
|
|
413
|
+
items: {
|
|
414
|
+
type: 'object',
|
|
415
|
+
description: 'Individual notification preference',
|
|
416
|
+
properties: {
|
|
417
|
+
type: {
|
|
418
|
+
type: 'string',
|
|
419
|
+
description: 'Type of notification',
|
|
420
|
+
enum: ['email', 'sms', 'push'],
|
|
421
|
+
},
|
|
422
|
+
enabled: {
|
|
423
|
+
type: 'boolean',
|
|
424
|
+
description: 'Whether this notification is enabled',
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
theme: {
|
|
430
|
+
type: 'string',
|
|
431
|
+
description: 'UI theme preference',
|
|
432
|
+
enum: ['light', 'dark', 'system'],
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const zodSchema = convertJsonSchemaToZod(schema);
|
|
440
|
+
|
|
441
|
+
// Test top-level description
|
|
442
|
+
expect(zodSchema?.description).toBe('User profile configuration');
|
|
443
|
+
|
|
444
|
+
const shape = zodSchema instanceof z.ZodObject ? zodSchema.shape : {};
|
|
445
|
+
|
|
446
|
+
// Test basic info descriptions
|
|
447
|
+
if ('basicInfo' in shape) {
|
|
448
|
+
expect(shape.basicInfo.description).toBe('Basic user information');
|
|
449
|
+
const basicInfoShape = shape.basicInfo instanceof z.ZodObject ? shape.basicInfo.shape : {};
|
|
450
|
+
|
|
451
|
+
if ('name' in basicInfoShape && 'age' in basicInfoShape) {
|
|
452
|
+
expect(basicInfoShape.name.description).toBe('Full name of the user');
|
|
453
|
+
expect(basicInfoShape.age.description).toBe('User age in years');
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Test preferences descriptions
|
|
458
|
+
if ('preferences' in shape) {
|
|
459
|
+
expect(shape.preferences.description).toBe('User preferences');
|
|
460
|
+
const preferencesShape =
|
|
461
|
+
shape.preferences instanceof z.ZodObject ? shape.preferences.shape : {};
|
|
462
|
+
|
|
463
|
+
if ('notifications' in preferencesShape && 'theme' in preferencesShape) {
|
|
464
|
+
expect(preferencesShape.notifications.description).toBe('Notification settings');
|
|
465
|
+
expect(preferencesShape.theme.description).toBe('UI theme preference');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
describe('empty object handling', () => {
|
|
472
|
+
it('should return undefined for empty object schemas when allowEmptyObject is false', () => {
|
|
473
|
+
const emptyObjectSchemas = [
|
|
474
|
+
{ type: 'object' as const },
|
|
475
|
+
{ type: 'object' as const, properties: {} },
|
|
476
|
+
];
|
|
477
|
+
|
|
478
|
+
emptyObjectSchemas.forEach((schema) => {
|
|
479
|
+
expect(convertJsonSchemaToZod(schema, { allowEmptyObject: false })).toBeUndefined();
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should return zod schema for empty object schemas when allowEmptyObject is true', () => {
|
|
484
|
+
const emptyObjectSchemas = [
|
|
485
|
+
{ type: 'object' as const },
|
|
486
|
+
{ type: 'object' as const, properties: {} },
|
|
487
|
+
];
|
|
488
|
+
|
|
489
|
+
emptyObjectSchemas.forEach((schema) => {
|
|
490
|
+
const result = convertJsonSchemaToZod(schema, { allowEmptyObject: true });
|
|
491
|
+
expect(result).toBeDefined();
|
|
492
|
+
expect(result instanceof z.ZodObject).toBeTruthy();
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should return zod schema for empty object schemas by default', () => {
|
|
497
|
+
const emptyObjectSchemas = [
|
|
498
|
+
{ type: 'object' as const },
|
|
499
|
+
{ type: 'object' as const, properties: {} },
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
emptyObjectSchemas.forEach((schema) => {
|
|
503
|
+
const result = convertJsonSchemaToZod(schema);
|
|
504
|
+
expect(result).toBeDefined();
|
|
505
|
+
expect(result instanceof z.ZodObject).toBeTruthy();
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('should still convert non-empty object schemas regardless of allowEmptyObject setting', () => {
|
|
510
|
+
const schema: JsonSchemaType = {
|
|
511
|
+
type: 'object',
|
|
512
|
+
properties: {
|
|
513
|
+
name: { type: 'string' },
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const resultWithFlag = convertJsonSchemaToZod(schema, { allowEmptyObject: false });
|
|
518
|
+
const resultWithoutFlag = convertJsonSchemaToZod(schema);
|
|
519
|
+
|
|
520
|
+
expect(resultWithFlag).toBeDefined();
|
|
521
|
+
expect(resultWithoutFlag).toBeDefined();
|
|
522
|
+
expect(resultWithFlag instanceof z.ZodObject).toBeTruthy();
|
|
523
|
+
expect(resultWithoutFlag instanceof z.ZodObject).toBeTruthy();
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
});
|
package/src/zod.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export type JsonSchemaType = {
|
|
4
|
+
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
5
|
+
enum?: string[];
|
|
6
|
+
items?: JsonSchemaType;
|
|
7
|
+
properties?: Record<string, JsonSchemaType>;
|
|
8
|
+
required?: string[];
|
|
9
|
+
description?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function isEmptyObjectSchema(jsonSchema?: JsonSchemaType): boolean {
|
|
13
|
+
return (
|
|
14
|
+
jsonSchema != null &&
|
|
15
|
+
typeof jsonSchema === 'object' &&
|
|
16
|
+
jsonSchema.type === 'object' &&
|
|
17
|
+
(jsonSchema.properties == null || Object.keys(jsonSchema.properties).length === 0)
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function convertJsonSchemaToZod(
|
|
22
|
+
schema: JsonSchemaType,
|
|
23
|
+
options: { allowEmptyObject?: boolean } = {},
|
|
24
|
+
): z.ZodType | undefined {
|
|
25
|
+
const { allowEmptyObject = true } = options;
|
|
26
|
+
if (!allowEmptyObject && isEmptyObjectSchema(schema)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let zodSchema: z.ZodType;
|
|
31
|
+
|
|
32
|
+
// Handle primitive types
|
|
33
|
+
if (schema.type === 'string') {
|
|
34
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
35
|
+
const [first, ...rest] = schema.enum;
|
|
36
|
+
zodSchema = z.enum([first, ...rest] as [string, ...string[]]);
|
|
37
|
+
} else {
|
|
38
|
+
zodSchema = z.string();
|
|
39
|
+
}
|
|
40
|
+
} else if (schema.type === 'number') {
|
|
41
|
+
zodSchema = z.number();
|
|
42
|
+
} else if (schema.type === 'boolean') {
|
|
43
|
+
zodSchema = z.boolean();
|
|
44
|
+
} else if (schema.type === 'array' && schema.items !== undefined) {
|
|
45
|
+
const itemSchema = convertJsonSchemaToZod(schema.items);
|
|
46
|
+
zodSchema = z.array(itemSchema as z.ZodType);
|
|
47
|
+
} else if (schema.type === 'object') {
|
|
48
|
+
const shape: Record<string, z.ZodType> = {};
|
|
49
|
+
const properties = schema.properties ?? {};
|
|
50
|
+
|
|
51
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
52
|
+
let fieldSchema = convertJsonSchemaToZod(value);
|
|
53
|
+
if (!fieldSchema) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (value.description != null && value.description !== '') {
|
|
57
|
+
fieldSchema = fieldSchema.describe(value.description);
|
|
58
|
+
}
|
|
59
|
+
shape[key] = fieldSchema;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let objectSchema = z.object(shape);
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(schema.required) && schema.required.length > 0) {
|
|
65
|
+
const partial = Object.fromEntries(
|
|
66
|
+
Object.entries(shape).map(([key, value]) => [
|
|
67
|
+
key,
|
|
68
|
+
schema.required?.includes(key) === true ? value : value.optional(),
|
|
69
|
+
]),
|
|
70
|
+
);
|
|
71
|
+
objectSchema = z.object(partial);
|
|
72
|
+
} else {
|
|
73
|
+
objectSchema = objectSchema.partial();
|
|
74
|
+
}
|
|
75
|
+
zodSchema = objectSchema;
|
|
76
|
+
} else {
|
|
77
|
+
zodSchema = z.unknown();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Add description if present
|
|
81
|
+
if (schema.description != null && schema.description !== '') {
|
|
82
|
+
zodSchema = zodSchema.describe(schema.description);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return zodSchema;
|
|
86
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
"isolatedModules": true,
|
|
19
19
|
"noEmit": true,
|
|
20
20
|
"sourceMap": true,
|
|
21
|
-
"baseUrl": ".",
|
|
21
|
+
"baseUrl": ".",
|
|
22
22
|
"paths": {
|
|
23
|
-
// Add path mappings
|
|
24
23
|
"librechat-data-provider/react-query": ["./src/react-query/index.ts"]
|
|
25
24
|
}
|
|
26
25
|
},
|