langgraph-api 0.1.9__tar.gz → 0.1.12__tar.gz

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

Potentially problematic release.


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

Files changed (107) hide show
  1. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/PKG-INFO +1 -1
  2. langgraph_api-0.1.12/langgraph_api/__init__.py +1 -0
  3. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/cli.py +33 -2
  4. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/package.json +1 -1
  5. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/api.test.mts +29 -0
  6. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/agent_simple.mts +23 -2
  7. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/yarn.lock +4 -4
  8. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/metadata.py +15 -11
  9. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/server.py +0 -2
  10. langgraph_api-0.1.12/langgraph_api/tunneling/cloudflare.py +119 -0
  11. langgraph_api-0.1.12/langgraph_license/validation.py +22 -0
  12. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/pyproject.toml +1 -1
  13. langgraph_api-0.1.9/langgraph_api/__init__.py +0 -1
  14. langgraph_api-0.1.9/langgraph_license/middleware.py +0 -21
  15. langgraph_api-0.1.9/langgraph_license/validation.py +0 -11
  16. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/LICENSE +0 -0
  17. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/README.md +0 -0
  18. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/api/__init__.py +0 -0
  19. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/api/assistants.py +0 -0
  20. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/api/mcp.py +0 -0
  21. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/api/meta.py +0 -0
  22. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/api/openapi.py +0 -0
  23. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/api/runs.py +0 -0
  24. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/api/store.py +0 -0
  25. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/api/threads.py +0 -0
  26. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/api/ui.py +0 -0
  27. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/asyncio.py +0 -0
  28. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/auth/__init__.py +0 -0
  29. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/auth/custom.py +0 -0
  30. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/auth/langsmith/__init__.py +0 -0
  31. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/auth/langsmith/backend.py +0 -0
  32. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/auth/langsmith/client.py +0 -0
  33. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/auth/middleware.py +0 -0
  34. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/auth/noop.py +0 -0
  35. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/auth/studio_user.py +0 -0
  36. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/command.py +0 -0
  37. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/config.py +0 -0
  38. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/cron_scheduler.py +0 -0
  39. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/errors.py +0 -0
  40. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/graph.py +0 -0
  41. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/http.py +0 -0
  42. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/.gitignore +0 -0
  43. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/.prettierrc +0 -0
  44. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/__init__.py +0 -0
  45. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/base.py +0 -0
  46. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/build.mts +0 -0
  47. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/client.http.mts +0 -0
  48. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/client.mts +0 -0
  49. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/errors.py +0 -0
  50. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/global.d.ts +0 -0
  51. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/remote.py +0 -0
  52. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/schema.py +0 -0
  53. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/src/graph.mts +0 -0
  54. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/src/hooks.mjs +0 -0
  55. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/src/parser/parser.mts +0 -0
  56. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
  57. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/src/schema/types.mts +0 -0
  58. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/src/schema/types.template.mts +0 -0
  59. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/src/utils/files.mts +0 -0
  60. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/src/utils/importMap.mts +0 -0
  61. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  62. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/src/utils/serde.mts +0 -0
  63. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/sse.py +0 -0
  64. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/auth.test.mts +0 -0
  65. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/compose-postgres.auth.yml +0 -0
  66. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/compose-postgres.yml +0 -0
  67. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/.gitignore +0 -0
  68. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/agent.css +0 -0
  69. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/agent.mts +0 -0
  70. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/agent.ui.tsx +0 -0
  71. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/auth.mts +0 -0
  72. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/command.mts +0 -0
  73. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/delay.mts +0 -0
  74. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/dynamic.mts +0 -0
  75. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/error.mts +0 -0
  76. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/http.mts +0 -0
  77. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
  78. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/nested.mts +0 -0
  79. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/package.json +0 -0
  80. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/weather.mts +0 -0
  81. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/graphs/yarn.lock +0 -0
  82. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/parser.test.mts +0 -0
  83. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/tests/utils.mts +0 -0
  84. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/js/ui.py +0 -0
  85. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/logging.py +0 -0
  86. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/middleware/__init__.py +0 -0
  87. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/middleware/http_logger.py +0 -0
  88. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/middleware/private_network.py +0 -0
  89. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/models/__init__.py +0 -0
  90. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/models/run.py +0 -0
  91. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/patch.py +0 -0
  92. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/queue_entrypoint.py +0 -0
  93. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/route.py +0 -0
  94. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/schema.py +0 -0
  95. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/serde.py +0 -0
  96. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/sse.py +0 -0
  97. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/state.py +0 -0
  98. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/stream.py +0 -0
  99. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/thread_ttl.py +0 -0
  100. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/utils.py +0 -0
  101. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/validation.py +0 -0
  102. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/webhook.py +0 -0
  103. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_api/worker.py +0 -0
  104. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_license/__init__.py +0 -0
  105. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/langgraph_runtime/__init__.py +0 -0
  106. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/logging.json +0 -0
  107. {langgraph_api-0.1.9 → langgraph_api-0.1.12}/openapi.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langgraph-api
