langgraph-api 0.0.48__tar.gz → 0.1.2__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.

Potentially problematic release.


This version of langgraph-api might be problematic. Click here for more details.

Files changed (111) hide show
  1. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/PKG-INFO +1 -2
  2. langgraph_api-0.1.2/langgraph_api/__init__.py +1 -0
  3. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/__init__.py +2 -2
  4. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/assistants.py +3 -3
  5. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/meta.py +9 -11
  6. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/runs.py +3 -3
  7. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/store.py +2 -2
  8. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/threads.py +3 -3
  9. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/custom.py +25 -4
  10. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/cli.py +3 -1
  11. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/config.py +3 -0
  12. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/cron_scheduler.py +3 -3
  13. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/graph.py +6 -14
  14. langgraph_api-0.1.2/langgraph_api/js/base.py +29 -0
  15. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/build.mts +3 -3
  16. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/client.mts +64 -3
  17. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/global.d.ts +1 -0
  18. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/package.json +4 -3
  19. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/remote.py +96 -5
  20. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/graph.mts +0 -6
  21. langgraph_api-0.1.2/langgraph_api/js/src/utils/files.mts +4 -0
  22. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/api.test.mts +80 -80
  23. langgraph_api-0.1.2/langgraph_api/js/tests/auth.test.mts +648 -0
  24. langgraph_api-0.1.2/langgraph_api/js/tests/compose-postgres.auth.yml +59 -0
  25. langgraph_api-0.1.2/langgraph_api/js/tests/graphs/agent_simple.mts +79 -0
  26. langgraph_api-0.1.2/langgraph_api/js/tests/graphs/auth.mts +106 -0
  27. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/package.json +3 -1
  28. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/yarn.lock +9 -4
  29. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/yarn.lock +18 -23
  30. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/metadata.py +7 -0
  31. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/models/run.py +10 -1
  32. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/queue_entrypoint.py +1 -1
  33. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/server.py +2 -2
  34. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/stream.py +5 -4
  35. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/thread_ttl.py +2 -2
  36. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/worker.py +4 -25
  37. langgraph_api-0.1.2/langgraph_runtime/__init__.py +39 -0
  38. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/pyproject.toml +3 -3
  39. langgraph_api-0.0.48/langgraph_api/__init__.py +0 -1
  40. langgraph_api-0.0.48/langgraph_api/js/base.py +0 -12
  41. langgraph_api-0.0.48/langgraph_api/lifespan.py +0 -74
  42. langgraph_api-0.0.48/langgraph_storage/checkpoint.py +0 -123
  43. langgraph_api-0.0.48/langgraph_storage/database.py +0 -200
  44. langgraph_api-0.0.48/langgraph_storage/inmem_stream.py +0 -109
  45. langgraph_api-0.0.48/langgraph_storage/ops.py +0 -2172
  46. langgraph_api-0.0.48/langgraph_storage/queue.py +0 -186
  47. langgraph_api-0.0.48/langgraph_storage/retry.py +0 -31
  48. langgraph_api-0.0.48/langgraph_storage/store.py +0 -100
  49. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/LICENSE +0 -0
  50. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/README.md +0 -0
  51. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/mcp.py +0 -0
  52. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/openapi.py +0 -0
  53. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/ui.py +0 -0
  54. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/asyncio.py +0 -0
  55. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/__init__.py +0 -0
  56. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/langsmith/__init__.py +0 -0
  57. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/langsmith/backend.py +0 -0
  58. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/langsmith/client.py +0 -0
  59. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/middleware.py +0 -0
  60. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/noop.py +0 -0
  61. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/studio_user.py +0 -0
  62. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/command.py +0 -0
  63. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/errors.py +0 -0
  64. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/http.py +0 -0
  65. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/.gitignore +0 -0
  66. {langgraph_api-0.0.48/langgraph_api/middleware → langgraph_api-0.1.2/langgraph_api/js}/__init__.py +0 -0
  67. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/errors.py +0 -0
  68. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/schema.py +0 -0
  69. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/hooks.mjs +0 -0
  70. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/parser/parser.mts +0 -0
  71. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
  72. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/schema/types.mts +0 -0
  73. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/schema/types.template.mts +0 -0
  74. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/utils/importMap.mts +0 -0
  75. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  76. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/utils/serde.mts +0 -0
  77. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/sse.py +0 -0
  78. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/compose-postgres.yml +0 -0
  79. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/.gitignore +0 -0
  80. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/agent.css +0 -0
  81. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/agent.mts +0 -0
  82. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/agent.ui.tsx +0 -0
  83. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/command.mts +0 -0
  84. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/delay.mts +0 -0
  85. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/dynamic.mts +0 -0
  86. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/error.mts +0 -0
  87. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
  88. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/nested.mts +0 -0
  89. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/weather.mts +0 -0
  90. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/parser.test.mts +0 -0
  91. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/utils.mts +0 -0
  92. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/ui.py +0 -0
  93. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/logging.py +0 -0
  94. {langgraph_api-0.0.48/langgraph_api/models → langgraph_api-0.1.2/langgraph_api/middleware}/__init__.py +0 -0
  95. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/middleware/http_logger.py +0 -0
  96. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/middleware/private_network.py +0 -0
  97. {langgraph_api-0.0.48/langgraph_license → langgraph_api-0.1.2/langgraph_api/models}/__init__.py +0 -0
  98. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/patch.py +0 -0
  99. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/route.py +0 -0
  100. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/schema.py +0 -0
  101. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/serde.py +0 -0
  102. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/sse.py +0 -0
  103. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/state.py +0 -0
  104. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/utils.py +0 -0
  105. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/validation.py +0 -0
  106. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/webhook.py +0 -0
  107. {langgraph_api-0.0.48/langgraph_storage → langgraph_api-0.1.2/langgraph_license}/__init__.py +0 -0
  108. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_license/middleware.py +0 -0
  109. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_license/validation.py +0 -0
  110. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/logging.json +0 -0
  111. {langgraph_api-0.0.48 → langgraph_api-0.1.2}/openapi.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langgraph-api
