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 )