@trigger.dev/sdk 0.0.0-prerelease-20260908122921 → 0.0.0-prerelease-streamfix-20260909094302

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.
Files changed (106) hide show
  1. package/dist/commonjs/imports/ai-runtime-cjs.cjs.map +1 -1
  2. package/dist/commonjs/imports/ai-runtime.js +0 -2
  3. package/dist/commonjs/v3/ai.d.ts +16 -199
  4. package/dist/commonjs/v3/ai.js +102 -983
  5. package/dist/commonjs/v3/ai.js.map +1 -1
  6. package/dist/commonjs/v3/chat-client.d.ts +2 -3
  7. package/dist/commonjs/v3/chat-client.js +5 -31
  8. package/dist/commonjs/v3/chat-client.js.map +1 -1
  9. package/dist/commonjs/v3/chat-react.d.ts +0 -34
  10. package/dist/commonjs/v3/chat-react.js +1 -47
  11. package/dist/commonjs/v3/chat-react.js.map +1 -1
  12. package/dist/commonjs/v3/chat-server.d.ts +6 -42
  13. package/dist/commonjs/v3/chat-server.js +7 -52
  14. package/dist/commonjs/v3/chat-server.js.map +1 -1
  15. package/dist/commonjs/v3/chat.d.ts +10 -81
  16. package/dist/commonjs/v3/chat.js +46 -292
  17. package/dist/commonjs/v3/chat.js.map +1 -1
  18. package/dist/commonjs/v3/sessions.d.ts +2 -15
  19. package/dist/commonjs/v3/sessions.js +1 -12
  20. package/dist/commonjs/v3/sessions.js.map +1 -1
  21. package/dist/commonjs/v3/shared.js +36 -30
  22. package/dist/commonjs/v3/shared.js.map +1 -1
  23. package/dist/commonjs/v3/test/mock-chat-agent.d.ts +0 -43
  24. package/dist/commonjs/v3/test/mock-chat-agent.js +0 -90
  25. package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
  26. package/dist/commonjs/v3/test/test-session-handle.js +0 -6
  27. package/dist/commonjs/v3/test/test-session-handle.js.map +1 -1
  28. package/dist/commonjs/version.js +1 -1
  29. package/dist/esm/imports/ai-runtime.d.ts +2 -2
  30. package/dist/esm/imports/ai-runtime.js +2 -2
  31. package/dist/esm/imports/ai-runtime.js.map +1 -1
  32. package/dist/esm/v3/ai.d.ts +16 -199
  33. package/dist/esm/v3/ai.js +103 -984
  34. package/dist/esm/v3/ai.js.map +1 -1
  35. package/dist/esm/v3/chat-client.d.ts +2 -3
  36. package/dist/esm/v3/chat-client.js +5 -31
  37. package/dist/esm/v3/chat-client.js.map +1 -1
  38. package/dist/esm/v3/chat-react.d.ts +0 -34
  39. package/dist/esm/v3/chat-react.js +1 -46
  40. package/dist/esm/v3/chat-react.js.map +1 -1
  41. package/dist/esm/v3/chat-server.d.ts +6 -42
  42. package/dist/esm/v3/chat-server.js +8 -53
  43. package/dist/esm/v3/chat-server.js.map +1 -1
  44. package/dist/esm/v3/chat.d.ts +10 -81
  45. package/dist/esm/v3/chat.js +47 -293
  46. package/dist/esm/v3/chat.js.map +1 -1
  47. package/dist/esm/v3/sessions.d.ts +2 -15
  48. package/dist/esm/v3/sessions.js +1 -11
  49. package/dist/esm/v3/sessions.js.map +1 -1
  50. package/dist/esm/v3/shared.js +23 -17
  51. package/dist/esm/v3/shared.js.map +1 -1
  52. package/dist/esm/v3/test/mock-chat-agent.d.ts +0 -43
  53. package/dist/esm/v3/test/mock-chat-agent.js +2 -92
  54. package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
  55. package/dist/esm/v3/test/test-session-handle.js +0 -6
  56. package/dist/esm/v3/test/test-session-handle.js.map +1 -1
  57. package/dist/esm/version.js +1 -1
  58. package/docs/ai-chat/actions.mdx +23 -55
  59. package/docs/ai-chat/anatomy.mdx +3 -3
  60. package/docs/ai-chat/backend.mdx +48 -125
  61. package/docs/ai-chat/background-injection.mdx +19 -67
  62. package/docs/ai-chat/client-protocol.mdx +4 -5
  63. package/docs/ai-chat/compaction.mdx +7 -11
  64. package/docs/ai-chat/custom-agents.mdx +0 -23
  65. package/docs/ai-chat/fast-starts.mdx +20 -27
  66. package/docs/ai-chat/frontend.mdx +14 -17
  67. package/docs/ai-chat/migrating-from-a-route-handler.mdx +14 -16
  68. package/docs/ai-chat/patterns/skills.mdx +10 -7
  69. package/docs/ai-chat/patterns/version-upgrades.mdx +6 -79
  70. package/docs/ai-chat/pending-messages.mdx +3 -3
  71. package/docs/ai-chat/prompt-caching.mdx +25 -23
  72. package/docs/ai-chat/quick-start.mdx +11 -11
  73. package/docs/ai-chat/reference.mdx +5 -12
  74. package/docs/ai-chat/sessions.mdx +1 -6
  75. package/docs/ai-chat/testing.mdx +1 -2
  76. package/docs/ai-chat/tools.mdx +13 -18
  77. package/docs/ai-chat/upgrade-guide.mdx +2 -2
  78. package/docs/apikeys.mdx +45 -27
  79. package/docs/deployment/overview.mdx +8 -4
  80. package/docs/deployment/preview-branches.mdx +4 -4
  81. package/docs/deployment/version-skew-protection.mdx +0 -62
  82. package/docs/manual-setup.mdx +7 -7
  83. package/docs/mcp-tools.mdx +0 -9
  84. package/docs/quick-start.mdx +3 -3
  85. package/docs/realtime/auth.mdx +1 -1
  86. package/docs/self-hosting/security.mdx +0 -5
  87. package/docs/tasks/scheduled.mdx +0 -24
  88. package/docs/triggering.mdx +1 -1
  89. package/package.json +4 -4
  90. package/skills/trigger-authoring-chat-agent/SKILL.md +27 -38
  91. package/skills/trigger-chat-agent-advanced/SKILL.md +12 -31
  92. package/dist/commonjs/v3/chatVersionSkew.d.ts +0 -12
  93. package/dist/commonjs/v3/chatVersionSkew.js +0 -30
  94. package/dist/commonjs/v3/chatVersionSkew.js.map +0 -1
  95. package/dist/commonjs/v3/externalDeploymentId.d.ts +0 -23
  96. package/dist/commonjs/v3/externalDeploymentId.js +0 -43
  97. package/dist/commonjs/v3/externalDeploymentId.js.map +0 -1
  98. package/dist/esm/v3/chatVersionSkew.d.ts +0 -12
  99. package/dist/esm/v3/chatVersionSkew.js +0 -27
  100. package/dist/esm/v3/chatVersionSkew.js.map +0 -1
  101. package/dist/esm/v3/externalDeploymentId.d.ts +0 -23
  102. package/dist/esm/v3/externalDeploymentId.js +0 -38
  103. package/dist/esm/v3/externalDeploymentId.js.map +0 -1
  104. package/docs/ai-chat/patterns/native-compaction.mdx +0 -310
  105. package/docs/reports.mdx +0 -157
  106. package/docs/troubleshooting-zod.mdx +0 -158
