langgraph-api 0.2.125__py3-none-any.whl → 0.2.126__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

langgraph_api/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.2.125"
1
+ __version__ = "0.2.126"
langgraph_api/config.py CHANGED
@@ -180,6 +180,7 @@ REDIS_MAX_CONNECTIONS = env("REDIS_MAX_CONNECTIONS", cast=int, default=2000)
180
180
  REDIS_CONNECT_TIMEOUT = env("REDIS_CONNECT_TIMEOUT", cast=float, default=10.0)
181
181
  REDIS_MAX_IDLE_TIME = env("REDIS_MAX_IDLE_TIME", cast=float, default=120.0)
182
182
  REDIS_KEY_PREFIX = env("REDIS_KEY_PREFIX", cast=str, default="")
183
+ RUN_STATS_CACHE_SECONDS = env("RUN_STATS_CACHE_SECONDS", cast=int, default=60)
183
184
 
184
185
  # server
185
186
  ALLOW_PRIVATE_NETWORK = env("ALLOW_PRIVATE_NETWORK", cast=bool, default=False)
@@ -6,6 +6,7 @@ from starlette.requests import ClientDisconnect
6
6
  from starlette.types import Message, Receive, Scope, Send
7
7
 
8
8
  from langgraph_api.http_metrics import HTTP_METRICS_COLLECTOR
9
+ from langgraph_api.utils.headers import should_include_header
9
10
 
10
11
  asgi = structlog.stdlib.get_logger("asgi")
11
12
 
@@ -99,12 +100,24 @@ class AccessLoggerMiddleware:
99
100
  )
100
101
 
101
102
 
102
- HEADERS_IGNORE = {b"authorization", b"cookie", b"set-cookie", b"x-api-key"}
103
+ IGNORE_HEADERS = {
104
+ b"authorization",
105
+ b"cookie",
106
+ b"set-cookie",
107
+ b"x-api-key",
108
+ }
103
109
 
104
110
 
105
111
  def _headers_to_dict(headers: list[tuple[bytes, bytes]] | None) -> dict[str, str]:
106
112
  if headers is None:
107
113
  return {}
108
- return {
109
- k.decode(): v.decode() for k, v in headers if k.lower() not in HEADERS_IGNORE
110
- }
114
+
115
+ result = {}
116
+ for k, v in headers:
117
+ if k in IGNORE_HEADERS:
118
+ continue
119
+ key = k.decode()
120
+ if should_include_header(key):
121
+ result[key] = v.decode()
122
+
123
+ return result
@@ -1,6 +1,4 @@
1
1
  import asyncio
2
- import functools
3
- import re
4
2
  import time
5
3
  import urllib.parse
6
4
  import uuid
@@ -28,6 +26,7 @@ from langgraph_api.schema import (
28
26
  StreamMode,
29
27
  )
30
28
  from langgraph_api.utils import AsyncConnectionProto, get_auth_ctx
29
+ from langgraph_api.utils.headers import should_include_header
31
30
  from langgraph_runtime.ops import Runs, logger
32
31
 
33
32
 
@@ -180,82 +179,61 @@ LANGSMITH_TAGS = "langsmith-tags"
180
179
  LANGSMITH_PROJECT = "langsmith-project"
181
180
 
182
181
 
183
- def translate_pattern(pat: str) -> re.Pattern[str]:
184
- """Translate a pattern to regex, supporting only literals and * wildcards to avoid RE DoS."""
185
- res = []
186
- i = 0
187
- n = len(pat)
188
-
189
- while i < n:
190
- c = pat[i]
191
- i += 1
192
-
193
- if c == "*":
194
- res.append(".*")
195
- else:
196
- res.append(re.escape(c))
197
-
198
- pattern = "".join(res)
199
- return re.compile(rf"(?s:{pattern})\Z")
200
-
201
-
202
- @functools.lru_cache(maxsize=1)
203
- def get_header_patterns() -> tuple[
204
- list[re.Pattern[str] | None], list[re.Pattern[str] | None]
205
- ]:
206
- from langgraph_api import config
207
-
208
- if not config.HTTP_CONFIG:
209
- return None, None
210
- configurable = config.HTTP_CONFIG.get("configurable_headers")
211
- if not configurable:
212
- return None, None
213
- header_includes = configurable.get("includes") or configurable.get("include") or []
214
- include_patterns = []
215
- for include in header_includes:
216
- include_patterns.append(translate_pattern(include))
217
- header_excludes = configurable.get("excludes") or configurable.get("exclude") or []
218
- exclude_patterns = []
219
- for exclude in header_excludes:
220
- exclude_patterns.append(translate_pattern(exclude))
221
- return include_patterns, exclude_patterns
182
+ # Default headers to exclude from run configuration for security
183
+ DEFAULT_RUN_HEADERS_EXCLUDE = {"x-api-key", "x-tenant-id", "x-service-key"}
222
184
 