3
- Version: 0.1.9
3
+ Version: 0.1.12
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -0,0 +1 @@
1
+ __version__ = "0.1.12"
@@ -122,6 +122,7 @@ def run_server(
122
122
  n_jobs_per_worker: int | None = None,
123
123
  env_file: str | None = None,
124
124
  open_browser: bool = False,
125
+ tunnel: bool = False,
125
126
  debug_port: int | None = None,
126
127
  wait_for_client: bool = False,
127
128
  env: str | pathlib.Path | Mapping[str, str] | None = None,
@@ -189,9 +190,33 @@ def run_server(
189
190
  debugpy.wait_for_client()
190
191
  logger.info("Debugger attached. Starting server...")
191
192
 
192
- local_url = f"http://{host}:{port}"
193
+ # Determine local or tunneled URL
194
+ upstream_url = f"http://{host}:{port}"
193
195
  if mount_prefix:
194
- local_url += mount_prefix
196
+ upstream_url += mount_prefix
197
+ if tunnel:
198
+ logger.info("Starting Cloudflare Tunnel...")
199
+ from concurrent.futures import TimeoutError as FutureTimeoutError
200
+
201
+ from langgraph_api.tunneling.cloudflare import start_tunnel
202
+
203
+ tunnel_obj = start_tunnel(port)
204
+ try:
205
+ public_url = tunnel_obj.url.result(timeout=30)
206
+ except FutureTimeoutError:
207
+ logger.warning(
208
+ "Timed out waiting for Cloudflare Tunnel URL; using local URL %s",
209
+ upstream_url,
210
+ )
211
+ public_url = upstream_url
212
+ except Exception as e:
213
+ tunnel_obj.process.kill()
214
+ raise RuntimeError("Failed to start Cloudflare Tunnel") from e
215
+ local_url = public_url
216
+ if mount_prefix:
217
+ local_url += mount_prefix
218
+ else:
219
+ local_url = upstream_url
195
220
  to_patch = dict(
196
221
  MIGRATIONS_PATH="__inmem",
197
222
  DATABASE_URI=":memory:",
@@ -361,6 +386,11 @@ def main():
361
386
  action="store_true",
362
387
  help="Whether to break and wait for a debugger to attach",
363
388
  )
389
+ parser.add_argument(
390
+ "--tunnel",
391
+ action="store_true",
392
+ help="Expose the server via Cloudflare Tunnel",
393
+ )
364
394
 
365
395
  args = parser.parse_args()
366
396
 
@@ -378,6 +408,7 @@ def main():
378
408
  graphs,
379
409
  n_jobs_per_worker=args.n_jobs_per_worker,
380
410
  open_browser=not args.no_browser,
411
+ tunnel=args.tunnel,
381
412
  debug_port=args.debug_port,
382
413
  wait_for_client=args.wait_for_client,
383
414
  env=config_data.get("env", None),
@@ -10,7 +10,7 @@
10
10
  "@hono/node-server": "^1.12.0",
11
11
  "@hono/zod-validator": "^0.2.2",
12
12
  "@langchain/core": "^0.3.44",
13
- "@langchain/langgraph": "^0.2.64",
13
+ "@langchain/langgraph": "^0.2.65",
14
14
  "@langchain/langgraph-checkpoint": "^0.0.17",
15
15
  "@types/json-schema": "^7.0.15",
16
16
  "@typescript/vfs": "^1.6.0",
@@ -2078,6 +2078,7 @@ it("custom routes - mutate request body", async () => {
2078
2078
  messages: expect.arrayContaining([
2079
2079
  expect.objectContaining({ content: "end: extra-client" }),
2080
2080
  ]),
2081
+ prompts: [],
2081
2082
  });
2082
2083
  });
