langgraph-api 0.0.27__py3-none-any.whl → 0.0.28__py3-none-any.whl
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.
Potentially problematic release.
This version of langgraph-api might be problematic. Click here for more details.
- langgraph_api/api/__init__.py +2 -0
- langgraph_api/api/assistants.py +43 -13
- langgraph_api/api/meta.py +1 -1
- langgraph_api/api/runs.py +14 -1
- langgraph_api/api/ui.py +68 -0
- langgraph_api/asyncio.py +43 -4
- langgraph_api/auth/middleware.py +2 -2
- langgraph_api/config.py +14 -1
- langgraph_api/cron_scheduler.py +1 -1
- langgraph_api/graph.py +5 -0
- langgraph_api/http.py +24 -7
- langgraph_api/js/.gitignore +2 -0
- langgraph_api/js/build.mts +44 -1
- langgraph_api/js/client.mts +67 -31
- langgraph_api/js/global.d.ts +1 -0
- langgraph_api/js/package.json +11 -5
- langgraph_api/js/remote.py +662 -16
- langgraph_api/js/sse.py +138 -0
- langgraph_api/js/tests/api.test.mts +28 -0
- langgraph_api/js/tests/compose-postgres.yml +2 -2
- langgraph_api/js/tests/graphs/agent.css +1 -0
- langgraph_api/js/tests/graphs/agent.ui.tsx +10 -0
- langgraph_api/js/tests/graphs/package.json +2 -2
- langgraph_api/js/tests/graphs/yarn.lock +13 -13
- langgraph_api/js/yarn.lock +706 -1188
- langgraph_api/lifespan.py +15 -5
- langgraph_api/logging.py +9 -0
- langgraph_api/metadata.py +5 -1
- langgraph_api/middleware/http_logger.py +1 -1
- langgraph_api/patch.py +2 -0
- langgraph_api/queue_entrypoint.py +63 -0
- langgraph_api/schema.py +2 -0
- langgraph_api/stream.py +1 -0
- langgraph_api/webhook.py +42 -0
- langgraph_api/{queue.py → worker.py} +52 -166
- {langgraph_api-0.0.27.dist-info → langgraph_api-0.0.28.dist-info}/METADATA +2 -2
- {langgraph_api-0.0.27.dist-info → langgraph_api-0.0.28.dist-info}/RECORD +47 -44
- langgraph_storage/database.py +8 -22
- langgraph_storage/inmem_stream.py +108 -0
- langgraph_storage/ops.py +80 -57
- langgraph_storage/queue.py +126 -103
- langgraph_storage/retry.py +5 -1
- langgraph_storage/store.py +5 -1
- openapi.json +3 -3
- langgraph_api/js/client.new.mts +0 -875
- langgraph_api/js/remote_new.py +0 -694
- langgraph_api/js/remote_old.py +0 -670
- langgraph_api/js/server_sent_events.py +0 -126
- {langgraph_api-0.0.27.dist-info → langgraph_api-0.0.28.dist-info}/LICENSE +0 -0
- {langgraph_api-0.0.27.dist-info → langgraph_api-0.0.28.dist-info}/WHEEL +0 -0
- {langgraph_api-0.0.27.dist-info → langgraph_api-0.0.28.dist-info}/entry_points.txt +0 -0
|
@@ -1,40 +1,37 @@
|
|
|
1
1
|
LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
|
|
2
2
|
langgraph_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
langgraph_api/api/__init__.py,sha256=
|
|
4
|
-
langgraph_api/api/assistants.py,sha256=
|
|
5
|
-
langgraph_api/api/meta.py,sha256=
|
|
3
|
+
langgraph_api/api/__init__.py,sha256=vwV4VnV7wA7tHqgcsSzeaHHQz9aTNZmTKLO6yT5l9UE,5044
|
|
4
|
+
langgraph_api/api/assistants.py,sha256=6K5HTUjl7lQv0sVRe4RURPrLzTW-BblUjbSYHeN2PfQ,13992
|
|
5
|
+
langgraph_api/api/meta.py,sha256=ifJ_Ki0Qf2DYbmY6OKlqKhLGxbt55gm0lEqH1A0cJbw,2790
|
|
6
6
|
langgraph_api/api/openapi.py,sha256=f9gfmWN2AMKNUpLCpSgZuw_aeOF9jCXPdOtFT5PaTWM,10960
|
|
7
|
-
langgraph_api/api/runs.py,sha256=
|
|
7
|
+
langgraph_api/api/runs.py,sha256=HWZ7QPRJwPlvtFtC8cyG5mKd3GJIjRkdR0KWlMssQ84,16574
|
|
8
8
|
langgraph_api/api/store.py,sha256=VzAJVOwO0IxosBB7km5TTf2rhlWGyPkVz_LpvbxetVY,5437
|
|
9
9
|
langgraph_api/api/threads.py,sha256=taU61XPcCEhBPCYPZcMDsgVDwwWUWJs8p-PrXFXWY48,8661
|
|
10
|
-
langgraph_api/
|
|
10
|
+
langgraph_api/api/ui.py,sha256=LiOZVewKOPbKEykCm30hCEaOA7vuS_Ti5hB32EEy4vw,2082
|
|
11
|
+
langgraph_api/asyncio.py,sha256=ipxOGL0CuKZeHw8895ojtfoBU2fj0iJOp48uhiLAmss,7786
|
|
11
12
|
langgraph_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
13
|
langgraph_api/auth/custom.py,sha256=oyXz7UONY1YeTJ5IBGRAbFUz_K6krkptNxW8zzjb0uk,21088
|
|
13
14
|
langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
15
|
langgraph_api/auth/langsmith/backend.py,sha256=InScaL-HYCnxYEauhxU198gRZV9pJn9SzzBoR9Edn7g,2654
|
|
15
16
|
langgraph_api/auth/langsmith/client.py,sha256=eKchvAom7hdkUXauD8vHNceBDDUijrFgdTV8bKd7x4Q,3998
|
|
16
|
-
langgraph_api/auth/middleware.py,sha256=
|
|
17
|
+
langgraph_api/auth/middleware.py,sha256=5VEC3KE0DrEgXHbEta95zpL1S6PYyD9ETXn3lWKVhqI,1684
|
|
17
18
|
langgraph_api/auth/noop.py,sha256=Bk6Nf3p8D_iMVy_OyfPlyiJp_aEwzL-sHrbxoXpCbac,586
|
|
18
19
|
langgraph_api/auth/studio_user.py,sha256=FzFQRROKDlA9JjtBuwyZvk6Mbwno5M9RVYjDO6FU3F8,186
|
|
19
20
|
langgraph_api/cli.py,sha256=YIiuS-2D0fyqLd8BzJBIUAM3mmAJPtLUdPEMS6Awj9o,12752
|
|
20
|
-
langgraph_api/config.py,sha256=
|
|
21
|
-
langgraph_api/cron_scheduler.py,sha256=
|
|
21
|
+
langgraph_api/config.py,sha256=iIekoGAQL-v--hVrCzSd2StrelzuHqb1vbpJAoHgxkU,5809
|
|
22
|
+
langgraph_api/cron_scheduler.py,sha256=9yzbbGxzNgJdIg4ZT7yu2oTwT_wRuPxD1c2sbbd52xs,2630
|
|
22
23
|
langgraph_api/errors.py,sha256=Bu_i5drgNTyJcLiyrwVE_6-XrSU50BHf9TDpttki9wQ,1690
|
|
23
|
-
langgraph_api/graph.py,sha256=
|
|
24
|
-
langgraph_api/http.py,sha256=
|
|
25
|
-
langgraph_api/js/.gitignore,sha256=
|
|
24
|
+
langgraph_api/graph.py,sha256=viFyQa8-BRRNRZqNNUzLB31cB4IypEMkMucQEJQYLWY,16556
|
|
25
|
+
langgraph_api/http.py,sha256=gYbxxjY8aLnsXeJymcJ7G7Nj_yToOGpPYQqmZ1_ggfA,5240
|
|
26
|
+
langgraph_api/js/.gitignore,sha256=l5yI6G_V6F1600I1IjiUKn87f4uYIrBAYU1MOyBBhg4,59
|
|
26
27
|
langgraph_api/js/base.py,sha256=BpE8-xkUp8HFPRjSKx1tfUQubvoV4jYl6OwZdre3veI,209
|
|
27
|
-
langgraph_api/js/build.mts,sha256=
|
|
28
|
-
langgraph_api/js/client.mts,sha256=
|
|
29
|
-
langgraph_api/js/client.new.mts,sha256=c3LEbfnFs2fTYLJsfKHs5-0HdV3Sr16nbIZSWGQU-TE,24201
|
|
28
|
+
langgraph_api/js/build.mts,sha256=ArAEn74HHY_KXt0JCne76FTyGP34VQCHMe6OfPyxLM8,2915
|
|
29
|
+
langgraph_api/js/client.mts,sha256=heQ5LMpK-hWfSrByNeINZn0JTJ3EYaJKCepzfpiXccU,24356
|
|
30
30
|
langgraph_api/js/errors.py,sha256=Cm1TKWlUCwZReDC5AQ6SgNIVGD27Qov2xcgHyf8-GXo,361
|
|
31
|
-
langgraph_api/js/global.d.ts,sha256=
|
|
32
|
-
langgraph_api/js/package.json,sha256=
|
|
33
|
-
langgraph_api/js/remote.py,sha256=
|
|
34
|
-
langgraph_api/js/remote_new.py,sha256=-9gsJeV32cHPXd-EESFVfPoBoPC7pqR7XKhkb_q9cHA,22781
|
|
35
|
-
langgraph_api/js/remote_old.py,sha256=V_3A3cyhdyFv_xm6CU6WUOQ2aqlkh_KDAMEEDWq_BQQ,22807
|
|
31
|
+
langgraph_api/js/global.d.ts,sha256=cLJRZfYVGmgQ6o_xFevVNNTIi918ZUdxVRnpLVSjiAY,133
|
|
32
|
+
langgraph_api/js/package.json,sha256=j6DMoVgwRqWqTwdd7R1f-kvmiTUAbO3HaUhM8K64lbE,1224
|
|
33
|
+
langgraph_api/js/remote.py,sha256=jUUgdskUbDR_tuhZFrJo4OewbndQ76joczBv-mZHG9k,22637
|
|
36
34
|
langgraph_api/js/schema.py,sha256=7idnv7URlYUdSNMBXQcw7E4SxaPxCq_Oxwnlml8q5ik,408
|
|
37
|
-
langgraph_api/js/server_sent_events.py,sha256=DLgXOHauemt7706vnfDUCG1GI3TidKycSizccdz9KgA,3702
|
|
38
35
|
langgraph_api/js/src/graph.mts,sha256=mRyMUp03Fwd5DlmNIFl3RiUCQuJ5XwmFp1AfAeKDfVc,3169
|
|
39
36
|
langgraph_api/js/src/hooks.mjs,sha256=XtktgmIHlls_DsknAuwib-z7TqCm0haRoTXvnkgzMuo,601
|
|
40
37
|
langgraph_api/js/src/parser/parser.mts,sha256=wXre7teh8N8RYmGcyhZp4vMJd0kNnnFgoSGEyMVPzpQ,13007
|
|
@@ -44,54 +41,60 @@ langgraph_api/js/src/schema/types.template.mts,sha256=oAwnwWOgkEAQ3EouB8dG5Mdg4H
|
|
|
44
41
|
langgraph_api/js/src/utils/importMap.mts,sha256=pX4TGOyUpuuWF82kXcxcv3-8mgusRezOGe6Uklm2O5A,1644
|
|
45
42
|
langgraph_api/js/src/utils/pythonSchemas.mts,sha256=98IW7Z_VP7L_CHNRMb3_MsiV3BgLE2JsWQY_PQcRR3o,685
|
|
46
43
|
langgraph_api/js/src/utils/serde.mts,sha256=OuyyO9btvwWd55rU_H4x91dFEJiaPxL-lL9O6Zgo908,742
|
|
47
|
-
langgraph_api/js/
|
|
48
|
-
langgraph_api/js/tests/
|
|
44
|
+
langgraph_api/js/sse.py,sha256=lsfp4nyJyA1COmlKG9e2gJnTttf_HGCB5wyH8OZBER8,4105
|
|
45
|
+
langgraph_api/js/tests/api.test.mts,sha256=7B7uIiUsi4X3tN5ji7loaU4PGkoJyHhTIbqPN2V-7kY,57709
|
|
46
|
+
langgraph_api/js/tests/compose-postgres.yml,sha256=wV1Kws7WwUWVIudPkB--v58MOPL9hOcV0MUK-cvNrpA,1738
|
|
49
47
|
langgraph_api/js/tests/graphs/.gitignore,sha256=26J8MarZNXh7snXD5eTpV3CPFTht5Znv8dtHYCLNfkw,12
|
|
48
|
+
langgraph_api/js/tests/graphs/agent.css,sha256=QgcOC0W7IBsrg4pSqqpull-WTgtULZfx_lF_5ZxLdag,23
|
|
50
49
|
langgraph_api/js/tests/graphs/agent.mts,sha256=E9WMv0alMv0njUEECqEsqoRk9NXJUgXW7SyQJ3GOZ8k,5396
|
|
50
|
+
langgraph_api/js/tests/graphs/agent.ui.tsx,sha256=JDFJdpdIS6rglkXTaROSb1Is0j1kt5wN9ML8W4cuht8,175
|
|
51
51
|
langgraph_api/js/tests/graphs/delay.mts,sha256=CFneKxqI4bGGK0lYjSbe80QirowPQlsRSuhDUKfydhk,703
|
|
52
52
|
langgraph_api/js/tests/graphs/error.mts,sha256=l4tk89449dj1BnEF_0ZcfPt0Ikk1gl8L1RaSnRfr3xo,487
|
|
53
53
|
langgraph_api/js/tests/graphs/langgraph.json,sha256=frxd7ZWILdeMYSZgUBH6UO-IR7I2YJSOfOlx2mnO1sI,189
|
|
54
54
|
langgraph_api/js/tests/graphs/nested.mts,sha256=4G7jSOSaFVQAza-_ARbK-Iai1biLlF2DIPDZXf7PLIY,1245
|
|
55
|
-
langgraph_api/js/tests/graphs/package.json,sha256=
|
|
55
|
+
langgraph_api/js/tests/graphs/package.json,sha256=Kv2kdlTNeWl00vYQAhngorQ6rLab4SMc7g1AgZslrHQ,118
|
|
56
56
|
langgraph_api/js/tests/graphs/weather.mts,sha256=A7mLK3xW8h5B-ZyJNAyX2M2fJJwzPJzXs4DYesJwreQ,1655
|
|
57
|
-
langgraph_api/js/tests/graphs/yarn.lock,sha256=
|
|
57
|
+
langgraph_api/js/tests/graphs/yarn.lock,sha256=i2AAIgXA3XBLM8-oU45wgUefCSG-Tne4ghWHmUCUKVk,10407
|
|
58
58
|
langgraph_api/js/tests/parser.test.mts,sha256=dEC8KTqKygeb1u39ZvpPqCT4HtfPD947nLmITt2buxA,27883
|
|
59
59
|
langgraph_api/js/tests/utils.mts,sha256=2kTybJ3O7Yfe1q3ehDouqV54ibXkNzsPZ_wBZLJvY-4,421
|
|
60
|
-
langgraph_api/js/yarn.lock,sha256=
|
|
61
|
-
langgraph_api/lifespan.py,sha256=
|
|
62
|
-
langgraph_api/logging.py,sha256=
|
|
63
|
-
langgraph_api/metadata.py,sha256=
|
|
60
|
+
langgraph_api/js/yarn.lock,sha256=W89dVYZMThcec08lJMcYnvEEnQK7VM5cPglvwpIdRv0,82773
|
|
61
|
+
langgraph_api/lifespan.py,sha256=tcW68-Vxh10y15NDn0TJG2qAbU9RwsrQmumT5xs2HBA,2046
|
|
62
|
+
langgraph_api/logging.py,sha256=rnd_pJsnTd-QbxVJQq-FswEDkd9tNzhoqp7g3_ReIzA,3598
|
|
63
|
+
langgraph_api/metadata.py,sha256=jPLNIRxHi7taZ0g60UdOEXenkvDwoYdI11tsmHenb28,3443
|
|
64
64
|
langgraph_api/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
|
-
langgraph_api/middleware/http_logger.py,sha256=
|
|
65
|
+
langgraph_api/middleware/http_logger.py,sha256=yuFPNFIWwn-4AE1CogBfWlo8KytzywLi_Bd4ccsyVQE,3150
|
|
66
66
|
langgraph_api/middleware/private_network.py,sha256=eYgdyU8AzU2XJu362i1L8aSFoQRiV7_aLBPw7_EgeqI,2111
|
|
67
67
|
langgraph_api/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
68
|
langgraph_api/models/run.py,sha256=1TzTmagDXFQD_LhIMRdguZHmrPSzztq1wiMjF63d2fc,9843
|
|
69
|
-
langgraph_api/patch.py,sha256=
|
|
70
|
-
langgraph_api/
|
|
69
|
+
langgraph_api/patch.py,sha256=82xjuFqY7tgrUm-k1XWHI6k8S6QovSD0zhe--8_xW4o,1296
|
|
70
|
+
langgraph_api/queue_entrypoint.py,sha256=4xICUxXarNV8DhnaqAMhVi3xCmyVKCL3J5NzHxPA9Xc,1835
|
|
71
71
|
langgraph_api/route.py,sha256=fM4qYCGbmH0a3_cV8uKocb1sLklehxO6HhdRXqLK6OM,4421
|
|
72
|
-
langgraph_api/schema.py,sha256=
|
|
72
|
+
langgraph_api/schema.py,sha256=hNbg6ep2wiGBBtBJVNBgMYA8uC33AfaqhRXXVUY_At8,5361
|
|
73
73
|
langgraph_api/serde.py,sha256=VoJ7Z1IuqrQGXFzEP1qijAITtWCrmjtVqlCRuScjXJI,3533
|
|
74
74
|
langgraph_api/server.py,sha256=CiNK327zTsEpoVGeJK1JOtZHvOBYRoz0CnBTZUmsC7c,4567
|
|
75
75
|
langgraph_api/sse.py,sha256=2wNodCOP2eg7a9mpSu0S3FQ0CHk2BBV_vv0UtIgJIcc,4034
|
|
76
76
|
langgraph_api/state.py,sha256=8jx4IoTCOjTJuwzuXJKKFwo1VseHjNnw_CCq4x1SW14,2284
|
|
77
|
-
langgraph_api/stream.py,sha256=
|
|
77
|
+
langgraph_api/stream.py,sha256=LKXxol0y9xEsrgnbsDwyeKIPBDE-75uVz01OcLI-_QE,11976
|
|
78
78
|
langgraph_api/utils.py,sha256=92mSti9GfGdMRRWyESKQW5yV-75Z9icGHnIrBYvdypU,3619
|
|
79
79
|
langgraph_api/validation.py,sha256=McizHlz-Ez8Jhdbc79mbPSde7GIuf2Jlbjx2yv_l6dA,4475
|
|
80
|
+
langgraph_api/webhook.py,sha256=1ncwO0rIZcj-Df9sxSnFEzd1gP1bfS4okeZQS8NSRoE,1382
|
|
81
|
+
langgraph_api/worker.py,sha256=7yQfZBANt1kgJDOEs5B5c3Xy65lzNMmngVbBqLs-r5s,9802
|
|
80
82
|
langgraph_license/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
81
83
|
langgraph_license/middleware.py,sha256=_ODIYzQkymr6W9_Fp9wtf1kAQspnpsmr53xuzyF2GA0,612
|
|
82
84
|
langgraph_license/validation.py,sha256=Uu_G8UGO_WTlLsBEY0gTVWjRR4czYGfw5YAD3HLZoj0,203
|
|
83
85
|
langgraph_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
84
86
|
langgraph_storage/checkpoint.py,sha256=V4t2GwYEJdPCHbhq_4Udhlv0TWKDzlMu_rlNPdTDc50,3589
|
|
85
|
-
langgraph_storage/database.py,sha256=
|
|
86
|
-
langgraph_storage/
|
|
87
|
-
langgraph_storage/
|
|
88
|
-
langgraph_storage/
|
|
89
|
-
langgraph_storage/
|
|
87
|
+
langgraph_storage/database.py,sha256=I0AgFeJ-NSTT34vxKxQBUf1z2syFP0S8QpKCqTixrzY,5652
|
|
88
|
+
langgraph_storage/inmem_stream.py,sha256=8bxkILIuFpr7P7RQ37SQAxrpRKvmbHdRB_nbfFiomlk,3263
|
|
89
|
+
langgraph_storage/ops.py,sha256=BB1UabucGRPg_j-S-KktUbBypvrIj9xWQ596mkZbaQQ,69504
|
|
90
|
+
langgraph_storage/queue.py,sha256=UDgsUTtUMfBSRDrQ8Onis-FJO4n7KTsX6sdpbY8Hs0A,5055
|
|
91
|
+
langgraph_storage/retry.py,sha256=XmldOP4e_H5s264CagJRVnQMDFcEJR_dldVR1Hm5XvM,763
|
|
92
|
+
langgraph_storage/store.py,sha256=SL7VYCO1wI3O1WP20A9jD3lsxXIN990XuuyVrrj6RVY,2855
|
|
90
93
|
langgraph_storage/ttl_dict.py,sha256=FlpEY8EANeXWKo_G5nmIotPquABZGyIJyk6HD9u6vqY,1533
|
|
91
94
|
logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
|
|
92
|
-
openapi.json,sha256=
|
|
93
|
-
langgraph_api-0.0.
|
|
94
|
-
langgraph_api-0.0.
|
|
95
|
-
langgraph_api-0.0.
|
|
96
|
-
langgraph_api-0.0.
|
|
97
|
-
langgraph_api-0.0.
|
|
95
|
+
openapi.json,sha256=8NhoZn3Dse6Ia0i7fXCB46sdGnEg5qAhov_8of-6m0o,125282
|
|
96
|
+
langgraph_api-0.0.28.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
|
|
97
|
+
langgraph_api-0.0.28.dist-info/METADATA,sha256=mDuNiHWUtLvJaA51ixXV2c6lIScZm-_uHqTP6MLa-7g,4029
|
|
98
|
+
langgraph_api-0.0.28.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
99
|
+
langgraph_api-0.0.28.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
|
|
100
|
+
langgraph_api-0.0.28.dist-info/RECORD,,
|
langgraph_storage/database.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import json
|
|
3
2
|
import os
|
|
4
|
-
import threading
|
|
5
3
|
import uuid
|
|
6
4
|
from collections import defaultdict
|
|
7
5
|
from collections.abc import AsyncIterator
|
|
@@ -13,9 +11,10 @@ from uuid import UUID
|
|
|
13
11
|
import structlog
|
|
14
12
|
from langgraph.checkpoint.memory import PersistentDict
|
|
15
13
|
|
|
14
|
+
from langgraph_api import config as langgraph_config
|
|
16
15
|
from langgraph_api.utils import AsyncConnectionProto
|
|
17
16
|
from langgraph_storage import store
|
|
18
|
-
from langgraph_storage.
|
|
17
|
+
from langgraph_storage.inmem_stream import start_stream, stop_stream
|
|
19
18
|
|
|
20
19
|
logger = structlog.stdlib.get_logger(__name__)
|
|
21
20
|
|
|
@@ -23,6 +22,7 @@ logger = structlog.stdlib.get_logger(__name__)
|
|
|
23
22
|
class Assistant(TypedDict):
|
|
24
23
|
assistant_id: UUID
|
|
25
24
|
graph_id: str
|
|
25
|
+
name: str
|
|
26
26
|
created_at: NotRequired[datetime]
|
|
27
27
|
updated_at: NotRequired[datetime]
|
|
28
28
|
config: dict[str, Any]
|
|
@@ -66,6 +66,7 @@ class AssistantVersion(TypedDict):
|
|
|
66
66
|
config: dict[str, Any]
|
|
67
67
|
metadata: dict[str, Any]
|
|
68
68
|
created_at: NotRequired[datetime]
|
|
69
|
+
name: str
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
class GlobalStore(PersistentDict):
|
|
@@ -109,26 +110,12 @@ GLOBAL_RETRY_COUNTER = InMemoryRetryCounter()
|
|
|
109
110
|
GLOBAL_STORE = GlobalStore(filename=OPS_FILENAME)
|
|
110
111
|
|
|
111
112
|
|
|
112
|
-
class LockDict(defaultdict):
|
|
113
|
-
def __init__(self, *args, **kwargs):
|
|
114
|
-
self.__masterlock__ = threading.Lock()
|
|
115
|
-
super().__init__(*args, **kwargs)
|
|
116
|
-
|
|
117
|
-
def __getitem__(self, key):
|
|
118
|
-
with self.__masterlock__:
|
|
119
|
-
return super().__getitem__(key)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
GLOBAL_LOCKS = LockDict(threading.Lock)
|
|
123
|
-
|
|
124
|
-
|
|
125
113
|
class InMemConnectionProto:
|
|
126
114
|
def __init__(self):
|
|
127
115
|
self.filename = OPS_FILENAME
|
|
128
116
|
self.store = GLOBAL_STORE
|
|
129
117
|
self.retry_counter = GLOBAL_RETRY_COUNTER
|
|
130
118
|
self.can_execute = False
|
|
131
|
-
self.locks = GLOBAL_LOCKS
|
|
132
119
|
|
|
133
120
|
@asynccontextmanager
|
|
134
121
|
async def pipeline(self):
|
|
@@ -139,7 +126,6 @@ class InMemConnectionProto:
|
|
|
139
126
|
|
|
140
127
|
def clear(self):
|
|
141
128
|
self.store.clear()
|
|
142
|
-
self.locks.clear()
|
|
143
129
|
keys = list(self.retry_counter._counters)
|
|
144
130
|
for key in keys:
|
|
145
131
|
del self.retry_counter._counters[key]
|
|
@@ -157,8 +143,8 @@ async def connect(*, __test__: bool = False) -> AsyncIterator[AsyncConnectionPro
|
|
|
157
143
|
|
|
158
144
|
async def start_pool() -> None:
|
|
159
145
|
if store._STORE_CONFIG is None:
|
|
160
|
-
if
|
|
161
|
-
config_ =
|
|
146
|
+
if langgraph_config.STORE_CONFIG:
|
|
147
|
+
config_ = langgraph_config.STORE_CONFIG
|
|
162
148
|
store.set_store_config(config_)
|
|
163
149
|
|
|
164
150
|
if not os.path.exists(".langgraph_api"):
|
|
@@ -187,7 +173,7 @@ async def start_pool() -> None:
|
|
|
187
173
|
for k in ["crons"]:
|
|
188
174
|
if not GLOBAL_STORE.get(k):
|
|
189
175
|
GLOBAL_STORE[k] = {}
|
|
190
|
-
await
|
|
176
|
+
await start_stream()
|
|
191
177
|
|
|
192
178
|
|
|
193
179
|
async def stop_pool() -> None:
|
|
@@ -200,7 +186,7 @@ async def stop_pool() -> None:
|
|
|
200
186
|
|
|
201
187
|
async with Checkpointer():
|
|
202
188
|
pass
|
|
203
|
-
await
|
|
189
|
+
await stop_stream()
|
|
204
190
|
|
|
205
191
|
|
|
206
192
|
async def healthcheck() -> None:
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Message:
|
|
12
|
+
topic: bytes
|
|
13
|
+
data: bytes
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ContextQueue(asyncio.Queue):
|
|
17
|
+
"""Queue that supports async context manager protocol"""
|
|
18
|
+
|
|
19
|
+
async def __aenter__(self):
|
|
20
|
+
return self
|
|
21
|
+
|
|
22
|
+
async def __aexit__(
|
|
23
|
+
self,
|
|
24
|
+
exc_type: type[BaseException] | None,
|
|
25
|
+
exc_val: BaseException | None,
|
|
26
|
+
exc_tb: object | None,
|
|
27
|
+
) -> None:
|
|
28
|
+
# Clear the queue
|
|
29
|
+
while not self.empty():
|
|
30
|
+
try:
|
|
31
|
+
self.get_nowait()
|
|
32
|
+
except asyncio.QueueEmpty:
|
|
33
|
+
break
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class StreamManager:
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self.queues = defaultdict(list) # Dict[UUID, List[asyncio.Queue]]
|
|
39
|
+
self.control_queues = defaultdict(list)
|
|
40
|
+
|
|
41
|
+
def get_queues(self, run_id: UUID) -> list[asyncio.Queue]:
|
|
42
|
+
return self.queues[run_id]
|
|
43
|
+
|
|
44
|
+
async def put(self, run_id: UUID, message: Message) -> None:
|
|
45
|
+
topic = message.topic.decode()
|
|
46
|
+
if "control" in topic:
|
|
47
|
+
self.control_queues[run_id].append(message)
|
|
48
|
+
queues = self.queues.get(run_id, [])
|
|
49
|
+
coros = [queue.put(message) for queue in queues]
|
|
50
|
+
results = await asyncio.gather(*coros, return_exceptions=True)
|
|
51
|
+
for result in results:
|
|
52
|
+
if isinstance(result, Exception):
|
|
53
|
+
logger.exception(f"Failed to put message in queue: {result}")
|
|
54
|
+
|
|
55
|
+
async def add_queue(self, run_id: UUID) -> asyncio.Queue:
|
|
56
|
+
queue = ContextQueue()
|
|
57
|
+
self.queues[run_id].append(queue)
|
|
58
|
+
for control_msg in self.control_queues[run_id]:
|
|
59
|
+
try:
|
|
60
|
+
await queue.put(control_msg)
|
|
61
|
+
except Exception:
|
|
62
|
+
logger.exception(
|
|
63
|
+
f"Failed to put control message in queue: {control_msg}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return queue
|
|
67
|
+
|
|
68
|
+
async def remove_queue(self, run_id: UUID, queue: asyncio.Queue):
|
|
69
|
+
if run_id in self.queues:
|
|
70
|
+
self.queues[run_id].remove(queue)
|
|
71
|
+
if not self.queues[run_id]:
|
|
72
|
+
del self.queues[run_id]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# Global instance
|
|
76
|
+
stream_manager = StreamManager()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def start_stream() -> None:
|
|
80
|
+
"""Initialize the queue system.
|
|
81
|
+
In this in-memory implementation, we just need to ensure we have a clean StreamManager instance.
|
|
82
|
+
"""
|
|
83
|
+
global stream_manager
|
|
84
|
+
stream_manager = StreamManager()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def stop_stream() -> None:
|
|
88
|
+
"""Clean up the queue system.
|
|
89
|
+
Clear all queues and stored control messages."""
|
|
90
|
+
global stream_manager
|
|
91
|
+
|
|
92
|
+
# Send 'done' message to all active queues before clearing
|
|
93
|
+
for run_id in list(stream_manager.queues.keys()):
|
|
94
|
+
control_message = Message(topic=f"run:{run_id}:control".encode(), data=b"done")
|
|
95
|
+
for queue in stream_manager.queues[run_id]:
|
|
96
|
+
try:
|
|
97
|
+
await queue.put(control_message)
|
|
98
|
+
except (Exception, RuntimeError):
|
|
99
|
+
pass # Ignore errors during shutdown
|
|
100
|
+
|
|
101
|
+
# Clear all stored data
|
|
102
|
+
stream_manager.queues.clear()
|
|
103
|
+
stream_manager.control_queues.clear()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_stream_manager() -> StreamManager:
|
|
107
|
+
"""Get the global stream manager instance."""
|
|
108
|
+
return stream_manager
|
langgraph_storage/ops.py
CHANGED
|
@@ -46,7 +46,7 @@ from langgraph_api.serde import Fragment
|
|
|
46
46
|
from langgraph_api.utils import fetchone, get_auth_ctx
|
|
47
47
|
from langgraph_storage.checkpoint import Checkpointer
|
|
48
48
|
from langgraph_storage.database import InMemConnectionProto, connect
|
|
49
|
-
from langgraph_storage.
|
|
49
|
+
from langgraph_storage.inmem_stream import Message, get_stream_manager
|
|
50
50
|
|
|
51
51
|
logger = structlog.stdlib.get_logger(__name__)
|
|
52
52
|
|
|
@@ -229,6 +229,7 @@ class Assistants(Authenticated):
|
|
|
229
229
|
"config": config or {},
|
|
230
230
|
"metadata": metadata or {},
|
|
231
231
|
"created_at": now,
|
|
232
|
+
"name": name,
|
|
232
233
|
}
|
|
233
234
|
conn.store["assistants"].append(new_assistant)
|
|
234
235
|
conn.store["assistant_versions"].append(new_version)
|
|
@@ -312,6 +313,7 @@ class Assistants(Authenticated):
|
|
|
312
313
|
"config": config if config is not None else assistant["config"],
|
|
313
314
|
"metadata": metadata if metadata is not None else assistant["metadata"],
|
|
314
315
|
"created_at": now,
|
|
316
|
+
"name": name if name is not None else assistant["name"],
|
|
315
317
|
}
|
|
316
318
|
conn.store["assistant_versions"].append(new_version_entry)
|
|
317
319
|
|
|
@@ -459,6 +461,14 @@ class Assistants(Authenticated):
|
|
|
459
461
|
"read",
|
|
460
462
|
Auth.types.AssistantsRead(assistant_id=assistant_id),
|
|
461
463
|
)
|
|
464
|
+
assistant = next(
|
|
465
|
+
(a for a in conn.store["assistants"] if a["assistant_id"] == assistant_id),
|
|
466
|
+
None,
|
|
467
|
+
)
|
|
468
|
+
if not assistant:
|
|
469
|
+
raise HTTPException(
|
|
470
|
+
status_code=404, detail=f"Assistant {assistant_id} not found"
|
|
471
|
+
)
|
|
462
472
|
versions = [
|
|
463
473
|
v
|
|
464
474
|
for v in conn.store["assistant_versions"]
|
|
@@ -466,6 +476,12 @@ class Assistants(Authenticated):
|
|
|
466
476
|
and (not metadata or is_jsonb_contained(v["metadata"], metadata))
|
|
467
477
|
and (not filters or _check_filter_match(v["metadata"], filters))
|
|
468
478
|
]
|
|
479
|
+
|
|
480
|
+
# Previously, the name was not included in the assistant_versions table. So we should add them here.
|
|
481
|
+
for v in versions:
|
|
482
|
+
if "name" not in v:
|
|
483
|
+
v["name"] = assistant["name"]
|
|
484
|
+
|
|
469
485
|
versions.sort(key=lambda x: x["version"], reverse=True)
|
|
470
486
|
|
|
471
487
|
async def _yield_versions():
|
|
@@ -767,7 +783,7 @@ class Threads(Authenticated):
|
|
|
767
783
|
async def has_pending_runs(conn_: InMemConnectionProto, tid: UUID) -> bool:
|
|
768
784
|
"""Check if thread has any pending runs."""
|
|
769
785
|
return any(
|
|
770
|
-
run["status"]
|
|
786
|
+
run["status"] in ("pending", "running") and run["thread_id"] == tid
|
|
771
787
|
for run in conn_.store["runs"]
|
|
772
788
|
)
|
|
773
789
|
|
|
@@ -846,8 +862,6 @@ class Threads(Authenticated):
|
|
|
846
862
|
raise HTTPException(
|
|
847
863
|
status_code=404, detail=f"Thread with ID {thread_id} not found"
|
|
848
864
|
)
|
|
849
|
-
# Delete the thread
|
|
850
|
-
conn.locks.pop(thread_id, None)
|
|
851
865
|
# Cascade delete all runs associated with this thread
|
|
852
866
|
conn.store["runs"] = [
|
|
853
867
|
run for run in conn.store["runs"] if run["thread_id"] != thread_id
|
|
@@ -1120,6 +1134,9 @@ class Threads(Authenticated):
|
|
|
1120
1134
|
return []
|
|
1121
1135
|
|
|
1122
1136
|
|
|
1137
|
+
RUN_LOCK = asyncio.Lock()
|
|
1138
|
+
|
|
1139
|
+
|
|
1123
1140
|
class Runs(Authenticated):
|
|
1124
1141
|
resource = "threads"
|
|
1125
1142
|
|
|
@@ -1127,12 +1144,18 @@ class Runs(Authenticated):
|
|
|
1127
1144
|
async def stats(conn: InMemConnectionProto) -> QueueStats:
|
|
1128
1145
|
"""Get stats about the queue."""
|
|
1129
1146
|
pending_runs = [run for run in conn.store["runs"] if run["status"] == "pending"]
|
|
1147
|
+
running_runs = [run for run in conn.store["runs"] if run["status"] == "running"]
|
|
1130
1148
|
|
|
1131
|
-
if not pending_runs:
|
|
1132
|
-
return {
|
|
1149
|
+
if not pending_runs and not running_runs:
|
|
1150
|
+
return {
|
|
1151
|
+
"n_pending": 0,
|
|
1152
|
+
"max_age_secs": None,
|
|
1153
|
+
"med_age_secs": None,
|
|
1154
|
+
"n_running": 0,
|
|
1155
|
+
}
|
|
1133
1156
|
|
|
1134
1157
|
# Get all creation timestamps
|
|
1135
|
-
created_times = [run.get("created_at") for run in pending_runs]
|
|
1158
|
+
created_times = [run.get("created_at") for run in (pending_runs + running_runs)]
|
|
1136
1159
|
created_times = [
|
|
1137
1160
|
t for t in created_times if t is not None
|
|
1138
1161
|
] # Filter out None values
|
|
@@ -1140,6 +1163,7 @@ class Runs(Authenticated):
|
|
|
1140
1163
|
if not created_times:
|
|
1141
1164
|
return {
|
|
1142
1165
|
"n_pending": len(pending_runs),
|
|
1166
|
+
"n_running": len(running_runs),
|
|
1143
1167
|
"max_age_secs": None,
|
|
1144
1168
|
"med_age_secs": None,
|
|
1145
1169
|
}
|
|
@@ -1154,13 +1178,13 @@ class Runs(Authenticated):
|
|
|
1154
1178
|
|
|
1155
1179
|
return {
|
|
1156
1180
|
"n_pending": len(pending_runs),
|
|
1181
|
+
"n_running": len(running_runs),
|
|
1157
1182
|
"max_age_secs": oldest_time,
|
|
1158
1183
|
"med_age_secs": median_time,
|
|
1159
1184
|
}
|
|
1160
1185
|
|
|
1161
|
-
@asynccontextmanager
|
|
1162
1186
|
@staticmethod
|
|
1163
|
-
async def next(wait: bool) -> AsyncIterator[tuple[Run, int]
|
|
1187
|
+
async def next(wait: bool, limit: int = 1) -> AsyncIterator[tuple[Run, int]]:
|
|
1164
1188
|
"""Get the next run from the queue, and the attempt number.
|
|
1165
1189
|
1 is the first attempt, 2 is the first retry, etc."""
|
|
1166
1190
|
now = datetime.now(UTC)
|
|
@@ -1170,7 +1194,7 @@ class Runs(Authenticated):
|
|
|
1170
1194
|
else:
|
|
1171
1195
|
await asyncio.sleep(0)
|
|
1172
1196
|
|
|
1173
|
-
async with connect() as conn:
|
|
1197
|
+
async with connect() as conn, RUN_LOCK:
|
|
1174
1198
|
pending_runs = sorted(
|
|
1175
1199
|
[
|
|
1176
1200
|
run
|
|
@@ -1181,48 +1205,47 @@ class Runs(Authenticated):
|
|
|
1181
1205
|
)
|
|
1182
1206
|
|
|
1183
1207
|
if not pending_runs:
|
|
1184
|
-
yield None
|
|
1185
1208
|
return
|
|
1186
1209
|
|
|
1187
1210
|
# Try to lock and get the first available run
|
|
1188
|
-
for run in pending_runs:
|
|
1211
|
+
for _, run in zip(range(limit), pending_runs, strict=False):
|
|
1212
|
+
if run["status"] != "pending":
|
|
1213
|
+
continue
|
|
1214
|
+
|
|
1189
1215
|
run_id = run["run_id"]
|
|
1190
1216
|
thread_id = run["thread_id"]
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
try:
|
|
1196
|
-
if run["status"] != "pending":
|
|
1197
|
-
continue
|
|
1217
|
+
thread = next(
|
|
1218
|
+
(t for t in conn.store["threads"] if t["thread_id"] == thread_id),
|
|
1219
|
+
None,
|
|
1220
|
+
)
|
|
1198
1221
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
if t["thread_id"] == run["thread_id"]
|
|
1204
|
-
),
|
|
1205
|
-
None,
|
|
1222
|
+
if thread is None:
|
|
1223
|
+
await logger.awarning(
|
|
1224
|
+
"Unexpected missing thread in Runs.next",
|
|
1225
|
+
thread_id=run["thread_id"],
|
|
1206
1226
|
)
|
|
1227
|
+
continue
|
|
1207
1228
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
"Unexpected missing thread in Runs.next",
|
|
1211
|
-
thread_id=run["thread_id"],
|
|
1212
|
-
)
|
|
1213
|
-
continue
|
|
1229
|
+
if run["status"] != "pending":
|
|
1230
|
+
continue
|
|
1214
1231
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1232
|
+
if any(
|
|
1233
|
+
run["status"] == "running"
|
|
1234
|
+
for run in conn.store["runs"]
|
|
1235
|
+
if run["thread_id"] == thread_id
|
|
1236
|
+
):
|
|
1237
|
+
continue
|
|
1238
|
+
# Increment attempt counter
|
|
1239
|
+
attempt = await conn.retry_counter.increment(run_id)
|
|
1240
|
+
# Set run as "running"
|
|
1241
|
+
run["status"] = "running"
|
|
1242
|
+
yield run, attempt
|
|
1222
1243
|
|
|
1223
1244
|
@asynccontextmanager
|
|
1224
1245
|
@staticmethod
|
|
1225
|
-
async def enter(
|
|
1246
|
+
async def enter(
|
|
1247
|
+
run_id: UUID, loop: asyncio.AbstractEventLoop
|
|
1248
|
+
) -> AsyncIterator[ValueEvent]:
|
|
1226
1249
|
"""Enter a run, listen for cancellation while running, signal when done."
|
|
1227
1250
|
This method should be called as a context manager by a worker executing a run.
|
|
1228
1251
|
"""
|
|
@@ -1234,20 +1257,18 @@ class Runs(Authenticated):
|
|
|
1234
1257
|
done = ValueEvent()
|
|
1235
1258
|
tg.create_task(listen_for_cancellation(queue, run_id, done))
|
|
1236
1259
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
topic=f"run:{run_id}:control".encode(), data=b"done"
|
|
1244
|
-
)
|
|
1260
|
+
# Give done event to caller
|
|
1261
|
+
yield done
|
|
1262
|
+
# Signal done to all subscribers
|
|
1263
|
+
control_message = Message(
|
|
1264
|
+
topic=f"run:{run_id}:control".encode(), data=b"done"
|
|
1265
|
+
)
|
|
1245
1266
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1267
|
+
# Store the control message for late subscribers
|
|
1268
|
+
await stream_manager.put(run_id, control_message)
|
|
1269
|
+
stream_manager.control_queues[run_id].append(control_message)
|
|
1270
|
+
# Clean up this queue
|
|
1271
|
+
await stream_manager.remove_queue(run_id, queue)
|
|
1251
1272
|
|
|
1252
1273
|
@staticmethod
|
|
1253
1274
|
async def sweep(conn: InMemConnectionProto) -> list[UUID]:
|
|
@@ -1350,6 +1371,7 @@ class Runs(Authenticated):
|
|
|
1350
1371
|
created_at=datetime.now(UTC),
|
|
1351
1372
|
updated_at=datetime.now(UTC),
|
|
1352
1373
|
)
|
|
1374
|
+
await logger.ainfo("Creating thread", thread_id=thread_id)
|
|
1353
1375
|
conn.store["threads"].append(thread)
|
|
1354
1376
|
elif existing_thread:
|
|
1355
1377
|
# Update existing thread
|
|
@@ -1382,7 +1404,7 @@ class Runs(Authenticated):
|
|
|
1382
1404
|
inflight_runs = [
|
|
1383
1405
|
r
|
|
1384
1406
|
for r in conn.store["runs"]
|
|
1385
|
-
if r["thread_id"] == thread_id and r["status"]
|
|
1407
|
+
if r["thread_id"] == thread_id and r["status"] in ("pending", "running")
|
|
1386
1408
|
]
|
|
1387
1409
|
if prevent_insert_if_inflight:
|
|
1388
1410
|
if inflight_runs:
|
|
@@ -1521,6 +1543,7 @@ class Runs(Authenticated):
|
|
|
1521
1543
|
raise HTTPException(status_code=404, detail="Run not found")
|
|
1522
1544
|
|
|
1523
1545
|
async def _yield_deleted():
|
|
1546
|
+
await logger.ainfo("Run deleted", run_id=run_id)
|
|
1524
1547
|
yield run_id
|
|
1525
1548
|
|
|
1526
1549
|
return _yield_deleted()
|
|
@@ -1545,7 +1568,7 @@ class Runs(Authenticated):
|
|
|
1545
1568
|
# wait for the run to complete
|
|
1546
1569
|
# Rely on this join's auth
|
|
1547
1570
|
async for mode, chunk in Runs.Stream.join(
|
|
1548
|
-
run_id, thread_id=thread_id, stream_mode="values", ctx=ctx
|
|
1571
|
+
run_id, thread_id=thread_id, stream_mode="values", ctx=ctx, ignore_404=True
|
|
1549
1572
|
):
|
|
1550
1573
|
if mode == b"values":
|
|
1551
1574
|
last_chunk = chunk
|
|
@@ -1616,7 +1639,7 @@ class Runs(Authenticated):
|
|
|
1616
1639
|
coros.append(stream_manager.put(run_id, control_message))
|
|
1617
1640
|
|
|
1618
1641
|
# Update status for pending runs
|
|
1619
|
-
if run["status"]
|
|
1642
|
+
if run["status"] in ("pending", "running"):
|
|
1620
1643
|
if queues or action != "rollback":
|
|
1621
1644
|
run["status"] = "interrupted"
|
|
1622
1645
|
run["updated_at"] = datetime.now(tz=UTC)
|
|
@@ -1793,7 +1816,7 @@ class Runs(Authenticated):
|
|
|
1793
1816
|
),
|
|
1794
1817
|
)
|
|
1795
1818
|
break
|
|
1796
|
-
elif run["status"]
|
|
1819
|
+
elif run["status"] not in ("pending", "running"):
|
|
1797
1820
|
break
|
|
1798
1821
|
except WrappedHTTPException as e:
|
|
1799
1822
|
raise e.http_exception from None
|