Files
langgraph/libs/cli/langgraph_cli/host_backend.py
T
Andrew NguonlyandGitHub e77201cbb1 feat(cli): Add deploy list and deploy delete subcommands (#7106)
### Summary
This PR introduces a subcommand implementation that allows `langgraph
deploy list` and `langgraph deploy delete` subcommands.

#### `langgraph deploy list`
```bash
(env) andrewnguonly@Andrew-Nguonly-KC23X90J02 langgraph % langgraph deploy list --help                                                                              ⎈ gke_langchain-test-387119_us-west1_langgraph-cloud-us-west1
Usage: langgraph deploy list [OPTIONS]

  [Beta] List LangSmith Deployments.

Options:
  --name-contains TEXT  Only show deployments whose names contain this value.
  --api-key TEXT        API key. Can also be set via LANGGRAPH_HOST_API_KEY,
                        LANGSMITH_API_KEY, or LANGCHAIN_API_KEY environment
                        variable or .env file.
  --help                Show this message and exit.
```

Output example:
```bash
(env) andrewnguonly@Andrew-Nguonly-KC23X90J02 cli % langgraph deploy list
Deployment ID                         Deployment Name             Deployment URL                                                                      
------------------------------------  --------------------------  ------------------------------------------------------------------------------------
a40d6567-87c0-485a-a23d-94309a7d4519  ht-andrew-test-04           -                                                                                   
9da26acb-d0c9-4af0-af9e-f3fe8dfe85bc  ht-anirudh-deployment-test  https://ht-anirudh-deployment-test-428af4737f8a533cb2b107587eb8f38f.us.langgraph.app
```

#### `langgraph deploy delete`
```bash
(env) andrewnguonly@Andrew-Nguonly-KC23X90J02 langgraph % langgraph deploy delete --help                                                                            ⎈ gke_langchain-test-387119_us-west1_langgraph-cloud-us-west1
Usage: langgraph deploy delete [OPTIONS] DEPLOYMENT_ID

  [Beta] Delete a LangSmith Deployment.

Options:
  --force         Delete without prompting for confirmation.
  --api-key TEXT  API key. Can also be set via LANGGRAPH_HOST_API_KEY,
                  LANGSMITH_API_KEY, or LANGCHAIN_API_KEY environment variable
                  or .env file.
  --help          Show this message and exit.
```

Output example:
```bash
(env) andrewnguonly@Andrew-Nguonly-KC23X90J02 cli % langgraph deploy delete a40d6567-87c0-485a-a23d-94309a7d4519
Are you sure you want to delete deployment ID a40d6567-87c0-485a-a23d-94309a7d4519? (Y/n): Y
Host API key: 
Deleted deployment a40d6567-87c0-485a-a23d-94309a7d4519.
```

```bash
(env) andrewnguonly@Andrew-Nguonly-KC23X90J02 cli % langgraph deploy delete a40d6567-87c0-485a-a23d-94309a7d4519
Are you sure you want to delete deployment ID a40d6567-87c0-485a-a23d-94309a7d4519? (Y/n): n
Aborted!
```

```bash
(env) andrewnguonly@Andrew-Nguonly-KC23X90J02 cli % langgraph deploy delete 9da26acb-d0c9-4af0-af9e-f3fe8dfe85bc --force
Host API key: 
Deleted deployment 9da26acb-d0c9-4af0-af9e-f3fe8dfe85bc.
```
2026-03-11 09:12:17 -07:00

119 lines
3.8 KiB
Python

"""HTTP client for LangGraph host backend deployments."""
from __future__ import annotations
from typing import Any
import click
import httpx
class HostBackendError(click.ClickException):
"""Raised when the host backend returns an error response."""
def __init__(self, message: str, status_code: int | None = None):
super().__init__(message)
self.status_code = status_code
class HostBackendClient:
"""Minimal JSON HTTP client for the host backend deployment service."""
def __init__(self, base_url: str, api_key: str, tenant_id: str | None = None):
if not base_url:
raise click.UsageError("Host backend URL is required")
transport = httpx.HTTPTransport(retries=3)
headers: dict[str, str] = {
"X-Api-Key": api_key,
"Accept": "application/json",
}
if tenant_id:
headers["X-Tenant-ID"] = tenant_id
self._base_url = base_url.rstrip("/")
self._api_key = api_key
self._client = httpx.Client(
base_url=self._base_url,
headers=headers,
transport=transport,
timeout=30,
)
def _request(
self,
method: str,
path: str,
payload: dict[str, Any] | None = None,
params: dict[str, Any] | None = None,
) -> Any:
try:
resp = self._client.request(method, path, json=payload, params=params)
resp.raise_for_status()
except httpx.HTTPStatusError as err:
detail = err.response.text or str(err.response.status_code)
raise HostBackendError(
f"{method} {path} failed with status {err.response.status_code}: {detail}",
status_code=err.response.status_code,
) from None
except httpx.TransportError as err:
raise HostBackendError(str(err)) from None
if not resp.content:
return None
try:
return resp.json()
except ValueError as err:
raise HostBackendError(
f"Failed to decode response from {path}: {err}"
) from None
def create_deployment(self, payload: dict[str, Any]) -> dict[str, Any]:
return self._request("POST", "/v2/deployments", payload)
def list_deployments(self, name_contains: str = "") -> dict[str, Any]:
return self._request(
"GET",
"/v2/deployments",
params={"name_contains": name_contains},
)
def get_deployment(self, deployment_id: str) -> dict[str, Any]:
return self._request("GET", f"/v2/deployments/{deployment_id}")
def delete_deployment(self, deployment_id: str) -> None:
return self._request("DELETE", f"/v2/deployments/{deployment_id}")
def request_push_token(self, deployment_id: str) -> dict[str, Any]:
return self._request(
"POST",
f"/v2/deployments/{deployment_id}/push-token",
)
def update_deployment(
self,
deployment_id: str,
image_uri: str,
secrets: list[dict[str, str]] | None = None,
) -> dict[str, Any]:
payload: dict[str, Any] = {
"source_revision_config": {"image_uri": image_uri},
}
if secrets is not None:
payload["secrets"] = secrets
return self._request(
"PATCH",
f"/v2/deployments/{deployment_id}",
payload,
)
def list_revisions(self, deployment_id: str, limit: int = 1) -> dict[str, Any]:
return self._request(
"GET",
f"/v2/deployments/{deployment_id}/revisions?limit={limit}",
)
def get_revision(self, deployment_id: str, revision_id: str) -> dict[str, Any]:
return self._request(
"GET",
f"/v2/deployments/{deployment_id}/revisions/{revision_id}",
)