langgraph-api 0.7.102__tar.gz → 0.8.0__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.
Files changed (297) hide show
  1. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/PKG-INFO +1 -1
  2. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/capacity_runner.mjs +4 -4
  3. langgraph_api-0.8.0/langgraph_api/__init__.py +1 -0
  4. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/cli.py +54 -36
  5. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/config/__init__.py +2 -1
  6. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/config/schemas.py +9 -1
  7. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/http.py +31 -0
  8. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/package.json +1 -1
  9. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/yarn.lock +7 -7
  10. langgraph_api-0.8.0/langgraph_api/lc_security/__init__.py +29 -0
  11. langgraph_api-0.8.0/langgraph_api/lc_security/exceptions.py +9 -0
  12. langgraph_api-0.8.0/langgraph_api/lc_security/policy.py +349 -0
  13. langgraph_api-0.8.0/langgraph_api/lc_security/transport.py +201 -0
  14. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/webhook.py +101 -62
  15. langgraph_api-0.7.102/langgraph_api/__init__.py +0 -1
  16. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/.gitignore +0 -0
  17. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/LICENSE +0 -0
  18. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/Makefile +0 -0
  19. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/README.md +0 -0
  20. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/.gitignore +0 -0
  21. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/Makefile +0 -0
  22. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/README.md +0 -0
  23. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/assistant.ts +0 -0
  24. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/benchmark-runner.ts +0 -0
  25. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/benchmark_profiles.ts +0 -0
  26. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/benchmarks.ts +0 -0
  27. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/cancel_first_second_completes.ts +0 -0
  28. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/enqueued_runs_order.ts +0 -0
  29. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/log-failure.ts +0 -0
  30. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/meta_workload.ts +0 -0
  31. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/stream_write.ts +0 -0
  32. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/thread.ts +0 -0
  33. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/thread_runs_metadata_search.ts +0 -0
  34. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/threads_search_metadata.ts +0 -0
  35. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/types.ts +0 -0
  36. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/benchmark-runners/wait_write.ts +0 -0
  37. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/capacity_dd_report.py +0 -0
  38. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/capacity_k6.js +0 -0
  39. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/capacity_slack_report.py +0 -0
  40. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/capacity_urls.mjs +0 -0
  41. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/clean-cli.js +0 -0
  42. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/clean.js +0 -0
  43. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/continuous/README.md +0 -0
  44. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/continuous/pyproject.toml +0 -0
  45. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/continuous/runner.py +0 -0
  46. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/continuous/uv.lock +0 -0
  47. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/graphs.js +0 -0
  48. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/mixed_workload_k6.js +0 -0
  49. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/mixed_workload_runner.mjs +0 -0
  50. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/package.json +0 -0
  51. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/ramp.js +0 -0
  52. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/reporting/dd_reporting.py +0 -0
  53. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/reporting/slack_slowest_runs.py +0 -0
  54. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/reporting/slack_summary.py +0 -0
  55. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/run_local.sh +0 -0
  56. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/staircase.py +0 -0
  57. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/staircase_step_k6.js +0 -0
  58. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/tsconfig.json +0 -0
  59. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/update-revision.js +0 -0
  60. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/benchmark/weather.js +0 -0
  61. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/constraints.txt +0 -0
  62. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/custom_store.sql +0 -0
  63. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/forbidden.txt +0 -0
  64. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/hatch_build.py +0 -0
  65. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/healthcheck.py +0 -0
  66. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph-cloud-debugging-20260210132856.zip +0 -0
  67. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/_checkpointer/__init__.py +0 -0
  68. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/_checkpointer/_adapter.py +0 -0
  69. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/_checkpointer/protocol.py +0 -0
  70. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/_factory_utils.py +0 -0
  71. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/__init__.py +0 -0
  72. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/a2a.py +0 -0
  73. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/assistants.py +0 -0
  74. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/mcp/__init__.py +0 -0
  75. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/mcp/_constants.py +0 -0
  76. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/mcp/_handlers.py +0 -0
  77. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/mcp/_models.py +0 -0
  78. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/mcp/_routes.py +0 -0
  79. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/mcp/_sanitizers.py +0 -0
  80. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/meta.py +0 -0
  81. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/openapi.py +0 -0
  82. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/profile.py +0 -0
  83. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/runs.py +0 -0
  84. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/store.py +0 -0
  85. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/threads.py +0 -0
  86. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/api/ui.py +0 -0
  87. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/asgi_transport.py +0 -0
  88. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/asyncio.py +0 -0
  89. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/auth/__init__.py +0 -0
  90. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/auth/custom.py +0 -0
  91. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/auth/errors.py +0 -0
  92. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/auth/langsmith/__init__.py +0 -0
  93. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/auth/langsmith/backend.py +0 -0
  94. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/auth/langsmith/client.py +0 -0
  95. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/auth/middleware.py +0 -0
  96. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/auth/noop.py +0 -0
  97. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/auth/studio_user.py +0 -0
  98. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/cache.py +0 -0
  99. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/command.py +0 -0
  100. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/config/_parse.py +0 -0
  101. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/cron_scheduler.py +0 -0
  102. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/encryption/__init__.py +0 -0
  103. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/encryption/aes_json.py +0 -0
  104. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/encryption/context.py +0 -0
  105. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/encryption/custom.py +0 -0
  106. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/encryption/middleware.py +0 -0
  107. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/encryption/shared.py +0 -0
  108. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/errors.py +0 -0
  109. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/executor_entrypoint.py +0 -0
  110. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/feature_flags.py +0 -0
  111. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/graph.py +0 -0
  112. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/__init__.py +0 -0
  113. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/client.py +0 -0
  114. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/generated/core_api_pb2.pyi +0 -0
  115. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/ops/__init__.py +0 -0
  116. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/ops/assistants.py +0 -0
  117. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/ops/cache.py +0 -0
  118. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/ops/crons.py +0 -0
  119. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/ops/runs.py +0 -0
  120. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/ops/threads.py +0 -0
  121. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/server.py +0 -0
  122. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/servicers/__init__.py +0 -0
  123. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/servicers/checkpointer.py +0 -0
  124. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/grpc/servicers/encryption.py +0 -0
  125. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/http_metrics.py +0 -0
  126. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/http_metrics_utils.py +0 -0
  127. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/.gitignore +0 -0
  128. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/.prettierrc +0 -0
  129. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/__init__.py +0 -0
  130. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/base.py +0 -0
  131. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/build.mts +0 -0
  132. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/client.http.mts +0 -0
  133. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/client.mts +0 -0
  134. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/errors.py +0 -0
  135. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/global.d.ts +0 -0
  136. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/remote.py +0 -0
  137. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/schema.py +0 -0
  138. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/src/graph.mts +0 -0
  139. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/src/load.hooks.mjs +0 -0
  140. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/src/preload.mjs +0 -0
  141. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/src/utils/files.mts +0 -0
  142. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/src/utils/importMap.mts +0 -0
  143. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  144. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/src/utils/serde.mts +0 -0
  145. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/sse.py +0 -0
  146. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/traceblock.mts +0 -0
  147. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/tsconfig.json +0 -0
  148. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/js/ui.py +0 -0
  149. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/logging.py +0 -0
  150. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/metadata.py +0 -0
  151. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/metrics_datadog.py +0 -0
  152. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/middleware/__init__.py +0 -0
  153. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/middleware/ensure_store.py +0 -0
  154. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/middleware/http_logger.py +0 -0
  155. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/middleware/private_network.py +0 -0
  156. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/middleware/request_id.py +0 -0
  157. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/models/__init__.py +0 -0
  158. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/models/run.py +0 -0
  159. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/otel_context.py +0 -0
  160. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/patch.py +0 -0
  161. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/queue_entrypoint.py +0 -0
  162. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/route.py +0 -0
  163. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/schema.py +0 -0
  164. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/self_hosted_logs.py +0 -0
  165. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/self_hosted_metrics.py +0 -0
  166. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/serde.py +0 -0
  167. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/server.py +0 -0
  168. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/sse.py +0 -0
  169. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/state.py +0 -0
  170. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/store.py +0 -0
  171. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/stream.py +0 -0
  172. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/timing/__init__.py +0 -0
  173. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/timing/profiler.py +0 -0
  174. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/timing/timer.py +0 -0
  175. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/traceblock.py +0 -0
  176. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/tunneling/cloudflare.py +0 -0
  177. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/__init__.py +0 -0
  178. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/cache.py +0 -0
  179. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/config.py +0 -0
  180. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/errors.py +0 -0
  181. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/extract.py +0 -0
  182. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/future.py +0 -0
  183. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/headers.py +0 -0
  184. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/network.py +0 -0
  185. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/retriable_client.py +0 -0
  186. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/stream_codec.py +0 -0
  187. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/utils/uuids.py +0 -0
  188. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/validation.py +0 -0
  189. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_api/worker.py +0 -0
  190. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/__init__.py +0 -0
  191. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/checkpointer.py +0 -0
  192. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/__init__.py +0 -0
  193. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/_compat.py +0 -0
  194. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/channel.py +0 -0
  195. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/checkpoint.py +0 -0
  196. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/config.py +0 -0
  197. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/durability.py +0 -0
  198. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/exception.py +0 -0
  199. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/graph.py +0 -0
  200. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/interrupt.py +0 -0
  201. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/messages.py +0 -0
  202. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/orchestrator_response.py +0 -0
  203. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/runopts.py +0 -0
  204. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/stream_mode.py +0 -0
  205. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/struct.py +0 -0
  206. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/task.py +0 -0
  207. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/conversion/value.py +0 -0
  208. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/__init__.py +0 -0
  209. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/checkpointer_pb2.py +0 -0
  210. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/checkpointer_pb2.pyi +0 -0
  211. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/checkpointer_pb2_grpc.py +0 -0
  212. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/checkpointer_pb2_grpc.pyi +0 -0
  213. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/core_api_pb2.py +0 -0
  214. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/core_api_pb2.pyi +0 -0
  215. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/core_api_pb2_grpc.py +0 -0
  216. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/core_api_pb2_grpc.pyi +0 -0
  217. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/encryption_pb2.py +0 -0
  218. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/encryption_pb2.pyi +0 -0
  219. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/encryption_pb2_grpc.py +0 -0
  220. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/encryption_pb2_grpc.pyi +0 -0
  221. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/engine_api_pb2.py +0 -0
  222. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/engine_api_pb2.pyi +0 -0
  223. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/engine_api_pb2_grpc.py +0 -0
  224. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/engine_api_pb2_grpc.pyi +0 -0
  225. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/engine_common_pb2.py +0 -0
  226. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/engine_common_pb2.pyi +0 -0
  227. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/engine_common_pb2_grpc.py +0 -0
  228. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/engine_common_pb2_grpc.pyi +0 -0
  229. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2.py +0 -0
  230. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2.pyi +0 -0
  231. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2_grpc.py +0 -0
  232. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2_grpc.pyi +0 -0
  233. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_control_signal_pb2.py +0 -0
  234. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_control_signal_pb2.pyi +0 -0
  235. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_control_signal_pb2_grpc.py +0 -0
  236. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_control_signal_pb2_grpc.pyi +0 -0
  237. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2.py +0 -0
  238. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2.pyi +0 -0
  239. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2_grpc.py +0 -0
  240. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2_grpc.pyi +0 -0
  241. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_durability_pb2.py +0 -0
  242. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_durability_pb2.pyi +0 -0
  243. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_durability_pb2_grpc.py +0 -0
  244. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_durability_pb2_grpc.pyi +0 -0
  245. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2.py +0 -0
  246. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2.pyi +0 -0
  247. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2_grpc.py +0 -0
  248. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2_grpc.pyi +0 -0
  249. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_run_status_pb2.py +0 -0
  250. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_run_status_pb2.pyi +0 -0
  251. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_run_status_pb2_grpc.py +0 -0
  252. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_run_status_pb2_grpc.pyi +0 -0
  253. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_store_operation_entry_type_pb2.py +0 -0
  254. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_store_operation_entry_type_pb2.pyi +0 -0
  255. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_store_operation_entry_type_pb2_grpc.py +0 -0
  256. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_store_operation_entry_type_pb2_grpc.pyi +0 -0
  257. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_stream_mode_pb2.py +0 -0
  258. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_stream_mode_pb2.pyi +0 -0
  259. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_stream_mode_pb2_grpc.py +0 -0
  260. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_stream_mode_pb2_grpc.pyi +0 -0
  261. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_thread_status_pb2.py +0 -0
  262. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_thread_status_pb2.pyi +0 -0
  263. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_thread_status_pb2_grpc.py +0 -0
  264. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_thread_status_pb2_grpc.pyi +0 -0
  265. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2.py +0 -0
  266. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2.pyi +0 -0
  267. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2_grpc.py +0 -0
  268. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2_grpc.pyi +0 -0
  269. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/errors_pb2.py +0 -0
  270. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/errors_pb2.pyi +0 -0
  271. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/errors_pb2_grpc.py +0 -0
  272. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/errors_pb2_grpc.pyi +0 -0
  273. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/executor_api_pb2.py +0 -0
  274. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/executor_api_pb2.pyi +0 -0
  275. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/executor_api_pb2_grpc.py +0 -0
  276. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/proto/executor_api_pb2_grpc.pyi +0 -0
  277. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/sanitize.py +0 -0
  278. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_grpc_common/serde.py +0 -0
  279. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_license/__init__.py +0 -0
  280. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_license/validation.py +0 -0
  281. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_runtime/__init__.py +0 -0
  282. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_runtime/checkpoint.py +0 -0
  283. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_runtime/database.py +0 -0
  284. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_runtime/lifespan.py +0 -0
  285. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_runtime/metrics.py +0 -0
  286. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_runtime/ops.py +0 -0
  287. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_runtime/queue.py +0 -0
  288. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_runtime/retry.py +0 -0
  289. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_runtime/routes.py +0 -0
  290. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/langgraph_runtime/store.py +0 -0
  291. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/logging.json +0 -0
  292. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/openapi.json +0 -0
  293. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/pyproject.toml +0 -0
  294. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/scripts/build_wheel.py +0 -0
  295. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/scripts/create_license.py +0 -0
  296. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/scripts/run_a2a_tck.py +0 -0
  297. {langgraph_api-0.7.102 → langgraph_api-0.8.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.7.102
3
+ Version: 0.8.0
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
@@ -30,11 +30,11 @@ const clusterNameToSettings = {
30
30
  },
31
31
  'py-7-node': {
32
32
  url: 'https://cap-bench-py-7-node-5f471cdb8a725e0bbb076cc9fb32b76d.staging.langgraph.app',
33
- rampEndMultiplier: 1.8,
33
+ rampEndMultiplier: 5,
34
34
  },
35
35
  'py-20-node': {
36
36
  url: 'https://cap-bench-py-20-node-0970dd3e458059e488db99d48c69ca69.staging.langgraph.app',
37
- rampEndMultiplier: 2.4,
37
+ rampEndMultiplier: 10,
38
38
  },
39
39
  // Distributed runtime multi-node scaling benchmarks
40
40
  'dr-1-node': {
@@ -47,11 +47,11 @@ const clusterNameToSettings = {
47
47
  },
48
48
  'dr-7-node': {
49
49
  url: 'https://cap-bench-dr-7-node-fbf64b46fc9b57239764478187abe534.staging.langgraph.app',
50
- rampEndMultiplier: 1.8,
50
+ rampEndMultiplier: 5,
51
51
  },
52
52
  'dr-20-node': {
53
53
  url: 'https://cap-bench-dr-20-node-7cea036a01a25a9caec0be0b873f9b0a.staging.langgraph.app',
54
- rampEndMultiplier: 2.4,
54
+ rampEndMultiplier: 10,
55
55
  },
56
56
  };
57
57
 
@@ -0,0 +1 @@
1
+ __version__ = "0.8.0"
@@ -108,6 +108,47 @@ def _find_open_port(host: str) -> int:
108
108
  return s.getsockname()[1]
109
109
 
110
110
 
111
+ def _resolve_server_url(
112
+ host: str, port: int, *, mount_prefix: str | None, tunnel: bool
113
+ ) -> str:
114
+ """Return the public-facing base URL for the server.
115
+
116
+ When *tunnel* is True a Cloudflare tunnel is started and the tunnel
117
+ URL is returned; otherwise the local ``http://host:port`` URL is used.
118
+ *mount_prefix* (if any) is appended to the result.
119
+ """
120
+ from langgraph_api.utils.network import format_hostport # noqa: PLC0415
121
+
122
+ upstream_url = f"http://{format_hostport(host, port)}"
123
+ if mount_prefix:
124
+ upstream_url += mount_prefix
125
+
126
+ if not tunnel:
127
+ return upstream_url
128
+
129
+ logger.info("Starting Cloudflare Tunnel...")
130
+ from langgraph_api.tunneling.cloudflare import start_tunnel # noqa: PLC0415
131
+
132
+ tunnel_obj = start_tunnel(port)
133
+ try:
134
+ public_url = tunnel_obj.url.result(timeout=30)
135
+ except FutureTimeoutError:
136
+ logger.warning(
137
+ "Timed out waiting for Cloudflare Tunnel URL; using local URL %s",
138
+ upstream_url,
139
+ )
140
+ public_url = upstream_url
141
+ except Exception as e:
142
+ tunnel_obj.process.kill()
143
+ raise RuntimeError("Failed to start Cloudflare Tunnel") from e
144
+
145
+ # Only append the prefix if we got a real tunnel URL; on timeout
146
+ # fallback, public_url is already upstream_url which has the prefix.
147
+ if mount_prefix and public_url != upstream_url:
148
+ public_url += mount_prefix
149
+ return public_url
150
+
151
+
111
152
  def _resolve_port(host: str, port: int | None) -> int:
112
153
  """Resolve the port to use for the server."""
113
154
  if port is not None:
@@ -209,38 +250,11 @@ def run_server(
209
250
  debugpy.wait_for_client()
210
251
  logger.info("Debugger attached. Starting server...")
211
252
 
212
- # Determine local or tunneled URL
213
- with patch_environment(
214
- MIGRATIONS_PATH=__migrations_path__,
215
- DATABASE_URI=__database_uri__,
216
- REDIS_URI=__redis_uri__,
217
- ):
218
- from langgraph_api.utils.network import format_hostport # noqa: PLC0415
219
-
220
- upstream_url = f"http://{format_hostport(host, port)}"
221
- if mount_prefix:
222
- upstream_url += mount_prefix
223
- if tunnel:
224
- logger.info("Starting Cloudflare Tunnel...")
225
- from langgraph_api.tunneling.cloudflare import start_tunnel # noqa: PLC0415
226
-
227
- tunnel_obj = start_tunnel(port)
228
- try:
229
- public_url = tunnel_obj.url.result(timeout=30)
230
- except FutureTimeoutError:
231
- logger.warning(
232
- "Timed out waiting for Cloudflare Tunnel URL; using local URL %s",
233
- upstream_url,
234
- )
235
- public_url = upstream_url
236
- except Exception as e:
237
- tunnel_obj.process.kill()
238
- raise RuntimeError("Failed to start Cloudflare Tunnel") from e
239
- local_url = public_url
240
- if mount_prefix:
241
- local_url += mount_prefix
242
- else:
243
- local_url = upstream_url
253
+ # Build all env patches up front so that langgraph_api.config (which
254
+ # is read at import time) sees every config value. LANGGRAPH_API_URL
255
+ # is resolved inside the block (it depends on tunnel/mount_prefix) and
256
+ # overwritten then; the empty placeholder ensures patch_environment
257
+ # tracks and restores the original value on exit.
244
258
  to_patch = dict(
245
259
  MIGRATIONS_PATH=__migrations_path__,
246
260
  DATABASE_URI=__database_uri__,
@@ -258,7 +272,7 @@ def run_server(
258
272
  LANGGRAPH_UI_CONFIG=json.dumps(ui_config) if ui_config else None,
259
273
  LANGGRAPH_CHECKPOINTER=json.dumps(checkpointer) if checkpointer else None,
260
274
  LANGGRAPH_UI_BUNDLER="true",
261
- LANGGRAPH_API_URL=local_url,
275
+ LANGGRAPH_API_URL="", # resolved below, inside the patched block
262
276
  LANGGRAPH_DISABLE_FILE_PERSISTENCE=str(disable_persistence).lower(),
263
277
  LANGGRAPH_RUNTIME_EDITION=runtime_edition,
264
278
  # If true, we will not raise on blocking IO calls (via blockbuster)
@@ -273,9 +287,13 @@ def run_server(
273
287
  logger.debug(f"Skipping loaded env var {k}={v}")
274
288
  continue
275
289
  to_patch[k] = v
276
- with patch_environment(
277
- **to_patch,
278
- ):
290
+
291
+ with patch_environment(**to_patch):
292
+ local_url = _resolve_server_url(
293
+ host, port, mount_prefix=mount_prefix, tunnel=tunnel
294
+ )
295
+ os.environ["LANGGRAPH_API_URL"] = local_url
296
+
279
297
  studio_origin = studio_url or _get_ls_origin() or "https://smith.langchain.com"
280
298
  full_studio_url = f"{studio_origin}/studio/?baseUrl={local_url}"
281
299
 
@@ -351,7 +351,8 @@ FF_CRONS_ENABLED = env("FF_CRONS_ENABLED", cast=bool, default=True)
351
351
  FF_LOG_DROPPED_EVENTS = env("FF_LOG_DROPPED_EVENTS", cast=bool, default=False)
352
352
  FF_LOG_QUERY_AND_PARAMS = env("FF_LOG_QUERY_AND_PARAMS", cast=bool, default=False)
353
353
  # Not in public docs: internal feature flag
354
- FF_USE_REDIS_QUEUE = env("FF_USE_REDIS_QUEUE", cast=bool, default=False)
354
+ FF_USE_REDIS_QUEUE = env("FF_USE_REDIS_QUEUE", cast=bool, default=True)
355
+
355
356
 
356
357
  # Internal flag intended for testing only
357
358
  CRON_SCHEDULER_SLEEP_TIME = env("CRON_SCHEDULER_SLEEP_TIME", cast=int, default=5)
@@ -386,7 +386,13 @@ class WebhookUrlPolicy(TypedDict, total=False):
386
386
  max_url_length: int
387
387
  """Maximum permitted URL length in characters; longer inputs are rejected early."""
388
388
  disable_loopback: bool
389
- """Disallow relative URLs (internal loopback calls) when true."""
389
+ """Disallow relative URLs (internal loopback calls) and localhost hostnames when true."""
390
+ disable_private_ips: bool
391
+ """Block RFC 1918 / CGN private IP ranges as webhook targets when true.
392
+
393
+ Defaults to false (private IPs allowed). Set to true to block private
394
+ IP ranges for stricter SSRF protection.
395
+ """
390
396
 
391
397
 
392
398
  # Matches things like "${{ env.LG_WEBHOOK_FOO_BAR }}"
@@ -421,6 +427,8 @@ def _validate_url_policy(
421
427
  policy["max_url_length"] = 2048
422
428
  if "disable_loopback" not in policy:
423
429
  policy["disable_loopback"] = False
430
+ if "disable_private_ips" not in policy:
431
+ policy["disable_private_ips"] = False
424
432
  return policy
425
433
 
426
434
 
@@ -123,6 +123,37 @@ def get_loopback_client() -> JsonHttpClient:
123
123
  return _loopback_client
124
124
 
125
125
 
126
+ _webhook_http_client: JsonHttpClient | None = None
127
+
128
+
129
+ async def ensure_webhook_http_client() -> JsonHttpClient:
130
+ """Return (or create) an SSRF-safe HTTP client for webhook dispatch."""
131
+ global _webhook_http_client
132
+ if _webhook_http_client is not None:
133
+ return _webhook_http_client
134
+
135
+ from langgraph_api.lc_security import ssrf_safe_async_client # noqa: PLC0415
136
+ from langgraph_api.webhook import _get_webhook_config # noqa: PLC0415
137
+
138
+ inner = ssrf_safe_async_client(
139
+ policy=_get_webhook_config().base_ssrf_policy,
140
+ retries=2,
141
+ limits=httpx.Limits(max_keepalive_connections=10, keepalive_expiry=60.0),
142
+ follow_redirects=True,
143
+ max_redirects=5,
144
+ )
145
+ _webhook_http_client = JsonHttpClient(client=inner)
146
+ return _webhook_http_client
147
+
148
+
149
+ async def stop_webhook_http_client() -> None:
150
+ global _webhook_http_client
151
+ if _webhook_http_client is None:
152
+ return
153
+ await _webhook_http_client.client.aclose()
154
+ _webhook_http_client = None
155
+
156
+
126
157
  def is_retriable_error(exception: BaseException) -> bool:
127
158
  # httpx error hierarchy: https://www.python-httpx.org/exceptions/
128
159
  # Retry all timeout related errors
@@ -19,7 +19,7 @@
19
19
  "dedent": "^1.7.2",
20
20
  "esbuild": "^0.27.4",
21
21
  "exit-hook": "^5.1.0",
22
- "hono": "^4.12.12",
22
+ "hono": "^4.12.14",
23
23
  "p-queue": "^9.0.1",
24
24
  "p-retry": "^7.1.1",
25
25
  "tsx": "^4.21.0",
@@ -1316,10 +1316,10 @@ graceful-fs@^4.2.4:
1316
1316
  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
1317
1317
  integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
1318
1318
 
1319
- hono@^4.12.12, hono@^4.5.4:
1320
- version "4.12.12"
1321
- resolved "https://registry.yarnpkg.com/hono/-/hono-4.12.12.tgz#1f14b0ffb47c386ff50d457d66e706d9c9a7f09c"
1322
- integrity sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==
1319
+ hono@^4.12.14, hono@^4.5.4:
1320
+ version "4.12.14"
1321
+ resolved "https://registry.yarnpkg.com/hono/-/hono-4.12.14.tgz#4777c9512b7c84138e4f09e61e3d2fa305eb1414"
1322
+ integrity sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==
1323
1323
 
1324
1324
  icss-utils@^5.0.0, icss-utils@^5.1.0:
1325
1325
  version "5.1.0"
@@ -1393,9 +1393,9 @@ kuler@^2.0.0:
1393
1393
  integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
1394
1394
 
1395
1395
  "langsmith@>=0.3.33 <1.0.0", langsmith@>=0.4.6, "langsmith@>=0.5.0 <1.0.0":
1396
- version "0.5.18"
1397
- resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.5.18.tgz#c691ad23614f0b46eaf07d982e0ac988e1f43880"
1398
- integrity sha512-3zuZUWffTHQ+73EAwnodADtf534VNEZUpXr9jC12qyG8/IQuJET7PRsCpTb9wX2lmBspakwLUpqpj3tNm/0bVA==
1396
+ version "0.5.20"
1397
+ resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.5.20.tgz#4021847d2ccd5a86c5eb96060f9bb5f19f80eca5"
1398
+ integrity sha512-ULhLM8RswvQDXufLtNtvclHrWCBx8Cb5UPI6lAZC+8Dq59iHsVPz/3Ac9khWNm1VIvChRsuykixD/WrmzuuA3Q==
1399
1399
  dependencies:
1400
1400
  p-queue "6.6.2"
1401
1401
  uuid "10.0.0"
@@ -0,0 +1,29 @@
1
+ """lc_security — SSRF protection and security utilities.
2
+
3
+ Vendored from langchainplus/lc_security. When lc-security is published
4
+ to PyPI, delete this package and add the pip dependency instead.
5
+ """
6
+
7
+ from langgraph_api.lc_security.exceptions import SSRFBlockedError
8
+ from langgraph_api.lc_security.policy import (
9
+ SSRFPolicy,
10
+ validate_hostname,
11
+ validate_resolved_ip,
12
+ validate_url,
13
+ validate_url_sync,
14
+ )
15
+ from langgraph_api.lc_security.transport import (
16
+ SSRFSafeTransport,
17
+ ssrf_safe_async_client,
18
+ )
19
+
20
+ __all__ = [
21
+ "SSRFBlockedError",
22
+ "SSRFPolicy",
23
+ "SSRFSafeTransport",
24
+ "ssrf_safe_async_client",
25
+ "validate_hostname",
26
+ "validate_resolved_ip",
27
+ "validate_url",
28
+ "validate_url_sync",
29
+ ]
@@ -0,0 +1,9 @@
1
+ """SSRF protection exceptions."""
2
+
3
+
4
+ class SSRFBlockedError(Exception):
5
+ """Raised when a request is blocked by SSRF protection policy."""
6
+
7
+ def __init__(self, reason: str) -> None:
8
+ self.reason = reason
9
+ super().__init__(f"SSRF blocked: {reason}")
@@ -0,0 +1,349 @@
1
+ """SSRF protection policy with IP validation and DNS-aware URL checking."""
2
+
3
+ import asyncio
4
+ import dataclasses
5
+ import ipaddress
6
+ import socket
7
+ import urllib.parse
8
+
9
+ import structlog
10
+
11
+ from langgraph_api.lc_security.exceptions import SSRFBlockedError
12
+
13
+ logger = structlog.get_logger(__name__)
14
+
15
+ # ---------------------------------------------------------------------------
16
+ # Blocklist constants
17
+ # ---------------------------------------------------------------------------
18
+
19
+ # Ranges that are NEVER valid webhook targets (always blocked regardless of
20
+ # deployment mode).
21
+ _ALWAYS_BLOCKED_IPV4_NETWORKS: tuple[ipaddress.IPv4Network, ...] = tuple(
22
+ ipaddress.IPv4Network(n)
23
+ for n in (
24
+ "169.254.0.0/16", # RFC 3927 - link-local
25
+ "0.0.0.0/8", # RFC 1122 - "this network"
26
+ "192.0.0.0/24", # RFC 6890 - IETF protocol assignments
27
+ "192.0.2.0/24", # RFC 5737 - TEST-NET-1 (documentation)
28
+ "198.18.0.0/15", # RFC 2544 - benchmarking
29
+ "198.51.100.0/24", # RFC 5737 - TEST-NET-2 (documentation)
30
+ "203.0.113.0/24", # RFC 5737 - TEST-NET-3 (documentation)
31
+ "224.0.0.0/4", # RFC 5771 - multicast
32
+ "240.0.0.0/4", # RFC 1112 - reserved for future use
33
+ "255.255.255.255/32", # RFC 919 - limited broadcast
34
+ )
35
+ )
36
+
37
+ # Loopback ranges — blocked when block_localhost is set, allowed by default.
38
+ _LOOPBACK_IPV4_NETWORKS: tuple[ipaddress.IPv4Network, ...] = tuple(
39
+ ipaddress.IPv4Network(n)
40
+ for n in ("127.0.0.0/8",) # RFC 1122 - loopback
41
+ )
42
+
43
+ # Private/internal ranges — blocked in SAAS, allowed in self-hosted/BYOC
44
+ # where internal webhook targets are legitimate.
45
+ _PRIVATE_IPV4_NETWORKS: tuple[ipaddress.IPv4Network, ...] = tuple(
46
+ ipaddress.IPv4Network(n)
47
+ for n in (
48
+ "10.0.0.0/8", # RFC 1918 - private class A
49
+ "172.16.0.0/12", # RFC 1918 - private class B
50
+ "192.168.0.0/16", # RFC 1918 - private class C
51
+ "100.64.0.0/10", # RFC 6598 - shared/CGN address space
52
+ )
53
+ )
54
+
55
+ _ALWAYS_BLOCKED_IPV6_NETWORKS: tuple[ipaddress.IPv6Network, ...] = tuple(
56
+ ipaddress.IPv6Network(n)
57
+ for n in (
58
+ "fe80::/10", # RFC 4291 - link-local
59
+ "ff00::/8", # RFC 4291 - multicast
60
+ "::ffff:0:0/96", # RFC 4291 - IPv4-mapped IPv6 addresses
61
+ "::0.0.0.0/96", # RFC 4291 - IPv4-compatible IPv6 (deprecated)
62
+ "64:ff9b::/96", # RFC 6052 - NAT64 well-known prefix
63
+ "64:ff9b:1::/48", # RFC 8215 - NAT64 discovery prefix
64
+ )
65
+ )
66
+
67
+ _LOOPBACK_IPV6_NETWORKS: tuple[ipaddress.IPv6Network, ...] = tuple(
68
+ ipaddress.IPv6Network(n)
69
+ for n in ("::1/128",) # RFC 4291 - loopback
70
+ )
71
+
72
+ # Private/internal IPv6 — blocked in SAAS, allowed in self-hosted/BYOC.
73
+ _PRIVATE_IPV6_NETWORKS: tuple[ipaddress.IPv6Network, ...] = tuple(
74
+ ipaddress.IPv6Network(n)
75
+ for n in (
76
+ "fc00::/7", # RFC 4193 - unique local addresses (ULA)
77
+ )
78
+ )
79
+
80
+ _CLOUD_METADATA_IPS: frozenset[str] = frozenset(
81
+ {
82
+ "169.254.169.254",
83
+ "169.254.170.2",
84
+ "100.100.100.200",
85
+ "fd00:ec2::254",
86
+ }
87
+ )
88
+
89
+ _CLOUD_METADATA_HOSTNAMES: frozenset[str] = frozenset(
90
+ {
91
+ "metadata.google.internal",
92
+ "metadata.amazonaws.com",
93
+ "metadata",
94
+ "instance-data",
95
+ }
96
+ )
97
+
98
+ _LOCALHOST_NAMES: frozenset[str] = frozenset(
99
+ {
100
+ "localhost",
101
+ "localhost.localdomain",
102
+ "host.docker.internal",
103
+ }
104
+ )
105
+
106
+ _K8S_SUFFIX = ".svc.cluster.local"
107
+
108
+ # NAT64 well-known prefixes
109
+ _NAT64_PREFIX = ipaddress.IPv6Network("64:ff9b::/96")
110
+
111
+ # ---------------------------------------------------------------------------
112
+ # SSRFPolicy
113
+ # ---------------------------------------------------------------------------
114
+
115
+
116
+ @dataclasses.dataclass(frozen=True)
117
+ class SSRFPolicy:
118
+ """Immutable policy controlling which URLs/IPs are considered safe."""
119
+
120
+ allowed_schemes: frozenset[str] = frozenset({"http", "https"})
121
+ block_private_ips: bool = False
122
+ block_localhost: bool = False
123
+ block_cloud_metadata: bool = True
124
+ block_k8s_internal: bool = True
125
+ allowed_hosts: frozenset[str] = frozenset()
126
+ additional_blocked_cidrs: tuple[
127
+ ipaddress.IPv4Network | ipaddress.IPv6Network, ...
128
+ ] = ()
129
+
130
+
131
+ # ---------------------------------------------------------------------------
132
+ # Helpers
133
+ # ---------------------------------------------------------------------------
134
+
135
+
136
+ def _extract_embedded_ipv4(
137
+ addr: ipaddress.IPv6Address,
138
+ ) -> ipaddress.IPv4Address | None:
139
+ """Extract an embedded IPv4 from IPv4-mapped or NAT64 IPv6 addresses."""
140
+ # Check ipv4_mapped first (covers ::ffff:x.x.x.x)
141
+ if addr.ipv4_mapped is not None:
142
+ return addr.ipv4_mapped
143
+
144
+ # Check NAT64 well-known prefix (/96) - embedded IPv4 is in the last 4 bytes.
145
+ # NOTE: We intentionally only extract for the /96 prefix where the
146
+ # last-4-bytes extraction is correct per RFC 6052 §2.2. The /48 discovery
147
+ # prefix (64:ff9b:1::/48, RFC 8215) embeds IPv4 at a different offset
148
+ # and is already blocked outright via _BLOCKED_IPV6_NETWORKS.
149
+ if addr in _NAT64_PREFIX:
150
+ raw = addr.packed
151
+ return ipaddress.IPv4Address(raw[-4:])
152
+
153
+ return None
154
+
155
+
156
+ def _ip_in_blocked_networks(
157
+ addr: ipaddress.IPv4Address | ipaddress.IPv6Address,
158
+ policy: SSRFPolicy,
159
+ ) -> str | None:
160
+ """Return a reason string if *addr* falls in a blocked range, else None."""
161
+ if isinstance(addr, ipaddress.IPv4Address):
162
+ # Always-blocked ranges (link-local, reserved, etc.)
163
+ for net in _ALWAYS_BLOCKED_IPV4_NETWORKS:
164
+ if addr in net:
165
+ return "blocked IP range"
166
+ # Loopback — only when block_localhost is set.
167
+ if policy.block_localhost:
168
+ for net in _LOOPBACK_IPV4_NETWORKS:
169
+ if addr in net:
170
+ return "loopback IP range"
171
+ # Private ranges (RFC 1918, CGN) — only when policy says so.
172
+ if policy.block_private_ips:
173
+ for net in _PRIVATE_IPV4_NETWORKS:
174
+ if addr in net:
175
+ return "private IP range"
176
+ for net in policy.additional_blocked_cidrs:
177
+ if isinstance(net, ipaddress.IPv4Network) and addr in net:
178
+ return "blocked CIDR"
179
+ else:
180
+ for net in _ALWAYS_BLOCKED_IPV6_NETWORKS:
181
+ if addr in net:
182
+ return "blocked IP range"
183
+ # Loopback — only when block_localhost is set.
184
+ if policy.block_localhost:
185
+ for net in _LOOPBACK_IPV6_NETWORKS:
186
+ if addr in net:
187
+ return "loopback IP range"
188
+ if policy.block_private_ips:
189
+ for net in _PRIVATE_IPV6_NETWORKS:
190
+ if addr in net:
191
+ return "private IP range"
192
+ for net in policy.additional_blocked_cidrs:
193
+ if isinstance(net, ipaddress.IPv6Network) and addr in net:
194
+ return "blocked CIDR"
195
+
196
+ # Cloud metadata IP check
197
+ if policy.block_cloud_metadata and str(addr) in _CLOUD_METADATA_IPS:
198
+ return "cloud metadata endpoint"
199
+
200
+ return None
201
+
202
+
203
+ # ---------------------------------------------------------------------------
204
+ # Public validation functions
205
+ # ---------------------------------------------------------------------------
206
+
207
+
208
+ def validate_resolved_ip(ip_str: str, policy: SSRFPolicy) -> None:
209
+ """Validate a resolved IP address against the SSRF policy.
210
+
211
+ Raises SSRFBlockedError if the IP is blocked.
212
+ """
213
+ try:
214
+ addr = ipaddress.ip_address(ip_str)
215
+ except ValueError as exc:
216
+ raise SSRFBlockedError("invalid IP address") from exc
217
+
218
+ if isinstance(addr, ipaddress.IPv6Address):
219
+ # Check the original IPv6 address first — this catches addresses
220
+ # in blocked IPv6 networks (e.g. NAT64 discovery prefix 64:ff9b:1::/48)
221
+ # before we attempt IPv4 extraction.
222
+ reason = _ip_in_blocked_networks(addr, policy)
223
+ if reason is not None:
224
+ raise SSRFBlockedError(reason)
225
+ inner = _extract_embedded_ipv4(addr)
226
+ if inner is not None:
227
+ addr = inner
228
+
229
+ reason = _ip_in_blocked_networks(addr, policy)
230
+ if reason is not None:
231
+ raise SSRFBlockedError(reason)
232
+
233
+
234
+ def validate_hostname(hostname: str, policy: SSRFPolicy) -> None:
235
+ """Validate a hostname against the SSRF policy.
236
+
237
+ Raises SSRFBlockedError if the hostname is blocked.
238
+ """
239
+ lower = hostname.lower()
240
+
241
+ if policy.block_localhost and lower in _LOCALHOST_NAMES:
242
+ raise SSRFBlockedError("localhost address")
243
+
244
+ if policy.block_cloud_metadata and lower in _CLOUD_METADATA_HOSTNAMES:
245
+ raise SSRFBlockedError("cloud metadata endpoint")
246
+
247
+ if policy.block_k8s_internal and lower.endswith(_K8S_SUFFIX):
248
+ raise SSRFBlockedError("Kubernetes internal DNS")
249
+
250
+
251
+ def _effective_allowed_hosts(policy: SSRFPolicy) -> frozenset[str]:
252
+ """Return the policy's allowed_hosts set."""
253
+ return policy.allowed_hosts
254
+
255
+
256
+ async def validate_url(url: str, policy: SSRFPolicy = SSRFPolicy()) -> None:
257
+ """Validate a URL against the SSRF policy, including DNS resolution.
258
+
259
+ This is the primary entry-point for async code paths. It delegates
260
+ scheme/hostname/allowed-hosts checks to ``validate_url_sync``, then
261
+ resolves DNS and validates every resolved IP.
262
+
263
+ Raises SSRFBlockedError on any violation.
264
+ """
265
+ parsed = urllib.parse.urlparse(url)
266
+ hostname = parsed.hostname or ""
267
+
268
+ # Reuse synchronous checks (scheme, hostname, allowed-hosts bypass).
269
+ try:
270
+ validate_url_sync(url, policy)
271
+ except SSRFBlockedError as exc:
272
+ logger.warning(
273
+ "ssrf_blocked",
274
+ hostname=hostname,
275
+ reason=str(exc),
276
+ validation_type="write_time",
277
+ )
278
+ raise
279
+
280
+ # If the host is in the allowed list, validate_url_sync returned
281
+ # successfully and no DNS/IP checks are needed.
282
+ allowed = {h.lower() for h in _effective_allowed_hosts(policy)}
283
+ if hostname.lower() in allowed:
284
+ return
285
+
286
+ # If localhost is allowed and hostname is a localhost name, skip DNS/IP
287
+ # checks — the resolved IP (e.g. link-local in Docker) is irrelevant
288
+ # since we've explicitly permitted localhost access.
289
+ if not policy.block_localhost and hostname.lower() in _LOCALHOST_NAMES:
290
+ return
291
+
292
+ # DNS resolution
293
+ scheme = (parsed.scheme or "").lower()
294
+ port = parsed.port or (443 if scheme == "https" else 80)
295
+ try:
296
+ addrinfo = await asyncio.to_thread(
297
+ socket.getaddrinfo, hostname, port, type=socket.SOCK_STREAM
298
+ )
299
+ except socket.gaierror as exc:
300
+ logger.warning(
301
+ "ssrf_blocked",
302
+ hostname=hostname,
303
+ reason="DNS resolution failed",
304
+ validation_type="write_time",
305
+ )
306
+ raise SSRFBlockedError("DNS resolution failed") from exc
307
+
308
+ # Validate every resolved IP
309
+ for _family, _type, _proto, _canonname, sockaddr in addrinfo:
310
+ ip_str = sockaddr[0]
311
+ try:
312
+ validate_resolved_ip(ip_str, policy)
313
+ except SSRFBlockedError as exc:
314
+ logger.warning(
315
+ "ssrf_blocked",
316
+ hostname=hostname,
317
+ reason=str(exc),
318
+ validation_type="write_time",
319
+ )
320
+ raise
321
+
322
+
323
+ def validate_url_sync(url: str, policy: SSRFPolicy = SSRFPolicy()) -> None:
324
+ """Synchronous URL validation (no DNS resolution).
325
+
326
+ Suitable for Pydantic validators and other sync contexts. Checks scheme
327
+ and hostname patterns only — use validate_url for full DNS-aware checking.
328
+
329
+ Raises SSRFBlockedError on any violation.
330
+ """
331
+ parsed = urllib.parse.urlparse(url)
332
+
333
+ # Scheme check
334
+ scheme = (parsed.scheme or "").lower()
335
+ if scheme not in policy.allowed_schemes:
336
+ raise SSRFBlockedError(f"scheme '{scheme}' not allowed")
337
+
338
+ # Hostname check
339
+ hostname = parsed.hostname
340
+ if not hostname:
341
+ raise SSRFBlockedError("missing hostname")
342
+
343
+ # Allowed-hosts bypass
344
+ allowed = _effective_allowed_hosts(policy)
345
+ if hostname.lower() in {h.lower() for h in allowed}:
346
+ return
347
+
348
+ # Hostname pattern checks
349
+ validate_hostname(hostname, policy)