mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
remove Vercel AI Gateway and Vercel Sandbox (#33067)
* remove Vercel AI Gateway provider and Vercel Sandbox terminal backend Both Vercel-hosted integrations are removed end-to-end. Users on the AI Gateway should switch to OpenRouter or one of the other aggregators (Nous Portal, Kilo Code). Users on the Vercel Sandbox backend should switch to Docker, Modal, Daytona, or SSH. What's removed: - `plugins/model-providers/ai-gateway/` provider plugin - `hermes_cli/vercel_auth.py` Vercel-Sandbox auth helper - `tools/environments/vercel_sandbox.py` terminal backend - `ai-gateway` provider wiring across auth, doctor, setup, models, config, status, providers, main, web_server, model_normalize, dump - `vercel_sandbox` backend wiring across terminal_tool, file_tools, code_execution_tool, file_operations, approval, skills_tool, environments/local, credential_files, lazy_deps, prompt_builder, cli, gateway/run - `AI_GATEWAY_BASE_URL` constant, `_AI_GATEWAY_HEADERS` auxiliary-client header set, run_agent base-URL header/reasoning special-cases - `[vercel]` pyproject extra and `vercel`/`vercel-workers` from uv.lock - env vars: `AI_GATEWAY_API_KEY`, `AI_GATEWAY_BASE_URL`, `VERCEL_TOKEN`, `VERCEL_PROJECT_ID`, `VERCEL_TEAM_ID`, `VERCEL_OIDC_TOKEN`, `TERMINAL_VERCEL_RUNTIME` - Tests: deletes test_ai_gateway_models.py and test_vercel_sandbox_environment.py; scrubs references across 23 surviving test files (no entire tests deleted unless they were dedicated to AI Gateway / Sandbox) - Docs: provider tables, env-var reference, setup guides, security notes, tool config, terminal-backend tables — English plus zh-Hans i18n parity - `hermes-agent` skill: provider table entry and remote-backend list What stays (intentional): - `popular-web-designs/templates/vercel.md` — CSS design reference, unrelated to Vercel-the-AI-product - `x-vercel-id` in `stream_diag.py` headers — generic Vercel CDN response header, useful diag signal on any Vercel-hosted endpoint - `vercel-labs/agent-browser` URL in browser config — lightpanda browser project, different OSS effort - `userStories.json` historical contributor entry mentioning Vercel Sandbox — archive, not active docs Validation: - 1153 tests in the 22 targeted files pass (`scripts/run_tests.sh`) - Full repo `py_compile` clean - Live import of every touched module + invariant check (no `ai-gateway` in `PROVIDER_REGISTRY`, no `_AI_GATEWAY_HEADERS`, no `vercel_sandbox` in `_REMOTE_TERMINAL_BACKENDS`) * test: convert profile-count check from change-detector to invariant The hardcoded "== 34" assertion broke when ai-gateway was removed. Per AGENTS.md change-detector-test guidance, assert the relationship (registry count >= number of plugin dirs) instead of a literal count. Counts shift when providers are added/removed; that's expected.
This commit is contained in:
parent
cb38ce28cb
commit
febc4cfec0
95 changed files with 111 additions and 3088 deletions
|
|
@ -3,18 +3,16 @@
|
|||
Terminal Tool Module
|
||||
|
||||
A terminal tool that executes commands in local, Docker, Modal, SSH,
|
||||
Singularity, Daytona, and Vercel Sandbox environments. Supports local
|
||||
execution, containerized backends, and cloud sandboxes, including managed
|
||||
Modal mode.
|
||||
Singularity, and Daytona environments. Supports local execution,
|
||||
containerized backends, and cloud sandboxes, including managed Modal mode.
|
||||
|
||||
Environment Selection (via TERMINAL_ENV environment variable):
|
||||
Supported environments:
|
||||
- "local": Execute directly on the host machine (default, fastest)
|
||||
- "docker": Execute in Docker containers (isolated, requires Docker)
|
||||
- "modal": Execute in Modal cloud sandboxes (direct Modal or managed gateway)
|
||||
- "vercel_sandbox": Execute in Vercel Sandbox cloud sandboxes
|
||||
|
||||
Features:
|
||||
- Multiple execution backends (local, docker, modal, vercel_sandbox)
|
||||
- Multiple execution backends (local, docker, modal)
|
||||
- Background task support
|
||||
- VM/container lifecycle management
|
||||
- Automatic cleanup after inactivity
|
||||
|
|
@ -119,68 +117,6 @@ DISK_USAGE_WARNING_THRESHOLD_GB = _safe_parse_import_env(
|
|||
float,
|
||||
"number",
|
||||
)
|
||||
_VERCEL_SANDBOX_DEFAULT_CWD = "/vercel/sandbox"
|
||||
_SUPPORTED_VERCEL_RUNTIMES = ("node24", "node22", "python3.13")
|
||||
|
||||
|
||||
def _is_supported_vercel_runtime(runtime: str) -> bool:
|
||||
return not runtime or runtime in _SUPPORTED_VERCEL_RUNTIMES
|
||||
|
||||
|
||||
def _check_vercel_sandbox_requirements(config: dict[str, Any]) -> bool:
|
||||
"""Validate Vercel Sandbox terminal backend requirements."""
|
||||
runtime = (config.get("vercel_runtime") or "").strip()
|
||||
if not _is_supported_vercel_runtime(runtime):
|
||||
supported = ", ".join(_SUPPORTED_VERCEL_RUNTIMES)
|
||||
logger.error(
|
||||
"Vercel Sandbox runtime %r is not supported. "
|
||||
"Set TERMINAL_VERCEL_RUNTIME to one of: %s.",
|
||||
runtime,
|
||||
supported,
|
||||
)
|
||||
return False
|
||||
|
||||
disk = config.get("container_disk", 51200)
|
||||
if disk not in {0, 51200}:
|
||||
logger.error(
|
||||
"Vercel Sandbox does not support custom TERMINAL_CONTAINER_DISK=%s. "
|
||||
"Use the default shared setting (51200 MB).",
|
||||
disk,
|
||||
)
|
||||
return False
|
||||
|
||||
if importlib.util.find_spec("vercel") is None:
|
||||
logger.error(
|
||||
"vercel is required for the Vercel Sandbox terminal backend: pip install vercel"
|
||||
)
|
||||
return False
|
||||
|
||||
has_oidc = bool(os.getenv("VERCEL_OIDC_TOKEN"))
|
||||
has_token = bool(os.getenv("VERCEL_TOKEN"))
|
||||
has_project = bool(os.getenv("VERCEL_PROJECT_ID"))
|
||||
has_team = bool(os.getenv("VERCEL_TEAM_ID"))
|
||||
|
||||
if has_oidc:
|
||||
return True
|
||||
|
||||
if has_token or has_project or has_team:
|
||||
if has_token and has_project and has_team:
|
||||
return True
|
||||
logger.error(
|
||||
"Vercel Sandbox backend selected with token auth, but "
|
||||
"VERCEL_TOKEN, VERCEL_PROJECT_ID, and VERCEL_TEAM_ID must all "
|
||||
"be set together. VERCEL_OIDC_TOKEN is supported for one-off "
|
||||
"local development only."
|
||||
)
|
||||
return False
|
||||
|
||||
logger.error(
|
||||
"Vercel Sandbox backend selected but no supported auth configuration "
|
||||
"was found. Set VERCEL_TOKEN, VERCEL_PROJECT_ID, and VERCEL_TEAM_ID "
|
||||
"for normal use. VERCEL_OIDC_TOKEN is supported for one-off local "
|
||||
"development only."
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def _check_disk_usage_warning():
|
||||
|
|
@ -837,10 +773,9 @@ def _transform_sudo_command(command: str | None) -> tuple[str | None, str | None
|
|||
should prepend sudo_stdin to their stdin_data and pass the merged bytes to
|
||||
Popen's stdin pipe.
|
||||
|
||||
Callers that cannot pipe subprocess stdin (modal, daytona,
|
||||
vercel_sandbox) must embed the password in the command string
|
||||
themselves; see their execute() methods for how they handle the
|
||||
non-None sudo_stdin case.
|
||||
Callers that cannot pipe subprocess stdin (modal, daytona) must embed
|
||||
the password in the command string themselves; see their execute()
|
||||
methods for how they handle the non-None sudo_stdin case.
|
||||
|
||||
If SUDO_PASSWORD is not set and in interactive mode (HERMES_INTERACTIVE=1):
|
||||
Prompts user for password with 45s timeout, caches for session.
|
||||
|
|
@ -1015,14 +950,12 @@ def _get_env_config() -> Dict[str, Any]:
|
|||
mount_docker_cwd = os.getenv("TERMINAL_DOCKER_MOUNT_CWD_TO_WORKSPACE", "false").lower() in {"true", "1", "yes"}
|
||||
|
||||
# Default cwd: local uses the host's current directory, ssh uses the
|
||||
# remote home, Vercel uses its documented workspace root, and everything
|
||||
# else starts in the backend's default root-like cwd.
|
||||
# remote home, and everything else starts in the backend's default
|
||||
# root-like cwd.
|
||||
if env_type == "local":
|
||||
default_cwd = os.getcwd()
|
||||
elif env_type == "ssh":
|
||||
default_cwd = "~"
|
||||
elif env_type == "vercel_sandbox":
|
||||
default_cwd = _VERCEL_SANDBOX_DEFAULT_CWD
|
||||
else:
|
||||
default_cwd = "/root"
|
||||
|
||||
|
|
@ -1044,7 +977,7 @@ def _get_env_config() -> Dict[str, Any]:
|
|||
):
|
||||
host_cwd = candidate
|
||||
cwd = "/workspace"
|
||||
elif env_type in {"modal", "docker", "singularity", "daytona", "vercel_sandbox"} and cwd:
|
||||
elif env_type in {"modal", "docker", "singularity", "daytona"} and cwd:
|
||||
# Host paths and relative paths that won't work inside containers
|
||||
is_host_path = any(cwd.startswith(p) for p in host_prefixes)
|
||||
is_relative = not os.path.isabs(cwd) # e.g. "." or "src/"
|
||||
|
|
@ -1062,7 +995,6 @@ def _get_env_config() -> Dict[str, Any]:
|
|||
"singularity_image": os.getenv("TERMINAL_SINGULARITY_IMAGE", f"docker://{default_image}"),
|
||||
"modal_image": os.getenv("TERMINAL_MODAL_IMAGE", default_image),
|
||||
"daytona_image": os.getenv("TERMINAL_DAYTONA_IMAGE", default_image),
|
||||
"vercel_runtime": os.getenv("TERMINAL_VERCEL_RUNTIME", "").strip(),
|
||||
"cwd": cwd,
|
||||
"host_cwd": host_cwd,
|
||||
"docker_mount_cwd_to_workspace": mount_docker_cwd,
|
||||
|
|
@ -1082,7 +1014,7 @@ def _get_env_config() -> Dict[str, Any]:
|
|||
).lower() in {"true", "1", "yes"},
|
||||
"local_persistent": os.getenv("TERMINAL_LOCAL_PERSISTENT", "false").lower() in {"true", "1", "yes"},
|
||||
# Container resource config (applies to docker, singularity, modal,
|
||||
# daytona, and vercel_sandbox -- ignored for local/ssh)
|
||||
# daytona -- ignored for local/ssh)
|
||||
"container_cpu": _parse_env_var("TERMINAL_CONTAINER_CPU", "1", float, "number"),
|
||||
"container_memory": _parse_env_var("TERMINAL_CONTAINER_MEMORY", "5120"), # MB (default 5GB)
|
||||
"container_disk": _parse_env_var("TERMINAL_CONTAINER_DISK", "51200"), # MB (default 50GB)
|
||||
|
|
@ -1113,8 +1045,8 @@ def _create_environment(env_type: str, image: str, cwd: str, timeout: int,
|
|||
|
||||
Args:
|
||||
env_type: One of "local", "docker", "singularity", "modal",
|
||||
"daytona", "vercel_sandbox", "ssh"
|
||||
image: Docker/Singularity/Modal image name (ignored for local/ssh/vercel)
|
||||
"daytona", "ssh"
|
||||
image: Docker/Singularity/Modal image name (ignored for local/ssh)
|
||||
cwd: Working directory
|
||||
timeout: Default command timeout
|
||||
ssh_config: SSH connection config (for env_type="ssh")
|
||||
|
|
@ -1220,21 +1152,6 @@ def _create_environment(env_type: str, image: str, cwd: str, timeout: int,
|
|||
persistent_filesystem=persistent, task_id=task_id,
|
||||
)
|
||||
|
||||
elif env_type == "vercel_sandbox":
|
||||
from tools.environments.vercel_sandbox import (
|
||||
VercelSandboxEnvironment as _VercelSandboxEnvironment,
|
||||
)
|
||||
return _VercelSandboxEnvironment(
|
||||
runtime=cc.get("vercel_runtime") or None,
|
||||
cwd=cwd,
|
||||
timeout=timeout,
|
||||
cpu=cpu,
|
||||
memory=memory,
|
||||
disk=disk,
|
||||
persistent_filesystem=persistent,
|
||||
task_id=task_id,
|
||||
)
|
||||
|
||||
elif env_type == "ssh":
|
||||
if not ssh_config or not ssh_config.get("host") or not ssh_config.get("user"):
|
||||
raise ValueError("SSH environment requires ssh_host and ssh_user to be configured")
|
||||
|
|
@ -1250,7 +1167,7 @@ def _create_environment(env_type: str, image: str, cwd: str, timeout: int,
|
|||
else:
|
||||
raise ValueError(
|
||||
f"Unknown environment type: {env_type}. Use 'local', 'docker', "
|
||||
f"'singularity', 'modal', 'daytona', 'vercel_sandbox', or 'ssh'"
|
||||
f"'singularity', 'modal', 'daytona', or 'ssh'"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1809,14 +1726,13 @@ def terminal_tool(
|
|||
}
|
||||
|
||||
container_config = None
|
||||
if env_type in {"docker", "singularity", "modal", "daytona", "vercel_sandbox"}:
|
||||
if env_type in {"docker", "singularity", "modal", "daytona"}:
|
||||
container_config = {
|
||||
"container_cpu": config.get("container_cpu", 1),
|
||||
"container_memory": config.get("container_memory", 5120),
|
||||
"container_disk": config.get("container_disk", 51200),
|
||||
"container_persistent": config.get("container_persistent", True),
|
||||
"modal_mode": config.get("modal_mode", "auto"),
|
||||
"vercel_runtime": config.get("vercel_runtime", ""),
|
||||
"docker_volumes": config.get("docker_volumes", []),
|
||||
"docker_mount_cwd_to_workspace": config.get("docker_mount_cwd_to_workspace", False),
|
||||
"docker_forward_env": config.get("docker_forward_env", []),
|
||||
|
|
@ -2265,9 +2181,6 @@ def check_terminal_requirements() -> bool:
|
|||
|
||||
return True
|
||||
|
||||
elif env_type == "vercel_sandbox":
|
||||
return _check_vercel_sandbox_requirements(config)
|
||||
|
||||
elif env_type == "daytona":
|
||||
from daytona import Daytona # noqa: F401 — SDK presence check
|
||||
return os.getenv("DAYTONA_API_KEY") is not None
|
||||
|
|
@ -2275,7 +2188,7 @@ def check_terminal_requirements() -> bool:
|
|||
else:
|
||||
logger.error(
|
||||
"Unknown TERMINAL_ENV '%s'. Use one of: local, docker, singularity, "
|
||||
"modal, daytona, vercel_sandbox, ssh.",
|
||||
"modal, daytona, ssh.",
|
||||
env_type,
|
||||
)
|
||||
return False
|
||||
|
|
@ -2318,7 +2231,7 @@ if __name__ == "__main__":
|
|||
print(
|
||||
" TERMINAL_ENV: "
|
||||
f"{os.getenv('TERMINAL_ENV', 'local')} "
|
||||
"(local/docker/singularity/modal/daytona/vercel_sandbox/ssh)"
|
||||
"(local/docker/singularity/modal/daytona/ssh)"
|
||||
)
|
||||
print(f" TERMINAL_DOCKER_IMAGE: {os.getenv('TERMINAL_DOCKER_IMAGE', default_img)}")
|
||||
print(f" TERMINAL_SINGULARITY_IMAGE: {os.getenv('TERMINAL_SINGULARITY_IMAGE', f'docker://{default_img}')}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue