langgraph-api 0.0.8__tar.gz → 0.0.9__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 (87) hide show
  1. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/PKG-INFO +2 -2
  2. langgraph_api-0.0.9/langgraph_api/api/openapi.py +69 -0
  3. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/api/runs.py +70 -36
  4. langgraph_api-0.0.9/langgraph_api/auth/custom.py +520 -0
  5. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/auth/middleware.py +8 -3
  6. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/cli.py +47 -1
  7. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/config.py +24 -0
  8. langgraph_api-0.0.9/langgraph_api/cron_scheduler.py +65 -0
  9. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/graph.py +0 -11
  10. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/models/run.py +77 -19
  11. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/route.py +2 -0
  12. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/utils.py +32 -0
  13. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_storage/checkpoint.py +16 -0
  14. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_storage/database.py +17 -1
  15. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_storage/ops.py +495 -69
  16. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/pyproject.toml +4 -4
  17. langgraph_api-0.0.8/langgraph_api/api/openapi.py +0 -32
  18. langgraph_api-0.0.8/langgraph_api/cron_scheduler.py +0 -60
  19. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/LICENSE +0 -0
  20. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/README.md +0 -0
  21. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/__init__.py +0 -0
  22. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/api/__init__.py +0 -0
  23. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/api/assistants.py +0 -0
  24. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/api/meta.py +0 -0
  25. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/api/store.py +0 -0
  26. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/api/threads.py +0 -0
  27. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/asyncio.py +0 -0
  28. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/auth/__init__.py +0 -0
  29. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/auth/langsmith/__init__.py +0 -0
  30. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/auth/langsmith/backend.py +0 -0
  31. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/auth/langsmith/client.py +0 -0
  32. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/auth/noop.py +0 -0
  33. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/errors.py +0 -0
  34. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/http.py +0 -0
  35. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/http_logger.py +0 -0
  36. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/.gitignore +0 -0
  37. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/build.mts +0 -0
  38. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/client.mts +0 -0
  39. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/global.d.ts +0 -0
  40. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/package.json +0 -0
  41. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/remote.py +0 -0
  42. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/server_sent_events.py +0 -0
  43. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/src/graph.mts +0 -0
  44. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/src/hooks.mjs +0 -0
  45. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/src/parser/parser.mts +0 -0
  46. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
  47. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/src/schema/types.mts +0 -0
  48. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/src/schema/types.template.mts +0 -0
  49. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/src/utils/importMap.mts +0 -0
  50. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  51. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/src/utils/serde.mts +0 -0
  52. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/api.test.mts +0 -0
  53. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/compose-postgres.yml +0 -0
  54. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/graphs/.gitignore +0 -0
  55. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/graphs/agent.mts +0 -0
  56. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/graphs/error.mts +0 -0
  57. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
  58. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/graphs/nested.mts +0 -0
  59. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/graphs/package.json +0 -0
  60. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/graphs/weather.mts +0 -0
  61. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/graphs/yarn.lock +0 -0
  62. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/parser.test.mts +0 -0
  63. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/tests/utils.mts +0 -0
  64. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/js/yarn.lock +0 -0
  65. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/lifespan.py +0 -0
  66. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/logging.py +0 -0
  67. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/metadata.py +0 -0
  68. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/models/__init__.py +0 -0
  69. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/patch.py +0 -0
  70. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/queue.py +0 -0
  71. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/schema.py +0 -0
  72. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/serde.py +0 -0
  73. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/server.py +0 -0
  74. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/sse.py +0 -0
  75. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/state.py +0 -0
  76. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/stream.py +0 -0
  77. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_api/validation.py +0 -0
  78. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_license/__init__.py +0 -0
  79. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_license/middleware.py +0 -0
  80. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_license/validation.py +0 -0
  81. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_storage/__init__.py +0 -0
  82. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_storage/queue.py +0 -0
  83. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_storage/retry.py +0 -0
  84. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_storage/store.py +0 -0
  85. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/langgraph_storage/ttl_dict.py +0 -0
  86. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/logging.json +0 -0
  87. {langgraph_api-0.0.8 → langgraph_api-0.0.9}/openapi.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-api
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -16,7 +16,7 @@ Requires-Dist: jsonschema-rs (>=0.25.0,<0.26.0)
16
16
  Requires-Dist: langchain-core (>=0.2.38,<0.4.0)
17
17
  Requires-Dist: langgraph (>=0.2.56,<0.3.0)
18
18
  Requires-Dist: langgraph-checkpoint (>=2.0.7,<3.0)
19
- Requires-Dist: langsmith (>=0.1.63,<0.2.0)
19
+ Requires-Dist: langsmith (>=0.1.63,<0.3.0)
20
20
  Requires-Dist: orjson (>=3.10.1)
