@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/esm/v3/chat.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* }
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
|
-
import { controlSubtype, headerValue,
|
|
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
|
-
|
|
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).
|
|
685
|
-
* `onAction` fire on the agent side
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
1438
|
-
|
|
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) {
|