@ai-sdk/google 4.0.79 → 4.0.82
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 +23 -0
- package/README.md +1 -1
- package/dist/index.js +493 -519
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +6 -0
- package/dist/internal/index.js +356 -342
- package/dist/internal/index.js.map +1 -1
- package/docs/15-google.mdx +14 -14
- package/package.json +3 -3
- package/src/convert-to-google-messages.ts +56 -10
- package/src/download-tool-result-files.ts +20 -2
- package/src/google-language-model.ts +16 -0
- package/src/google-prompt.ts +7 -3
- package/src/interactions/convert-to-google-interactions-input.ts +2 -2
- package/src/interactions/google-interactions-prompt.ts +2 -2
package/docs/15-google.mdx
CHANGED
|
@@ -923,14 +923,14 @@ The `vertexRagStore` tool accepts the following configuration options:
|
|
|
923
923
|
|
|
924
924
|
### Image Outputs
|
|
925
925
|
|
|
926
|
-
Gemini models with image generation capabilities (e.g. `gemini-
|
|
926
|
+
Gemini models with image generation capabilities (e.g. `gemini-3.1-flash-image-preview`) support generating images as part of a multimodal response. Images are exposed as files in the response.
|
|
927
927
|
|
|
928
928
|
```ts
|
|
929
929
|
import { google } from '@ai-sdk/google';
|
|
930
930
|
import { generateText } from 'ai';
|
|
931
931
|
|
|
932
932
|
const result = await generateText({
|
|
933
|
-
model: google('gemini-
|
|
933
|
+
model: google('gemini-3.1-flash-image-preview'),
|
|
934
934
|
prompt:
|
|
935
935
|
'Create a picture of a nano banana dish in a fancy restaurant with a Gemini theme',
|
|
936
936
|
});
|
|
@@ -1088,7 +1088,7 @@ You can pass a `webhookUrl` to receive a notification when the batch reaches a t
|
|
|
1088
1088
|
import { google } from '@ai-sdk/google';
|
|
1089
1089
|
import { experimental_startBatch as startBatch } from 'ai';
|
|
1090
1090
|
|
|
1091
|
-
const model = 'gemini-3.
|
|
1091
|
+
const model = 'gemini-3.8-flash';
|
|
1092
1092
|
|
|
1093
1093
|
const batch = await startBatch({
|
|
1094
1094
|
provider: google,
|
|
@@ -1578,7 +1578,7 @@ import { google, type GoogleInteractionsVideoOptions } from '@ai-sdk/google';
|
|
|
1578
1578
|
import { generateText } from 'ai';
|
|
1579
1579
|
|
|
1580
1580
|
const result = await generateText({
|
|
1581
|
-
model: google.interactions('gemini-3.
|
|
1581
|
+
model: google.interactions('gemini-3.8-flash'),
|
|
1582
1582
|
messages: [
|
|
1583
1583
|
{
|
|
1584
1584
|
role: 'user',
|
|
@@ -1626,7 +1626,7 @@ These parts are informational and do not require a tool response. They are
|
|
|
1626
1626
|
available from both `generateText` and `streamText`.
|
|
1627
1627
|
|
|
1628
1628
|
For static processing, use `'static'` or provide clipping and frame-rate
|
|
1629
|
-
options:
|
|
1629
|
+
options. `startOffset` and `endOffset` are measured in seconds:
|
|
1630
1630
|
|
|
1631
1631
|
```ts
|
|
1632
1632
|
const video = {
|
|
@@ -1637,8 +1637,8 @@ const video = {
|
|
|
1637
1637
|
google: {
|
|
1638
1638
|
processing: {
|
|
1639
1639
|
type: 'static',
|
|
1640
|
-
startOffset:
|
|
1641
|
-
endOffset:
|
|
1640
|
+
startOffset: 0,
|
|
1641
|
+
endOffset: 10.5,
|
|
1642
1642
|
fps: 0.5,
|
|
1643
1643
|
},
|
|
1644
1644
|
} satisfies GoogleInteractionsVideoOptions,
|
|
@@ -1778,7 +1778,7 @@ import {
|
|
|
1778
1778
|
import { generateText } from 'ai';
|
|
1779
1779
|
|
|
1780
1780
|
const result = await generateText({
|
|
1781
|
-
model: google.interactions('gemini-
|
|
1781
|
+
model: google.interactions('gemini-3.1-flash-image-preview'),
|
|
1782
1782
|
prompt:
|
|
1783
1783
|
'Tell me a three sentence bedtime story about a unicorn, accompanied by a suitable illustration.',
|
|
1784
1784
|
providerOptions: {
|
|
@@ -2042,7 +2042,7 @@ You can create models that call the [Google Generative AI embeddings API](https:
|
|
|
2042
2042
|
using the `.embedding()` factory method.
|
|
2043
2043
|
|
|
2044
2044
|
```ts
|
|
2045
|
-
const model = google.embedding('gemini-embedding-
|
|
2045
|
+
const model = google.embedding('gemini-embedding-2');
|
|
2046
2046
|
```
|
|
2047
2047
|
|
|
2048
2048
|
The Google provider sends API calls to the right endpoint based on the type of embedding:
|
|
@@ -2078,7 +2078,7 @@ import { google, type GoogleEmbeddingModelOptions } from '@ai-sdk/google';
|
|
|
2078
2078
|
import { embedMany } from 'ai';
|
|
2079
2079
|
|
|
2080
2080
|
const { embeddings } = await embedMany({
|
|
2081
|
-
model: google.embedding('gemini-embedding-2
|
|
2081
|
+
model: google.embedding('gemini-embedding-2'),
|
|
2082
2082
|
values: ['sunny day at the beach', 'rainy afternoon in the city'],
|
|
2083
2083
|
providerOptions: {
|
|
2084
2084
|
google: {
|
|
@@ -2132,14 +2132,14 @@ output capabilities through the `:generateContent` API.
|
|
|
2132
2132
|
|
|
2133
2133
|
### Gemini Image Models
|
|
2134
2134
|
|
|
2135
|
-
[Gemini image models](https://ai.google.dev/gemini-api/docs/image-generation) (e.g. `gemini-
|
|
2135
|
+
[Gemini image models](https://ai.google.dev/gemini-api/docs/image-generation) (e.g. `gemini-3.1-flash-image-preview`) are technically multimodal output language models, but they can be used with the `generateImage()` function for a simpler image generation experience. Internally, the provider calls the language model API with `responseModalities: ['IMAGE']`.
|
|
2136
2136
|
|
|
2137
2137
|
```ts
|
|
2138
2138
|
import { google } from '@ai-sdk/google';
|
|
2139
2139
|
import { generateImage } from 'ai';
|
|
2140
2140
|
|
|
2141
2141
|
const { image } = await generateImage({
|
|
2142
|
-
model: google.image('gemini-
|
|
2142
|
+
model: google.image('gemini-3.1-flash-image-preview'),
|
|
2143
2143
|
prompt: 'A photorealistic image of a cat wearing a wizard hat',
|
|
2144
2144
|
aspectRatio: '1:1',
|
|
2145
2145
|
});
|
|
@@ -2155,7 +2155,7 @@ import fs from 'node:fs';
|
|
|
2155
2155
|
const sourceImage = fs.readFileSync('./cat.png');
|
|
2156
2156
|
|
|
2157
2157
|
const { image } = await generateImage({
|
|
2158
|
-
model: google.image('gemini-
|
|
2158
|
+
model: google.image('gemini-3.1-flash-image-preview'),
|
|
2159
2159
|
prompt: {
|
|
2160
2160
|
text: 'Add a small wizard hat to this cat',
|
|
2161
2161
|
images: [sourceImage],
|
|
@@ -2170,7 +2170,7 @@ import { google } from '@ai-sdk/google';
|
|
|
2170
2170
|
import { generateImage } from 'ai';
|
|
2171
2171
|
|
|
2172
2172
|
const { image } = await generateImage({
|
|
2173
|
-
model: google.image('gemini-
|
|
2173
|
+
model: google.image('gemini-3.1-flash-image-preview'),
|
|
2174
2174
|
prompt: {
|
|
2175
2175
|
text: 'Add a small wizard hat to this cat',
|
|
2176
2176
|
images: ['https://example.com/cat.png'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/google",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.82",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@ai-sdk/provider": "4.0.18",
|
|
39
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
39
|
+
"@ai-sdk/provider-utils": "5.0.49"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@ai-sdk/test-server": "2.0.
|
|
42
|
+
"@ai-sdk/test-server": "2.0.2",
|
|
43
43
|
"@types/node": "22.19.19",
|
|
44
44
|
"@vercel/ai-tsconfig": "0.0.0",
|
|
45
45
|
"tsup": "^8.5.1",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
UnsupportedFunctionalityError,
|
|
3
|
+
type JSONValue,
|
|
3
4
|
type LanguageModelV4Prompt,
|
|
4
5
|
type LanguageModelV4ToolResultOutput,
|
|
5
6
|
type SharedV4Warning,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
convertToBase64,
|
|
9
10
|
getTopLevelMediaType,
|
|
10
11
|
isFullMediaType,
|
|
12
|
+
isUrlSupported,
|
|
11
13
|
resolveFullMediaType,
|
|
12
14
|
resolveProviderReference,
|
|
13
15
|
secureJsonParse,
|
|
@@ -51,9 +53,6 @@ function parseBase64DataUrl(
|
|
|
51
53
|
function convertUrlToolResultPart(
|
|
52
54
|
url: string,
|
|
53
55
|
): GoogleFunctionResponsePart | undefined {
|
|
54
|
-
// Per https://ai.google.dev/api/caching#FunctionResponsePart, only inline data is supported.
|
|
55
|
-
// https://docs.cloud.google.com/vertex-ai/generative-ai/docs/model-reference/function-calling#functionresponsepart suggests that this
|
|
56
|
-
// may be different for Vertex, but this needs to be confirmed and further tested for both APIs.
|
|
57
56
|
const parsedDataUrl = parseBase64DataUrl(url);
|
|
58
57
|
if (parsedDataUrl == null) {
|
|
59
58
|
return undefined;
|
|
@@ -67,6 +66,30 @@ function convertUrlToolResultPart(
|
|
|
67
66
|
};
|
|
68
67
|
}
|
|
69
68
|
|
|
69
|
+
function containsJSONSchemaReference(value: JSONValue | undefined): boolean {
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
return value.some(containsJSONSchemaReference);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof value !== 'object' || value === null) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return Object.entries(value).some(
|
|
79
|
+
([key, nestedValue]) =>
|
|
80
|
+
key === '$ref' || containsJSONSchemaReference(nestedValue),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function serializeFunctionResponseContent(
|
|
85
|
+
value: JSONValue,
|
|
86
|
+
): JSONValue | string {
|
|
87
|
+
// Google reserves { $ref: displayName } in structured function responses for
|
|
88
|
+
// multimodal parts. This conflicts with JSON Schema $ref, so serialize the
|
|
89
|
+
// result to preserve it without triggering Google's reference handling.
|
|
90
|
+
return containsJSONSchemaReference(value) ? JSON.stringify(value) : value;
|
|
91
|
+
}
|
|
92
|
+
|
|
70
93
|
/*
|
|
71
94
|
* Appends tool result content parts to the message using the functionResponse
|
|
72
95
|
* format with support for multimodal parts (e.g. inline images/files alongside
|
|
@@ -81,6 +104,7 @@ function appendToolResultParts(
|
|
|
81
104
|
>['value'],
|
|
82
105
|
toolCallId?: string,
|
|
83
106
|
includeFunctionCallIds = true,
|
|
107
|
+
supportedUrls: Record<string, RegExp[]> = {},
|
|
84
108
|
): void {
|
|
85
109
|
const functionResponseParts: GoogleFunctionResponsePart[] = [];
|
|
86
110
|
const responseTextParts: string[] = [];
|
|
@@ -100,12 +124,30 @@ function appendToolResultParts(
|
|
|
100
124
|
},
|
|
101
125
|
});
|
|
102
126
|
} else if (contentPart.data.type === 'url') {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
127
|
+
const url = contentPart.data.url.toString();
|
|
128
|
+
const convertedUrlPart = convertUrlToolResultPart(url);
|
|
129
|
+
const supportedUrl =
|
|
130
|
+
contentPart.data.url.protocol === 'gs:' &&
|
|
131
|
+
contentPart.data.originalUrl != null
|
|
132
|
+
? contentPart.data.originalUrl
|
|
133
|
+
: url;
|
|
134
|
+
|
|
135
|
+
if (convertedUrlPart != null) {
|
|
136
|
+
functionResponseParts.push(convertedUrlPart);
|
|
137
|
+
} else if (
|
|
138
|
+
isFullMediaType(contentPart.mediaType) &&
|
|
139
|
+
isUrlSupported({
|
|
140
|
+
url: supportedUrl,
|
|
141
|
+
mediaType: contentPart.mediaType,
|
|
142
|
+
supportedUrls,
|
|
143
|
+
})
|
|
144
|
+
) {
|
|
145
|
+
functionResponseParts.push({
|
|
146
|
+
fileData: {
|
|
147
|
+
mimeType: contentPart.mediaType,
|
|
148
|
+
fileUri: supportedUrl,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
109
151
|
} else {
|
|
110
152
|
responseTextParts.push(JSON.stringify(contentPart));
|
|
111
153
|
}
|
|
@@ -217,6 +259,7 @@ export function convertToGoogleMessages(
|
|
|
217
259
|
providerOptionsNames?: readonly string[];
|
|
218
260
|
supportsFunctionResponseParts?: boolean;
|
|
219
261
|
includeFunctionCallIds?: boolean;
|
|
262
|
+
supportedFunctionResponseUrls?: Record<string, RegExp[]>;
|
|
220
263
|
},
|
|
221
264
|
): GooglePrompt {
|
|
222
265
|
const systemInstructionParts: Array<{ text: string }> = [];
|
|
@@ -230,6 +273,8 @@ export function convertToGoogleMessages(
|
|
|
230
273
|
const supportsFunctionResponseParts =
|
|
231
274
|
options?.supportsFunctionResponseParts ?? true;
|
|
232
275
|
const includeFunctionCallIds = options?.includeFunctionCallIds ?? true;
|
|
276
|
+
const supportedFunctionResponseUrls =
|
|
277
|
+
options?.supportedFunctionResponseUrls ?? {};
|
|
233
278
|
|
|
234
279
|
let sentinelInjected = false;
|
|
235
280
|
const missingSignatureToolNames: string[] = [];
|
|
@@ -632,6 +677,7 @@ export function convertToGoogleMessages(
|
|
|
632
677
|
output.value,
|
|
633
678
|
part.toolCallId,
|
|
634
679
|
includeFunctionCallIds,
|
|
680
|
+
supportedFunctionResponseUrls,
|
|
635
681
|
);
|
|
636
682
|
} else {
|
|
637
683
|
appendLegacyToolResultParts(
|
|
@@ -654,7 +700,7 @@ export function convertToGoogleMessages(
|
|
|
654
700
|
content:
|
|
655
701
|
output.type === 'execution-denied'
|
|
656
702
|
? (output.reason ?? 'Tool call execution denied.')
|
|
657
|
-
: output.value,
|
|
703
|
+
: serializeFunctionResponseContent(output.value),
|
|
658
704
|
},
|
|
659
705
|
},
|
|
660
706
|
});
|
|
@@ -6,20 +6,23 @@ import {
|
|
|
6
6
|
detectMediaType,
|
|
7
7
|
downloadBlob,
|
|
8
8
|
isFullMediaType,
|
|
9
|
+
isUrlSupported,
|
|
9
10
|
} from '@ai-sdk/provider-utils';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* Download tool result file URLs that Vertex cannot reference directly before
|
|
14
|
+
* converting the prompt to the Google request format.
|
|
14
15
|
*/
|
|
15
16
|
export async function downloadToolResultFiles(
|
|
16
17
|
prompt: LanguageModelV4Prompt,
|
|
17
18
|
{
|
|
18
19
|
abortSignal,
|
|
19
20
|
maxBytes,
|
|
21
|
+
supportedUrls = {},
|
|
20
22
|
}: {
|
|
21
23
|
abortSignal: AbortSignal | undefined;
|
|
22
24
|
maxBytes: number;
|
|
25
|
+
supportedUrls?: Record<string, RegExp[]>;
|
|
23
26
|
},
|
|
24
27
|
): Promise<LanguageModelV4Prompt> {
|
|
25
28
|
const result: LanguageModelV4Prompt = [];
|
|
@@ -36,6 +39,7 @@ export async function downloadToolResultFiles(
|
|
|
36
39
|
output: await downloadToolResultOutput(part.output, {
|
|
37
40
|
abortSignal,
|
|
38
41
|
maxBytes,
|
|
42
|
+
supportedUrls,
|
|
39
43
|
}),
|
|
40
44
|
}
|
|
41
45
|
: part,
|
|
@@ -60,6 +64,7 @@ export async function downloadToolResultFiles(
|
|
|
60
64
|
output: await downloadToolResultOutput(part.output, {
|
|
61
65
|
abortSignal,
|
|
62
66
|
maxBytes,
|
|
67
|
+
supportedUrls,
|
|
63
68
|
}),
|
|
64
69
|
});
|
|
65
70
|
}
|
|
@@ -79,9 +84,11 @@ async function downloadToolResultOutput(
|
|
|
79
84
|
{
|
|
80
85
|
abortSignal,
|
|
81
86
|
maxBytes,
|
|
87
|
+
supportedUrls,
|
|
82
88
|
}: {
|
|
83
89
|
abortSignal: AbortSignal | undefined;
|
|
84
90
|
maxBytes: number;
|
|
91
|
+
supportedUrls: Record<string, RegExp[]>;
|
|
85
92
|
},
|
|
86
93
|
): Promise<LanguageModelV4ToolResultOutput> {
|
|
87
94
|
if (output.type !== 'content') {
|
|
@@ -96,6 +103,17 @@ async function downloadToolResultOutput(
|
|
|
96
103
|
continue;
|
|
97
104
|
}
|
|
98
105
|
|
|
106
|
+
if (
|
|
107
|
+
isUrlSupported({
|
|
108
|
+
url: part.data.url.toString(),
|
|
109
|
+
mediaType: part.mediaType,
|
|
110
|
+
supportedUrls,
|
|
111
|
+
})
|
|
112
|
+
) {
|
|
113
|
+
value.push(part);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
99
117
|
const blob = await downloadBlob(part.data.url.toString(), {
|
|
100
118
|
abortSignal,
|
|
101
119
|
maxBytes,
|
|
@@ -66,6 +66,14 @@ const configurableSafetySettingCategories = [
|
|
|
66
66
|
|
|
67
67
|
const gemini25ModelPattern = /(^|\/)gemini-2\.5(?:[.-]|$)/i;
|
|
68
68
|
|
|
69
|
+
const googleCloudStorageFunctionResponseUrls = {
|
|
70
|
+
'image/png': [/^gs:\/\/.*$/],
|
|
71
|
+
'image/jpeg': [/^gs:\/\/.*$/],
|
|
72
|
+
'image/webp': [/^gs:\/\/.*$/],
|
|
73
|
+
'application/pdf': [/^gs:\/\/.*$/],
|
|
74
|
+
'text/plain': [/^gs:\/\/.*$/],
|
|
75
|
+
} satisfies Record<string, RegExp[]>;
|
|
76
|
+
|
|
69
77
|
export type GoogleLanguageModelConfig = {
|
|
70
78
|
provider: string;
|
|
71
79
|
baseURL: string;
|
|
@@ -83,6 +91,7 @@ export type GoogleLanguageModelConfig = {
|
|
|
83
91
|
*/
|
|
84
92
|
downloadToolResultFiles?: {
|
|
85
93
|
maxBytes: number;
|
|
94
|
+
supportsGoogleCloudStorageUrls?: boolean;
|
|
86
95
|
};
|
|
87
96
|
};
|
|
88
97
|
|
|
@@ -295,11 +304,17 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
295
304
|
}
|
|
296
305
|
|
|
297
306
|
const { usesGemini3Features } = getGoogleModelCapabilities(modelId);
|
|
307
|
+
const supportedFunctionResponseUrls =
|
|
308
|
+
usesGemini3Features &&
|
|
309
|
+
config.downloadToolResultFiles?.supportsGoogleCloudStorageUrls
|
|
310
|
+
? googleCloudStorageFunctionResponseUrls
|
|
311
|
+
: undefined;
|
|
298
312
|
|
|
299
313
|
const promptWithDownloadedToolResultFiles = config.downloadToolResultFiles
|
|
300
314
|
? await downloadToolResultFiles(prompt, {
|
|
301
315
|
abortSignal,
|
|
302
316
|
maxBytes: config.downloadToolResultFiles.maxBytes,
|
|
317
|
+
supportedUrls: supportedFunctionResponseUrls,
|
|
303
318
|
})
|
|
304
319
|
: prompt;
|
|
305
320
|
|
|
@@ -312,6 +327,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
312
327
|
providerOptionsNames,
|
|
313
328
|
supportsFunctionResponseParts: usesGemini3Features,
|
|
314
329
|
includeFunctionCallIds: !isVertexProvider,
|
|
330
|
+
supportedFunctionResponseUrls,
|
|
315
331
|
},
|
|
316
332
|
);
|
|
317
333
|
|
package/src/google-prompt.ts
CHANGED
|
@@ -73,9 +73,13 @@ export type GoogleContentPart =
|
|
|
73
73
|
};
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
export type GoogleFunctionResponsePart =
|
|
77
|
-
|
|
78
|
-
};
|
|
76
|
+
export type GoogleFunctionResponsePart =
|
|
77
|
+
| {
|
|
78
|
+
inlineData: { mimeType: string; data: string };
|
|
79
|
+
}
|
|
80
|
+
| {
|
|
81
|
+
fileData: { mimeType: string; fileUri: string };
|
|
82
|
+
};
|
|
79
83
|
|
|
80
84
|
export type GoogleGroundingMetadata = GroundingMetadataSchema;
|
|
81
85
|
|
|
@@ -401,10 +401,10 @@ function getVideoProcessingField({
|
|
|
401
401
|
processing: {
|
|
402
402
|
type: 'static',
|
|
403
403
|
...(typeof config.startOffset === 'number'
|
|
404
|
-
? { start_offset: config.startOffset }
|
|
404
|
+
? { start_offset: `${config.startOffset}s` }
|
|
405
405
|
: {}),
|
|
406
406
|
...(typeof config.endOffset === 'number'
|
|
407
|
-
? { end_offset: config.endOffset }
|
|
407
|
+
? { end_offset: `${config.endOffset}s` }
|
|
408
408
|
: {}),
|
|
409
409
|
...(typeof config.fps === 'number' ? { fps: config.fps } : {}),
|
|
410
410
|
},
|