langgraph-api 0.2.132__tar.gz → 0.2.135__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 (120) hide show
  1. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/Makefile +2 -0
  2. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/PKG-INFO +2 -2
  3. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/benchmark/Makefile +8 -0
  4. langgraph_api-0.2.135/benchmark/clean.js +87 -0
  5. langgraph_api-0.2.135/benchmark/update-revision.js +148 -0
  6. langgraph_api-0.2.135/langgraph_api/__init__.py +1 -0
  7. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/api/assistants.py +26 -1
  8. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/api/runs.py +38 -1
  9. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/api/threads.py +27 -1
  10. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/package.json +2 -2
  11. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/yarn.lock +9 -9
  12. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/route.py +2 -1
  13. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/schema.py +70 -5
  14. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/utils/__init__.py +19 -0
  15. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/validation.py +9 -0
  16. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/openapi.json +331 -1
  17. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/pyproject.toml +1 -1
  18. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/uv.lock +76 -61
  19. langgraph_api-0.2.132/langgraph_api/__init__.py +0 -1
  20. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/.gitignore +0 -0
  21. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/LICENSE +0 -0
  22. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/README.md +0 -0
  23. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/benchmark/.gitignore +0 -0
  24. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/benchmark/README.md +0 -0
  25. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/benchmark/burst.js +0 -0
  26. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/benchmark/graphs.js +0 -0
  27. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/benchmark/package.json +0 -0
  28. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/benchmark/ramp.js +0 -0
  29. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/benchmark/weather.js +0 -0
  30. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/constraints.txt +0 -0
  31. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/forbidden.txt +0 -0
  32. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/healthcheck.py +0 -0
  33. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/api/__init__.py +0 -0
  34. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/api/mcp.py +0 -0
  35. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/api/meta.py +0 -0
  36. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/api/openapi.py +0 -0
  37. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/api/store.py +0 -0
  38. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/api/ui.py +0 -0
  39. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/asgi_transport.py +0 -0
  40. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/asyncio.py +0 -0
  41. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/auth/__init__.py +0 -0
  42. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/auth/custom.py +0 -0
  43. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/auth/langsmith/__init__.py +0 -0
  44. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/auth/langsmith/backend.py +0 -0
  45. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/auth/langsmith/client.py +0 -0
  46. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/auth/middleware.py +0 -0
  47. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/auth/noop.py +0 -0
  48. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/auth/studio_user.py +0 -0
  49. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/cli.py +0 -0
  50. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/command.py +0 -0
  51. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/config.py +0 -0
  52. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/cron_scheduler.py +0 -0
  53. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/errors.py +0 -0
  54. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/executor_entrypoint.py +0 -0
  55. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/feature_flags.py +0 -0
  56. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/graph.py +0 -0
  57. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/http.py +0 -0
  58. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/http_metrics.py +0 -0
  59. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/.gitignore +0 -0
  60. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/.prettierrc +0 -0
  61. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/__init__.py +0 -0
  62. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/base.py +0 -0
  63. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/build.mts +0 -0
  64. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/client.http.mts +0 -0
  65. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/client.mts +0 -0
  66. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/errors.py +0 -0
  67. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/global.d.ts +0 -0
  68. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/remote.py +0 -0
  69. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/schema.py +0 -0
  70. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/src/graph.mts +0 -0
  71. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/src/load.hooks.mjs +0 -0
  72. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/src/preload.mjs +0 -0
  73. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/src/utils/files.mts +0 -0
  74. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/src/utils/importMap.mts +0 -0
  75. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  76. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/src/utils/serde.mts +0 -0
  77. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/sse.py +0 -0
  78. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/traceblock.mts +0 -0
  79. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/tsconfig.json +0 -0
  80. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/js/ui.py +0 -0
  81. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/logging.py +0 -0
  82. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/metadata.py +0 -0
  83. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/middleware/__init__.py +0 -0
  84. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/middleware/http_logger.py +0 -0
  85. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/middleware/private_network.py +0 -0
  86. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/middleware/request_id.py +0 -0
  87. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/models/__init__.py +0 -0
  88. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/models/run.py +0 -0
  89. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/patch.py +0 -0
  90. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/queue_entrypoint.py +0 -0
  91. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/serde.py +0 -0
  92. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/server.py +0 -0
  93. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/sse.py +0 -0
  94. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/state.py +0 -0
  95. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/store.py +0 -0
  96. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/stream.py +0 -0
  97. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/thread_ttl.py +0 -0
  98. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/traceblock.py +0 -0
  99. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/tunneling/cloudflare.py +0 -0
  100. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/utils/cache.py +0 -0
  101. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/utils/config.py +0 -0
  102. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/utils/future.py +0 -0
  103. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/utils/headers.py +0 -0
  104. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/utils/uuids.py +0 -0
  105. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/utils.py +0 -0
  106. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/webhook.py +0 -0
  107. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_api/worker.py +0 -0
  108. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_license/__init__.py +0 -0
  109. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_license/validation.py +0 -0
  110. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_runtime/__init__.py +0 -0
  111. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_runtime/checkpoint.py +0 -0
  112. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_runtime/database.py +0 -0
  113. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_runtime/lifespan.py +0 -0
  114. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_runtime/metrics.py +0 -0
  115. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_runtime/ops.py +0 -0
  116. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_runtime/queue.py +0 -0
  117. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_runtime/retry.py +0 -0
  118. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/langgraph_runtime/store.py +0 -0
  119. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/logging.json +0 -0
  120. {langgraph_api-0.2.132 → langgraph_api-0.2.135}/scripts/create_license.py +0 -0