223
185
 
224
186
  def get_configurable_headers(headers: dict[str, str]) -> dict[str, str]:
187
+ """Extract headers that should be added to run configuration.
188
+
189
+ This function handles special cases like langsmith-trace and baggage headers,
190
+ while respecting the configurable header patterns.
191
+ """
225
192
  configurable = {}
226
- include_patterns, exclude_patterns = get_header_patterns()
193
+
227
194
  for key, value in headers.items():
228
- # First handle tracing stuff; not configurable
195
+ # First handle tracing stuff - always included regardless of patterns
229
196
  if key == "langsmith-trace":
230
197
  configurable[key] = value
231
198
  if baggage := headers.get("baggage"):
232
199
  for item in baggage.split(","):
233
- key, value = item.split("=")
234
- if key == LANGSMITH_METADATA and key not in configurable:
235
- configurable[key] = orjson.loads(urllib.parse.unquote(value))
236
- elif key == LANGSMITH_TAGS:
237
- configurable[key] = urllib.parse.unquote(value).split(",")
238
- elif key == LANGSMITH_PROJECT:
239
- configurable[key] = urllib.parse.unquote(value)
240
- # Then handle overridable behavior
241
- if exclude_patterns and any(pattern.match(key) for pattern in exclude_patterns):
242
- continue
243
- if include_patterns and any(pattern.match(key) for pattern in include_patterns):
244
- configurable[key] = value
200
+ baggage_key, baggage_value = item.split("=")
201
+ if (
202
+ baggage_key == LANGSMITH_METADATA
203
+ and baggage_key not in configurable
204
+ ):
205
+ configurable[baggage_key] = orjson.loads(
206
+ urllib.parse.unquote(baggage_value)
207
+ )
208
+ elif baggage_key == LANGSMITH_TAGS:
209
+ configurable[baggage_key] = urllib.parse.unquote(
210
+ baggage_value
211
+ ).split(",")
212
+ elif baggage_key == LANGSMITH_PROJECT:
213
+ configurable[baggage_key] = urllib.parse.unquote(baggage_value)
245
214
  continue
246
215
 
247
- # Then handle default behavior
216
+ # Check if header should be included based on patterns
217
+ # For run configuration, we have specific default behavior for x-* headers
248
218
  if key.startswith("x-"):
249
- if key in (
250
- "x-api-key",
251
- "x-tenant-id",
252
- "x-service-key",
253
- ):
219
+ # Check against default excludes for x-* headers
220
+ if key in DEFAULT_RUN_HEADERS_EXCLUDE:
221
+ # Check if explicitly included via patterns
222
+ if should_include_header(key):
223
+ configurable[key] = value
254
224
  continue
255
- configurable[key] = value
256
-
225
+ # Other x-* headers are included by default unless patterns exclude them
226
+ if should_include_header(key):
227
+ configurable[key] = value
257
228
  elif key == "user-agent":
258
- configurable[key] = value
229
+ # user-agent is included by default unless excluded by patterns
230
+ if should_include_header(key):
231
+ configurable[key] = value
232
+ else:
233
+ # All other headers only included if patterns allow
234
+ if should_include_header(key):
235
+ configurable[key] = value
236
+
259
237
  return configurable
260
238
 
261
239
 
