@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.
@@ -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** _string_
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), long decode requires
486
- `background: true`. The server runs every request itself, so no client-side
487
- continuation is needed, and `usage` covers all requests. Set an explicit
488
- `maxOutputTokens` above 65,536 (without it, you get a single 64K request) and
489
- keep `store` enabled (the default). The provider polls the interaction (or
490
- streams it, reconnecting when the connection drops) until it finishes.
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
- Without `background: true`, an interaction generates at most 65,536 tokens.
510
- Requests with a higher `maxOutputTokens` are rejected by the API, and an
511
- interaction that reaches the cap finishes with `length`. Long decode is not
512
- available with the Vertex AI Interactions API.
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.108",
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.34",
39
- "@ai-sdk/provider-utils": "5.0.71"
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(value: JSONValue | undefined): boolean {
71
- if (Array.isArray(value)) {
72
- return value.some(containsJSONSchemaReference);
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 (typeof value !== 'object' || value === null) {
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
- return containsJSONSchemaReference(value) ? JSON.stringify(value) : value;
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
+ }
@@ -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?: () => Record<string, string | undefined>;
30
+ headers?: Resolvable<Record<string, string | undefined>>;
30
31
  fetch?: FetchFunction;
31
32
  };
32
33
 
@@ -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
+ }
@@ -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: () => Record<string, string | undefined>;
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 resolvedHeaders = combineHeaders(
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
- ...resolvedHeaders,
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: combineHeaders(resolvedHeaders),
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 { getGoogleModelCapabilities } from './google-model-capabilities';
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 isGemmaModel = modelId.toLowerCase().startsWith('gemma-');
298
+ const entry = getGoogleLanguageModelEntry(modelId);
299
+ const isGemmaModel =
300
+ entry.requestConfig.language.systemInstruction === 'user';
300
301
  const isGemini25DeveloperApiModel =
301
- !isVertexProvider && gemini25ModelPattern.test(modelId);
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 { usesGemini3Features } = getGoogleModelCapabilities(modelId);
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
- /(^|\/)gemini-/i.test(this.modelId) &&
478
- (!getGoogleModelCapabilities(this.modelId).usesGemini3Features ||
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 mergedHeaders = combineHeaders(
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: mergedHeaders,
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 headers = combineHeaders(
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: error.code ?? undefined,
1053
+ statusCode,
1050
1054
  isRetryable:
1051
- error.code != null &&
1052
- (error.code === 408 ||
1053
- error.code === 409 ||
1054
- error.code === 429 ||
1055
- error.code >= 500),
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
- getGoogleModelCapabilities(modelId).usesGemini3Features &&
1614
- !modelId.includes('gemini-3-pro-image')
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 = getMinimumThinkingLevelForGemini3Model(modelId);
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 minThinkingBudget = getMinThinkingTokensForGemini25Model(modelId);
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: getMaxOutputTokensForGemini25Model(),
1714
- maxReasoningBudget: getMaxThinkingTokensForGemini25Model(modelId),
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
- type GoogleModelCapabilities = {
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
- (isGeminiModel && !isKnownPreGemini2) ||
39
- modelId.toLowerCase().includes('nano-banana'),
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
  }