@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.
- package/dist/commonjs/imports/ai-runtime-cjs.cjs.map +1 -1
- package/dist/commonjs/imports/ai-runtime.js +0 -2
- package/dist/commonjs/v3/ai.d.ts +16 -199
- package/dist/commonjs/v3/ai.js +102 -983
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/chat-client.d.ts +2 -3
- package/dist/commonjs/v3/chat-client.js +5 -31
- package/dist/commonjs/v3/chat-client.js.map +1 -1
- package/dist/commonjs/v3/chat-react.d.ts +0 -34
- package/dist/commonjs/v3/chat-react.js +1 -47
- package/dist/commonjs/v3/chat-react.js.map +1 -1
- package/dist/commonjs/v3/chat-server.d.ts +6 -42
- package/dist/commonjs/v3/chat-server.js +7 -52
- package/dist/commonjs/v3/chat-server.js.map +1 -1
- package/dist/commonjs/v3/chat.d.ts +10 -81
- package/dist/commonjs/v3/chat.js +46 -292
- package/dist/commonjs/v3/chat.js.map +1 -1
- package/dist/commonjs/v3/sessions.d.ts +2 -15
- package/dist/commonjs/v3/sessions.js +1 -12
- package/dist/commonjs/v3/sessions.js.map +1 -1
- package/dist/commonjs/v3/shared.js +36 -30
- package/dist/commonjs/v3/shared.js.map +1 -1
- package/dist/commonjs/v3/test/mock-chat-agent.d.ts +0 -43
- package/dist/commonjs/v3/test/mock-chat-agent.js +0 -90
- package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
- package/dist/commonjs/v3/test/test-session-handle.js +0 -6
- package/dist/commonjs/v3/test/test-session-handle.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/imports/ai-runtime.d.ts +2 -2
- package/dist/esm/imports/ai-runtime.js +2 -2
- package/dist/esm/imports/ai-runtime.js.map +1 -1
- package/dist/esm/v3/ai.d.ts +16 -199
- package/dist/esm/v3/ai.js +103 -984
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/chat-client.d.ts +2 -3
- package/dist/esm/v3/chat-client.js +5 -31
- package/dist/esm/v3/chat-client.js.map +1 -1
- package/dist/esm/v3/chat-react.d.ts +0 -34
- package/dist/esm/v3/chat-react.js +1 -46
- package/dist/esm/v3/chat-react.js.map +1 -1
- package/dist/esm/v3/chat-server.d.ts +6 -42
- package/dist/esm/v3/chat-server.js +8 -53
- package/dist/esm/v3/chat-server.js.map +1 -1
- package/dist/esm/v3/chat.d.ts +10 -81
- package/dist/esm/v3/chat.js +47 -293
- package/dist/esm/v3/chat.js.map +1 -1
- package/dist/esm/v3/sessions.d.ts +2 -15
- package/dist/esm/v3/sessions.js +1 -11
- package/dist/esm/v3/sessions.js.map +1 -1
- package/dist/esm/v3/shared.js +23 -17
- package/dist/esm/v3/shared.js.map +1 -1
- package/dist/esm/v3/test/mock-chat-agent.d.ts +0 -43
- package/dist/esm/v3/test/mock-chat-agent.js +2 -92
- package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
- package/dist/esm/v3/test/test-session-handle.js +0 -6
- package/dist/esm/v3/test/test-session-handle.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/docs/ai-chat/actions.mdx +23 -55
- package/docs/ai-chat/anatomy.mdx +3 -3
- package/docs/ai-chat/backend.mdx +48 -125
- package/docs/ai-chat/background-injection.mdx +19 -67
- package/docs/ai-chat/client-protocol.mdx +4 -5
- package/docs/ai-chat/compaction.mdx +7 -11
- package/docs/ai-chat/custom-agents.mdx +0 -23
- package/docs/ai-chat/fast-starts.mdx +20 -27
- package/docs/ai-chat/frontend.mdx +14 -17
- package/docs/ai-chat/migrating-from-a-route-handler.mdx +14 -16
- package/docs/ai-chat/patterns/skills.mdx +10 -7
- package/docs/ai-chat/patterns/version-upgrades.mdx +6 -79
- package/docs/ai-chat/pending-messages.mdx +3 -3
- package/docs/ai-chat/prompt-caching.mdx +25 -23
- package/docs/ai-chat/quick-start.mdx +11 -11
- package/docs/ai-chat/reference.mdx +5 -12
- package/docs/ai-chat/sessions.mdx +1 -6
- package/docs/ai-chat/testing.mdx +1 -2
- package/docs/ai-chat/tools.mdx +13 -18
- package/docs/ai-chat/upgrade-guide.mdx +2 -2
- package/docs/apikeys.mdx +45 -27
- package/docs/deployment/overview.mdx +8 -4
- package/docs/deployment/preview-branches.mdx +4 -4
- package/docs/deployment/version-skew-protection.mdx +0 -62
- package/docs/manual-setup.mdx +7 -7
- package/docs/mcp-tools.mdx +0 -9
- package/docs/quick-start.mdx +3 -3
- package/docs/realtime/auth.mdx +1 -1
- package/docs/self-hosting/security.mdx +0 -5
- package/docs/tasks/scheduled.mdx +0 -24
- package/docs/triggering.mdx +1 -1
- package/package.json +4 -4
- package/skills/trigger-authoring-chat-agent/SKILL.md +27 -38
- package/skills/trigger-chat-agent-advanced/SKILL.md +12 -31
- package/dist/commonjs/v3/chatVersionSkew.d.ts +0 -12
- package/dist/commonjs/v3/chatVersionSkew.js +0 -30
- package/dist/commonjs/v3/chatVersionSkew.js.map +0 -1
- package/dist/commonjs/v3/externalDeploymentId.d.ts +0 -23
- package/dist/commonjs/v3/externalDeploymentId.js +0 -43
- package/dist/commonjs/v3/externalDeploymentId.js.map +0 -1
- package/dist/esm/v3/chatVersionSkew.d.ts +0 -12
- package/dist/esm/v3/chatVersionSkew.js +0 -27
- package/dist/esm/v3/chatVersionSkew.js.map +0 -1
- package/dist/esm/v3/externalDeploymentId.d.ts +0 -23
- package/dist/esm/v3/externalDeploymentId.js +0 -38
- package/dist/esm/v3/externalDeploymentId.js.map +0 -1
- package/docs/ai-chat/patterns/native-compaction.mdx +0 -310
- package/docs/reports.mdx +0 -157
- package/docs/troubleshooting-zod.mdx +0 -158
package/dist/commonjs/v3/chat.js
CHANGED
|
@@ -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
|
-
|
|
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).
|
|
689
|
-
* `onAction` fire on the agent side
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
1442
|
-
|
|
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) {
|