langgraph-cli 0.4.16__tar.gz → 0.4.18__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 (128) hide show
  1. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/PKG-INFO +1 -1
  2. langgraph_cli-0.4.18/langgraph_cli/__init__.py +1 -0
  3. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/cli.py +37 -17
  4. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/templates.py +19 -55
  5. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/test_deploy_helpers.py +92 -0
  6. langgraph_cli-0.4.16/langgraph_cli/__init__.py +0 -1
  7. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/.gitignore +0 -0
  8. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/LICENSE +0 -0
  9. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/Makefile +0 -0
  10. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/README.md +0 -0
  11. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/.env.example +0 -0
  12. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/.gitignore +0 -0
  13. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/Makefile +0 -0
  14. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graph_prerelease_reqs/agent.py +0 -0
  15. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graph_prerelease_reqs/deps/additional_deps/pyproject.toml +0 -0
  16. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graph_prerelease_reqs/deps/zuper_deps/pyproject.toml +0 -0
  17. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graph_prerelease_reqs/langgraph.json +0 -0
  18. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graph_prerelease_reqs/pyproject.toml +0 -0
  19. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graph_prerelease_reqs_fail/agent.py +0 -0
  20. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graph_prerelease_reqs_fail/langgraph.json +0 -0
  21. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graph_prerelease_reqs_fail/pyproject.toml +0 -0
  22. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs/agent.py +0 -0
  23. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs/langgraph.json +0 -0
  24. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs/storm.py +0 -0
  25. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_a/__init__.py +0 -0
  26. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_a/graphs_submod/__init__.py +0 -0
  27. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_a/graphs_submod/agent.py +0 -0
  28. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_a/graphs_submod/subprompt.txt +0 -0
  29. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_a/hello.py +0 -0
  30. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_a/langgraph.json +0 -0
  31. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_a/prompt.txt +0 -0
  32. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_a/requirements.txt +0 -0
  33. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_b/graphs_submod/agent.py +0 -0
  34. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_b/graphs_submod/subprompt.txt +0 -0
  35. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_b/hello.py +0 -0
  36. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_b/langgraph.json +0 -0
  37. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_b/prompt.txt +0 -0
  38. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_b/requirements.txt +0 -0
  39. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_b/utils/__init__.py +0 -0
  40. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/graphs_reqs_b/utils/greeter.py +0 -0
  41. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/langgraph.json +0 -0
  42. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/my_app.py +0 -0
  43. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/pipconf.txt +0 -0
  44. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/poetry.lock +0 -0
  45. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/examples/pyproject.toml +0 -0
  46. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/generate_schema.py +0 -0
  47. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/.dockerignore +0 -0
  48. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/.editorconfig +0 -0
  49. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/.env.example +0 -0
  50. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/.eslintrc.cjs +0 -0
  51. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/.gitignore +0 -0
  52. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/LICENSE +0 -0
  53. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/README.md +0 -0
  54. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/jest.config.js +0 -0
  55. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/langgraph.json +0 -0
  56. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/package.json +0 -0
  57. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/src/agent/graph.ts +0 -0
  58. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/src/agent/state.ts +0 -0
  59. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/static/studio.png +0 -0
  60. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/tests/agent.test.ts +0 -0
  61. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/tests/graph.int.test.ts +0 -0
  62. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/tsconfig.json +0 -0
  63. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-examples/yarn.lock +0 -0
  64. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/.eslintrc.cjs +0 -0
  65. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/apps/agent/langgraph.json +0 -0
  66. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/apps/agent/package.json +0 -0
  67. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/apps/agent/src/graph.ts +0 -0
  68. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/apps/agent/src/state.ts +0 -0
  69. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/apps/agent/tsconfig.json +0 -0
  70. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/libs/shared/package.json +0 -0
  71. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/libs/shared/src/index.ts +0 -0
  72. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/libs/shared/tsconfig.json +0 -0
  73. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/package.json +0 -0
  74. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/tsconfig.json +0 -0
  75. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/turbo.json +0 -0
  76. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/js-monorepo-example/yarn.lock +0 -0
  77. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/__main__.py +0 -0
  78. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/analytics.py +0 -0
  79. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/config.py +0 -0
  80. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/constants.py +0 -0
  81. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/docker.py +0 -0
  82. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/exec.py +0 -0
  83. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/helpers.py +0 -0
  84. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/host_backend.py +0 -0
  85. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/progress.py +0 -0
  86. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/py.typed +0 -0
  87. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/schemas.py +0 -0
  88. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/util.py +0 -0
  89. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/langgraph_cli/version.py +0 -0
  90. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/pyproject.toml +0 -0
  91. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/apps/agent/.env.example +0 -0
  92. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/apps/agent/langgraph.json +0 -0
  93. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/apps/agent/pyproject.toml +0 -0
  94. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/apps/agent/src/agent/__init__.py +0 -0
  95. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/apps/agent/src/agent/graph.py +0 -0
  96. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/apps/agent/src/agent/state.py +0 -0
  97. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/libs/common/__init__.py +0 -0
  98. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/libs/common/helpers.py +0 -0
  99. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/libs/shared/pyproject.toml +0 -0
  100. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/libs/shared/src/shared/__init__.py +0 -0
  101. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/libs/shared/src/shared/utils.py +0 -0
  102. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/python-monorepo-example/pyproject.toml +0 -0
  103. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/schemas/schema.json +0 -0
  104. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/schemas/schema.v0.json +0 -0
  105. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/schemas/version.schema.json +0 -0
  106. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/__init__.py +0 -0
  107. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/integration_tests/__init__.py +0 -0
  108. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/integration_tests/test_cli.py +0 -0
  109. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/__init__.py +0 -0
  110. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/agent.py +0 -0
  111. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/cli/__init__.py +0 -0
  112. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/cli/langgraph.json +0 -0
  113. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/cli/pyproject.toml +0 -0
  114. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/cli/test_cli.py +0 -0
  115. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/cli/test_templates.py +0 -0
  116. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/conftest.py +0 -0
  117. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/graphs/agent.py +0 -0
  118. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/helpers.py +0 -0
  119. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/multiplatform/js.mts +0 -0
  120. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/multiplatform/python.py +0 -0
  121. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/pipconfig.txt +0 -0
  122. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/test_config.json +0 -0
  123. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/test_config.py +0 -0
  124. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/test_docker.py +0 -0
  125. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/test_host_backend.py +0 -0
  126. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/test_logs_helpers.py +0 -0
  127. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/tests/unit_tests/test_util.py +0 -0
  128. {langgraph_cli-0.4.16 → langgraph_cli-0.4.18}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-cli