3
- Version: 0.0.48
3
+ Version: 0.1.2
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -11,7 +11,6 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
- Requires-Dist: blockbuster (>=1.5.24,<2.0.0)
15
14
  Requires-Dist: cloudpickle (>=3.0.0,<4.0.0)
16
15
  Requires-Dist: cryptography (>=42.0.0,<45.0)
17
16
  Requires-Dist: httpx (>=0.25.0)
@@ -0,0 +1 @@
1
+ __version__ = "0.1.1"
@@ -21,7 +21,7 @@ from langgraph_api.auth.middleware import auth_middleware
21
21
  from langgraph_api.config import HTTP_CONFIG, MIGRATIONS_PATH
22
22
  from langgraph_api.graph import js_bg_tasks
23
23
  from langgraph_api.validation import DOCS_HTML
24
- from langgraph_storage.database import connect, healthcheck
24
+ from langgraph_runtime.database import connect, healthcheck
25
25
 
26
26
  logger = structlog.stdlib.get_logger(__name__)
27
27
 
@@ -138,7 +138,7 @@ else:
138
138
  if "inmem" in MIGRATIONS_PATH:
139
139
 
140
140
  async def truncate(request: Request):
141
- from langgraph_storage.checkpoint import Checkpointer
141
+ from langgraph_runtime.checkpoint import Checkpointer
142
142
 
143
143
  await asyncio.to_thread(Checkpointer().clear)
144
144
  async with connect() as conn:
@@ -21,9 +21,9 @@ from langgraph_api.validation import (
21
21
  AssistantVersionChange,
22
22
  AssistantVersionsSearchRequest,
23
23
  )
24
- from langgraph_storage.database import connect
25
- from langgraph_storage.ops import Assistants
26
- from langgraph_storage.retry import retry_db
24
+ from langgraph_runtime.database import connect
25
+ from langgraph_runtime.ops import Assistants
26
+ from langgraph_runtime.retry import retry_db
27
27
 
28
28
  logger = structlog.stdlib.get_logger(__name__)
29
29
 
@@ -5,9 +5,9 @@ from starlette.responses import JSONResponse, PlainTextResponse
5
5
  from langgraph_api import config
6
6
  from langgraph_api.route import ApiRequest
7
7
  from langgraph_license.validation import plus_features_enabled
8
- from langgraph_storage.database import connect, pool_stats
9
- from langgraph_storage.ops import Runs
10
- from langgraph_storage.queue import WORKERS
8
+ from langgraph_runtime.database import connect, pool_stats
9
+ from langgraph_runtime.metrics import get_metrics
10
+ from langgraph_runtime.ops import Runs
11
11
 
