@langgraph-js/pure-graph 3.2.0 → 3.2.2

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-jYlUzTZO.js';
2
+ import { c as createEndpoint } from '../../createEndpoint-vMmFiMSz.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,37 @@ 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(
745
+ thread_id,
746
+ payload.assistant_id,
747
+ camelcaseKeys(payload)
748
+ );
749
+ for await (const { event, data } of generator) {
750
+ if (isCleaningUp || writer.signal.aborted) {
751
+ break;
752
+ }
753
+ await writer.writeSSE({ data: serialiseAsDict(data) ?? "", event });
754
+ }
755
+ } catch (error) {
756
+ if (!writer.signal.aborted && !isCleaningUp) {
757
+ throw error;
758
+ }
759
+ } finally {
760
+ writer.signal.removeEventListener("abort", abortHandler);
761
+ if (generator) {
762
+ try {
763
+ await generator.return(void 0);
764
+ } catch (e) {
765
+ }
766
+ generator = null;
767
+ }
724
768
  }
725
769
  })
726
770
  );
@@ -737,19 +781,33 @@ async function joinRunStream(req, context) {
737
781
  return createSSEStream(
738
782
  withHeartbeat(async (writer) => {
739
783
  const controller = new AbortController();
740
- if (cancel_on_disconnect) {
741
- const cleanup = () => {
742
- controller.abort("Client disconnected");
743
- };
744
- req.signal?.addEventListener("abort", cleanup);
784
+ let generator = null;
785
+ let isCleaningUp = false;
786
+ const abortHandlers = [];
787
+ const cleanup = () => {
788
+ controller.abort("Client disconnected");
789
+ };
790
+ if (req.signal) {
791
+ req.signal.addEventListener("abort", cleanup);
792
+ abortHandlers.push({ signal: req.signal, handler: cleanup });
745
793
  }
794
+ const writerAbortHandler = () => {
795
+ isCleaningUp = true;
796
+ controller.abort("SSE stream closed");
797
+ };
798
+ writer.signal.addEventListener("abort", writerAbortHandler);
799
+ abortHandlers.push({ signal: writer.signal, handler: writerAbortHandler });
746
800
  try {
747
- for await (const { event, data, id } of client.runs.joinStream(thread_id, run_id, {
801
+ generator = client.runs.joinStream(thread_id, run_id, {
748
802
  signal: controller.signal,
749
803
  cancelOnDisconnect: cancel_on_disconnect,
750
804
  lastEventId: last_event_id,
751
805
  streamMode: stream_mode ? [stream_mode] : void 0
752
- })) {
806
+ });
807
+ for await (const { event, data, id } of generator) {
808
+ if (isCleaningUp || writer.signal.aborted || controller.signal.aborted) {
809
+ break;
810
+ }
753
811
  await writer.writeSSE({
754
812
  data: serialiseAsDict(data) ?? "",
755
813
  event,
@@ -757,7 +815,8 @@ async function joinRunStream(req, context) {
757
815
  });
758
816
  }
759
817
  } catch (error) {
760
- if (!(error instanceof Error) || !error.message.includes("user cancel")) {
818
+ const isAbortError = controller.signal.aborted || writer.signal.aborted;
819
+ if (!isAbortError && !(error instanceof Error && error.message.includes("user cancel"))) {
761
820
  console.error("Join stream error:", error);
762
821
  await writer.writeSSE({
763
822
  event: "error",
@@ -766,6 +825,20 @@ async function joinRunStream(req, context) {
766
825
  })
767
826
  });
768
827
  }
828
+ } finally {
829
+ for (const { signal, handler } of abortHandlers) {
830
+ try {
831
+ signal.removeEventListener("abort", handler);
832
+ } catch (e) {
833
+ }
834
+ }
835
+ if (generator) {
836
+ try {
837
+ await generator.return(void 0);
838
+ } catch (e) {
839
+ }
840
+ generator = null;
841
+ }
769
842
  }
770
843
  })
771
844
  );
@@ -819,6 +892,7 @@ async function createRun(req, context) {
819
892
  const threads = client.threads;
820
893
  const run = await threads.createRun(thread_id, payload.assistant_id, camelPayload);
821
894
  (async () => {
895
+ let queueCleared = false;
822
896
  try {
823
897
  for await (const _ of streamState(threads, run, camelPayload, {
824
898
  attempt: 0,
@@ -827,6 +901,14 @@ async function createRun(req, context) {
827
901
  }
828
902
  } catch (error) {
829
903
  console.error("Background run error:", error);
904
+ } finally {
905
+ if (!queueCleared) {
906
+ queueCleared = true;
907
+ try {
908
+ await LangGraphGlobal.globalMessageQueue.removeQueue(run.run_id);
909
+ } catch (e) {
910
+ }
911
+ }
830
912
  }
831
913
  })();
832
914
  return jsonResponse(run, 200, {
@@ -955,6 +1037,7 @@ async function createStatelessRun(req, context) {
955
1037
  camelPayload.temporary = true;
956
1038
  const run = await threads.createRun(thread.thread_id, payload.assistant_id, camelPayload);
957
1039
  (async () => {
1040
+ let queueCleared = false;
958
1041
  try {
959
1042
  for await (const _ of streamState(threads, run, camelPayload, {
960
1043
  attempt: 0,
@@ -969,6 +1052,13 @@ async function createStatelessRun(req, context) {
969
1052
  } catch (e) {
970
1053
  console.error("Error cleaning up temporary thread:", e);
971
1054
  }
1055
+ if (!queueCleared) {
1056
+ queueCleared = true;
1057
+ try {
1058
+ await LangGraphGlobal.globalMessageQueue.removeQueue(run.run_id);
1059
+ } catch (e) {
1060
+ }
1061
+ }
972
1062
  }
973
1063
  })();
974
1064
  return jsonResponse(run, 200, {
@@ -1092,6 +1182,7 @@ async function createBatchRuns(req, context) {
1092
1182
  camelPayload.temporary = true;
1093
1183
  const run = await threads.createRun(thread.thread_id, payload.assistant_id, camelPayload);
1094
1184
  (async () => {
1185
+ let queueCleared = false;
1095
1186
  try {
1096
1187
  for await (const _ of streamState(threads, run, camelPayload, {
1097
1188
  attempt: 0,
@@ -1106,6 +1197,13 @@ async function createBatchRuns(req, context) {
1106
1197
  } catch (e) {
1107
1198
  console.error("Error cleaning up temporary thread:", e);
1108
1199
  }
1200
+ if (!queueCleared) {
1201
+ queueCleared = true;
1202
+ try {
1203
+ await LangGraphGlobal.globalMessageQueue.removeQueue(run.run_id);
1204
+ } catch (e) {
1205
+ }
1206
+ }
1109
1207
  }
1110
1208
  })();
1111
1209
  return { thread_id: thread.thread_id, run_id: run.run_id };