@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
@@ -22,7 +22,7 @@
22
22
  * }
23
23
  * ```
24
24
  */
25
- import { controlSubtype, headerValue, SESSION_CLOSED_HEADER, SESSION_CLOSED_REASON_HEADER, PUBLIC_ACCESS_TOKEN_HEADER, SESSION_IN_EVENT_ID_HEADER, SSEStreamSubscription, TRIGGER_CONTROL_SUBTYPE, } from "@trigger.dev/core/v3";
25
+ import { controlSubtype, headerValue, PUBLIC_ACCESS_TOKEN_HEADER, SESSION_IN_EVENT_ID_HEADER, SSEStreamSubscription, TRIGGER_CONTROL_SUBTYPE, } from "@trigger.dev/core/v3";
26
26
  function byteLength(body) {
27
27
  return new TextEncoder().encode(body).byteLength;
28
28
  }
@@ -40,32 +40,6 @@ function isAuthError(error) {
40
40
  const e = error;
41
41
  return e.name === "TriggerApiError" && (e.status === 401 || e.status === 403);
42
42
  }
43
- /**
44
- * Detect the closed-session refusal from `.in/append`: HTTP 409 with the
45
- * stable `session_closed` code. Terminal — never retried.
46
- */
47
- function isSessionClosedError(error) {
48
- if (error === null || typeof error !== "object")
49
- return false;
50
- const e = error;
51
- return e.name === "TriggerApiError" && e.status === 409 && e.code === "session_closed";
52
- }
53
- function sessionClosedReasonOf(error) {
54
- if (error === null || typeof error !== "object")
55
- return undefined;
56
- const reason = error.closedReason;
57
- return typeof reason === "string" ? reason : undefined;
58
- }
59
- /** The local form of the refusal, for sends the transport rejects itself. */
60
- function sessionClosedError(chatId, reason) {
61
- const err = new Error(reason ? `Chat session ${chatId} is closed: ${reason}` : `Chat session ${chatId} is closed`);
62
- err.name = "TriggerApiError";
63
- err.status = 409;
64
- err.code = "session_closed";
65
- if (reason)
66
- err.closedReason = reason;
67
- return err;
68
- }
69
43
  /**
70
44
  * Detect a 404 from a session-PAT-authed call — i.e. the session doesn't
71
45
  * exist in the current environment. Happens when hydrated session state
@@ -210,8 +184,6 @@ export class TriggerChatTransport {
210
184
  lastEventId: session.lastEventId,
211
185
  activeInputSeq: session.activeInputSeq,
212
186
  isStreaming: session.isStreaming,
213
- closed: session.closed,
214
- closedReason: session.closedReason,
215
187
  });
216
188
  }
217
189
  }
@@ -261,10 +233,6 @@ export class TriggerChatTransport {
261
233
  */
262
234
  sendMessages = async (options) => {
263
235
  const { trigger, chatId, messageId, messages, abortSignal, body, metadata } = options;
264
- const closedState = this.sessions.get(chatId);
265
- if (closedState?.closed) {
266
- throw sessionClosedError(chatId, closedState.closedReason);
267
- }
268
236
  if (this.coordinator) {
269
237
  if (this.coordinator.isReadOnly(chatId)) {
270
238
  throw new Error("This chat is active in another tab");
@@ -274,14 +242,6 @@ export class TriggerChatTransport {
274
242
  const mergedMetadata = this.defaultMetadata || metadata
275
243
  ? { ...(this.defaultMetadata ?? {}), ...(metadata ?? {}) }
276
244
  : undefined;
277
- // An action sent through `useChat`. `useChatActions` (or any caller) puts
278
- // it in `body.action`; sending it here rather than through
279
- // `transport.sendAction` means `useChat` owns the response stream, so an
280
- // action that becomes a turn renders the way a message turn does.
281
- const actionInBody = body?.action;
282
- if (actionInBody !== undefined) {
283
- return this.sendAction(chatId, actionInBody, { abortSignal, metadata: mergedMetadata });
284
- }
285
245
  // First-turn handover routing — when `headStart` is set AND no
286
246
  // session state exists yet for this chatId, POST the wire payload
287
247
  // to the customer's `chat.handover` route handler. The handler
@@ -344,9 +304,11 @@ export class TriggerChatTransport {
344
304
  activeStream.abort();
345
305
  this.activeStreams.delete(chatId);
346
306
  }
307
+ // A stop that never saw its TURN_COMPLETE leaves the flag set, and the new
308
+ // turn would be skipped record by record.
309
+ state.skipToTurnComplete = false;
347
310
  state.activeInputSeq = inSeq;
348
311
  state.isStreaming = true;
349
- state.outstandingTurnAbandoned = false;
350
312
  this.notifySessionChange(chatId, state);
351
313
  // Owning turn: aborting this live send stops the turn the user drives.
352
314
  return this.subscribeToSessionStream(state, abortSignal, chatId, {
@@ -418,16 +380,6 @@ export class TriggerChatTransport {
418
380
  };
419
381
  this.sessions.set(chatId, state);
420
382
  this.notifySessionChange(chatId, state);
421
- // Step 1 streams from the warm server either way; this says the agent run that owes step 2
422
- // is parked on an undeployed external deployment id.
423
- if (response.headers.get("X-Trigger-Chat-Pending-Version") === "1") {
424
- this.emitEvent({
425
- type: "run-pending-version",
426
- chatId,
427
- timestamp: Date.now(),
428
- source: "head-start",
429
- });
430
- }
431
383
  // Filter the parsed UIMessage stream:
432
384
  // - Drop control chunks (`trigger:turn-complete`,
433
385
  // `trigger:session-state`) before they reach AI SDK — they
@@ -594,9 +546,6 @@ export class TriggerChatTransport {
594
546
  const state = this.sessions.get(options.chatId);
595
547
  if (!state)
596
548
  return null;
597
- // A closed session has no further turns to resume.
598
- if (state.closed)
599
- return null;
600
549
  // Watch is a standing subscription: a settled session is exactly the
601
550
  // state it waits in, so a completed last turn must not block the resume.
602
551
  if (state.isStreaming === false && !this.watchMode)
@@ -641,13 +590,7 @@ export class TriggerChatTransport {
641
590
  catch {
642
591
  return false;
643
592
  }
644
- // Only gate when a sent turn is still outstanding. A stop at a boundary has
645
- // nothing to supersede, and gating it would swallow the next turn.
646
- if (!state.outstandingTurnAbandoned &&
647
- (state.isStreaming || state.activeInputSeq !== undefined)) {
648
- state.skipToTurnComplete = true;
649
- state.supersededInputSeq = state.activeInputSeq;
650
- }
593
+ state.skipToTurnComplete = true;
651
594
  const activeStream = this.activeStreams.get(chatId);
652
595
  if (activeStream) {
653
596
  activeStream.abort();
@@ -666,28 +609,15 @@ export class TriggerChatTransport {
666
609
  this.notifySessionChange(chatId, state);
667
610
  return true;
668
611
  };
669
- /**
670
- * Clear the supersede gate armed by a stop. Call this when the stopped
671
- * turn died without ever writing its `trigger:turn-complete` boundary,
672
- * otherwise the next turn stays gated forever.
673
- */
674
- clearSupersedeGate = (chatId) => {
675
- const state = this.sessions.get(chatId);
676
- if (!state)
677
- return;
678
- state.skipToTurnComplete = false;
679
- state.supersededInputSeq = undefined;
680
- state.outstandingTurnAbandoned = true;
681
- };
682
612
  /**
683
613
  * Send a custom action chunk (for `chat.agent`'s `actionSchema` /
684
- * `onAction` hook). An action is an edit: only `hydrateMessages` and
685
- * `onAction` fire on the agent side, and the returned stream completes
686
- * with `trigger:turn-complete` once the edit is persisted. When `onAction`
687
- * returns `chat.turn()` the turn's response follows on the same stream.
688
- * Per-action `metadata` is merged over the transport's `clientData`.
614
+ * `onAction` hook). Actions are not turns — only `hydrateMessages`
615
+ * and `onAction` fire on the agent side. The returned stream
616
+ * carries any model response `onAction` produced (when it returns a
617
+ * `StreamTextResult`); for `void`-returning side-effect-only actions
618
+ * the stream completes immediately with `trigger:turn-complete`.
689
619
  */
690
- sendAction = async (chatId, action, options) => {
620
+ sendAction = async (chatId, action) => {
691
621
  if (this.coordinator) {
692
622
  if (this.coordinator.isReadOnly(chatId)) {
693
623
  throw new Error("This chat is active in another tab");
@@ -699,9 +629,7 @@ export class TriggerChatTransport {
699
629
  chatId,
700
630
  trigger: "action",
701
631
  action,
702
- metadata: this.defaultMetadata || options?.metadata
703
- ? { ...(this.defaultMetadata ?? {}), ...(options?.metadata ?? {}) }
704
- : undefined,
632
+ metadata: this.defaultMetadata ?? undefined,
705
633
  };
706
634
  const body = this.serializeInputChunk({ kind: "message", payload: wirePayload });
707
635
  const partId = crypto.randomUUID();
@@ -716,14 +644,16 @@ export class TriggerChatTransport {
716
644
  activeStream.abort();
717
645
  this.activeStreams.delete(chatId);
718
646
  }
647
+ // A stop that never saw its TURN_COMPLETE leaves the flag set, and the new
648
+ // turn would be skipped record by record.
649
+ state.skipToTurnComplete = false;
719
650
  // Mark streaming + persist so a reload mid-action resumes (reconnectToStream
720
651
  // no-ops when the persisted session says isStreaming: false).
721
652
  state.activeInputSeq = inSeq;
722
653
  state.isStreaming = true;
723
- state.outstandingTurnAbandoned = false;
724
654
  this.notifySessionChange(chatId, state);
725
655
  // Owning action: aborting this send stops the turn the user drives.
726
- return this.subscribeToSessionStream(state, options?.abortSignal, chatId, {
656
+ return this.subscribeToSessionStream(state, undefined, chatId, {
727
657
  sinceInSeq: inSeq,
728
658
  sendStopOnAbort: true,
729
659
  });
@@ -816,22 +746,6 @@ export class TriggerChatTransport {
816
746
  setClientData(clientData) {
817
747
  this.defaultMetadata = clientData;
818
748
  }
819
- /**
820
- * Update the request-time callbacks. Same reason as `setClientData`:
821
- * a host that rebuilds these per render (a dashboard navigating between
822
- * projects) would otherwise keep hitting the endpoint captured at
823
- * construction. Every read goes through the live field, so the next
824
- * request uses the latest callback.
825
- */
826
- setAccessToken(accessToken) {
827
- this.resolveAccessToken = accessToken;
828
- }
829
- setStartSession(startSession) {
830
- this.resolveStartSession = startSession;
831
- }
832
- setFetch(fetchOverride) {
833
- this.fetchOverride = fetchOverride;
834
- }
835
749
  // -------------------------------------------------------------------------
836
750
  // Multi-tab coordination passthrough
837
751
  // -------------------------------------------------------------------------
@@ -879,42 +793,7 @@ export class TriggerChatTransport {
879
793
  lastEventId: state.lastEventId,
880
794
  activeInputSeq: state.activeInputSeq,
881
795
  isStreaming: state.isStreaming,
882
- closed: state.closed,
883
- closedReason: state.closedReason,
884
796
  });
885
- /**
886
- * Flip a session to closed and tell listeners once. Terminal: sends and
887
- * reconnects stop from here on, and there is no reopening.
888
- */
889
- markSessionClosed(chatId, state, reason, source) {
890
- if (state.closed)
891
- return;
892
- state.closed = true;
893
- if (reason)
894
- state.closedReason = reason;
895
- state.isStreaming = false;
896
- state.activeInputSeq = undefined;
897
- state.skipToTurnComplete = false;
898
- state.supersededInputSeq = undefined;
899
- this.emitEvent({
900
- type: "session-closed",
901
- chatId,
902
- timestamp: Date.now(),
903
- ...(reason ? { reason } : {}),
904
- source,
905
- });
906
- this.notifySessionChange(chatId, state);
907
- this.coordinator?.release(chatId);
908
- }
909
- /**
910
- * Whether the conversation has been closed — by the agent via
911
- * `chat.close()`, or from outside via `sessions.close()`. Closed is
912
- * one-way: the transport refuses further sends and reconnects, and the
913
- * transcript stays readable.
914
- */
915
- sessionStatus = (chatId) => this.sessions.get(chatId)?.closed ? "closed" : "open";
916
- /** The reason the session was closed, when one was given. */
917
- sessionClosedReason = (chatId) => this.sessions.get(chatId)?.closedReason;
918
797
  notifySessionChange(chatId, session) {
919
798
  if (!this._onSessionChange)
920
799
  return;
@@ -958,19 +837,11 @@ export class TriggerChatTransport {
958
837
  if (!this.resolveStartSession) {
959
838
  throw new Error("TriggerChatTransport: `startSession` is required to call `start()` / `preload()`. Either provide it or pre-hydrate the session via `sessions: { ... }`.");
960
839
  }
961
- const { publicAccessToken, pendingVersion } = await this.resolveStartSession({
840
+ const { publicAccessToken } = await this.resolveStartSession({
962
841
  taskId: this.taskId,
963
842
  chatId,
964
843
  clientData: (this.defaultMetadata ?? {}),
965
844
  });
966
- if (pendingVersion) {
967
- this.emitEvent({
968
- type: "run-pending-version",
969
- chatId,
970
- timestamp: Date.now(),
971
- source: "start",
972
- });
973
- }
974
845
  const state = {
975
846
  publicAccessToken,
976
847
  isStreaming: false,
@@ -987,33 +858,6 @@ export class TriggerChatTransport {
987
858
  const raw = this.resolveBaseURLFn(ctx);
988
859
  return raw.replace(/\/$/, "");
989
860
  }
990
- /**
991
- * `fetchClient` for an `out` subscription. Reads `this.fetchOverride` on
992
- * every call, not once per subscription: a watch-mode subscription
993
- * reconnects for the lifetime of the page, and `setFetch` may have swapped
994
- * the override in between.
995
- */
996
- sseFetchClient(ctx) {
997
- return ((input, init) => {
998
- const override = this.fetchOverride;
999
- if (!override)
1000
- return fetch(input, init);
1001
- if (typeof input === "string") {
1002
- return override(input, init ?? {}, ctx);
1003
- }
1004
- if (input instanceof URL) {
1005
- return override(input.toString(), init ?? {}, ctx);
1006
- }
1007
- // Request — preserve its url + intrinsic init, let any provided init
1008
- // override on top (matches fetch(Request, init) semantics).
1009
- return override(input.url, {
1010
- method: input.method,
1011
- headers: input.headers,
1012
- signal: input.signal,
1013
- ...(init ?? {}),
1014
- }, ctx);
1015
- });
1016
- }
1017
861
  async doFetch(ctx, url, init) {
1018
862
  return this.fetchOverride ? this.fetchOverride(url, init, ctx) : fetch(url, init);
1019
863
  }
@@ -1035,65 +879,24 @@ export class TriggerChatTransport {
1035
879
  const err = new Error(`appendToSessionStream failed: ${response.status} ${text}`);
1036
880
  err.name = "TriggerApiError";
1037
881
  err.status = response.status;
1038
- let parsed;
1039
- try {
1040
- parsed = JSON.parse(text);
1041
- }
1042
- catch {
1043
- parsed = undefined;
1044
- }
1045
- if (typeof parsed?.code === "string")
1046
- err.code = parsed.code;
1047
- if (typeof parsed?.closedReason === "string")
1048
- err.closedReason = parsed.closedReason;
1049
882
  throw err;
1050
883
  }
1051
884
  // The appended record's `.in` seq, for correlating the response stream to
1052
885
  // this send. Omitted by older webapps / a lost idempotency claim.
1053
886
  const data = (await response.json().catch(() => undefined));
1054
- if (data?.pendingVersion === true) {
1055
- this.emitEvent({
1056
- type: "run-pending-version",
1057
- chatId,
1058
- timestamp: Date.now(),
1059
- source: "send",
1060
- });
1061
- }
1062
887
  return typeof data?.seq === "number" ? data.seq : undefined;
1063
888
  }
1064
889
  async callWithAuthRetry(chatId, state, op) {
1065
- if (state.closed) {
1066
- throw sessionClosedError(chatId, state.closedReason);
1067
- }
1068
- // Every attempt goes through here, so a closed-session refusal is terminal
1069
- // on whichever one hits it. Reaching a 409 from a later attempt and not
1070
- // marking it would leave local status open on a conversation the server
1071
- // has already ended.
1072
- const attempt = async (token) => {
1073
- try {
1074
- return await op(token);
1075
- }
1076
- catch (err) {
1077
- if (isSessionClosedError(err)) {
1078
- // Terminal. Retrying, refreshing the PAT, or recreating the session
1079
- // would all be wrong: the conversation is over.
1080
- this.markSessionClosed(chatId, state, sessionClosedReasonOf(err), "append");
1081
- }
1082
- throw err;
1083
- }
1084
- };
1085
890
  // 1) Try with the current PAT.
1086
891
  try {
1087
- return await attempt(state.publicAccessToken);
892
+ return await op(state.publicAccessToken);
1088
893
  }
1089
894
  catch (err) {
1090
- if (isSessionClosedError(err))
1091
- throw err;
1092
895
  if (isSessionNotFoundError(err)) {
1093
896
  // The cached PAT authenticated but the session doesn't exist here —
1094
897
  // recreate it and retry.
1095
898
  await this.recreateSession(chatId, state);
1096
- return await attempt(state.publicAccessToken);
899
+ return await op(state.publicAccessToken);
1097
900
  }
1098
901
  if (!isAuthError(err))
1099
902
  throw err;
@@ -1104,7 +907,7 @@ export class TriggerChatTransport {
1104
907
  state.publicAccessToken = fresh;
1105
908
  this.notifySessionChange(chatId, state);
1106
909
  try {
1107
- return await attempt(fresh);
910
+ return await op(fresh);
1108
911
  }
1109
912
  catch (err) {
1110
913
  if (!isSessionNotFoundError(err))
@@ -1114,7 +917,7 @@ export class TriggerChatTransport {
1114
917
  // state is stale (created in a different environment, or before the
1115
918
  // sessions upgrade). Recreate the session and retry once.
1116
919
  await this.recreateSession(chatId, state);
1117
- return await attempt(state.publicAccessToken);
920
+ return await op(state.publicAccessToken);
1118
921
  }
1119
922
  /**
1120
923
  * Repair a stale/phantom hydrated session. The cached state pointed at a
@@ -1130,19 +933,11 @@ export class TriggerChatTransport {
1130
933
  if (!this.resolveStartSession) {
1131
934
  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.");
1132
935
  }
1133
- const { publicAccessToken, pendingVersion } = await this.resolveStartSession({
936
+ const { publicAccessToken } = await this.resolveStartSession({
1134
937
  taskId: this.taskId,
1135
938
  chatId,
1136
939
  clientData: (this.defaultMetadata ?? {}),
1137
940
  });
1138
- if (pendingVersion) {
1139
- this.emitEvent({
1140
- type: "run-pending-version",
1141
- chatId,
1142
- timestamp: Date.now(),
1143
- source: "start",
1144
- });
1145
- }
1146
941
  state.publicAccessToken = publicAccessToken;
1147
942
  state.lastEventId = undefined;
1148
943
  state.isStreaming = false;
@@ -1166,13 +961,8 @@ export class TriggerChatTransport {
1166
961
  : internalAbort.signal;
1167
962
  if (abortSignal) {
1168
963
  abortSignal.addEventListener("abort", () => {
1169
- // A late abort (unmount, or the consumer dropping a drained stream)
1170
- // has no turn to stop: don't gate the next one, don't write a stop.
1171
- const outstanding = !state.outstandingTurnAbandoned &&
1172
- (state.isStreaming || state.activeInputSeq !== undefined);
1173
- if (options?.sendStopOnAbort !== false && outstanding && !internalAbort.signal.aborted) {
964
+ if (options?.sendStopOnAbort !== false) {
1174
965
  state.skipToTurnComplete = true;
1175
- state.supersededInputSeq = state.activeInputSeq;
1176
966
  this.appendInputChunk(chatId, state.publicAccessToken, this.serializeInputChunk({ kind: "stop" })).catch(() => { });
1177
967
  }
1178
968
  internalAbort.abort();
@@ -1232,7 +1022,26 @@ export class TriggerChatTransport {
1232
1022
  })()
1233
1023
  : () => { };
1234
1024
  const sseCtx = { endpoint: "out", chatId };
1235
- const sseFetchClient = this.sseFetchClient(sseCtx);
1025
+ const fetchOverride = this.fetchOverride;
1026
+ const sseFetchClient = fetchOverride
1027
+ ? ((input, init) => {
1028
+ if (typeof input === "string") {
1029
+ return fetchOverride(input, init ?? {}, sseCtx);
1030
+ }
1031
+ if (input instanceof URL) {
1032
+ return fetchOverride(input.toString(), init ?? {}, sseCtx);
1033
+ }
1034
+ // Request — preserve its url + intrinsic init, let any
1035
+ // provided init override on top (matches fetch(Request, init)
1036
+ // semantics).
1037
+ return fetchOverride(input.url, {
1038
+ method: input.method,
1039
+ headers: input.headers,
1040
+ signal: input.signal,
1041
+ ...(init ?? {}),
1042
+ }, sseCtx);
1043
+ })
1044
+ : undefined;
1236
1045
  let sawFirstChunk = false;
1237
1046
  let sinceInSeq = options?.sinceInSeq;
1238
1047
  const connectSseOnce = async (token) => {
@@ -1433,41 +1242,11 @@ export class TriggerChatTransport {
1433
1242
  continue;
1434
1243
  }
1435
1244
  }
1436
- if (state.skipToTurnComplete &&
1437
- controlValue !== TRIGGER_CONTROL_SUBTYPE.SESSION_CLOSED &&
1438
- controlValue !== TRIGGER_CONTROL_SUBTYPE.PENDING_VERSION) {
1439
- // Fall through on a boundary so the `sinceInSeq` check and the
1440
- // turn-completed emit below still run; content records are skipped.
1441
- // A close is terminal and a pending version is the gated turn's own
1442
- // handover status, so neither is ever gated away.
1443
- if (controlValue !== TRIGGER_CONTROL_SUBTYPE.TURN_COMPLETE)
1444
- continue;
1445
- // A boundary older than the superseded turn is not its boundary —
1446
- // keep gating. A header-less boundary (legacy wire) still clears.
1447
- const gateCursorRaw = headerValue(value.headers, SESSION_IN_EVENT_ID_HEADER);
1448
- const gateCursor = gateCursorRaw !== undefined ? Number.parseInt(gateCursorRaw, 10) : NaN;
1449
- if (state.supersededInputSeq !== undefined &&
1450
- !Number.isNaN(gateCursor) &&
1451
- gateCursor < state.supersededInputSeq) {
1452
- continue;
1453
- }
1454
- state.skipToTurnComplete = false;
1455
- state.supersededInputSeq = undefined;
1456
- // This boundary is the new turn's own, so the gate swallowed its
1457
- // output: fail the turn instead of completing an empty answer, and
1458
- // leave nothing armed for the retry.
1459
- if (sinceInSeq !== undefined &&
1460
- !Number.isNaN(gateCursor) &&
1461
- gateCursor >= sinceInSeq) {
1462
- // The lost boundary may also carry the session close.
1463
- if (headerValue(value.headers, SESSION_CLOSED_HEADER) === "true") {
1464
- this.markSessionClosed(chatId, state, headerValue(value.headers, SESSION_CLOSED_REASON_HEADER), "stream");
1465
- }
1466
- state.activeInputSeq = undefined;
1467
- state.isStreaming = false;
1468
- this.notifySessionChange(chatId, state);
1469
- throw new Error("The previous turn's output was lost. Try again.");
1245
+ if (state.skipToTurnComplete) {
1246
+ if (controlValue === TRIGGER_CONTROL_SUBTYPE.TURN_COMPLETE) {
1247
+ state.skipToTurnComplete = false;
1470
1248
  }
1249
+ continue;
1471
1250
  }
1472
1251
  if (controlValue === TRIGGER_CONTROL_SUBTYPE.UPGRADE_REQUIRED) {
1473
1252
  // Server has already triggered the new run via
@@ -1476,26 +1255,6 @@ export class TriggerChatTransport {
1476
1255
  // and keep reading.
1477
1256
  continue;
1478
1257
  }
1479
- if (controlValue === TRIGGER_CONTROL_SUBTYPE.SESSION_CLOSED) {
1480
- this.markSessionClosed(chatId, state, headerValue(value.headers, SESSION_CLOSED_REASON_HEADER), "stream");
1481
- internalAbort.abort();
1482
- try {
1483
- controller.close();
1484
- }
1485
- catch {
1486
- /* already closed */
1487
- }
1488
- return;
1489
- }
1490
- if (controlValue === TRIGGER_CONTROL_SUBTYPE.PENDING_VERSION) {
1491
- this.emitEvent({
1492
- type: "run-pending-version",
1493
- chatId,
1494
- timestamp: Date.now(),
1495
- source: "upgrade",
1496
- });
1497
- continue;
1498
- }
1499
1258
  if (controlValue === TRIGGER_CONTROL_SUBTYPE.TURN_COMPLETE) {
1500
1259
  // Skip a turn-complete from an earlier turn (committed `.in` cursor
1501
1260
  // below this send's seq), e.g. an undo action that raced this send.
@@ -1506,11 +1265,6 @@ export class TriggerChatTransport {
1506
1265
  continue;
1507
1266
  }
1508
1267
  }
1509
- // A close decided during this turn rides out on turn-complete —
1510
- // the last record a reader sees before it ends the stream.
1511
- if (headerValue(value.headers, SESSION_CLOSED_HEADER) === "true") {
1512
- this.markSessionClosed(chatId, state, headerValue(value.headers, SESSION_CLOSED_REASON_HEADER), "stream");
1513
- }
1514
1268
  const refreshedToken = headerValue(value.headers, PUBLIC_ACCESS_TOKEN_HEADER) ??
1515
1269
  legacyChunk?.publicAccessToken;
1516
1270
  if (refreshedToken) {