diff --git a/gateway/run.py b/gateway/run.py index 35cbd7a0408b..b48e530a8191 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -18312,12 +18312,21 @@ async def _handle_message_with_agent(self, event, source, _quick_key: str, run_g context_length=agent_result.get("context_length") or None, cwd=os.environ.get("TERMINAL_CWD", ""), turn_seconds=_turn_seconds, + tokens_in=agent_result.get("last_prompt_tokens", 0) or 0, + tokens_out=agent_result.get("output_tokens", 0) or 0, + effort=(_load_gateway_config().get("agent") or {}).get("reasoning_effort") or None, ) except Exception as _footer_err: logger.debug("runtime_footer build failed: %s", _footer_err) _footer_line = "" if _footer_line and response and not agent_result.get("already_sent") and not _intentional_silence: - response = f"{response}\n\n{_footer_line}" + if source.platform == Platform.FEISHU: + # Card-style footer for Feishu: separated by a divider, + # rendered as a small quote block so it reads as metadata + # (mirrors cc-connect style) instead of inline text. + response = f"{response}\n\n---\n> {_footer_line}" + else: + response = f"{response}\n\n{_footer_line}" # Emit agent:end hook await self.hooks.emit("agent:end", { diff --git a/gateway/runtime_footer.py b/gateway/runtime_footer.py index 8719524d5a1d..ea6a51801c33 100644 --- a/gateway/runtime_footer.py +++ b/gateway/runtime_footer.py @@ -16,9 +16,13 @@ context_pct — last-call context occupancy as a percent (``5%``) latency — wall-clock duration of the turn (``22s``, ``1m05s``) cwd — home-relative working dir (``~``) + tokens_in — last-call prompt tokens (``15.9k``) + tokens_out — last-call output tokens (``1.2k``) + effort — agent reasoning effort (``high``) -``latency`` is opt-in: it is NOT in the default field set, so a footer whose -``fields`` are unset renders exactly as before. +``latency``/``tokens_in``/``tokens_out``/``effort`` are opt-in: they are NOT in +the default field set, so a footer whose ``fields`` are unset renders exactly +as before. Per-platform overrides live under ``display.platforms..runtime_footer``. Users can toggle the global setting with ``/footer on|off`` from both the CLI @@ -41,6 +45,17 @@ _SEP = " · " +def _fmt_tokens(n: int) -> str: + """Format a token count compactly: 15932 -> 15.9k, 28 -> 28.""" + if not n or n <= 0: + return "0" + if n >= 1000: + v = n / 1000.0 + s = f"{v:.1f}".rstrip("0").rstrip(".") + return f"{s}k" + return str(n) + + def _home_relative_cwd(cwd: str) -> str: """Return *cwd* with ``$HOME`` collapsed to ``~``. Empty string if unset.""" if not cwd: @@ -115,12 +130,16 @@ def format_runtime_footer( context_length: Optional[int], cwd: Optional[str] = None, turn_seconds: Optional[float] = None, + tokens_in: int = 0, + tokens_out: int = 0, + effort: Optional[str] = None, fields: Iterable[str] = _DEFAULT_FIELDS, ) -> str: """Render the footer line, or return "" if no fields have data. Fields are skipped silently when their underlying data is missing — a partially-populated footer is better than a line with ``?%`` or empty slots. + Supported fields: model, context_pct, cwd, latency, tokens_in, tokens_out, effort. """ parts: list[str] = [] for field in fields: @@ -131,7 +150,7 @@ def format_runtime_footer( elif field == "context_pct": if context_length and context_length > 0 and context_tokens >= 0: pct = max(0, min(100, round((context_tokens / context_length) * 100))) - parts.append(f"{pct}%") + parts.append(f"ctx {pct}%") elif field == "latency": # Wall-clock turn duration. Skipped when the caller supplied no # timing (call sites that don't measure) or the value is negative. @@ -141,6 +160,15 @@ def format_runtime_footer( rel = _home_relative_cwd(cwd or os.environ.get("TERMINAL_CWD", "")) if rel: parts.append(rel) + elif field == "tokens_in": + if tokens_in and tokens_in > 0: + parts.append(f"in {_fmt_tokens(tokens_in)}") + elif field == "tokens_out": + if tokens_out and tokens_out > 0: + parts.append(f"out {_fmt_tokens(tokens_out)}") + elif field == "effort": + if effort: + parts.append(f"effort:{effort}") # Unknown field names are silently ignored. if not parts: @@ -157,6 +185,9 @@ def build_footer_line( context_length: Optional[int], cwd: Optional[str] = None, turn_seconds: Optional[float] = None, + tokens_in: int = 0, + tokens_out: int = 0, + effort: Optional[str] = None, ) -> str: """Top-level entry point used by gateway/run.py. @@ -177,5 +208,8 @@ def build_footer_line( context_length=context_length, cwd=cwd, turn_seconds=turn_seconds, + tokens_in=tokens_in, + tokens_out=tokens_out, + effort=effort, fields=cfg.get("fields") or _DEFAULT_FIELDS, )