@@ -41,6 +41,8 @@ test-watch-oss:
41
41
 
42
42
  test: test-license-oss
43
43
  test-watch: test-watch-oss
44
+ unit-test:
45
+ DATABASE_URI="test" REDIS_URI="test" uv run pytest tests/unit_tests
44
46
 
45
47
  test-auth:
46
48
  LANGGRAPH_RUNTIME_EDITION=inmem LANGGRAPH_AUTH='{"path": "./tests/graphs/fastapi_jwt_auth.py:get_current_active_user"}' REDIS_URI=_FAKE DATABASE_URI=:memory: MIGRATIONS_PATH=__inmem__ uv run pytest -v $(AUTH_TEST)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.2.132
3
+ Version: 0.2.135
4
4
  Author-email: Nuno Campos <nuno@langchain.dev>, Will Fu-Hinthorn <will@langchain.dev>
5
5
  License: Elastic-2.0
6
6
  License-File: LICENSE
@@ -11,7 +11,7 @@ Requires-Dist: httpx>=0.25.0
11
11
  Requires-Dist: jsonschema-rs<0.30,>=0.20.0
12
12
  Requires-Dist: langchain-core>=0.3.64
13
13
  Requires-Dist: langgraph-checkpoint>=2.0.23
14
- Requires-Dist: langgraph-runtime-inmem<0.7,>=0.6.13
14
+ Requires-Dist: langgraph-runtime-inmem<0.9.0,>=0.8.0
15
15
  Requires-Dist: langgraph-sdk>=0.2.0
16
16
  Requires-Dist: langgraph>=0.4.0
17
17
  Requires-Dist: langsmith>=0.3.45
@@ -1,14 +1,22 @@
1
1
  # Benchmark commands
2
2
  benchmark-burst:
3
+ make benchmark-reset
3
4
  k6 run burst.js
4
5
 
5
6
  benchmark-ramp:
7
+ make benchmark-reset
6
8
  k6 run --out json=raw_data_$(shell date +%Y-%m-%dT%H-%M-%S).json ramp.js
7
9
 
8
10
  benchmark-charts:
9
11
  npm install
10
12
  node graphs.js $(shell ls -t raw_data_*.json | head -1) true
11
13
 
14
+ benchmark-reset:
15
+ node clean.js
16
+
17
+ benchmark-new-revision:
18
+ node update-revision.js
19
+
12
20
  benchmark-clean:
13
21
  rm -f results_*.json summary_*.json raw_data_*.json *_chart_*.png
14
22
 
