/ gateway / runtime_footer.py
runtime_footer.py
  1  """Gateway runtime-metadata footer.
  2  
  3  Renders a compact footer showing runtime state (model, context %, cwd) and
  4  appends it to the FINAL message of an agent turn when enabled.  Off by default
  5  to keep replies minimal.
  6  
  7  Config (``~/.hermes/config.yaml``)::
  8  
  9      display:
 10        runtime_footer:
 11          enabled: true                       # off by default
 12          fields: [model, context_pct, cwd]   # order shown; drop any to hide
 13  
 14  Per-platform overrides live under ``display.platforms.<platform>.runtime_footer``.
 15  Users can toggle the global setting with ``/footer on|off`` from both the CLI
 16  and any gateway platform.
 17  
 18  The footer is appended to the final response text in ``gateway/run.py`` right
 19  before returning the response to the adapter send path โ€” so it only lands on
 20  the final message a user sees, not on tool-progress updates or streaming
 21  partials.  When streaming is on and the final text has already been delivered
 22  piecemeal, the footer is sent as a separate trailing message via
 23  ``send_trailing_footer()``.
 24  """
 25  
 26  from __future__ import annotations
 27  
 28  import os
 29  from pathlib import Path
 30  from typing import Any, Iterable, Optional
 31  
 32  _DEFAULT_FIELDS: tuple[str, ...] = ("model", "context_pct", "cwd")
 33  _SEP = " ยท "
 34  
 35  
 36  def _home_relative_cwd(cwd: str) -> str:
 37      """Return *cwd* with ``$HOME`` collapsed to ``~``.  Empty string if unset."""
 38      if not cwd:
 39          return ""
 40      try:
 41          home = os.path.expanduser("~")
 42          p = os.path.abspath(cwd)
 43          if home and (p == home or p.startswith(home + os.sep)):
 44              return "~" + p[len(home):]
 45          return p
 46      except Exception:
 47          return cwd
 48  
 49  
 50  def _model_short(model: Optional[str]) -> str:
 51      """Drop ``vendor/`` prefix for readability (``openai/gpt-5.4`` โ†’ ``gpt-5.4``)."""
 52      if not model:
 53          return ""
 54      return model.rsplit("/", 1)[-1]
 55  
 56  
 57  def resolve_footer_config(
 58      user_config: dict[str, Any] | None,
 59      platform_key: str | None = None,
 60  ) -> dict[str, Any]:
 61      """Resolve effective runtime-footer config for *platform_key*.
 62  
 63      Merge order (later wins):
 64          1. Built-in defaults (enabled=False)
 65          2. ``display.runtime_footer``
 66          3. ``display.platforms.<platform_key>.runtime_footer``
 67      """
 68      resolved = {"enabled": False, "fields": list(_DEFAULT_FIELDS)}
 69      cfg = (user_config or {}).get("display") or {}
 70  
 71      global_cfg = cfg.get("runtime_footer")
 72      if isinstance(global_cfg, dict):
 73          if "enabled" in global_cfg:
 74              resolved["enabled"] = bool(global_cfg.get("enabled"))
 75          if isinstance(global_cfg.get("fields"), list) and global_cfg["fields"]:
 76              resolved["fields"] = [str(f) for f in global_cfg["fields"]]
 77  
 78      if platform_key:
 79          platforms = cfg.get("platforms") or {}
 80          plat_cfg = platforms.get(platform_key)
 81          if isinstance(plat_cfg, dict):
 82              plat_footer = plat_cfg.get("runtime_footer")
 83              if isinstance(plat_footer, dict):
 84                  if "enabled" in plat_footer:
 85                      resolved["enabled"] = bool(plat_footer.get("enabled"))
 86                  if isinstance(plat_footer.get("fields"), list) and plat_footer["fields"]:
 87                      resolved["fields"] = [str(f) for f in plat_footer["fields"]]
 88  
 89      return resolved
 90  
 91  
 92  def format_runtime_footer(
 93      *,
 94      model: Optional[str],
 95      context_tokens: int,
 96      context_length: Optional[int],
 97      cwd: Optional[str] = None,
 98      fields: Iterable[str] = _DEFAULT_FIELDS,
 99  ) -> str:
100      """Render the footer line, or return "" if no fields have data.
101  
102      Fields are skipped silently when their underlying data is missing โ€” a
103      partially-populated footer is better than a line with ``?%`` or empty slots.
104      """
105      parts: list[str] = []
106      for field in fields:
107          if field == "model":
108              m = _model_short(model)
109              if m:
110                  parts.append(m)
111          elif field == "context_pct":
112              if context_length and context_length > 0 and context_tokens >= 0:
113                  pct = max(0, min(100, round((context_tokens / context_length) * 100)))
114                  parts.append(f"{pct}%")
115          elif field == "cwd":
116              rel = _home_relative_cwd(cwd or os.environ.get("TERMINAL_CWD", ""))
117              if rel:
118                  parts.append(rel)
119          # Unknown field names are silently ignored.
120  
121      if not parts:
122          return ""
123      return _SEP.join(parts)
124  
125  
126  def build_footer_line(
127      *,
128      user_config: dict[str, Any] | None,
129      platform_key: str | None,
130      model: Optional[str],
131      context_tokens: int,
132      context_length: Optional[int],
133      cwd: Optional[str] = None,
134  ) -> str:
135      """Top-level entry point used by gateway/run.py.
136  
137      Returns the footer text (empty string when disabled or no data).  Callers
138      append this to the final response themselves, preserving a single blank
139      line of separation.
140      """
141      cfg = resolve_footer_config(user_config, platform_key)
142      if not cfg.get("enabled"):
143          return ""
144      return format_runtime_footer(
145          model=model,
146          context_tokens=context_tokens,
147          context_length=context_length,
148          cwd=cwd,
149          fields=cfg.get("fields") or _DEFAULT_FIELDS,
150      )