langgraph-api 0.1.0__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.
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/PKG-INFO +1 -2
- langgraph_api-0.1.2/langgraph_api/__init__.py +1 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/auth/custom.py +25 -4
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/graph.py +4 -12
- langgraph_api-0.1.2/langgraph_api/js/base.py +29 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/build.mts +3 -3
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/client.mts +64 -3
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/global.d.ts +1 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/package.json +4 -3
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/remote.py +93 -2
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/src/graph.mts +0 -6
- langgraph_api-0.1.2/langgraph_api/js/src/utils/files.mts +4 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/api.test.mts +80 -80
- langgraph_api-0.1.2/langgraph_api/js/tests/auth.test.mts +648 -0
- langgraph_api-0.1.2/langgraph_api/js/tests/compose-postgres.auth.yml +59 -0
- langgraph_api-0.1.2/langgraph_api/js/tests/graphs/agent_simple.mts +79 -0
- langgraph_api-0.1.2/langgraph_api/js/tests/graphs/auth.mts +106 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/package.json +3 -1
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/yarn.lock +9 -4
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/yarn.lock +18 -23
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/stream.py +2 -1
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/worker.py +1 -22
- langgraph_api-0.1.2/langgraph_license/__init__.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/pyproject.toml +1 -2
- langgraph_api-0.1.0/langgraph_api/__init__.py +0 -1
- langgraph_api-0.1.0/langgraph_api/js/base.py +0 -12
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/LICENSE +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/README.md +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/api/__init__.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/api/assistants.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/api/mcp.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/api/meta.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/api/openapi.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/api/runs.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/api/store.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/api/threads.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/api/ui.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/asyncio.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/auth/__init__.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/auth/langsmith/__init__.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/auth/langsmith/backend.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/auth/langsmith/client.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/auth/middleware.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/auth/noop.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/auth/studio_user.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/cli.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/command.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/config.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/cron_scheduler.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/errors.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/http.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/.gitignore +0 -0
- {langgraph_api-0.1.0/langgraph_api/middleware → langgraph_api-0.1.2/langgraph_api/js}/__init__.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/errors.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/schema.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/src/hooks.mjs +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/src/parser/parser.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/src/schema/types.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/src/schema/types.template.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/src/utils/importMap.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/src/utils/serde.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/sse.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/compose-postgres.yml +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/.gitignore +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/agent.css +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/agent.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/agent.ui.tsx +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/command.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/delay.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/dynamic.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/error.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/nested.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/weather.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/parser.test.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/tests/utils.mts +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/js/ui.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/logging.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/metadata.py +0 -0
- {langgraph_api-0.1.0/langgraph_api/models → langgraph_api-0.1.2/langgraph_api/middleware}/__init__.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/middleware/http_logger.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/middleware/private_network.py +0 -0
- {langgraph_api-0.1.0/langgraph_license → langgraph_api-0.1.2/langgraph_api/models}/__init__.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/models/run.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/patch.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/queue_entrypoint.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/route.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/schema.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/serde.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/server.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/sse.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/state.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/thread_ttl.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/utils.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/validation.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_api/webhook.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_license/middleware.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_license/validation.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/langgraph_runtime/__init__.py +0 -0
- {langgraph_api-0.1.0 → langgraph_api-0.1.2}/logging.json +0 -0
- {langgraph_api-0.1.0 → 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.1.
|
|
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"
|
|
@@ -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
|
|
@@ -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:
|
|
@@ -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 =
|
|
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 =
|
|
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) => {
|
|
@@ -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.
|
|
28
|
-
"@langchain/langgraph-ui": "~0.0.
|
|
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
|
-
"
|
|
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
|
|
@@ -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>>;
|