@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.
- package/CHANGELOG.md +25 -0
- package/dist/index.js +173 -329
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +9 -4
- package/dist/internal/index.js +159 -322
- package/dist/internal/index.js.map +1 -1
- package/package.json +1 -1
- package/src/download-tool-result-files.ts +124 -0
- package/src/google-language-model.ts +32 -13
- package/src/google-prepare-tools.ts +3 -23
- package/src/realtime/google-realtime-event-mapper.ts +24 -3
- package/src/sanitize-response-json-schema.ts +65 -0
- package/src/convert-json-schema-to-openapi-schema.ts +0 -456
package/package.json
CHANGED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LanguageModelV4Prompt,
|
|
3
|
+
LanguageModelV4ToolResultOutput,
|
|
4
|
+
} from '@ai-sdk/provider';
|
|
5
|
+
import {
|
|
6
|
+
detectMediaType,
|
|
7
|
+
downloadBlob,
|
|
8
|
+
isFullMediaType,
|
|
9
|
+
} from '@ai-sdk/provider-utils';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Vertex function responses only accept inline file data. Download remote tool
|
|
13
|
+
* result files before converting the prompt to the Google request format.
|
|
14
|
+
*/
|
|
15
|
+
export async function downloadToolResultFiles(
|
|
16
|
+
prompt: LanguageModelV4Prompt,
|
|
17
|
+
{
|
|
18
|
+
abortSignal,
|
|
19
|
+
maxBytes,
|
|
20
|
+
}: {
|
|
21
|
+
abortSignal: AbortSignal | undefined;
|
|
22
|
+
maxBytes: number;
|
|
23
|
+
},
|
|
24
|
+
): Promise<LanguageModelV4Prompt> {
|
|
25
|
+
const result: LanguageModelV4Prompt = [];
|
|
26
|
+
|
|
27
|
+
for (const message of prompt) {
|
|
28
|
+
if (message.role === 'assistant') {
|
|
29
|
+
const content: typeof message.content = [];
|
|
30
|
+
|
|
31
|
+
for (const part of message.content) {
|
|
32
|
+
content.push(
|
|
33
|
+
part.type === 'tool-result'
|
|
34
|
+
? {
|
|
35
|
+
...part,
|
|
36
|
+
output: await downloadToolResultOutput(part.output, {
|
|
37
|
+
abortSignal,
|
|
38
|
+
maxBytes,
|
|
39
|
+
}),
|
|
40
|
+
}
|
|
41
|
+
: part,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
result.push({ ...message, content });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (message.role === 'tool') {
|
|
50
|
+
const content: typeof message.content = [];
|
|
51
|
+
|
|
52
|
+
for (const part of message.content) {
|
|
53
|
+
if (part.type !== 'tool-result') {
|
|
54
|
+
content.push(part);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
content.push({
|
|
59
|
+
...part,
|
|
60
|
+
output: await downloadToolResultOutput(part.output, {
|
|
61
|
+
abortSignal,
|
|
62
|
+
maxBytes,
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
result.push({ ...message, content });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
result.push(message);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function downloadToolResultOutput(
|
|
78
|
+
output: LanguageModelV4ToolResultOutput,
|
|
79
|
+
{
|
|
80
|
+
abortSignal,
|
|
81
|
+
maxBytes,
|
|
82
|
+
}: {
|
|
83
|
+
abortSignal: AbortSignal | undefined;
|
|
84
|
+
maxBytes: number;
|
|
85
|
+
},
|
|
86
|
+
): Promise<LanguageModelV4ToolResultOutput> {
|
|
87
|
+
if (output.type !== 'content') {
|
|
88
|
+
return output;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const value: typeof output.value = [];
|
|
92
|
+
|
|
93
|
+
for (const part of output.value) {
|
|
94
|
+
if (part.type !== 'file' || part.data.type !== 'url') {
|
|
95
|
+
value.push(part);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const blob = await downloadBlob(part.data.url.toString(), {
|
|
100
|
+
abortSignal,
|
|
101
|
+
maxBytes,
|
|
102
|
+
});
|
|
103
|
+
const data = new Uint8Array(await blob.arrayBuffer());
|
|
104
|
+
const detectedMediaType = detectMediaType({
|
|
105
|
+
data,
|
|
106
|
+
topLevelType: 'image',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
value.push({
|
|
110
|
+
...part,
|
|
111
|
+
data: { type: 'data' as const, data },
|
|
112
|
+
mediaType:
|
|
113
|
+
detectedMediaType ??
|
|
114
|
+
(blob.type && !isFullMediaType(part.mediaType)
|
|
115
|
+
? blob.type
|
|
116
|
+
: part.mediaType),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
...output,
|
|
122
|
+
value,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -38,10 +38,11 @@ import {
|
|
|
38
38
|
convertGoogleUsage,
|
|
39
39
|
type GoogleUsageMetadata,
|
|
40
40
|
} from './convert-google-usage';
|
|
41
|
-
import { convertJSONSchemaToOpenAPISchema } from './convert-json-schema-to-openapi-schema';
|
|
42
41
|
import { convertToGoogleMessages } from './convert-to-google-messages';
|
|
42
|
+
import { downloadToolResultFiles } from './download-tool-result-files';
|
|
43
43
|
import { getModelPath } from './get-model-path';
|
|
44
44
|
import { googleFailedResponseHandler } from './google-error';
|
|
45
|
+
import { sanitizeResponseJsonSchema } from './sanitize-response-json-schema';
|
|
45
46
|
import {
|
|
46
47
|
googleLanguageModelOptions,
|
|
47
48
|
type GoogleLanguageModelOptions,
|
|
@@ -76,6 +77,13 @@ export type GoogleLanguageModelConfig = {
|
|
|
76
77
|
* The supported URLs for the model.
|
|
77
78
|
*/
|
|
78
79
|
supportedUrls?: () => LanguageModelV4['supportedUrls'];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Settings for downloading remote files in tool results before conversion.
|
|
83
|
+
*/
|
|
84
|
+
downloadToolResultFiles?: {
|
|
85
|
+
maxBytes: number;
|
|
86
|
+
};
|
|
79
87
|
};
|
|
80
88
|
|
|
81
89
|
export class GoogleLanguageModel implements LanguageModelV4 {
|
|
@@ -132,6 +140,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
132
140
|
toolChoice,
|
|
133
141
|
reasoning,
|
|
134
142
|
providerOptions,
|
|
143
|
+
abortSignal,
|
|
135
144
|
},
|
|
136
145
|
isStreaming = false,
|
|
137
146
|
}: {
|
|
@@ -287,14 +296,24 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
287
296
|
|
|
288
297
|
const { usesGemini3Features } = getGoogleModelCapabilities(modelId);
|
|
289
298
|
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
299
|
+
const promptWithDownloadedToolResultFiles = config.downloadToolResultFiles
|
|
300
|
+
? await downloadToolResultFiles(prompt, {
|
|
301
|
+
abortSignal,
|
|
302
|
+
maxBytes: config.downloadToolResultFiles.maxBytes,
|
|
303
|
+
})
|
|
304
|
+
: prompt;
|
|
305
|
+
|
|
306
|
+
const { contents, systemInstruction } = convertToGoogleMessages(
|
|
307
|
+
promptWithDownloadedToolResultFiles,
|
|
308
|
+
{
|
|
309
|
+
isGemmaModel,
|
|
310
|
+
isGemini3Model: usesGemini3Features,
|
|
311
|
+
onWarning: warning => warnings.push(warning),
|
|
312
|
+
providerOptionsNames,
|
|
313
|
+
supportsFunctionResponseParts: usesGemini3Features,
|
|
314
|
+
includeFunctionCallIds: !isVertexProvider,
|
|
315
|
+
},
|
|
316
|
+
);
|
|
298
317
|
|
|
299
318
|
const {
|
|
300
319
|
tools: googleTools,
|
|
@@ -376,14 +395,14 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
376
395
|
// response format:
|
|
377
396
|
responseMimeType:
|
|
378
397
|
responseFormat?.type === 'json' ? 'application/json' : undefined,
|
|
379
|
-
|
|
398
|
+
responseJsonSchema:
|
|
380
399
|
responseFormat?.type === 'json' &&
|
|
381
400
|
responseFormat.schema != null &&
|
|
382
|
-
// Google
|
|
383
|
-
// so this is needed as an escape hatch:
|
|
401
|
+
// Google does not support all JSON Schema features in
|
|
402
|
+
// responseJsonSchema, so this is needed as an escape hatch:
|
|
384
403
|
// TODO convert into provider option
|
|
385
404
|
(googleOptions?.structuredOutputs ?? true)
|
|
386
|
-
?
|
|
405
|
+
? sanitizeResponseJsonSchema(responseFormat.schema)
|
|
387
406
|
: undefined,
|
|
388
407
|
...(googleOptions?.audioTimestamp && {
|
|
389
408
|
audioTimestamp: googleOptions.audioTimestamp,
|
|
@@ -3,10 +3,6 @@ import {
|
|
|
3
3
|
type LanguageModelV4CallOptions,
|
|
4
4
|
type SharedV4Warning,
|
|
5
5
|
} from '@ai-sdk/provider';
|
|
6
|
-
import {
|
|
7
|
-
convertJSONSchemaToOpenAPISchema,
|
|
8
|
-
isRecursiveJSONSchemaReferenceError,
|
|
9
|
-
} from './convert-json-schema-to-openapi-schema';
|
|
10
6
|
import type { GoogleModelId } from './google-language-model-options';
|
|
11
7
|
import { getGoogleModelCapabilities } from './google-model-capabilities';
|
|
12
8
|
|
|
@@ -18,8 +14,7 @@ type FunctionTool = Extract<
|
|
|
18
14
|
type GoogleFunctionDeclaration = {
|
|
19
15
|
name: string;
|
|
20
16
|
description: string;
|
|
21
|
-
|
|
22
|
-
parametersJsonSchema?: unknown;
|
|
17
|
+
parametersJsonSchema: unknown;
|
|
23
18
|
};
|
|
24
19
|
|
|
25
20
|
export function prepareTools({
|
|
@@ -317,24 +312,9 @@ export function prepareTools({
|
|
|
317
312
|
function prepareFunctionDeclaration(
|
|
318
313
|
tool: FunctionTool,
|
|
319
314
|
): GoogleFunctionDeclaration {
|
|
320
|
-
|
|
315
|
+
return {
|
|
321
316
|
name: tool.name,
|
|
322
317
|
description: tool.description ?? '',
|
|
318
|
+
parametersJsonSchema: tool.inputSchema,
|
|
323
319
|
};
|
|
324
|
-
|
|
325
|
-
try {
|
|
326
|
-
return {
|
|
327
|
-
...declaration,
|
|
328
|
-
parameters: convertJSONSchemaToOpenAPISchema(tool.inputSchema),
|
|
329
|
-
};
|
|
330
|
-
} catch (error) {
|
|
331
|
-
if (!isRecursiveJSONSchemaReferenceError(error)) {
|
|
332
|
-
throw error;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return {
|
|
336
|
-
...declaration,
|
|
337
|
-
parametersJsonSchema: tool.inputSchema,
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
320
|
}
|
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
Experimental_RealtimeModelV4SessionConfig as RealtimeModelV4SessionConfig,
|
|
7
7
|
} from '@ai-sdk/provider';
|
|
8
8
|
import { isRecord, safeParseJSON } from '@ai-sdk/provider-utils';
|
|
9
|
-
import { convertJSONSchemaToOpenAPISchema } from '../convert-json-schema-to-openapi-schema';
|
|
10
9
|
import { getModelPath } from '../get-model-path';
|
|
11
10
|
import type { GoogleRealtimeModelOptions } from './google-realtime-model-options';
|
|
12
11
|
|
|
@@ -337,11 +336,33 @@ export class GoogleRealtimeEventMapper {
|
|
|
337
336
|
}
|
|
338
337
|
}
|
|
339
338
|
|
|
339
|
+
/**
|
|
340
|
+
* The value Gemini accepts for `functionResponse.response`.
|
|
341
|
+
*
|
|
342
|
+
* The field is typed `google.protobuf.Struct`, which is an object and nothing else, so
|
|
343
|
+
* a string, number, array or `null` on the wire is a protocol violation and the socket
|
|
344
|
+
* closes with 1007. `onToolCall` returns `unknown` and `addToolOutput` takes `unknown`,
|
|
345
|
+
* so those shapes reach here as perfectly valid JSON.
|
|
346
|
+
*
|
|
347
|
+
* The field's own docstring says what to do with one: "Use `output` key to specify
|
|
348
|
+
* function output ... If `output` and `error` keys are not specified, then whole
|
|
349
|
+
* `response` is treated as function output." An object is passed through unchanged and
|
|
350
|
+
* everything else is wrapped.
|
|
351
|
+
*/
|
|
352
|
+
function toFunctionResponseStruct(value: unknown): Record<string, unknown> {
|
|
353
|
+
const isStruct =
|
|
354
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
355
|
+
return isStruct ? (value as Record<string, unknown>) : { output: value };
|
|
356
|
+
}
|
|
357
|
+
|
|
340
358
|
async function serializeFunctionCallOutput(
|
|
341
359
|
item: RealtimeModelV4FunctionCallOutput,
|
|
342
360
|
): Promise<unknown> {
|
|
343
361
|
const parseResult = await safeParseJSON({ text: item.output });
|
|
344
|
-
const response = parseResult.success
|
|
362
|
+
const response = parseResult.success
|
|
363
|
+
? toFunctionResponseStruct(parseResult.value)
|
|
364
|
+
: // Preserve non-JSON output in the required object wrapper.
|
|
365
|
+
{ output: item.output };
|
|
345
366
|
|
|
346
367
|
return {
|
|
347
368
|
toolResponse: {
|
|
@@ -402,7 +423,7 @@ export function buildGoogleSessionConfig(
|
|
|
402
423
|
functionDeclarations: config.tools.map(tool => ({
|
|
403
424
|
name: tool.name,
|
|
404
425
|
description: tool.description,
|
|
405
|
-
|
|
426
|
+
parametersJsonSchema: tool.parameters,
|
|
406
427
|
})),
|
|
407
428
|
},
|
|
408
429
|
];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { JSONSchema7, JSONSchema7Definition } from '@ai-sdk/provider';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recursively replaces `const` with a single-value `enum` in the JSON Schema
|
|
5
|
+
* locations supported by Google because `responseJsonSchema` does not support
|
|
6
|
+
* `const`. All other schema properties are preserved.
|
|
7
|
+
*/
|
|
8
|
+
export function sanitizeResponseJsonSchema(schema: JSONSchema7): JSONSchema7 {
|
|
9
|
+
const {
|
|
10
|
+
const: constValue,
|
|
11
|
+
properties,
|
|
12
|
+
items,
|
|
13
|
+
additionalProperties,
|
|
14
|
+
anyOf,
|
|
15
|
+
oneOf,
|
|
16
|
+
...result
|
|
17
|
+
} = schema;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
...result,
|
|
21
|
+
...(constValue !== undefined ? { enum: [constValue] } : {}),
|
|
22
|
+
...(properties != null
|
|
23
|
+
? { properties: sanitizeDefinitions(properties) }
|
|
24
|
+
: {}),
|
|
25
|
+
...(items != null
|
|
26
|
+
? {
|
|
27
|
+
items: Array.isArray(items)
|
|
28
|
+
? items.map(sanitizeDefinition)
|
|
29
|
+
: sanitizeDefinition(items),
|
|
30
|
+
}
|
|
31
|
+
: {}),
|
|
32
|
+
...(additionalProperties != null
|
|
33
|
+
? {
|
|
34
|
+
additionalProperties:
|
|
35
|
+
typeof additionalProperties === 'boolean'
|
|
36
|
+
? additionalProperties
|
|
37
|
+
: sanitizeDefinition(additionalProperties),
|
|
38
|
+
}
|
|
39
|
+
: {}),
|
|
40
|
+
...(anyOf != null ? { anyOf: anyOf.map(sanitizeDefinition) } : {}),
|
|
41
|
+
...(oneOf != null ? { oneOf: oneOf.map(sanitizeDefinition) } : {}),
|
|
42
|
+
...(result.$defs != null
|
|
43
|
+
? { $defs: sanitizeDefinitions(result.$defs) }
|
|
44
|
+
: {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function sanitizeDefinitions(
|
|
49
|
+
definitions: Record<string, JSONSchema7Definition>,
|
|
50
|
+
): Record<string, JSONSchema7Definition> {
|
|
51
|
+
return Object.fromEntries(
|
|
52
|
+
Object.entries(definitions).map(([name, definition]) => [
|
|
53
|
+
name,
|
|
54
|
+
sanitizeDefinition(definition),
|
|
55
|
+
]),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sanitizeDefinition(
|
|
60
|
+
definition: JSONSchema7Definition,
|
|
61
|
+
): JSONSchema7Definition {
|
|
62
|
+
return typeof definition === 'boolean'
|
|
63
|
+
? definition
|
|
64
|
+
: sanitizeResponseJsonSchema(definition);
|
|
65
|
+
}
|