@@ -44,32 +44,6 @@ function isAuthError(error) {
44
44
  const e = error;
45
45
  return e.name === "TriggerApiError" && (e.status === 401 || e.status === 403);
46
46
  }
47
- /**
48
- * Detect the closed-session refusal from `.in/append`: HTTP 409 with the
49
- * stable `session_closed` code. Terminal — never retried.
50
- */
51
- function isSessionClosedError(error) {
52
- if (error === null || typeof error !== "object")
53
- return false;
54
- const e = error;
55
- return e.name === "TriggerApiError" && e.status === 409 && e.code === "session_closed";
56
- }
57
- function sessionClosedReasonOf(error) {
58
- if (error === null || typeof error !== "object")
59
- return undefined;
60
- const reason = error.closedReason;
61
- return typeof reason === "string" ? reason : undefined;
62
- }
63
- /** The local form of the refusal, for sends the transport rejects itself. */
64
- function sessionClosedError(chatId, reason) {
65
- const err = new Error(reason ? `Chat session ${chatId} is closed: ${reason}` : `Chat session ${chatId} is closed`);
66
- err.name = "TriggerApiError";
67
- err.status = 409;
68
- err.code = "session_closed";
69
- if (reason)
70
- err.closedReason = reason;
71
- return err;
72
- }
73
47
  /**
74
48
  * Detect a 404 from a session-PAT-authed call — i.e. the session doesn't
75
49
  * exist in the current environment. Happens when hydrated session state
@@ -214,8 +188,6 @@ class TriggerChatTransport {
214
188
  lastEventId: session.lastEventId,
215
189
  activeInputSeq: session.activeInputSeq,
216
190
  isStreaming: session.isStreaming,
217
- closed: session.closed,
218
- closedReason: session.closedReason,
219
191
  });
220
192
  }
221
193
  }
@@ -265,10 +237,6 @@ class TriggerChatTransport {
265
237
  */
