@trigger.dev/sdk 0.0.0-prerelease-20260304181730 → 0.0.0-prerelease-20260305142821

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.
@@ -516,6 +516,7 @@ const sampleChunks = [
516
516
  (0, vitest_1.describe)("multiple sessions", () => {
517
517
  (0, vitest_1.it)("should track multiple chat sessions independently", async () => {
518
518
  let callCount = 0;
519
+ const turnCompleteChunk = { type: "__trigger_turn_complete" };
519
520
  global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
520
521
  const urlStr = typeof url === "string" ? url : url.toString();
521
522
  if (urlStr.includes("/trigger")) {
@@ -529,7 +530,9 @@ const sampleChunks = [
529
530
  });
530
531
  }
531
532
  if (urlStr.includes("/realtime/v1/streams/")) {
532
- return new Response(createSSEStream(""), {
533
+ // Include turn-complete chunk so the session is preserved
534
+ const chunks = [...sampleChunks, turnCompleteChunk];
535
+ return new Response(createSSEStream(sseEncode(chunks)), {
533
536
  status: 200,
534
537
  headers: {
535
538
  "content-type": "text/event-stream",
@@ -544,21 +547,25 @@ const sampleChunks = [
544
547
  accessToken: "token",
545
548
  baseURL: "https://api.test.trigger.dev",
546
549
  });
547
- // Start two independent chat sessions
548
- await transport.sendMessages({
550
+ // Start two independent chat sessions and consume the streams
551
+ const s1 = await transport.sendMessages({
549
552
  trigger: "submit-message",
550
553
  chatId: "session-a",
551
554
  messageId: undefined,
552
555
  messages: [createUserMessage("Hello A")],
553
556
  abortSignal: undefined,
554
557
  });
555
- await transport.sendMessages({
558
+ const r1 = s1.getReader();
559
+ while (!(await r1.read()).done) { }
560
+ const s2 = await transport.sendMessages({
556
561
  trigger: "submit-message",
557
562
  chatId: "session-b",
558
563
  messageId: undefined,
559
564
  messages: [createUserMessage("Hello B")],
560
565
  abortSignal: undefined,
561
566
  });
567
+ const r2 = s2.getReader();
568
+ while (!(await r2.read()).done) { }
562
569
  // Both sessions should be independently reconnectable
563
570
  const streamA = await transport.reconnectToStream({ chatId: "session-a" });
564
571
  const streamB = await transport.reconnectToStream({ chatId: "session-b" });
@@ -727,11 +734,7 @@ const sampleChunks = [
727
734
  });
728
735
  (0, vitest_1.describe)("lastEventId tracking", () => {
729
736
  (0, vitest_1.it)("should pass lastEventId to SSE subscription on subsequent turns", async () => {
730
- const controlChunk = {
731
- type: "__trigger_waitpoint_ready",
732
- tokenId: "wp_token_eid",
733
- publicAccessToken: "wp_access_eid",
734
- };
737
+ const turnCompleteChunk = { type: "__trigger_turn_complete" };
735
738
  let triggerCallCount = 0;
736
739
  const streamFetchCalls = [];
737
740
  global.fetch = vitest_1.vi.fn().mockImplementation(async (url, init) => {
@@ -746,8 +749,9 @@ const sampleChunks = [
746
749
  },
747
750
  });
748
751
  }
749
- if (urlStr.includes("/api/v1/waitpoints/tokens/") && urlStr.includes("/complete")) {
750
- return new Response(JSON.stringify({ success: true }), {
752
+ // Handle input stream sends (for second message)
753
+ if (urlStr.includes("/realtime/v1/streams/") && urlStr.includes("/input/")) {
754
+ return new Response(JSON.stringify({ ok: true }), {
751
755
  status: 200,
752
756
  headers: { "content-type": "application/json" },
753
757
  });
@@ -760,7 +764,7 @@ const sampleChunks = [
760
764
  const chunks = [
761
765
  ...sampleChunks,
762
766
  { type: "finish", id: "part-1" },
763
- controlChunk,
767
+ turnCompleteChunk,
764
768
  ];
765
769
  return new Response(createSSEStream(sseEncode(chunks)), {
766
770
  status: 200,
@@ -791,7 +795,7 @@ const sampleChunks = [
791
795
  if (done)
792
796
  break;
793
797
  }
794
- // Second message — completes the waitpoint
798
+ // Second message — sends via input stream
795
799
  const stream2 = await transport.sendMessages({
796
800
  trigger: "submit-message",
797
801
  chatId: "chat-eid",
@@ -812,13 +816,125 @@ const sampleChunks = [
812
816
  (0, vitest_1.expect)(secondStreamHeaders["Last-Event-ID"]).toBeDefined();
813
817
  });
814
818
  });
819
+ (0, vitest_1.describe)("minimal wire payloads", () => {
820
+ (0, vitest_1.it)("should send only new messages via input stream on turn 2+", async () => {
821
+ const turnCompleteChunk = { type: "__trigger_turn_complete" };
822
+ const inputStreamPayloads = [];
823
+ global.fetch = vitest_1.vi.fn().mockImplementation(async (url, init) => {
824
+ const urlStr = typeof url === "string" ? url : url.toString();
825
+ if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
826
+ return new Response(JSON.stringify({ id: "run_minimal" }), {
827
+ status: 200,
828
+ headers: {
829
+ "content-type": "application/json",
830
+ "x-trigger-jwt": "pub_token_minimal",
831
+ },
832
+ });
833
+ }
834
+ // Capture input stream payloads (ApiClient wraps in { data: ... })
835
+ if (urlStr.includes("/realtime/v1/streams/") && urlStr.includes("/input/")) {
836
+ const body = JSON.parse(init?.body);
837
+ inputStreamPayloads.push(body.data);
838
+ return new Response(JSON.stringify({ ok: true }), {
839
+ status: 200,
840
+ headers: { "content-type": "application/json" },
841
+ });
842
+ }
843
+ if (urlStr.includes("/realtime/v1/streams/")) {
844
+ const chunks = [
845
+ ...sampleChunks,
846
+ turnCompleteChunk,
847
+ ];
848
+ return new Response(createSSEStream(sseEncode(chunks)), {
849
+ status: 200,
850
+ headers: {
851
+ "content-type": "text/event-stream",
852
+ "X-Stream-Version": "v1",
853
+ },
854
+ });
855
+ }
856
+ throw new Error(`Unexpected fetch URL: ${urlStr}`);
857
+ });
858
+ const transport = new chat_js_1.TriggerChatTransport({
859
+ task: "my-task",
860
+ accessToken: "token",
861
+ baseURL: "https://api.test.trigger.dev",
862
+ });
863
+ const userMsg1 = createUserMessage("Hello");
864
+ const assistantMsg = createAssistantMessage("Hi there!");
865
+ const userMsg2 = createUserMessage("What's up?");
866
+ // Turn 1 — triggers a new run with full history
867
+ const stream1 = await transport.sendMessages({
868
+ trigger: "submit-message",
869
+ chatId: "chat-minimal",
870
+ messageId: undefined,
871
+ messages: [userMsg1],
872
+ abortSignal: undefined,
873
+ });
874
+ const r1 = stream1.getReader();
875
+ while (!(await r1.read()).done) { }
876
+ // Turn 2 — sends via input stream, should only include NEW messages
877
+ const stream2 = await transport.sendMessages({
878
+ trigger: "submit-message",
879
+ chatId: "chat-minimal",
880
+ messageId: undefined,
881
+ messages: [userMsg1, assistantMsg, userMsg2],
882
+ abortSignal: undefined,
883
+ });
884
+ const r2 = stream2.getReader();
885
+ while (!(await r2.read()).done) { }
886
+ // Verify: the input stream payload should only contain the new user message
887
+ (0, vitest_1.expect)(inputStreamPayloads).toHaveLength(1);
888
+ const sentPayload = inputStreamPayloads[0];
889
+ // Only the new user message should be sent (backend already has the assistant response)
890
+ (0, vitest_1.expect)(sentPayload.messages).toHaveLength(1);
891
+ (0, vitest_1.expect)(sentPayload.messages[0]).toEqual(userMsg2);
892
+ });
893
+ (0, vitest_1.it)("should send full history on first message (trigger)", async () => {
894
+ let triggerPayload;
895
+ global.fetch = vitest_1.vi.fn().mockImplementation(async (url, init) => {
896
+ const urlStr = typeof url === "string" ? url : url.toString();
897
+ if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
898
+ triggerPayload = JSON.parse(init?.body);
899
+ return new Response(JSON.stringify({ id: "run_full" }), {
900
+ status: 200,
901
+ headers: {
902
+ "content-type": "application/json",
903
+ "x-trigger-jwt": "pub_token_full",
904
+ },
905
+ });
906
+ }
907
+ if (urlStr.includes("/realtime/v1/streams/")) {
908
+ return new Response(createSSEStream(sseEncode(sampleChunks)), {
909
+ status: 200,
910
+ headers: {
911
+ "content-type": "text/event-stream",
912
+ "X-Stream-Version": "v1",
913
+ },
914
+ });
915
+ }
916
+ throw new Error(`Unexpected fetch URL: ${urlStr}`);
917
+ });
918
+ const transport = new chat_js_1.TriggerChatTransport({
919
+ task: "my-task",
920
+ accessToken: "token",
921
+ baseURL: "https://api.test.trigger.dev",
922
+ });
923
+ const messages = [createUserMessage("Hello"), createAssistantMessage("Hi!"), createUserMessage("More")];
924
+ await transport.sendMessages({
925
+ trigger: "submit-message",
926
+ chatId: "chat-full",
927
+ messageId: undefined,
928
+ messages,
929
+ abortSignal: undefined,
930
+ });
931
+ // First message always sends full history via trigger
932
+ (0, vitest_1.expect)(triggerPayload.payload.messages).toHaveLength(3);
933
+ });
934
+ });
815
935
  (0, vitest_1.describe)("AbortController cleanup", () => {
816
936
  (0, vitest_1.it)("should terminate SSE connection after intercepting control chunk", async () => {
817
- const controlChunk = {
818
- type: "__trigger_waitpoint_ready",
819
- tokenId: "wp_token_abort",
820
- publicAccessToken: "wp_access_abort",
821
- };
937
+ const controlChunk = { type: "__trigger_turn_complete" };
822
938
  let streamAborted = false;
823
939
  global.fetch = vitest_1.vi.fn().mockImplementation(async (url, init) => {
824
940
  const urlStr = typeof url === "string" ? url : url.toString();
@@ -925,14 +1041,10 @@ const sampleChunks = [
925
1041
  });
926
1042
  (0, vitest_1.expect)(tokenCallCount).toBe(1);
927
1043
  });
928
- (0, vitest_1.it)("should resolve async token for waitpoint completion flow", async () => {
929
- const controlChunk = {
930
- type: "__trigger_waitpoint_ready",
931
- tokenId: "wp_token_async",
932
- publicAccessToken: "wp_access_async",
933
- };
1044
+ (0, vitest_1.it)("should not resolve async token for input stream send flow", async () => {
1045
+ const turnCompleteChunk = { type: "__trigger_turn_complete" };
934
1046
  let tokenCallCount = 0;
935
- let completeWaitpointCalled = false;
1047
+ let inputStreamSendCalled = false;
936
1048
  global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
937
1049
  const urlStr = typeof url === "string" ? url : url.toString();
938
1050
  if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
@@ -944,9 +1056,10 @@ const sampleChunks = [
944
1056
  },
945
1057
  });
946
1058
  }
947
- if (urlStr.includes("/api/v1/waitpoints/tokens/") && urlStr.includes("/complete")) {
948
- completeWaitpointCalled = true;
949
- return new Response(JSON.stringify({ success: true }), {
1059
+ // Handle input stream sends
1060
+ if (urlStr.includes("/realtime/v1/streams/") && urlStr.includes("/input/")) {
1061
+ inputStreamSendCalled = true;
1062
+ return new Response(JSON.stringify({ ok: true }), {
950
1063
  status: 200,
951
1064
  headers: { "content-type": "application/json" },
952
1065
  });
@@ -955,7 +1068,7 @@ const sampleChunks = [
955
1068
  const chunks = [
956
1069
  ...sampleChunks,
957
1070
  { type: "finish", id: "part-1" },
958
- controlChunk,
1071
+ turnCompleteChunk,
959
1072
  ];
960
1073
  return new Response(createSSEStream(sseEncode(chunks)), {
961
1074
  status: 200,
@@ -991,7 +1104,7 @@ const sampleChunks = [
991
1104
  break;
992
1105
  }
993
1106
  const firstTokenCount = tokenCallCount;
994
- // Second message — should complete waitpoint (does NOT call async token)
1107
+ // Second message — should send via input stream (does NOT call async token)
995
1108
  const stream2 = await transport.sendMessages({
996
1109
  trigger: "submit-message",
997
1110
  chatId: "chat-async-wp",
@@ -1005,18 +1118,14 @@ const sampleChunks = [
1005
1118
  if (done)
1006
1119
  break;
1007
1120
  }
1008
- // Token function should NOT have been called again for the waitpoint path
1121
+ // Token function should NOT have been called again for the input stream path
1009
1122
  (0, vitest_1.expect)(tokenCallCount).toBe(firstTokenCount);
1010
- (0, vitest_1.expect)(completeWaitpointCalled).toBe(true);
1123
+ (0, vitest_1.expect)(inputStreamSendCalled).toBe(true);
1011
1124
  });
1012
1125
  });
1013
- (0, vitest_1.describe)("single-run mode (waitpoint loop)", () => {
1014
- (0, vitest_1.it)("should store waitpoint token from control chunk and not forward it to consumer", async () => {
1015
- const controlChunk = {
1016
- type: "__trigger_waitpoint_ready",
1017
- tokenId: "wp_token_123",
1018
- publicAccessToken: "wp_access_abc",
1019
- };
1126
+ (0, vitest_1.describe)("single-run mode (input stream loop)", () => {
1127
+ (0, vitest_1.it)("should not forward turn-complete control chunk to consumer", async () => {
1128
+ const turnCompleteChunk = { type: "__trigger_turn_complete" };
1020
1129
  global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
1021
1130
  const urlStr = typeof url === "string" ? url : url.toString();
1022
1131
  if (urlStr.includes("/trigger")) {
@@ -1032,7 +1141,7 @@ const sampleChunks = [
1032
1141
  const chunks = [
1033
1142
  ...sampleChunks,
1034
1143
  { type: "finish", id: "part-1" },
1035
- controlChunk,
1144
+ turnCompleteChunk,
1036
1145
  ];
1037
1146
  return new Response(createSSEStream(sseEncode(chunks)), {
1038
1147
  status: 200,
@@ -1068,16 +1177,12 @@ const sampleChunks = [
1068
1177
  // All AI SDK chunks should be forwarded
1069
1178
  (0, vitest_1.expect)(receivedChunks.length).toBe(sampleChunks.length + 1); // +1 for the finish chunk
1070
1179
  // Control chunk should not be in the output
1071
- (0, vitest_1.expect)(receivedChunks.every((c) => c.type !== "__trigger_waitpoint_ready")).toBe(true);
1180
+ (0, vitest_1.expect)(receivedChunks.every((c) => c.type !== "__trigger_turn_complete")).toBe(true);
1072
1181
  });
1073
- (0, vitest_1.it)("should complete waitpoint token on second message instead of triggering a new run", async () => {
1074
- const controlChunk = {
1075
- type: "__trigger_waitpoint_ready",
1076
- tokenId: "wp_token_456",
1077
- publicAccessToken: "wp_access_def",
1078
- };
1182
+ (0, vitest_1.it)("should send via input stream on second message instead of triggering a new run", async () => {
1183
+ const turnCompleteChunk = { type: "__trigger_turn_complete" };
1079
1184
  let triggerCallCount = 0;
1080
- let completeWaitpointCalled = false;
1185
+ let inputStreamSendCalled = false;
1081
1186
  global.fetch = vitest_1.vi.fn().mockImplementation(async (url, init) => {
1082
1187
  const urlStr = typeof url === "string" ? url : url.toString();
1083
1188
  if (urlStr.includes("/api/v1/tasks/") && urlStr.includes("/trigger")) {
@@ -1090,10 +1195,10 @@ const sampleChunks = [
1090
1195
  },
1091
1196
  });
1092
1197
  }
1093
- // Handle waitpoint token completion
1094
- if (urlStr.includes("/api/v1/waitpoints/tokens/") && urlStr.includes("/complete")) {
1095
- completeWaitpointCalled = true;
1096
- return new Response(JSON.stringify({ success: true }), {
1198
+ // Handle input stream sends
1199
+ if (urlStr.includes("/realtime/v1/streams/") && urlStr.includes("/input/")) {
1200
+ inputStreamSendCalled = true;
1201
+ return new Response(JSON.stringify({ ok: true }), {
1097
1202
  status: 200,
1098
1203
  headers: { "content-type": "application/json" },
1099
1204
  });
@@ -1102,7 +1207,7 @@ const sampleChunks = [
1102
1207
  const chunks = [
1103
1208
  ...sampleChunks,
1104
1209
  { type: "finish", id: "part-1" },
1105
- controlChunk,
1210
+ turnCompleteChunk,
1106
1211
  ];
1107
1212
  return new Response(createSSEStream(sseEncode(chunks)), {
1108
1213
  status: 200,
@@ -1127,7 +1232,7 @@ const sampleChunks = [
1127
1232
  messages: [createUserMessage("Hello")],
1128
1233
  abortSignal: undefined,
1129
1234
  });
1130
- // Consume stream to capture the control chunk
1235
+ // Consume stream
1131
1236
  const reader1 = stream1.getReader();
1132
1237
  while (true) {
1133
1238
  const { done } = await reader1.read();
@@ -1135,7 +1240,7 @@ const sampleChunks = [
1135
1240
  break;
1136
1241
  }
1137
1242
  (0, vitest_1.expect)(triggerCallCount).toBe(1);
1138
- // Second message — should complete the waitpoint instead of triggering
1243
+ // Second message — should send via input stream instead of triggering
1139
1244
  const stream2 = await transport.sendMessages({
1140
1245
  trigger: "submit-message",
1141
1246
  chatId: "chat-resume",
@@ -1152,8 +1257,8 @@ const sampleChunks = [
1152
1257
  }
1153
1258
  // Should NOT have triggered a second run
1154
1259
  (0, vitest_1.expect)(triggerCallCount).toBe(1);
1155
- // Should have completed the waitpoint
1156
- (0, vitest_1.expect)(completeWaitpointCalled).toBe(true);
1260
+ // Should have sent via input stream
1261
+ (0, vitest_1.expect)(inputStreamSendCalled).toBe(true);
1157
1262
  });
1158
1263
  (0, vitest_1.it)("should fall back to triggering a new run if stream closes without control chunk", async () => {
1159
1264
  let triggerCallCount = 0;
@@ -1217,12 +1322,8 @@ const sampleChunks = [
1217
1322
  // Should have triggered a second run
1218
1323
  (0, vitest_1.expect)(triggerCallCount).toBe(2);
1219
1324
  });
1220
- (0, vitest_1.it)("should fall back to new run when completing waitpoint fails", async () => {
1221
- const controlChunk = {
1222
- type: "__trigger_waitpoint_ready",
1223
- tokenId: "wp_token_fail",
1224
- publicAccessToken: "wp_access_fail",
1225
- };
1325
+ (0, vitest_1.it)("should fall back to new run when sendInputStream fails", async () => {
1326
+ const turnCompleteChunk = { type: "__trigger_turn_complete" };
1226
1327
  let triggerCallCount = 0;
1227
1328
  global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
1228
1329
  const urlStr = typeof url === "string" ? url : url.toString();
@@ -1236,22 +1337,19 @@ const sampleChunks = [
1236
1337
  },
1237
1338
  });
1238
1339
  }
1239
- // Waitpoint completion fails
1240
- if (urlStr.includes("/api/v1/waitpoints/tokens/") && urlStr.includes("/complete")) {
1241
- return new Response(JSON.stringify({ error: "Token expired" }), {
1242
- status: 400,
1340
+ // Input stream send fails
1341
+ if (urlStr.includes("/realtime/v1/streams/") && urlStr.includes("/input/")) {
1342
+ return new Response(JSON.stringify({ error: "Run not found" }), {
1343
+ status: 404,
1243
1344
  headers: { "content-type": "application/json" },
1244
1345
  });
1245
1346
  }
1246
1347
  if (urlStr.includes("/realtime/v1/streams/")) {
1247
- // First call has control chunk, subsequent calls don't
1248
1348
  const chunks = [
1249
1349
  ...sampleChunks,
1250
1350
  { type: "finish", id: "part-1" },
1351
+ turnCompleteChunk,
1251
1352
  ];
1252
- if (triggerCallCount <= 1) {
1253
- chunks.push(controlChunk);
1254
- }
1255
1353
  return new Response(createSSEStream(sseEncode(chunks)), {
1256
1354
  status: 200,
1257
1355
  headers: {
@@ -1282,7 +1380,7 @@ const sampleChunks = [
1282
1380
  break;
1283
1381
  }
1284
1382
  (0, vitest_1.expect)(triggerCallCount).toBe(1);
1285
- // Second message — waitpoint completion will fail, should fall back to new run
1383
+ // Second message — sendInputStream will fail, should fall back to new run
1286
1384
  const stream2 = await transport.sendMessages({
1287
1385
  trigger: "submit-message",
1288
1386
  chatId: "chat-fail",
@@ -1300,5 +1398,160 @@ const sampleChunks = [
1300
1398
  (0, vitest_1.expect)(triggerCallCount).toBe(2);
1301
1399
  });
1302
1400
  });
1401
+ (0, vitest_1.describe)("onSessionChange", () => {
1402
+ (0, vitest_1.it)("should fire when a new session is created", async () => {
1403
+ const onSessionChange = vitest_1.vi.fn();
1404
+ const triggerRunId = "run_session_new";
1405
+ const publicToken = "pub_session_new";
1406
+ global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
1407
+ const urlStr = typeof url === "string" ? url : url.toString();
1408
+ if (urlStr.includes("/trigger")) {
1409
+ return new Response(JSON.stringify({ id: triggerRunId }), {
1410
+ status: 200,
1411
+ headers: {
1412
+ "content-type": "application/json",
1413
+ "x-trigger-jwt": publicToken,
1414
+ },
1415
+ });
1416
+ }
1417
+ if (urlStr.includes("/realtime/v1/streams/")) {
1418
+ const chunks = [
1419
+ ...sampleChunks,
1420
+ { type: "__trigger_turn_complete" },
1421
+ ];
1422
+ return new Response(createSSEStream(sseEncode(chunks)), {
1423
+ status: 200,
1424
+ headers: {
1425
+ "content-type": "text/event-stream",
1426
+ "X-Stream-Version": "v1",
1427
+ },
1428
+ });
1429
+ }
1430
+ throw new Error(`Unexpected fetch URL: ${urlStr}`);
1431
+ });
1432
+ const transport = new chat_js_1.TriggerChatTransport({
1433
+ task: "my-task",
1434
+ accessToken: "token",
1435
+ baseURL: "https://api.test.trigger.dev",
1436
+ onSessionChange,
1437
+ });
1438
+ const stream = await transport.sendMessages({
1439
+ trigger: "submit-message",
1440
+ chatId: "chat-1",
1441
+ messageId: undefined,
1442
+ messages: [createUserMessage("Hello")],
1443
+ abortSignal: undefined,
1444
+ });
1445
+ // Session created notification should have fired
1446
+ (0, vitest_1.expect)(onSessionChange).toHaveBeenCalledWith("chat-1", {
1447
+ runId: triggerRunId,
1448
+ publicAccessToken: publicToken,
1449
+ lastEventId: undefined,
1450
+ });
1451
+ // Consume stream
1452
+ const reader = stream.getReader();
1453
+ while (!(await reader.read()).done) { }
1454
+ // Should also fire with updated lastEventId on turn complete
1455
+ const lastCall = onSessionChange.mock.calls[onSessionChange.mock.calls.length - 1];
1456
+ (0, vitest_1.expect)(lastCall[0]).toBe("chat-1");
1457
+ (0, vitest_1.expect)(lastCall[1]).not.toBeNull();
1458
+ (0, vitest_1.expect)(lastCall[1].lastEventId).toBeDefined();
1459
+ });
1460
+ (0, vitest_1.it)("should fire with null when session is deleted (stream ends naturally)", async () => {
1461
+ const onSessionChange = vitest_1.vi.fn();
1462
+ global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
1463
+ const urlStr = typeof url === "string" ? url : url.toString();
1464
+ if (urlStr.includes("/trigger")) {
1465
+ return new Response(JSON.stringify({ id: "run_end" }), {
1466
+ status: 200,
1467
+ headers: {
1468
+ "content-type": "application/json",
1469
+ "x-trigger-jwt": "pub_end",
1470
+ },
1471
+ });
1472
+ }
1473
+ if (urlStr.includes("/realtime/v1/streams/")) {
1474
+ // No turn-complete chunk — stream ends naturally (run completed)
1475
+ return new Response(createSSEStream(sseEncode(sampleChunks)), {
1476
+ status: 200,
1477
+ headers: {
1478
+ "content-type": "text/event-stream",
1479
+ "X-Stream-Version": "v1",
1480
+ },
1481
+ });
1482
+ }
1483
+ throw new Error(`Unexpected fetch URL: ${urlStr}`);
1484
+ });
1485
+ const transport = new chat_js_1.TriggerChatTransport({
1486
+ task: "my-task",
1487
+ accessToken: "token",
1488
+ baseURL: "https://api.test.trigger.dev",
1489
+ onSessionChange,
1490
+ });
1491
+ const stream = await transport.sendMessages({
1492
+ trigger: "submit-message",
1493
+ chatId: "chat-end",
1494
+ messageId: undefined,
1495
+ messages: [createUserMessage("Hello")],
1496
+ abortSignal: undefined,
1497
+ });
1498
+ // Consume the stream fully
1499
+ const reader = stream.getReader();
1500
+ while (!(await reader.read()).done) { }
1501
+ // Session should have been created then deleted
1502
+ (0, vitest_1.expect)(onSessionChange).toHaveBeenCalledWith("chat-end", vitest_1.expect.objectContaining({
1503
+ runId: "run_end",
1504
+ }));
1505
+ (0, vitest_1.expect)(onSessionChange).toHaveBeenCalledWith("chat-end", null);
1506
+ });
1507
+ (0, vitest_1.it)("should be updatable via setOnSessionChange", async () => {
1508
+ const onSessionChange1 = vitest_1.vi.fn();
1509
+ const onSessionChange2 = vitest_1.vi.fn();
1510
+ global.fetch = vitest_1.vi.fn().mockImplementation(async (url) => {
1511
+ const urlStr = typeof url === "string" ? url : url.toString();
1512
+ if (urlStr.includes("/trigger")) {
1513
+ return new Response(JSON.stringify({ id: "run_update" }), {
1514
+ status: 200,
1515
+ headers: {
1516
+ "content-type": "application/json",
1517
+ "x-trigger-jwt": "pub_update",
1518
+ },
1519
+ });
1520
+ }
1521
+ if (urlStr.includes("/realtime/v1/streams/")) {
1522
+ const chunks = [
1523
+ ...sampleChunks,
1524
+ { type: "__trigger_turn_complete" },
1525
+ ];
1526
+ return new Response(createSSEStream(sseEncode(chunks)), {
1527
+ status: 200,
1528
+ headers: {
1529
+ "content-type": "text/event-stream",
1530
+ "X-Stream-Version": "v1",
1531
+ },
1532
+ });
1533
+ }
1534
+ throw new Error(`Unexpected fetch URL: ${urlStr}`);
1535
+ });
1536
+ const transport = new chat_js_1.TriggerChatTransport({
1537
+ task: "my-task",
1538
+ accessToken: "token",
1539
+ baseURL: "https://api.test.trigger.dev",
1540
+ onSessionChange: onSessionChange1,
1541
+ });
1542
+ // Update the callback before sending
1543
+ transport.setOnSessionChange(onSessionChange2);
1544
+ await transport.sendMessages({
1545
+ trigger: "submit-message",
1546
+ chatId: "chat-update",
1547
+ messageId: undefined,
1548
+ messages: [createUserMessage("Hello")],
1549
+ abortSignal: undefined,
1550
+ });
1551
+ // Only onSessionChange2 should have been called
1552
+ (0, vitest_1.expect)(onSessionChange1).not.toHaveBeenCalled();
1553
+ (0, vitest_1.expect)(onSessionChange2).toHaveBeenCalled();
1554
+ });
1555
+ });
1303
1556
  });
1304
1557
  //# sourceMappingURL=chat.test.js.map