@ai-sdk/google 4.0.68 → 4.0.70

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,456 +0,0 @@
1
- import {
2
- UnsupportedFunctionalityError,
3
- type JSONSchema7,
4
- type JSONSchema7Definition,
5
- } from '@ai-sdk/provider';
6
-
7
- type JSONSchema7WithDefinitions = JSONSchema7 & {
8
- $defs?: Record<string, JSONSchema7Definition>;
9
- };
10
-
11
- type ReferenceContext = {
12
- definitions: Record<string, JSONSchema7Definition> | undefined;
13
- dollarDefinitions: Record<string, JSONSchema7Definition> | undefined;
14
- resolvingReferences: ReadonlySet<string>;
15
- };
16
-
17
- const recursiveReferenceFunctionalityPrefix =
18
- 'recursive JSON Schema reference:';
19
-
20
- export function isRecursiveJSONSchemaReferenceError(
21
- error: unknown,
22
- ): error is UnsupportedFunctionalityError {
23
- return (
24
- UnsupportedFunctionalityError.isInstance(error) &&
25
- error.functionality.startsWith(recursiveReferenceFunctionalityPrefix)
26
- );
27
- }
28
-
29
- /**
30
- * Converts JSON Schema 7 to OpenAPI Schema 3.0
31
- */
32
- export function convertJSONSchemaToOpenAPISchema(
33
- jsonSchema: JSONSchema7Definition | undefined,
34
- isRoot = true,
35
- ): unknown {
36
- const rootSchema =
37
- typeof jsonSchema === 'object'
38
- ? (jsonSchema as JSONSchema7WithDefinitions)
39
- : undefined;
40
-
41
- return convertJSONSchemaDefinition(jsonSchema, isRoot, {
42
- definitions: rootSchema?.definitions,
43
- dollarDefinitions: rootSchema?.$defs,
44
- resolvingReferences: new Set(),
45
- });
46
- }
47
-
48
- function convertJSONSchemaDefinition(
49
- jsonSchema: JSONSchema7Definition | undefined,
50
- isRoot: boolean,
51
- referenceContext: ReferenceContext,
52
- ): unknown {
53
- if (jsonSchema == null) {
54
- return undefined;
55
- }
56
-
57
- if (typeof jsonSchema === 'boolean') {
58
- return { type: 'boolean', properties: {} };
59
- }
60
-
61
- if (jsonSchema.$ref != null) {
62
- return convertJSONSchemaReference({
63
- jsonSchema,
64
- reference: jsonSchema.$ref,
65
- isRoot,
66
- referenceContext,
67
- });
68
- }
69
-
70
- // Handle empty object schemas: undefined at root, preserved when nested
71
- if (isEmptyObjectSchema(jsonSchema)) {
72
- if (isRoot) {
73
- return undefined;
74
- }
75
-
76
- if (jsonSchema.description) {
77
- return { type: 'object', description: jsonSchema.description };
78
- }
79
- return { type: 'object' };
80
- }
81
-
82
- const {
83
- type,
84
- description,
85
- required,
86
- properties,
87
- items,
88
- allOf,
89
- anyOf,
90
- oneOf,
91
- format,
92
- const: constValue,
93
- minLength,
94
- minItems,
95
- maxItems,
96
- enum: enumValues,
97
- } = jsonSchema;
98
-
99
- const result: Record<string, unknown> = {};
100
-
101
- if (description) result.description = description;
102
- if (required) result.required = required;
103
- if (format) result.format = format;
104
-
105
- // Handle type
106
- if (type) {
107
- if (Array.isArray(type)) {
108
- const hasNull = type.includes('null');
109
- const nonNullTypes = type.filter(t => t !== 'null');
110
-
111
- if (nonNullTypes.length === 0) {
112
- // Only null type
113
- result.type = 'null';
114
- } else {
115
- // One or more non-null types: always use anyOf
116
- result.anyOf = nonNullTypes.map(t => ({ type: t }));
117
- if (hasNull) {
118
- result.nullable = true;
119
- }
120
- }
121
- } else {
122
- result.type = type;
123
- }
124
- }
125
-
126
- const values =
127
- enumValues ?? (constValue !== undefined ? [constValue] : undefined);
128
-
129
- if (values !== undefined) {
130
- addEnumToSchema({ values, type, result });
131
- }
132
-
133
- if (properties != null) {
134
- result.properties = Object.entries(properties).reduce(
135
- (acc, [key, value]) => {
136
- acc[key] = convertJSONSchemaDefinition(value, false, referenceContext);
137
- return acc;
138
- },
139
- {} as Record<string, unknown>,
140
- );
141
- }
142
-
143
- if (items) {
144
- result.items = Array.isArray(items)
145
- ? items.map(item =>
146
- convertJSONSchemaDefinition(item, false, referenceContext),
147
- )
148
- : convertJSONSchemaDefinition(items, false, referenceContext);
149
- }
150
-
151
- if (allOf) {
152
- result.allOf = allOf.map(item =>
153
- convertJSONSchemaDefinition(item, false, referenceContext),
154
- );
155
- }
156
- if (anyOf) {
157
- // Handle cases where anyOf includes a null type
158
- if (
159
- anyOf.some(
160
- schema => typeof schema === 'object' && schema?.type === 'null',
161
- )
162
- ) {
163
- const nonNullSchemas = anyOf.filter(
164
- schema => !(typeof schema === 'object' && schema?.type === 'null'),
165
- );
166
-
167
- if (nonNullSchemas.length === 1) {
168
- // If there's only one non-null schema, convert it and make it nullable
169
- const converted = convertJSONSchemaDefinition(
170
- nonNullSchemas[0],
171
- false,
172
- referenceContext,
173
- );
174
- if (typeof converted === 'object') {
175
- result.nullable = true;
176
- Object.assign(result, converted);
177
- }
178
- } else {
179
- // If there are multiple non-null schemas, keep them in anyOf
180
- result.anyOf = nonNullSchemas.map(item =>
181
- convertJSONSchemaDefinition(item, false, referenceContext),
182
- );
183
- result.nullable = true;
184
- }
185
- } else {
186
- result.anyOf = anyOf.map(item =>
187
- convertJSONSchemaDefinition(item, false, referenceContext),
188
- );
189
- }
190
- }
191
- if (oneOf) {
192
- result.oneOf = oneOf.map(item =>
193
- convertJSONSchemaDefinition(item, false, referenceContext),
194
- );
195
- }
196
-
197
- if (minLength !== undefined) {
198
- result.minLength = minLength;
199
- }
200
-
201
- if (minItems !== undefined) {
202
- result.minItems = minItems;
203
- }
204
-
205
- if (maxItems !== undefined) {
206
- result.maxItems = maxItems;
207
- }
208
-
209
- return result;
210
- }
211
-
212
- function convertJSONSchemaReference({
213
- jsonSchema,
214
- reference,
215
- isRoot,
216
- referenceContext,
217
- }: {
218
- jsonSchema: JSONSchema7;
219
- reference: string;
220
- isRoot: boolean;
221
- referenceContext: ReferenceContext;
222
- }): unknown {
223
- const { definition, referenceKey } = getReferencedDefinition(
224
- reference,
225
- referenceContext,
226
- );
227
-
228
- if (referenceContext.resolvingReferences.has(referenceKey)) {
229
- throw new UnsupportedFunctionalityError({
230
- functionality: `${recursiveReferenceFunctionalityPrefix} ${reference}`,
231
- message:
232
- 'Google schema conversion does not support recursive JSON Schema references.',
233
- });
234
- }
235
-
236
- const resolvingReferences = new Set(referenceContext.resolvingReferences);
237
- resolvingReferences.add(referenceKey);
238
-
239
- // Inline references instead of emitting Google's `ref` / `defs` fields.
240
- // Those fields are supported by Vertex AI's Schema representation but are
241
- // rejected by the Gemini Developer API representation used by this shared
242
- // converter.
243
- const { $ref: _reference, ...siblingSchema } = jsonSchema;
244
- const resolvedSchema =
245
- typeof definition === 'boolean'
246
- ? definition
247
- ? siblingSchema
248
- : false
249
- : { ...definition, ...siblingSchema };
250
-
251
- return convertJSONSchemaDefinition(resolvedSchema, isRoot, {
252
- ...referenceContext,
253
- resolvingReferences,
254
- });
255
- }
256
-
257
- function getReferencedDefinition(
258
- reference: string,
259
- referenceContext: ReferenceContext,
260
- ): {
261
- definition: JSONSchema7Definition;
262
- referenceKey: string;
263
- } {
264
- const definitionSources = [
265
- {
266
- prefix: '#/$defs/',
267
- definitions: referenceContext.dollarDefinitions,
268
- },
269
- {
270
- prefix: '#/definitions/',
271
- definitions: referenceContext.definitions,
272
- },
273
- ];
274
-
275
- const source = definitionSources.find(({ prefix }) =>
276
- reference.startsWith(prefix),
277
- );
278
- const encodedDefinitionName = source
279
- ? reference.slice(source.prefix.length)
280
- : undefined;
281
-
282
- if (
283
- source == null ||
284
- encodedDefinitionName == null ||
285
- encodedDefinitionName.length === 0 ||
286
- encodedDefinitionName.includes('/')
287
- ) {
288
- throwUnsupportedReference(reference);
289
- }
290
-
291
- let decodedDefinitionName: string;
292
- try {
293
- decodedDefinitionName = decodeURIComponent(encodedDefinitionName);
294
- } catch {
295
- throwUnsupportedReference(reference);
296
- }
297
-
298
- if (
299
- decodedDefinitionName.includes('/') ||
300
- /~(?![01])/u.test(decodedDefinitionName) ||
301
- source.definitions == null
302
- ) {
303
- throwUnsupportedReference(reference);
304
- }
305
-
306
- const definitionName = decodedDefinitionName.replace(/~[01]/g, match =>
307
- match === '~1' ? '/' : '~',
308
- );
309
-
310
- if (
311
- !Object.prototype.hasOwnProperty.call(source.definitions, definitionName)
312
- ) {
313
- throwUnsupportedReference(reference);
314
- }
315
-
316
- return {
317
- definition: source.definitions[definitionName],
318
- referenceKey: `${source.prefix}${definitionName}`,
319
- };
320
- }
321
-
322
- function throwUnsupportedReference(reference: string): never {
323
- throw new UnsupportedFunctionalityError({
324
- functionality: `JSON Schema reference: ${reference}`,
325
- message:
326
- 'Google schema conversion only supports references to direct children of root-level $defs or definitions.',
327
- });
328
- }
329
-
330
- type EnumValues = NonNullable<JSONSchema7['enum']>;
331
- type EnumType = 'string' | 'number' | 'integer' | 'boolean';
332
- type GoogleEnumSchema = {
333
- type?: JSONSchema7['type'];
334
- enum?: JSONSchema7['enum'];
335
- format?: JSONSchema7['format'];
336
- anyOf?: JSONSchema7['anyOf'];
337
- nullable?: boolean;
338
- };
339
-
340
- function addEnumToSchema({
341
- values,
342
- type,
343
- result,
344
- }: {
345
- values: EnumValues;
346
- type: JSONSchema7['type'];
347
- result: GoogleEnumSchema;
348
- }) {
349
- const nullable =
350
- (Array.isArray(type) && type.includes('null')) ||
351
- (type === undefined && values.includes(null));
352
-
353
- // Gemini uses nullable instead of a null enum member.
354
- const enumValues = nullable ? values.filter(value => value !== null) : values;
355
-
356
- if (values.length > 0 && values.every(value => value === null)) {
357
- const typeAllowsNull =
358
- type === undefined ||
359
- type === 'null' ||
360
- (Array.isArray(type) && type.includes('null'));
361
-
362
- if (typeAllowsNull) {
363
- result.type = 'null';
364
- if (Array.isArray(type)) {
365
- delete result.anyOf;
366
- }
367
- return;
368
- }
369
- }
370
-
371
- const enumType = getEnumType({ values: enumValues, type });
372
-
373
- if (enumType === undefined) {
374
- throw new UnsupportedFunctionalityError({
375
- functionality: 'JSON Schema enum with mixed or unsupported values',
376
- message:
377
- 'Google does not support this JSON Schema enum. Enum values must share one supported primitive type and match the schema type.',
378
- });
379
- }
380
-
381
- result.type = enumType;
382
-
383
- // The earlier type-array conversion created anyOf. The enum gives us one
384
- // concrete value type, so store that type directly.
385
- if (Array.isArray(type)) {
386
- delete result.anyOf;
387
- }
388
-
389
- if (nullable) {
390
- result.nullable = true;
391
- }
392
-
393
- if (enumType === 'string') {
394
- result.enum = enumValues;
395
- } else {
396
- result.format = 'enum';
397
- result.enum = enumValues.map(String);
398
- }
399
- }
400
-
401
- function getEnumType({
402
- values,
403
- type,
404
- }: {
405
- values: EnumValues;
406
- type: JSONSchema7['type'];
407
- }): EnumType | undefined {
408
- if (values.length === 0) {
409
- return undefined;
410
- }
411
-
412
- const typeAllows = (enumType: EnumType) =>
413
- type === undefined ||
414
- type === enumType ||
415
- (Array.isArray(type) && type.includes(enumType));
416
-
417
- if (
418
- typeAllows('string') &&
419
- values.every(value => typeof value === 'string')
420
- ) {
421
- return 'string';
422
- }
423
-
424
- if (
425
- (typeAllows('number') || typeAllows('integer')) &&
426
- values.every(value => typeof value === 'number' && Number.isFinite(value))
427
- ) {
428
- if (typeAllows('number')) {
429
- return 'number';
430
- }
431
-
432
- if (values.every(value => Number.isInteger(value))) {
433
- return 'integer';
434
- }
435
- }
436
-
437
- if (
438
- typeAllows('boolean') &&
439
- values.every(value => typeof value === 'boolean')
440
- ) {
441
- return 'boolean';
442
- }
443
-
444
- return undefined;
445
- }
446
-
447
- function isEmptyObjectSchema(jsonSchema: JSONSchema7Definition): boolean {
448
- return (
449
- jsonSchema != null &&
450
- typeof jsonSchema === 'object' &&
451
- jsonSchema.type === 'object' &&
452
- (jsonSchema.properties == null ||
453
- Object.keys(jsonSchema.properties).length === 0) &&
454
- !jsonSchema.additionalProperties
455
- );
456
- }