From dcdcb13a99dfec8dc8065c712b256e9b4619368e Mon Sep 17 00:00:00 2001 From: Hari Dhanushkodi Date: Fri, 16 Oct 2026 18:50:31 +0000 Subject: [PATCH] fix(cli): keep AWS deployments on dev until serverless is supported Co-authored-by: open-swe[bot] --- libs/cli/langgraph_cli/deploy.py | 14 +++- libs/cli/langgraph_cli/host_backend.py | 4 + .../unit_tests/cli/test_deploy_command.py | 75 +++++++++++++++++++ .../cli/tests/unit_tests/test_host_backend.py | 26 +++++++ 4 files changed, 116 insertions(+), 3 deletions(-) diff --git a/libs/cli/langgraph_cli/deploy.py b/libs/cli/langgraph_cli/deploy.py index f385a8bc0..dbe231d65 100644 --- a/libs/cli/langgraph_cli/deploy.py +++ b/libs/cli/langgraph_cli/deploy.py @@ -1548,6 +1548,11 @@ def _resolve_or_create( ) if found is not None: return found.id, step + if ctx.endpoints.is_aws and ctx.deployment_type == "dev_zero": + raise click.UsageError( + "Serverless deployments are not supported on AWS yet. " + "Use --deployment-type dev instead." + ) try: created, step = _create_deployment( ctx.client, @@ -2214,8 +2219,8 @@ def _deploy_base_options( click.option( "--deployment-type", type=click.Choice(["serverless", "dedicated", "dev", "prod"]), - default="serverless", - show_default=True, + default=None, + show_default="serverless; dev on AWS", help=( "Deployment type (used when creating a new deployment): " "serverless (scale to zero) or dedicated (prod); " @@ -2396,7 +2401,7 @@ def _deploy_cmd( host_url: str | None, api_key: str | None, deployment_id: str | None, - deployment_type: str, + deployment_type: str | None, name: str | None, agent_id: str | None, environment: str | None, @@ -2488,6 +2493,9 @@ def _deploy_cmd( ) client = _create_host_backend_client(host_url, api_key, env_vars=env_vars) + deployment_type = deployment_type or ( + "dev" if client.endpoints.is_aws else "serverless" + ) try: tracked_packages = find_tracked_packages(config, config_json) or None except Exception as exc: diff --git a/libs/cli/langgraph_cli/host_backend.py b/libs/cli/langgraph_cli/host_backend.py index 377b261e7..c129123e6 100644 --- a/libs/cli/langgraph_cli/host_backend.py +++ b/libs/cli/langgraph_cli/host_backend.py @@ -44,6 +44,10 @@ class ControlPlaneEndpoints: f".{CLOUD_CONTROL_PLANE_HOST}" ) + @property + def is_aws(self) -> bool: + return urlparse(self.control_plane_url).hostname == "aws.api.host.langchain.com" + @classmethod def from_control_plane_url(cls, url: str) -> ControlPlaneEndpoints: control_plane_url = url.rstrip("/") diff --git a/libs/cli/tests/unit_tests/cli/test_deploy_command.py b/libs/cli/tests/unit_tests/cli/test_deploy_command.py index 47bc2bb85..5d06c00f2 100644 --- a/libs/cli/tests/unit_tests/cli/test_deploy_command.py +++ b/libs/cli/tests/unit_tests/cli/test_deploy_command.py @@ -344,6 +344,81 @@ def deploy_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> DeployPro return DeployProject(control_plane, docker, timeline, uploads) +@pytest.mark.parametrize("build_mode", ["--no-remote", "--remote"]) +@pytest.mark.parametrize("deployment_args", [(), ("--deployment-type", "dev")]) +def test_aws_deploy_uses_dev( + deploy_project: DeployProject, build_mode: str, deployment_args: tuple[str, ...] +) -> None: + result = deploy_project.run( + build_mode, *deployment_args, host_url="https://aws.api.host.langchain.com" + ) + + assert result.exit_code == 0, result.output + assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT]["source_config"] == { + "deployment_type": "dev" + } + + +@pytest.mark.parametrize("build_mode", ["--no-remote", "--remote"]) +def test_aws_deploy_rejects_serverless_before_creating_or_building( + deploy_project: DeployProject, build_mode: str +) -> None: + result = deploy_project.run( + build_mode, + "--deployment-type", + "serverless", + host_url="https://aws.api.host.langchain.com", + ) + + assert result.exit_code == 2, result.output + assert "Serverless deployments are not supported on AWS yet" in result.output + assert "Use --deployment-type dev instead" in result.output + assert deploy_project.timeline == [LIST_DEPLOYMENTS] + + +@pytest.mark.parametrize("endpoint_source", ["environment", "dotenv"]) +def test_aws_default_is_resolved_from_langsmith_endpoint( + deploy_project: DeployProject, + monkeypatch: pytest.MonkeyPatch, + endpoint_source: str, +) -> None: + endpoint = "https://aws.api.smith.langchain.com" + if endpoint_source == "environment": + monkeypatch.setenv("LANGSMITH_ENDPOINT", endpoint) + else: + Path(".env").write_text(f"LANGSMITH_ENDPOINT={endpoint}\n") + result = CliRunner().invoke( + cli, + [ + "deploy", + "--api-key", + "test-key", + "--name", + "my-app", + "--no-input", + "--no-wait", + "--remote", + ], + ) + + assert result.exit_code == 0, result.output + assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT]["source_config"] == { + "deployment_type": "dev" + } + + +def test_explicit_host_takes_precedence_over_aws_langsmith_endpoint( + deploy_project: DeployProject, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setenv("LANGSMITH_ENDPOINT", "https://aws.api.smith.langchain.com") + result = deploy_project.run("--remote", host_url=CLOUD_CONTROL_PLANE_URL) + + assert result.exit_code == 0, result.output + assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT]["source_config"] == { + "deployment_type": "dev_zero" + } + + def test_first_local_deploy_creates_then_builds_pushes_and_updates_in_order( deploy_project: DeployProject, ) -> None: diff --git a/libs/cli/tests/unit_tests/test_host_backend.py b/libs/cli/tests/unit_tests/test_host_backend.py index bf9f922f7..daef66564 100644 --- a/libs/cli/tests/unit_tests/test_host_backend.py +++ b/libs/cli/tests/unit_tests/test_host_backend.py @@ -10,6 +10,32 @@ from langgraph_cli.host_backend import ( ) +@pytest.mark.parametrize( + ("host_url", "langsmith_endpoint", "expected"), + [ + ("https://aws.api.host.langchain.com/", None, True), + ("https://AWS.api.host.langchain.com:443", None, True), + (None, "https://aws.api.smith.langchain.com", True), + (None, None, False), + ("https://eu.api.host.langchain.com", None, False), + ("https://apac.api.host.langchain.com", None, False), + ("https://aws.example.com/api-host", None, False), + ("https://aws.api.host.langchain.com.example.com", None, False), + ( + "https://api.host.langchain.com", + "https://aws.api.smith.langchain.com", + False, + ), + ], +) +def test_aws_detection_uses_the_resolved_control_plane_host( + host_url: str | None, langsmith_endpoint: str | None, expected: bool +) -> None: + endpoints = ControlPlaneEndpoints.resolve(host_url, langsmith_endpoint) + + assert endpoints.is_aws is expected + + def test_constructor_strips_trailing_slash(): c = HostBackendClient("https://api.example.com/", "key") assert c.base_url == "https://api.example.com"