21
21
  Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
22
22
  Requires-Dist: sse-starlette (>=2.1.0,<3.0.0)
@@ -0,0 +1,69 @@
1
+ import logging
2
+ from functools import lru_cache
3
+
4
+ import orjson
5
+
6
+ from langgraph_api.config import LANGGRAPH_AUTH, LANGGRAPH_AUTH_TYPE
7
+ from langgraph_api.graph import GRAPHS
8
+ from langgraph_api.validation import openapi
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @lru_cache(maxsize=1)
14
+ def get_openapi_spec() -> str:
15
+ # patch the graph_id enums
16
+ graph_ids = list(GRAPHS.keys())
17
+ for schema in (
18
+ "Assistant",
19
+ "AssistantCreate",
20
+ "AssistantPatch",
21
+ "GraphSchema",
22
+ "AssistantSearchRequest",
23
+ ):
24
+ openapi["components"]["schemas"][schema]["properties"]["graph_id"]["enum"] = (
25
+ graph_ids
26
+ )
27
+ # patch the auth schemes
28
+ if LANGGRAPH_AUTH_TYPE == "langsmith":
29
+ openapi["security"] = [
30
+ {"x-api-key": []},
31
+ ]
32
+ openapi["components"]["securitySchemes"] = {
33
+ "x-api-key": {"type": "apiKey", "in": "header", "name": "x-api-key"}
34
+ }
35
+ elif LANGGRAPH_AUTH_TYPE == "custom":
36
+ # Allow user to specify OpenAPI security configuration
37
+ if isinstance(LANGGRAPH_AUTH, dict) and "openapi" in LANGGRAPH_AUTH:
38
+ openapi_config = LANGGRAPH_AUTH["openapi"]
39
+ if isinstance(openapi_config, dict):
40
+ # Add security schemes
41
+ if "securitySchemes" in openapi_config:
42
+ openapi["components"]["securitySchemes"] = openapi_config[
43
+ "securitySchemes"
44
+ ]
45
+ elif "security_schemes" in openapi_config:
46
+ # For our sorry python users
47
+ openapi["components"]["securitySchemes"] = openapi_config[
48
+ "security_schemes"
49
+ ]
50
+
51
+ # Add default security if specified
52
+ if "security" in openapi_config:
53
+ openapi["security"] = openapi_config["security"]
54
+
55
+ if "paths" in openapi_config:
56
+ for path, methods in openapi_config["paths"].items():
57
+ if path in openapi["paths"]:
58
+ openapi_path = openapi["paths"][path]
59
+ for method, security in methods.items():
60
+ method = method.lower()
61
+ if method in openapi_path:
62
+ openapi_path[method]["security"] = security
63
+ else:
64
+ logger.warning(
65
+ "Custom authentication is enabled but no OpenAPI security configuration was provided. "
66
+ "API documentation will not show authentication requirements. "
67
+ "Add 'openapi' section to auth section of your `langgraph.json` file to specify security schemes."
68
+ )
69
+ return orjson.dumps(openapi)
@@ -24,13 +24,6 @@ from langgraph_storage.ops import Crons, Runs, Threads
24
24
  from langgraph_storage.retry import retry_db
25
25
 
26
26
 
27
- def get_user_id(request: ApiRequest) -> str | None:
28
- try:
29
- return request.user.display_name
30
- except AssertionError:
31
- return None
32
-
33
-
34
27
  @retry_db
35
28
  async def create_run(request: ApiRequest):
36
29
  """Create a run."""
@@ -41,7 +34,6 @@ async def create_run(request: ApiRequest):
41
34
  conn,
42
35
  thread_id,
43
36
  payload,
44
- get_user_id(request),
45
37
  request.headers,
46
38
  )
47
39
  return ApiResponse(run)
@@ -53,7 +45,10 @@ async def create_stateless_run(request: ApiRequest):
53
45
  payload = await request.json(RunCreateStateless)
54
46
  async with connect() as conn:
55
47
  run = await create_valid_run(
56
- conn, None, payload, get_user_id(request), request.headers
48
+ conn,
49
+ None,
50
+ payload,
51
+ request.headers,
57
52
  )
58
53
  return ApiResponse(run)
59
54
 
@@ -66,12 +61,15 @@ async def create_stateless_run_batch(request: ApiRequest):
66
61
  barrier = asyncio.Barrier(len(batch_payload))
67
62
  coros = [
68
63
  create_valid_run(
69
- conn, None, payload, get_user_id(request), request.headers, barrier
64
+ conn,
65
+ None,
66
+ payload,
67
+ request.headers,
68
+ barrier,
70
69
  )
71
70
  for payload in batch_payload
72
71
  ]
73
72
  runs = await asyncio.gather(*coros)
