@ai-sdk/google 4.0.0-canary.58 → 4.0.0-canary.59

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @ai-sdk/google
2
2
 
3
+ ## 4.0.0-canary.59
4
+
5
+ ### Patch Changes
6
+
7
+ - db394ab: feat(provider/google): support cancelling long-running Interactions API agents via AbortSignal, and process their intermittent stream
8
+ - Updated dependencies [ca446f8]
9
+ - @ai-sdk/provider-utils@5.0.0-canary.38
10
+
3
11
  ## 4.0.0-canary.58
4
12
 
5
13
  ### Patch Changes
package/dist/index.js CHANGED
@@ -3,11 +3,11 @@ import {
3
3
  generateId as generateId2,
4
4
  loadApiKey,
5
5
  withoutTrailingSlash,
6
- withUserAgentSuffix
6
+ withUserAgentSuffix as withUserAgentSuffix2
7
7
  } from "@ai-sdk/provider-utils";
8
8
 
9
9
  // src/version.ts
10
- var VERSION = true ? "4.0.0-canary.58" : "0.0.0-test";
10
+ var VERSION = true ? "4.0.0-canary.59" : "0.0.0-test";
11
11
 
12
12
  // src/google-embedding-model.ts
13
13
  import {
@@ -3462,8 +3462,8 @@ var googleOperationSchema = z17.object({
3462
3462
 
3463
3463
  // src/interactions/google-interactions-language-model.ts
3464
3464
  import {
3465
- combineHeaders as combineHeaders6,
3466
- createEventSourceResponseHandler as createEventSourceResponseHandler2,
3465
+ combineHeaders as combineHeaders7,
3466
+ createEventSourceResponseHandler as createEventSourceResponseHandler3,
3467
3467
  createJsonResponseHandler as createJsonResponseHandler7,
3468
3468
  generateId as defaultGenerateId2,
3469
3469
  parseProviderOptions as parseProviderOptions6,
@@ -5074,8 +5074,45 @@ function parseGoogleInteractionsOutputs({
5074
5074
  import {
5075
5075
  createJsonResponseHandler as createJsonResponseHandler6,
5076
5076
  delay as delay3,
5077
- getFromApi as getFromApi3
5077
+ getFromApi as getFromApi3,
5078
+ isAbortError
5079
+ } from "@ai-sdk/provider-utils";
5080
+
5081
+ // src/interactions/cancel-google-interaction.ts
5082
+ import {
5083
+ combineHeaders as combineHeaders6,
5084
+ getRuntimeEnvironmentUserAgent,
5085
+ withUserAgentSuffix
5078
5086
  } from "@ai-sdk/provider-utils";
5087
+ var getOriginalFetch = () => globalThis.fetch;
5088
+ async function cancelGoogleInteraction({
5089
+ baseURL,
5090
+ interactionId,
5091
+ headers,
5092
+ fetch = getOriginalFetch()
5093
+ }) {
5094
+ if (interactionId == null || interactionId.length === 0) {
5095
+ return;
5096
+ }
5097
+ const url = `${baseURL}/interactions/${encodeURIComponent(interactionId)}/cancel`;
5098
+ try {
5099
+ const response = await fetch(url, {
5100
+ method: "POST",
5101
+ headers: withUserAgentSuffix(
5102
+ combineHeaders6({ "Content-Type": "application/json" }, headers),
5103
+ getRuntimeEnvironmentUserAgent()
5104
+ ),
5105
+ body: "{}"
5106
+ });
5107
+ try {
5108
+ await response.text();
5109
+ } catch (e) {
5110
+ }
5111
+ } catch (e) {
5112
+ }
5113
+ }
5114
+
5115
+ // src/interactions/poll-google-interactions.ts
5079
5116
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "incomplete"]);
5080
5117
  function isTerminalStatus(status) {
5081
5118
  return status != null && TERMINAL_STATUSES.has(status);
@@ -5101,34 +5138,43 @@ async function pollGoogleInteractionUntilTerminal({
5101
5138
  const startedAt = Date.now();
5102
5139
  let nextDelayMs = initialDelayMs;
5103
5140
  const url = `${baseURL}/interactions/${encodeURIComponent(interactionId)}`;
5104
- while (true) {
5105
- if (abortSignal == null ? void 0 : abortSignal.aborted) {
5106
- throw new DOMException("Polling was aborted", "AbortError");
5107
- }
5108
- if (Date.now() - startedAt > timeoutMs) {
5109
- throw new Error(
5110
- `google.interactions: timed out polling interaction ${interactionId} after ${timeoutMs}ms.`
5111
- );
5141
+ const cancelOnServer = () => cancelGoogleInteraction({ baseURL, interactionId, headers, fetch });
5142
+ try {
5143
+ while (true) {
5144
+ if (abortSignal == null ? void 0 : abortSignal.aborted) {
5145
+ await cancelOnServer();
5146
+ throw new DOMException("Polling was aborted", "AbortError");
5147
+ }
5148
+ if (Date.now() - startedAt > timeoutMs) {
5149
+ throw new Error(
5150
+ `google.interactions: timed out polling interaction ${interactionId} after ${timeoutMs}ms.`
5151
+ );
5152
+ }
5153
+ await delay3(nextDelayMs, { abortSignal });
5154
+ const {
5155
+ value: response,
5156
+ rawValue: rawResponse,
5157
+ responseHeaders
5158
+ } = await getFromApi3({
5159
+ url,
5160
+ headers,
5161
+ failedResponseHandler: googleFailedResponseHandler,
5162
+ successfulResponseHandler: createJsonResponseHandler6(
5163
+ googleInteractionsResponseSchema
5164
+ ),
5165
+ abortSignal,
5166
+ fetch
5167
+ });
5168
+ if (isTerminalStatus(response.status)) {
5169
+ return { response, rawResponse, responseHeaders };
5170
+ }
5171
+ nextDelayMs = Math.min(nextDelayMs * 2, maxDelayMs);
5112
5172
  }
5113
- await delay3(nextDelayMs, { abortSignal });
5114
- const {
5115
- value: response,
5116
- rawValue: rawResponse,
5117
- responseHeaders
5118
- } = await getFromApi3({
5119
- url,
5120
- headers,
5121
- failedResponseHandler: googleFailedResponseHandler,
5122
- successfulResponseHandler: createJsonResponseHandler6(
5123
- googleInteractionsResponseSchema
5124
- ),
5125
- abortSignal,
5126
- fetch
5127
- });
5128
- if (isTerminalStatus(response.status)) {
5129
- return { response, rawResponse, responseHeaders };
5173
+ } catch (error) {
5174
+ if (isAbortError(error)) {
5175
+ await cancelOnServer();
5130
5176
  }
5131
- nextDelayMs = Math.min(nextDelayMs * 2, maxDelayMs);
5177
+ throw error;
5132
5178
  }
5133
5179
  }
5134
5180
 
@@ -5280,6 +5326,174 @@ function prepareGoogleInteractionsTools({
5280
5326
  };
5281
5327
  }
5282
5328
 
5329
+ // src/interactions/stream-google-interactions.ts
5330
+ import {
5331
+ createEventSourceResponseHandler as createEventSourceResponseHandler2,
5332
+ delay as delay4,
5333
+ getFromApi as getFromApi4,
5334
+ isAbortError as isAbortError2
5335
+ } from "@ai-sdk/provider-utils";
5336
+ var DEFAULT_MAX_RETRIES = 3;
5337
+ var DEFAULT_RETRY_DELAY_MS = 500;
5338
+ function streamGoogleInteractionEvents({
5339
+ baseURL,
5340
+ interactionId,
5341
+ headers,
5342
+ fetch,
5343
+ abortSignal,
5344
+ maxRetries = DEFAULT_MAX_RETRIES,
5345
+ retryDelayMs = DEFAULT_RETRY_DELAY_MS
5346
+ }) {
5347
+ if (interactionId.length === 0) {
5348
+ throw new Error(
5349
+ "google.interactions: cannot stream a background interaction without an id."
5350
+ );
5351
+ }
5352
+ const eventSourceHeaders = {
5353
+ ...headers,
5354
+ accept: "text/event-stream"
5355
+ };
5356
+ let lastEventId;
5357
+ let complete = false;
5358
+ let attempt = 0;
5359
+ let receivedAnyEventThisAttempt = false;
5360
+ let currentReader;
5361
+ const internalAbort = new AbortController();
5362
+ const upstreamAbortHandler = () => internalAbort.abort();
5363
+ if (abortSignal != null) {
5364
+ if (abortSignal.aborted) {
5365
+ internalAbort.abort();
5366
+ } else {
5367
+ abortSignal.addEventListener("abort", upstreamAbortHandler, {
5368
+ once: true
5369
+ });
5370
+ }
5371
+ }
5372
+ const effectiveSignal = internalAbort.signal;
5373
+ function buildUrl() {
5374
+ const base = `${baseURL}/interactions/${encodeURIComponent(interactionId)}`;
5375
+ const params = new URLSearchParams({ stream: "true" });
5376
+ if (lastEventId != null) {
5377
+ params.set("last_event_id", lastEventId);
5378
+ }
5379
+ return `${base}?${params.toString()}`;
5380
+ }
5381
+ async function openReader() {
5382
+ const { value: stream } = await getFromApi4({
5383
+ url: buildUrl(),
5384
+ headers: eventSourceHeaders,
5385
+ failedResponseHandler: googleFailedResponseHandler,
5386
+ successfulResponseHandler: createEventSourceResponseHandler2(
5387
+ googleInteractionsEventSchema
5388
+ ),
5389
+ abortSignal: effectiveSignal,
5390
+ fetch
5391
+ });
5392
+ return stream.getReader();
5393
+ }
5394
+ return new ReadableStream({
5395
+ async start(controller) {
5396
+ try {
5397
+ while (!complete && !effectiveSignal.aborted) {
5398
+ if (currentReader == null) {
5399
+ try {
5400
+ currentReader = await openReader();
5401
+ receivedAnyEventThisAttempt = false;
5402
+ } catch (error) {
5403
+ if (isAbortError2(error) || effectiveSignal.aborted) {
5404
+ controller.error(error);
5405
+ return;
5406
+ }
5407
+ attempt++;
5408
+ if (attempt >= maxRetries) {
5409
+ controller.error(error);
5410
+ return;
5411
+ }
5412
+ await delay4(retryDelayMs * attempt, {
5413
+ abortSignal: effectiveSignal
5414
+ });
5415
+ continue;
5416
+ }
5417
+ }
5418
+ try {
5419
+ const { done, value } = await currentReader.read();
5420
+ if (done) {
5421
+ currentReader = void 0;
5422
+ if (complete) break;
5423
+ if (!receivedAnyEventThisAttempt) {
5424
+ attempt++;
5425
+ if (attempt >= maxRetries) {
5426
+ controller.error(
5427
+ new Error(
5428
+ "google.interactions: SSE stream closed without producing any events."
5429
+ )
5430
+ );
5431
+ return;
5432
+ }
5433
+ await delay4(retryDelayMs * attempt, {
5434
+ abortSignal: effectiveSignal
5435
+ });
5436
+ } else {
5437
+ attempt = 0;
5438
+ }
5439
+ continue;
5440
+ }
5441
+ receivedAnyEventThisAttempt = true;
5442
+ if (value.success) {
5443
+ const ev = value.value;
5444
+ if (typeof ev.event_id === "string" && ev.event_id.length > 0) {
5445
+ lastEventId = ev.event_id;
5446
+ }
5447
+ if (ev.event_type === "interaction.complete" || ev.event_type === "error") {
5448
+ complete = true;
5449
+ }
5450
+ }
5451
+ controller.enqueue(value);
5452
+ } catch (error) {
5453
+ if (isAbortError2(error) || effectiveSignal.aborted) {
5454
+ controller.error(error);
5455
+ return;
5456
+ }
5457
+ currentReader = void 0;
5458
+ attempt++;
5459
+ if (attempt >= maxRetries) {
5460
+ controller.error(error);
5461
+ return;
5462
+ }
5463
+ await delay4(retryDelayMs * attempt, {
5464
+ abortSignal: effectiveSignal
5465
+ });
5466
+ }
5467
+ }
5468
+ controller.close();
5469
+ } catch (error) {
5470
+ controller.error(error);
5471
+ } finally {
5472
+ if (abortSignal != null) {
5473
+ abortSignal.removeEventListener("abort", upstreamAbortHandler);
5474
+ }
5475
+ currentReader == null ? void 0 : currentReader.cancel().catch(() => {
5476
+ });
5477
+ currentReader = void 0;
5478
+ if (effectiveSignal.aborted && !complete) {
5479
+ await cancelGoogleInteraction({
5480
+ baseURL,
5481
+ interactionId,
5482
+ headers,
5483
+ fetch
5484
+ });
5485
+ }
5486
+ }
5487
+ },
5488
+ cancel() {
5489
+ internalAbort.abort();
5490
+ currentReader == null ? void 0 : currentReader.cancel().catch(() => {
5491
+ });
5492
+ currentReader = void 0;
5493
+ }
5494
+ });
5495
+ }
5496
+
5283
5497
  // src/interactions/synthesize-google-interactions-agent-stream.ts
5284
5498
  function synthesizeGoogleInteractionsAgentStream({
5285
5499
  response,
@@ -5608,7 +5822,7 @@ var GoogleInteractionsLanguageModel = class _GoogleInteractionsLanguageModel {
5608
5822
  var _a, _b, _c, _d, _e, _f;
5609
5823
  const { args, warnings, isAgent, pollingTimeoutMs } = await this.getArgs(options);
5610
5824
  const url = `${this.config.baseURL}/interactions`;
5611
- const mergedHeaders = combineHeaders6(
5825
+ const mergedHeaders = combineHeaders7(
5612
5826
  this.config.headers ? await resolve5(this.config.headers) : void 0,
5613
5827
  options.headers
5614
5828
  );
@@ -5688,12 +5902,12 @@ var GoogleInteractionsLanguageModel = class _GoogleInteractionsLanguageModel {
5688
5902
  var _a;
5689
5903
  const { args, warnings, isAgent, pollingTimeoutMs } = await this.getArgs(options);
5690
5904
  const url = `${this.config.baseURL}/interactions`;
5691
- const mergedHeaders = combineHeaders6(
5905
+ const mergedHeaders = combineHeaders7(
5692
5906
  this.config.headers ? await resolve5(this.config.headers) : void 0,
5693
5907
  options.headers
5694
5908
  );
5695
5909
  if (isAgent) {
5696
- return this.doStreamAgent({
5910
+ return this.doStreamBackground({
5697
5911
  args,
5698
5912
  warnings,
5699
5913
  url,
@@ -5708,7 +5922,7 @@ var GoogleInteractionsLanguageModel = class _GoogleInteractionsLanguageModel {
5708
5922
  headers: mergedHeaders,
5709
5923
  body,
5710
5924
  failedResponseHandler: googleFailedResponseHandler,
5711
- successfulResponseHandler: createEventSourceResponseHandler2(
5925
+ successfulResponseHandler: createEventSourceResponseHandler3(
5712
5926
  googleInteractionsEventSchema
5713
5927
  ),
5714
5928
  abortSignal: options.abortSignal,
@@ -5728,26 +5942,24 @@ var GoogleInteractionsLanguageModel = class _GoogleInteractionsLanguageModel {
5728
5942
  };
5729
5943
  }
5730
5944
  /*
5731
- * Drive the streaming surface for agent calls. Agent calls require
5945
+ * Drive the streaming surface for agent calls. Agents require
5732
5946
  * `background: true`, which is incompatible with `stream: true` on POST.
5733
5947
  *
5734
- * In principle the API also exposes `GET /interactions/{id}?stream=true`
5735
- * to replay events as the agent runs. In practice the connection is
5736
- * idle for long stretches while the agent thinks (deep-research can run
5737
- * for a minute or more between SSE events), and undici's default body
5738
- * timeout terminates the request mid-flight with `UND_ERR_BODY_TIMEOUT`.
5739
- * Tuning the timeout per-call would require the caller to thread an
5740
- * `undici.Agent` through `fetch`, which contradicts the AI SDK's
5741
- * pluggable-fetch contract.
5948
+ * Approach:
5949
+ * 1. POST `/interactions` with `background: true`. The response includes
5950
+ * the interaction id and an initial (usually non-terminal) status.
5951
+ * 2. If the POST status is already terminal (rare), synthesize a stream
5952
+ * from the polled outputs and we're done.
5953
+ * 3. Otherwise open `GET /interactions/{id}?stream=true` and pipe the
5954
+ * SSE events through `buildGoogleInteractionsStreamTransform` so the
5955
+ * consumer receives text deltas / thinking summaries / tool events as
5956
+ * they happen instead of all at once at the end.
5742
5957
  *
5743
- * We therefore drive `doStream` exactly like `doGenerate` for agents:
5744
- * POST with `background: true`, poll `GET /interactions/{id}` until
5745
- * terminal, then synthesize the stream from the final outputs. The
5746
- * user-facing surface stays identical -- text-start / text-delta /
5747
- * text-end / finish parts arrive in the same order as a true SSE
5748
- * response, just buffered until the agent completes.
5958
+ * The SSE connection can drop while the agent idles between events
5959
+ * (`UND_ERR_BODY_TIMEOUT`); `streamGoogleInteractionEvents` handles the
5960
+ * reconnect-with-`last_event_id` loop transparently.
5749
5961
  */
5750
- async doStreamAgent({
5962
+ async doStreamBackground({
5751
5963
  args,
5752
5964
  warnings,
5753
5965
  url,
@@ -5767,34 +5979,44 @@ var GoogleInteractionsLanguageModel = class _GoogleInteractionsLanguageModel {
5767
5979
  abortSignal: options.abortSignal,
5768
5980
  fetch: this.config.fetch
5769
5981
  });
5770
- let { responseHeaders: postHeaders, value: postResponse } = postResult;
5982
+ const { responseHeaders: postHeaders, value: postResponse } = postResult;
5771
5983
  const interactionId = postResponse.id;
5772
5984
  if (interactionId == null || interactionId.length === 0) {
5773
5985
  throw new Error(
5774
- "google.interactions: agent POST response did not include an interaction id; cannot poll for the agent result."
5986
+ "google.interactions: background POST response did not include an interaction id; cannot stream the result."
5775
5987
  );
5776
5988
  }
5777
- if (!isTerminalStatus(postResponse.status)) {
5778
- const polled = await pollGoogleInteractionUntilTerminal({
5779
- baseURL: this.config.baseURL,
5780
- interactionId,
5781
- headers: mergedHeaders,
5782
- fetch: this.config.fetch,
5783
- abortSignal: options.abortSignal,
5784
- timeoutMs: pollingTimeoutMs
5989
+ const headerServiceTier = postHeaders == null ? void 0 : postHeaders["x-gemini-service-tier"];
5990
+ if (isTerminalStatus(postResponse.status)) {
5991
+ const synthesized = synthesizeGoogleInteractionsAgentStream({
5992
+ response: postResponse,
5993
+ warnings,
5994
+ generateId: (_a = this.config.generateId) != null ? _a : defaultGenerateId2,
5995
+ includeRawChunks: options.includeRawChunks,
5996
+ headerServiceTier
5785
5997
  });
5786
- postResponse = polled.response;
5787
- postHeaders = (_a = polled.responseHeaders) != null ? _a : postHeaders;
5998
+ return {
5999
+ stream: synthesized,
6000
+ request: { body: args },
6001
+ response: { headers: postHeaders }
6002
+ };
5788
6003
  }
5789
- const stream = synthesizeGoogleInteractionsAgentStream({
5790
- response: postResponse,
6004
+ void pollingTimeoutMs;
6005
+ const events = streamGoogleInteractionEvents({
6006
+ baseURL: this.config.baseURL,
6007
+ interactionId,
6008
+ headers: mergedHeaders,
6009
+ fetch: this.config.fetch,
6010
+ abortSignal: options.abortSignal
6011
+ });
6012
+ const transform = buildGoogleInteractionsStreamTransform({
5791
6013
  warnings,
5792
6014
  generateId: (_b = this.config.generateId) != null ? _b : defaultGenerateId2,
5793
6015
  includeRawChunks: options.includeRawChunks,
5794
- headerServiceTier: postHeaders == null ? void 0 : postHeaders["x-gemini-service-tier"]
6016
+ serviceTier: headerServiceTier
5795
6017
  });
5796
6018
  return {
5797
- stream,
6019
+ stream: events.pipeThrough(transform),
5798
6020
  request: { body: args },
5799
6021
  response: { headers: postHeaders }
5800
6022
  };
@@ -5814,7 +6036,7 @@ function createGoogle(options = {}) {
5814
6036
  var _a, _b;
5815
6037
  const baseURL = (_a = withoutTrailingSlash(options.baseURL)) != null ? _a : "https://generativelanguage.googleapis.com/v1beta";
5816
6038
  const providerName = (_b = options.name) != null ? _b : "google.generative-ai";
5817
- const getHeaders = () => withUserAgentSuffix(
6039
+ const getHeaders = () => withUserAgentSuffix2(
5818
6040
  {
5819
6041
  "x-goog-api-key": loadApiKey({
5820
6042
  apiKey: options.apiKey,