langgraph-api 0.0.14__py3-none-any.whl → 0.0.16__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.

Files changed (37) hide show
  1. langgraph_api/api/__init__.py +2 -1
  2. langgraph_api/api/assistants.py +4 -4
  3. langgraph_api/api/store.py +67 -15
  4. langgraph_api/asyncio.py +5 -0
  5. langgraph_api/auth/custom.py +20 -5
  6. langgraph_api/config.py +1 -0
  7. langgraph_api/graph.py +6 -13
  8. langgraph_api/js/base.py +9 -0
  9. langgraph_api/js/build.mts +2 -0
  10. langgraph_api/js/client.mts +383 -409
  11. langgraph_api/js/client.new.mts +856 -0
  12. langgraph_api/js/errors.py +11 -0
  13. langgraph_api/js/package.json +3 -1
  14. langgraph_api/js/remote.py +16 -673
  15. langgraph_api/js/remote_new.py +693 -0
  16. langgraph_api/js/remote_old.py +665 -0
  17. langgraph_api/js/schema.py +29 -0
  18. langgraph_api/js/src/utils/serde.mts +7 -0
  19. langgraph_api/js/tests/api.test.mts +125 -8
  20. langgraph_api/js/tests/compose-postgres.yml +2 -1
  21. langgraph_api/js/tests/graphs/agent.mts +2 -0
  22. langgraph_api/js/tests/graphs/delay.mts +30 -0
  23. langgraph_api/js/tests/graphs/langgraph.json +2 -1
  24. langgraph_api/js/yarn.lock +870 -18
  25. langgraph_api/models/run.py +1 -0
  26. langgraph_api/queue.py +129 -31
  27. langgraph_api/route.py +8 -3
  28. langgraph_api/schema.py +1 -1
  29. langgraph_api/stream.py +12 -5
  30. langgraph_api/utils.py +11 -5
  31. {langgraph_api-0.0.14.dist-info → langgraph_api-0.0.16.dist-info}/METADATA +3 -3
  32. {langgraph_api-0.0.14.dist-info → langgraph_api-0.0.16.dist-info}/RECORD +37 -30
  33. langgraph_storage/ops.py +9 -2
  34. openapi.json +5 -5
  35. {langgraph_api-0.0.14.dist-info → langgraph_api-0.0.16.dist-info}/LICENSE +0 -0
  36. {langgraph_api-0.0.14.dist-info → langgraph_api-0.0.16.dist-info}/WHEEL +0 -0
  37. {langgraph_api-0.0.14.dist-info → langgraph_api-0.0.16.dist-info}/entry_points.txt +0 -0
