@ai-sdk/google 4.0.94 → 4.0.95

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.
@@ -253,6 +253,12 @@ The following optional provider options are available for Google models:
253
253
  [Priority inference](https://ai.google.dev/gemini-api/docs/priority-inference)
254
254
  and [Flex inference](https://ai.google.dev/gemini-api/docs/flex-inference).
255
255
 
256
+ - **continuationToken** _string_
257
+
258
+ Optional. Resumes a response that stopped because it used up its
259
+ `maxOutputTokens` budget (Gemini 4 long decode). Send it with the original
260
+ request and a higher `maxOutputTokens`. See [Long decode](#long-decode).
261
+
256
262
  - **threshold** _string_
257
263
 
258
264
  Optional. Standalone threshold setting that can be used independently of `safetySettings`.
@@ -318,6 +324,136 @@ console.log(text);
318
324
  console.log(reasoning); // Reasoning summary
319
325
  ```
320
326
 
327
+ ### Long decode
328
+
329
+ Gemini 4 models such as `gemini-4-argon` can keep thinking and writing past a
330
+ single request's limits, up to the shared 1M token context window (input,
331
+ thinking, and output tokens together). Use it for hard math, science, proofs,
332
+ and long coding or agentic tasks.
333
+
334
+ - A single request generates at most 32,768 tokens without streaming and 65,536
335
+ tokens with streaming (`streamText`). Thinking tokens count towards this cap.
336
+ - `maxOutputTokens` is a **cumulative budget across requests**, not a target
337
+ length. When a request reaches the per-request cap while budget remains, the
338
+ provider automatically sends the same request again with a continuation token
339
+ and combines the results into one response. Set `maxOutputTokens` above the
340
+ per-request cap (up to 1M minus the input tokens) to enable this. If you do
341
+ not set it, the call ends after one request.
342
+ - A lower `maxOutputTokens` does not make the model think less, it ends the
343
+ response early (possibly before any answer) and you are still charged for the
344
+ tokens. Use `thinkingConfig.thinkingLevel` to control how much the model
345
+ thinks. `thinkingBudget` is not supported by these models.
346
+ - Complex requests can run for several minutes. Make sure your HTTP client
347
+ allows read timeouts of at least 15 to 20 minutes.
348
+
349
+ **Usage across requests.** Each continued request re-sends the prompt plus all
350
+ output so far as input, so its prompt token count includes everything
351
+ generated before it. `usage` is the sum of all requests, which matches what
352
+ Google bills (re-sent tokens are billed at a discount through implicit
353
+ caching). Do not use the summed input tokens for context window checks or
354
+ compaction: they can be several times larger than the real context. When a
355
+ response spanned more than one request,
356
+ `providerMetadata.google.initialPromptTokenCount` holds the prompt token count
357
+ of the first request (the google-genai SDKs expose the same value as
358
+ `initial_prompt_token_count`):
359
+
360
+ ```ts
361
+ const result = await generateText({
362
+ model: google('gemini-4-argon'),
363
+ prompt,
364
+ maxOutputTokens: 500_000,
365
+ });
366
+
367
+ // Billing: summed across all requests
368
+ console.log(result.usage.inputTokens.total);
369
+
370
+ // Context window: the first request's prompt plus the generated output
371
+ const promptTokens =
372
+ result.finalStep.providerMetadata?.google?.initialPromptTokenCount ??
373
+ result.usage.inputTokens.total;
374
+ const contextTokens = promptTokens + (result.usage.outputTokens.total ?? 0);
375
+ ```
376
+
377
+ **Resuming after the budget is used up.** When the cumulative generated tokens
378
+ reach `maxOutputTokens`, the finish reason is `length` and the provider returns
379
+ a continuation token on `result.providerMetadata.google.continuationToken`. To
380
+ continue, send the **original request unchanged** (same prompt, tools, and
381
+ settings; do not append the partial output as a message) together with the
382
+ token as `providerOptions.google.continuationToken` and a **higher**
383
+ `maxOutputTokens`. Resending with the same `maxOutputTokens` fails because the
384
+ budget is used up. Each response contains only the newly generated content.
385
+
386
+ ```ts
387
+ import { google } from '@ai-sdk/google';
388
+ import { generateText } from 'ai';
389
+
390
+ const prompt = 'Prove that sqrt(2) is irrational, in detail.';
391
+
392
+ const result = await generateText({
393
+ model: google('gemini-4-argon'),
394
+ prompt,
395
+ maxOutputTokens: 128_000,
396
+ });
397
+
398
+ const continuationToken =
399
+ result.finalStep.providerMetadata?.google?.continuationToken;
400
+
401
+ if (result.finishReason === 'length' && typeof continuationToken === 'string') {
402
+ // The budget was used up. Raise it to keep going.
403
+ const continued = await generateText({
404
+ model: google('gemini-4-argon'),
405
+ prompt,
406
+ maxOutputTokens: 256_000,
407
+ providerOptions: { google: { continuationToken } },
408
+ });
409
+
410
+ console.log(result.text + continued.text);
411
+ }
412
+ ```
413
+
414
+ Things to keep in mind:
415
+
416
+ - **Dropped streams.** Streamed parts are forwarded as they arrive and are not
417
+ buffered between the server's checkpoints, so a stream that drops mid-request
418
+ is not resumed automatically. Retry the request from the start.
419
+ - **Provider metadata is per call.** If you store the conversation history,
420
+ combine the content of all calls into one model message only after the final
421
+ call finishes. Keep thought signatures unchanged, including the ones on empty
422
+ trailing text parts.
423
+ - **Zero Data Retention.** Continuation works with `store: false` because the
424
+ state travels encrypted inside the token.
425
+
426
+ #### Long decode with the Interactions API
427
+
428
+ With the [Interactions API](#interactions-api), long decode requires
429
+ `background: true`. The server runs every request itself, so no client-side
430
+ continuation is needed, and `usage` covers all requests. Set an explicit
431
+ `maxOutputTokens` above 65,536 (without it, you get a single 64K request) and
432
+ keep `store` enabled (the default). The provider polls the interaction (or
433
+ streams it, reconnecting when the connection drops) until it finishes.
434
+
435
+ ```ts
436
+ import { google } from '@ai-sdk/google';
437
+ import { generateText } from 'ai';
438
+
439
+ const result = await generateText({
440
+ model: google.interactions('gemini-4-argon'),
441
+ prompt: 'Prove that sqrt(2) is irrational, in detail.',
442
+ maxOutputTokens: 500_000,
443
+ providerOptions: {
444
+ google: {
445
+ background: true,
446
+ pollingTimeoutMs: 60 * 60 * 1000, // default: 30 minutes
447
+ },
448
+ },
449
+ });
450
+ ```
451
+
452
+ Without `background: true`, an interaction generates at most 65,536 tokens.
453
+ Requests with a higher `maxOutputTokens` are rejected by the API, and an
454
+ interaction that reaches the cap finishes with `length`. Long decode is not
455
+ available with the Vertex AI Interactions API.
456
+
321
457
  ### File Inputs
322
458
 
323
459
  The Google provider supports file inputs, e.g. PDF files.
@@ -1059,6 +1195,7 @@ will still validate the response against the schema in `Output.object`.
1059
1195
  | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | Google Search | URL Context |
1060
1196
  | ------------------------------------- | ----------- | ----------------- | ---------- | -------------- | ------------- | ----------- |
1061
1197
  | `gemini-nano-banana-2.1` | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Check /> | <Cross /> |
1198
+ | `gemini-4-argon` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1062
1199
  | `gemini-3.8-flash` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1063
1200
  | `gemini-3.7-flash` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1064
1201
  | `gemini-3.6-flash` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "4.0.94",
3
+ "version": "4.0.95",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -0,0 +1,75 @@
1
+ import type { ParseResult } from '@ai-sdk/provider-utils';
2
+
3
+ /**
4
+ * Concatenates the event streams of a long decode response that spans several
5
+ * requests (Gemini 4 long decode).
6
+ *
7
+ * Events of the current stream are passed through unchanged. When an event
8
+ * signals that the per-request cap was reached while budget remains
9
+ * (`getContinuationToken` returns a token), the next request is sent with that
10
+ * token once the current stream ends, and its events are appended. If the
11
+ * continuation request fails, the error is emitted as a failed parse result
12
+ * and the stream ends.
13
+ */
14
+ export function continueGoogleStream<T>({
15
+ stream,
16
+ getContinuationToken,
17
+ continueStream,
18
+ }: {
19
+ stream: ReadableStream<ParseResult<T>>;
20
+ getContinuationToken: (value: T) => string | undefined;
21
+ continueStream: (
22
+ continuationToken: string,
23
+ ) => Promise<ReadableStream<ParseResult<T>>>;
24
+ }): ReadableStream<ParseResult<T>> {
25
+ let reader = stream.getReader();
26
+ let pendingContinuationToken: string | undefined;
27
+
28
+ return new ReadableStream<ParseResult<T>>({
29
+ async pull(controller) {
30
+ while (true) {
31
+ const { done, value } = await reader.read();
32
+
33
+ if (!done) {
34
+ if (value.success) {
35
+ const token = getContinuationToken(value.value);
36
+ if (token != null) {
37
+ pendingContinuationToken = token;
38
+ }
39
+ }
40
+ controller.enqueue(value);
41
+ return;
42
+ }
43
+
44
+ if (pendingContinuationToken == null) {
45
+ controller.close();
46
+ return;
47
+ }
48
+
49
+ const continuationToken = pendingContinuationToken;
50
+ pendingContinuationToken = undefined;
51
+
52
+ try {
53
+ reader = (await continueStream(continuationToken)).getReader();
54
+ } catch (error) {
55
+ // Surfaced as an `error` stream part, followed by the `finish` part
56
+ // with the last continuation token so the caller can resume.
57
+ controller.enqueue({
58
+ success: false,
59
+ error: error as Extract<
60
+ ParseResult<T>,
61
+ { success: false }
62
+ >['error'],
63
+ rawValue: undefined,
64
+ });
65
+ controller.close();
66
+ return;
67
+ }
68
+ }
69
+ },
70
+
71
+ async cancel(reason) {
72
+ await reader.cancel(reason);
73
+ },
74
+ });
75
+ }
@@ -50,3 +50,79 @@ export function convertGoogleUsage(
50
50
  raw: usage,
51
51
  };
52
52
  }
53
+
54
+ /**
55
+ * Sums the usage metadata of two requests, e.g. the hops of a long decode
56
+ * response that was continued automatically. Non-numeric fields are taken
57
+ * from the later request.
58
+ */
59
+ export function addGoogleUsageMetadata<T extends GoogleUsageMetadata>(
60
+ a: T | undefined,
61
+ b: T | undefined,
62
+ ): T | undefined {
63
+ if (a == null || b == null) {
64
+ return b ?? a;
65
+ }
66
+
67
+ return {
68
+ ...a,
69
+ ...b,
70
+ promptTokenCount: addCounts(a.promptTokenCount, b.promptTokenCount),
71
+ candidatesTokenCount: addCounts(
72
+ a.candidatesTokenCount,
73
+ b.candidatesTokenCount,
74
+ ),
75
+ toolUsePromptTokenCount: addCounts(
76
+ a.toolUsePromptTokenCount,
77
+ b.toolUsePromptTokenCount,
78
+ ),
79
+ totalTokenCount: addCounts(a.totalTokenCount, b.totalTokenCount),
80
+ cachedContentTokenCount: addCounts(
81
+ a.cachedContentTokenCount,
82
+ b.cachedContentTokenCount,
83
+ ),
84
+ thoughtsTokenCount: addCounts(a.thoughtsTokenCount, b.thoughtsTokenCount),
85
+ promptTokensDetails: addTokenDetails(
86
+ a.promptTokensDetails,
87
+ b.promptTokensDetails,
88
+ ),
89
+ cacheTokensDetails: addTokenDetails(
90
+ a.cacheTokensDetails,
91
+ b.cacheTokensDetails,
92
+ ),
93
+ candidatesTokensDetails: addTokenDetails(
94
+ a.candidatesTokensDetails,
95
+ b.candidatesTokensDetails,
96
+ ),
97
+ toolUsePromptTokensDetails: addTokenDetails(
98
+ a.toolUsePromptTokensDetails,
99
+ b.toolUsePromptTokensDetails,
100
+ ),
101
+ };
102
+ }
103
+
104
+ function addCounts(
105
+ a: number | null | undefined,
106
+ b: number | null | undefined,
107
+ ): number | null | undefined {
108
+ return a == null && b == null ? (b ?? a) : (a ?? 0) + (b ?? 0);
109
+ }
110
+
111
+ function addTokenDetails(
112
+ a: GoogleTokenDetail[] | null | undefined,
113
+ b: GoogleTokenDetail[] | null | undefined,
114
+ ): GoogleTokenDetail[] | null | undefined {
115
+ if (a == null || b == null) {
116
+ return b ?? a;
117
+ }
118
+
119
+ const tokenCounts = new Map<string, number>();
120
+ for (const { modality, tokenCount } of [...a, ...b]) {
121
+ tokenCounts.set(modality, (tokenCounts.get(modality) ?? 0) + tokenCount);
122
+ }
123
+
124
+ return Array.from(tokenCounts, ([modality, tokenCount]) => ({
125
+ modality,
126
+ tokenCount,
127
+ }));
128
+ }
@@ -35,6 +35,7 @@ export type GoogleModelId =
35
35
  | 'gemini-3.6-flash'
36
36
  | 'gemini-3.7-flash'
37
37
  | 'gemini-3.8-flash'
38
+ | 'gemini-4-argon'
38
39
  | 'gemini-nano-banana-2.1'
39
40
  // latest version
40
41
  // https://ai.google.dev/gemini-api/docs/models#latest
@@ -259,6 +260,17 @@ export const googleLanguageModelOptions = lazySchema(() =>
259
260
  */
260
261
  serviceTier: z.enum(['standard', 'flex', 'priority']).optional(),
261
262
 
263
+ /**
264
+ * Optional. Resumes a response that stopped with `MAX_TOKENS` because it
265
+ * used up its `maxOutputTokens` budget (Gemini 4 long decode). Pass the
266
+ * `continuationToken` from the previous response's provider metadata
267
+ * together with the original prompt and a higher `maxOutputTokens` (the
268
+ * budget is cumulative across requests). Requests that reach the
269
+ * per-request cap while budget remains (`CONTINUATION`) are continued
270
+ * automatically. Sent as the `continuationToken` body field.
271
+ */
272
+ continuationToken: z.string().optional(),
273
+
262
274
  /**
263
275
  * Optional. Vertex AI only. Sent as the
264
276
  * `X-Vertex-AI-LLM-Shared-Request-Type` request header to select a
@@ -36,7 +36,9 @@ import {
36
36
  type Resolvable,
37
37
  } from '@ai-sdk/provider-utils';
38
38
  import { z } from 'zod/v4';
39
+ import { continueGoogleStream } from './continue-google-stream';
39
40
  import {
41
+ addGoogleUsageMetadata,
40
42
  convertGoogleUsage,
41
43
  type GoogleUsageMetadata,
42
44
  } from './convert-google-usage';
@@ -442,6 +444,9 @@ export class GoogleLanguageModel implements LanguageModelV4 {
442
444
  cachedContent: googleOptions?.cachedContent,
443
445
  labels: googleOptions?.labels,
444
446
  serviceTier: bodyServiceTier,
447
+ ...(googleOptions?.continuationToken != null && {
448
+ continuationToken: googleOptions.continuationToken,
449
+ }),
445
450
  },
446
451
  warnings: [...warnings, ...toolWarnings],
447
452
  providerOptionsNames,
@@ -510,6 +515,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
510
515
  providerOptionsNames,
511
516
  toolNameMapping,
512
517
  jsonResponseToolName,
518
+ initialPromptTokenCount,
513
519
  }: {
514
520
  config: GoogleLanguageModelConfig;
515
521
  response: InferSchema<typeof responseSchema>;
@@ -517,6 +523,7 @@ export class GoogleLanguageModel implements LanguageModelV4 {
517
523
  providerOptionsNames: readonly string[];
518
524
  toolNameMapping?: ReturnType<typeof createToolNameMapping>;
519
525
  jsonResponseToolName?: string;
526
+ initialPromptTokenCount?: number | null;
520
527
  }): LanguageModelV4GenerateResult {
521
528
  const wrapProviderMetadata = (payload: Record<string, unknown>) =>
522
529
  Object.fromEntries(
@@ -712,6 +719,10 @@ export class GoogleLanguageModel implements LanguageModelV4 {
712
719
  usageMetadata: usageMetadata ?? null,
713
720
  finishMessage: candidate?.finishMessage ?? null,
714
721
  serviceTier: usageMetadata?.serviceTier ?? null,
722
+ ...(candidate?.continuationToken != null
723
+ ? { continuationToken: candidate.continuationToken }
724
+ : {}),
725
+ ...(initialPromptTokenCount != null ? { initialPromptTokenCount } : {}),
715
726
  } satisfies GoogleProviderMetadata),
716
727
  response: {
717
728
  // TODO timestamp, model id
@@ -738,31 +749,88 @@ export class GoogleLanguageModel implements LanguageModelV4 {
738
749
  extraHeaders,
739
750
  );
740
751
 
741
- const {
752
+ const generateContent = (body: typeof args) =>
753
+ postJsonToApi({
754
+ url: `${this.config.baseURL}/${getModelPath(
755
+ this.modelId,
756
+ )}:generateContent`,
757
+ headers: mergedHeaders,
758
+ body,
759
+ failedResponseHandler: googleFailedResponseHandler,
760
+ successfulResponseHandler: createJsonResponseHandler(responseSchema),
761
+ abortSignal: options.abortSignal,
762
+ fetch: this.config.fetch,
763
+ });
764
+
765
+ let {
742
766
  responseHeaders,
743
767
  value: response,
744
768
  rawValue: rawResponse,
745
- } = await postJsonToApi({
746
- url: `${this.config.baseURL}/${getModelPath(
747
- this.modelId,
748
- )}:generateContent`,
749
- headers: mergedHeaders,
750
- body: args,
751
- failedResponseHandler: googleFailedResponseHandler,
752
- successfulResponseHandler: createJsonResponseHandler(responseSchema),
753
- abortSignal: options.abortSignal,
754
- fetch: this.config.fetch,
755
- });
769
+ } = await generateContent(args);
770
+
771
+ // Gemini 4 long decode: a request that reaches the per-request cap while
772
+ // `maxOutputTokens` budget remains finishes with `CONTINUATION`. Resend
773
+ // the same request with the continuation token (the server tracks the
774
+ // cumulative tokens) and combine the content of all requests.
775
+ //
776
+ // Usage is summed across requests (matching billing). Each request re-sends
777
+ // the prompt plus all output so far as input, so the first request's
778
+ // prompt token count is kept separately for context window checks.
779
+ const content: LanguageModelV4Content[] = [];
780
+ let usageMetadata: GoogleUsageMetadata | undefined;
781
+ const firstPromptTokenCount = response.usageMetadata?.promptTokenCount;
782
+ let isContinued = false;
783
+
784
+ while (true) {
785
+ const candidate = response.candidates?.[0];
786
+ const continuationToken =
787
+ candidate?.finishReason === 'CONTINUATION'
788
+ ? candidate.continuationToken
789
+ : undefined;
756
790
 
757
- const result = GoogleLanguageModel.convertGenerateContentResponse({
791
+ usageMetadata = addGoogleUsageMetadata(
792
+ usageMetadata,
793
+ response.usageMetadata ?? undefined,
794
+ );
795
+
796
+ if (continuationToken == null) {
797
+ break;
798
+ }
799
+
800
+ content.push(
801
+ ...GoogleLanguageModel.convertGenerateContentResponse({
802
+ config: this.config,
803
+ response,
804
+ warnings,
805
+ providerOptionsNames,
806
+ toolNameMapping,
807
+ jsonResponseToolName,
808
+ }).content,
809
+ );
810
+
811
+ isContinued = true;
812
+ ({
813
+ responseHeaders,
814
+ value: response,
815
+ rawValue: rawResponse,
816
+ } = await generateContent({ ...args, continuationToken }));
817
+ }
818
+
819
+ const lastResult = GoogleLanguageModel.convertGenerateContentResponse({
758
820
  config: this.config,
759
- response,
821
+ response: { ...response, usageMetadata },
760
822
  warnings,
761
823
  providerOptionsNames,
762
824
  toolNameMapping,
763
825
  jsonResponseToolName,
826
+ initialPromptTokenCount: isContinued ? firstPromptTokenCount : undefined,
764
827
  });
765
828
 
829
+ const result = {
830
+ ...lastResult,
831
+ content: [...content, ...lastResult.content],
832
+ };
833
+
766
834
  return {
767
835
  ...result,
768
836
  request: { body: args },
@@ -796,16 +864,37 @@ export class GoogleLanguageModel implements LanguageModelV4 {
796
864
  extraHeaders,
797
865
  );
798
866
 
799
- const { responseHeaders, value: response } = await postJsonToApi({
800
- url: `${this.config.baseURL}/${getModelPath(
801
- this.modelId,
802
- )}:streamGenerateContent?alt=sse`,
803
- headers,
804
- body: args,
805
- failedResponseHandler: googleFailedResponseHandler,
806
- successfulResponseHandler: createEventSourceResponseHandler(chunkSchema),
807
- abortSignal: options.abortSignal,
808
- fetch: this.config.fetch,
867
+ const streamGenerateContent = (body: typeof args) =>
868
+ postJsonToApi({
869
+ url: `${this.config.baseURL}/${getModelPath(
870
+ this.modelId,
871
+ )}:streamGenerateContent?alt=sse`,
872
+ headers,
873
+ body,
874
+ failedResponseHandler: googleFailedResponseHandler,
875
+ successfulResponseHandler:
876
+ createEventSourceResponseHandler(chunkSchema),
877
+ abortSignal: options.abortSignal,
878
+ fetch: this.config.fetch,
879
+ });
880
+
881
+ const { responseHeaders, value: firstResponse } =
882
+ await streamGenerateContent(args);
883
+
884
+ // Gemini 4 long decode: when a stream reaches the per-request cap while
885
+ // `maxOutputTokens` budget remains, its last chunk finishes with
886
+ // `CONTINUATION`. Resend the same request with the continuation token and
887
+ // append the new stream, which contains only newly generated content.
888
+ const response = continueGoogleStream({
889
+ stream: firstResponse,
890
+ getContinuationToken: chunk => {
891
+ const candidate = chunk.candidates?.[0];
892
+ return candidate?.finishReason === 'CONTINUATION'
893
+ ? (candidate.continuationToken ?? undefined)
894
+ : undefined;
895
+ },
896
+ continueStream: async continuationToken =>
897
+ (await streamGenerateContent({ ...args, continuationToken })).value,
809
898
  });
810
899
 
811
900
  let finishReason: LanguageModelV4FinishReason = {
@@ -813,11 +902,16 @@ export class GoogleLanguageModel implements LanguageModelV4 {
813
902
  raw: undefined,
814
903
  };
815
904
  let usage: GoogleUsageMetadata | undefined = undefined;
905
+ // Usage of the completed requests of an automatically continued response.
906
+ let continuedUsage: GoogleUsageMetadata | undefined = undefined;
907
+ // Prompt token count of the first request, for context window checks.
908
+ let initialPromptTokenCount: number | null | undefined = undefined;
816
909
  let promptFeedback: PromptFeedbackSchema | null = null;
817
910
  let lastGroundingMetadata: GroundingMetadataSchema | null = null;
818
911
  let lastUrlContextMetadata: UrlContextMetadataSchema | null = null;
819
912
  let lastSafetyRatings: SafetyRatingSchema[] | null = null;
820
913
  let lastFinishMessage: string | null = null;
914
+ let lastContinuationToken: string | null = null;
821
915
  let confirmedPromptBlockReason: string | undefined;
822
916
 
823
917
  const generateId = this.config.generateId;
@@ -947,6 +1041,9 @@ export class GoogleLanguageModel implements LanguageModelV4 {
947
1041
  if (candidate.finishMessage != null) {
948
1042
  lastFinishMessage = candidate.finishMessage;
949
1043
  }
1044
+ if (candidate.continuationToken != null) {
1045
+ lastContinuationToken = candidate.continuationToken;
1046
+ }
950
1047
  }
951
1048
 
952
1049
  // A confirmed prompt block is terminal for generated content, but
@@ -1322,6 +1419,16 @@ export class GoogleLanguageModel implements LanguageModelV4 {
1322
1419
  }
1323
1420
  }
1324
1421
 
1422
+ if (candidate.finishReason === 'CONTINUATION') {
1423
+ // Usage metadata is per request. The next request reports its
1424
+ // own usage, so bank the usage of this one.
1425
+ if (continuedUsage == null) {
1426
+ initialPromptTokenCount = usage?.promptTokenCount;
1427
+ }
1428
+ continuedUsage = addGoogleUsageMetadata(continuedUsage, usage);
1429
+ usage = undefined;
1430
+ }
1431
+
1325
1432
  if (candidate.finishReason != null) {
1326
1433
  finishReason = {
1327
1434
  unified: mapGoogleFinishReason({
@@ -1334,6 +1441,8 @@ export class GoogleLanguageModel implements LanguageModelV4 {
1334
1441
  },
1335
1442
 
1336
1443
  flush(controller) {
1444
+ usage = addGoogleUsageMetadata(continuedUsage, usage);
1445
+
1337
1446
  if (currentTextBlockId !== null) {
1338
1447
  controller.enqueue({
1339
1448
  type: 'text-end',
@@ -1359,6 +1468,17 @@ export class GoogleLanguageModel implements LanguageModelV4 {
1359
1468
  usageMetadata: usage ?? null,
1360
1469
  finishMessage: lastFinishMessage,
1361
1470
  serviceTier: usage?.serviceTier ?? null,
1471
+ // Only surface the token when the response stopped early
1472
+ // (budget used up, or continuing failed), not for the
1473
+ // checkpoint tokens of a stream that finished normally.
1474
+ ...(lastContinuationToken != null &&
1475
+ (finishReason.raw === 'MAX_TOKENS' ||
1476
+ finishReason.raw === 'CONTINUATION')
1477
+ ? { continuationToken: lastContinuationToken }
1478
+ : {}),
1479
+ ...(initialPromptTokenCount != null
1480
+ ? { initialPromptTokenCount }
1481
+ : {}),
1362
1482
  } satisfies GoogleProviderMetadata),
1363
1483
  });
1364
1484
  },
@@ -1845,6 +1965,7 @@ export const responseSchema = lazySchema(() =>
1845
1965
  content: getContentSchema().nullish().or(z.object({}).strict()),
1846
1966
  finishReason: z.string().nullish(),
1847
1967
  finishMessage: z.string().nullish(),
1968
+ continuationToken: z.string().nullish(),
1848
1969
  safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
1849
1970
  groundingMetadata: getGroundingMetadataSchema().nullish(),
1850
1971
  urlContextMetadata: getUrlContextMetadataSchema().nullish(),
@@ -1898,6 +2019,7 @@ const chunkSchema = lazySchema(() =>
1898
2019
  content: getContentSchema().nullish(),
1899
2020
  finishReason: z.string().nullish(),
1900
2021
  finishMessage: z.string().nullish(),
2022
+ continuationToken: z.string().nullish(),
1901
2023
  safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
1902
2024
  groundingMetadata: getGroundingMetadataSchema().nullish(),
1903
2025
  urlContextMetadata: getUrlContextMetadataSchema().nullish(),
@@ -100,4 +100,22 @@ export interface GoogleProviderMetadata {
100
100
  finishReason?: string | null;
101
101
  finishMessage: string | null;
102
102
  serviceTier: string | null;
103
+
104
+ /**
105
+ * Opaque token returned when a response stops with `MAX_TOKENS` because it
106
+ * used up its `maxOutputTokens` budget. Pass it back as
107
+ * `providerOptions.google.continuationToken` together with a higher
108
+ * `maxOutputTokens` and the original prompt to resume generation.
109
+ */
110
+ continuationToken?: string | null;
111
+
112
+ /**
113
+ * Prompt token count of the first request of a long decode response that
114
+ * was continued automatically (Gemini 4). Usage is summed across requests
115
+ * to match billing, and each continued request re-sends the prompt plus all
116
+ * output so far as input. Use this value instead of the summed input tokens
117
+ * for context window checks and compaction. Only present when the response
118
+ * spanned more than one request.
119
+ */
120
+ initialPromptTokenCount?: number | null;
103
121
  }
@@ -37,6 +37,7 @@ export type GoogleInteractionsModelId =
37
37
  | 'gemini-3.6-flash'
38
38
  | 'gemini-3.7-flash'
39
39
  | 'gemini-3.8-flash'
40
+ | 'gemini-4-argon'
40
41
  | 'gemini-nano-banana-2.1'
41
42
  | 'lyria-3-clip-preview'
42
43
  | 'lyria-3-pro-preview'