12
12
  METRICS_FORMATS = {"prometheus", "json"}
13
13
 
@@ -32,20 +32,18 @@ async def meta_metrics(request: ApiRequest):
32
32
  format = "prometheus"
33
33
 
34
34
  # collect stats
35
- workers_max = config.N_JOBS_PER_WORKER
36
- workers_active = len(WORKERS)
37
- workers_available = workers_max - workers_active
35
+ metrics = get_metrics()
36
+ worker_metrics = metrics["workers"]
37
+ workers_max = worker_metrics["max"]
38
+ workers_active = worker_metrics["active"]
39
+ workers_available = worker_metrics["available"]
38
40
 
39
41
  if format == "json":
40
42
  async with connect() as conn:
41
43
  return JSONResponse(
42
44
  {
43
45
  **pool_stats(),
44
- "workers": {
45
- "max": workers_max,
46
- "active": workers_active,
47
- "available": workers_available,
48
- },
46
+ "workers": worker_metrics,
49
47
  "queue": await Runs.stats(conn),
50
48
  }
51
49
  )
@@ -22,9 +22,9 @@ from langgraph_api.validation import (
22
22
  RunsCancel,
23
23
  )
24
24
  from langgraph_license.validation import plus_features_enabled
25
- from langgraph_storage.database import connect
26
- from langgraph_storage.ops import Crons, Runs, Threads
27
- from langgraph_storage.retry import retry_db
25
+ from langgraph_runtime.database import connect
26
+ from langgraph_runtime.ops import Crons, Runs, Threads
27
+ from langgraph_runtime.retry import retry_db
28
28
 
29
29
 
30
30
  @retry_db
@@ -13,8 +13,8 @@ from langgraph_api.validation import (
13
13
  StorePutRequest,
14
14
  StoreSearchRequest,
15
15
  )
16
- from langgraph_storage.retry import retry_db
17
- from langgraph_storage.store import Store
16
+ from langgraph_runtime.retry import retry_db
17
+ from langgraph_runtime.store import Store
18
18
 
19
19
 
20
20
  def _validate_namespace(namespace: tuple[str, ...]) -> Response | None:
@@ -15,9 +15,9 @@ from langgraph_api.validation import (
15
15
  ThreadStateSearch,
16
16
  ThreadStateUpdate,
17
17
  )
18
- from langgraph_storage.database import connect
19
- from langgraph_storage.ops import Threads
20
- from langgraph_storage.retry import retry_db
18
+ from langgraph_runtime.database import connect
19
+ from langgraph_runtime.ops import Threads
20
+ from langgraph_runtime.retry import retry_db
21
21
 
22
22
 
23
23
  @retry_db
@@ -7,7 +7,7 @@ import os
7
7
  import sys
8
8
  from collections.abc import Awaitable, Callable, Mapping
9
9
  from contextlib import AsyncExitStack
10
- from typing import Any, get_args
10
+ from typing import Any, Literal, get_args
11
11
 
12
12
  import structlog
13
13
  from langgraph_sdk import Auth
@@ -26,6 +26,7 @@ from starlette.responses import Response
26
26
  from langgraph_api.auth.langsmith.backend import LangsmithAuthBackend
27
27
  from langgraph_api.auth.studio_user import StudioUser
28
28
  from langgraph_api.config import LANGGRAPH_AUTH, LANGGRAPH_AUTH_TYPE
29
+ from langgraph_api.js.base import is_js_path
29
30
 
30
31
  logger = structlog.stdlib.get_logger(__name__)
31
32
 
@@ -56,7 +57,7 @@ def get_custom_auth_middleware() -> AuthenticationBackend:
56
57
 
57
58
 
58
59
  @functools.lru_cache(maxsize=1)
59
- def get_auth_instance() -> Auth | None:
60
+ def get_auth_instance() -> Auth | Literal["js"] | None:
60
61
  logger.info(
61
62
  f"Getting auth instance: {LANGGRAPH_AUTH}", langgraph_auth=str(LANGGRAPH_AUTH)
62
63
  )
