langgraph-runtime-inmem 0.38.0.dev6__tar.gz → 0.38.0.dev8__tar.gz

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.
Files changed (19) hide show
  1. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/PKG-INFO +1 -1
  2. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/__init__.py +1 -1
  3. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/ops.py +145 -26
  4. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/uv.lock +10 -10
  5. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/.gitignore +0 -0
  6. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/Makefile +0 -0
  7. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/README.md +0 -0
  8. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/_persistence.py +0 -0
  9. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/checkpoint.py +0 -0
  10. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/database.py +0 -0
  11. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/encryption.py +0 -0
  12. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/inmem_stream.py +0 -0
  13. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/lifespan.py +0 -0
  14. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/metrics.py +0 -0
  15. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/queue.py +0 -0
  16. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/retry.py +0 -0
  17. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/routes.py +0 -0
  18. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/langgraph_runtime_inmem/store.py +0 -0
  19. {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev8}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-runtime-inmem
3
- Version: 0.38.0.dev6
3
+ Version: 0.38.0.dev8
4
4
  Summary: Inmem implementation for the LangGraph API server.
5
5
  Author-email: Will Fu-Hinthorn <will@langchain.dev>
6
6
  License: Elastic-2.0
@@ -11,7 +11,7 @@ from langgraph_runtime_inmem import (
11
11
  store,
12
12
  )
13
13
 
14
- __version__ = "0.38.0.dev6"
14
+ __version__ = "0.38.0.dev8"
15
15
  __all__ = [
16
16
  "ops",
17
17
  "database",
@@ -10,7 +10,7 @@ import typing
10
10
  import uuid
11
11
  from collections import defaultdict
12
12
  from collections.abc import AsyncIterator, Sequence
13
- from contextlib import AsyncExitStack, asynccontextmanager
13
+ from contextlib import AsyncExitStack, aclosing, asynccontextmanager
14
14
  from datetime import UTC, datetime, timedelta
15
15
  from typing import Any, Literal, cast
16
16
  from uuid import UUID, uuid4
@@ -54,6 +54,7 @@ if typing.TYPE_CHECKING:
54
54
  PoolStats,
55
55
  QueueStats,
56
56
  Run,
57
+ RunCancelProgress,
57
58
  RunSelectField,
58
59
  RunStatus,
59
60
  StreamMode,
@@ -263,6 +264,18 @@ def _ensure_uuid(id_: str | uuid.UUID | None) -> uuid.UUID:
263
264
  return id_
264
265
 
265
266
 
267
+ _EPOCH = datetime(1970, 1, 1, tzinfo=UTC)
268
+
269
+
270
+ def _accepted_at_or_before(run_id: uuid.UUID, boundary_ms: int) -> bool:
271
+ """Whether a run was accepted at or before a Unix-millisecond boundary.
272
+
273
+ Run IDs are UUIDv7, whose first 48 bits are the millisecond they were
274
+ generated. A run whose ID isn't a UUIDv7 counts as accepted before.
275
+ """
276
+ return run_id.version != 7 or run_id.int >> 80 <= boundary_ms
277
+
278
+
266
279
  def _ensure_datetime(value: datetime | str | None) -> datetime | None:
267
280
  if value is None or isinstance(value, datetime):
268
281
  return value
@@ -2177,7 +2190,38 @@ class Threads(Authenticated):
2177
2190
  metadata: MetadataInput = None,
2178
2191
  ctx: Auth.types.BaseAuthContext | None = None,
2179
2192
  ) -> list[StateSnapshot]:
2180
- """Get the history of a thread."""
2193
+ """Get the history of a thread.
2194
+
2195
+ Kept for langgraph-api versions that predate ``iterate``.
2196
+ """
2197
+ return [
2198
+ state
2199
+ async for state in Threads.State.iterate(
2200
+ conn,
2201
+ config=config,
2202
+ limit=limit,
2203
+ before=before,
2204
+ metadata=metadata,
2205
+ ctx=ctx,
2206
+ )
2207
+ ]
2208
+
2209
+ @staticmethod
2210
+ async def iterate(
2211
+ conn: InMemConnectionProto,
2212
+ *,
2213
+ config: Config,
2214
+ limit: int = 1,
2215
+ before: str | Checkpoint | None = None,
2216
+ metadata: MetadataInput = None,
2217
+ ctx: Auth.types.BaseAuthContext | None = None,
2218
+ incremental: bool = False,
2219
+ ) -> AsyncIterator[StateSnapshot]:
2220
+ """Yield the history of a thread.
2221
+
2222
+ With ``incremental``, checkpoints load in growing pages, so a
2223
+ caller that stops early does not load the rest of ``limit``.
2224
+ """
2181
2225
  from langgraph_api.graph import get_graph # noqa: PLC0415
