@ai-sdk/google 4.0.108 → 4.0.110
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 +26 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +238 -81
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +226 -68
- package/dist/internal/index.js.map +1 -1
- package/docs/15-google.mdx +23 -11
- package/package.json +3 -3
- package/src/convert-to-google-messages.ts +61 -7
- package/src/google-batch.ts +3 -4
- package/src/google-embedding-model.ts +2 -1
- package/src/google-error.ts +37 -0
- package/src/google-files.ts +7 -7
- package/src/google-language-model.ts +43 -92
- package/src/google-model-capabilities.ts +7 -40
- package/src/google-model-catalog.ts +319 -0
- package/src/google-provider.ts +15 -11
- package/src/interactions/cancel-google-interaction.ts +7 -2
- package/src/interactions/google-interactions-language-model.ts +8 -12
- package/src/interactions/poll-google-interactions.ts +4 -2
- package/src/interactions/stream-google-interactions.ts +4 -7
- package/src/realtime/google-realtime-model.ts +7 -3
- package/src/speech-translation/google-speech-translation-model.ts +7 -2
package/docs/15-google.mdx
CHANGED
|
@@ -97,7 +97,11 @@ You can use the following optional settings to customize the Google provider ins
|
|
|
97
97
|
Use a different URL prefix for API calls, e.g. to use proxy servers.
|
|
98
98
|
The default prefix is `https://generativelanguage.googleapis.com/v1beta`.
|
|
99
99
|
|
|
100
|
-
- **apiKey**
|
|
100
|
+
- **apiKey** _string | PromiseLike<string> | (() => string | PromiseLike<string>)_
|
|
101
|
+
|
|
102
|
+
Pass a function to resolve credentials before each request, including when keys
|
|
103
|
+
rotate. The function can return a string or a promise. Credential caching and
|
|
104
|
+
refresh are managed by the function.
|
|
101
105
|
|
|
102
106
|
API key that is being sent using the `x-goog-api-key` header.
|
|
103
107
|
It defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.
|
|
@@ -482,12 +486,12 @@ Things to keep in mind:
|
|
|
482
486
|
|
|
483
487
|
#### Long decode with the Interactions API
|
|
484
488
|
|
|
485
|
-
With the [Interactions API](#interactions-api),
|
|
486
|
-
`background: true
|
|
487
|
-
continuation is needed, and `usage` covers all requests. Set an
|
|
488
|
-
`maxOutputTokens` above 65,536
|
|
489
|
-
|
|
490
|
-
|
|
489
|
+
With the [Interactions API](#interactions-api), the AI SDK currently requires
|
|
490
|
+
`background: true` for long decode. The server runs every request itself, so no
|
|
491
|
+
client-side continuation is needed, and `usage` covers all requests. Set an
|
|
492
|
+
explicit `maxOutputTokens` above 65,536 and keep `store` enabled (the default).
|
|
493
|
+
The provider polls the interaction (or streams it, reconnecting when the
|
|
494
|
+
connection drops) until it finishes.
|
|
491
495
|
|
|
492
496
|
```ts
|
|
493
497
|
import { google } from '@ai-sdk/google';
|
|
@@ -506,10 +510,14 @@ const result = await generateText({
|
|
|
506
510
|
});
|
|
507
511
|
```
|
|
508
512
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
+
Foreground client-side continuation is not yet supported by the AI SDK.
|
|
514
|
+
If you do not set `maxOutputTokens`, non-streaming calls (`generateText`)
|
|
515
|
+
default to 32,768 tokens and streaming calls (`streamText`) to 65,536 tokens.
|
|
516
|
+
An interaction that reaches its limit finishes with `length`. Long decode is
|
|
517
|
+
not available with the Vertex AI Interactions API.
|
|
518
|
+
|
|
519
|
+
The token budget includes both thinking and output tokens. To retain the previous
|
|
520
|
+
64K budget for a non-streaming call, set `maxOutputTokens: 65_536` explicitly.
|
|
513
521
|
|
|
514
522
|
### File Inputs
|
|
515
523
|
|
|
@@ -1265,6 +1273,8 @@ will still validate the response against the schema in `Output.object`.
|
|
|
1265
1273
|
|
|
1266
1274
|
### Model Capabilities
|
|
1267
1275
|
|
|
1276
|
+
{/* model-capabilities:google-language:start */}
|
|
1277
|
+
|
|
1268
1278
|
| Model | Image Input | Object Generation | Tool Usage | Tool Streaming | Google Search | URL Context |
|
|
1269
1279
|
| ------------------------------------- | ----------- | ----------------- | ---------- | -------------- | ------------- | ----------- |
|
|
1270
1280
|
| `gemini-nano-banana-2.1` | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Check /> | <Cross /> |
|
|
@@ -1286,6 +1296,8 @@ will still validate the response against the schema in `Output.object`.
|
|
|
1286
1296
|
| `gemini-2.5-flash-lite-preview-06-17` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
1287
1297
|
| `gemini-2.0-flash` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
1288
1298
|
|
|
1299
|
+
{/* model-capabilities:google-language:end */}
|
|
1300
|
+
|
|
1289
1301
|
<Note>
|
|
1290
1302
|
The table above lists popular models. Please see the [Google Generative AI
|
|
1291
1303
|
docs](https://ai.google.dev/gemini-api/docs/models/) for a full list of
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/google",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.110",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@ai-sdk/provider": "4.0.
|
|
39
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
38
|
+
"@ai-sdk/provider": "4.0.35",
|
|
39
|
+
"@ai-sdk/provider-utils": "5.0.73"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@ai-sdk/test-server": "2.0.4",
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type SharedV4Warning,
|
|
7
7
|
} from '@ai-sdk/provider';
|
|
8
8
|
import {
|
|
9
|
+
stringifyWithBigIntAndCycles,
|
|
9
10
|
convertToBase64,
|
|
10
11
|
getTopLevelMediaType,
|
|
11
12
|
isFullMediaType,
|
|
@@ -67,18 +68,26 @@ function convertUrlToolResultPart(
|
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
function containsJSONSchemaReference(
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
function containsJSONSchemaReference(
|
|
72
|
+
value: JSONValue | undefined,
|
|
73
|
+
seen: WeakSet<object> = new WeakSet(),
|
|
74
|
+
): boolean {
|
|
75
|
+
if (value === null || typeof value !== 'object') {
|
|
76
|
+
return false;
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
if (
|
|
79
|
+
if (seen.has(value)) {
|
|
76
80
|
return false;
|
|
77
81
|
}
|
|
82
|
+
seen.add(value);
|
|
83
|
+
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return value.some(item => containsJSONSchemaReference(item, seen));
|
|
86
|
+
}
|
|
78
87
|
|
|
79
88
|
return Object.entries(value).some(
|
|
80
89
|
([key, nestedValue]) =>
|
|
81
|
-
key === '$ref' || containsJSONSchemaReference(nestedValue),
|
|
90
|
+
key === '$ref' || containsJSONSchemaReference(nestedValue, seen),
|
|
82
91
|
);
|
|
83
92
|
}
|
|
84
93
|
|
|
@@ -88,7 +97,41 @@ function serializeFunctionResponseContent(
|
|
|
88
97
|
// Google reserves { $ref: displayName } in structured function responses for
|
|
89
98
|
// multimodal parts. This conflicts with JSON Schema $ref, so serialize the
|
|
90
99
|
// result to preserve it without triggering Google's reference handling.
|
|
91
|
-
|
|
100
|
+
if (containsJSONSchemaReference(value)) {
|
|
101
|
+
return stringifyWithBigIntAndCycles(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Keep Google's structured response for ordinary JSON, but normalize values
|
|
105
|
+
// that would otherwise fail when the final request body is serialized.
|
|
106
|
+
return containsNonJsonValue(value)
|
|
107
|
+
? secureJsonParse(stringifyWithBigIntAndCycles(value))
|
|
108
|
+
: value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function containsNonJsonValue(
|
|
112
|
+
value: unknown,
|
|
113
|
+
ancestors = new Set<object>(),
|
|
114
|
+
): boolean {
|
|
115
|
+
if (typeof value === 'bigint') {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (typeof value !== 'object' || value === null) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (typeof (value as { toJSON?: unknown }).toJSON === 'function') {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (ancestors.has(value)) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
ancestors.add(value);
|
|
128
|
+
try {
|
|
129
|
+
return Object.values(value).some(item =>
|
|
130
|
+
containsNonJsonValue(item, ancestors),
|
|
131
|
+
);
|
|
132
|
+
} finally {
|
|
133
|
+
ancestors.delete(value);
|
|
134
|
+
}
|
|
92
135
|
}
|
|
93
136
|
|
|
94
137
|
/*
|
|
@@ -586,7 +629,7 @@ export function convertToGoogleMessages(
|
|
|
586
629
|
? { id: part.toolCallId }
|
|
587
630
|
: {}),
|
|
588
631
|
name: part.toolName,
|
|
589
|
-
args: part.input,
|
|
632
|
+
args: parseToolCallInput(part.input),
|
|
590
633
|
},
|
|
591
634
|
thoughtSignature: effectiveThoughtSignature,
|
|
592
635
|
};
|
|
@@ -775,3 +818,14 @@ export function convertToGoogleMessages(
|
|
|
775
818
|
contents,
|
|
776
819
|
};
|
|
777
820
|
}
|
|
821
|
+
|
|
822
|
+
function parseToolCallInput(input: unknown): unknown {
|
|
823
|
+
if (typeof input !== 'string') {
|
|
824
|
+
return input;
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
return secureJsonParse(input);
|
|
828
|
+
} catch {
|
|
829
|
+
return input;
|
|
830
|
+
}
|
|
831
|
+
}
|
package/src/google-batch.ts
CHANGED
|
@@ -292,7 +292,6 @@ export class GoogleBatch implements BatchV4<GoogleBatchModelIds> {
|
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
const headers = await this.getHeaders(options.headers);
|
|
296
295
|
const createUrl = `${this.batchConfig.baseURL}/${getModelPath(
|
|
297
296
|
modelId,
|
|
298
297
|
)}:batchGenerateContent`;
|
|
@@ -300,7 +299,7 @@ export class GoogleBatch implements BatchV4<GoogleBatchModelIds> {
|
|
|
300
299
|
if (fileParts == null) {
|
|
301
300
|
const { value: operation } = await postJsonToApi({
|
|
302
301
|
url: createUrl,
|
|
303
|
-
headers,
|
|
302
|
+
headers: await this.getHeaders(options.headers),
|
|
304
303
|
body: inlineBatchBody,
|
|
305
304
|
failedResponseHandler: googleFailedResponseHandler,
|
|
306
305
|
successfulResponseHandler: createJsonResponseHandler(
|
|
@@ -329,7 +328,7 @@ export class GoogleBatch implements BatchV4<GoogleBatchModelIds> {
|
|
|
329
328
|
|
|
330
329
|
const { value: uploadUrl } = await postJsonToApi({
|
|
331
330
|
url: `${this.getBaseOrigin()}/upload/v1beta/files`,
|
|
332
|
-
headers: combineHeaders(headers, {
|
|
331
|
+
headers: combineHeaders(await this.getHeaders(options.headers), {
|
|
333
332
|
'X-Goog-Upload-Protocol': 'resumable',
|
|
334
333
|
'X-Goog-Upload-Command': 'start',
|
|
335
334
|
'X-Goog-Upload-Header-Content-Length': String(inputFile.size),
|
|
@@ -370,7 +369,7 @@ export class GoogleBatch implements BatchV4<GoogleBatchModelIds> {
|
|
|
370
369
|
|
|
371
370
|
const { value: operation } = await postJsonToApi({
|
|
372
371
|
url: createUrl,
|
|
373
|
-
headers,
|
|
372
|
+
headers: await this.getHeaders(options.headers),
|
|
374
373
|
body: {
|
|
375
374
|
batch: {
|
|
376
375
|
displayName,
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type EmbeddingModelV4,
|
|
4
4
|
} from '@ai-sdk/provider';
|
|
5
5
|
import {
|
|
6
|
+
type Resolvable,
|
|
6
7
|
combineHeaders,
|
|
7
8
|
createJsonResponseHandler,
|
|
8
9
|
type EmbeddingModelProviderOptionsTransformer,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
type GoogleEmbeddingConfig = {
|
|
27
28
|
provider: string;
|
|
28
29
|
baseURL: string;
|
|
29
|
-
headers?:
|
|
30
|
+
headers?: Resolvable<Record<string, string | undefined>>;
|
|
30
31
|
fetch?: FetchFunction;
|
|
31
32
|
};
|
|
32
33
|
|
package/src/google-error.ts
CHANGED
|
@@ -28,3 +28,40 @@ export const googleFailedResponseHandler = createJsonErrorResponseHandler({
|
|
|
28
28
|
errorSchema: googleErrorDataSchema,
|
|
29
29
|
errorToMessage: data => data.error.message,
|
|
30
30
|
});
|
|
31
|
+
|
|
32
|
+
// Google RPC status names can be sent without a numeric HTTP error code.
|
|
33
|
+
export function mapGoogleErrorStatusToHttpStatusCode(
|
|
34
|
+
status: string,
|
|
35
|
+
): number | undefined {
|
|
36
|
+
switch (status) {
|
|
37
|
+
case 'CANCELLED':
|
|
38
|
+
return 499;
|
|
39
|
+
case 'INVALID_ARGUMENT':
|
|
40
|
+
case 'FAILED_PRECONDITION':
|
|
41
|
+
case 'OUT_OF_RANGE':
|
|
42
|
+
return 400;
|
|
43
|
+
case 'DEADLINE_EXCEEDED':
|
|
44
|
+
return 504;
|
|
45
|
+
case 'NOT_FOUND':
|
|
46
|
+
return 404;
|
|
47
|
+
case 'ALREADY_EXISTS':
|
|
48
|
+
case 'ABORTED':
|
|
49
|
+
return 409;
|
|
50
|
+
case 'PERMISSION_DENIED':
|
|
51
|
+
return 403;
|
|
52
|
+
case 'UNAUTHENTICATED':
|
|
53
|
+
return 401;
|
|
54
|
+
case 'RESOURCE_EXHAUSTED':
|
|
55
|
+
return 429;
|
|
56
|
+
case 'UNIMPLEMENTED':
|
|
57
|
+
return 501;
|
|
58
|
+
case 'UNKNOWN':
|
|
59
|
+
case 'INTERNAL':
|
|
60
|
+
case 'DATA_LOSS':
|
|
61
|
+
return 500;
|
|
62
|
+
case 'UNAVAILABLE':
|
|
63
|
+
return 503;
|
|
64
|
+
default:
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/google-files.ts
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
type SharedV4Warning,
|
|
7
7
|
} from '@ai-sdk/provider';
|
|
8
8
|
import {
|
|
9
|
+
resolve,
|
|
10
|
+
type Resolvable,
|
|
9
11
|
combineHeaders,
|
|
10
12
|
convertInlineFileDataToUint8Array,
|
|
11
13
|
createJsonResponseHandler,
|
|
@@ -31,7 +33,7 @@ export type GoogleFilesUploadOptions = {
|
|
|
31
33
|
interface GoogleFilesConfig {
|
|
32
34
|
provider: string;
|
|
33
35
|
baseURL: string;
|
|
34
|
-
headers:
|
|
36
|
+
headers: Resolvable<Record<string, string | undefined>>;
|
|
35
37
|
fetch?: FetchFunction;
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -53,10 +55,8 @@ export class GoogleFiles implements FilesV4 {
|
|
|
53
55
|
schema: googleFilesUploadOptionsSchema,
|
|
54
56
|
})) as GoogleFilesUploadOptions | undefined;
|
|
55
57
|
|
|
56
|
-
const
|
|
57
|
-
this.config.headers
|
|
58
|
-
options.headers,
|
|
59
|
-
);
|
|
58
|
+
const getHeaders = async () =>
|
|
59
|
+
combineHeaders(await resolve(this.config.headers), options.headers);
|
|
60
60
|
const fetchFn = this.config.fetch ?? globalThis.fetch;
|
|
61
61
|
|
|
62
62
|
const warnings: Array<SharedV4Warning> = [];
|
|
@@ -74,7 +74,7 @@ export class GoogleFiles implements FilesV4 {
|
|
|
74
74
|
const initResponse = await fetchFn(`${baseOrigin}/upload/v1beta/files`, {
|
|
75
75
|
method: 'POST',
|
|
76
76
|
headers: {
|
|
77
|
-
...
|
|
77
|
+
...(await getHeaders()),
|
|
78
78
|
'X-Goog-Upload-Protocol': 'resumable',
|
|
79
79
|
'X-Goog-Upload-Command': 'start',
|
|
80
80
|
'X-Goog-Upload-Header-Content-Length': String(fileBytes.length),
|
|
@@ -152,7 +152,7 @@ export class GoogleFiles implements FilesV4 {
|
|
|
152
152
|
const { value: fileStatus } = await getFromApi({
|
|
153
153
|
url: `${this.config.baseURL}/${filePath}`,
|
|
154
154
|
validateUrl: false,
|
|
155
|
-
headers:
|
|
155
|
+
headers: await getHeaders(),
|
|
156
156
|
successfulResponseHandler: createJsonResponseHandler(
|
|
157
157
|
googleFileResponseSchema,
|
|
158
158
|
),
|
|
@@ -51,6 +51,7 @@ import { getModelPath } from './get-model-path';
|
|
|
51
51
|
import {
|
|
52
52
|
getGoogleErrorSchema,
|
|
53
53
|
googleFailedResponseHandler,
|
|
54
|
+
mapGoogleErrorStatusToHttpStatusCode,
|
|
54
55
|
} from './google-error';
|
|
55
56
|
import { sanitizeResponseJsonSchema } from './sanitize-response-json-schema';
|
|
56
57
|
import {
|
|
@@ -58,7 +59,7 @@ import {
|
|
|
58
59
|
type GoogleLanguageModelOptions,
|
|
59
60
|
type GoogleModelId,
|
|
60
61
|
} from './google-language-model-options';
|
|
61
|
-
import {
|
|
62
|
+
import { getGoogleLanguageModelEntry } from './google-model-catalog';
|
|
62
63
|
import type { GoogleProviderMetadata } from './google-prompt';
|
|
63
64
|
import { prepareTools } from './google-prepare-tools';
|
|
64
65
|
import {
|
|
@@ -74,8 +75,6 @@ const configurableSafetySettingCategories = [
|
|
|
74
75
|
'HARM_CATEGORY_SEXUALLY_EXPLICIT',
|
|
75
76
|
] as const;
|
|
76
77
|
|
|
77
|
-
const gemini25ModelPattern = /(^|[/.])gemini-2\.5(?:[.-]|$)/i;
|
|
78
|
-
|
|
79
78
|
const googleCloudStorageFunctionResponseUrls = {
|
|
80
79
|
'image/png': [/^gs:\/\/.*$/],
|
|
81
80
|
'image/jpeg': [/^gs:\/\/.*$/],
|
|
@@ -296,9 +295,12 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
296
295
|
}
|
|
297
296
|
}
|
|
298
297
|
|
|
299
|
-
const
|
|
298
|
+
const entry = getGoogleLanguageModelEntry(modelId);
|
|
299
|
+
const isGemmaModel =
|
|
300
|
+
entry.requestConfig.language.systemInstruction === 'user';
|
|
300
301
|
const isGemini25DeveloperApiModel =
|
|
301
|
-
!isVertexProvider &&
|
|
302
|
+
!isVertexProvider &&
|
|
303
|
+
entry.capabilities.language.sampling.penalties !== true;
|
|
302
304
|
|
|
303
305
|
if (isGemini25DeveloperApiModel && frequencyPenalty != null) {
|
|
304
306
|
warnings.push({
|
|
@@ -313,7 +315,8 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
313
315
|
});
|
|
314
316
|
}
|
|
315
317
|
|
|
316
|
-
const
|
|
318
|
+
const usesGemini3Features =
|
|
319
|
+
entry.capabilities.language.tools.functionResponseParts;
|
|
317
320
|
const supportedFunctionResponseUrls =
|
|
318
321
|
usesGemini3Features &&
|
|
319
322
|
config.downloadToolResultFiles?.supportsGoogleCloudStorageUrls
|
|
@@ -466,6 +469,8 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
466
469
|
{ isStreaming = false }: { isStreaming?: boolean } = {},
|
|
467
470
|
) {
|
|
468
471
|
const { responseFormat, tools, toolChoice } = options;
|
|
472
|
+
const entry = getGoogleLanguageModelEntry(this.modelId);
|
|
473
|
+
const { structuredOutput } = entry.capabilities.language;
|
|
469
474
|
let jsonResponseTool: LanguageModelV4FunctionTool | undefined;
|
|
470
475
|
// Gemini models before Gemini 3 reject JSON response formatting together
|
|
471
476
|
// with function tools. Gemini 3 supports the combination only with automatic
|
|
@@ -474,8 +479,8 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
474
479
|
responseFormat?.type === 'json' &&
|
|
475
480
|
responseFormat.schema != null &&
|
|
476
481
|
tools?.some(tool => tool.type === 'function') &&
|
|
477
|
-
|
|
478
|
-
(!
|
|
482
|
+
entry.requestConfig.language.jsonResponseTool &&
|
|
483
|
+
(!structuredOutput.withFunctionTools ||
|
|
479
484
|
toolChoice?.type === 'required' ||
|
|
480
485
|
toolChoice?.type === 'tool')
|
|
481
486
|
) {
|
|
@@ -754,18 +759,16 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
754
759
|
jsonResponseToolName,
|
|
755
760
|
} = await this.getArgs(options);
|
|
756
761
|
|
|
757
|
-
const
|
|
758
|
-
this.config.headers ? await resolve(this.config.headers) : undefined,
|
|
759
|
-
options.headers,
|
|
760
|
-
extraHeaders,
|
|
761
|
-
);
|
|
762
|
-
|
|
763
|
-
const generateContent = (body: typeof args) =>
|
|
762
|
+
const generateContent = async (body: typeof args) =>
|
|
764
763
|
postJsonToApi({
|
|
765
764
|
url: `${this.config.baseURL}/${getModelPath(
|
|
766
765
|
this.modelId,
|
|
767
766
|
)}:generateContent`,
|
|
768
|
-
headers:
|
|
767
|
+
headers: combineHeaders(
|
|
768
|
+
await resolve(this.config.headers),
|
|
769
|
+
options.headers,
|
|
770
|
+
extraHeaders,
|
|
771
|
+
),
|
|
769
772
|
body,
|
|
770
773
|
failedResponseHandler: googleFailedResponseHandler,
|
|
771
774
|
successfulResponseHandler: createJsonResponseHandler(responseSchema),
|
|
@@ -875,18 +878,16 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
875
878
|
providerOptionsNames.map(name => [name, payload]),
|
|
876
879
|
) as SharedV4ProviderMetadata;
|
|
877
880
|
|
|
878
|
-
const
|
|
879
|
-
this.config.headers ? await resolve(this.config.headers) : undefined,
|
|
880
|
-
options.headers,
|
|
881
|
-
extraHeaders,
|
|
882
|
-
);
|
|
883
|
-
|
|
884
|
-
const streamGenerateContent = (body: typeof args) =>
|
|
881
|
+
const streamGenerateContent = async (body: typeof args) =>
|
|
885
882
|
postJsonToApi({
|
|
886
883
|
url: `${this.config.baseURL}/${getModelPath(
|
|
887
884
|
this.modelId,
|
|
888
885
|
)}:streamGenerateContent?alt=sse`,
|
|
889
|
-
headers
|
|
886
|
+
headers: combineHeaders(
|
|
887
|
+
await resolve(this.config.headers),
|
|
888
|
+
options.headers,
|
|
889
|
+
extraHeaders,
|
|
890
|
+
),
|
|
890
891
|
body,
|
|
891
892
|
failedResponseHandler: googleFailedResponseHandler,
|
|
892
893
|
successfulResponseHandler:
|
|
@@ -1040,19 +1041,22 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
1040
1041
|
if (value.error != null) {
|
|
1041
1042
|
finishReason = { unified: 'error', raw: undefined };
|
|
1042
1043
|
const error = value.error;
|
|
1044
|
+
const statusCode =
|
|
1045
|
+
error.code ??
|
|
1046
|
+
mapGoogleErrorStatusToHttpStatusCode(error.status);
|
|
1043
1047
|
controller.enqueue({
|
|
1044
1048
|
type: 'error',
|
|
1045
1049
|
error: createProviderStreamError({
|
|
1046
1050
|
message: error.message,
|
|
1047
1051
|
type: error.status,
|
|
1048
1052
|
code: error.code ?? undefined,
|
|
1049
|
-
statusCode
|
|
1053
|
+
statusCode,
|
|
1050
1054
|
isRetryable:
|
|
1051
|
-
|
|
1052
|
-
(
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1055
|
+
statusCode != null &&
|
|
1056
|
+
(statusCode === 408 ||
|
|
1057
|
+
statusCode === 409 ||
|
|
1058
|
+
statusCode === 429 ||
|
|
1059
|
+
statusCode >= 500),
|
|
1056
1060
|
data: error,
|
|
1057
1061
|
}),
|
|
1058
1062
|
});
|
|
@@ -1571,27 +1575,6 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
1571
1575
|
}
|
|
1572
1576
|
}
|
|
1573
1577
|
|
|
1574
|
-
function getMaxOutputTokensForGemini25Model(): number {
|
|
1575
|
-
return 65536;
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
function getMaxThinkingTokensForGemini25Model(modelId: string): number {
|
|
1579
|
-
const id = modelId.toLowerCase();
|
|
1580
|
-
if (id.includes('2.5-pro') || id.includes('gemini-3-pro-image')) {
|
|
1581
|
-
return 32768;
|
|
1582
|
-
}
|
|
1583
|
-
return 24576;
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
/**
|
|
1587
|
-
* Gemini 2.5 Pro cannot disable thinking and requires a thinking budget of at
|
|
1588
|
-
* least 128 tokens. Other Gemini 2.5 models can disable thinking with a budget
|
|
1589
|
-
* of 0.
|
|
1590
|
-
*/
|
|
1591
|
-
function getMinThinkingTokensForGemini25Model(modelId: string): number {
|
|
1592
|
-
return modelId.toLowerCase().includes('2.5-pro') ? 128 : 0;
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
1578
|
type GoogleThinkingConfig = NonNullable<
|
|
1596
1579
|
InferSchema<typeof googleLanguageModelOptions>['thinkingConfig']
|
|
1597
1580
|
>;
|
|
@@ -1610,8 +1593,8 @@ function resolveThinkingConfig({
|
|
|
1610
1593
|
}
|
|
1611
1594
|
|
|
1612
1595
|
if (
|
|
1613
|
-
|
|
1614
|
-
|
|
1596
|
+
getGoogleLanguageModelEntry(modelId).requestConfig.language
|
|
1597
|
+
.thinkingFormat === 'level'
|
|
1615
1598
|
) {
|
|
1616
1599
|
return resolveGemini3ThinkingConfig({ reasoning, modelId, warnings });
|
|
1617
1600
|
}
|
|
@@ -1631,7 +1614,9 @@ function resolveGemini3ThinkingConfig({
|
|
|
1631
1614
|
modelId: string;
|
|
1632
1615
|
warnings: SharedV4Warning[];
|
|
1633
1616
|
}): Pick<GoogleThinkingConfig, 'thinkingLevel'> | undefined {
|
|
1634
|
-
const minimumThinkingLevel =
|
|
1617
|
+
const minimumThinkingLevel =
|
|
1618
|
+
getGoogleLanguageModelEntry(modelId).capabilities.language.reasoning
|
|
1619
|
+
.efforts?.[0];
|
|
1635
1620
|
|
|
1636
1621
|
// It's not possible to fully disable thinking with Gemini 3, so reasoning
|
|
1637
1622
|
// 'none' maps to the lowest supported thinking level.
|
|
@@ -1655,41 +1640,6 @@ function resolveGemini3ThinkingConfig({
|
|
|
1655
1640
|
return { thinkingLevel };
|
|
1656
1641
|
}
|
|
1657
1642
|
|
|
1658
|
-
function getMinimumThinkingLevelForGemini3Model(
|
|
1659
|
-
modelId: string,
|
|
1660
|
-
): 'minimal' | 'low' {
|
|
1661
|
-
const modelName = /(?:^|[/.])(gemini-[^/]*)$/i
|
|
1662
|
-
.exec(modelId)?.[1]
|
|
1663
|
-
.toLowerCase();
|
|
1664
|
-
|
|
1665
|
-
if (modelName === 'gemini-flash-latest') {
|
|
1666
|
-
return 'low';
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
const proVersionMatch = /^gemini-(\d+)(?:\.\d+)?-pro(?:$|-)/.exec(
|
|
1670
|
-
modelName ?? '',
|
|
1671
|
-
);
|
|
1672
|
-
|
|
1673
|
-
if (Number(proVersionMatch?.[1]) >= 3) {
|
|
1674
|
-
return 'low';
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
const versionMatch = /^gemini-(\d+)\.(\d+)-flash(?:$|-(?!lite(?:-|$)))/.exec(
|
|
1678
|
-
modelName ?? '',
|
|
1679
|
-
);
|
|
1680
|
-
|
|
1681
|
-
if (versionMatch == null) {
|
|
1682
|
-
return 'minimal';
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
const majorVersion = Number(versionMatch[1]);
|
|
1686
|
-
const minorVersion = Number(versionMatch[2]);
|
|
1687
|
-
|
|
1688
|
-
return majorVersion > 3 || (majorVersion === 3 && minorVersion >= 7)
|
|
1689
|
-
? 'low'
|
|
1690
|
-
: 'minimal';
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
1643
|
function resolveGemini25ThinkingConfig({
|
|
1694
1644
|
reasoning,
|
|
1695
1645
|
modelId,
|
|
@@ -1702,7 +1652,8 @@ function resolveGemini25ThinkingConfig({
|
|
|
1702
1652
|
modelId: string;
|
|
1703
1653
|
warnings: SharedV4Warning[];
|
|
1704
1654
|
}): Pick<GoogleThinkingConfig, 'thinkingBudget'> {
|
|
1705
|
-
const
|
|
1655
|
+
const entry = getGoogleLanguageModelEntry(modelId);
|
|
1656
|
+
const minThinkingBudget = entry.capabilities.language.reasoning.minBudget;
|
|
1706
1657
|
|
|
1707
1658
|
if (reasoning === 'none' && minThinkingBudget === 0) {
|
|
1708
1659
|
return { thinkingBudget: 0 };
|
|
@@ -1710,8 +1661,8 @@ function resolveGemini25ThinkingConfig({
|
|
|
1710
1661
|
|
|
1711
1662
|
const thinkingBudget = mapReasoningToProviderBudget({
|
|
1712
1663
|
reasoning,
|
|
1713
|
-
maxOutputTokens:
|
|
1714
|
-
maxReasoningBudget:
|
|
1664
|
+
maxOutputTokens: entry.requestConfig.language.reasoningMaxOutputTokens,
|
|
1665
|
+
maxReasoningBudget: entry.capabilities.language.reasoning.maxBudget,
|
|
1715
1666
|
minReasoningBudget: minThinkingBudget,
|
|
1716
1667
|
warnings,
|
|
1717
1668
|
});
|
|
@@ -1,44 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
supportsGemini2Tools: boolean;
|
|
3
|
-
supportsFileSearch: boolean;
|
|
4
|
-
usesGemini3Features: boolean;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
const gemini1ModelPattern = /(^|[/.])gemini-1(?:[.-]|$)/i;
|
|
8
|
-
const gemini2ModelPattern = /(^|[/.])gemini-2(?:[.-]|$)/i;
|
|
9
|
-
const gemini25ModelPattern = /(^|[/.])gemini-2\.5(?:[.-]|$)/i;
|
|
10
|
-
const geminiModelPattern = /(^|[/.])gemini-/i;
|
|
11
|
-
|
|
12
|
-
function isKnownPreGemini2Model(modelId: string): boolean {
|
|
13
|
-
return (
|
|
14
|
-
gemini1ModelPattern.test(modelId) ||
|
|
15
|
-
/(^|[/.])gemini-pro(?:-vision)?$/i.test(modelId) ||
|
|
16
|
-
/(^|[/.])gemini-robotics-er-1\.5(?:[.-]|$)/i.test(modelId)
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Classifies Gemini capabilities by excluding known older generations.
|
|
22
|
-
*
|
|
23
|
-
* Google model IDs are open-ended, so unrecognized Gemini IDs and aliases
|
|
24
|
-
* intentionally inherit the newest supported behavior. Add exceptions here
|
|
25
|
-
* only when a model is known to require a legacy request shape.
|
|
26
|
-
*/
|
|
27
|
-
export function getGoogleModelCapabilities(
|
|
28
|
-
modelId: string,
|
|
29
|
-
): GoogleModelCapabilities {
|
|
30
|
-
const isGeminiModel = geminiModelPattern.test(modelId);
|
|
31
|
-
const isGemini2Model = gemini2ModelPattern.test(modelId);
|
|
32
|
-
const isKnownPreGemini2 = isKnownPreGemini2Model(modelId);
|
|
33
|
-
const isKnownOlderModel = isKnownPreGemini2 || isGemini2Model;
|
|
34
|
-
const usesGemini3Features = isGeminiModel && !isKnownOlderModel;
|
|
1
|
+
import { getGoogleLanguageModelEntry } from './google-model-catalog';
|
|
35
2
|
|
|
3
|
+
/** Compatibility projection of the resolved catalog for existing request code. */
|
|
4
|
+
export function getGoogleModelCapabilities(modelId: string) {
|
|
5
|
+
const { tools } = getGoogleLanguageModelEntry(modelId).capabilities.language;
|
|
36
6
|
return {
|
|
37
|
-
supportsGemini2Tools:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
supportsFileSearch:
|
|
41
|
-
gemini25ModelPattern.test(modelId) || usesGemini3Features,
|
|
42
|
-
usesGemini3Features,
|
|
7
|
+
supportsGemini2Tools: tools.gemini2,
|
|
8
|
+
supportsFileSearch: tools.fileSearch,
|
|
9
|
+
usesGemini3Features: tools.mixed,
|
|
43
10
|
};
|
|
44
11
|
}
|