@@ -0,0 +1,72 @@
1
+ """Shared utilities for configurable header filtering."""
2
+
3
+ import functools
4
+ import re
5
+
6
+
7
+ def translate_pattern(pat: str) -> re.Pattern[str]:
8
+ """Translate a pattern to regex, supporting only literals and * wildcards to avoid RE DoS."""
9
+ res = []
10
+ i = 0
11
+ n = len(pat)
12
+
13
+ while i < n:
14
+ c = pat[i]
15
+ i += 1
16
+
17
+ if c == "*":
18
+ res.append(".*")
19
+ else:
20
+ res.append(re.escape(c))
21
+
22
+ pattern = "".join(res)
23
+ return re.compile(rf"(?s:{pattern})\Z")
24
+
25
+
26
+ @functools.lru_cache(maxsize=1)
27
+ def get_header_patterns() -> tuple[
28
+ list[re.Pattern[str]] | None, list[re.Pattern[str]] | None
29
+ ]:
30
+ """Get the configured header include/exclude patterns."""
31
+ from langgraph_api import config
32
+
33
+ if not config.HTTP_CONFIG:
34
+ return None, None
35
+ configurable = config.HTTP_CONFIG.get("configurable_headers")
36
+ if not configurable:
37
+ return None, None
38
+ header_includes = configurable.get("includes") or configurable.get("include") or []
39
+ include_patterns = []
40
+ for include in header_includes:
41
+ include_patterns.append(translate_pattern(include))
42
+ header_excludes = configurable.get("excludes") or configurable.get("exclude") or []
43
+ exclude_patterns = []
44
+ for exclude in header_excludes:
45
+ exclude_patterns.append(translate_pattern(exclude))
46
+ return include_patterns or None, exclude_patterns or None
47
+
48
+
49
+ @functools.lru_cache(maxsize=512)
50
+ def should_include_header(key: str) -> bool:
51
+ """Check if a header should be included based on cached patterns.
52
+
53
+ This function uses cached patterns from get_header_patterns() and
54
+ provides efficient header filtering.
55
+
56
+ Args:
57
+ key: The header key to check
58
+
59
+ Returns:
60
+ True if the header should be included, False otherwise
61
+ """
62
+ include_patterns, exclude_patterns = get_header_patterns()
63
+
64
+ # Handle configurable behavior
65
+ if exclude_patterns and any(pattern.match(key) for pattern in exclude_patterns):
66
+ return False
67
+ if include_patterns:
68
+ # If include patterns are specified, only include headers matching them
69
+ return any(pattern.match(key) for pattern in include_patterns)
70
+
71
+ # Default behavior - include if not excluded
72
+ return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.2.125
3
+ Version: 0.2.126
4
4
  Author-email: Nuno Campos <nuno@langchain.dev>, Will Fu-Hinthorn <will@langchain.dev>
5
5
  License: Elastic-2.0
6
6
  License-File: LICENSE
@@ -1,9 +1,9 @@
1
- langgraph_api/__init__.py,sha256=bcQUJPOGIDh7Iu1C7gCmymyGyrOInGOeeO_ttoZlkyA,24
1
+ langgraph_api/__init__.py,sha256=MJXz1iIiYzSsVkIx-r66BL7rUK8vsMX6Q89086HsRSU,24
2
2
  langgraph_api/asgi_transport.py,sha256=eqifhHxNnxvI7jJqrY1_8RjL4Fp9NdN4prEub2FWBt8,5091
3
3
  langgraph_api/asyncio.py,sha256=Wv4Rwm-a-Cf6JpfgJmVuVlXQ7SlwrjbTn0eq1ux8I2Q,9652
4
4
  langgraph_api/cli.py,sha256=xQojITwmmKSJw48Lr2regcnRPRq2FJqWlPpeyr5TgbU,16158
5
5
  langgraph_api/command.py,sha256=3O9v3i0OPa96ARyJ_oJbLXkfO8rPgDhLCswgO9koTFA,768
6
- langgraph_api/config.py,sha256=Nxhx6fOsxk_u-Aae54JAGn46JQ1wKXPjeu_KX_3d4wQ,11918
6
+ langgraph_api/config.py,sha256=P89uB2IOycXW0qD0bb3stiaN2xAv7ixw_vqBWHM94Bw,11997
7
7
  langgraph_api/cron_scheduler.py,sha256=CiwZ-U4gDOdG9zl9dlr7mH50USUgNB2Fvb8YTKVRBN4,2625
8
8
  langgraph_api/errors.py,sha256=zlnl3xXIwVG0oGNKKpXf1an9Rn_SBDHSyhe53hU6aLw,1858
9
9
  langgraph_api/feature_flags.py,sha256=GjwmNjfg0Jhs3OzR2VbK2WgrRy3o5l8ibIYiUtQkDPA,363
@@ -70,16 +70,17 @@ langgraph_api/js/src/utils/importMap.mts,sha256=pX4TGOyUpuuWF82kXcxcv3-8mgusRezO
70
70
  langgraph_api/js/src/utils/pythonSchemas.mts,sha256=98IW7Z_VP7L_CHNRMb3_MsiV3BgLE2JsWQY_PQcRR3o,685
71
71
  langgraph_api/js/src/utils/serde.mts,sha256=D9o6MwTgwPezC_DEmsWS5NnLPnjPMVWIb1I1D4QPEPo,743
72
72
  langgraph_api/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
- langgraph_api/middleware/http_logger.py,sha256=L7ZhypmQjlHBfm93GqZaqUXzu0r-ieaoO1lY7t1jGb0,3701
73
+ langgraph_api/middleware/http_logger.py,sha256=tUdWuIKtDa2EkFtG_kCjw1Wkgv7gbGH3hZSgWM5Pta4,3892
74
74
  langgraph_api/middleware/private_network.py,sha256=eYgdyU8AzU2XJu362i1L8aSFoQRiV7_aLBPw7_EgeqI,2111
75
75
  langgraph_api/middleware/request_id.py,sha256=SDj3Yi3WvTbFQ2ewrPQBjAV8sYReOJGeIiuoHeZpR9g,1242
76
76
  langgraph_api/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
- langgraph_api/models/run.py,sha256=RX2LaE1kmTruX8o8HgvqeEt5YpPGHILWByChjMZVJ58,15035
77
+ langgraph_api/models/run.py,sha256=Yn1VuAAzxLMb9akOw8mLMWgx2cxPhJiIZ4-lYe75guQ,14808
78
78
  langgraph_api/tunneling/cloudflare.py,sha256=iKb6tj-VWPlDchHFjuQyep2Dpb-w2NGfJKt-WJG9LH0,3650
79
79
  langgraph_api/utils/__init__.py,sha256=EQu0PShwHhxUI_9mDFgqlAf5_y5bX8TEk723P5iby24,4161
80
80
  langgraph_api/utils/cache.py,sha256=SrtIWYibbrNeZzLXLUGBFhJPkMVNQnVxR5giiYGHEfI,1810
81
81
  langgraph_api/utils/config.py,sha256=gONI0UsoSpuR72D9lSGAmpr-_iSMDFdD4M_tiXXjmNk,3936
82
82
  langgraph_api/utils/future.py,sha256=CGhUb_Ht4_CnTuXc2kI8evEn1gnMKYN0ce9ZyUkW5G4,7251
83
+ langgraph_api/utils/headers.py,sha256=fRZWsR279KOlbfbdcoajguVDDgVdN7V2OYkWxnHeXCE,2239
83
84
  langgraph_license/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
85
  langgraph_license/validation.py,sha256=CU38RUZ5xhP1S8F_y8TNeV6OmtO-tIGjCXbXTwJjJO4,612
85
86
  langgraph_runtime/__init__.py,sha256=O4GgSmu33c-Pr8Xzxj_brcK5vkm70iNTcyxEjICFZxA,1075
@@ -94,8 +95,8 @@ langgraph_runtime/store.py,sha256=7mowndlsIroGHv3NpTSOZDJR0lCuaYMBoTnTrewjslw,11
94
95
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
95
96
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
96
97
  openapi.json,sha256=SPCrzYpta2xTl-WE2W6qwosYdQqLeB8qpzaYEbcK44k,150725
97
- langgraph_api-0.2.125.dist-info/METADATA,sha256=nf3cx_h7rAHCrc6PLmUvnzQk21W-B5ugdabVU9VoiDU,3890
98
- langgraph_api-0.2.125.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
99
- langgraph_api-0.2.125.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
100
- langgraph_api-0.2.125.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
101
- langgraph_api-0.2.125.dist-info/RECORD,,
98
+ langgraph_api-0.2.126.dist-info/METADATA,sha256=2nFffECkS7whKzQeFOf4N-BWWgyRgAbKq1VVLqDIIA4,3890
99
+ langgraph_api-0.2.126.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
100
+ langgraph_api-0.2.126.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
101
+ langgraph_api-0.2.126.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
102
+ langgraph_api-0.2.126.dist-info/RECORD,,