Files
wehub-resource-sync 327604cc89
Rebuild Cookbook Website / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:41:49 +08:00

229 lines
7.3 KiB
Python
Executable File

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import http.client
import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any
DEFAULT_MANAGER_PORT = 8732
def main() -> int:
parser = argparse.ArgumentParser(
description="Deploy a local Agents SDK project through the Deployment Manager."
)
parser.add_argument("project_path", nargs="?", default=os.getcwd())
parser.add_argument("--manager-port", type=int, default=DEFAULT_MANAGER_PORT)
parser.add_argument("--app-port", type=int)
parser.add_argument("--name")
parser.add_argument("--sandbox-backend", default="docker")
parser.add_argument("--target", default="local-docker", choices=["local-process", "local-docker"])
parser.add_argument("--no-start", action="store_true")
args = parser.parse_args()
project_path = Path(args.project_path).expanduser().resolve()
manager_root = find_manager_root()
manager_url = f"http://127.0.0.1:{args.manager_port}"
if not project_path.exists():
raise SystemExit(f"Project path does not exist: {project_path}")
if not manager_root.exists():
raise SystemExit(f"Deployment Manager not found: {manager_root}")
layout = inspect_layout(project_path)
print_layout(project_path, layout)
if not api_ok(manager_url):
start_manager(manager_root, args.manager_port)
wait_for_manager(manager_url)
project = post_json(manager_url, "/api/projects/import", {"path": str(project_path)})["project"]
deployment = ensure_deployment(
manager_url=manager_url,
project=project,
target=args.target,
sandbox_backend=args.sandbox_backend,
app_port=args.app_port,
deployment_name=args.name,
)
if not args.no_start:
deployment = post_json(manager_url, f"/api/deployments/{deployment['id']}/start", {})[
"deployment"
]
if deployment.get("status") == "failed":
raise SystemExit(deployment.get("error") or "deployment failed")
wait_for_app(str(deployment.get("app_url") or ""))
result = {
"manager_url": manager_url,
"project": project,
"deployment": deployment,
"app_url": deployment.get("app_url"),
}
print("\nDeployment ready:")
print(f"- Manager: {manager_url}")
print(f"- Deployment: {deployment['id']} ({deployment['status']})")
if deployment.get("app_url"):
print(f"- App: {deployment['app_url']}")
print("\nJSON:")
print(json.dumps(result, indent=2))
return 0
def find_manager_root() -> Path:
env_root = os.environ.get("DEPLOYMENT_MANAGER_ROOT")
if env_root:
return Path(env_root).expanduser().resolve()
cwd = Path.cwd().resolve()
if (cwd / "deployment_manager").is_dir():
return cwd / "deployment_manager"
if cwd.name == "deployment_manager" and (cwd / "app").is_dir():
return cwd
return Path(__file__).resolve().parents[1]
def inspect_layout(project_path: Path) -> dict[str, Any]:
pyproject_path = project_path / "pyproject.toml"
main_path = project_path / "main.py"
agent_paths = [
path
for path in sorted(project_path.glob("*.py"))
if path.name != "main.py" and "agent" in path.stem.lower()
]
pyproject_text = (
pyproject_path.read_text(encoding="utf-8", errors="replace")
if pyproject_path.exists()
else ""
)
agent_text = "\n".join(
path.read_text(encoding="utf-8", errors="replace") for path in agent_paths
)
return {
"pyproject": pyproject_path.exists(),
"main": main_path.exists(),
"agent_files": [path.name for path in agent_paths],
"sandbox_agent": "SandboxAgent" in agent_text,
"openai_agents": "openai-agents" in pyproject_text,
"uses_uv": pyproject_path.exists(),
}
def print_layout(project_path: Path, layout: dict[str, Any]) -> None:
print(f"Project: {project_path}")
print("Layout:")
for key, value in layout.items():
print(f"- {key}: {value}")
missing = [key for key in ("pyproject", "main", "openai_agents") if not layout[key]]
if missing:
print(f"Warning: missing expected Agents SDK layout signals: {', '.join(missing)}")
def start_manager(manager_root: Path, port: int) -> None:
subprocess.run(
["make", "start", f"PORT={port}"],
cwd=manager_root,
check=True,
)
def ensure_deployment(
*,
manager_url: str,
project: dict[str, Any],
target: str,
sandbox_backend: str,
app_port: int | None,
deployment_name: str | None,
) -> dict[str, Any]:
deployments = get_json(manager_url, "/api/deployments")["deployments"]
port = app_port or int(project.get("port") or 8421)
for deployment in reversed(deployments):
if (
deployment.get("project_id") == project["id"]
and deployment.get("target") == target
and deployment.get("sandbox_backend") == sandbox_backend
and int(deployment.get("port") or 0) == port
and (deployment_name is None or deployment.get("name") == deployment_name)
):
return deployment
payload: dict[str, Any] = {
"project_id": project["id"],
"target": target,
"sandbox_backend": sandbox_backend,
"port": port,
}
if deployment_name:
payload["name"] = deployment_name
return post_json(manager_url, "/api/deployments", payload)["deployment"]
def wait_for_manager(manager_url: str) -> None:
for _ in range(40):
if api_ok(manager_url):
return
time.sleep(0.5)
raise SystemExit(f"Deployment Manager did not become healthy: {manager_url}")
def wait_for_app(app_url: str) -> None:
if not app_url:
return
health_url = f"{app_url.rstrip('/')}/health"
for _ in range(40):
try:
request(health_url, timeout=1)
return
except (urllib.error.URLError, TimeoutError, http.client.HTTPException, OSError):
pass
time.sleep(0.5)
raise SystemExit(f"App did not become healthy: {health_url}")
def api_ok(manager_url: str) -> bool:
try:
payload = get_json(manager_url, "/api/health")
except urllib.error.URLError:
return False
return payload.get("ok") == "true"
def get_json(base_url: str, path: str) -> dict[str, Any]:
return json.loads(request(f"{base_url}{path}").decode("utf-8"))
def post_json(base_url: str, path: str, payload: dict[str, Any]) -> dict[str, Any]:
body = json.dumps(payload).encode("utf-8")
headers = {"Content-Type": "application/json"}
return json.loads(request(f"{base_url}{path}", data=body, headers=headers).decode("utf-8"))
def request(
url: str,
*,
data: bytes | None = None,
headers: dict[str, str] | None = None,
timeout: float = 10,
) -> bytes:
req = urllib.request.Request(url, data=data, headers=headers or {})
with urllib.request.urlopen(req, timeout=timeout) as response:
return response.read()
if __name__ == "__main__":
try:
raise SystemExit(main())
except urllib.error.HTTPError as exc:
sys.stderr.write(exc.read().decode("utf-8", errors="replace") + "\n")
raise SystemExit(exc.code)