@@ -0,0 +1,87 @@
1
+ /*
2
+ * Delete all threads and runs from the last benchmark run for consistent tests
3
+ * The default benchmark server has a thread TTL of one hour that should clean things up too so this doesn't run too long.
4
+ */
5
+
6
+ // URL of your LangGraph server
7
+ const BASE_URL = process.env.BASE_URL || 'http://localhost:9123';
8
+ // LangSmith API key only needed with a custom server endpoint
9
+ const LANGSMITH_API_KEY = process.env.LANGSMITH_API_KEY;
10
+
11
+ async function clean() {
12
+ const headers = { 'Content-Type': 'application/json' };
13
+ if (LANGSMITH_API_KEY) {
14
+ headers['x-api-key'] = LANGSMITH_API_KEY;
15
+ }
16
+
17
+ const searchUrl = `${BASE_URL}/threads/search`;
18
+ let totalDeleted = 0;
19
+
20
+ try {
21
+ console.log('Starting thread cleanup...');
22
+
23
+ while (true) {
24
+ try {
25
+ // Get the next page of threads
26
+ console.log('Searching for threads...');
27
+ const searchResponse = await fetch(searchUrl, {
28
+ method: 'POST',
29
+ headers,
30
+ body: JSON.stringify({
31
+ limit: 1000
32
+ })
33
+ });
34
+
35
+ if (!searchResponse.ok) {
36
+ throw new Error(`Search request failed: ${searchResponse.status} ${searchResponse.statusText}`);
37
+ }
38
+
39
+ const threads = await searchResponse.json();
40
+
41
+ // If no threads found, we're done
42
+ if (!threads || threads.length === 0) {
43
+ console.log('No more threads found.');
44
+ break;
45
+ }
46
+
47
+ console.log(`Found ${threads.length} threads to delete`);
48
+
49
+ // Delete each thread
50
+ for (const thread of threads) {
51
+ try {
52
+ const deleteUrl = `${BASE_URL}/threads/${thread.thread_id}`;
53
+ const deleteResponse = await fetch(deleteUrl, {
54
+ method: 'DELETE',
55
+ headers
56
+ });
57
+
58
+ if (!deleteResponse.ok) {
59
+ console.error(`Failed to delete thread ${thread.thread_id}: ${deleteResponse.status} ${deleteResponse.statusText}`);
60
+ } else {
61
+ totalDeleted++;
62
+ }
63
+ } catch (deleteError) {
64
+ console.error(`Error deleting thread ${thread.thread_id}:`, deleteError.message);
65
+ }
66
+ }
67
+
68
+ console.log(`Deleted ${threads.length} threads in this batch`);
69
+
70
+ } catch (batchError) {
71
+ console.error('Error in batch processing:', batchError.message);
72
+ break;
73
+ }
74
+ }
75
+
76
+ console.log(`Cleanup completed. Total threads deleted: ${totalDeleted}`);
77
+
78
+ } catch (error) {
79
+ console.error('Fatal error during cleanup:', error.message);
80
+ process.exit(1);
81
+ }
82
+ }
83
+
84
+ clean().catch(error => {
85
+ console.error('Unhandled error:', error.message);
86
+ process.exit(1);
87
+ });
@@ -0,0 +1,148 @@
1
+ /*
2
+ * Trigger a new revision deployment and wait for it to complete
3
+ * This ensures the benchmark instance is running the latest code
4
+ */
5
+
6
+ // LangSmith API endpoints and credentials
7
+ const DEPLOYMENT_ID = process.env.DEPLOYMENT_ID || 'a23f03ff-6d4d-4efd-8149-bb5a7f3b95cf'; // jdr-benchmark deployment id
8
+ const LANGSMITH_API_KEY = process.env.LANGSMITH_API_KEY;
9
+ const API_BASE = 'https://api.host.langchain.com/v1';
10
+
11
+ // Deployment configuration
12
+ const REVISION_CONFIG = {
13
+ repo_path: "langgraph.json",
14
+ env_vars: [
15
+ {
16
+ name: "N_JOBS_PER_WORKER",
17
+ value: "100",
18
+ value_from: "secret",
19
+ type: "secret"
20
+ }
21
+ ],
22
+ shareable: false,
23
+ container_spec: {
24
+ cpu: null,
25
+ memory_mb: null,
26
+ min_scale: 10,
27
+ max_scale: null
28
+ }
29
+ };
30
+
31
+ // Expected deployment statuses in order
32
+ const EXPECTED_STATUSES = ['CREATED', 'AWAITING_BUILD', 'BUILDING', 'AWAITING_DEPLOY', 'DEPLOYING', 'DEPLOYED', 'QUEUED'];
33
+ const FINAL_STATUS = 'DEPLOYED';
34
+ const POLL_INTERVAL = 10000; // 10 seconds
35
+ const MAX_WAIT_TIME = 30 * 60 * 1000; // 30 minutes
36
+
37
+ async function updateRevision() {
38
+ if (!LANGSMITH_API_KEY) {
39
+ console.error('LANGSMITH_API_KEY environment variable is required');
40
+ process.exit(1);
41
+ }
42
+
43
+ const headers = {
44
+ 'Content-Type': 'application/json',
45
+ 'x-api-key': LANGSMITH_API_KEY
46
+ };
47
+
48
+ const createUrl = `${API_BASE}/projects/${DEPLOYMENT_ID}/revisions`;
49
+ const pollUrl = `${API_BASE}/projects/${DEPLOYMENT_ID}/revisions?limit=1&offset=0`;
50
+
51
+ try {
52
+ console.log('Triggering new revision deployment...');
53
+
54
+ // Step 1: Create new revision
55
+ const createResponse = await fetch(createUrl, {
56
+ method: 'POST',
57
+ headers,
58
+ body: JSON.stringify(REVISION_CONFIG)
59
+ });
60
+
61
+ if (createResponse.status === 409) {
62
+ console.log('⚠️ A new revision is already in progress. Continuing to poll existing deployment...');
63
+ } else if (!createResponse.ok) {
64
+ throw new Error(`Failed to create revision: ${createResponse.status} ${createResponse.statusText}`);
65
+ } else {
66
+ const newRevision = await createResponse.json();
67
+ const revisionId = newRevision.resource.latest_revision.hosted_langserve_revision_id;
68
+ console.log(`✓ New revision created: ${revisionId}`);
69
+ console.log(`Initial status: ${newRevision.status}`);
70
+ }
71
+
72
+ // Step 2: Poll until deployment is complete
73
+ console.log('\nPolling deployment status...');
74
+ const startTime = Date.now();
75
+ let lastStatus = null;
76
+
77
+ while (true) {
78
+ let pollResponse;
79
+ try {
80
+ pollResponse = await fetch(pollUrl, {
81
+ method: 'GET',
82
+ headers
83
+ });
84
+
85
+ if (!pollResponse.ok) {
86
+ throw new Error(`Failed to poll revisions: ${pollResponse.status} ${pollResponse.statusText}`);
87
+ }
88
+ } catch (pollError) {
89
+ console.error('Error calling poll endpoint (will retry):', pollError.message);
90
+ // Wait before next poll and continue
91
+ await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));
92
+ continue;
93
+ }
94
+
95
+ // Parse response and handle deployment logic (these errors should still be thrown)
96
+ const revisions = await pollResponse.json();
97
+
98
+ if (!revisions || revisions.length === 0) {
99
+ throw new Error('No revisions found');
100
+ }
101
+
102
+ const latestRevision = revisions[0];
103
+ const currentStatus = latestRevision.status;
104
+
105
+ // Log status changes
106
+ if (currentStatus !== lastStatus) {
107
+ const timestamp = new Date().toISOString();
108
+ console.log(`[${timestamp}] Status: ${currentStatus}`);
109
+
110
+ if (latestRevision.status_message) {
111
+ console.log(` Message: ${latestRevision.status_message}`);
112
+ }
113
+
114
+ lastStatus = currentStatus;
115
+ }
116
+
117
+ // Check if deployment is complete
118
+ if (currentStatus === FINAL_STATUS) {
119
+ console.log(`\n✓ Deployment completed successfully!`);
120
+ console.log(`Revision ID: ${latestRevision.id}`);
121
+ console.log(`Total time: ${Math.round((Date.now() - startTime) / 1000)}s`);
122
+ break;
123
+ }
124
+
125
+ // Check for failure statuses
126
+ if (!EXPECTED_STATUSES.includes(currentStatus)) {
127
+ throw new Error(`Deployment failed with status: ${currentStatus}${latestRevision.status_message ? ` - ${latestRevision.status_message}` : ''}`);
128
+ }
129
+
130
+ // Check timeout
131
+ if (Date.now() - startTime > MAX_WAIT_TIME) {
132
+ throw new Error(`Deployment timeout after ${MAX_WAIT_TIME / 60000} minutes. Last status: ${currentStatus}`);
133
+ }
134
+
135
+ // Wait before next poll
136
+ await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));
137
+ }
138
+
139
+ } catch (error) {
140
+ console.error('Fatal error during revision update:', error.message);
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ updateRevision().catch(error => {
146
+ console.error('Unhandled error:', error.message);
147
+ process.exit(1);
148
+ });
@@ -0,0 +1 @@
1
+ __version__ = "0.2.135"
@@ -16,9 +16,16 @@ from langgraph_api.feature_flags import USE_RUNTIME_CONTEXT_API
16
16
  from langgraph_api.graph import get_assistant_id, get_graph
17
17
  from langgraph_api.js.base import BaseRemotePregel
18
18
  from langgraph_api.route import ApiRequest, ApiResponse, ApiRoute
19
+ from langgraph_api.schema import ASSISTANT_FIELDS
19
20
  from langgraph_api.serde import ajson_loads
20
- from langgraph_api.utils import fetchone, get_pagination_headers, validate_uuid
21
+ from langgraph_api.utils import (
22
+ fetchone,
23
+ get_pagination_headers,
24
+ validate_select_columns,
25
+ validate_uuid,
26
+ )
21
27
  from langgraph_api.validation import (
28
+ AssistantCountRequest,
22
29
  AssistantCreate,
23
30
  AssistantPatch,
24
31
  AssistantSearchRequest,
@@ -173,6 +180,7 @@ async def search_assistants(
173
180
  ) -> ApiResponse:
174
181
  """List assistants."""
175
182
  payload = await request.json(AssistantSearchRequest)
183
+ select = validate_select_columns(payload.get("select") or None, ASSISTANT_FIELDS)
176
184
  offset = int(payload.get("offset") or 0)
177
185
  async with connect() as conn:
178
186
  assistants_iter, next_offset = await Assistants.search(
@@ -183,6 +191,7 @@ async def search_assistants(
183
191
  offset=offset,
184
192
  sort_by=payload.get("sort_by"),
185
193
  sort_order=payload.get("sort_order"),
194
+ select=select,
186
195
  )
187
196
  assistants, response_headers = await get_pagination_headers(
188
197
  assistants_iter, next_offset, offset
@@ -190,6 +199,21 @@ async def search_assistants(
190
199
  return ApiResponse(assistants, headers=response_headers)
191
200
 
192
201
 
202
+ @retry_db
203
+ async def count_assistants(
204
+ request: ApiRequest,
205
+ ) -> ApiResponse:
206
+ """Count assistants."""
207
+ payload = await request.json(AssistantCountRequest)
208
+ async with connect() as conn:
209
+ count = await Assistants.count(
210
+ conn,
211
+ graph_id=payload.get("graph_id"),
212
+ metadata=payload.get("metadata"),
213
+ )
214
+ return ApiResponse(count)
215
+
216
+
193
217
  @retry_db
194
218
  async def get_assistant(
195
219
  request: ApiRequest,
@@ -415,6 +439,7 @@ async def set_latest_assistant_version(request: ApiRequest) -> ApiResponse:
415
439
  assistants_routes: list[BaseRoute] = [
416
440
  ApiRoute("/assistants", create_assistant, methods=["POST"]),
417
441
  ApiRoute("/assistants/search", search_assistants, methods=["POST"]),
442
+ ApiRoute("/assistants/count", count_assistants, methods=["POST"]),
418
443
  ApiRoute(
419
444
  "/assistants/{assistant_id}/latest",
420
445
  set_latest_assistant_version,
@@ -10,9 +10,17 @@ from langgraph_api import config
10
10
  from langgraph_api.asyncio import ValueEvent, aclosing
11
11
  from langgraph_api.models.run import create_valid_run
12
12
  from langgraph_api.route import ApiRequest, ApiResponse, ApiRoute
13
+ from langgraph_api.schema import CRON_FIELDS, RUN_FIELDS
13
14
  from langgraph_api.sse import EventSourceResponse
14
- from langgraph_api.utils import fetchone, get_pagination_headers, uuid7, validate_uuid
15
+ from langgraph_api.utils import (
16
+ fetchone,
17
+ get_pagination_headers,
18
+ uuid7,
19
+ validate_select_columns,
20
+ validate_uuid,
21
+ )
15
22
  from langgraph_api.validation import (
23
+ CronCountRequest,
16
24
  CronCreate,
17
25
  CronSearch,
18
26
  RunBatchCreate,
@@ -337,6 +345,9 @@ async def list_runs(
337
345
  limit = int(request.query_params.get("limit", 10))
338
346
  offset = int(request.query_params.get("offset", 0))
339
347
  status = request.query_params.get("status")
348
+ select = validate_select_columns(
349
+ request.query_params.getlist("select") or None, RUN_FIELDS
350
+ )
340
351
 
341
352
  async with connect() as conn, conn.pipeline():
342
353
  thread, runs = await asyncio.gather(
@@ -347,6 +358,7 @@ async def list_runs(
347
358
  limit=limit,
348
359
  offset=offset,
349
360
  status=status,
361
+ select=select,
350
362
  ),
351
363
  )
352
364
  await fetchone(thread)
@@ -560,6 +572,7 @@ async def delete_cron(request: ApiRequest):
560
572
  async def search_crons(request: ApiRequest):
561
573
  """List all cron jobs for an assistant"""
562
574
  payload = await request.json(CronSearch)
575
+ select = validate_select_columns(payload.get("select") or None, CRON_FIELDS)
563
576
  if assistant_id := payload.get("assistant_id"):
564
577
  validate_uuid(assistant_id, "Invalid assistant ID: must be a UUID")
565
578
  if thread_id := payload.get("thread_id"):
@@ -575,6 +588,7 @@ async def search_crons(request: ApiRequest):
575
588
  offset=offset,
576
589
  sort_by=payload.get("sort_by"),
577
590
  sort_order=payload.get("sort_order"),
591
+ select=select,
578
592
  )
579
593
  crons, response_headers = await get_pagination_headers(
580
594
  crons_iter, next_offset, offset
@@ -582,6 +596,24 @@ async def search_crons(request: ApiRequest):
582
596
  return ApiResponse(crons, headers=response_headers)
583
597
 
584
598
 
599
+ @retry_db
600
+ async def count_crons(request: ApiRequest):
601
+ """Count cron jobs."""
602
+ payload = await request.json(CronCountRequest)
603
+ if assistant_id := payload.get("assistant_id"):
604
+ validate_uuid(assistant_id, "Invalid assistant ID: must be a UUID")
605
+ if thread_id := payload.get("thread_id"):
606
+ validate_uuid(thread_id, "Invalid thread ID: must be a UUID")
607
+
608
+ async with connect() as conn:
609
+ count = await Crons.count(
610
+ conn,
611
+ assistant_id=assistant_id,
612
+ thread_id=thread_id,
613
+ )
614
+ return ApiResponse(count)
615
+
616
+
585
617
  runs_routes = [
586
618
  ApiRoute("/runs/stream", stream_run_stateless, methods=["POST"]),
587
619
  ApiRoute("/runs/wait", wait_run_stateless, methods=["POST"]),
@@ -598,6 +630,11 @@ runs_routes = [
598
630
  if config.FF_CRONS_ENABLED and plus_features_enabled()
599
631
  else None
600
632
  ),
633
+ (
634
+ ApiRoute("/runs/crons/count", count_crons, methods=["POST"])
635
+ if config.FF_CRONS_ENABLED and plus_features_enabled()
636
+ else None
637
+ ),
601
638
  ApiRoute("/threads/{thread_id}/runs/{run_id}/join", join_run, methods=["GET"]),
602
639
  ApiRoute(
603
640
  "/threads/{thread_id}/runs/{run_id}/stream",
@@ -5,9 +5,16 @@ from starlette.responses import Response
5
5
  from starlette.routing import BaseRoute
6
6
 
7
7
  from langgraph_api.route import ApiRequest, ApiResponse, ApiRoute
8
+ from langgraph_api.schema import THREAD_FIELDS
8
9
  from langgraph_api.state import state_snapshot_to_thread_state
9
- from langgraph_api.utils import fetchone, get_pagination_headers, validate_uuid
10
+ from langgraph_api.utils import (
11
+ fetchone,
12
+ get_pagination_headers,
13
+ validate_select_columns,
14
+ validate_uuid,
15
+ )
10
16
  from langgraph_api.validation import (
17
+ ThreadCountRequest,
11
18
  ThreadCreate,
12
19
  ThreadPatch,
13
20
  ThreadSearchRequest,
@@ -58,6 +65,7 @@ async def search_threads(
58
65
  ):
59
66
  """List threads."""
60
67
  payload = await request.json(ThreadSearchRequest)
68
+ select = validate_select_columns(payload.get("select") or None, THREAD_FIELDS)
61
69
  limit = int(payload.get("limit") or 10)
62
70
  offset = int(payload.get("offset") or 0)
63
71
  async with connect() as conn:
@@ -70,6 +78,7 @@ async def search_threads(
70
78
  offset=offset,
71
79
  sort_by=payload.get("sort_by"),
72
80
  sort_order=payload.get("sort_order"),
81
+ select=select,
73
82
  )
74
83
  threads, response_headers = await get_pagination_headers(
75
84
  threads_iter, next_offset, offset
@@ -77,6 +86,22 @@ async def search_threads(
77
86
  return ApiResponse(threads, headers=response_headers)
78
87
 
79
88
 
89
+ @retry_db
90
+ async def count_threads(
91
+ request: ApiRequest,
92
+ ):
93
+ """Count threads."""
94
+ payload = await request.json(ThreadCountRequest)
95
+ async with connect() as conn:
96
+ count = await Threads.count(
97
+ conn,
98
+ status=payload.get("status"),
99
+ values=payload.get("values"),
100
+ metadata=payload.get("metadata"),
101
+ )
102
+ return ApiResponse(count)
103
+
104
+
80
105
  @retry_db
81
106
  async def get_thread_state(
82
107
  request: ApiRequest,
@@ -260,6 +285,7 @@ async def copy_thread(request: ApiRequest):
260
285
  threads_routes: list[BaseRoute] = [
261
286
  ApiRoute("/threads", endpoint=create_thread, methods=["POST"]),
262
287
  ApiRoute("/threads/search", endpoint=search_threads, methods=["POST"]),
288
+ ApiRoute("/threads/count", endpoint=count_threads, methods=["POST"]),
263
289
  ApiRoute("/threads/{thread_id}", endpoint=get_thread, methods=["GET"]),
264
290
  ApiRoute("/threads/{thread_id}", endpoint=patch_thread, methods=["PATCH"]),
265
291
  ApiRoute("/threads/{thread_id}", endpoint=delete_thread, methods=["DELETE"]),
@@ -11,8 +11,8 @@
11
11
  "@hono/zod-validator": "^0.2.2",
12
12
  "@langchain/core": "^0.3.59",
13
13
  "@langchain/langgraph": "^0.2.65",
14
- "@langchain/langgraph-api": "~0.0.56",
15
- "@langchain/langgraph-ui": "~0.0.56",
14
+ "@langchain/langgraph-api": "~0.0.59",
15
+ "@langchain/langgraph-ui": "~0.0.59",
16
16
  "@langchain/langgraph-checkpoint": "~0.0.18",
17
17
  "@types/json-schema": "^7.0.15",
18
18
  "@typescript/vfs": "^1.6.0",
@@ -203,15 +203,15 @@
203
203
  zod "^3.25.32"
204
204
  zod-to-json-schema "^3.22.3"
205
205
 
206
- "@langchain/langgraph-api@~0.0.56":
207
- version "0.0.56"
208
- resolved "https://registry.yarnpkg.com/@langchain/langgraph-api/-/langgraph-api-0.0.56.tgz#9e0a6e5dd9af2f6ec259a32ca9081b19457a0825"
209
- integrity sha512-BhrbMKSc3f4CXXDXgvHR8mE2bsgD/G6dDzr+yDkEC7a9eeFI7C8bPhkGmegFJ+q7i6GWtqlQOYSCWHqQFZxynQ==
206
+ "@langchain/langgraph-api@~0.0.59":
207
+ version "0.0.59"
208
+ resolved "https://registry.yarnpkg.com/@langchain/langgraph-api/-/langgraph-api-0.0.59.tgz#a3d69b8cc68ceebd8a8d86b77abd03924ddcd02c"
209
+ integrity sha512-pUt3yKB2z1nsXdhqpRQgVefYg5MdJVsqH64gTNzSb/JYJBPIUj2h8XttRx9+3mHtVqCzvL2bb+HVIterNFOKtw==
210
210
  dependencies:
211
211
  "@babel/code-frame" "^7.26.2"
212
212
  "@hono/node-server" "^1.12.0"
213
213
  "@hono/zod-validator" "^0.2.2"
214
- "@langchain/langgraph-ui" "0.0.56"
214
+ "@langchain/langgraph-ui" "0.0.59"
215
215
  "@types/json-schema" "^7.0.15"
216
216
  "@typescript/vfs" "^1.6.0"
217
217
  dedent "^1.5.3"
@@ -256,10 +256,10 @@
256
256
  p-retry "4"
257
257
  uuid "^9.0.0"
258
258
 
259
- "@langchain/langgraph-ui@0.0.56", "@langchain/langgraph-ui@~0.0.56":
260
- version "0.0.56"
261
- resolved "https://registry.yarnpkg.com/@langchain/langgraph-ui/-/langgraph-ui-0.0.56.tgz#b566afa4ad940fc6bd942205a00b3afac7fd7363"
262
- integrity sha512-cRU+fMCz1NAOjRLcreNa64Gap89yio0p8baRigehtL1SNsXJEaZ2xdGjbnjaWIl8rTrriDak4oisx07whwcBjQ==
259
+ "@langchain/langgraph-ui@0.0.59", "@langchain/langgraph-ui@~0.0.59":
260
+ version "0.0.59"
261
+ resolved "https://registry.yarnpkg.com/@langchain/langgraph-ui/-/langgraph-ui-0.0.59.tgz#41988eae48c7520c5ebfa9bcdda65ddadfe1aab9"
262
+ integrity sha512-x6Jqt7TZRfHGU0MyxVbz7ugucT/oJQm9kM+DlGdmfAEm2JrNqgy1W85E589xJCJjpuRiDLpP7NiqSwnp8lCEqg==
263
263
  dependencies:
264
264
  "@commander-js/extra-typings" "^13.0.0"
265
265
  commander "^13.0.0"
@@ -97,7 +97,8 @@ class ApiRoute(Route):
97
97
  include_in_schema: bool = True,
98
98
  middleware: typing.Sequence[Middleware] | None = None,
99
99
  ) -> None:
100
- assert path.startswith("/"), "Routed paths must start with '/'"
100
+ if not path.startswith("/"):
101
+ raise ValueError("Routed paths must start with '/'")
101
102
  self.path = path
102
103
  self.endpoint = endpoint
103
104
  self.name = get_name(endpoint) if name is None else name
@@ -1,6 +1,6 @@
1
1
  from collections.abc import Sequence
2
2
  from datetime import datetime
3
- from typing import Any, Literal, Optional, TypeAlias
3
+ from typing import Any, Literal, NotRequired, Optional, TypeAlias
4
4
  from uuid import UUID
5
5
 
6
6
  from langchain_core.runnables.config import RunnableConfig
@@ -121,6 +121,8 @@ class Thread(TypedDict):
121
121
  """The thread metadata."""
122
122
  config: Fragment
123
123
  """The thread config."""
124
+ context: Fragment
125
+ """The thread context."""
124
126
  status: ThreadStatus
125
127
  """The status of the thread. One of 'idle', 'busy', 'interrupted', "error"."""
126
128
  values: Fragment
@@ -223,16 +225,16 @@ class Cron(TypedDict):
223
225
  """The time the cron was created."""
224
226
  updated_at: datetime
225
227
  """The last time the cron was updated."""
226
- user_id: UUID | None
227
- """The ID of the user."""
228
+ user_id: str | None
229
+ """The ID of the user (string identity)."""
228
230
  payload: Fragment
229
231
  """The run payload to use for creating new run."""
230
232
  next_run_date: datetime
231
233
  """The next run date of the cron."""
232
234
  metadata: Fragment
233
235
  """The cron metadata."""
234
- now: datetime
235
- """The current time."""
236
+ now: NotRequired[datetime]
237
+ """The current time (present in internal next() only)."""
236
238
 
237
239
 
238
240
  class ThreadUpdateResponse(TypedDict):
@@ -246,3 +248,66 @@ class QueueStats(TypedDict):
246
248
  n_running: int
247
249
  max_age_secs: datetime | None
248
250
  med_age_secs: datetime | None
251
+
252
+
253
+ # Canonical field sets for select= validation and type aliases for ops
254
+
255
+ # Assistant select fields (intentionally excludes 'context')
256
+ AssistantSelectField = Literal[
257
+ "assistant_id",
258
+ "graph_id",
259
+ "name",
260
+ "description",
261
+ "config",
262
+ "context",
263
+ "created_at",
264
+ "updated_at",
265
+ "metadata",
266
+ "version",
267
+ ]
268
+ ASSISTANT_FIELDS: set[str] = set(AssistantSelectField.__args__) # type: ignore[attr-defined]
269
+
270
+ # Thread select fields
271
+ ThreadSelectField = Literal[
272
+ "thread_id",
273
+ "created_at",
274
+ "updated_at",
275
+ "metadata",
276
+ "config",
277
+ "context",
278
+ "status",
279
+ "values",
280
+ "interrupts",
281
+ ]
282
+ THREAD_FIELDS: set[str] = set(ThreadSelectField.__args__) # type: ignore[attr-defined]
283
+
284
+ # Run select fields
285
+ RunSelectField = Literal[
286
+ "run_id",
287
+ "thread_id",
288
+ "assistant_id",
289
+ "created_at",
290
+ "updated_at",
291
+ "status",
292
+ "metadata",
293
+ "kwargs",
294
+ "multitask_strategy",
295
+ ]
296
+ RUN_FIELDS: set[str] = set(RunSelectField.__args__) # type: ignore[attr-defined]
297
+
298
+ # Cron select fields
299
+ CronSelectField = Literal[
300
+ "cron_id",
301
+ "assistant_id",
302
+ "thread_id",
303
+ "end_time",
304
+ "schedule",
305
+ "created_at",
306
+ "updated_at",
307
+ "user_id",
308
+ "payload",
309
+ "next_run_date",
310
+ "metadata",
311
+ "now",
312
+ ]
313
+ CRON_FIELDS: set[str] = set(CronSelectField.__args__) # type: ignore[attr-defined]