2083
2084
 
@@ -2094,6 +2095,34 @@ it("custom routes - langgraph", async () => {
2094
2095
  messages: expect.arrayContaining([
2095
2096
  expect.objectContaining({ content: "input" }),
2096
2097
  ]),
2098
+ prompts: [],
2099
+ },
2100
+ });
2101
+ });
2102
+
2103
+ it("send map-reduce", async () => {
2104
+ const assistant = await client.assistants.create({ graphId: "agent_simple" });
2105
+ const thread = await client.threads.create();
2106
+
2107
+ const chunks = await gatherIterator(
2108
+ client.runs.stream(thread.thread_id, assistant.assistant_id, {
2109
+ input: { messages: [{ role: "human", content: "input" }] },
2110
+ config: { configurable: { "map-reduce": true } },
2111
+ }),
2112
+ );
2113
+
2114
+ expect(chunks.filter((i) => i.event === "error")).toEqual([]);
2115
+ expect(findLast(chunks, (i) => i.event === "values")).toMatchObject({
2116
+ data: {
2117
+ messages: [
2118
+ { type: "human", content: "input" },
2119
+ { type: "human", content: "map-reduce" },
2120
+ ],
2121
+ prompts: [
2122
+ { type: "ai", content: "first" },
2123
+ { type: "ai", content: "second" },
2124
+ { type: "ai", content: "third" },
2125
+ ],
2097
2126
  },
2098
2127
  });
2099
2128
  });
@@ -7,7 +7,7 @@ import {
7
7
  START,
8
8
  LangGraphRunnableConfig,
9
9
  } from "@langchain/langgraph";
10
- import { BaseMessage, ToolMessage } from "@langchain/core/messages";
10
+ import { AIMessage, BaseMessage, ToolMessage } from "@langchain/core/messages";
11
11
  import { FakeListChatModel } from "@langchain/core/utils/testing";
12
12
 