@@ -89,6 +90,12 @@ async def handle_event(
89
90
  auth = get_auth_instance()
90
91
  if auth is None:
91
92
  return
93
+
94
+ if auth == "js":
95
+ from langgraph_api.js.remote import handle_js_auth_event
96
+
97
+ return await handle_js_auth_event(ctx, value)
98
+
92
99
  handler = _get_handler(auth, ctx)
93
100
  if not handler:
94
101
  return
@@ -195,6 +202,7 @@ def _get_custom_auth_middleware(
195
202
  else:
196
203
  path = config.get("path")
197
204
  disable_studio_auth = config.get("disable_studio_auth", disable_studio_auth)
205
+
198
206
  auth_instance = _get_auth_instance(path)
199
207
  if auth_instance is None:
200
208
  raise ValueError(
@@ -204,6 +212,12 @@ def _get_custom_auth_middleware(
204
212
  "from langgraph_sdk import Auth\n"
205
213
  "auth = Auth()"
206
214
  )
215
+
216
+ if auth_instance == "js":
217
+ from langgraph_api.js.remote import CustomJsAuthBackend
218
+
219
+ return CustomJsAuthBackend(disable_studio_auth=disable_studio_auth)
220
+
207
221
  if auth_instance._authenticate_handler is None:
208
222
  raise ValueError(
209
223
  f"Custom Auth object at path: {path} does not have an authenticate handler."
@@ -214,6 +228,7 @@ def _get_custom_auth_middleware(
214
228
  "async def authenticate(request):\n"
215
229
  ' return "my-user-id"'
216
230
  )
231
+
217
232
  result = CustomAuthBackend(
218
233
  auth_instance._authenticate_handler,
219
234
  disable_studio_auth,
@@ -223,12 +238,15 @@ def _get_custom_auth_middleware(
223
238
 
224
239
 
225
240
  @functools.lru_cache(maxsize=1)
226
- def _get_auth_instance(path: str | None = None) -> Auth | None:
241
+ def _get_auth_instance(path: str | None = None) -> Auth | Literal["js"] | None:
227
242
  if path is not None:
228
243
  auth_instance = _load_auth_obj(path)
229
244
  else:
230
245
  auth_instance = None
231
246
 
247
+ if auth_instance == "js":
248
+ return auth_instance
249
+
232
250
  if auth_instance is not None and (
233
251
  deps := _get_dependencies(auth_instance._authenticate_handler)
234
252
  ):
@@ -544,7 +562,7 @@ def normalize_user(user: Any) -> BaseUser:
544
562
  )
545
563
 
546
564
 
547
- def _load_auth_obj(path: str) -> Auth:
565
+ def _load_auth_obj(path: str) -> Auth | Literal["js"]:
548
566
  """Load an object from a path string."""
549
567
  if ":" not in path:
550
568
  raise ValueError(
@@ -555,6 +573,9 @@ def _load_auth_obj(path: str) -> Auth:
555
573
  module_name, callable_name = path.rsplit(":", 1)
556
574
  module_name = module_name.rstrip(":")
557
575
 
576
+ if is_js_path(module_name):
577
+ return "js"
578
+
558
579
  try:
559
580
  if "/" in module_name or ".py" in module_name:
560
581
  # Load from file path
@@ -6,6 +6,7 @@ import pathlib
6
6
  import threading
7
7
  import typing
8
8
  from collections.abc import Mapping, Sequence
9
+ from typing import Literal
9
10
 
10
11
  from typing_extensions import TypedDict
11
12
 
@@ -134,6 +135,7 @@ def run_server(
134
135
  studio_url: str | None = None,
135
136
  disable_persistence: bool = False,
136
137
  allow_blocking: bool = False,
138
+ runtime_edition: Literal["inmem", "community", "postgres"] = "inmem",
137
139
  **kwargs: typing.Any,
138
140
  ):
139
141
  """Run the LangGraph API server."""
@@ -197,6 +199,7 @@ def run_server(
197
199
  LANGGRAPH_UI_BUNDLER="true",
198
200
  LANGGRAPH_API_URL=local_url,
199
201
  LANGGRAPH_DISABLE_FILE_PERSISTENCE=str(disable_persistence).lower(),
202
+ LANGGRAPH_RUNTIME_EDITION=runtime_edition,
200
203
  # If true, we will not raise on blocking IO calls (via blockbuster)
201
204
  LANGGRAPH_ALLOW_BLOCKING=str(allow_blocking).lower(),
202
205
  # See https://developer.chrome.com/blog/private-network-access-update-2024-03
@@ -274,7 +277,6 @@ For production use, please use LangGraph Cloud.
274
277
 
275
278
  """
276
279
  logger.info(welcome)
277
-
278
280
  if open_browser:
279
281
  threading.Thread(target=_open_browser, daemon=True).start()
280
282
  supported_kwargs = {
@@ -323,6 +323,9 @@ USES_INDEXING = (
323
323
  and STORE_CONFIG.get("index").get("embed")
324
324
  )
325
325
  USES_CUSTOM_APP = HTTP_CONFIG and HTTP_CONFIG.get("app")
326
+ USES_CUSTOM_AUTH = bool(LANGGRAPH_AUTH)
327
+ USES_THREAD_TTL = bool(THREAD_TTL)
328
+ USES_STORE_TTL = bool(STORE_CONFIG and STORE_CONFIG.get("ttl"))
326
329
 
327
330
  API_VARIANT = env("LANGSMITH_LANGGRAPH_API_VARIANT", cast=str, default="")
328
331
 
@@ -7,9 +7,9 @@ from langchain_core.runnables.config import run_in_executor
7
7
  from langgraph_api.models.run import create_valid_run
8
8
  from langgraph_api.utils import next_cron_date
9
9
  from langgraph_api.worker import set_auth_ctx_for_run
10
- from langgraph_storage.database import connect
11
- from langgraph_storage.ops import Crons
12
- from langgraph_storage.retry import retry_db
10
+ from langgraph_runtime.database import connect
11
+ from langgraph_runtime.ops import Crons
12
+ from langgraph_runtime.retry import retry_db
13
13
 
14
14
  logger = structlog.stdlib.get_logger(__name__)
15
15
 
@@ -24,7 +24,7 @@ from starlette.exceptions import HTTPException
24
24
 
25
25
  from langgraph_api import asyncio as lg_asyncio
26
26
  from langgraph_api import config
27
- from langgraph_api.js.base import BaseRemotePregel
27
+ from langgraph_api.js.base import BaseRemotePregel, is_js_path
28
28
  from langgraph_api.schema import Config
29
29
 
30
30
  if TYPE_CHECKING:
@@ -50,8 +50,8 @@ async def register_graph(
50
50
  description: str | None = None,
51
51
  ) -> None:
52
52
  """Register a graph."""
53
- from langgraph_storage.database import connect
54
- from langgraph_storage.ops import Assistants
53
+ from langgraph_runtime.database import connect
54
+ from langgraph_runtime.ops import Assistants
55
55
 
56
56
  await logger.ainfo(f"Registering graph with id '{graph_id}'", graph_id=graph_id)
57
57
  GRAPHS[graph_id] = graph
@@ -181,17 +181,6 @@ class GraphSpec(NamedTuple):
181
181
  js_bg_tasks: set[asyncio.Task] = set()
182
182
 
183
183
 
184
- def is_js_spec(spec: GraphSpec) -> bool:
185
- return spec.path is not None and os.path.splitext(spec.path)[1] in (
186
- ".ts",
187
- ".mts",
188
- ".cts",
189
- ".js",
190
- ".mjs",
191
- ".cjs",
192
- )
193
-
194
-
195
184
  def _load_graph_config_from_env() -> dict | None:
196
185
  """Return graph config from env."""
197
186
  config_str = os.getenv("LANGGRAPH_CONFIG")
@@ -283,6 +272,9 @@ async def collect_graphs_from_env(register: bool = False) -> None:
283
272
  for graph_path in glob.glob("/graphs/*.py")
284
273
  ]
285
274
 
275
+ def is_js_spec(x: GraphSpec) -> bool:
276
+ return is_js_path(x.path)
277
+
286
278
  js_specs = list(filter(is_js_spec, specs))
287
279
  py_specs = list(filterfalse(is_js_spec, specs))
288
280
 
@@ -0,0 +1,29 @@
1
+ import os
2
+
3
+ from langchain_core.runnables import Runnable
4
+
5
+ from langgraph_api.schema import Config
6
+
7
+ JS_EXTENSIONS = (
8
+ ".ts",
9
+ ".mts",
10
+ ".cts",
11
+ ".js",
12
+ ".mjs",
13
+ ".cjs",
14
+ )
15
+
16
+
17
+ def is_js_path(path: str | None) -> bool:
18
+ if path is None:
19
+ return False
20
+ return os.path.splitext(path)[1] in JS_EXTENSIONS
21
+
22
+
23
+ class BaseRemotePregel(Runnable):
24
+ name: str = "LangGraph"
25
+
26
+ graph_id: str
27
+
28
+ # Config passed from get_graph()
29
+ config: Config
@@ -4,19 +4,19 @@ import { z } from "zod";
4
4
  import * as fs from "node:fs/promises";
5
5
  import * as path from "node:path";
6
6
  import {
7
- filterValidGraphSpecs,
8
7
  GraphSchema,
9
8
  resolveGraph,
10
9
  runGraphSchemaWorker,
11
10
  } from "./src/graph.mts";
12
11
  import { build } from "@langchain/langgraph-ui";
12
+ import { filterValidExportPath } from "./src/utils/files.mts";
13
13
 
14
14
  const __dirname = new URL(".", import.meta.url).pathname;
15
15
 
16
16
  async function main() {
17
- const specs = filterValidGraphSpecs(
17
+ const specs = Object.entries(
18
18
  z.record(z.string()).parse(JSON.parse(process.env.LANGSERVE_GRAPHS))
19
- );
19
+ ).filter(([_, spec]) => filterValidExportPath(spec));
20
20
 
21
21
  const GRAPH_SCHEMAS: Record<string, Record<string, GraphSchema> | false> = {};
22
22
  let failed = false;
@@ -42,11 +42,17 @@ import {
42
42
  GraphSchema,
43
43
  resolveGraph,
44
44
  GraphSpec,
45
- filterValidGraphSpecs,
46
45
  type CompiledGraphFactory,
47
46
  } from "./src/graph.mts";
48
47
  import { asyncExitHook, gracefulExit } from "exit-hook";
49
48
  import { awaitAllCallbacks } from "@langchain/core/callbacks/promises";
49
+ import { StatusCode } from "hono/utils/http-status";
50
+ import {
51
+ authenticate,
52
+ authorize,
53
+ registerAuth,
54
+ } from "@langchain/langgraph-api/auth";
55
+ import { filterValidExportPath } from "./src/utils/files.mts";
50
56
 
51
57
  const logger = createLogger({
52
58
  level: "debug",
@@ -872,9 +878,9 @@ async function main() {
872
878
  store: new RemoteStore(),
873
879
  };
874
880
 
875
- const specs = filterValidGraphSpecs(
881
+ const specs = Object.entries(
876
882
  z.record(z.string()).parse(JSON.parse(process.env.LANGSERVE_GRAPHS ?? "{}"))
877
- );
883
+ ).filter(([_, spec]) => filterValidExportPath(spec));
878
884
 
879
885
  if (!process.argv.includes("--skip-schema-cache")) {
880
886
  try {
@@ -944,6 +950,61 @@ async function main() {
944
950
  )
945
951
  );
946
952
 
953
+ // Load LANGGRAPH_AUTH
954
+ const auth = z
955
+ .object({
956
+ path: z.string().optional(),
957
+ disable_studio_auth: z.boolean().optional(),
958
+ })
959
+ .parse(JSON.parse(process.env.LANGGRAPH_AUTH ?? "{}"));
960
+
961
+ if (filterValidExportPath(auth.path)) {
962
+ await registerAuth(auth, { cwd: process.cwd() });
963
+
964
+ app.post("/auth/authenticate", async (c) => {
965
+ try {
966
+ const rawHeaders = c.req.raw.headers;
967
+ const authUrl = rawHeaders.get("x-langgraph-auth-url") as string;
968
+ const method = rawHeaders.get("x-langgraph-auth-method") as string;
969
+
970
+ const headers = new Headers(rawHeaders);
971
+ headers.delete("x-langgraph-auth-url");
972
+ headers.delete("x-langgraph-auth-method");
973
+
974
+ const context = await authenticate(
975
+ new Request(authUrl, { headers, method })
976
+ );
977
+
978
+ return c.json(context);
979
+ } catch (error) {
980
+ if (error instanceof HTTPException) {
981
+ return c.json(
982
+ {
983
+ ...serializeError(error),
984
+ status: error.res?.status ?? error.status,
985
+ headers: error.res?.headers,
986
+ },
987
+ error.status as StatusCode
988
+ );
989
+ }
990
+
991
+ return c.json(serializeError(error), 403);
992
+ }
993
+ });
994
+
995
+ app.post("/auth/authorize", async (c) => {
996
+ try {
997
+ return c.json(await authorize(await c.req.json()));
998
+ } catch (error) {
999
+ if (error instanceof HTTPException) {
1000
+ return c.json(serializeError(error), error.status);
1001
+ }
1002
+
1003
+ return c.json(serializeError(error), 500);
1004
+ }
1005
+ });
1006
+ }
1007
+
947
1008
  app.get("/ok", (c) => c.json({ ok: true }));
948
1009
 
949
1010
  app.onError((err, c) => {
@@ -3,6 +3,7 @@ declare namespace NodeJS {
3
3
  LANGSERVE_GRAPHS: string;
4
4
  LANGGRAPH_UI?: string;
5
5
  LANGGRAPH_UI_CONFIG?: string;
6
+ LANGGRAPH_AUTH?: string;
6
7
  PORT: string;
7
8
  }
8
9
  }
@@ -24,15 +24,16 @@
24
24
  "undici": "^6.21.1",
25
25
  "uuid": "^10.0.0",
26
26
  "winston": "^3.17.0",
27
- "@langchain/langgraph-api": "~0.0.20",
28
- "@langchain/langgraph-ui": "~0.0.20",
27
+ "@langchain/langgraph-api": "~0.0.21",
28
+ "@langchain/langgraph-ui": "~0.0.21",
29
29
  "zod": "^3.23.8"
30
30
  },
31
31
  "resolutions": {
32
32
  "esbuild": "^0.25.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@langchain/langgraph-sdk": "^0.0.60",
35
+ "jose": "^6.0.10",
36
+ "@langchain/langgraph-sdk": "^0.0.66",
36
37
  "@types/react": "^19.0.8",
37
38
  "@types/react-dom": "^19.0.3",
38
39
  "@types/node": "^22.2.0",
@@ -5,7 +5,7 @@ import shutil
5
5
  import ssl
6
6
  from collections.abc import AsyncIterator
7
7
  from contextlib import AbstractContextManager
8
- from typing import Any, Literal, Self
8
+ from typing import Any, Literal, Self, cast
9
9
 
10
10
  import certifi
11
11
  import httpx
@@ -24,12 +24,20 @@ from langgraph.checkpoint.serde.base import SerializerProtocol
24
24
  from langgraph.pregel.types import PregelTask, StateSnapshot
25
25
  from langgraph.store.base import GetOp, Item, ListNamespacesOp, PutOp, SearchOp
26
26
  from langgraph.types import Command, Interrupt, Send
27
+ from langgraph_sdk import Auth
27
28
  from pydantic import BaseModel
28
29
  from starlette.applications import Starlette
30
+ from starlette.authentication import (
31
+ AuthCredentials,
32
+ AuthenticationBackend,
33
+ BaseUser,
34
+ )
29
35
  from starlette.exceptions import HTTPException
30
- from starlette.requests import Request
36
+ from starlette.requests import HTTPConnection, Request
31
37
  from starlette.routing import Route
32
38
 
39
+ from langgraph_api.auth.custom import DotDict, ProxyUser
40
+ from langgraph_api.config import LANGGRAPH_AUTH_TYPE
33
41
  from langgraph_api.js.base import BaseRemotePregel
34
42
  from langgraph_api.js.errors import RemoteException
35
43
  from langgraph_api.js.sse import SSEDecoder, aiter_lines_raw
@@ -365,7 +373,7 @@ async def run_js_process(paths_str: str, watch: bool = False):
365
373
 
366
374
 
367
375
  def _get_passthrough_checkpointer(conn: AsyncConnectionProto):
368
- from langgraph_storage.checkpoint import Checkpointer
376
+ from langgraph_runtime.checkpoint import Checkpointer
369
377
 
370
378
  class PassthroughSerialiser(SerializerProtocol):
371
379
  def dumps(self, obj: Any) -> bytes:
@@ -393,7 +401,7 @@ def _get_passthrough_checkpointer(conn: AsyncConnectionProto):
393
401
 
394
402
 
395
403
  def _get_passthrough_store():
396
- from langgraph_storage.store import Store
404
+ from langgraph_runtime.store import Store
397
405
 
398
406
  return Store()
399
407
 
@@ -401,7 +409,7 @@ def _get_passthrough_store():
401
409
  # Setup a HTTP server on top of CHECKPOINTER_SOCKET unix socket
402
410
  # used by `client.mts` to communicate with the Python checkpointer
403
411
  async def run_remote_checkpointer():
404
- from langgraph_storage.database import connect
412
+ from langgraph_runtime.database import connect
405
413
 
406
414
  async def checkpointer_list(payload: dict):
407
415
  """Search checkpoints"""
@@ -729,3 +737,86 @@ async def js_healthcheck():
729
737
  status_code=500,
730
738
  detail="JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
731
739
  ) from exc
740
+
741
+
742
+ class CustomJsAuthBackend(AuthenticationBackend):
743
+ ls_auth: AuthenticationBackend | None
744
+
745
+ def __init__(self, disable_studio_auth: bool = False):
746
+ self.ls_auth = None
747
+ if not disable_studio_auth and LANGGRAPH_AUTH_TYPE == "langsmith":
748
+ from langgraph_api.auth.langsmith.backend import LangsmithAuthBackend
749
+
750
+ self.ls_auth = LangsmithAuthBackend()
751
+
752
+ async def authenticate(
753
+ self, conn: HTTPConnection
754
+ ) -> tuple[AuthCredentials, BaseUser] | None:
755
+ if self.ls_auth is not None and (
756
+ (auth_scheme := conn.headers.get("x-auth-scheme"))
757
+ and auth_scheme == "langsmith"
758
+ ):
759
+ return await self.ls_auth.authenticate(conn)
760
+
761
+ headers = dict(conn.headers)
762
+ # need to remove content-length to prevent confusing the HTTP client
763
+ headers.pop("content-length", None)
764
+ headers["x-langgraph-auth-url"] = str(conn.url)
765
+ headers["x-langgraph-auth-method"] = conn.scope.get("method")
766
+
767
+ res = await _client.post("/auth/authenticate", headers=headers)
768
+ data = res.json()
769
+
770
+ if data.get("error"):
771
+ status = data.get("status") or 403
772
+ headers = data.get("headers")
773
+ message = data.get("message") or "Unauthorized"
774
+
775
+ raise HTTPException(status_code=status, detail=message, headers=headers)
776
+
777
+ return AuthCredentials(data["scopes"]), ProxyUser(DotDict(data["user"]))
778
+
779
+
780
+ async def handle_js_auth_event(
781
+ ctx: Auth.types.AuthContext | None,
782
+ value: dict,
783
+ ) -> Auth.types.FilterType | None:
784
+ res = await _client.post(
785
+ "/auth/authorize",
786
+ headers={"Content-Type": "application/json"},
787
+ data=json_dumpb(
788
+ {
789
+ "resource": ctx.resource,
790
+ "action": ctx.action,
791
+ "value": value,
792
+ "context": {
793
+ "user": cast(DotDict, ctx.user).dict(),
794
+ "scopes": ctx.permissions,
795
+ }
796
+ if ctx
797
+ else None,
798
+ }
799
+ ),
800
+ )
801
+
802
+ response = res.json()
803
+
804
+ if response.get("error"):
805
+ status = response.get("status") or 403
806
+ headers = response.get("headers")
807
+ message = response.get("message") or "Unauthorized"
808
+
809
+ raise HTTPException(status_code=status, detail=message, headers=headers)
810
+
811
+ filters = cast(Auth.types.FilterType | None, response.get("filters"))
812
+
813
+ # mutate metadata in value if applicable
814
+ # we need to preserve the identity of the object, so cannot create a new
815
+ # dictionary, otherwise the changes will not persist
816
+ if isinstance(value, dict) and (updated_value := response.get("value")):
817
+ if isinstance(value.get("metadata"), dict) and (
818
+ metadata := updated_value.get("metadata")
819
+ ):
820
+ value["metadata"].update(metadata)
821
+
822
+ return filters
@@ -20,12 +20,6 @@ export interface GraphSpec {
20
20
  exportSymbol: string;
21
21
  }
22
22
 
23
- export function filterValidGraphSpecs(specs: Record<string, string>) {
24
- return Object.entries(specs).filter(
25
- ([_, spec]) => !spec.split(":")[0].endsWith(".py")
26
- );
27
- }
28
-
29
23
  export type CompiledGraphFactory<T extends string> = (config: {
30
24
  configurable?: Record<string, unknown>;
31
25
  }) => Promise<CompiledGraph<T>>;
@@ -0,0 +1,4 @@
1
+ export function filterValidExportPath(path: string | undefined) {
2
+ if (!path) return false;
3
+ return !path.split(":")[0].endsWith(".py");
4
+ }