2182
2226
  from langgraph_api.store import get_store # noqa: PLC0415
2183
2227
  from langgraph_api.utils import fetchone # noqa: PLC0415
@@ -2196,7 +2240,7 @@ class Threads(Authenticated):
2196
2240
  # Parse thread metadata and config
2197
2241
  thread_metadata = thread["metadata"]
2198
2242
  if not _check_filter_match(thread_metadata, filters):
2199
- return []
2243
+ return
2200
2244
 
2201
2245
  thread_config = cast(dict[str, Any], thread["config"])
2202
2246
  thread_config = {
@@ -2225,16 +2269,27 @@ class Threads(Authenticated):
2225
2269
  else before
2226
2270
  )
2227
2271
 
2228
- states = [
2229
- state
2230
- async for state in graph.aget_state_history(
2231
- config, limit=limit, filter=metadata, before=before_param
2272
+ if incremental:
2273
+ from langgraph_api.state import ( # noqa: PLC0415
2274
+ iter_state_history,
2232
2275
  )
2233
- ]
2234
-
2235
- return states
2236
2276
 
2237
- return []
2277
+ history = iter_state_history(
2278
+ graph,
2279
+ config,
2280
+ limit=limit,
2281
+ filter=metadata,
2282
+ before=before_param,
2283
+ )
2284
+ else:
2285
+ history = graph.aget_state_history(
2286
+ config, limit=limit, filter=metadata, before=before_param
2287
+ )
2288
+ async with aclosing(history) as states:
2289
+ async for state in states:
2290
+ yield state
2291
+ # Drop the entry before the next page loads.
2292
+ del state
2238
2293
 
2239
2294
  class Stream(Authenticated):
2240
2295
  resource = "threads"
@@ -3248,12 +3303,88 @@ class Runs(Authenticated):
3248
3303
  return
3249
3304
  raise HTTPException(status_code=404, detail="No runs found to cancel.")
3250
3305
 
3306
+ cancelable_runs = await Runs._cancel_runs(
3307
+ conn, candidate_runs, action=action, reason=reason
3308
+ )
3309
+ if not cancelable_runs:
3310
+ # When cancelling by assistant_id, it's valid to have no cancelable runs.
3311
+ # An interrupt that finds a rollback under way leaves it alone and
3312
+ # succeeds, as in the Go runtime.
3313
+ if assistant_id is not None or any(
3314
+ run["status"] == "running" and run.get("cancel_action") == "rollback"
3315
+ for run in candidate_runs
3316
+ ):
3317
+ return
3318
+ raise HTTPException(
3319
+ status_code=404,
3320
+ detail="No matching runs to cancel. Please verify the thread ID and run IDs are correct, and the runs haven't been deleted or completed.",
3321
+ )
3322
+
3323
+ await logger.ainfo(
3324
+ "Cancelled runs",
3325
+ run_ids=[str(r["run_id"]) for r in cancelable_runs],
3326
+ thread_id=str(thread_id) if thread_id else None,
3327
+ status=status,
3328
+ action=action,
3329
+ )
3330
+
3331
+ @staticmethod
3332
+ async def cancel_accepted_before(
3333
+ conn: InMemConnectionProto,
3334
+ accepted_before: datetime,
3335
+ *,
3336
+ reason: str,
3337
+ ) -> RunCancelProgress:
3338
+ """Interrupt every in-flight run accepted at or before `accepted_before`.
3339
+
3340
+ Applies no auth filters, so only the operator route may call it. As with
3341
+ `cancel`, running runs are marked interrupted when they're signalled, so
3342
+ `finishing` only counts runs whose worker is still applying a rollback.
3343
+ """
3344
+ boundary_ms = (accepted_before - _EPOCH) // timedelta(milliseconds=1)
3345
+
3346
+ def in_flight_targets() -> list[dict]:
3347
+ return [
3348
+ run
3349
+ for run in conn.store["runs"]
3350
+ if run["status"] in ("pending", "running")
3351
+ and _accepted_at_or_before(_ensure_uuid(run["run_id"]), boundary_ms)
3352
+ ]
3353
+
3354
+ cancelled = await Runs._cancel_runs(
3355
+ conn, in_flight_targets(), action="interrupt", reason=reason
3356
+ )
3357
+ if cancelled:
3358
+ await logger.ainfo(
3359
+ "Cancelled runs accepted before boundary",
3360
+ run_ids=[str(r["run_id"]) for r in cancelled],
3361
+ accepted_before=accepted_before.isoformat(),
3362
+ )
3363
+ remaining = in_flight_targets()
3364
+ finishing = sum(1 for run in remaining if run.get("cancel_action"))
3365
+ return {"not_yet_reached": len(remaining) - finishing, "finishing": finishing}
3366
+
3367
+ @staticmethod
3368
+ async def _cancel_runs(
3369
+ conn: InMemConnectionProto,
3370
+ candidate_runs: list[dict],
3371
+ *,
3372
+ action: Literal["interrupt", "rollback"],
3373
+ reason: str | None,
3374
+ ) -> list[dict]:
3375
+ """Signal each candidate run and cancel the ones still in flight.
3376
+
3377
+ Returns the runs that were in flight. When none were, nothing is changed.
3378
+ """
3251
3379
  stream_manager = get_stream_manager()
3252
3380
  coros = []
3253
3381
  cancelable_runs = []
3254
3382
  pending_thread_ids: set[UUID] = set()
3255
3383
 
3256
3384
  for run in candidate_runs:
3385
+ if action == "interrupt" and run.get("cancel_action") == "rollback":
3386
+ # A rollback already under way wins; its worker finalizes it.
3387
+ continue
3257
3388
  run_id = run["run_id"]
3258
3389
  control_message = Message(
3259
3390
  topic=f"run:{run_id}:control".encode(),
@@ -3271,7 +3402,7 @@ class Runs(Authenticated):
3271
3402
  pending_thread_ids.add(run["thread_id"])
3272
3403
  if action == "rollback" and run["status"] == "running":
3273
3404
  # Its worker finalizes the rollback; staying "running" holds the thread's next run until then.
3274
- pass
3405
+ run["cancel_action"] = "rollback"
3275
3406
  elif queues or action != "rollback":
3276
3407
  run["status"] = "interrupted"
3277
3408
  run["cancel_reason"] = reason
@@ -3290,13 +3421,7 @@ class Runs(Authenticated):
3290
3421
  )
3291
3422
 
3292
3423
  if not cancelable_runs:
3293
- # When cancelling by assistant_id, it's valid to have no cancelable runs
3294
- if assistant_id is not None:
3295
- return
3296
- raise HTTPException(
3297
- status_code=404,
3298
- detail="No matching runs to cancel. Please verify the thread ID and run IDs are correct, and the runs haven't been deleted or completed.",
3299
- )
3424
+ return []
3300
3425
 
3301
3426
  if coros:
3302
3427
  await asyncio.gather(*coros)
@@ -3312,13 +3437,7 @@ class Runs(Authenticated):
3312
3437
  )
