HermesAgent / docker /space_runtime.py
cjovs's picture
Fix HF Space web UI gateway port handoff
8a530f3 verified
"""Resolve Space-specific runtime defaults for Hermes containers."""
from __future__ import annotations
import os
import shlex
import sys
from collections.abc import Mapping, Sequence
from pathlib import Path
SPACE_WEB_ARGS = ["dashboard", "--host", "0.0.0.0", "--port", "7860", "--no-open"]
DEFAULT_HERMES_HOME = "/opt/data"
SPACE_HERMES_HOME = "/data/hermes"
DEFAULT_API_SERVER_PORT = "8642"
DEFAULT_API_SERVER_UPSTREAM_HOST = "127.0.0.1"
API_SERVER_PORT_PATHS = (
("platforms", "api_server", "extra", "port"),
("platforms", "api_server", "port"),
("api_server", "extra", "port"),
("api_server", "port"),
)
def is_hf_space(env: Mapping[str, str]) -> bool:
return bool(env.get("SPACE_ID") or env.get("SPACE_HOST"))
def resolve_hermes_home(env: Mapping[str, str]) -> str:
current = env.get("HERMES_HOME") or DEFAULT_HERMES_HOME
if is_hf_space(env) and current == DEFAULT_HERMES_HOME:
return SPACE_HERMES_HOME
return current
def resolve_default_command(env: Mapping[str, str], argv: Sequence[str]) -> list[str]:
if argv:
return list(argv)
if is_hf_space(env):
return SPACE_WEB_ARGS.copy()
return []
def resolve_hermes_command(
env: Mapping[str, str],
argv: Sequence[str],
executable: str = "hermes",
) -> list[str]:
return [executable, *resolve_default_command(env, argv)]
def _resolve_config_path(env: Mapping[str, str]) -> Path:
return Path(resolve_hermes_home(env)) / "config.yaml"
def _strip_inline_comment(text: str) -> str:
quote: str | None = None
result: list[str] = []
for char in text:
if char in {"'", '"'}:
if quote == char:
quote = None
elif quote is None:
quote = char
if char == "#" and quote is None:
break
result.append(char)
return "".join(result).rstrip()
def _parse_scalar(text: str) -> str | None:
value = _strip_inline_comment(text).strip()
if not value:
return None
if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}:
return value[1:-1]
return value
def _read_yaml_path_value(text: str, target_paths: Sequence[tuple[str, ...]]) -> str | None:
stack: list[tuple[int, str]] = []
for raw_line in text.splitlines():
line = raw_line.rstrip()
stripped = line.lstrip(" ")
if not stripped or stripped.startswith("#") or stripped.startswith("-"):
continue
indent = len(line) - len(stripped)
key, separator, remainder = stripped.partition(":")
if not separator:
continue
while stack and indent <= stack[-1][0]:
stack.pop()
path = tuple(part for _, part in stack) + (key.strip(),)
value = _parse_scalar(remainder)
if path in target_paths and value is not None:
return value
if value is None:
stack.append((indent, key.strip()))
return None
def _config_api_server_port(env: Mapping[str, str]) -> str | None:
config_path = _resolve_config_path(env)
try:
config_text = config_path.read_text(encoding="utf-8")
except OSError:
return None
value = _read_yaml_path_value(config_text, API_SERVER_PORT_PATHS)
if value is None:
return None
try:
port = int(value, 10)
except ValueError:
return None
if 0 < port <= 65535:
return str(port)
return None
def resolve_api_server_port(env: Mapping[str, str]) -> str:
explicit = (env.get("API_SERVER_PORT") or "").strip()
if explicit:
return explicit
return _config_api_server_port(env) or DEFAULT_API_SERVER_PORT
def resolve_api_server_upstream(env: Mapping[str, str]) -> str:
explicit = (env.get("UPSTREAM") or "").strip()
if explicit:
return explicit
return f"http://{DEFAULT_API_SERVER_UPSTREAM_HOST}:{resolve_api_server_port(env)}"
def _command_string(env: Mapping[str, str], argv: Sequence[str]) -> str:
executable = env.get("HERMES_EXECUTABLE", "hermes")
return shlex.join(resolve_hermes_command(env, argv, executable=executable))
def main(argv: Sequence[str] | None = None) -> int:
argv = list(sys.argv[1:] if argv is None else argv)
env = os.environ
if not argv or argv[0] == "home":
print(resolve_hermes_home(env))
return 0
if argv[0] == "command":
print(_command_string(env, argv[1:]))
return 0
if argv[0] == "api-server-port":
print(resolve_api_server_port(env))
return 0
if argv[0] == "api-server-upstream":
print(resolve_api_server_upstream(env))
return 0
if argv[0] == "exec":
print_only = False
args = argv[1:]
if args and args[0] == "--print-only":
print_only = True
args = args[1:]
command = resolve_hermes_command(
env,
args,
executable=env.get("HERMES_EXECUTABLE", "hermes"),
)
if print_only:
print(shlex.join(command))
return 0
os.execvpe(command[0], command, dict(env))
prog = os.path.basename(sys.argv[0] or "space_runtime.py")
raise SystemExit(
f"Usage: {prog} [home|command|api-server-port|api-server-upstream|exec [--print-only] [args...]]"
)
if __name__ == "__main__":
raise SystemExit(main())