langgraph-api 0.14.0.dev4__py3-none-any.whl → 0.14.0.dev6__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.
langgraph_api/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.14.0.dev4"
1
+ __version__ = "0.14.0.dev6"
@@ -262,6 +262,17 @@ class _CustomCheckpointerAdapter(BaseCheckpointSaver):
262
262
  return f"{next_v:032}.{next_h:.16f}"
263
263
 
264
264
 
265
+ def _encryption_context_provider() -> Callable[[], dict[str, Any]] | None:
266
+ """Supply request-scoped encryption context to the gRPC checkpointer."""
267
+ if not config.LANGGRAPH_ENCRYPTION:
268
+ return None
269
+ from langgraph_api.encryption.context import ( # noqa: PLC0415
270
+ get_encryption_context,
271
+ )
272
+
273
+ return get_encryption_context
274
+
275
+
265
276
  async def get_checkpointer(
266
277
  *,
267
278
  conn: Any | None = None,
@@ -331,6 +342,7 @@ async def get_checkpointer(
331
342
  GrpcCheckpointer(
332
343
  get_stub=_get_shared_checkpointer_stub,
333
344
  serializer=_ApiSerializer(),
345
+ encryption_context_provider=_encryption_context_provider(),
334
346
  ),
335
347
  )
336
348
 
@@ -345,6 +357,7 @@ async def get_checkpointer(
345
357
  GrpcCheckpointer(
346
358
  get_stub=_get_shared_checkpointer_stub,
347
359
  serializer=_ApiSerializer(),
360
+ encryption_context_provider=_encryption_context_provider(),
348
361
  ),
349
362
  )
350
363
 
@@ -236,6 +236,11 @@ if HTTP_CONFIG:
236
236
  user_router.router.lifespan_context,
237
237
  )
238
238
 
239
+ # Keep the reference to user router's lifespan in case it gets combined with other lifespans.
240
+ custom_app_lifespan = (
241
+ None if user_router is None else user_router.router.lifespan_context
242
+ )
243
+
239
244
 
240
245
  if "__inmem" in MIGRATIONS_PATH:
241
246
  from langgraph_runtime_inmem.routes import get_internal_routes
langgraph_api/api/a2a.py CHANGED
@@ -27,6 +27,7 @@ from starlette.responses import JSONResponse, Response
27
27
  from typing_extensions import TypedDict
28
28
 
29
29
  from langgraph_api import __version__
30
+ from langgraph_api.config import A2A_ALLOWED_TOOL_CALL_RESULTS
30
31
  from langgraph_api.metadata import USER_API_URL
31
32
  from langgraph_api.route import ApiRequest, ApiRoute
32
33
  from langgraph_api.schema import RunCommand
@@ -893,6 +894,12 @@ def _tool_result_data(message: dict[str, Any]) -> dict[str, Any] | None:
893
894
  if not isinstance(tool_call_id, str) or not tool_call_id:
894
895
  return None
895
896
 
897
+ if (
898
+ A2A_ALLOWED_TOOL_CALL_RESULTS is not None
899
+ and message.get("name") not in A2A_ALLOWED_TOOL_CALL_RESULTS
900
+ ):
901
+ return None
902
+
896
903
  result: dict[str, Any] = {"toolCallId": tool_call_id}
897
904
  content = message.get("content")
898
905
  if content not in (None, ""):
@@ -0,0 +1,71 @@
1
+ """Data-plane JWT authentication for Beacon OTLP exports."""
2
+
3
+ import time
4
+ from datetime import UTC, datetime, timedelta
5
+ from typing import Any
6
+
7
+ import jwt
8
+ from requests.sessions import Session
9
+
10
+ DATA_PLANE_AUTH_HEADER = "X-Data-Plane-Auth"
11
+ BEACON_OTEL_COLLECTOR_PURPOSE = "beacon-otel-collector"
12
+
13
+ _BEACON_OTEL_JWT_TTL_SECONDS = 60 * 60
14
+ _BEACON_OTEL_JWT_REFRESH_BEFORE_SECONDS = 60
15
+
16
+
17
+ def sign_data_plane_jwt(data_plane_id: str, organization_id: str, secret: str) -> str:
18
+ now = datetime.now(UTC)
19
+ return jwt.encode(
20
+ {
21
+ "data_plane_id": data_plane_id,
22
+ "organization_id": organization_id,
23
+ "purpose": BEACON_OTEL_COLLECTOR_PURPOSE,
24
+ "iat": now,
25
+ "exp": now + timedelta(seconds=_BEACON_OTEL_JWT_TTL_SECONDS),
26
+ },
27
+ secret,
28
+ algorithm="HS256",
29
+ )
30
+
31
+
32
+ class _BeaconOtelJWTCache:
33
+ def __init__(self, data_plane_id: str, organization_id: str, secret: str) -> None:
34
+ self._data_plane_id = data_plane_id
35
+ self._organization_id = organization_id
36
+ self._secret = secret
37
+ self._cached_token: tuple[str, float] | None = None
38
+
39
+ def get(self) -> str:
40
+ cached_token = self._cached_token
41
+ if cached_token is not None:
42
+ token, refresh_at = cached_token
43
+ if time.monotonic() < refresh_at:
44
+ return token
45
+
46
+ token = sign_data_plane_jwt(
47
+ self._data_plane_id, self._organization_id, self._secret
48
+ )
49
+ refresh_at = time.monotonic() + (
50
+ _BEACON_OTEL_JWT_TTL_SECONDS - _BEACON_OTEL_JWT_REFRESH_BEFORE_SECONDS
51
+ )
52
+ self._cached_token = (token, refresh_at)
53
+ return token
54
+
55
+
56
+ class _DataPlaneAuthSession(Session):
57
+ def __init__(self, cache: _BeaconOtelJWTCache) -> None:
58
+ super().__init__()
59
+ self._cache = cache
60
+
61
+ def request(self, method: str, url: str, **kwargs: Any):
62
+ headers = dict(kwargs.pop("headers", None) or {})
63
+ headers[DATA_PLANE_AUTH_HEADER] = self._cache.get()
64
+ return super().request(method, url, headers=headers, **kwargs)
65
+
66
+
67
+ def beacon_otel_session(
68
+ data_plane_id: str, organization_id: str, secret: str
69
+ ) -> Session:
70
+ cache = _BeaconOtelJWTCache(data_plane_id, organization_id, secret)
71
+ return _DataPlaneAuthSession(cache)
@@ -251,6 +251,17 @@ JSON_THREAD_POOL_MINIMUM_SIZE_BYTES = 100 * 1024 # 100 KB
251
251
  HTTP_CONFIG = env("LANGGRAPH_HTTP", cast=_parse.parse_schema(HttpConfig), default=None)
252
252
  MCP_ENABLED = HTTP_CONFIG is None or not HTTP_CONFIG.get("disable_mcp")
253
253
  A2A_ENABLED = HTTP_CONFIG is None or not HTTP_CONFIG.get("disable_a2a")
254
+
255
+
256
+ def _parse_allowed_tool_call_results(value: str) -> frozenset[str]:
257
+ return frozenset(name.strip() for name in value.split(",") if name.strip())
258
+
259
+
260
+ A2A_ALLOWED_TOOL_CALL_RESULTS = env(
261
+ "A2A_ALLOWED_TOOL_CALL_RESULTS",
262
+ cast=_parse_allowed_tool_call_results,
263
+ default=None,
264
+ )
254
265
  WEBHOOKS_ENABLED = HTTP_CONFIG and HTTP_CONFIG.get("disable_webhooks")
255
266
  # Not in public docs: populated by langgraph.json config, not set as env var directly
256
267
  STORE_CONFIG = env(
@@ -603,6 +614,14 @@ DATADOG_METRICS_ENABLED = bool(LSD_DD_API_KEY)
603
614
  EXPOSE_INTERNAL_METRICS_PROMETHEUS = env(
604
615
  "EXPOSE_INTERNAL_METRICS_PROMETHEUS", cast=bool, default=False
605
616
  )
617
+
618
+ # These are only set for internal agents (polly/insights/engine/etc..) deployed in byoc so we can fetch logs (see api/langgraph_api/self_hosted_logs.py)
619
+ DATA_PLANE_ID: str | None = env("DATA_PLANE_ID", cast=str, default=None)
620
+ DATA_PLANE_ORG_ID: str | None = env("DATA_PLANE_ORG_ID", cast=str, default=None)
621
+ DATA_PLANE_JWT_SECRET: str | None = env("DATA_PLANE_JWT_SECRET", cast=str, default=None)
622
+ BYOC_HOST = env("BYOC_HOST", cast=str, default="byoc")
623
+ BYOC_ENV = env("BYOC_ENV", cast=str, default="byoc")
624
+
606
625
  LANGGRAPH_LOGS_ENDPOINT = env("LANGGRAPH_LOGS_ENDPOINT", cast=str, default=None)
607
626
  LANGGRAPH_LOGS_ENABLED = env("LANGGRAPH_LOGS_ENABLED", cast=bool, default=False)
608
627
 
@@ -649,6 +668,8 @@ __all__ = [
649
668
  "BG_JOB_MAX_RETRIES",
650
669
  "BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS",
651
670
  "BG_JOB_TIMEOUT_SECS",
671
+ "BYOC_ENV",
672
+ "BYOC_HOST",
652
673
  "CHECKPOINTER_CONFIG",
653
674
  "CHECKPOINTER_POSTGRES_POOL_MIN_SIZE",
654
675
  "CHECKPOINTER_POSTGRES_POOL_TIMEOUT_SECONDS",
@@ -659,6 +680,9 @@ __all__ = [
659
680
  "CRON_SCHEDULER_SLEEP_TIME",
660
681
  "DATABASE_URI",
661
682
  "DATADOG_METRICS_ENABLED",
683
+ "DATA_PLANE_ID",
684
+ "DATA_PLANE_JWT_SECRET",
685
+ "DATA_PLANE_ORG_ID",
662
686
  "DB_MIGRATION_BY_CORE_API",
663
687
  "EXPOSE_INTERNAL_METRICS_PROMETHEUS",
664
688
  "FF_CRONS_ENABLED",
@@ -19,6 +19,7 @@ from langgraph_grpc_common.proto.encryption_pb2 import (
19
19
  from langgraph_grpc_common.proto.encryption_pb2_grpc import EncryptionServicer
20
20
  from langgraph_sdk import EncryptionContext
21
21
 
22
+ from langgraph_api.encryption.custom import get_custom_encryption_instance
22
23
  from langgraph_api.encryption.middleware import _extract_skip_fields
23
24
  from langgraph_api.encryption.shared import BLOB_ENCRYPTION_CONTEXT_KEY, get_encryption
24
25
  from langgraph_api.schema import NESTED_ENCRYPTED_SUBFIELDS
@@ -250,10 +251,11 @@ class EncryptionServicerImpl(EncryptionServicer):
250
251
 
251
252
  encryptor = encryption_instance.get_json_encryptor(model_type)
252
253
  if encryptor is None:
253
- context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
254
- model_err = f"No encryptor configured for model type: {model_type}"
255
- context.set_details(model_err)
256
- raise RuntimeError(model_err)
254
+ metadata = _parse_metadata(dict(request.context.metadata))
255
+ if metadata:
256
+ data[BLOB_ENCRYPTION_CONTEXT_KEY] = metadata
257
+ return EncryptResponse(data=orjson.dumps(data))
258
+ return EncryptResponse(data=request.data)
257
259
 
258
260
  # Use recursive encryption that handles NESTED_ENCRYPTED_SUBFIELDS
259
261
  encrypted = await self._encrypt_field_recursive(
@@ -279,12 +281,7 @@ class EncryptionServicerImpl(EncryptionServicer):
279
281
  request,
280
282
  context: grpc_aio.ServicerContext,
281
283
  ) -> DecryptResponse:
282
- """Decrypt JSON data using the configured encryption.
283
-
284
- Uses the JsonEncryptionWrapper which routes to the appropriate decryptor
285
- based on the encryption context marker (handles AES migration).
286
- Handles nested subfields defined in NESTED_ENCRYPTED_SUBFIELDS recursively.
287
- """
284
+ """Decrypt JSON data using the configured encryption."""
288
285
  try:
289
286
  encryption_instance = get_encryption()
290
287
  if encryption_instance is None:
@@ -300,12 +297,9 @@ class EncryptionServicerImpl(EncryptionServicer):
300
297
  # removes it, so we save and restore it after decryption.
301
298
  blob_enc_ctx = data.get(BLOB_ENCRYPTION_CONTEXT_KEY)
302
299
 
303
- # Model and field are optional, used for decryptor selection/routing
304
300
  model_type: ModelType | None = request.model or None
305
301
  field = request.field or None
306
302
 
307
- # Get the decryptor from the wrapper (handles routing based on
308
- # __encryption_context__ marker in the data)
309
303
  decryptor = encryption_instance.get_json_decryptor(model_type)
310
304
  if decryptor is None:
311
305
  model_err = f"No decryptor configured for model type: {model_type}"
@@ -313,7 +307,6 @@ class EncryptionServicerImpl(EncryptionServicer):
313
307
  context.set_details(model_err)
314
308
  raise RuntimeError(model_err)
315
309
 
316
- # Use recursive decryption that handles NESTED_ENCRYPTED_SUBFIELDS
317
310
  decrypted = await self._decrypt_field_recursive(
318
311
  data, model_type, field or "", decryptor
319
312
  )
@@ -334,3 +327,63 @@ class EncryptionServicerImpl(EncryptionServicer):
334
327
  context.set_code(grpc.StatusCode.INTERNAL)
335
328
  context.set_details(f"Decryption failed: {e}")
336
329
  raise
330
+
331
+ async def EncryptBlob(
332
+ self,
333
+ request,
334
+ context: grpc_aio.ServicerContext,
335
+ ) -> EncryptResponse:
336
+ """Encrypt opaque bytes with the configured blob handler."""
337
+ try:
338
+ encryption_instance = get_custom_encryption_instance()
339
+ if (
340
+ encryption_instance is None
341
+ or encryption_instance._blob_encryptor is None
342
+ ):
343
+ context.set_code(grpc.StatusCode.FAILED_PRECONDITION)
344
+ context.set_details("No blob encryption configured")
345
+ raise RuntimeError("No blob encryption configured")
346
+
347
+ enc_ctx = _build_encryption_context(
348
+ request.context.model or None,
349
+ request.context.field or None,
350
+ dict(request.context.metadata),
351
+ )
352
+ encrypted = await encryption_instance._blob_encryptor(enc_ctx, request.data)
353
+ return EncryptResponse(data=encrypted)
354
+ except Exception as e:
355
+ await logger.aerror("EncryptBlob failed", error=str(e), exc_info=True)
356
+ if context.code() is None:
357
+ context.set_code(grpc.StatusCode.INTERNAL)
358
+ context.set_details(f"Blob encryption failed: {e}")
359
+ raise
360
+
361
+ async def DecryptBlob(
362
+ self,
363
+ request,
364
+ context: grpc_aio.ServicerContext,
365
+ ) -> DecryptResponse:
366
+ """Decrypt opaque bytes with the configured blob handler."""
367
+ try:
368
+ encryption_instance = get_custom_encryption_instance()
369
+ if (
370
+ encryption_instance is None
371
+ or encryption_instance._blob_decryptor is None
372
+ ):
373
+ context.set_code(grpc.StatusCode.FAILED_PRECONDITION)
374
+ context.set_details("No blob decryption configured")
375
+ raise RuntimeError("No blob decryption configured")
376
+
377
+ enc_ctx = _build_encryption_context(
378
+ request.context.model or None,
379
+ request.context.field or None,
380
+ dict(request.context.metadata),
381
+ )
382
+ decrypted = await encryption_instance._blob_decryptor(enc_ctx, request.data)
383
+ return DecryptResponse(data=decrypted)
384
+ except Exception as e:
385
+ await logger.aerror("DecryptBlob failed", error=str(e), exc_info=True)
386
+ if context.code() is None:
387
+ context.set_code(grpc.StatusCode.INTERNAL)
388
+ context.set_details(f"Blob decryption failed: {e}")
389
+ raise
@@ -180,12 +180,11 @@ async def entrypoint(
180
180
  ):
181
181
  from langgraph_api import logging as lg_logging # noqa: PLC0415
182
182
  from langgraph_api import timing # noqa: PLC0415
183
- from langgraph_api.api import user_router # noqa: PLC0415
183
+ from langgraph_api.api import custom_app_lifespan # noqa: PLC0415
184
184
  from langgraph_api.server import app # noqa: PLC0415
185
185
 
186
186
  lg_logging.set_logging_context({"entrypoint": entrypoint_name})
187
187
  tasks: set[asyncio.Task] = set()
188
- user_lifespan = None if user_router is None else user_router.router.lifespan_context
189
188
  wrapped_lifespan = timing.combine_lifespans(
190
189
  functools.partial(
191
190
  lifespan.lifespan,
@@ -193,7 +192,7 @@ async def entrypoint(
193
192
  taskset=tasks,
194
193
  cancel_event=cancel_event,
195
194
  ),
196
- user_lifespan,
195
+ custom_app_lifespan,
197
196
  )
198
197
 
199
198
  async with wrapped_lifespan(app):
@@ -10,6 +10,7 @@ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
10
10
  from opentelemetry.util.types import Attributes
11
11
 
12
12
  from langgraph_api import config
13
+ from langgraph_api.beacon_data_plane_jwt_auth import beacon_otel_session
13
14
  from langgraph_api.logging import OTLPFormatter
14
15
 
15
16
  logger = structlog.stdlib.get_logger(__name__)
@@ -45,10 +46,27 @@ def initialize_self_hosted_logs() -> None:
45
46
  raise RuntimeError(
46
47
  "LANGGRAPH_LOGS_ENABLED is true but no LANGGRAPH_LOGS_ENDPOINT is configured"
47
48
  )
49
+ log_endpoint = config.LANGGRAPH_LOGS_ENDPOINT
50
+
51
+ data_plane_settings = (
52
+ ("DATA_PLANE_ID", config.DATA_PLANE_ID),
53
+ ("DATA_PLANE_ORG_ID", config.DATA_PLANE_ORG_ID),
54
+ ("DATA_PLANE_JWT_SECRET", config.DATA_PLANE_JWT_SECRET),
55
+ )
56
+ has_data_plane_settings = any(value for _, value in data_plane_settings)
57
+ missing_data_plane_settings = [
58
+ name for name, value in data_plane_settings if not value
59
+ ]
60
+ if has_data_plane_settings and missing_data_plane_settings:
61
+ raise RuntimeError(
62
+ "LANGGRAPH_LOGS_ENABLED is true but required data plane settings are missing: "
63
+ + ", ".join(missing_data_plane_settings)
64
+ )
48
65
 
49
- # For now, this is only enabled for fully self-hosted customers
50
- # We will need to update the otel collector auth model to support hybrid customers
51
- if not config.LANGGRAPH_CLOUD_LICENSE_KEY:
66
+ # "data-plane" here refers to a BYOC data plane
67
+ is_data_plane = has_data_plane_settings
68
+ # For now, the non-data-plane path is only enabled for fully self-hosted customers.
69
+ if not is_data_plane and not config.LANGGRAPH_CLOUD_LICENSE_KEY:
52
70
  logger.warning(
53
71
  "Self-hosted logs require a license key, and do not work with hybrid deployments yet."
54
72
  )
@@ -58,6 +76,13 @@ def initialize_self_hosted_logs() -> None:
58
76
  resource_attributes = {
59
77
  SERVICE_NAME: config.SELF_HOSTED_OBSERVABILITY_SERVICE_NAME,
60
78
  }
79
+ if is_data_plane:
80
+ resource_attributes["data_plane_id"] = cast("str", config.DATA_PLANE_ID)
81
+ resource_attributes["organization_id"] = cast(
82
+ "str", config.DATA_PLANE_ORG_ID
83
+ )
84
+ resource_attributes["host"] = config.BYOC_HOST
85
+ resource_attributes["env"] = config.BYOC_ENV
61
86
 
62
87
  if config.LANGGRAPH_CLOUD_LICENSE_KEY:
63
88
  try:
@@ -70,43 +95,47 @@ def initialize_self_hosted_logs() -> None:
70
95
  _customer_attributes["customer_id"] = CUSTOMER_ID
71
96
  if CUSTOMER_NAME:
72
97
  _customer_attributes["customer_name"] = CUSTOMER_NAME
73
-
74
- # resolves to pod name in k8s, or container id in docker
75
- instance_id = os.environ.get("HOSTNAME")
76
- if instance_id:
77
- _customer_attributes["instance_id"] = instance_id
78
98
  except ImportError:
79
99
  pass
80
100
  except Exception as e:
81
101
  logger.warning("Failed to get customer info from license", exc_info=e)
82
102
 
103
+ # Resolves to pod name in k8s, or container ID in Docker.
104
+ instance_id = os.environ.get("HOSTNAME")
105
+ if instance_id:
106
+ _customer_attributes["instance_id"] = instance_id
107
+
83
108
  if config.IS_QUEUE_ENTRYPOINT:
84
109
  _customer_attributes["entrypoint"] = "queue"
85
- elif config.IS_EXECUTOR_ENTRYPOINT:
86
- _customer_attributes["entrypoint"] = "executor"
87
110
  else:
88
111
  _customer_attributes["entrypoint"] = "api"
89
112
 
90
- _logger_provider = LoggerProvider(
91
- resource=Resource.create(resource_attributes),
92
- )
93
- _logger_provider.add_log_record_processor(
94
- BatchLogRecordProcessor(
95
- OTLPLogExporter(
96
- endpoint=config.LANGGRAPH_LOGS_ENDPOINT,
97
- headers={
98
- "X-Langchain-License-Key": config.LANGGRAPH_CLOUD_LICENSE_KEY,
99
- },
100
- )
113
+ if is_data_plane:
114
+ exporter = OTLPLogExporter(
115
+ endpoint=log_endpoint,
116
+ session=beacon_otel_session(
117
+ cast("str", config.DATA_PLANE_ID),
118
+ cast("str", config.DATA_PLANE_ORG_ID),
119
+ cast("str", config.DATA_PLANE_JWT_SECRET),
120
+ ),
101
121
  )
102
- )
122
+ else:
123
+ exporter = OTLPLogExporter(
124
+ endpoint=log_endpoint,
125
+ headers={
126
+ "X-Langchain-License-Key": config.LANGGRAPH_CLOUD_LICENSE_KEY,
127
+ },
128
+ )
129
+
130
+ _logger_provider = LoggerProvider(resource=Resource.create(resource_attributes))
131
+ _logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
103
132
  handler = AttrFilteredLoggingHandler(logger_provider=_logger_provider)
104
133
  handler.setFormatter(OTLPFormatter("%(message)s", None, "%"))
105
134
  logging.getLogger().addHandler(handler)
106
135
 
107
136
  logger.info(
108
137
  "Self-hosted logs initialized successfully",
109
- endpoint=config.LANGGRAPH_LOGS_ENDPOINT,
138
+ endpoint=log_endpoint,
110
139
  )
111
140
  except Exception as e:
112
141
  logger.exception("Failed to initialize self-hosted logs", exc_info=e)
langgraph_api/server.py CHANGED
@@ -30,6 +30,7 @@ from starlette.types import Receive, Scope, Send
30
30
 
31
31
  import langgraph_api.config as config
32
32
  from langgraph_api.api import (
33
+ custom_app_lifespan,
33
34
  middleware_for_protected_routes,
34
35
  protected_routes,
35
36
  shadowable_meta_routes,
@@ -234,9 +235,10 @@ if user_router:
234
235
  update_openapi_spec(app)
235
236
 
236
237
  # Merge lifespans (base + user)
237
- user_lifespan = app.router.lifespan_context
238
238
  validate_router_lifespan_hooks(app.router)
239
- app.router.lifespan_context = timing.combine_lifespans(lifespan, user_lifespan)
239
+ app.router.lifespan_context = timing.combine_lifespans(
240
+ lifespan, custom_app_lifespan
241
+ )
240
242
 
241
243
  # Merge exception handlers (base + user)
242
244
  for k, v in exception_handlers.items():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.14.0.dev4
3
+ Version: 0.14.0.dev6
4
4
  Author-email: Will Fu-Hinthorn <will@langchain.dev>, Josh Rogers <josh@langchain.dev>, Parker Rule <parker@langchain.dev>
5
5
  License: Elastic-2.0
6
6
  License-File: LICENSE
@@ -1,7 +1,8 @@
1
- langgraph_api/__init__.py,sha256=TRbvHLqUmdbeUp8WLcQvUajr8378KygoWAAegER8gcU,28
1
+ langgraph_api/__init__.py,sha256=5ZhhvR8DrRSoagQmnUmoCJhIF6VMGURhyWB6-ohDQmU,28
2
2
  langgraph_api/_factory_utils.py,sha256=_FqW0olMu2PGLsnRq0ylt3a4jDCe12YIFHExMXI6Nyo,5976
3
3
  langgraph_api/asgi_transport.py,sha256=XApY3lIWBZTMbbsl8dDJzl0cLGirmAGE0SifqZUnXvs,11896
4
4
  langgraph_api/asyncio.py,sha256=smqyAoO9nIyPPQRw1IVSCh85fwoZP4PEo-5dDhxGruc,10676
5
+ langgraph_api/beacon_data_plane_jwt_auth.py,sha256=SoX2M-_u7oas6zWyss2LBs83E6ynBCFOoD5vVJEHRSU,2297
5
6
  langgraph_api/cache.py,sha256=LuB3Te0UdXC8a-uEJoRHoe5XsgXulXI59f_Q6DCJNKc,13005
6
7
  langgraph_api/cli.py,sha256=ATtS9s9Cx7QNiGPJceKnMCko29A25ZA-xz39fdxmgfg,22389
7
8
  langgraph_api/command.py,sha256=d-k8h6H4ix1n7fSZ-Zb01NbSkEyqrD6cMKfDFXEIYEw,821
@@ -18,14 +19,14 @@ langgraph_api/metrics_collector.py,sha256=gMLHL18rJyYl985AOmu9eH7W1ttdRdkPHzeycz
18
19
  langgraph_api/metrics_otlp.py,sha256=YppnNCHmQBj7wZbtKYLJS68_tbD6sGTJXGKIySqUTjU,29657
19
20
  langgraph_api/otel_context.py,sha256=DWFwW4Yu88QY4W2J0IRcURR450Th9J2DupvDDkSkMBA,7166
20
21
  langgraph_api/patch.py,sha256=ViUknYvyQWS6y0f5XuaEoci2qB_mQv8vZl-oaUxsI6M,1448
21
- langgraph_api/queue_entrypoint.py,sha256=1pb7kUWLbc1jIElZ2KTronREFXh3Vq0Sx4_feZX-YxE,11610
22
+ langgraph_api/queue_entrypoint.py,sha256=Bh8YPWN_2Dj3hxnYE9T3w9Igc8bIq7UcT8dngOOX36k,11535
22
23
  langgraph_api/release_tags.py,sha256=Dd2L2Mdh7c5D7RhFvdGCXROLH3WI1QKXbEqgjITFFoQ,9437
23
24
  langgraph_api/route.py,sha256=_KE8A8Q-J-QfqjGlyM2Kc6n5cirmgt8xmI5-pI8kVEE,8837
24
25
  langgraph_api/schema.py,sha256=EkarLwAUKApevb03AmLRumrAVAc2EsG_SLhYFPncSzk,15318
25
- langgraph_api/self_hosted_logs.py,sha256=FoUkPdtpt-nuEhejne8o1Q2phE9CccoHdoR_PvXPcBU,4442
26
+ langgraph_api/self_hosted_logs.py,sha256=rzzr9iVwJgNbDHjrxvuTODCtzpdr9-9X6UAB-3OaXU0,5742
26
27
  langgraph_api/self_hosted_metrics.py,sha256=pWsQQ-2ukoFIbmVfzNOSkwCqZ5Cnts6pRSWTII44Ll4,16844
27
28
  langgraph_api/serde.py,sha256=V3fO9bkUOlBX3okw5Qi31nlcr59fcuXMgL7DHNyarZY,8855
28
- langgraph_api/server.py,sha256=1eAZPim0Pkgh5oGS4EvW-_7Zh_82iGOZtR1rpX08FoA,11216
29
+ langgraph_api/server.py,sha256=R0z4R6DjnRwsqjDOjWD-7R3nKoQcS6v7UriG1LuzA1o,11213
29
30
  langgraph_api/sse.py,sha256=cChZ7raQUHp8p5BreE_5wMBR8lFO0n7746sV8_HQOrc,4822
30
31
  langgraph_api/state.py,sha256=A6jx2OpcVmiFNBafjZ2tX1x6s_AhBk5EW2drd-vaqIc,4396
31
32
  langgraph_api/store.py,sha256=i-jVfWzDEmPESVofh1tNWbo0HMLiLz_BSKKV4QjN-dE,5027
@@ -37,10 +38,10 @@ langgraph_api/validation.py,sha256=4wBPaPbgZU3oysd5XSYi3RRQN7MXM_qIdiifSpnjl10,1
37
38
  langgraph_api/webhook.py,sha256=qXEtkE6orek2POeOQmPRsEarJgXIYp-LBrZB-OwITxc,9572
38
39
  langgraph_api/worker.py,sha256=zWd14iZ9m9TLb9yD_QwTMxHcfM8dd3bhKtM1lz86M80,21256
39
40
  langgraph_api/_checkpointer/__init__.py,sha256=ofJTJLGy7Hsuzhj-2dpfDvrDloM0BzlhTzvZOdR9K8U,2223
40
- langgraph_api/_checkpointer/_adapter.py,sha256=1jJi0vXoNKeCwNP26W8bmYrwe-fOWFqepnovYFt14W0,21782
41
+ langgraph_api/_checkpointer/_adapter.py,sha256=idGYL-FwyVtUXcmhvfYboARfQAkoj0aFD40L5h9N7F8,22289
41
42
  langgraph_api/_checkpointer/protocol.py,sha256=udgYKMNtKWG_eLDwYkHXV3b2bZLZg8Rsfm3fjkhU-rU,3635
42
- langgraph_api/api/__init__.py,sha256=Zu1ew3dxYZu7cLRAjn-6HcYmtuQBdihlVFMKMJ77Y3c,9269
43
- langgraph_api/api/a2a.py,sha256=l2nAmN5qHuYhpyi4ecvvPQDeXgpL3UJkTNe6_7f975k,113469
43
+ langgraph_api/api/__init__.py,sha256=88elgNmz6Y1tIi81dzhHa94KVhLvvIqyEJjcov4aH5U,9463
44
+ langgraph_api/api/a2a.py,sha256=yfVWREWZZYLUUsOEuT8dnTIyQsNIa7CrNfIlGQqfBLQ,113688
44
45
  langgraph_api/api/assistants.py,sha256=YelrvQWKNLIoqJheQ1riK2dhCTH3GenbmWyD5fEp-ok,21936
45
46
  langgraph_api/api/event_streaming.py,sha256=jvoBp5qHI92JJHsx4f9LEpb0i0VE31G_qFhS5JQWtlA,17809
46
47
  langgraph_api/api/meta.py,sha256=5s017GfMs-Ghq6Z5K6yW4UjbVFzCsmeXKn4UEJmfs98,4757
@@ -65,7 +66,7 @@ langgraph_api/auth/studio_user.py,sha256=gNCicIo6cYaHmFj2sEdsvDYkKW7NWfGXGS2tTAM
65
66
  langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
67
  langgraph_api/auth/langsmith/backend.py,sha256=Y6-VxD7zfV1jzGdjmQ66CgNa3SenLbo3d_375CcKZ9U,3770
67
68
  langgraph_api/auth/langsmith/client.py,sha256=79kwCVeHU64nsHsxWipfZhf44lM6vfs2nlfTxlJF6LU,4142
68
- langgraph_api/config/__init__.py,sha256=lAjul8uDI2uKU5RrxT_EGEoYS4SfGRYwb8G_3dR1MjA,26500
69
+ langgraph_api/config/__init__.py,sha256=4hZs8qHDt5Yx9Pr8bvPCsnf5BjAokrD4BLCvtGNbwkM,27403
69
70
  langgraph_api/config/_parse.py,sha256=RpfZgFAPJlRMv13AzGr3kAYbIrqHcgjzO8IgeboTw4A,3922
70
71
  langgraph_api/config/schemas.py,sha256=cHzVvepthpD7DDeWE2ytkOHah-iDNH7xRx9dSWUatQI,20864
71
72
  langgraph_api/encryption/__init__.py,sha256=gaCZ00CocSbqSqrDn6XJHaSp2CZCnC8qnrD9G4fbzyI,363
@@ -95,7 +96,7 @@ langgraph_api/grpc/ops/runs.py,sha256=4-Mp-4MgDAn8uerLrOpmXuUqOdfcnzSmb3hSRlO8Hh
95
96
  langgraph_api/grpc/ops/threads.py,sha256=SJraV838nZ0m6wRrCia7sLYh8KK1WfY7x5xmcspZLek,48140
96
97
  langgraph_api/grpc/servicers/__init__.py,sha256=l8yqTA_gMbIj0xHw5-RZYo0JbjL-EpFD8RxPMgW_-bA,422
97
98
  langgraph_api/grpc/servicers/checkpointer.py,sha256=aeB2hzsDxbb9i-ildx-kylpDxaYtpAUSG6rphOzbDOs,10482
98
- langgraph_api/grpc/servicers/encryption.py,sha256=_qpcrjURpnm7PvdWOronNt3FtmqR1mjwxUb5bWMj32I,12962
99
+ langgraph_api/grpc/servicers/encryption.py,sha256=UuBjpUK-tCbPXfx2-AFn0vbmeWw_UBe7xl8pMbwQdbA,15083
99
100
  langgraph_api/js/.gitignore,sha256=l5yI6G_V6F1600I1IjiUKn87f4uYIrBAYU1MOyBBhg4,59
100
101
  langgraph_api/js/.prettierrc,sha256=0es3ovvyNIqIw81rPQsdt1zCQcOdBqyR_DMbFE4Ifms,19
101
102
  langgraph_api/js/.yarnclean,sha256=a5Vf1gAk2gxjcNrGidXoA3URy1fI3Ow5F3zDuVQg5wQ,728
@@ -165,7 +166,7 @@ hatch_build.py,sha256=Nn9N-EFfVqmH5q3iCUrhqX6zc7qT1jLI3gWSBN2xgYQ,1738
165
166
  logging.json,sha256=sBy8HDDPuucpLbLUD6e8t5fsiQjJoklZsbVHi2qQ7aQ,367
166
167
  openapi.json,sha256=C009Hi2gZaUKU4OOf2P12Qdjodx1mxZd16RAm1lZiIw,219991
167
168
  langgraph_grpc_common/__init__.py,sha256=sO7jFHekQxt6zZPhTPZAWLYLB_S8w7-VuJ7NyWFlFNo,97
168
- langgraph_grpc_common/checkpointer.py,sha256=-rn9SwChijGHZQyWNHfK-WIMCqu3UCeo5ZKlOb6zAMg,14175
169
+ langgraph_grpc_common/checkpointer.py,sha256=-F51xCHjP3A2gWfwVTTm7x7w9YsH78qJnZ57zTYAwdI,15344
169
170
  langgraph_grpc_common/serde.py,sha256=8q290nZ6TdxbEhCTzwEs44XGi2b-3pfrUeMYY5WGlTY,6179
170
171
  langgraph_grpc_common/store.py,sha256=YCO8oUssjnFq-n1syBsA_wxkwo8Sp0bgnDrpYjUxv40,4030
171
172
  langgraph_grpc_common/conversion/__init__.py,sha256=pLsOrRtmF9YkgmCPhcfym6lYOB7PCJTRItyo1YK_AT8,208
@@ -177,18 +178,18 @@ langgraph_grpc_common/conversion/store.py,sha256=eQQ7YHrMYRsd1jN9LbqvCxGQGErySV_
177
178
  langgraph_grpc_common/conversion/struct.py,sha256=yk4jpPdywzn8UosAohun21lU5D_IIglOY9rkBhiG-gA,2072
178
179
  langgraph_grpc_common/conversion/value.py,sha256=KW-z0gvpvCq8iWUjjtx8F3XQGM7XsL-D9GzPzdtuMsA,10206
179
180
  langgraph_grpc_common/proto/__init__.py,sha256=eBejrrULiDdasi3_Hga_AJW8ZPMkjKyyAeW9Ey640Ks,1375
180
- langgraph_grpc_common/proto/checkpointer_pb2.py,sha256=8tjrdaG1VCOZFOhoz3n29CAG5T-1YKGnxVgDjEHCgms,7915
181
- langgraph_grpc_common/proto/checkpointer_pb2.pyi,sha256=rGUxQyNFifmu8OPy0S418sCWSBQY_CVckobPXtFfZfs,20763
181
+ langgraph_grpc_common/proto/checkpointer_pb2.py,sha256=dQMt9vNXqXDvTU6euvH52uliDsL44BSppJIsAySmPfc,9039
182
+ langgraph_grpc_common/proto/checkpointer_pb2.pyi,sha256=Jjr9-oeKGk1wuIFf8m7zoCb9lHty1Bh9u-jxvRvqYLI,23319
182
183
  langgraph_grpc_common/proto/checkpointer_pb2_grpc.py,sha256=FQJK-lC8alOkLltA51aq1ABam62rVerTZRZccTFqUjw,20152
183
184
  langgraph_grpc_common/proto/checkpointer_pb2_grpc.pyi,sha256=8fPL90WjHnMaREG7l9qYt5fQePeT8YFWQ0qVy56QtYw,10210
184
185
  langgraph_grpc_common/proto/core_api_pb2.py,sha256=r5KLcYAtjqTiKzv671Vwa6teYdsV9gHFkorK5HOs6Aw,51151
185
186
  langgraph_grpc_common/proto/core_api_pb2.pyi,sha256=sulKXZ_SD94mQKkz7zKHum1XGo3KDfFSnR09vkV15A0,208955
186
187
  langgraph_grpc_common/proto/core_api_pb2_grpc.py,sha256=bONBb3GptNvrgOSI4accjE_f9IA8xxbfarDs1Knrosg,81198
187
188
  langgraph_grpc_common/proto/core_api_pb2_grpc.pyi,sha256=XLu2-5YZHm9QyZWjWsnUWe7uN6n1-avVvsyR6N5D3fs,29325
188
- langgraph_grpc_common/proto/encryption_pb2.py,sha256=CNR6gn2boBIXMbFyOwiA11Ss6kbldPkN1PGnZqTyrjo,3355
189
- langgraph_grpc_common/proto/encryption_pb2.pyi,sha256=5RL8DYNT4B1y30Oo5dt1kVvIULOlehXkNiYRmiPwAeY,8264
190
- langgraph_grpc_common/proto/encryption_pb2_grpc.py,sha256=fXed_A8Iwzk8vJl-22SdoUq1HHXpqqwRNsSmD3P_3BM,5213
191
- langgraph_grpc_common/proto/encryption_pb2_grpc.pyi,sha256=WtMy-dZA1KtMwltz_UP8MuKAxRX-tRwE8UuJouAUh7o,2902
189
+ langgraph_grpc_common/proto/encryption_pb2.py,sha256=CEDsn3bJv2e_QzAFsvp2845Nac_lBRPGsb_9pFJthxE,4119
190
+ langgraph_grpc_common/proto/encryption_pb2.pyi,sha256=YWilpCKKzV9u2BA8i-Pq4gStaxhBgqo_fAxTLnreG40,10647
191
+ langgraph_grpc_common/proto/encryption_pb2_grpc.py,sha256=H05u5VP1bVgYCtiELRb44DOr0wlmA6w8WtMCuqAyYLk,8681
192
+ langgraph_grpc_common/proto/encryption_pb2_grpc.pyi,sha256=KJzuCnp3xKuy-hAwRNHZEF75DQ6scmiq_QMTDY-BSbY,4428
192
193
  langgraph_grpc_common/proto/engine_common_pb2.py,sha256=CdNIXXE924mrseYyOcD6x0O5zLcNeU8ihdiCxlamHH8,29198
193
194
  langgraph_grpc_common/proto/engine_common_pb2.pyi,sha256=BM3-HPagho_LA74pg4SAX1FV_Lnn6jXut2CKArFBu88,115071
194
195
  langgraph_grpc_common/proto/engine_common_pb2_grpc.py,sha256=dqNIakFHauOuu0B6VK7z_VRb6yuY-jdA11glk0IgdkM,893
@@ -237,8 +238,8 @@ langgraph_grpc_common/proto/store_pb2.py,sha256=thQ5HBLblZ7Adayp1U8ty_lrpn-7XEHn
237
238
  langgraph_grpc_common/proto/store_pb2.pyi,sha256=_dcxyxV8moR6H67qU42r2h2p_Ub-PZSHs3OoS9KMdSY,27782
238
239
  langgraph_grpc_common/proto/store_pb2_grpc.py,sha256=Kyt8eV2NXkjh3be_BYytGdD19kNf74961cruonMvCrw,3300
239
240
  langgraph_grpc_common/proto/store_pb2_grpc.pyi,sha256=oS1h2cDoq2OjyM8xazcmc8LwRDC5oCAD0zto2QUmPQw,2027
240
- langgraph_api-0.14.0.dev4.dist-info/METADATA,sha256=PfmnlH5UDtzwQGa8uLrV7Y1mBQZTmHAIjSoU6QmdhBg,4630
241
- langgraph_api-0.14.0.dev4.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
242
- langgraph_api-0.14.0.dev4.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
243
- langgraph_api-0.14.0.dev4.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
244
- langgraph_api-0.14.0.dev4.dist-info/RECORD,,
241
+ langgraph_api-0.14.0.dev6.dist-info/METADATA,sha256=Y8KYhDNzjBif5sHNqYkfnqy9YrbQmUcKERm8yBnfYeQ,4630
242
+ langgraph_api-0.14.0.dev6.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
243
+ langgraph_api-0.14.0.dev6.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
244
+ langgraph_api-0.14.0.dev6.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
245
+ langgraph_api-0.14.0.dev6.dist-info/RECORD,,
@@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, Any, TypeVar, cast
16
16
 
17
17
  import grpc
18
18
  import grpc.aio
19
+ import orjson
19
20
  from langgraph.checkpoint.base import (
20
21
  BaseCheckpointSaver,
21
22
  ChannelVersions,
@@ -43,6 +44,7 @@ if TYPE_CHECKING:
43
44
  T = TypeVar("T")
44
45
  RetryFn = Callable[[Callable[[], Awaitable[Any]], str], Awaitable[Any]]
45
46
  StubProvider = Callable[[], Awaitable["CheckpointerStub"]]
47
+ EncryptionContextProvider = Callable[[], Mapping[str, Any]]
46
48
 
47
49
 
48
50
  class GrpcCheckpointer(BaseCheckpointSaver):
@@ -57,6 +59,7 @@ class GrpcCheckpointer(BaseCheckpointSaver):
57
59
  retry: RetryFn | None = None,
58
60
  retry_context_prefix: str | None = None,
59
61
  serializer: SerializerProtocol | None = None,
62
+ encryption_context_provider: EncryptionContextProvider | None = None,
60
63
  ) -> None:
61
64
  """Construct a gRPC checkpointer client.
62
65
 
@@ -74,18 +77,34 @@ class GrpcCheckpointer(BaseCheckpointSaver):
74
77
  so unrelated gRPC ops sharing
75
78
  ``langgraph_grpc_common.conversion.*`` keep using the
76
79
  process-wide default
80
+ encryption_context_provider: Optional callable returning the
81
+ request-scoped encryption context, sent with writes so the
82
+ server can encrypt blobs with the caller's context. Injected
83
+ rather than read directly because the context lives in a
84
+ ``ContextVar`` owned by ``langgraph_api``, which this package
85
+ does not depend on.
77
86
  """
78
87
  super().__init__(serde=None)
79
88
  self._get_stub = get_stub
80
89
  self._retry = retry
81
90
  self._retry_context_prefix = retry_context_prefix or type(self).__name__
82
91
  self._serializer = serializer
92
+ self._encryption_context_provider = encryption_context_provider
83
93
  self.latest_iter = None
84
94
  try:
85
95
  self._loop: asyncio.AbstractEventLoop | None = asyncio.get_running_loop()
86
96
  except RuntimeError:
87
97
  self._loop = None
88
98
 
99
+ def _encryption_context_json(self) -> dict[str, bytes]:
100
+ """Return encryption context as JSON-encoded proto map values."""
101
+ if self._encryption_context_provider is None:
102
+ return {}
103
+ context = self._encryption_context_provider()
104
+ if not context:
105
+ return {}
106
+ return {key: orjson.dumps(value) for key, value in context.items()}
107
+
89
108
  def _scoped(self) -> AbstractContextManager[None]:
90
109
  """Bind ``self._serializer`` for the duration of a conversion call."""
91
110
  if self._serializer is None:
@@ -168,6 +187,7 @@ class GrpcCheckpointer(BaseCheckpointSaver):
168
187
  checkpoint=ckpt_conv.checkpoint_to_proto(checkpoint),
169
188
  metadata=ckpt_conv.checkpoint_metadata_to_proto(metadata),
170
189
  new_versions={k: str(v) for k, v in new_versions.items()},
190
+ encryption_context_json=self._encryption_context_json(),
171
191
  )
172
192
 
173
193
  async def _request() -> RunnableConfig:
@@ -202,6 +222,7 @@ class GrpcCheckpointer(BaseCheckpointSaver):
202
222
  writes=ckpt_conv.writes_to_proto(writes),
203
223
  task_id=task_id,
204
224
  task_path=task_path,
225
+ encryption_context_json=self._encryption_context_json(),
205
226
  )
206
227
 
207
228
  async def _request() -> None:
@@ -26,7 +26,7 @@ from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
26
26
  from . import engine_common_pb2 as engine__common__pb2
27
27
 
28
28
 
29
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12\x63heckpointer.proto\x12\x0c\x63heckpointer\x1a\x1bgoogle/protobuf/empty.proto\x1a\x13\x65ngine-common.proto\"\x97\x02\n\nPutRequest\x12\x32\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\x12,\n\ncheckpoint\x18\x02 \x01(\x0b\x32\x18.engineCommon.Checkpoint\x12\x32\n\x08metadata\x18\x03 \x01(\x0b\x32 .engineCommon.CheckpointMetadata\x12?\n\x0cnew_versions\x18\x04 \x03(\x0b\x32).checkpointer.PutRequest.NewVersionsEntry\x1a\x32\n\x10NewVersionsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x8f\x01\n\x10PutWritesRequest\x12\x32\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\x12#\n\x06writes\x18\x02 \x03(\x0b\x32\x13.engineCommon.Write\x12\x0f\n\x07task_id\x18\x03 \x01(\t\x12\x11\n\ttask_path\x18\x04 \x01(\t\"\x86\x01\n\x0c\x43\x61pabilities\x12\x1e\n\x16supports_delete_thread\x18\x01 \x01(\x08\x12\x16\n\x0esupports_prune\x18\x02 \x01(\x08\x12 \n\x18supports_delete_for_runs\x18\x03 \x01(\x08\x12\x1c\n\x14supports_copy_thread\x18\x04 \x01(\x08\"\xa8\x01\n\x0bListRequest\x12\x32\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\x12\x13\n\x0b\x66ilter_json\x18\x02 \x01(\x0c\x12\x32\n\x06\x62\x65\x66ore\x18\x03 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\x12\x12\n\x05limit\x18\x04 \x01(\x03H\x00\x88\x01\x01\x42\x08\n\x06_limit\"(\n\x13\x44\x65leteThreadRequest\x12\x11\n\tthread_id\x18\x01 \x01(\t\"\'\n\x14\x44\x65leteForRunsRequest\x12\x0f\n\x07run_ids\x18\x01 \x03(\t\"A\n\x11\x43opyThreadRequest\x12\x16\n\x0e\x66rom_thread_id\x18\x01 \x01(\t\x12\x14\n\x0cto_thread_id\x18\x02 \x01(\t\"\xa1\x01\n\x0cPruneRequest\x12\x12\n\nthread_ids\x18\x01 \x03(\t\x12:\n\x08strategy\x18\x02 \x01(\x0e\x32(.checkpointer.PruneRequest.PruneStrategy\"A\n\rPruneStrategy\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x0f\n\x0bKEEP_LATEST\x10\x01\x12\x0e\n\nDELETE_ALL\x10\x02\"E\n\x0fGetTupleRequest\x12\x32\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\"F\n\x0bPutResponse\x12\x37\n\x0bnext_config\x18\x01 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\"H\n\x0cListResponse\x12\x38\n\x11\x63heckpoint_tuples\x18\x01 \x03(\x0b\x32\x1d.engineCommon.CheckpointTuple\"e\n\x10GetTupleResponse\x12<\n\x10\x63heckpoint_tuple\x18\x01 \x01(\x0b\x32\x1d.engineCommon.CheckpointTupleH\x00\x88\x01\x01\x42\x13\n\x11_checkpoint_tuple\"~\n\x18\x44\x65ltaChannelHistoryEntry\x12-\n\x04seed\x18\x01 \x01(\x0b\x32\x1a.engineCommon.ChannelValueH\x00\x88\x01\x01\x12*\n\x06writes\x18\x02 \x03(\x0b\x32\x1a.engineCommon.PendingWriteB\x07\n\x05_seed\"r\n\x1dGetDeltaChannelHistoryRequest\x12\x11\n\tthread_id\x18\x01 \x01(\t\x12\x15\n\rcheckpoint_ns\x18\x02 \x01(\t\x12\x15\n\rcheckpoint_id\x18\x03 \x01(\t\x12\x10\n\x08\x63hannels\x18\x04 \x03(\t\"\xc4\x01\n\x1eGetDeltaChannelHistoryResponse\x12J\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x39.checkpointer.GetDeltaChannelHistoryResponse.EntriesEntry\x1aV\n\x0c\x45ntriesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x35\n\x05value\x18\x02 \x01(\x0b\x32&.checkpointer.DeltaChannelHistoryEntry:\x02\x38\x01\x32\xf1\x05\n\x0c\x43heckpointer\x12:\n\x03Put\x12\x18.checkpointer.PutRequest\x1a\x19.checkpointer.PutResponse\x12\x43\n\tPutWrites\x12\x1e.checkpointer.PutWritesRequest\x1a\x16.google.protobuf.Empty\x12\x45\n\x0fGetCapabilities\x12\x16.google.protobuf.Empty\x1a\x1a.checkpointer.Capabilities\x12=\n\x04List\x12\x19.checkpointer.ListRequest\x1a\x1a.checkpointer.ListResponse\x12I\n\x08GetTuple\x12\x1d.checkpointer.GetTupleRequest\x1a\x1e.checkpointer.GetTupleResponse\x12I\n\x0c\x44\x65leteThread\x12!.checkpointer.DeleteThreadRequest\x1a\x16.google.protobuf.Empty\x12K\n\rDeleteForRuns\x12\".checkpointer.DeleteForRunsRequest\x1a\x16.google.protobuf.Empty\x12\x45\n\nCopyThread\x12\x1f.checkpointer.CopyThreadRequest\x1a\x16.google.protobuf.Empty\x12;\n\x05Prune\x12\x1a.checkpointer.PruneRequest\x1a\x16.google.protobuf.Empty\x12s\n\x16GetDeltaChannelHistory\x12+.checkpointer.GetDeltaChannelHistoryRequest\x1a,.checkpointer.GetDeltaChannelHistoryResponseB?Z=github.com/langchain-ai/langgraph-api/core/internal/engine/pbb\x06proto3')
29
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12\x63heckpointer.proto\x12\x0c\x63heckpointer\x1a\x1bgoogle/protobuf/empty.proto\x1a\x13\x65ngine-common.proto\"\xab\x03\n\nPutRequest\x12\x32\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\x12,\n\ncheckpoint\x18\x02 \x01(\x0b\x32\x18.engineCommon.Checkpoint\x12\x32\n\x08metadata\x18\x03 \x01(\x0b\x32 .engineCommon.CheckpointMetadata\x12?\n\x0cnew_versions\x18\x04 \x03(\x0b\x32).checkpointer.PutRequest.NewVersionsEntry\x12T\n\x17\x65ncryption_context_json\x18\x05 \x03(\x0b\x32\x33.checkpointer.PutRequest.EncryptionContextJsonEntry\x1a\x32\n\x10NewVersionsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a<\n\x1a\x45ncryptionContextJsonEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"\xa9\x02\n\x10PutWritesRequest\x12\x32\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\x12#\n\x06writes\x18\x02 \x03(\x0b\x32\x13.engineCommon.Write\x12\x0f\n\x07task_id\x18\x03 \x01(\t\x12\x11\n\ttask_path\x18\x04 \x01(\t\x12Z\n\x17\x65ncryption_context_json\x18\x05 \x03(\x0b\x32\x39.checkpointer.PutWritesRequest.EncryptionContextJsonEntry\x1a<\n\x1a\x45ncryptionContextJsonEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"\x86\x01\n\x0c\x43\x61pabilities\x12\x1e\n\x16supports_delete_thread\x18\x01 \x01(\x08\x12\x16\n\x0esupports_prune\x18\x02 \x01(\x08\x12 \n\x18supports_delete_for_runs\x18\x03 \x01(\x08\x12\x1c\n\x14supports_copy_thread\x18\x04 \x01(\x08\"\xa8\x01\n\x0bListRequest\x12\x32\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\x12\x13\n\x0b\x66ilter_json\x18\x02 \x01(\x0c\x12\x32\n\x06\x62\x65\x66ore\x18\x03 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\x12\x12\n\x05limit\x18\x04 \x01(\x03H\x00\x88\x01\x01\x42\x08\n\x06_limit\"(\n\x13\x44\x65leteThreadRequest\x12\x11\n\tthread_id\x18\x01 \x01(\t\"\'\n\x14\x44\x65leteForRunsRequest\x12\x0f\n\x07run_ids\x18\x01 \x03(\t\"A\n\x11\x43opyThreadRequest\x12\x16\n\x0e\x66rom_thread_id\x18\x01 \x01(\t\x12\x14\n\x0cto_thread_id\x18\x02 \x01(\t\"\xa1\x01\n\x0cPruneRequest\x12\x12\n\nthread_ids\x18\x01 \x03(\t\x12:\n\x08strategy\x18\x02 \x01(\x0e\x32(.checkpointer.PruneRequest.PruneStrategy\"A\n\rPruneStrategy\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x0f\n\x0bKEEP_LATEST\x10\x01\x12\x0e\n\nDELETE_ALL\x10\x02\"E\n\x0fGetTupleRequest\x12\x32\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\"F\n\x0bPutResponse\x12\x37\n\x0bnext_config\x18\x01 \x01(\x0b\x32\".engineCommon.EngineRunnableConfig\"H\n\x0cListResponse\x12\x38\n\x11\x63heckpoint_tuples\x18\x01 \x03(\x0b\x32\x1d.engineCommon.CheckpointTuple\"e\n\x10GetTupleResponse\x12<\n\x10\x63heckpoint_tuple\x18\x01 \x01(\x0b\x32\x1d.engineCommon.CheckpointTupleH\x00\x88\x01\x01\x42\x13\n\x11_checkpoint_tuple\"~\n\x18\x44\x65ltaChannelHistoryEntry\x12-\n\x04seed\x18\x01 \x01(\x0b\x32\x1a.engineCommon.ChannelValueH\x00\x88\x01\x01\x12*\n\x06writes\x18\x02 \x03(\x0b\x32\x1a.engineCommon.PendingWriteB\x07\n\x05_seed\"r\n\x1dGetDeltaChannelHistoryRequest\x12\x11\n\tthread_id\x18\x01 \x01(\t\x12\x15\n\rcheckpoint_ns\x18\x02 \x01(\t\x12\x15\n\rcheckpoint_id\x18\x03 \x01(\t\x12\x10\n\x08\x63hannels\x18\x04 \x03(\t\"\xc4\x01\n\x1eGetDeltaChannelHistoryResponse\x12J\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x39.checkpointer.GetDeltaChannelHistoryResponse.EntriesEntry\x1aV\n\x0c\x45ntriesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x35\n\x05value\x18\x02 \x01(\x0b\x32&.checkpointer.DeltaChannelHistoryEntry:\x02\x38\x01\x32\xf1\x05\n\x0c\x43heckpointer\x12:\n\x03Put\x12\x18.checkpointer.PutRequest\x1a\x19.checkpointer.PutResponse\x12\x43\n\tPutWrites\x12\x1e.checkpointer.PutWritesRequest\x1a\x16.google.protobuf.Empty\x12\x45\n\x0fGetCapabilities\x12\x16.google.protobuf.Empty\x1a\x1a.checkpointer.Capabilities\x12=\n\x04List\x12\x19.checkpointer.ListRequest\x1a\x1a.checkpointer.ListResponse\x12I\n\x08GetTuple\x12\x1d.checkpointer.GetTupleRequest\x1a\x1e.checkpointer.GetTupleResponse\x12I\n\x0c\x44\x65leteThread\x12!.checkpointer.DeleteThreadRequest\x1a\x16.google.protobuf.Empty\x12K\n\rDeleteForRuns\x12\".checkpointer.DeleteForRunsRequest\x1a\x16.google.protobuf.Empty\x12\x45\n\nCopyThread\x12\x1f.checkpointer.CopyThreadRequest\x1a\x16.google.protobuf.Empty\x12;\n\x05Prune\x12\x1a.checkpointer.PruneRequest\x1a\x16.google.protobuf.Empty\x12s\n\x16GetDeltaChannelHistory\x12+.checkpointer.GetDeltaChannelHistoryRequest\x1a,.checkpointer.GetDeltaChannelHistoryResponseB?Z=github.com/langchain-ai/langgraph-api/core/internal/engine/pbb\x06proto3')
30
30
 
31
31
  _globals = globals()
32
32
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -36,44 +36,52 @@ if not _descriptor._USE_C_DESCRIPTORS:
36
36
  _globals['DESCRIPTOR']._serialized_options = b'Z=github.com/langchain-ai/langgraph-api/core/internal/engine/pb'
37
37
  _globals['_PUTREQUEST_NEWVERSIONSENTRY']._loaded_options = None
38
38
  _globals['_PUTREQUEST_NEWVERSIONSENTRY']._serialized_options = b'8\001'
39
+ _globals['_PUTREQUEST_ENCRYPTIONCONTEXTJSONENTRY']._loaded_options = None
40
+ _globals['_PUTREQUEST_ENCRYPTIONCONTEXTJSONENTRY']._serialized_options = b'8\001'
41
+ _globals['_PUTWRITESREQUEST_ENCRYPTIONCONTEXTJSONENTRY']._loaded_options = None
42
+ _globals['_PUTWRITESREQUEST_ENCRYPTIONCONTEXTJSONENTRY']._serialized_options = b'8\001'
39
43
  _globals['_GETDELTACHANNELHISTORYRESPONSE_ENTRIESENTRY']._loaded_options = None
40
44
  _globals['_GETDELTACHANNELHISTORYRESPONSE_ENTRIESENTRY']._serialized_options = b'8\001'
41
45
  _globals['_PUTREQUEST']._serialized_start=87
42
- _globals['_PUTREQUEST']._serialized_end=366
43
- _globals['_PUTREQUEST_NEWVERSIONSENTRY']._serialized_start=316
44
- _globals['_PUTREQUEST_NEWVERSIONSENTRY']._serialized_end=366
45
- _globals['_PUTWRITESREQUEST']._serialized_start=369
46
- _globals['_PUTWRITESREQUEST']._serialized_end=512
47
- _globals['_CAPABILITIES']._serialized_start=515
48
- _globals['_CAPABILITIES']._serialized_end=649
49
- _globals['_LISTREQUEST']._serialized_start=652
50
- _globals['_LISTREQUEST']._serialized_end=820
51
- _globals['_DELETETHREADREQUEST']._serialized_start=822
52
- _globals['_DELETETHREADREQUEST']._serialized_end=862
53
- _globals['_DELETEFORRUNSREQUEST']._serialized_start=864
54
- _globals['_DELETEFORRUNSREQUEST']._serialized_end=903
55
- _globals['_COPYTHREADREQUEST']._serialized_start=905
56
- _globals['_COPYTHREADREQUEST']._serialized_end=970
57
- _globals['_PRUNEREQUEST']._serialized_start=973
58
- _globals['_PRUNEREQUEST']._serialized_end=1134
59
- _globals['_PRUNEREQUEST_PRUNESTRATEGY']._serialized_start=1069
60
- _globals['_PRUNEREQUEST_PRUNESTRATEGY']._serialized_end=1134
61
- _globals['_GETTUPLEREQUEST']._serialized_start=1136
62
- _globals['_GETTUPLEREQUEST']._serialized_end=1205
63
- _globals['_PUTRESPONSE']._serialized_start=1207
64
- _globals['_PUTRESPONSE']._serialized_end=1277
65
- _globals['_LISTRESPONSE']._serialized_start=1279
66
- _globals['_LISTRESPONSE']._serialized_end=1351
67
- _globals['_GETTUPLERESPONSE']._serialized_start=1353
68
- _globals['_GETTUPLERESPONSE']._serialized_end=1454
69
- _globals['_DELTACHANNELHISTORYENTRY']._serialized_start=1456
70
- _globals['_DELTACHANNELHISTORYENTRY']._serialized_end=1582
71
- _globals['_GETDELTACHANNELHISTORYREQUEST']._serialized_start=1584
72
- _globals['_GETDELTACHANNELHISTORYREQUEST']._serialized_end=1698
73
- _globals['_GETDELTACHANNELHISTORYRESPONSE']._serialized_start=1701
74
- _globals['_GETDELTACHANNELHISTORYRESPONSE']._serialized_end=1897
75
- _globals['_GETDELTACHANNELHISTORYRESPONSE_ENTRIESENTRY']._serialized_start=1811
76
- _globals['_GETDELTACHANNELHISTORYRESPONSE_ENTRIESENTRY']._serialized_end=1897
77
- _globals['_CHECKPOINTER']._serialized_start=1900
78
- _globals['_CHECKPOINTER']._serialized_end=2653
46
+ _globals['_PUTREQUEST']._serialized_end=514
47
+ _globals['_PUTREQUEST_NEWVERSIONSENTRY']._serialized_start=402
48
+ _globals['_PUTREQUEST_NEWVERSIONSENTRY']._serialized_end=452
49
+ _globals['_PUTREQUEST_ENCRYPTIONCONTEXTJSONENTRY']._serialized_start=454
50
+ _globals['_PUTREQUEST_ENCRYPTIONCONTEXTJSONENTRY']._serialized_end=514
51
+ _globals['_PUTWRITESREQUEST']._serialized_start=517
52
+ _globals['_PUTWRITESREQUEST']._serialized_end=814
53
+ _globals['_PUTWRITESREQUEST_ENCRYPTIONCONTEXTJSONENTRY']._serialized_start=454
54
+ _globals['_PUTWRITESREQUEST_ENCRYPTIONCONTEXTJSONENTRY']._serialized_end=514
55
+ _globals['_CAPABILITIES']._serialized_start=817
56
+ _globals['_CAPABILITIES']._serialized_end=951
57
+ _globals['_LISTREQUEST']._serialized_start=954
58
+ _globals['_LISTREQUEST']._serialized_end=1122
59
+ _globals['_DELETETHREADREQUEST']._serialized_start=1124
60
+ _globals['_DELETETHREADREQUEST']._serialized_end=1164
61
+ _globals['_DELETEFORRUNSREQUEST']._serialized_start=1166
62
+ _globals['_DELETEFORRUNSREQUEST']._serialized_end=1205
63
+ _globals['_COPYTHREADREQUEST']._serialized_start=1207
64
+ _globals['_COPYTHREADREQUEST']._serialized_end=1272
65
+ _globals['_PRUNEREQUEST']._serialized_start=1275
66
+ _globals['_PRUNEREQUEST']._serialized_end=1436
67
+ _globals['_PRUNEREQUEST_PRUNESTRATEGY']._serialized_start=1371
68
+ _globals['_PRUNEREQUEST_PRUNESTRATEGY']._serialized_end=1436
69
+ _globals['_GETTUPLEREQUEST']._serialized_start=1438
70
+ _globals['_GETTUPLEREQUEST']._serialized_end=1507
71
+ _globals['_PUTRESPONSE']._serialized_start=1509
72
+ _globals['_PUTRESPONSE']._serialized_end=1579
73
+ _globals['_LISTRESPONSE']._serialized_start=1581
74
+ _globals['_LISTRESPONSE']._serialized_end=1653
75
+ _globals['_GETTUPLERESPONSE']._serialized_start=1655
76
+ _globals['_GETTUPLERESPONSE']._serialized_end=1756
77
+ _globals['_DELTACHANNELHISTORYENTRY']._serialized_start=1758
78
+ _globals['_DELTACHANNELHISTORYENTRY']._serialized_end=1884
79
+ _globals['_GETDELTACHANNELHISTORYREQUEST']._serialized_start=1886
80
+ _globals['_GETDELTACHANNELHISTORYREQUEST']._serialized_end=2000
81
+ _globals['_GETDELTACHANNELHISTORYRESPONSE']._serialized_start=2003
82
+ _globals['_GETDELTACHANNELHISTORYRESPONSE']._serialized_end=2199
83
+ _globals['_GETDELTACHANNELHISTORYRESPONSE_ENTRIESENTRY']._serialized_start=2113
84
+ _globals['_GETDELTACHANNELHISTORYRESPONSE_ENTRIESENTRY']._serialized_end=2199
85
+ _globals['_CHECKPOINTER']._serialized_start=2202
86
+ _globals['_CHECKPOINTER']._serialized_end=2955
79
87
  # @@protoc_insertion_point(module_scope)
@@ -22,6 +22,12 @@ DESCRIPTOR: _descriptor.FileDescriptor
22
22
 
23
23
  @_typing.final
24
24
  class PutRequest(_message.Message):
25
+ """encryption_context_json: request-scoped encryption context, keyed by name
26
+ with JSON-encoded values (e.g. {"tenant_id": "\\"acme\\""}). Writes must pass
27
+ it because it lives in a Python ContextVar the core server cannot observe;
28
+ reads recover it from the stored blob wrapper.
29
+ """
30
+
25
31
  DESCRIPTOR: _descriptor.Descriptor
26
32
 
27
33
  @_typing.final
@@ -44,10 +50,31 @@ class PutRequest(_message.Message):
44
50
  def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
45
51
  def WhichOneof(self, oneof_group: _Never) -> None: ...
46
52
 
53
+ @_typing.final
54
+ class EncryptionContextJsonEntry(_message.Message):
55
+ DESCRIPTOR: _descriptor.Descriptor
56
+
57
+ KEY_FIELD_NUMBER: _builtins.int
58
+ VALUE_FIELD_NUMBER: _builtins.int
59
+ key: _builtins.str
60
+ value: _builtins.bytes
61
+ def __init__(
62
+ self,
63
+ *,
64
+ key: _builtins.str = ...,
65
+ value: _builtins.bytes = ...,
66
+ ) -> None: ...
67
+ _HasFieldArgType: _TypeAlias = _Never # noqa: Y015
68
+ def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ...
69
+ _ClearFieldArgType: _TypeAlias = _typing.Literal["key", b"key", "value", b"value"] # noqa: Y015
70
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
71
+ def WhichOneof(self, oneof_group: _Never) -> None: ...
72
+
47
73
  CONFIG_FIELD_NUMBER: _builtins.int
48
74
  CHECKPOINT_FIELD_NUMBER: _builtins.int
49
75
  METADATA_FIELD_NUMBER: _builtins.int
50
76
  NEW_VERSIONS_FIELD_NUMBER: _builtins.int
77
+ ENCRYPTION_CONTEXT_JSON_FIELD_NUMBER: _builtins.int
51
78
  @_builtins.property
52
79
  def config(self) -> _engine_common_pb2.EngineRunnableConfig: ...
53
80
  @_builtins.property
@@ -56,6 +83,8 @@ class PutRequest(_message.Message):
56
83
  def metadata(self) -> _engine_common_pb2.CheckpointMetadata: ...
57
84
  @_builtins.property
58
85
  def new_versions(self) -> _containers.ScalarMap[_builtins.str, _builtins.str]: ...
86
+ @_builtins.property
87
+ def encryption_context_json(self) -> _containers.ScalarMap[_builtins.str, _builtins.bytes]: ...
59
88
  def __init__(
60
89
  self,
61
90
  *,
@@ -63,10 +92,11 @@ class PutRequest(_message.Message):
63
92
  checkpoint: _engine_common_pb2.Checkpoint | None = ...,
64
93
  metadata: _engine_common_pb2.CheckpointMetadata | None = ...,
65
94
  new_versions: _abc.Mapping[_builtins.str, _builtins.str] | None = ...,
95
+ encryption_context_json: _abc.Mapping[_builtins.str, _builtins.bytes] | None = ...,
66
96
  ) -> None: ...
67
97
  _HasFieldArgType: _TypeAlias = _typing.Literal["checkpoint", b"checkpoint", "config", b"config", "metadata", b"metadata"] # noqa: Y015
68
98
  def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ...
69
- _ClearFieldArgType: _TypeAlias = _typing.Literal["checkpoint", b"checkpoint", "config", b"config", "metadata", b"metadata", "new_versions", b"new_versions"] # noqa: Y015
99
+ _ClearFieldArgType: _TypeAlias = _typing.Literal["checkpoint", b"checkpoint", "config", b"config", "encryption_context_json", b"encryption_context_json", "metadata", b"metadata", "new_versions", b"new_versions"] # noqa: Y015
70
100
  def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
71
101
  def WhichOneof(self, oneof_group: _Never) -> None: ...
72
102
 
@@ -76,16 +106,39 @@ Global___PutRequest: _TypeAlias = PutRequest # noqa: Y015
76
106
  class PutWritesRequest(_message.Message):
77
107
  DESCRIPTOR: _descriptor.Descriptor
78
108
 
109
+ @_typing.final
110
+ class EncryptionContextJsonEntry(_message.Message):
111
+ DESCRIPTOR: _descriptor.Descriptor
112
+
113
+ KEY_FIELD_NUMBER: _builtins.int
114
+ VALUE_FIELD_NUMBER: _builtins.int
115
+ key: _builtins.str
116
+ value: _builtins.bytes
117
+ def __init__(
118
+ self,
119
+ *,
120
+ key: _builtins.str = ...,
121
+ value: _builtins.bytes = ...,
122
+ ) -> None: ...
123
+ _HasFieldArgType: _TypeAlias = _Never # noqa: Y015
124
+ def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ...
125
+ _ClearFieldArgType: _TypeAlias = _typing.Literal["key", b"key", "value", b"value"] # noqa: Y015
126
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
127
+ def WhichOneof(self, oneof_group: _Never) -> None: ...
128
+
79
129
  CONFIG_FIELD_NUMBER: _builtins.int
80
130
  WRITES_FIELD_NUMBER: _builtins.int
81
131
  TASK_ID_FIELD_NUMBER: _builtins.int
82
132
  TASK_PATH_FIELD_NUMBER: _builtins.int
133
+ ENCRYPTION_CONTEXT_JSON_FIELD_NUMBER: _builtins.int
83
134
  task_id: _builtins.str
84
135
  task_path: _builtins.str
85
136
  @_builtins.property
86
137
  def config(self) -> _engine_common_pb2.EngineRunnableConfig: ...
87
138
  @_builtins.property
88
139
  def writes(self) -> _containers.RepeatedCompositeFieldContainer[_engine_common_pb2.Write]: ...
140
+ @_builtins.property
141
+ def encryption_context_json(self) -> _containers.ScalarMap[_builtins.str, _builtins.bytes]: ...
89
142
  def __init__(
90
143
  self,
91
144
  *,
@@ -93,10 +146,11 @@ class PutWritesRequest(_message.Message):
93
146
  writes: _abc.Iterable[_engine_common_pb2.Write] | None = ...,
94
147
  task_id: _builtins.str = ...,
95
148
  task_path: _builtins.str = ...,
149
+ encryption_context_json: _abc.Mapping[_builtins.str, _builtins.bytes] | None = ...,
96
150
  ) -> None: ...
97
151
  _HasFieldArgType: _TypeAlias = _typing.Literal["config", b"config"] # noqa: Y015
98
152
  def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ...
99
- _ClearFieldArgType: _TypeAlias = _typing.Literal["config", b"config", "task_id", b"task_id", "task_path", b"task_path", "writes", b"writes"] # noqa: Y015
153
+ _ClearFieldArgType: _TypeAlias = _typing.Literal["config", b"config", "encryption_context_json", b"encryption_context_json", "task_id", b"task_id", "task_path", b"task_path", "writes", b"writes"] # noqa: Y015
100
154
  def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
101
155
  def WhichOneof(self, oneof_group: _Never) -> None: ...
102
156
 
@@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
24
24
 
25
25
 
26
26
 
27
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x65ncryption.proto\x12\nencryption\"\xbf\x01\n\x11\x45ncryptionContext\x12\x12\n\x05model\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05\x66ield\x18\x02 \x01(\tH\x01\x88\x01\x01\x12=\n\x08metadata\x18\x03 \x03(\x0b\x32+.encryption.EncryptionContext.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x42\x08\n\x06_modelB\x08\n\x06_field\"R\n\x12\x45ncryptJSONRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12.\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x1d.encryption.EncryptionContext\"^\n\x12\x44\x65\x63ryptJSONRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x12\n\x05\x66ield\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05model\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x08\n\x06_fieldB\x08\n\x06_model\"\x1f\n\x0f\x45ncryptResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x1f\n\x0f\x44\x65\x63ryptResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x32\xa4\x01\n\nEncryption\x12J\n\x0b\x45ncryptJSON\x12\x1e.encryption.EncryptJSONRequest\x1a\x1b.encryption.EncryptResponse\x12J\n\x0b\x44\x65\x63ryptJSON\x12\x1e.encryption.DecryptJSONRequest\x1a\x1b.encryption.DecryptResponseBAZ?github.com/langchain-ai/langgraph-api/core/internal/core-api/pbb\x06proto3')
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x65ncryption.proto\x12\nencryption\"\xbf\x01\n\x11\x45ncryptionContext\x12\x12\n\x05model\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05\x66ield\x18\x02 \x01(\tH\x01\x88\x01\x01\x12=\n\x08metadata\x18\x03 \x03(\x0b\x32+.encryption.EncryptionContext.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x42\x08\n\x06_modelB\x08\n\x06_field\"R\n\x12\x45ncryptJSONRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12.\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x1d.encryption.EncryptionContext\"^\n\x12\x44\x65\x63ryptJSONRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x12\n\x05\x66ield\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05model\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x08\n\x06_fieldB\x08\n\x06_model\"R\n\x12\x45ncryptBlobRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12.\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x1d.encryption.EncryptionContext\"R\n\x12\x44\x65\x63ryptBlobRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12.\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x1d.encryption.EncryptionContext\"\x1f\n\x0f\x45ncryptResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"4\n\x0f\x44\x65\x63ryptResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x13\n\x0breplacement\x18\x02 \x01(\x0c\x32\xbc\x02\n\nEncryption\x12J\n\x0b\x45ncryptJSON\x12\x1e.encryption.EncryptJSONRequest\x1a\x1b.encryption.EncryptResponse\x12J\n\x0b\x44\x65\x63ryptJSON\x12\x1e.encryption.DecryptJSONRequest\x1a\x1b.encryption.DecryptResponse\x12J\n\x0b\x45ncryptBlob\x12\x1e.encryption.EncryptBlobRequest\x1a\x1b.encryption.EncryptResponse\x12J\n\x0b\x44\x65\x63ryptBlob\x12\x1e.encryption.DecryptBlobRequest\x1a\x1b.encryption.DecryptResponseBAZ?github.com/langchain-ai/langgraph-api/core/internal/core-api/pbb\x06proto3')
28
28
 
29
29
  _globals = globals()
30
30
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -42,10 +42,14 @@ if not _descriptor._USE_C_DESCRIPTORS:
42
42
  _globals['_ENCRYPTJSONREQUEST']._serialized_end=308
43
43
  _globals['_DECRYPTJSONREQUEST']._serialized_start=310
44
44
  _globals['_DECRYPTJSONREQUEST']._serialized_end=404
45
- _globals['_ENCRYPTRESPONSE']._serialized_start=406
46
- _globals['_ENCRYPTRESPONSE']._serialized_end=437
47
- _globals['_DECRYPTRESPONSE']._serialized_start=439
48
- _globals['_DECRYPTRESPONSE']._serialized_end=470
49
- _globals['_ENCRYPTION']._serialized_start=473
50
- _globals['_ENCRYPTION']._serialized_end=637
45
+ _globals['_ENCRYPTBLOBREQUEST']._serialized_start=406
46
+ _globals['_ENCRYPTBLOBREQUEST']._serialized_end=488
47
+ _globals['_DECRYPTBLOBREQUEST']._serialized_start=490
48
+ _globals['_DECRYPTBLOBREQUEST']._serialized_end=572
49
+ _globals['_ENCRYPTRESPONSE']._serialized_start=574
50
+ _globals['_ENCRYPTRESPONSE']._serialized_end=605
51
+ _globals['_DECRYPTRESPONSE']._serialized_start=607
52
+ _globals['_DECRYPTRESPONSE']._serialized_end=659
53
+ _globals['_ENCRYPTION']._serialized_start=662
54
+ _globals['_ENCRYPTION']._serialized_end=978
51
55
  # @@protoc_insertion_point(module_scope)
@@ -148,6 +148,62 @@ class DecryptJSONRequest(_message.Message):
148
148
 
149
149
  Global___DecryptJSONRequest: _TypeAlias = DecryptJSONRequest # noqa: Y015
150
150
 
151
+ @_typing.final
152
+ class EncryptBlobRequest(_message.Message):
153
+ """Request to encrypt binary data (e.g., checkpoint channel values and writes)."""
154
+
155
+ DESCRIPTOR: _descriptor.Descriptor
156
+
157
+ DATA_FIELD_NUMBER: _builtins.int
158
+ CONTEXT_FIELD_NUMBER: _builtins.int
159
+ data: _builtins.bytes
160
+ """The plaintext blob bytes"""
161
+ @_builtins.property
162
+ def context(self) -> Global___EncryptionContext:
163
+ """Encryption context for key derivation"""
164
+
165
+ def __init__(
166
+ self,
167
+ *,
168
+ data: _builtins.bytes = ...,
169
+ context: Global___EncryptionContext | None = ...,
170
+ ) -> None: ...
171
+ _HasFieldArgType: _TypeAlias = _typing.Literal["context", b"context"] # noqa: Y015
172
+ def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ...
173
+ _ClearFieldArgType: _TypeAlias = _typing.Literal["context", b"context", "data", b"data"] # noqa: Y015
174
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
175
+ def WhichOneof(self, oneof_group: _Never) -> None: ...
176
+
177
+ Global___EncryptBlobRequest: _TypeAlias = EncryptBlobRequest # noqa: Y015
178
+
179
+ @_typing.final
180
+ class DecryptBlobRequest(_message.Message):
181
+ """Request to decrypt binary data."""
182
+
183
+ DESCRIPTOR: _descriptor.Descriptor
184
+
185
+ DATA_FIELD_NUMBER: _builtins.int
186
+ CONTEXT_FIELD_NUMBER: _builtins.int
187
+ data: _builtins.bytes
188
+ """The encrypted blob bytes"""
189
+ @_builtins.property
190
+ def context(self) -> Global___EncryptionContext:
191
+ """Encryption context, recovered from the stored blob wrapper"""
192
+
193
+ def __init__(
194
+ self,
195
+ *,
196
+ data: _builtins.bytes = ...,
197
+ context: Global___EncryptionContext | None = ...,
198
+ ) -> None: ...
199
+ _HasFieldArgType: _TypeAlias = _typing.Literal["context", b"context"] # noqa: Y015
200
+ def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ...
201
+ _ClearFieldArgType: _TypeAlias = _typing.Literal["context", b"context", "data", b"data"] # noqa: Y015
202
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
203
+ def WhichOneof(self, oneof_group: _Never) -> None: ...
204
+
205
+ Global___DecryptBlobRequest: _TypeAlias = DecryptBlobRequest # noqa: Y015
206
+
151
207
  @_typing.final
152
208
  class EncryptResponse(_message.Message):
153
209
  """Response from encryption operations."""
@@ -177,16 +233,20 @@ class DecryptResponse(_message.Message):
177
233
  DESCRIPTOR: _descriptor.Descriptor
178
234
 
179
235
  DATA_FIELD_NUMBER: _builtins.int
236
+ REPLACEMENT_FIELD_NUMBER: _builtins.int
180
237
  data: _builtins.bytes
181
238
  """The decrypted plaintext data as bytes"""
239
+ replacement: _builtins.bytes
240
+ """Optional replacement ciphertext for the original storage location."""
182
241
  def __init__(
183
242
  self,
184
243
  *,
185
244
  data: _builtins.bytes = ...,
245
+ replacement: _builtins.bytes = ...,
186
246
  ) -> None: ...
187
247
  _HasFieldArgType: _TypeAlias = _Never # noqa: Y015
188
248
  def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ...
189
- _ClearFieldArgType: _TypeAlias = _typing.Literal["data", b"data"] # noqa: Y015
249
+ _ClearFieldArgType: _TypeAlias = _typing.Literal["data", b"data", "replacement", b"replacement"] # noqa: Y015
190
250
  def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
191
251
  def WhichOneof(self, oneof_group: _Never) -> None: ...
192
252
 
@@ -45,6 +45,16 @@ class EncryptionStub:
45
45
  request_serializer=encryption__pb2.DecryptJSONRequest.SerializeToString,
46
46
  response_deserializer=encryption__pb2.DecryptResponse.FromString,
47
47
  _registered_method=True)
48
+ self.EncryptBlob = channel.unary_unary(
49
+ '/encryption.Encryption/EncryptBlob',
50
+ request_serializer=encryption__pb2.EncryptBlobRequest.SerializeToString,
51
+ response_deserializer=encryption__pb2.EncryptResponse.FromString,
52
+ _registered_method=True)
53
+ self.DecryptBlob = channel.unary_unary(
54
+ '/encryption.Encryption/DecryptBlob',
55
+ request_serializer=encryption__pb2.DecryptBlobRequest.SerializeToString,
56
+ response_deserializer=encryption__pb2.DecryptResponse.FromString,
57
+ _registered_method=True)
48
58
 
49
59
 
50
60
  class EncryptionServicer:
@@ -65,6 +75,20 @@ class EncryptionServicer:
65
75
  context.set_details('Method not implemented!')
66
76
  raise NotImplementedError('Method not implemented!')
67
77
 
78
+ def EncryptBlob(self, request, context):
79
+ """Encrypt blob data (checkpoint channel values and pending writes).
80
+ """
81
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
82
+ context.set_details('Method not implemented!')
83
+ raise NotImplementedError('Method not implemented!')
84
+
85
+ def DecryptBlob(self, request, context):
86
+ """Decrypt blob data. Context is supplied by the caller from the blob wrapper.
87
+ """
88
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
89
+ context.set_details('Method not implemented!')
90
+ raise NotImplementedError('Method not implemented!')
91
+
68
92
 
69
93
  def add_EncryptionServicer_to_server(servicer, server):
70
94
  rpc_method_handlers = {
@@ -78,6 +102,16 @@ def add_EncryptionServicer_to_server(servicer, server):
78
102
  request_deserializer=encryption__pb2.DecryptJSONRequest.FromString,
79
103
  response_serializer=encryption__pb2.DecryptResponse.SerializeToString,
80
104
  ),
105
+ 'EncryptBlob': grpc.unary_unary_rpc_method_handler(
106
+ servicer.EncryptBlob,
107
+ request_deserializer=encryption__pb2.EncryptBlobRequest.FromString,
108
+ response_serializer=encryption__pb2.EncryptResponse.SerializeToString,
109
+ ),
110
+ 'DecryptBlob': grpc.unary_unary_rpc_method_handler(
111
+ servicer.DecryptBlob,
112
+ request_deserializer=encryption__pb2.DecryptBlobRequest.FromString,
113
+ response_serializer=encryption__pb2.DecryptResponse.SerializeToString,
114
+ ),
81
115
  }
82
116
  generic_handler = grpc.method_handlers_generic_handler(
83
117
  'encryption.Encryption', rpc_method_handlers)
@@ -143,3 +177,57 @@ class Encryption:
143
177
  timeout,
144
178
  metadata,
145
179
  _registered_method=True)
180
+
181
+ @staticmethod
182
+ def EncryptBlob(request,
183
+ target,
184
+ options=(),
185
+ channel_credentials=None,
186
+ call_credentials=None,
187
+ insecure=False,
188
+ compression=None,
189
+ wait_for_ready=None,
190
+ timeout=None,
191
+ metadata=None):
192
+ return grpc.experimental.unary_unary(
193
+ request,
194
+ target,
195
+ '/encryption.Encryption/EncryptBlob',
196
+ encryption__pb2.EncryptBlobRequest.SerializeToString,
197
+ encryption__pb2.EncryptResponse.FromString,
198
+ options,
199
+ channel_credentials,
200
+ insecure,
201
+ call_credentials,
202
+ compression,
203
+ wait_for_ready,
204
+ timeout,
205
+ metadata,
206
+ _registered_method=True)
207
+
208
+ @staticmethod
209
+ def DecryptBlob(request,
210
+ target,
211
+ options=(),
212
+ channel_credentials=None,
213
+ call_credentials=None,
214
+ insecure=False,
215
+ compression=None,
216
+ wait_for_ready=None,
217
+ timeout=None,
218
+ metadata=None):
219
+ return grpc.experimental.unary_unary(
220
+ request,
221
+ target,
222
+ '/encryption.Encryption/DecryptBlob',
223
+ encryption__pb2.DecryptBlobRequest.SerializeToString,
224
+ encryption__pb2.DecryptResponse.FromString,
225
+ options,
226
+ channel_credentials,
227
+ insecure,
228
+ call_credentials,
229
+ compression,
230
+ wait_for_ready,
231
+ timeout,
232
+ metadata,
233
+ _registered_method=True)
@@ -37,6 +37,10 @@ class EncryptionStub:
37
37
  """Encrypt JSON data (e.g., thread metadata, run kwargs)."""
38
38
  DecryptJSON: _grpc.UnaryUnaryMultiCallable[_encryption_pb2.DecryptJSONRequest, _encryption_pb2.DecryptResponse]
39
39
  """Decrypt JSON data. Context is extracted from __encryption_context__ in the data."""
40
+ EncryptBlob: _grpc.UnaryUnaryMultiCallable[_encryption_pb2.EncryptBlobRequest, _encryption_pb2.EncryptResponse]
41
+ """Encrypt blob data (checkpoint channel values and pending writes)."""
42
+ DecryptBlob: _grpc.UnaryUnaryMultiCallable[_encryption_pb2.DecryptBlobRequest, _encryption_pb2.DecryptResponse]
43
+ """Decrypt blob data. Context is supplied by the caller from the blob wrapper."""
40
44
 
41
45
  @_typing.type_check_only
42
46
  class EncryptionAsyncStub(EncryptionStub):
@@ -47,6 +51,10 @@ class EncryptionAsyncStub(EncryptionStub):
47
51
  """Encrypt JSON data (e.g., thread metadata, run kwargs)."""
48
52
  DecryptJSON: _aio.UnaryUnaryMultiCallable[_encryption_pb2.DecryptJSONRequest, _encryption_pb2.DecryptResponse] # type: ignore[assignment]
49
53
  """Decrypt JSON data. Context is extracted from __encryption_context__ in the data."""
54
+ EncryptBlob: _aio.UnaryUnaryMultiCallable[_encryption_pb2.EncryptBlobRequest, _encryption_pb2.EncryptResponse] # type: ignore[assignment]
55
+ """Encrypt blob data (checkpoint channel values and pending writes)."""
56
+ DecryptBlob: _aio.UnaryUnaryMultiCallable[_encryption_pb2.DecryptBlobRequest, _encryption_pb2.DecryptResponse] # type: ignore[assignment]
57
+ """Decrypt blob data. Context is supplied by the caller from the blob wrapper."""
50
58
 
51
59
  class EncryptionServicer(metaclass=_abc_1.ABCMeta):
52
60
  """Encryption service for custom encryption implementations."""
@@ -67,4 +75,20 @@ class EncryptionServicer(metaclass=_abc_1.ABCMeta):
67
75
  ) -> _typing.Union[_encryption_pb2.DecryptResponse, _abc.Awaitable[_encryption_pb2.DecryptResponse]]:
68
76
  """Decrypt JSON data. Context is extracted from __encryption_context__ in the data."""
69
77
 
78
+ @_abc_1.abstractmethod
79
+ def EncryptBlob(
80
+ self,
81
+ request: _encryption_pb2.EncryptBlobRequest,
82
+ context: _ServicerContext,
83
+ ) -> _typing.Union[_encryption_pb2.EncryptResponse, _abc.Awaitable[_encryption_pb2.EncryptResponse]]:
84
+ """Encrypt blob data (checkpoint channel values and pending writes)."""
85
+
86
+ @_abc_1.abstractmethod
87
+ def DecryptBlob(
88
+ self,
89
+ request: _encryption_pb2.DecryptBlobRequest,
90
+ context: _ServicerContext,
91
+ ) -> _typing.Union[_encryption_pb2.DecryptResponse, _abc.Awaitable[_encryption_pb2.DecryptResponse]]:
92
+ """Decrypt blob data. Context is supplied by the caller from the blob wrapper."""
93
+
70
94
  def add_EncryptionServicer_to_server(servicer: EncryptionServicer, server: _typing.Union[_grpc.Server, _aio.Server]) -> None: ...