3
- Version: 0.4.16
3
+ Version: 0.4.18
4
4
  Summary: CLI for interacting with LangGraph API
5
5
  Project-URL: Source, https://github.com/langchain-ai/langgraph/tree/main/libs/cli
6
6
  Project-URL: Twitter, https://x.com/LangChain
@@ -0,0 +1 @@
1
+ __version__ = "0.4.18"
@@ -764,7 +764,7 @@ def _deploy_base_options(
764
764
  @cli.group(
765
765
  cls=DeployGroup,
766
766
  help=(
767
- "[Beta] Build and deploy a LangGraph image to LangSmith Deployments.\n\n"
767
+ "[Beta] Build and deploy a LangGraph image to LangSmith Deployment.\n\n"
768
768
  "This command is in beta and under active development. "
769
769
  "Expect frequent updates and improvements.\n\n"
770
770
  "Run from the root of your LangGraph project (where langgraph.json "
@@ -1122,7 +1122,7 @@ def _deploy(
1122
1122
  )
1123
1123
  else:
1124
1124
  click.secho(
1125
- " Check status in the LangSmith Deployments dashboard.",
1125
+ " Check status in the LangSmith Deployment dashboard.",
1126
1126
  fg="yellow",
1127
1127
  )
1128
1128
 
@@ -1165,22 +1165,42 @@ def _call_host_backend_with_optional_tenant(
1165
1165
  in-place so all subsequent calls through the same instance are
1166
1166
  tenant-aware.
1167
1167
  """
1168
- try:
1169
- return operation(client)
1170
- except HostBackendError as err:
1171
- if err.status_code == 403 and "requires workspace specification" in err.message:
1172
- click.secho(
1173
- "Your API key is org-scoped and requires a workspace ID.",
1174
- fg="yellow",
1175
- )
1176
- click.secho(
1177
- "Find your workspace ID in LangSmith under Settings > Workspaces.",
1178
- fg="yellow",
1179
- )
1180
- tenant_id = click.prompt("Workspace ID")
1181
- client._client.headers["X-Tenant-ID"] = tenant_id
1168
+ prompted_for_tenant = False
1169
+
1170
+ while True:
1171
+ try:
1182
1172
  return operation(client)
1183
- raise
1173
+ except HostBackendError as err:
1174
+ if (
1175
+ not prompted_for_tenant
1176
+ and err.status_code == 403
1177
+ and "requires workspace specification" in err.message
1178
+ ):
1179
+ click.secho(
1180
+ "Your API key is org-scoped and requires a workspace ID.",
1181
+ fg="yellow",
1182
+ )
1183
+ click.secho(
1184
+ "Find your workspace ID in LangSmith under Settings > Workspaces.",
1185
+ fg="yellow",
1186
+ )
1187
+ client._client.headers["X-Tenant-ID"] = click.prompt("Workspace ID")
1188
+ prompted_for_tenant = True
1189
+ continue
1190
+ if err.status_code == 403 and "not enabled" in err.message.lower():
1191
+ from urllib.parse import urlparse
1192
+
1193
+ smith_host = "smith.langchain.com"
1194
+ parsed = urlparse(client._base_url)
1195
+ if (parsed.hostname or "").startswith("eu."):
1196
+ smith_host = "eu.smith.langchain.com"
1197
+ raise HostBackendError(
1198
+ "LangSmith Deployment is not enabled for this organization. "
1199
+ f"Enable it at https://{smith_host}/host/deployments"
1200
+ " (ensure this matches the organization for your API key).",
1201
+ status_code=403,
1202
+ ) from None
1203
+ raise
1184
1204
 
1185
1205
 
1186
1206
  @OPT_HOST_API_KEY
@@ -8,31 +8,20 @@ from zipfile import ZipFile
8
8
  import click
9
9
 
10
10
  TEMPLATES: dict[str, dict[str, str]] = {
11
+ "Deep Agent": {
12
+ "description": "An opinionated deployment template for a Deep Agent.",
13
+ "python": "https://github.com/langchain-ai/deep-agent-template/archive/refs/heads/main.zip",
14
+ "js": "https://github.com/langchain-ai/deep-agent-template-js/archive/refs/heads/main.zip",
15
+ },
16
+ "Agent": {
17
+ "description": "A simple agent that can be flexibly extended to many tools.",
18
+ "python": "https://github.com/langchain-ai/simple-agent-template/archive/refs/heads/main.zip",
19
+ },
11
20
  "New LangGraph Project": {
12
21
  "description": "A simple, minimal chatbot with memory.",
13
22
  "python": "https://github.com/langchain-ai/new-langgraph-project/archive/refs/heads/main.zip",
14
23
  "js": "https://github.com/langchain-ai/new-langgraphjs-project/archive/refs/heads/main.zip",
15
24
  },
16
- "ReAct Agent": {
17
- "description": "A simple agent that can be flexibly extended to many tools.",
18
- "python": "https://github.com/langchain-ai/react-agent/archive/refs/heads/main.zip",
19
- "js": "https://github.com/langchain-ai/react-agent-js/archive/refs/heads/main.zip",
20
- },
21
- "Memory Agent": {
22
- "description": "A ReAct-style agent with an additional tool to store memories for use across conversational threads.",
23
- "python": "https://github.com/langchain-ai/memory-agent/archive/refs/heads/main.zip",
24
- "js": "https://github.com/langchain-ai/memory-agent-js/archive/refs/heads/main.zip",
25
- },
26
- "Retrieval Agent": {
27
- "description": "An agent that includes a retrieval-based question-answering system.",
28
- "python": "https://github.com/langchain-ai/retrieval-agent-template/archive/refs/heads/main.zip",
29
- "js": "https://github.com/langchain-ai/retrieval-agent-template-js/archive/refs/heads/main.zip",
30
- },
31
- "Data-enrichment Agent": {
32
- "description": "An agent that performs web searches and organizes its findings into a structured format.",
33
- "python": "https://github.com/langchain-ai/data-enrichment/archive/refs/heads/main.zip",
34
- "js": "https://github.com/langchain-ai/data-enrichment-js/archive/refs/heads/main.zip",
35
- },
36
25
  }
37
26
 
38
27
  # Generate TEMPLATE_IDS programmatically
@@ -78,19 +67,25 @@ def _choose_template() -> str:
78
67
  click.secho("❌ Invalid choice. Please try again.", fg="red")
79
68
  return _choose_template()
80
69
 
81
- # Prompt the user to choose between Python or JS/TS version
70
+ template_info = TEMPLATES[selected_template]
71
+ available_langs = [lang for lang in ("python", "js") if lang in template_info]
72
+
82
73
  click.secho(
83
- f"\nYou selected: {selected_template} - {TEMPLATES[selected_template]['description']}",
74
+ f"\nYou selected: {selected_template} - {template_info['description']}",
84
75
  fg="green",
85
76
  )
77
+
78
+ if len(available_langs) == 1:
79
+ return template_info[available_langs[0]]
80
+
86
81
  version_choice: int = click.prompt(
87
82
  "Choose language (1 for Python 🐍, 2 for JS/TS 🌐)", type=int
88
83
  )
89
84
 
90
85
  if version_choice == 1:
91
- return TEMPLATES[selected_template]["python"]
86
+ return template_info["python"]
92
87
  elif version_choice == 2:
93
- return TEMPLATES[selected_template]["js"]
88
+ return template_info["js"]
94
89
  else:
95
90
  click.secho("❌ Invalid choice. Please try again.", fg="red")
96
91
  return _choose_template()
@@ -130,37 +125,6 @@ def _download_repo_with_requests(repo_url: str, path: str) -> None:
130
125
  sys.exit(1)
131
126
 
132
127
 
133
- def _get_template_url(template_name: str) -> str | None:
134
- """
135
- Retrieves the template URL based on the provided template name.
136
-
137
- Args:
138
- template_name: The name of the template.
139
-
140
- Returns:
141
- Optional[str]: The URL of the template if found, else None.
142
- """
143
- if template_name in TEMPLATES:
144
- click.secho(f"Template selected: {template_name}", fg="green")
145
- version_choice: int = click.prompt(
146
- "Choose version (1 for Python 🐍, 2 for JS/TS 🌐)", type=int
147
- )
148
-
149
- if version_choice == 1:
150
- return TEMPLATES[template_name]["python"]
151
- elif version_choice == 2:
152
- return TEMPLATES[template_name]["js"]
153
- else:
154
- click.secho("❌ Invalid choice. Please try again.", fg="red")
155
- return None
156
- else:
157
- click.secho(
158
- f"Template '{template_name}' not found. Please select from the available options.",
159
- fg="red",
160
- )
161
- return None
162
-
163
-
164
128
  def create_new(path: str | None, template: str | None) -> None:
165
129
  """Create a new LangGraph project at the specified PATH using the chosen TEMPLATE.
166
130
 
@@ -3,14 +3,17 @@ import json
3
3
  import os
4
4
 
5
5
  import click
6
+ import httpx
6
7
  import pytest
7
8
 
8
9
  from langgraph_cli.cli import (
10
+ _call_host_backend_with_optional_tenant,
9
11
  _docker_config_for_token,
10
12
  _normalize_image_name,
11
13
  _normalize_image_tag,
12
14
  _parse_env_from_config,
13
15
  )
16
+ from langgraph_cli.host_backend import HostBackendClient, HostBackendError
14
17
 
15
18
 
16
19
  class TestDockerConfigForToken:
@@ -132,3 +135,92 @@ class TestParseEnvFromConfig:
132
135
  assert result["GOOD"] == "value"
133
136
  # EMPTY= gives empty string, not None, so it should be present
134
137
  assert result["EMPTY"] == ""
138
+
139
+
140
+ class TestCallHostBackendWithOptionalTenant:
141
+ def _make_client(self, handler):
142
+ c = HostBackendClient("https://api.example.com", "test-key")
143
+ c._client = httpx.Client(
144
+ base_url="https://api.example.com",
145
+ transport=httpx.MockTransport(handler),
146
+ headers={"X-Api-Key": "test-key", "Accept": "application/json"},
147
+ timeout=30,
148
+ )
149
+ return c
150
+
151
+ def _make_eu_client(self, handler):
152
+ c = HostBackendClient("https://eu.api.host.langchain.com", "test-key")
153
+ c._client = httpx.Client(
154
+ base_url="https://eu.api.host.langchain.com",
155
+ transport=httpx.MockTransport(handler),
156
+ headers={"X-Api-Key": "test-key", "Accept": "application/json"},
157
+ timeout=30,
158
+ )
159
+ return c
160
+
161
+ def test_success_passes_through(self):
162
+ client = self._make_client(lambda req: httpx.Response(200, json={"ok": True}))
163
+ result = _call_host_backend_with_optional_tenant(
164
+ client, lambda c: c.list_deployments()
165
+ )
166
+ assert result == {"ok": True}
167
+
168
+ def test_403_not_enabled_gives_actionable_error(self):
169
+ detail = (
170
+ '{"detail":"LangSmith Deployment is not enabled for this organization"}'
171
+ )
172
+ client = self._make_client(lambda req: httpx.Response(403, text=detail))
173
+ with pytest.raises(HostBackendError, match="not enabled") as exc_info:
174
+ _call_host_backend_with_optional_tenant(
175
+ client, lambda c: c.list_deployments()
176
+ )
177
+ assert exc_info.value.status_code == 403
178
+ assert "smith.langchain.com" in exc_info.value.message
179
+
180
+ def test_403_not_enabled_eu_url(self):
181
+ detail = (
182
+ '{"detail":"LangSmith Deployment is not enabled for this organization"}'
183
+ )
184
+ client = self._make_eu_client(lambda req: httpx.Response(403, text=detail))
185
+ with pytest.raises(HostBackendError, match="not enabled") as exc_info:
186
+ _call_host_backend_with_optional_tenant(
187
+ client, lambda c: c.list_deployments()
188
+ )
189
+ assert "eu.smith.langchain.com" in exc_info.value.message
190
+
191
+ def test_workspace_retry_then_not_enabled_gives_actionable_error(self, monkeypatch):
192
+ requires_workspace = '{"detail":"requires workspace specification"}'
193
+ not_enabled = (
194
+ '{"detail":"LangSmith Deployment is not enabled for this organization"}'
195
+ )
196
+ seen_tenant_ids = []
197
+
198
+ def handler(req):
199
+ seen_tenant_ids.append(req.headers.get("X-Tenant-ID"))
200
+ if len(seen_tenant_ids) == 1:
201
+ return httpx.Response(403, text=requires_workspace)
202
+ if len(seen_tenant_ids) == 2:
203
+ return httpx.Response(403, text=not_enabled)
204
+ raise AssertionError("unexpected extra request")
205
+
206
+ monkeypatch.setattr(click, "prompt", lambda _text: "workspace-123")
207
+ client = self._make_client(handler)
208
+
209
+ with pytest.raises(HostBackendError, match="not enabled") as exc_info:
210
+ _call_host_backend_with_optional_tenant(
211
+ client, lambda c: c.list_deployments()
212
+ )
213
+
214
+ assert exc_info.value.status_code == 403
215
+ assert "smith.langchain.com" in exc_info.value.message
216
+ assert seen_tenant_ids == [None, "workspace-123"]
217
+ assert client._client.headers["X-Tenant-ID"] == "workspace-123"
218
+
219
+ def test_other_403_re_raises_original(self):
220
+ client = self._make_client(
221
+ lambda req: httpx.Response(403, text='{"detail":"some other error"}')
222
+ )
223
+ with pytest.raises(HostBackendError, match="some other error"):
224
+ _call_host_backend_with_optional_tenant(
225
+ client, lambda c: c.list_deployments()
226
+ )
@@ -1 +0,0 @@
1
- __version__ = "0.4.16"
File without changes
File without changes
File without changes
File without changes