3313
3438
  thread["updated_at"] = datetime.now(tz=UTC)
3314
3439
 
3315
- await logger.ainfo(
3316
- "Cancelled runs",
3317
- run_ids=[str(r["run_id"]) for r in cancelable_runs],
3318
- thread_id=str(thread_id) if thread_id else None,
3319
- status=status,
3320
- action=action,
3321
- )
3440
+ return cancelable_runs
3322
3441
 
3323
3442
  @staticmethod
3324
3443
  async def search(
@@ -1,5 +1,5 @@
1
1
  version = 1
2
- revision = 3
2
+ revision = 5
3
3
  requires-python = ">=3.11.0"
4
4
 
5
5
  [[package]]
@@ -300,15 +300,15 @@ wheels = [
300
300
 
301
301
  [[package]]
302
302
  name = "langgraph-checkpoint"
303
- version = "4.2.0"
303
+ version = "4.3.0"
304
304
  source = { registry = "https://pypi.org/simple" }
305
305
  dependencies = [
306
306
  { name = "langchain-core" },
307
307
  { name = "ormsgpack" },
308
308
  ]
309
- sdist = { url = "https://files.pythonhosted.org/packages/dc/e1/089c4c9e0a2fec7f883f82ae8e6a727138d50074cfeb6644bc2d13b1019b/langgraph_checkpoint-4.2.0.tar.gz", hash = "sha256:51a593b6bee684b0818e5d6e58e28ab340c6db7794575056ce7bd1b746a84ed7", size = 180239, upload-time = "2026-08-07T20:05:03.756Z" }
309
+ sdist = { url = "https://files.pythonhosted.org/packages/0f/69/31fdbdc65a85bbd6178afa193c772bb926620f47b4869638bc2bc80afaaa/langgraph_checkpoint-4.3.0.tar.gz", hash = "sha256:c75965d84cc2c1d549163e910a15bcb577758001b141619d05297c463280b018", size = 182652, upload-time = "2026-10-12T22:26:31.478Z" }
310
310
  wheels = [
311
- { url = "https://files.pythonhosted.org/packages/05/71/3b475f09bd57d3a5649792c66353312b4432afd843f301739dfcebd157f0/langgraph_checkpoint-4.2.0-py3-none-any.whl", hash = "sha256:0547fd228935a0b758865de3a3d6d7a2537c308895d0f9ab092ce9151b5da942", size = 56833, upload-time = "2026-08-07T20:05:02.655Z" },
311
+ { url = "https://files.pythonhosted.org/packages/1f/0c/84747e340bf4f29291c84cdd5733fc8d0a822f3d33bb24e664a18afa4a7c/langgraph_checkpoint-4.3.0-py3-none-any.whl", hash = "sha256:bedfafe2f997ded60e4fa593e79f56f436a6e45586392dc382aa810d0c751c64", size = 58063, upload-time = "2026-10-12T22:26:30.429Z" },
312
312
  ]
313
313
 
314
314
  [[package]]
@@ -388,7 +388,7 @@ dev = [
388
388
 
389
389
  [[package]]
390
390
  name = "langgraph-sdk"
391
- version = "0.4.2"
391
+ version = "0.4.4"
392
392
  source = { registry = "https://pypi.org/simple" }
393
393
  dependencies = [
394
394
  { name = "httpx" },
@@ -397,9 +397,9 @@ dependencies = [
397
397
  { name = "orjson" },
398
398
  { name = "websockets" },
399
399
  ]
400
- sdist = { url = "https://files.pythonhosted.org/packages/b4/2b/bd8ac26d4e97f6df88ef05ce5b6a38945a3903e1025d926f4752aa88aa97/langgraph_sdk-0.4.2.tar.gz", hash = "sha256:b88f0f5f6328ac0680d6790614a905b2bcfa257f2276dba4e38f0e86db0aa738", size = 348327, upload-time = "2026-06-01T17:51:19.856Z" }
400
+ sdist = { url = "https://files.pythonhosted.org/packages/3f/ae/91446c1fffa04a2dc1f81afbfb5bfff3590452891a72bd9098a656ab2657/langgraph_sdk-0.4.4.tar.gz", hash = "sha256:4e651ffa09de695681579396375377bdde23bedbc8e35b070e615cbd5af7da8b", size = 345789, upload-time = "2026-08-27T21:25:22.593Z" }
401
401
  wheels = [
402
- { url = "https://files.pythonhosted.org/packages/a0/05/aac507337cceae773c2cc9ab91eb6301963af7aeeb55b4217a00e15aff17/langgraph_sdk-0.4.2-py3-none-any.whl", hash = "sha256:75fa5096c1177ce39c847096a8fe3745ffd480ddb412995f836e9f5f884c43dd", size = 160521, upload-time = "2026-06-01T17:51:18.849Z" },
402
+ { url = "https://files.pythonhosted.org/packages/43/d5/2ad7f3a835c6fcebf58b6669552536ecd52d2dfe4cf49c6c36648fd83572/langgraph_sdk-0.4.4-py3-none-any.whl", hash = "sha256:39afe416c91742925e6f8a93715f566d499b36e1b636b804a4ffe3190e4f4e64", size = 162270, upload-time = "2026-08-27T21:25:21.072Z" },
403
403
  ]
404
404
 
405
405
  [[package]]
@@ -1199,14 +1199,14 @@ wheels = [
1199
1199
 
1200
1200
  [[package]]
1201
1201
  name = "werkzeug"
1202
- version = "3.1.8"
1202
+ version = "3.1.9"
1203
1203
  source = { registry = "https://pypi.org/simple" }
1204
1204
  dependencies = [
1205
1205
  { name = "markupsafe" },
1206
1206
  ]
1207
- sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" }
1207
+ sdist = { url = "https://files.pythonhosted.org/packages/a4/34/4dd12fc8bb7d61c91467ec3efe415ffa7d5456f799954b40c5bbaeae470e/werkzeug-3.1.9.tar.gz", hash = "sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060", size = 940188, upload-time = "2026-09-27T18:33:41.637Z" }
1208
1208
  wheels = [
1209
- { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" },
1209
+ { url = "https://files.pythonhosted.org/packages/a1/38/df03f564f43cec2684823f3cccae1a652ee7face1cbaa76fb223096e64d7/werkzeug-3.1.9-py3-none-any.whl", hash = "sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab", size = 228700, upload-time = "2026-09-27T18:33:39.685Z" },
1210
1210
  ]
1211
1211
 
1212
1212
  [[package]]