@langgraph-js/pure-graph 3.2.0 → 3.2.3

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.
@@ -1,5 +1,5 @@
1
- import { s as serialiseAsDict, a as streamState, g as getGraph, L as LangGraphGlobal } from '../../stream-pZfO6Y-p.js';
2
- import { c as createEndpoint } from '../../createEndpoint-C2KsYHDx.js';
1
+ import { s as serialiseAsDict, a as streamState, g as getGraph, L as LangGraphGlobal } from '../../stream-Zt8tbgEj.js';
2
+ import { c as createEndpoint } from '../../createEndpoint-CTPbz_D8.js';
3
3
  import z from 'zod';
4
4
  import camelcaseKeys from 'camelcase-keys';
5
5
 
@@ -284,13 +284,15 @@ function errorResponse(error, status = 500) {
284
284
  function createSSEStream(streamFn) {
285
285
  let controller;
286
286
  let isClosed = false;
287
+ const abortController = new AbortController();
287
288
  const stream = new ReadableStream({
288
289
  async start(ctrl) {
289
290
  controller = ctrl;
290
291
  const encoder = new TextEncoder();
291
292
  const writer = {
293
+ signal: abortController.signal,
292
294
  writeSSE: async ({ data, event, id }) => {
293
- if (isClosed) {
295
+ if (isClosed || abortController.signal.aborted) {
294
296
  return;
295
297
  }
296
298
  try {
@@ -308,7 +310,7 @@ function createSSEStream(streamFn) {
308
310
  `;
309
311
  controller.enqueue(encoder.encode(message));
310
312
  } catch (error) {
311
- if (!isClosed) {
313
+ if (!isClosed && !abortController.signal.aborted) {
312
314
  throw error;
313
315
  }
314
316
  }
@@ -326,7 +328,9 @@ function createSSEStream(streamFn) {
326
328
  try {
327
329
  await streamFn(writer);
328
330
  } catch (error) {
329
- console.error("SSE stream error:", error);
331
+ if (!abortController.signal.aborted) {
332
+ console.error("SSE stream error:", error);
333
+ }
330
334
  } finally {
331
335
  if (!isClosed) {
332
336
  isClosed = true;
@@ -339,6 +343,9 @@ function createSSEStream(streamFn) {
339
343
  },
340
344
  cancel() {
341
345
  isClosed = true;
346
+ if (!abortController.signal.aborted) {
347
+ abortController.abort("Client disconnected");
348
+ }
342
349
  }
343
350
  });
344
351
  return new Response(stream, {
@@ -352,32 +359,42 @@ function createSSEStream(streamFn) {
352
359
  function withHeartbeat(streamFn, heartbeatInterval = process.env.HEARTBEAT_INTERVAL ? parseInt(process.env.HEARTBEAT_INTERVAL) : 1500) {
353
360
  return async (writer) => {
354
361
  let heartbeatTimer = null;
355
- const startHeartbeat = () => {
362
+ let isCleaningUp = false;
363
+ const stopHeartbeat = () => {
356
364
  if (heartbeatTimer) {
357
365
  clearInterval(heartbeatTimer);
366
+ heartbeatTimer = null;
358
367
  }
368
+ };
369
+ const startHeartbeat = () => {
370
+ stopHeartbeat();
359
371
  heartbeatTimer = setInterval(async () => {
372
+ if (writer.signal.aborted || isCleaningUp) {
373
+ stopHeartbeat();
374
+ return;
375
+ }
360
376
  try {
361
377
  await writer.writeSSE({ event: "ping", data: "{}" });
362
378
  } catch (error) {
363
- if (heartbeatTimer) {
364
- clearInterval(heartbeatTimer);
365
- heartbeatTimer = null;
366
- }
379
+ stopHeartbeat();
367
380
  }
368
381
  }, heartbeatInterval);
369
382
  };
370
- const stopHeartbeat = () => {
371
- if (heartbeatTimer) {
372
- clearInterval(heartbeatTimer);
373
- heartbeatTimer = null;
374
- }
383
+ const abortHandler = () => {
384
+ stopHeartbeat();
375
385
  };
386
+ writer.signal.addEventListener("abort", abortHandler);
376
387
  const proxiedWriter = {
388
+ signal: writer.signal,
377
389
  writeSSE: async (data) => {
390
+ if (writer.signal.aborted) {
391
+ return;
392
+ }
378
393
  stopHeartbeat();
379
394
  await writer.writeSSE(data);
380
- startHeartbeat();
395
+ if (!writer.signal.aborted) {
396
+ startHeartbeat();
397
+ }
381
398
  },
382
399
  close: () => {
383
400
  stopHeartbeat();
@@ -388,7 +405,9 @@ function withHeartbeat(streamFn, heartbeatInterval = process.env.HEARTBEAT_INTER
388
405
  try {
389
406
  await streamFn(proxiedWriter);
390
407
  } finally {
408
+ isCleaningUp = true;
391
409
  stopHeartbeat();
410
+ writer.signal.removeEventListener("abort", abortHandler);
392
411
  }
393
412
  };
394
413
  }
@@ -715,12 +734,33 @@ async function streamRun(req, context) {
715
734
  if (langgraphContext) {
716
735
  Object.assign(payload.config.configurable, langgraphContext);
717
736
  }
718
- for await (const { event, data } of client.runs.stream(
719
- thread_id,
720
- payload.assistant_id,
721
- camelcaseKeys(payload)
722
- )) {
723
- await writer.writeSSE({ data: serialiseAsDict(data) ?? "", event });
737
+ let generator = null;
738
+ let isCleaningUp = false;
739
+ const abortHandler = () => {
740
+ isCleaningUp = true;
741
+ };
742
+ writer.signal.addEventListener("abort", abortHandler);
743
+ try {
744
+ generator = client.runs.stream(thread_id, payload.assistant_id, camelcaseKeys(payload));
745
+ for await (const { event, data } of generator) {
746
+ if (isCleaningUp || writer.signal.aborted) {
747
+ break;
748
+ }
749
+ await writer.writeSSE({ data: serialiseAsDict(data) ?? "", event });
750
+ }
751
+ } catch (error) {
752
+ if (!writer.signal.aborted && !isCleaningUp) {
753
+ throw error;
754
+ }
755
+ } finally {
756
+ writer.signal.removeEventListener("abort", abortHandler);
757
+ if (generator) {
758
+ try {
759
+ await generator.return(void 0);
760
+ } catch (e) {
761
+ }
762
+ generator = null;
763
+ }
724
764
  }
725
765
  })
726
766
  );
@@ -737,19 +777,33 @@ async function joinRunStream(req, context) {
737
777
  return createSSEStream(
738
778
  withHeartbeat(async (writer) => {
739
779
  const controller = new AbortController();
740
- if (cancel_on_disconnect) {
741
- const cleanup = () => {
742
- controller.abort("Client disconnected");
743
- };
744
- req.signal?.addEventListener("abort", cleanup);
780
+ let generator = null;
781
+ let isCleaningUp = false;
782
+ const abortHandlers = [];
783
+ const cleanup = () => {
784
+ controller.abort("Client disconnected");
785
+ };
786
+ if (req.signal) {
787
+ req.signal.addEventListener("abort", cleanup);
788
+ abortHandlers.push({ signal: req.signal, handler: cleanup });
745
789
  }
790
+ const writerAbortHandler = () => {
791
+ isCleaningUp = true;
792
+ controller.abort("SSE stream closed");
793
+ };
794
+ writer.signal.addEventListener("abort", writerAbortHandler);
795
+ abortHandlers.push({ signal: writer.signal, handler: writerAbortHandler });
746
796
  try {
747
- for await (const { event, data, id } of client.runs.joinStream(thread_id, run_id, {
797
+ generator = client.runs.joinStream(thread_id, run_id, {
748
798
  signal: controller.signal,
749
799
  cancelOnDisconnect: cancel_on_disconnect,
750
800
  lastEventId: last_event_id,
751
801
  streamMode: stream_mode ? [stream_mode] : void 0
752
- })) {
802
+ });
803
+ for await (const { event, data, id } of generator) {
804
+ if (isCleaningUp || writer.signal.aborted || controller.signal.aborted) {
805
+ break;
806
+ }
753
807
  await writer.writeSSE({
754
808
  data: serialiseAsDict(data) ?? "",
755
809
  event,
@@ -757,7 +811,8 @@ async function joinRunStream(req, context) {
757
811
  });
758
812
  }
759
813
  } catch (error) {
760
- if (!(error instanceof Error) || !error.message.includes("user cancel")) {
814
+ const isAbortError = controller.signal.aborted || writer.signal.aborted;
815
+ if (!isAbortError && !(error instanceof Error && error.message.includes("user cancel"))) {
761
816
  console.error("Join stream error:", error);
762
817
  await writer.writeSSE({
763
818
  event: "error",
@@ -766,6 +821,20 @@ async function joinRunStream(req, context) {
766
821
  })
767
822
  });
768
823
  }
824
+ } finally {
825
+ for (const { signal, handler } of abortHandlers) {
826
+ try {
827
+ signal.removeEventListener("abort", handler);
828
+ } catch (e) {
829
+ }
830
+ }
831
+ if (generator) {
832
+ try {
833
+ await generator.return(void 0);
834
+ } catch (e) {
835
+ }
836
+ generator = null;
837
+ }
769
838
  }
770
839
  })
771
840
  );
@@ -819,6 +888,7 @@ async function createRun(req, context) {
819
888
  const threads = client.threads;
820
889
  const run = await threads.createRun(thread_id, payload.assistant_id, camelPayload);
821
890
  (async () => {
891
+ let queueCleared = false;
822
892
  try {
823
893
  for await (const _ of streamState(threads, run, camelPayload, {
824
894
  attempt: 0,
@@ -827,6 +897,14 @@ async function createRun(req, context) {
827
897
  }
828
898
  } catch (error) {
829
899
  console.error("Background run error:", error);
900
+ } finally {
901
+ if (!queueCleared) {
902
+ queueCleared = true;
903
+ try {
904
+ await LangGraphGlobal.globalMessageQueue.removeQueue(run.run_id);
905
+ } catch (e) {
906
+ }
907
+ }
830
908
  }
831
909
  })();
832
910
  return jsonResponse(run, 200, {
@@ -955,6 +1033,7 @@ async function createStatelessRun(req, context) {
955
1033
  camelPayload.temporary = true;
956
1034
  const run = await threads.createRun(thread.thread_id, payload.assistant_id, camelPayload);
957
1035
  (async () => {
1036
+ let queueCleared = false;
958
1037
  try {
959
1038
  for await (const _ of streamState(threads, run, camelPayload, {
960
1039
  attempt: 0,
@@ -969,6 +1048,13 @@ async function createStatelessRun(req, context) {
969
1048
  } catch (e) {
970
1049
  console.error("Error cleaning up temporary thread:", e);
971
1050
  }
1051
+ if (!queueCleared) {
1052
+ queueCleared = true;
1053
+ try {
1054
+ await LangGraphGlobal.globalMessageQueue.removeQueue(run.run_id);
1055
+ } catch (e) {
1056
+ }
1057
+ }
972
1058
  }
973
1059
  })();
974
1060
  return jsonResponse(run, 200, {
@@ -1092,6 +1178,7 @@ async function createBatchRuns(req, context) {
1092
1178
  camelPayload.temporary = true;
1093
1179
  const run = await threads.createRun(thread.thread_id, payload.assistant_id, camelPayload);
1094
1180
  (async () => {
1181
+ let queueCleared = false;
1095
1182
  try {
1096
1183
  for await (const _ of streamState(threads, run, camelPayload, {
1097
1184
  attempt: 0,
@@ -1106,6 +1193,13 @@ async function createBatchRuns(req, context) {
1106
1193
  } catch (e) {
1107
1194
  console.error("Error cleaning up temporary thread:", e);
1108
1195
  }
1196
+ if (!queueCleared) {
1197
+ queueCleared = true;
1198
+ try {
1199
+ await LangGraphGlobal.globalMessageQueue.removeQueue(run.run_id);
1200
+ } catch (e) {
1201
+ }
1202
+ }
1109
1203
  }
1110
1204
  })();
1111
1205
  return { thread_id: thread.thread_id, run_id: run.run_id };