@@ -191,6 +191,7 @@ async def create_valid_run(
191
191
  user_id = get_user_id(user)
192
192
  config["configurable"]["langgraph_auth_user"] = user
193
193
  config["configurable"]["langgraph_auth_user_id"] = user_id
194
+ config["configurable"]["langgraph_auth_permissions"] = ctx.permissions
194
195
  else:
195
196
  user_id = None
196
197
  run_coro = Runs.put(
langgraph_api/queue.py CHANGED
@@ -1,33 +1,41 @@
1
1
  import asyncio
2
- from contextlib import AsyncExitStack
2
+ from collections.abc import AsyncGenerator
3
+ from contextlib import AsyncExitStack, asynccontextmanager
3
4
  from datetime import UTC, datetime
4
5
  from random import random
5
- from typing import cast
6
+ from typing import TypedDict, cast
6
7
 
7
8
  import structlog
8
9
  from langgraph.pregel.debug import CheckpointPayload, TaskResultPayload
9
10
 
11
+ from langgraph_api.auth.custom import normalize_user
10
12
  from langgraph_api.config import BG_JOB_NO_DELAY, STATS_INTERVAL_SECS
11
13
  from langgraph_api.errors import (
12
14
  UserInterrupt,
13
15
  UserRollback,
14
16
  )
15
17
  from langgraph_api.http import get_http_client
16
- from langgraph_api.js.remote import RemoteException
18
+ from langgraph_api.js.errors import RemoteException
17
19
  from langgraph_api.metadata import incr_runs
18
20
  from langgraph_api.schema import Run
19
21
  from langgraph_api.stream import (
20
22
  astream_state,
21
23
  consume,
22
24
  )
23
- from langgraph_api.utils import AsyncConnectionProto
25
+ from langgraph_api.utils import AsyncConnectionProto, set_auth_ctx, with_user
24
26
  from langgraph_storage.database import connect
25
27
  from langgraph_storage.ops import Runs, Threads
26
28
  from langgraph_storage.retry import RETRIABLE_EXCEPTIONS
27
29
 
30
+ try:
31
+ from psycopg.errors import InFailedSqlTransaction
32
+ except ImportError:
33
+ InFailedSqlTransaction = ()
34
+
28
35
  logger = structlog.stdlib.get_logger(__name__)
29
36
 
30
37
  WORKERS: set[asyncio.Task] = set()
38
+ WEBHOOKS: set[asyncio.Task] = set()
31
39
  MAX_RETRY_ATTEMPTS = 3
32
40
  SHUTDOWN_GRACE_PERIOD_SECS = 5
33
41
 
@@ -45,10 +53,46 @@ async def queue(concurrency: int, timeout: float):
45
53
  WORKERS.remove(task)
46
54
  semaphore.release()
47
55
  try:
48
- if exc := task.exception():
56
+ result: WorkerResult | None = task.result()
57
+ exc = task.exception()
58
+ if exc:
49
59
  logger.exception("Background worker failed", exc_info=exc)
60
+ if result and result["webhook"]:
61
+ checkpoint = result["checkpoint"]
62
+ payload = {
63
+ **result["run"],
64
+ "status": result["status"],
65
+ "run_started_at": result["run_started_at"],
66
+ "run_ended_at": result["run_ended_at"],
67
+ "webhook_sent_at": datetime.now(UTC).isoformat(),
68
+ "values": checkpoint["values"] if checkpoint else None,
69
+ }
70
+ if exception := result["exception"]:
71
+ payload["error"] = str(exception)
72
+
73
+ async def _call_webhook() -> None:
74
+ try:
75
+ await get_http_client().post(
76
+ result["webhook"], json=payload, total_timeout=20
77
+ )
78
+ except Exception as exc:
79
+ logger.exception(
80
+ f"Background worker failed to call webhook {result['webhook']}",
81
+ exc_info=exc,
82
+ webhook=result["webhook"],
83
+ )
84
+
85
+ hook_task = asyncio.create_task(
86
+ _call_webhook(),
87
+ name=f"webhook-{result['run']['run_id']}",
88
+ )
89
+ WEBHOOKS.add(hook_task)
90
+ hook_task.add_done_callback(WEBHOOKS.remove)
91
+
50
92
  except asyncio.CancelledError:
51
93
  pass
94
+ except Exception as exc:
95
+ logger.exception("Background worker cleanup failed", exc_info=exc)
52
96
 
53
97
  await logger.ainfo(f"Starting {concurrency} background workers")
54
98
  try:
@@ -73,9 +117,10 @@ async def queue(concurrency: int, timeout: float):
73
117
  stats = await Runs.stats(conn)
74
118
  await logger.ainfo("Queue stats", **stats)
75
119
  if tup := await exit.enter_async_context(Runs.next(conn)):
120
+ run_, attempt_ = tup
76
121
  task = asyncio.create_task(
77
- worker(timeout, exit, conn, *tup),
78
- name=f"run-{tup[0]['run_id']}-attempt-{tup[1]}",
122
+ worker(timeout, exit, conn, run_, attempt_),
123
+ name=f"run-{run_['run_id']}-attempt-{attempt_}",
79
124
  )
80
125
  task.add_done_callback(cleanup)
81
126
  WORKERS.add(task)
@@ -93,28 +138,61 @@ async def queue(concurrency: int, timeout: float):
93
138
  logger.info("Shutting down background workers")
94
139
  for task in WORKERS:
95
140
  task.cancel()
141
+ for task in WEBHOOKS:
142
+ task.cancel()
96
143
  await asyncio.wait_for(
97
- asyncio.gather(*WORKERS, return_exceptions=True), SHUTDOWN_GRACE_PERIOD_SECS
144
+ asyncio.gather(*WORKERS, *WEBHOOKS, return_exceptions=True),
145
+ SHUTDOWN_GRACE_PERIOD_SECS,
98
146
  )
99
147
 
100
148
 
149
+ class WorkerResult(TypedDict):
150
+ checkpoint: CheckpointPayload | None
151
+ status: str | None
152
+ exception: Exception | None
153
+ run: Run
154
+ webhook: str | None
155
+ run_started_at: str
156
+ run_ended_at: str | None
157
+
158
+
159
+ @asynccontextmanager
160
+ async def set_auth_ctx_for_run(run: Run) -> AsyncGenerator[None, None]:
161
+ try:
162
+ user = run["kwargs"]["config"]["configurable"]["langgraph_auth_user"]
163
+ permissions = run["kwargs"]["config"]["configurable"][
164
+ "langgraph_auth_permissions"
165
+ ]
166
+ if user is not None:
167
+ user = normalize_user(user)
168
+ async with with_user(user, permissions):
169
+ yield None
170
+ else:
171
+ yield None
172
+
173
+ except KeyError:
174
+ pass
175
+
176
+
101
177
  async def worker(
102
178
  timeout: float,
103
179
  exit: AsyncExitStack,
104
180
  conn: AsyncConnectionProto,
105
181
  run: Run,
106
182
  attempt: int,
107
- ):
183
+ ) -> WorkerResult:
108
184
  run_id = run["run_id"]
109
185
  if attempt == 1:
110
186
  incr_runs()
111
- async with Runs.enter(run_id) as done, exit:
187
+ checkpoint: CheckpointPayload | None = None
188
+ exception: Exception | None = None
189
+ status: str | None = None
190
+ webhook = run["kwargs"].pop("webhook", None)
191
+ run_started_at = datetime.now(UTC)
192
+ run_ended_at: str | None = None
193
+
194
+ async with set_auth_ctx_for_run(run), Runs.enter(run_id) as done, exit:
112
195
  temporary = run["kwargs"].get("temporary", False)
113
- webhook = run["kwargs"].pop("webhook", None)
114
- checkpoint: CheckpointPayload | None = None
115
- exception: Exception | None = None
116
- status: str | None = None
117
- run_started_at = datetime.now(UTC)
118
196
  run_created_at = run["created_at"].isoformat()
119
197
  await logger.ainfo(
120
198
  "Starting background run",
@@ -154,13 +232,14 @@ async def worker(
154
232
  on_task_result=on_task_result,
155
233
  )
156
234
  await asyncio.wait_for(consume(stream, run_id), timeout)
235
+ run_ended_at = datetime.now(UTC).isoformat()
157
236
  await logger.ainfo(
158
237
  "Background run succeeded",
159
238
  run_id=str(run_id),
160
239
  run_attempt=attempt,
161
240
  run_created_at=run_created_at,
162
241
  run_started_at=run_started_at.isoformat(),
163
- run_ended_at=datetime.now().isoformat(),
242
+ run_ended_at=run_ended_at,
164
243
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
165
244
  )
166
245
  status = "success"
@@ -168,45 +247,62 @@ async def worker(
168
247
  except TimeoutError as e:
169
248
  exception = e
170
249
  status = "timeout"
250
+ run_ended_at = datetime.now(UTC).isoformat()
171
251
  await logger.awarning(
172
252
  "Background run timed out",
173
253
  run_id=str(run_id),
174
254
  run_attempt=attempt,
175
255
  run_created_at=run_created_at,
176
256
  run_started_at=run_started_at.isoformat(),
177
- run_ended_at=datetime.now().isoformat(),
257
+ run_ended_at=run_ended_at,
178
258
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
179
259
  )
180
260
  await Runs.set_status(conn, run_id, "timeout")
181
261
  except UserRollback as e:
182
262
  exception = e
183
263
  status = "rollback"
264
+ run_ended_at = datetime.now(UTC).isoformat()
184
265
  await logger.ainfo(
185
266
  "Background run rolled back",
186
267
  run_id=str(run_id),
187
268
  run_attempt=attempt,
188
269
  run_created_at=run_created_at,
189
270
  run_started_at=run_started_at.isoformat(),
190
- run_ended_at=datetime.now().isoformat(),
271
+ run_ended_at=run_ended_at,
191
272
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
192
273
  )
193
- await Runs.delete(conn, run_id, thread_id=run["thread_id"])
274
+ try:
275
+ await Runs.delete(conn, run_id, thread_id=run["thread_id"])
276
+ except InFailedSqlTransaction as e:
277
+ await logger.ainfo(
278
+ "Ignoring rollback error",
279
+ run_id=str(run_id),
280
+ run_attempt=attempt,
281
+ run_created_at=run_created_at,
282
+ exc=str(e),
283
+ )
284
+ # We need to clean up the transaction early if we want to
285
+ # update the thread status with the same connection
286
+ await exit.aclose()
287
+ checkpoint = None # reset the checkpoint
194
288
  except UserInterrupt as e:
195
289
  exception = e
196
290
  status = "interrupted"
291
+ run_ended_at = datetime.now(UTC).isoformat()
197
292
  await logger.ainfo(
198
293
  "Background run interrupted",
199
294
  run_id=str(run_id),
200
295
  run_attempt=attempt,
201
296
  run_created_at=run_created_at,
202
297
  run_started_at=run_started_at.isoformat(),
203
- run_ended_at=datetime.now().isoformat(),
298
+ run_ended_at=run_ended_at,
204
299
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
205
300
  )
206
301
  await Runs.set_status(conn, run_id, "interrupted")
207
302
  except RETRIABLE_EXCEPTIONS as e:
208
303
  exception = e
209
304
  status = "retry"
305
+ run_ended_at = datetime.now(UTC).isoformat()
210
306
  await logger.awarning(
211
307
  "Background run failed, will retry",
212
308
  exc_info=True,
@@ -214,7 +310,7 @@ async def worker(
214
310
  run_attempt=attempt,
215
311
  run_created_at=run_created_at,
216
312
  run_started_at=run_started_at.isoformat(),
217
- run_ended_at=datetime.now().isoformat(),
313
+ run_ended_at=run_ended_at,
218
314
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
219
315
  )
220
316
  raise
@@ -223,6 +319,7 @@ async def worker(
223
319
  except Exception as exc:
224
320
  exception = exc
225
321
  status = "error"
322
+ run_ended_at = datetime.now(UTC).isoformat()
226
323
  await logger.aexception(
227
324
  "Background run failed",
228
325
  exc_info=not isinstance(exc, RemoteException),
@@ -230,24 +327,25 @@ async def worker(
230
327
  run_attempt=attempt,
231
328
  run_created_at=run_created_at,
232
329
  run_started_at=run_started_at.isoformat(),
233
- run_ended_at=datetime.now().isoformat(),
330
+ run_ended_at=run_ended_at,
234
331
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
235
332
  )
236
333
  await Runs.set_status(conn, run_id, "error")
334
+ set_auth_ctx(None, None)
237
335
  # delete or set status of thread
238
336
  if temporary:
239
337
  await Threads.delete(conn, run["thread_id"])
240
338
  else:
241
339
  await Threads.set_status(conn, run["thread_id"], checkpoint, exception)
242
- if webhook:
243
- # TODO add error, values to webhook payload
244
- # TODO add retries for webhook calls
245
- try:
246
- await get_http_client().post(
247
- webhook, json={**run, "status": status}, total_timeout=5
248
- )
249
- except Exception as e:
250
- logger.warning("Failed to send webhook", exc_info=e)
251
340
  # Note we don't handle asyncio.CancelledError here, as we want to
252
341
  # let it bubble up and rollback db transaction, thus marking the run
253
342
  # as available to be picked up by another worker
343
+ return {
344
+ "checkpoint": checkpoint,
345
+ "status": status,
346
+ "run_started_at": run_started_at.isoformat(),
347
+ "run_ended_at": run_ended_at,
348
+ "run": run,
349
+ "exception": exception,
350
+ "webhook": webhook,
351
+ }
langgraph_api/route.py CHANGED
@@ -14,7 +14,7 @@ from starlette.routing import Route, compile_path, get_name
14
14
  from starlette.types import ASGIApp, Receive, Scope, Send
15
15
 
16
16
  from langgraph_api.serde import json_dumpb
17
- from langgraph_api.utils import set_auth_ctx
17
+ from langgraph_api.utils import get_auth_ctx, with_user
18
18
 
19
19
 
20
20
  def api_request_response(
@@ -116,5 +116,10 @@ class ApiRoute(Route):
116
116
  async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:
117
117
  # https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope
118
118
  scope["route"] = self.path
119
- set_auth_ctx(scope.get("user"), scope.get("auth"))
120
- return await super().handle(scope, receive, send)
119
+ ctx = get_auth_ctx()
120
+ if ctx:
121
+ user, auth = ctx.user, ctx.permissions
122
+ else:
123
+ user, auth = scope.get("user"), scope.get("auth")
124
+ async with with_user(user, auth):
125
+ return await super().handle(scope, receive, send)
langgraph_api/schema.py CHANGED
@@ -157,7 +157,7 @@ class RunSend(TypedDict):
157
157
 
158
158
  class RunCommand(TypedDict):
159
159
  goto: str | RunSend | Sequence[RunSend | str] | None
160
- update: dict[str, Any] | None
160
+ update: dict[str, Any] | Sequence[tuple[str, Any]] | None
161
161
  resume: Any | None
162
162
 
163
163
 
langgraph_api/stream.py CHANGED
@@ -25,7 +25,7 @@ from pydantic.v1 import ValidationError as ValidationErrorLegacy
25
25
 
26
26
  from langgraph_api.asyncio import ValueEvent, wait_if_not_done
27
27
  from langgraph_api.graph import get_graph
28
- from langgraph_api.js.remote import RemotePregel
28
+ from langgraph_api.js.base import BaseRemotePregel
29
29
  from langgraph_api.metadata import HOST, PLAN, incr_nodes
30
30
  from langgraph_api.schema import Run, RunCommand, StreamMode
31
31
  from langgraph_api.serde import json_dumpb
@@ -75,8 +75,15 @@ def _map_cmd(cmd: RunCommand) -> Command:
75
75
  if goto is not None and not isinstance(goto, list):
76
76
  goto = [cmd.get("goto")]
77
77
 
78
+ update = cmd.get("update")
79
+ if isinstance(update, tuple | list) and all(
80
+ isinstance(t, tuple | list) and len(t) == 2 and isinstance(t[0], str)
81
+ for t in update
82
+ ):
83
+ update = [tuple(t) for t in update]
84
+
78
85
  return Command(
79
- update=cmd.get("update"),
86
+ update=update,
80
87
  goto=(
81
88
  [
82
89
  it if isinstance(it, str) else Send(it["node"], it["input"])
@@ -133,7 +140,7 @@ async def astream_state(
133
140
  config["metadata"]["langgraph_plan"] = PLAN
134
141
  config["metadata"]["langgraph_host"] = HOST
135
142
  # attach node counter
136
- if not isinstance(graph, RemotePregel):
143
+ if not isinstance(graph, BaseRemotePregel):
137
144
  config["configurable"]["__pregel_node_finished"] = incr_nodes
138
145
  # TODO add node tracking for JS graphs
139
146
  # attach run_id to config
@@ -142,7 +149,7 @@ async def astream_state(
142
149
  # set up state
143
150
  checkpoint: CheckpointPayload | None = None
144
151
  messages: dict[str, BaseMessageChunk] = {}
145
- use_astream_events = "events" in stream_mode or isinstance(graph, RemotePregel)
152
+ use_astream_events = "events" in stream_mode or isinstance(graph, BaseRemotePregel)
146
153
  # yield metadata chunk
147
154
  yield "metadata", {"run_id": run_id, "attempt": attempt}
148
155
  # stream run
@@ -166,7 +173,7 @@ async def astream_state(
166
173
  break
167
174
  if event.get("tags") and "langsmith:hidden" in event["tags"]:
168
175
  continue
169
- if "messages" in stream_mode and isinstance(graph, RemotePregel):
176
+ if "messages" in stream_mode and isinstance(graph, BaseRemotePregel):
170
177
  if event["event"] == "on_custom_event" and event["name"] in (
171
178
  "messages/complete",
172
179
  "messages/partial",
langgraph_api/utils.py CHANGED
@@ -17,22 +17,28 @@ AuthContext = contextvars.ContextVar[Auth.types.BaseAuthContext | None](
17
17
 
18
18
 
19
19
  @asynccontextmanager
20
- async def with_user(user: BaseUser | None = None, auth: AuthCredentials | None = None):
20
+ async def with_user(
21
+ user: BaseUser | None = None, auth: AuthCredentials | list[str] | None = None
22
+ ):
21
23
  current = get_auth_ctx()
22
24
  set_auth_ctx(user, auth)
23
25
  yield
24
26
  if current is None:
25
27
  return
26
- set_auth_ctx(current.user, AuthCredentials(scopes=current.scopes))
28
+ set_auth_ctx(current.user, AuthCredentials(scopes=current.permissions))
27
29
 
28
30
 
29
- def set_auth_ctx(user: BaseUser | None, auth: AuthCredentials | None) -> None:
30
- if not user or not auth:
31
+ def set_auth_ctx(
32
+ user: BaseUser | None, auth: AuthCredentials | list[str] | None
33
+ ) -> None:
34
+ if user is None and auth is None:
31
35
  AuthContext.set(None)
32
36
  else:
33
37
  AuthContext.set(
34
38
  Auth.types.BaseAuthContext(
35
- permissions=auth.scopes,
39
+ permissions=(
40
+ auth.scopes if isinstance(auth, AuthCredentials) else (auth or [])
41
+ ),
36
42
  user=user or SimpleUser(""),
37
43
  )
38
44
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-api
3
- Version: 0.0.14
3
+ Version: 0.0.16
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -16,11 +16,11 @@ Requires-Dist: jsonschema-rs (>=0.25.0,<0.26.0)
16
16
  Requires-Dist: langchain-core (>=0.2.38,<0.4.0)
17
17
  Requires-Dist: langgraph (>=0.2.56,<0.3.0)
18
18
  Requires-Dist: langgraph-checkpoint (>=2.0.7,<3.0)
19
- Requires-Dist: langgraph-sdk (>=0.1.48,<0.2.0)
19
+ Requires-Dist: langgraph-sdk (>=0.1.51,<0.2.0)
20
20
  Requires-Dist: langsmith (>=0.1.63,<0.3.0)
21
21
  Requires-Dist: orjson (>=3.10.1)
22
22
  Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
23
- Requires-Dist: sse-starlette (>=2.1.0,<3.0.0)
23
+ Requires-Dist: sse-starlette (>=2.1.0,<2.2.0)
24
24
  Requires-Dist: starlette (>=0.38.6)
25
25
  Requires-Dist: structlog (>=24.4.0,<25.0.0)
26
26
  Requires-Dist: tenacity (>=8.3.0,<9.0.0)
@@ -1,15 +1,15 @@
1
1
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
2
2
  langgraph_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- langgraph_api/api/__init__.py,sha256=tlMXuqnyJt99aSlUXwR-dS3w5X6sDDczJu4hbm2LP30,2057
4
- langgraph_api/api/assistants.py,sha256=Cxryr4K4qFeRoT0gQTu-gld_jSPzMDRZRHoazlSBZVo,11585
3
+ langgraph_api/api/__init__.py,sha256=zAdS_0jgjmCJK6E0VwEuNcNRkaknMXfQ2C_OES5tEI4,2066
4
+ langgraph_api/api/assistants.py,sha256=nn-0Q5FTaEbdPq-oesrpVzqu223PDSzeejFy9fd5Xjw,11599
5
5
  langgraph_api/api/meta.py,sha256=hueasWpTDQ6xYLo9Bzt2jhNH8XQRzreH8FTeFfnRoxQ,2700
6
6
  langgraph_api/api/openapi.py,sha256=AUxfnD5hlRp7s-0g2hBC5dNSNk3HTwOLeJiF489DT44,2762
7
7
  langgraph_api/api/runs.py,sha256=wAzPXi_kcYB9BcLBL4FXgkBohWwCPIpe4XERnsnWnsA,16042
8
- langgraph_api/api/store.py,sha256=y7VIejpsE7rpPF-tiMGBqqBwWPZ1wb3o48th6NUvb5I,3849
8
+ langgraph_api/api/store.py,sha256=VzAJVOwO0IxosBB7km5TTf2rhlWGyPkVz_LpvbxetVY,5437
9
9
  langgraph_api/api/threads.py,sha256=taU61XPcCEhBPCYPZcMDsgVDwwWUWJs8p-PrXFXWY48,8661
10
- langgraph_api/asyncio.py,sha256=XiFEllu-Kg4zAO084npHPYOPnLQRire3V75XrVQYMxE,6023
10
+ langgraph_api/asyncio.py,sha256=2fOlx-cZvuj1gQ867Kw1R_wsBsl9jdHYHcUtK2a-x-U,6264
11
11
  langgraph_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- langgraph_api/auth/custom.py,sha256=_BlII18X8ji_t8qA9FIUq_ip99dMQGkNscjsuEiLHus,20527
12
+ langgraph_api/auth/custom.py,sha256=g_u7FdKm1Qj8eu_MZdnJeMsI4DlWyU5Mg0rPJzdOTSE,20913
13
13
  langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  langgraph_api/auth/langsmith/backend.py,sha256=InScaL-HYCnxYEauhxU198gRZV9pJn9SzzBoR9Edn7g,2654
15
15
  langgraph_api/auth/langsmith/client.py,sha256=eKchvAom7hdkUXauD8vHNceBDDUijrFgdTV8bKd7x4Q,3998
@@ -17,18 +17,24 @@ langgraph_api/auth/middleware.py,sha256=qc7SbaFoeWaqxS1wbjZ2PPQ4iI2p9T0shWL7c6g0
17
17
  langgraph_api/auth/noop.py,sha256=vDJmzG2vArJxVzdHePvrJWahEa0dvGnhc2LEMMeiFz0,391
18
18
  langgraph_api/auth/studio_user.py,sha256=FzFQRROKDlA9JjtBuwyZvk6Mbwno5M9RVYjDO6FU3F8,186
19
19
  langgraph_api/cli.py,sha256=7vQQiD3F50r-8KkbuFjwIz8LLbdKUTd4xZGUJPiO3yQ,11688
20
- langgraph_api/config.py,sha256=JueNW95UDn7uId3atctyC9BPZTzmUqwF2Gtr2i-MZ-g,2739
20
+ langgraph_api/config.py,sha256=HPNSCb-owhu_sU8KBLbTbKLdGIPGVLcSgdvKFKXg7uE,2816
21
21
  langgraph_api/cron_scheduler.py,sha256=CybK-9Jwopi_scObTHRyB7gyb0KjC4gqaT2GLe-WOFg,2587
22
22
  langgraph_api/errors.py,sha256=Bu_i5drgNTyJcLiyrwVE_6-XrSU50BHf9TDpttki9wQ,1690
23
- langgraph_api/graph.py,sha256=zjcjgtlvfPIEJws8RksC6wKvt3g-8IGaJj79ScSs6GE,16561
23
+ langgraph_api/graph.py,sha256=FombjYQkqj8jrXJFEVkl3m2UyFcq5nSVNswR2HoRsQY,16385
24
24
  langgraph_api/http.py,sha256=XrbyxpjtfSvnaWWh5ZLGpgZmY83WoDCrP_1GPguNiXI,4712
25
25
  langgraph_api/http_logger.py,sha256=Sxo_q-65tElauRvkzVLt9lJojgNdgtcHGBYD0IRyX7M,3146
26
26
  langgraph_api/js/.gitignore,sha256=qAah3Fq0HWAlfRj5ktZyC6QRQIsAolGLRGcRukA1XJI,33
27
- langgraph_api/js/build.mts,sha256=v4ZJFnfBJBuLn8g0q-Uab9sgNtcssXcFEI-CmMoiOBc,1301
28
- langgraph_api/js/client.mts,sha256=GF34EYtjIKvl9u44PeWhWD_QCbTbrxFQ-BL-7v6P23c,24434
27
+ langgraph_api/js/base.py,sha256=BpE8-xkUp8HFPRjSKx1tfUQubvoV4jYl6OwZdre3veI,209
28
+ langgraph_api/js/build.mts,sha256=sAZXB3xVSoG8exJ1ZEytFyikRmEsXJ_0OlLjDDgD79o,1342
29
+ langgraph_api/js/client.mts,sha256=ksiytm222KTUWj92ZnajqFku_y2AkRmfENmKie5LSPw,22519
30
+ langgraph_api/js/client.new.mts,sha256=9FrArkM20IeD176Q7u5lJNruaQXqAfAdDcqJkF0TPPA,23493
31
+ langgraph_api/js/errors.py,sha256=Cm1TKWlUCwZReDC5AQ6SgNIVGD27Qov2xcgHyf8-GXo,361
29
32
  langgraph_api/js/global.d.ts,sha256=zR_zLYfpzyPfxpEFth5RgZoyfGulIXyZYPRf7cU0K0Y,106
30
- langgraph_api/js/package.json,sha256=nxZ3fmuO4ZiKli087PxrK8p95ZrQKaubQa2wDKRnwsk,791
31
- langgraph_api/js/remote.py,sha256=rudGN-8kHVuEbnGeDMEZbn__edtqkjxXlrxIuO1xOo0,23323
33
+ langgraph_api/js/package.json,sha256=BGnWhiMMvxocEuPciTq14fEHp5NFmHo6fYV8q62n3t4,840
34
+ langgraph_api/js/remote.py,sha256=D9cqcEgXau-fm_trpNwCHMra5BXntgUa469lgs_a9JQ,622
35
+ langgraph_api/js/remote_new.py,sha256=T_Vr8459bax1C9xxqz_ZYmGivq5Vhspg2Iu9TL0Qc-Q,22707
36
+ langgraph_api/js/remote_old.py,sha256=2a-3ooAYUZs8aPsfnXafbBd4pP7lRmokoU7TiO7P9Js,22546
37
+ langgraph_api/js/schema.py,sha256=7idnv7URlYUdSNMBXQcw7E4SxaPxCq_Oxwnlml8q5ik,408
32
38
  langgraph_api/js/server_sent_events.py,sha256=DLgXOHauemt7706vnfDUCG1GI3TidKycSizccdz9KgA,3702
33
39
  langgraph_api/js/src/graph.mts,sha256=EO1ITYoKiUykzO_8V8mnQb6NYybooR1VXIovThZzywc,2998
34
40
  langgraph_api/js/src/hooks.mjs,sha256=XtktgmIHlls_DsknAuwib-z7TqCm0haRoTXvnkgzMuo,601
@@ -38,35 +44,36 @@ langgraph_api/js/src/schema/types.mts,sha256=SUj0vpvWVbID1mnGr2SUtumDBQkJ9zjfvJd
38
44
  langgraph_api/js/src/schema/types.template.mts,sha256=c-FA0Ykzp4KvPyYA6a-hDf60KdQRMyFEjmGVJyD2OPM,2011
39
45
  langgraph_api/js/src/utils/importMap.mts,sha256=pX4TGOyUpuuWF82kXcxcv3-8mgusRezOGe6Uklm2O5A,1644
40
46
  langgraph_api/js/src/utils/pythonSchemas.mts,sha256=98IW7Z_VP7L_CHNRMb3_MsiV3BgLE2JsWQY_PQcRR3o,685
41
- langgraph_api/js/src/utils/serde.mts,sha256=5SO-wYPnPa8f-D6HQX5Oy3NX3nuziZM8vQMqc2tuxbk,531
42
- langgraph_api/js/tests/api.test.mts,sha256=ZfwWmu6RgxMBELwR5PUpGOdVFkeDWlONPx-f_huSRok,52248
43
- langgraph_api/js/tests/compose-postgres.yml,sha256=7VCpWUgPjCzybf6Gu6pT788sZsxUnTjhbmji6c33cVc,1639
47
+ langgraph_api/js/src/utils/serde.mts,sha256=OuyyO9btvwWd55rU_H4x91dFEJiaPxL-lL9O6Zgo908,742
48
+ langgraph_api/js/tests/api.test.mts,sha256=2EpDEs888pJGdZnsyc76GdMp7uuRxM_SNlHBwITU-5I,55668
49
+ langgraph_api/js/tests/compose-postgres.yml,sha256=pbNfeqVUqhWILBuUdwAgQOYsVU_fgkCVm0YlTgU8og8,1721
44
50
  langgraph_api/js/tests/graphs/.gitignore,sha256=26J8MarZNXh7snXD5eTpV3CPFTht5Znv8dtHYCLNfkw,12
45
- langgraph_api/js/tests/graphs/agent.mts,sha256=i2s0GOnydW88laDGBatYkQnjUe9Q44RNHDhdEGIcT8w,3811
51
+ langgraph_api/js/tests/graphs/agent.mts,sha256=fFHm9vW04UN_2mGcHVHqtFIvPhjyFurBg62DAW-GJd4,3889
52
+ langgraph_api/js/tests/graphs/delay.mts,sha256=CFneKxqI4bGGK0lYjSbe80QirowPQlsRSuhDUKfydhk,703
46
53
  langgraph_api/js/tests/graphs/error.mts,sha256=l4tk89449dj1BnEF_0ZcfPt0Ikk1gl8L1RaSnRfr3xo,487
47
- langgraph_api/js/tests/graphs/langgraph.json,sha256=-wSU_9H2fraq7Tijq_dTuK8SBDYLHsnfrYgi2zYUK2o,155
54
+ langgraph_api/js/tests/graphs/langgraph.json,sha256=frxd7ZWILdeMYSZgUBH6UO-IR7I2YJSOfOlx2mnO1sI,189
48
55
  langgraph_api/js/tests/graphs/nested.mts,sha256=4G7jSOSaFVQAza-_ARbK-Iai1biLlF2DIPDZXf7PLIY,1245
49
56
  langgraph_api/js/tests/graphs/package.json,sha256=kG5a7jRCnlSQMV2WXUsXegmp5cCWm9F3AOIBUUqPn8A,118
50
57
  langgraph_api/js/tests/graphs/weather.mts,sha256=A7mLK3xW8h5B-ZyJNAyX2M2fJJwzPJzXs4DYesJwreQ,1655
51
58
  langgraph_api/js/tests/graphs/yarn.lock,sha256=q-1S--E5VWLYtkSv03shqtNzeDDv-N_J-N26FszLsjs,7903
52
59
  langgraph_api/js/tests/parser.test.mts,sha256=3zAbboUNhI-cY3hj4Ssr7J-sQXCBTeeI1ItrkG0Ftuk,26257
53
60
  langgraph_api/js/tests/utils.mts,sha256=2kTybJ3O7Yfe1q3ehDouqV54ibXkNzsPZ_wBZLJvY-4,421
54
- langgraph_api/js/yarn.lock,sha256=roj7_0fuKEyeDgQqHIx6CDLjfX73-OsQz342QQlQo_E,65156
61
+ langgraph_api/js/yarn.lock,sha256=JtRgt5AXlsD4qBm1Nrtzxg-TLtZkFWtRKCvyS-V8CLg,103690
55
62
  langgraph_api/lifespan.py,sha256=Uj7NV-NqxxD1fgx_umM9pVqclcy-VlqrIxDljyj2he0,1820
56
63
  langgraph_api/logging.py,sha256=tiDNrEFwqaIdL5ywZv908OXlzzfXsPCws9GXeoFtBV8,3367
57
64
  langgraph_api/metadata.py,sha256=mih2G7ScQxiqyUlbksVXkqR3Oo-pM1b6lXtzOsgR1sw,3044
58
65
  langgraph_api/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- langgraph_api/models/run.py,sha256=qGTcgQjnzmXrxer5VPaGHoGwifV1nyE4ZCXtBiZH1bc,9546
66
+ langgraph_api/models/run.py,sha256=qBN_w8mUpSbAZNVL1683_DfZHQ0zvYMNyqGg-g1sz6Y,9625
60
67
  langgraph_api/patch.py,sha256=94ddcTSZJe22JcpjxiSNjFZdYVnmeoWjk4IX4iBSoyk,1249
61
- langgraph_api/queue.py,sha256=7tsbDgv4GlUYieJsrvIJDMQUEok4Eu-n_PIQ93rwKjk,9810
62
- langgraph_api/route.py,sha256=HnAcWeStCrWNe37YUXIqEsjsrCPCIPGijHG7pM9ga28,4251
63
- langgraph_api/schema.py,sha256=EiCWRR2GmGrBrOYcuK9SeVQS5b98SdaJlKaqOL7t-WQ,5263
68
+ langgraph_api/queue.py,sha256=nYtcjbqjwvELK5OXxD2aw5BWAlSJ-VPyCXSODMMXIj0,13353
69
+ langgraph_api/route.py,sha256=fM4qYCGbmH0a3_cV8uKocb1sLklehxO6HhdRXqLK6OM,4421
70
+ langgraph_api/schema.py,sha256=4aZCFY-dxd_nTot71bdcd9S8QCIgKajuRyj0p2QfgJ4,5291
64
71
  langgraph_api/serde.py,sha256=VoJ7Z1IuqrQGXFzEP1qijAITtWCrmjtVqlCRuScjXJI,3533
65
72
  langgraph_api/server.py,sha256=afHDnL6b_fAIu_q4icnK60a74lHTTZOMIe1egdhRXIk,1522
66
73
  langgraph_api/sse.py,sha256=2wNodCOP2eg7a9mpSu0S3FQ0CHk2BBV_vv0UtIgJIcc,4034
67
74
  langgraph_api/state.py,sha256=8jx4IoTCOjTJuwzuXJKKFwo1VseHjNnw_CCq4x1SW14,2284
68
- langgraph_api/stream.py,sha256=uK1MFr3hp08o3yE-W5V36CljaPmo97VfpDtexa5eqOQ,11663
69
- langgraph_api/utils.py,sha256=o7TFlY25IjujeKdXgtyE2mMLPETIlrbOc3w6giYBq2Y,2509
75
+ langgraph_api/stream.py,sha256=Uygx6zcY5Wi9lBhRjtxqVDyLZSF1bsaqxg6mYoYVYcY,11900
76
+ langgraph_api/utils.py,sha256=fMl3DHOQEwAqkFtrnP0Alfbrqw1UvwZ_JVLm-WTSQJk,2654
70
77
  langgraph_api/validation.py,sha256=McizHlz-Ez8Jhdbc79mbPSde7GIuf2Jlbjx2yv_l6dA,4475
71
78
  langgraph_license/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
79
  langgraph_license/middleware.py,sha256=_ODIYzQkymr6W9_Fp9wtf1kAQspnpsmr53xuzyF2GA0,612
@@ -74,15 +81,15 @@ langgraph_license/validation.py,sha256=Uu_G8UGO_WTlLsBEY0gTVWjRR4czYGfw5YAD3HLZo
74
81
  langgraph_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
82
  langgraph_storage/checkpoint.py,sha256=V4t2GwYEJdPCHbhq_4Udhlv0TWKDzlMu_rlNPdTDc50,3589
76
83
  langgraph_storage/database.py,sha256=Nr5zE9Fur3-tESkqe7xNXMf2QlBuw3H0CUie7jVa6Q4,6003
77
- langgraph_storage/ops.py,sha256=l6sL3wq9vp2OJwhRbdNkPozi-bi2ma-yXHBNGRbmQzk,67985
84
+ langgraph_storage/ops.py,sha256=7kmfm7EO7YbP_ItEjMvFPKPsM0a2X6RMhjaKof63HaQ,68206
78
85
  langgraph_storage/queue.py,sha256=6cTZ0ubHu3S1T43yxHMVOwsQsDaJupByiU0sTUFFls8,3261
79
86
  langgraph_storage/retry.py,sha256=uvYFuXJ-T6S1QY1ZwkZHyZQbsvS-Ab68LSbzbUUSI2E,696
80
87
  langgraph_storage/store.py,sha256=D-p3cWc_umamkKp-6Cz3cAriSACpvM5nxUIvND6PuxE,2710
81
88
  langgraph_storage/ttl_dict.py,sha256=FlpEY8EANeXWKo_G5nmIotPquABZGyIJyk6HD9u6vqY,1533
82
89
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
83
- openapi.json,sha256=gh6FxpyQqspAuQQH3O22qqGW5owtFj45gyR15QAcS9k,124729
84
- langgraph_api-0.0.14.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
85
- langgraph_api-0.0.14.dist-info/METADATA,sha256=mmOzz3Y5VWe7rlFtMozGNTuOOmiWm5QFn_SCYH8OxAU,4041
86
- langgraph_api-0.0.14.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
87
- langgraph_api-0.0.14.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
88
- langgraph_api-0.0.14.dist-info/RECORD,,
90
+ openapi.json,sha256=qf2Rw3ieawlAcSJu4mGonh9mNOb6solBD70CGL3w24A,124699
91
+ langgraph_api-0.0.16.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
92
+ langgraph_api-0.0.16.dist-info/METADATA,sha256=HLMkdfTmxuTMN59uOzDQ4VCRILZvgslETZShl2Mdruw,4041
93
+ langgraph_api-0.0.16.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
94
+ langgraph_api-0.0.16.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
95
+ langgraph_api-0.0.16.dist-info/RECORD,,
langgraph_storage/ops.py CHANGED
@@ -1325,7 +1325,10 @@ class Runs(Authenticated):
1325
1325
  thread = Thread(
1326
1326
  thread_id=thread_id,
1327
1327
  status="busy",
1328
- metadata={"graph_id": assistant["graph_id"]},
1328
+ metadata={
1329
+ "graph_id": assistant["graph_id"],
1330
+ "assistant_id": str(assistant_id),
1331
+ },
1329
1332
  config=Runs._merge_jsonb(
1330
1333
  assistant["config"],
1331
1334
  config,
@@ -1345,7 +1348,11 @@ class Runs(Authenticated):
1345
1348
  if existing_thread["status"] != "busy":
1346
1349
  existing_thread["status"] = "busy"
1347
1350
  existing_thread["metadata"] = Runs._merge_jsonb(
1348
- existing_thread["metadata"], {"graph_id": assistant["graph_id"]}
1351
+ existing_thread["metadata"],
1352
+ {
1353
+ "graph_id": assistant["graph_id"],
1354
+ "assistant_id": str(assistant_id),
1355
+ },
1349
1356
  )
1350
1357
  existing_thread["config"] = Runs._merge_jsonb(
1351
1358
  assistant["config"],
openapi.json CHANGED
@@ -22,7 +22,7 @@
22
22
  "description": "A run is an invocation of a graph / assistant, with no state or memory persistence."
23
23
  },
24
24
  {
25
- "name": "Crons (Enterprise-only)",
25
+ "name": "Crons (Plus tier)",
26
26
  "description": "A cron is a periodic run that recurs on a given schedule. The repeats can be isolated, or share state in a thread"
27
27
  },
28
28
  {
@@ -1473,7 +1473,7 @@
1473
1473
  "/threads/{thread_id}/runs/crons": {
1474
1474
  "post": {
1475
1475
  "tags": [
1476
- "Crons (Enterprise-only)"
1476
+ "Crons (Plus tier)"
1477
1477
  ],
1478
1478
  "summary": "Create Thread Cron",
1479
1479
  "description": "Create a cron to schedule runs on a thread.",
@@ -2058,7 +2058,7 @@
2058
2058
  "/runs/crons": {
2059
2059
  "post": {
2060
2060
  "tags": [
2061
- "Crons (Enterprise-only)"
2061
+ "Crons (Plus tier)"
2062
2062
  ],
2063
2063
  "summary": "Create Cron",
2064
2064
  "description": "Create a cron to schedule runs on new threads.",
@@ -2110,7 +2110,7 @@
2110
2110
  "/runs/crons/search": {
2111
2111
  "post": {
2112
2112
  "tags": [
2113
- "Crons (Enterprise-only)"
2113
+ "Crons (Plus tier)"
2114
2114
  ],
2115
2115
  "summary": "Search Crons",
2116
2116
  "description": "Search all active crons",
@@ -2399,7 +2399,7 @@
2399
2399
  "/runs/crons/{cron_id}": {
2400
2400
  "delete": {
2401
2401
  "tags": [
2402
- "Crons (Enterprise-only)"
2402
+ "Crons (Plus tier)"
2403
2403
  ],
2404
2404
  "summary": "Delete Cron",
2405
2405
  "description": "Delete a cron by ID.",