74
-
75
73
  return ApiResponse(runs)
76
74
 
77
75
 
@@ -91,7 +89,6 @@ async def stream_run(
91
89
  conn,
92
90
  thread_id,
93
91
  payload,
94
- get_user_id(request),
95
92
  request.headers,
96
93
  run_id=run_id,
97
94
  )
@@ -127,7 +124,6 @@ async def stream_run_stateless(
127
124
  conn,
128
125
  None,
129
126
  payload,
130
- get_user_id(request),
131
127
  request.headers,
132
128
  run_id=run_id,
133
129
  )
@@ -165,7 +161,6 @@ async def wait_run(request: ApiRequest):
165
161
  conn,
166
162
  thread_id,
167
163
  payload,
168
- get_user_id(request),
169
164
  request.headers,
170
165
  run_id=run_id,
171
166
  )
@@ -226,7 +221,6 @@ async def wait_run_stateless(request: ApiRequest):
226
221
  conn,
227
222
  None,
228
223
  payload,
229
- get_user_id(request),
230
224
  request.headers,
231
225
  run_id=run_id,
232
226
  )
@@ -284,6 +278,7 @@ async def list_runs_http(
284
278
  limit = int(request.query_params.get("limit", 10))
285
279
  offset = int(request.query_params.get("offset", 0))
286
280
  status = request.query_params.get("status")
281
+
287
282
  async with connect() as conn, conn.pipeline():
288
283
  thread, runs = await asyncio.gather(
289
284
  Threads.get(conn, thread_id),
@@ -307,10 +302,15 @@ async def get_run_http(request: ApiRequest):
307
302
  run_id = request.path_params["run_id"]
308
303
  validate_uuid(thread_id, "Invalid thread ID: must be a UUID")
309
304
  validate_uuid(run_id, "Invalid run ID: must be a UUID")
305
+
310
306
  async with connect() as conn, conn.pipeline():
311
307
  thread, run = await asyncio.gather(
312
308
  Threads.get(conn, thread_id),
313
- Runs.get(conn, run_id, thread_id=thread_id),
309
+ Runs.get(
310
+ conn,
311
+ run_id,
312
+ thread_id=thread_id,
313
+ ),
314
314
  )
315
315
  await fetchone(thread)
316
316
  return ApiResponse(await fetchone(run))
@@ -323,7 +323,13 @@ async def join_run(request: ApiRequest):
323
323
  run_id = request.path_params["run_id"]
324
324
  validate_uuid(thread_id, "Invalid thread ID: must be a UUID")
325
325
  validate_uuid(run_id, "Invalid run ID: must be a UUID")
326
- return ApiResponse(await Runs.join(run_id, thread_id=thread_id))
326
+
327
+ return ApiResponse(
328
+ await Runs.join(
329
+ run_id,
330
+ thread_id=thread_id,
331
+ )
332
+ )
327
333
 
328
334
 
329
335
  @retry_db
@@ -337,7 +343,9 @@ async def join_run_stream_endpoint(request: ApiRequest):
337
343
  validate_uuid(run_id, "Invalid run ID: must be a UUID")
338
344
  return EventSourceResponse(
339
345
  Runs.Stream.join(
340
- run_id, thread_id=thread_id, cancel_on_disconnect=cancel_on_disconnect
346
+ run_id,
347
+ thread_id=thread_id,
348
+ cancel_on_disconnect=cancel_on_disconnect,
341
349
  )
342
350
  )
343
351
 
@@ -355,10 +363,19 @@ async def cancel_run(
355
363
  wait = wait_str.lower() in {"true", "yes", "1"}
356
364
  action_str = request.query_params.get("action", "interrupt")
357
365
  action = action_str if action_str in {"interrupt", "rollback"} else "interrupt"
366
+
358
367
  async with connect() as conn:
359
- await Runs.cancel(conn, [run_id], action=action, thread_id=thread_id)
368
+ await Runs.cancel(
369
+ conn,
370
+ [run_id],
371
+ action=action,
372
+ thread_id=thread_id,
373
+ )
360
374
  if wait:
361
- await Runs.join(run_id, thread_id=thread_id)
375
+ await Runs.join(
376
+ run_id,
377
+ thread_id=thread_id,
378
+ )
362
379
  return Response(status_code=204 if wait else 202)
363
380
 
364
381
 
@@ -369,8 +386,13 @@ async def delete_run(request: ApiRequest):
369
386
  run_id = request.path_params["run_id"]
370
387
  validate_uuid(thread_id, "Invalid thread ID: must be a UUID")
371
388
  validate_uuid(run_id, "Invalid run ID: must be a UUID")
389
+
372
390
  async with connect() as conn:
373
- rid = await Runs.delete(conn, run_id, thread_id=thread_id)
391
+ rid = await Runs.delete(
392
+ conn,
393
+ run_id,
394
+ thread_id=thread_id,
395
+ )
374
396
  await fetchone(rid)
375
397
  return Response(status_code=204)
376
398
 
@@ -383,7 +405,6 @@ async def create_cron(request: ApiRequest):
383
405
  async with connect() as conn:
384
406
  cron = await Crons.put(
385
407
  conn,
386
- user_id=get_user_id(request),
387
408
  thread_id=None,
388
409
  end_time=payload.get("end_time"),
389
410
  schedule=payload.get("schedule"),
@@ -402,7 +423,6 @@ async def create_thread_cron(request: ApiRequest):
402
423
  async with connect() as conn:
403
424
  cron = await Crons.put(
404
425
  conn,
405
- user_id=get_user_id(request),
406
426
  thread_id=thread_id,
407
427
  end_time=payload.get("end_time"),
408
428
  schedule=payload.get("schedule"),
@@ -418,7 +438,10 @@ async def delete_cron(request: ApiRequest):
418
438
  validate_uuid(cron_id, "Invalid cron ID: must be a UUID")
419
439
 
420
440
  async with connect() as conn:
421
- cid = await Crons.delete(conn, cron_id)
441
+ cid = await Crons.delete(
442
+ conn,
443
+ cron_id=cron_id,
444
+ )
422
445
  await fetchone(cid)
423
446
  return Response(status_code=204)
424
447
 
@@ -431,6 +454,7 @@ async def search_crons(request: ApiRequest):
431
454
  validate_uuid(assistant_id, "Invalid assistant ID: must be a UUID")
432
455
  if thread_id := payload.get("thread_id"):
433
456
  validate_uuid(thread_id, "Invalid thread ID: must be a UUID")
457
+
434
458
  async with connect() as conn:
435
459
  crons_iter = await Crons.search(
436
460
  conn,
@@ -447,12 +471,16 @@ runs_routes = [
447
471
  ApiRoute("/runs/wait", wait_run_stateless, methods=["POST"]),
448
472
  ApiRoute("/runs", create_stateless_run, methods=["POST"]),
449
473
  ApiRoute("/runs/batch", create_stateless_run_batch, methods=["POST"]),
450
- ApiRoute("/runs/crons", create_cron, methods=["POST"])
451
- if config.FF_CRONS_ENABLED and plus_features_enabled()
452
- else None,
453
- ApiRoute("/runs/crons/search", search_crons, methods=["POST"])
454
- if config.FF_CRONS_ENABLED and plus_features_enabled()
455
- else None,
474
+ (
475
+ ApiRoute("/runs/crons", create_cron, methods=["POST"])
476
+ if config.FF_CRONS_ENABLED and plus_features_enabled()
477
+ else None
478
+ ),
479
+ (
480
+ ApiRoute("/runs/crons/search", search_crons, methods=["POST"])
481
+ if config.FF_CRONS_ENABLED and plus_features_enabled()
482
+ else None
483
+ ),
456
484
  ApiRoute("/threads/{thread_id}/runs/{run_id}/join", join_run, methods=["GET"]),
457
485
  ApiRoute(
458
486
  "/threads/{thread_id}/runs/{run_id}/stream",
@@ -465,13 +493,19 @@ runs_routes = [
465
493
  ApiRoute("/threads/{thread_id}/runs/stream", stream_run, methods=["POST"]),
466
494
  ApiRoute("/threads/{thread_id}/runs/wait", wait_run, methods=["POST"]),
467
495
  ApiRoute("/threads/{thread_id}/runs", create_run, methods=["POST"]),
468
- ApiRoute("/threads/{thread_id}/runs/crons", create_thread_cron, methods=["POST"])
469
- if config.FF_CRONS_ENABLED and plus_features_enabled()
470
- else None,
496
+ (
497
+ ApiRoute(
498
+ "/threads/{thread_id}/runs/crons", create_thread_cron, methods=["POST"]
499
+ )
500
+ if config.FF_CRONS_ENABLED and plus_features_enabled()
501
+ else None
502
+ ),
471
503
  ApiRoute("/threads/{thread_id}/runs", list_runs_http, methods=["GET"]),
472
- ApiRoute("/runs/crons/{cron_id}", delete_cron, methods=["DELETE"])
473
- if config.FF_CRONS_ENABLED and plus_features_enabled()
474
- else None,
504
+ (
505
+ ApiRoute("/runs/crons/{cron_id}", delete_cron, methods=["DELETE"])
506
+ if config.FF_CRONS_ENABLED and plus_features_enabled()
507
+ else None
508
+ ),
475
509
  ]
476
510
 
477
511
  runs_routes = [route for route in runs_routes if route is not None]