266
238
  sendMessages = async (options) => {
267
239
  const { trigger, chatId, messageId, messages, abortSignal, body, metadata } = options;
268
- const closedState = this.sessions.get(chatId);
269
- if (closedState?.closed) {
270
- throw sessionClosedError(chatId, closedState.closedReason);
271
- }
272
240
  if (this.coordinator) {
273
241
  if (this.coordinator.isReadOnly(chatId)) {
274
242
  throw new Error("This chat is active in another tab");
@@ -278,14 +246,6 @@ class TriggerChatTransport {
278
246
  const mergedMetadata = this.defaultMetadata || metadata
279
247
  ? { ...(this.defaultMetadata ?? {}), ...(metadata ?? {}) }
280
248
  : undefined;
281
- // An action sent through `useChat`. `useChatActions` (or any caller) puts
282
- // it in `body.action`; sending it here rather than through
283
- // `transport.sendAction` means `useChat` owns the response stream, so an
284
- // action that becomes a turn renders the way a message turn does.
285
- const actionInBody = body?.action;
286
- if (actionInBody !== undefined) {
287
- return this.sendAction(chatId, actionInBody, { abortSignal, metadata: mergedMetadata });
288
- }
289
249
  // First-turn handover routing — when `headStart` is set AND no
290
250
  // session state exists yet for this chatId, POST the wire payload
291
251
  // to the customer's `chat.handover` route handler. The handler
@@ -348,9 +308,11 @@ class TriggerChatTransport {
348
308
  activeStream.abort();
349
309
  this.activeStreams.delete(chatId);
350
310
  }
311
+ // A stop that never saw its TURN_COMPLETE leaves the flag set, and the new
312
+ // turn would be skipped record by record.
313
+ state.skipToTurnComplete = false;
351
314
  state.activeInputSeq = inSeq;
352
315
  state.isStreaming = true;
353
- state.outstandingTurnAbandoned = false;
354
316
  this.notifySessionChange(chatId, state);
355
317
  // Owning turn: aborting this live send stops the turn the user drives.
356
318
  return this.subscribeToSessionStream(state, abortSignal, chatId, {
@@ -422,16 +384,6 @@ class TriggerChatTransport {
422
384
  };
423
385
  this.sessions.set(chatId, state);
424
386
  this.notifySessionChange(chatId, state);
425
- // Step 1 streams from the warm server either way; this says the agent run that owes step 2
426
- // is parked on an undeployed external deployment id.
427
- if (response.headers.get("X-Trigger-Chat-Pending-Version") === "1") {
428
- this.emitEvent({
429
- type: "run-pending-version",
430
- chatId,
431
- timestamp: Date.now(),
432
- source: "head-start",
433
- });
434
- }
435
387
  // Filter the parsed UIMessage stream:
436
388
  // - Drop control chunks (`trigger:turn-complete`,
437
389
  // `trigger:session-state`) before they reach AI SDK — they
@@ -598,9 +550,6 @@ class TriggerChatTransport {
598
550
  const state = this.sessions.get(options.chatId);
599
551
  if (!state)
600
552
  return null;
601
- // A closed session has no further turns to resume.
602
- if (state.closed)
603
- return null;
604
553
  // Watch is a standing subscription: a settled session is exactly the
605
554
  // state it waits in, so a completed last turn must not block the resume.
606
555
  if (state.isStreaming === false && !this.watchMode)
@@ -645,13 +594,7 @@ class TriggerChatTransport {
645
594
  catch {
646
595
  return false;
647
596
  }
648
- // Only gate when a sent turn is still outstanding. A stop at a boundary has
649
- // nothing to supersede, and gating it would swallow the next turn.
650
- if (!state.outstandingTurnAbandoned &&
651
- (state.isStreaming || state.activeInputSeq !== undefined)) {
652
- state.skipToTurnComplete = true;
653
- state.supersededInputSeq = state.activeInputSeq;
654
- }
597
+ state.skipToTurnComplete = true;
655
598
  const activeStream = this.activeStreams.get(chatId);
656
599
  if (activeStream) {
657
600
  activeStream.abort();
@@ -670,28 +613,15 @@ class TriggerChatTransport {
670
613
  this.notifySessionChange(chatId, state);
671
614
  return true;
672
615
  };
673
- /**
674
- * Clear the supersede gate armed by a stop. Call this when the stopped
675
- * turn died without ever writing its `trigger:turn-complete` boundary,
676
- * otherwise the next turn stays gated forever.
677
- */
678
- clearSupersedeGate = (chatId) => {
679
- const state = this.sessions.get(chatId);
680
- if (!state)
681
- return;
682
- state.skipToTurnComplete = false;
683
- state.supersededInputSeq = undefined;
684
- state.outstandingTurnAbandoned = true;
685
- };
686
616
  /**
687
617
  * Send a custom action chunk (for `chat.agent`'s `actionSchema` /
688
- * `onAction` hook). An action is an edit: only `hydrateMessages` and
689
- * `onAction` fire on the agent side, and the returned stream completes
690
- * with `trigger:turn-complete` once the edit is persisted. When `onAction`
691
- * returns `chat.turn()` the turn's response follows on the same stream.
692
- * Per-action `metadata` is merged over the transport's `clientData`.
618
+ * `onAction` hook). Actions are not turns — only `hydrateMessages`
619
+ * and `onAction` fire on the agent side. The returned stream
620
+ * carries any model response `onAction` produced (when it returns a
621
+ * `StreamTextResult`); for `void`-returning side-effect-only actions
622
+ * the stream completes immediately with `trigger:turn-complete`.
693
623
  */
694
- sendAction = async (chatId, action, options) => {
624
+ sendAction = async (chatId, action) => {
695
625
  if (this.coordinator) {
696
626
  if (this.coordinator.isReadOnly(chatId)) {
697
627
  throw new Error("This chat is active in another tab");
@@ -703,9 +633,7 @@ class TriggerChatTransport {
703
633
  chatId,
704
634
  trigger: "action",
705
635
  action,
706
- metadata: this.defaultMetadata || options?.metadata
707
- ? { ...(this.defaultMetadata ?? {}), ...(options?.metadata ?? {}) }
708
- : undefined,
636
+ metadata: this.defaultMetadata ?? undefined,
709
637
  };
710
638
  const body = this.serializeInputChunk({ kind: "message", payload: wirePayload });
711
639
  const partId = crypto.randomUUID();
@@ -720,14 +648,16 @@ class TriggerChatTransport {
720
648
  activeStream.abort();
721
649
  this.activeStreams.delete(chatId);
722
650
  }
651
+ // A stop that never saw its TURN_COMPLETE leaves the flag set, and the new
652
+ // turn would be skipped record by record.
653
+ state.skipToTurnComplete = false;
723
654
  // Mark streaming + persist so a reload mid-action resumes (reconnectToStream
724
655
  // no-ops when the persisted session says isStreaming: false).
725
656
  state.activeInputSeq = inSeq;
726
657
  state.isStreaming = true;
727
- state.outstandingTurnAbandoned = false;
728
658
  this.notifySessionChange(chatId, state);
729
659
  // Owning action: aborting this send stops the turn the user drives.
730
- return this.subscribeToSessionStream(state, options?.abortSignal, chatId, {
660
+ return this.subscribeToSessionStream(state, undefined, chatId, {
731
661
  sinceInSeq: inSeq,
732
662
  sendStopOnAbort: true,
733
663
  });
@@ -820,22 +750,6 @@ class TriggerChatTransport {
820
750
  setClientData(clientData) {
821
751
  this.defaultMetadata = clientData;
822
752
  }
823
- /**
824
- * Update the request-time callbacks. Same reason as `setClientData`:
825
- * a host that rebuilds these per render (a dashboard navigating between
826
- * projects) would otherwise keep hitting the endpoint captured at
827
- * construction. Every read goes through the live field, so the next
828
- * request uses the latest callback.
829
- */
830
- setAccessToken(accessToken) {
831
- this.resolveAccessToken = accessToken;
832
- }
833
- setStartSession(startSession) {
834
- this.resolveStartSession = startSession;
835
- }
836
- setFetch(fetchOverride) {
837
- this.fetchOverride = fetchOverride;
838
- }
839
753
  // -------------------------------------------------------------------------
840
754
  // Multi-tab coordination passthrough
841
755
  // -------------------------------------------------------------------------
@@ -883,42 +797,7 @@ class TriggerChatTransport {
883
797
  lastEventId: state.lastEventId,
884
798
  activeInputSeq: state.activeInputSeq,
885
799
  isStreaming: state.isStreaming,
886
- closed: state.closed,
887
- closedReason: state.closedReason,
888
800
  });
889
- /**
890
- * Flip a session to closed and tell listeners once. Terminal: sends and
891
- * reconnects stop from here on, and there is no reopening.
892
- */
893
- markSessionClosed(chatId, state, reason, source) {
894
- if (state.closed)
895
- return;
896
- state.closed = true;
897
- if (reason)
898
- state.closedReason = reason;
899
- state.isStreaming = false;
900
- state.activeInputSeq = undefined;
901
- state.skipToTurnComplete = false;
902
- state.supersededInputSeq = undefined;
903
- this.emitEvent({
904
- type: "session-closed",
905
- chatId,
906
- timestamp: Date.now(),
907
- ...(reason ? { reason } : {}),
908
- source,
909
- });
910
- this.notifySessionChange(chatId, state);
911
- this.coordinator?.release(chatId);
912
- }
913
- /**
914
- * Whether the conversation has been closed — by the agent via
915
- * `chat.close()`, or from outside via `sessions.close()`. Closed is
916
- * one-way: the transport refuses further sends and reconnects, and the
917
- * transcript stays readable.
918
- */
919
- sessionStatus = (chatId) => this.sessions.get(chatId)?.closed ? "closed" : "open";
920
- /** The reason the session was closed, when one was given. */
921
- sessionClosedReason = (chatId) => this.sessions.get(chatId)?.closedReason;
922
801
  notifySessionChange(chatId, session) {
923
802
  if (!this._onSessionChange)
924
803
  return;
@@ -962,19 +841,11 @@ class TriggerChatTransport {
962
841
  if (!this.resolveStartSession) {
963
842
  throw new Error("TriggerChatTransport: `startSession` is required to call `start()` / `preload()`. Either provide it or pre-hydrate the session via `sessions: { ... }`.");
964
843
  }
965
- const { publicAccessToken, pendingVersion } = await this.resolveStartSession({
844
+ const { publicAccessToken } = await this.resolveStartSession({
966
845
  taskId: this.taskId,
967
846
  chatId,
968
847
  clientData: (this.defaultMetadata ?? {}),
969
848
  });
970
- if (pendingVersion) {
971
- this.emitEvent({
972
- type: "run-pending-version",
973
- chatId,
974
- timestamp: Date.now(),
975
- source: "start",
976
- });
977
- }
978
849
  const state = {
979
850
  publicAccessToken,
980
851
  isStreaming: false,
@@ -991,33 +862,6 @@ class TriggerChatTransport {
991
862
  const raw = this.resolveBaseURLFn(ctx);
992
863
  return raw.replace(/\/$/, "");
993
864
  }
994
- /**
995
- * `fetchClient` for an `out` subscription. Reads `this.fetchOverride` on
996
- * every call, not once per subscription: a watch-mode subscription
997
- * reconnects for the lifetime of the page, and `setFetch` may have swapped
998
- * the override in between.
999
- */
1000
- sseFetchClient(ctx) {
1001
- return ((input, init) => {
1002
- const override = this.fetchOverride;
1003
- if (!override)
1004
- return fetch(input, init);
1005
- if (typeof input === "string") {
1006
- return override(input, init ?? {}, ctx);
1007
- }
1008
- if (input instanceof URL) {
1009
- return override(input.toString(), init ?? {}, ctx);
1010
- }
1011
- // Request — preserve its url + intrinsic init, let any provided init
1012
- // override on top (matches fetch(Request, init) semantics).
1013
- return override(input.url, {
1014
- method: input.method,
1015
- headers: input.headers,
1016
- signal: input.signal,
1017
- ...(init ?? {}),
1018
- }, ctx);
1019
- });
1020
- }
1021
865
  async doFetch(ctx, url, init) {
1022
866
  return this.fetchOverride ? this.fetchOverride(url, init, ctx) : fetch(url, init);
1023
867
  }
@@ -1039,65 +883,24 @@ class TriggerChatTransport {
1039
883
  const err = new Error(`appendToSessionStream failed: ${response.status} ${text}`);
1040
884
  err.name = "TriggerApiError";
1041
885
  err.status = response.status;
1042
- let parsed;
1043
- try {
1044
- parsed = JSON.parse(text);
1045
- }
1046
- catch {
1047
- parsed = undefined;
1048
- }
1049
- if (typeof parsed?.code === "string")
1050
- err.code = parsed.code;
1051
- if (typeof parsed?.closedReason === "string")
1052
- err.closedReason = parsed.closedReason;
1053
886
  throw err;
1054
887
  }
1055
888
  // The appended record's `.in` seq, for correlating the response stream to
1056
889
  // this send. Omitted by older webapps / a lost idempotency claim.
1057
890
  const data = (await response.json().catch(() => undefined));
1058
- if (data?.pendingVersion === true) {
1059
- this.emitEvent({
1060
- type: "run-pending-version",
1061
- chatId,
1062
- timestamp: Date.now(),
1063
- source: "send",
1064
- });
1065
- }
1066
891
  return typeof data?.seq === "number" ? data.seq : undefined;
1067
892
  }
1068
893
  async callWithAuthRetry(chatId, state, op) {
1069
- if (state.closed) {
1070
- throw sessionClosedError(chatId, state.closedReason);
1071
- }
1072
- // Every attempt goes through here, so a closed-session refusal is terminal
1073
- // on whichever one hits it. Reaching a 409 from a later attempt and not
1074
- // marking it would leave local status open on a conversation the server
1075
- // has already ended.
1076
- const attempt = async (token) => {
1077
- try {
1078
- return await op(token);
1079
- }
1080
- catch (err) {
1081
- if (isSessionClosedError(err)) {
1082
- // Terminal. Retrying, refreshing the PAT, or recreating the session
1083
- // would all be wrong: the conversation is over.
1084
- this.markSessionClosed(chatId, state, sessionClosedReasonOf(err), "append");
1085
- }
1086
- throw err;
1087
- }
1088
- };
1089
894
  // 1) Try with the current PAT.
1090
895
  try {
1091
- return await attempt(state.publicAccessToken);
896
+ return await op(state.publicAccessToken);
1092
897
  }
1093
898
  catch (err) {
1094
- if (isSessionClosedError(err))
1095
- throw err;
1096
899
  if (isSessionNotFoundError(err)) {
1097
900
  // The cached PAT authenticated but the session doesn't exist here —
1098
901
  // recreate it and retry.
1099
902
  await this.recreateSession(chatId, state);
1100
- return await attempt(state.publicAccessToken);
903
+ return await op(state.publicAccessToken);
1101
904
  }
1102
905
  if (!isAuthError(err))
1103
906
  throw err;
@@ -1108,7 +911,7 @@ class TriggerChatTransport {
1108
911
  state.publicAccessToken = fresh;
1109
912
  this.notifySessionChange(chatId, state);
1110
913
  try {
1111
- return await attempt(fresh);
914
+ return await op(fresh);
1112
915
  }
1113
916
  catch (err) {
1114
917
  if (!isSessionNotFoundError(err))
@@ -1118,7 +921,7 @@ class TriggerChatTransport {
1118
921
  // state is stale (created in a different environment, or before the
1119
922
  // sessions upgrade). Recreate the session and retry once.
1120
923
  await this.recreateSession(chatId, state);
1121
- return await attempt(state.publicAccessToken);
924
+ return await op(state.publicAccessToken);
1122
925
  }
1123
926
  /**
1124
927
  * Repair a stale/phantom hydrated session. The cached state pointed at a
@@ -1134,19 +937,11 @@ class TriggerChatTransport {
1134
937
  if (!this.resolveStartSession) {
1135
938
  throw new Error("TriggerChatTransport: session not found and no `startSession` configured to recreate it. The stored session state for this chat may be stale (e.g. created in a different environment) — provide `startSession` or clear the stored session so a fresh one can be created.");
1136
939
  }
1137
- const { publicAccessToken, pendingVersion } = await this.resolveStartSession({
940
+ const { publicAccessToken } = await this.resolveStartSession({
1138
941
  taskId: this.taskId,
1139
942
  chatId,
1140
943
  clientData: (this.defaultMetadata ?? {}),
1141
944
  });
1142
- if (pendingVersion) {
1143
- this.emitEvent({
1144
- type: "run-pending-version",
1145
- chatId,
1146
- timestamp: Date.now(),
1147
- source: "start",
1148
- });
1149
- }
1150
945
  state.publicAccessToken = publicAccessToken;
1151
946
  state.lastEventId = undefined;
1152
947
  state.isStreaming = false;
@@ -1170,13 +965,8 @@ class TriggerChatTransport {
1170
965
  : internalAbort.signal;
1171
966
  if (abortSignal) {
1172
967
  abortSignal.addEventListener("abort", () => {
1173
- // A late abort (unmount, or the consumer dropping a drained stream)
1174
- // has no turn to stop: don't gate the next one, don't write a stop.
1175
- const outstanding = !state.outstandingTurnAbandoned &&
1176
- (state.isStreaming || state.activeInputSeq !== undefined);
1177
- if (options?.sendStopOnAbort !== false && outstanding && !internalAbort.signal.aborted) {
968
+ if (options?.sendStopOnAbort !== false) {
1178
969
  state.skipToTurnComplete = true;
1179
- state.supersededInputSeq = state.activeInputSeq;
1180
970
  this.appendInputChunk(chatId, state.publicAccessToken, this.serializeInputChunk({ kind: "stop" })).catch(() => { });
1181
971
  }
1182
972
  internalAbort.abort();
@@ -1236,7 +1026,26 @@ class TriggerChatTransport {
1236
1026
  })()
1237
1027
  : () => { };
1238
1028
  const sseCtx = { endpoint: "out", chatId };
1239
- const sseFetchClient = this.sseFetchClient(sseCtx);
1029
+ const fetchOverride = this.fetchOverride;
1030
+ const sseFetchClient = fetchOverride
1031
+ ? ((input, init) => {
1032
+ if (typeof input === "string") {
1033
+ return fetchOverride(input, init ?? {}, sseCtx);
1034
+ }
1035
+ if (input instanceof URL) {
1036
+ return fetchOverride(input.toString(), init ?? {}, sseCtx);
1037
+ }
1038
+ // Request — preserve its url + intrinsic init, let any
1039
+ // provided init override on top (matches fetch(Request, init)
1040
+ // semantics).
1041
+ return fetchOverride(input.url, {
1042
+ method: input.method,
1043
+ headers: input.headers,
1044
+ signal: input.signal,
1045
+ ...(init ?? {}),
1046
+ }, sseCtx);
1047
+ })
1048
+ : undefined;
1240
1049
  let sawFirstChunk = false;
1241
1050
  let sinceInSeq = options?.sinceInSeq;
1242
1051
  const connectSseOnce = async (token) => {
@@ -1437,41 +1246,11 @@ class TriggerChatTransport {
1437
1246
  continue;
1438
1247
  }
1439
1248
  }
1440
- if (state.skipToTurnComplete &&
1441
- controlValue !== v3_1.TRIGGER_CONTROL_SUBTYPE.SESSION_CLOSED &&
1442
- controlValue !== v3_1.TRIGGER_CONTROL_SUBTYPE.PENDING_VERSION) {
1443
- // Fall through on a boundary so the `sinceInSeq` check and the
1444
- // turn-completed emit below still run; content records are skipped.
1445
- // A close is terminal and a pending version is the gated turn's own
1446
- // handover status, so neither is ever gated away.
1447
- if (controlValue !== v3_1.TRIGGER_CONTROL_SUBTYPE.TURN_COMPLETE)
1448
- continue;
1449
- // A boundary older than the superseded turn is not its boundary —
1450
- // keep gating. A header-less boundary (legacy wire) still clears.
1451
- const gateCursorRaw = (0, v3_1.headerValue)(value.headers, v3_1.SESSION_IN_EVENT_ID_HEADER);
1452
- const gateCursor = gateCursorRaw !== undefined ? Number.parseInt(gateCursorRaw, 10) : NaN;
1453
- if (state.supersededInputSeq !== undefined &&
1454
- !Number.isNaN(gateCursor) &&
1455
- gateCursor < state.supersededInputSeq) {
1456
- continue;
1457
- }
1458
- state.skipToTurnComplete = false;
1459
- state.supersededInputSeq = undefined;
1460
- // This boundary is the new turn's own, so the gate swallowed its
1461
- // output: fail the turn instead of completing an empty answer, and
1462
- // leave nothing armed for the retry.
1463
- if (sinceInSeq !== undefined &&
1464
- !Number.isNaN(gateCursor) &&
1465
- gateCursor >= sinceInSeq) {
1466
- // The lost boundary may also carry the session close.
1467
- if ((0, v3_1.headerValue)(value.headers, v3_1.SESSION_CLOSED_HEADER) === "true") {
1468
- this.markSessionClosed(chatId, state, (0, v3_1.headerValue)(value.headers, v3_1.SESSION_CLOSED_REASON_HEADER), "stream");
1469
- }
1470
- state.activeInputSeq = undefined;
1471
- state.isStreaming = false;
1472
- this.notifySessionChange(chatId, state);
1473
- throw new Error("The previous turn's output was lost. Try again.");
1249
+ if (state.skipToTurnComplete) {
1250
+ if (controlValue === v3_1.TRIGGER_CONTROL_SUBTYPE.TURN_COMPLETE) {
1251
+ state.skipToTurnComplete = false;
1474
1252
  }
1253
+ continue;
1475
1254
  }
1476
1255
  if (controlValue === v3_1.TRIGGER_CONTROL_SUBTYPE.UPGRADE_REQUIRED) {
1477
1256
  // Server has already triggered the new run via
@@ -1480,26 +1259,6 @@ class TriggerChatTransport {
1480
1259
  // and keep reading.
1481
1260
  continue;
1482
1261
  }
1483
- if (controlValue === v3_1.TRIGGER_CONTROL_SUBTYPE.SESSION_CLOSED) {
1484
- this.markSessionClosed(chatId, state, (0, v3_1.headerValue)(value.headers, v3_1.SESSION_CLOSED_REASON_HEADER), "stream");
1485
- internalAbort.abort();
1486
- try {
1487
- controller.close();
1488
- }
1489
- catch {
1490
- /* already closed */
1491
- }
1492
- return;
1493
- }
1494
- if (controlValue === v3_1.TRIGGER_CONTROL_SUBTYPE.PENDING_VERSION) {
1495
- this.emitEvent({
1496
- type: "run-pending-version",
1497
- chatId,
1498
- timestamp: Date.now(),
1499
- source: "upgrade",
1500
- });
1501
- continue;
1502
- }
1503
1262
  if (controlValue === v3_1.TRIGGER_CONTROL_SUBTYPE.TURN_COMPLETE) {
1504
1263
  // Skip a turn-complete from an earlier turn (committed `.in` cursor
1505
1264
  // below this send's seq), e.g. an undo action that raced this send.
@@ -1510,11 +1269,6 @@ class TriggerChatTransport {
1510
1269
  continue;
1511
1270
  }
1512
1271
  }
1513
- // A close decided during this turn rides out on turn-complete —
1514
- // the last record a reader sees before it ends the stream.
1515
- if ((0, v3_1.headerValue)(value.headers, v3_1.SESSION_CLOSED_HEADER) === "true") {
1516
- this.markSessionClosed(chatId, state, (0, v3_1.headerValue)(value.headers, v3_1.SESSION_CLOSED_REASON_HEADER), "stream");
1517
- }
1518
1272
  const refreshedToken = (0, v3_1.headerValue)(value.headers, v3_1.PUBLIC_ACCESS_TOKEN_HEADER) ??
1519
1273
  legacyChunk?.publicAccessToken;
1520
1274
  if (refreshedToken) {