13
13
  const getStableModel = (() => {
@@ -25,6 +25,7 @@ const AgentState = Annotation.Root({
25
25
  key_two: Annotation<string>(),
26
26
  sleep: Annotation<number>(),
27
27
  messages: MessagesAnnotation.spec.messages,
28
+ prompts: MessagesAnnotation.spec.messages,
28
29
  });
29
30
 
30
31
  async function callModel(
@@ -47,6 +48,10 @@ async function callModel(
47
48
  };
48
49
  }
49
50
 
51
+ if (config.configurable?.["map-reduce"] != null) {
52
+ return { messages: ["map-reduce"] };
53
+ }
54
+
50
55
  const model = getStableModel(config.configurable?.thread_id ?? "$");
51
56
  const existing = await config.store?.get([userId ?? "ALL"], "key_one");
52
57
  if (!existing) {
@@ -68,15 +73,31 @@ async function callTool(
68
73
  return { messages: [response] };
69
74
  }
70
75
 
71
- function shouldContinue(state: typeof AgentState.State): typeof END | Send {
76
+ function shouldContinue(
77
+ state: typeof AgentState.State,
78
+ ): typeof END | Send | Send[] {
72
79
  const lastMessage = state.messages.at(-1);
73
80
  if ((lastMessage?.content as string).startsWith("end")) return END;
81
+ if ((lastMessage?.content as string).includes("map-reduce")) {
82
+ return [
83
+ new Send("map-reduce", { messages: [new AIMessage("first")] }),
84
+ new Send("map-reduce", { messages: [new AIMessage("second")] }),
85
+ new Send("map-reduce", { messages: [new AIMessage("third")] }),
86
+ ];
87
+ }
74
88
  return new Send("tool", lastMessage);
75
89
  }
76
90
 
91
+ function callMapReduce(state: {
92
+ messages: BaseMessage[];
93
+ }): typeof AgentState.Update {
94
+ return { prompts: state.messages.slice(-1) };
95
+ }
96
+
77
97
  const workflow = new StateGraph(AgentState)
78
98
  .addNode("agent", callModel)
79
99
  .addNode("tool", callTool)
100
+ .addNode("map-reduce", callMapReduce)
80
101
  .addEdge(START, "agent")
81
102
  .addConditionalEdges("agent", shouldContinue)
82
103
  .addEdge("tool", "agent");
@@ -256,10 +256,10 @@
256
256
  esbuild-plugin-tailwindcss "^2.0.1"
257
257
  zod "^3.23.8"
258
258
 
259
- "@langchain/langgraph@^0.2.64":
260
- version "0.2.64"
261
- resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.64.tgz#90f8c32bc34fbc3f7c1b04595f1e5165dd5a5fb4"
262
- integrity sha512-M6lh8ekDoZVCLdA10jeqIsU58LODDzXpP38aeXil5A5pg31IJp5L8O4yBfbp8mRobVX+Bbga5R5ZRyQBQl6NTg==
259
+ "@langchain/langgraph@^0.2.65":
260
+ version "0.2.65"
261
+ resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.65.tgz#f080add1c26a3eb3744af2ef23b8b53eddbf5cb2"
262
+ integrity sha512-g/Xap2KSEaEBXMJXGZTh31fd0qrdfaWA1l8NJzweJg6AkvVSf+d6DmMk9DtzGW8W1H1qQ2I6FWZ3AdP61Kkaig==
263
263
  dependencies:
264
264
  "@langchain/langgraph-checkpoint" "~0.0.17"
265
265
  "@langchain/langgraph-sdk" "~0.0.32"
@@ -40,7 +40,10 @@ RUN_COUNTER = 0
40
40
  NODE_COUNTER = 0
41
41
  FROM_TIMESTAMP = datetime.now(UTC).isoformat()
42
42
 
43
- if "api.smith.langchain.com" in LANGSMITH_AUTH_ENDPOINT:
43
+ if (
44
+ "api.smith.langchain.com" in LANGSMITH_AUTH_ENDPOINT
45
+ and not LANGGRAPH_CLOUD_LICENSE_KEY
46
+ ):
44
47
  METADATA_ENDPOINT = LANGSMITH_AUTH_ENDPOINT.rstrip("/") + "/v1/metadata/submit"
45
48
  else:
46
49
  METADATA_ENDPOINT = "https://api.smith.langchain.com/v1/metadata/submit"
@@ -100,20 +103,21 @@ async def metadata_loop() -> None:
100
103
  "from_timestamp": from_timestamp,
101
104
  "to_timestamp": to_timestamp,
102
105
  "tags": {
106
+ # Tag values must be strings.
103
107
  "langgraph.python.version": langgraph.version.__version__,
104
- "langgraph_api.version": __version__,
105
- "langgraph.platform.revision": REVISION,
106
- "langgraph.platform.variant": VARIANT,
108
+ "langgraph_api.version": __version__ or "",
109
+ "langgraph.platform.revision": REVISION or "",
110
+ "langgraph.platform.variant": VARIANT or "",
107
111
  "langgraph.platform.host": HOST,
108
- "langgraph.platform.tenant_id": TENANT_ID,
109
- "langgraph.platform.project_id": PROJECT_ID,
112
+ "langgraph.platform.tenant_id": TENANT_ID or "",
113
+ "langgraph.platform.project_id": PROJECT_ID or "",
110
114
  "langgraph.platform.plan": PLAN,
111
115
  # user app features
112
- "user_app.uses_indexing": USES_INDEXING,
113
- "user_app.uses_custom_app": USES_CUSTOM_APP,
114
- "user_app.uses_custom_auth": USES_CUSTOM_AUTH,
115
- "user_app.uses_thread_ttl": USES_THREAD_TTL,
116
- "user_app.uses_store_ttl": USES_STORE_TTL,
116
+ "user_app.uses_indexing": str(USES_INDEXING or ""),
117
+ "user_app.uses_custom_app": str(USES_CUSTOM_APP or ""),
118
+ "user_app.uses_custom_auth": str(USES_CUSTOM_AUTH),
119
+ "user_app.uses_thread_ttl": str(USES_THREAD_TTL),
120
+ "user_app.uses_store_ttl": str(USES_STORE_TTL),
117
121
  },
118
122
  "measures": {
119
123
  "langgraph.platform.runs": runs,
@@ -30,7 +30,6 @@ from langgraph_runtime.lifespan import lifespan
30
30
  from langgraph_api.middleware.http_logger import AccessLoggerMiddleware
31
31
  from langgraph_api.middleware.private_network import PrivateNetworkMiddleware
32
32
  from langgraph_api.utils import SchemaGenerator
33
- from langgraph_license.middleware import LicenseValidationMiddleware
34
33
  from langgraph_runtime.retry import OVERLOADED_EXCEPTIONS
35
34
  from langgraph_api.js.base import is_js_path
36
35
  from langgraph_sdk.client import configure_loopback_transports
@@ -68,7 +67,6 @@ middleware.extend(
68
67
  **config.CORS_CONFIG,
69
68
  )
70
69
  ),
71
- Middleware(LicenseValidationMiddleware),
72
70
  Middleware(AccessLoggerMiddleware, logger=logger),
73
71
  ]
74
72
  )
@@ -0,0 +1,119 @@
1
+ import atexit
2
+ import logging
3
+ import os
4
+ import platform as _platform
5
+ import re
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ import tarfile
10
+ import tempfile
11
+ import threading
12
+ import urllib.request
13
+ from concurrent.futures import Future
14
+ from pathlib import Path
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ CLOUDFLARED_VERSION = os.environ.get("LANGGRAPH_CLOUDFLARED_VERSION", "2025.2.1")
19
+ CACHE_DIR = (
20
+ Path(os.path.expanduser("~"))
21
+ / ".langgraph_api"
22
+ / "cloudflared"
23
+ / CLOUDFLARED_VERSION
24
+ )
25
+
26
+
27
+ def get_platform_arch():
28
+ plat = sys.platform
29
+ if plat.startswith("darwin"):
30
+ plat = "darwin"
31
+ elif plat.startswith("linux"):
32
+ plat = "linux"
33
+ elif plat.startswith("win"):
34
+ plat = "windows"
35
+ else:
36
+ raise RuntimeError(f"Unsupported platform: {sys.platform}")
37
+ arch = _platform.machine().lower()
38
+ if arch in ("x86_64", "amd64"):
39
+ arch = "amd64"
40
+ elif arch in ("arm64", "aarch64"):
41
+ arch = "arm64"
42
+ elif arch in ("i386", "i686", "x86"):
43
+ arch = "386"
44
+ else:
45
+ raise RuntimeError(f"Unsupported architecture: {_platform.machine()}")
46
+ return plat, arch
47
+
48
+
49
+ def ensure_cloudflared() -> Path:
50
+ plat, arch = get_platform_arch()
51
+ # determine file names and if archive
52
+ if plat == "windows":
53
+ file_name = f"cloudflared-windows-{arch}.exe"
54
+ is_archive = False
55
+ elif plat == "darwin":
56
+ file_name = f"cloudflared-darwin-{arch}.tgz"
57
+ is_archive = True
58
+ else: # linux
59
+ file_name = f"cloudflared-linux-{arch}"
60
+ is_archive = False
61
+
62
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
63
+ binary_name = "cloudflared" if is_archive else file_name
64
+ target_bin = CACHE_DIR / binary_name
65
+ if not target_bin.exists():
66
+ url = f"https://github.com/cloudflare/cloudflared/releases/download/{CLOUDFLARED_VERSION}/{file_name}"
67
+ logger.info(f"Downloading cloudflared from {url}")
68
+ with urllib.request.urlopen(url) as resp:
69
+ data = resp.read()
70
+ with tempfile.TemporaryDirectory() as tmpd:
71
+ tmpd = Path(tmpd)
72
+ path = tmpd / file_name
73
+ path.write_bytes(data)
74
+ if is_archive:
75
+ with tarfile.open(path) as tf:
76
+ tf.extractall(tmpd)
77
+ src = tmpd / "cloudflared"
78
+ else:
79
+ src = path
80
+ shutil.move(str(src), str(target_bin))
81
+ target_bin.chmod(0o755)
82
+ return target_bin
83
+
84
+
85
+ class CloudflareTunnel:
86
+ def __init__(self, process: subprocess.Popen, url_future: Future[str]):
87
+ self.process = process
88
+ self.url = url_future
89
+
90
+
91
+ def start_tunnel(port: int) -> CloudflareTunnel:
92
+ bin_path = ensure_cloudflared()
93
+ cmd = [str(bin_path), "tunnel", "--url", f"http://localhost:{port}"]
94
+ proc = subprocess.Popen(
95
+ cmd,
96
+ stdout=subprocess.PIPE,
97
+ stderr=subprocess.PIPE,
98
+ text=True,
99
+ bufsize=1,
100
+ )
101
+ atexit.register(proc.kill)
102
+
103
+ url_future: Future[str] = Future()
104
+
105
+ def _reader(stream):
106
+ for line in stream:
107
+ line = line.strip()
108
+ logger.info(f"[cloudflared] {line}")
109
+ if not url_future.done():
110
+ # match any trycloudflare.com host with optional subdomains
111
+ pattern = re.compile(r"(https://[A-Za-z0-9.-]+\.trycloudflare\.com)")
112
+ m = pattern.search(line)
113
+ if m:
114
+ url_future.set_result(m.group(1))
115
+
116
+ threading.Thread(target=_reader, args=(proc.stdout,), daemon=True).start()
117
+ threading.Thread(target=_reader, args=(proc.stderr,), daemon=True).start()
118
+
119
+ return CloudflareTunnel(proc, url_future)
@@ -0,0 +1,22 @@
1
+ """Noop license middleware"""
2
+
3
+
4
+ async def get_license_status() -> bool:
5
+ """Always return true"""
6
+ return True
7
+
8
+
9
+ def plus_features_enabled() -> bool:
10
+ """Always return false"""
11
+ return False
12
+
13
+
14
+ async def check_license_periodically(_: int = 60):
15
+ """
16
+ Periodically re-verify the license.
17
+ If the license ever fails, you could decide to log,
18
+ raise an exception, or attempt a graceful shutdown.
19
+ """
20
+ raise NotImplementedError(
21
+ "This is a noop license middleware. No license check is performed."
22
+ )
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-api"
3
- version = "0.1.9"
3
+ version = "0.1.12"
4
4
  description = ""
5
5
  authors = [
6
6
  "Nuno Campos <nuno@langchain.dev>",
@@ -1 +0,0 @@
1
- __version__ = "0.1.9"
@@ -1,21 +0,0 @@
1
- """Middleware for license validation."""
2
-
3
- from typing import Any
4
-
5
- from starlette.middleware.base import BaseHTTPMiddleware
6
- from starlette.requests import Request
7
- from starlette.responses import Response
8
- from starlette.types import ASGIApp
9
-
10
-
11
- class LicenseValidationMiddleware(BaseHTTPMiddleware):
12
- """Noop license middleware"""
13
-
14
- def __init__(self, app: ASGIApp):
15
- """Initialize middleware."""
16
- super().__init__(app)
17
-
18
- async def dispatch(self, request: Request, call_next: Any) -> Response:
19
- """Noop middleware."""
20
- response = await call_next(request)
21
- return response
@@ -1,11 +0,0 @@
1
- """Noop license middleware"""
2
-
3
-
4
- async def get_license_status() -> bool:
5
- """Always return true"""
6
- return True
7
-
8
-
9
- def plus_features_enabled() -> bool:
10
- """Always return false"""
11
- return False
File without changes
File without changes