Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -6499,6 +6499,7 @@ def _approval_notify_sync(approval_data: dict) -> None:
_output_toks = getattr(_agent, "session_completion_tokens", 0)
_context_length = getattr(_agent.context_compressor, "context_length", 0) or 0
_resolved_model = getattr(_agent, "model", None) if _agent else None
_resolved_provider = getattr(_agent, "provider", None) if _agent else None

# Sync session_id immediately after run_conversation(). Compression
# can rotate before a follow-up model call fails; the failure return
Expand Down Expand Up @@ -6634,6 +6635,7 @@ def _approval_notify_sync(approval_data: dict) -> None:
"input_tokens": _input_toks,
"output_tokens": _output_toks,
"model": _resolved_model,
"provider": _resolved_provider,
"context_length": _context_length,
}

Expand Down Expand Up @@ -6715,6 +6717,7 @@ def _approval_notify_sync(approval_data: dict) -> None:
"input_tokens": _input_toks,
"output_tokens": _output_toks,
"model": _resolved_model,
"provider": _resolved_provider,
"context_length": _context_length,
"session_id": effective_session_id,
"response_previewed": result.get("response_previewed", False),
Expand Down Expand Up @@ -20645,6 +20648,7 @@ async def _handle_message_with_agent(self, event, source, _quick_key: str, run_g
_footer_line = _bfl(
user_config=_load_gateway_config(),
platform_key=_platform_config_key(source.platform),
provider=agent_result.get("provider"),
model=agent_result.get("model"),
context_tokens=agent_result.get("last_prompt_tokens", 0) or 0,
context_length=agent_result.get("context_length") or None,
Expand Down
12 changes: 10 additions & 2 deletions gateway/runtime_footer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
display:
runtime_footer:
enabled: true # off by default
fields: [model, context_pct, cwd] # order shown; drop any to hide
fields: [provider, model, context_pct, cwd] # order shown; drop any to hide

Available fields:
provider — active model provider/backend (``openrouter``)
model — bare model id, vendor prefix dropped (``gpt-5.4``)
context_pct — last-call context occupancy as a percent (``5%``)
latency — wall-clock duration of the turn (``22s``, ``1m05s``)
Expand Down Expand Up @@ -110,6 +111,7 @@ def _format_latency(seconds: float) -> str:

def format_runtime_footer(
*,
provider: Optional[str] = None,
model: Optional[str],
context_tokens: int,
context_length: Optional[int],
Expand All @@ -124,7 +126,11 @@ def format_runtime_footer(
"""
parts: list[str] = []
for field in fields:
if field == "model":
if field == "provider":
value = str(provider or "").strip()
if value:
parts.append(value)
elif field == "model":
m = _model_short(model)
if m:
parts.append(m)
Expand Down Expand Up @@ -152,6 +158,7 @@ def build_footer_line(
*,
user_config: dict[str, Any] | None,
platform_key: str | None,
provider: Optional[str] = None,
model: Optional[str],
context_tokens: int,
context_length: Optional[int],
Expand All @@ -172,6 +179,7 @@ def build_footer_line(
if not cfg.get("enabled"):
return ""
return format_runtime_footer(
provider=provider,
model=model,
context_tokens=context_tokens,
context_length=context_length,
Expand Down
63 changes: 62 additions & 1 deletion tests/gateway/test_runtime_footer.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,68 @@ def test_format_footer_skips_missing_context_length():
assert "/tmp/wd" in out


def test_format_footer_provider_renders_in_configured_field_order():
out = format_runtime_footer(
provider="openrouter",
model="openrouter/openai/gpt-5.4",
context_tokens=68000,
context_length=100000,
fields=("provider", "model", "context_pct"),
)
assert out == "openrouter · gpt-5.4 · 68%"


def test_format_footer_skips_missing_provider():
out = format_runtime_footer(
provider=None,
model="gpt-5.4",
context_tokens=0,
context_length=None,
fields=("provider", "model"),
)
assert out == "gpt-5.4"


@pytest.mark.parametrize(
"provider_value,expected",
[
(None, "gpt-5.4"),
("", "gpt-5.4"),
(" ", "gpt-5.4"),
("deepseek", "deepseek · gpt-5.4"),
],
)
def test_format_footer_provider_empty_and_whitespace_skipped(provider_value, expected):
out = format_runtime_footer(
provider=provider_value,
model="gpt-5.4",
context_tokens=0,
context_length=None,
fields=("provider", "model"),
)
assert out == expected


def test_build_footer_line_threads_provider():
out = build_footer_line(
user_config={
"display": {
"runtime_footer": {
"enabled": True,
"fields": ["provider", "model"],
}
}
},
platform_key="telegram",
provider="deepseek",
model="deepseek-v4-flash",
context_tokens=0,
context_length=None,
cwd="",
)
assert out == "deepseek · deepseek-v4-flash"


# ---------------------------------------------------------------------------
# resolve_footer_config
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -133,7 +195,6 @@ def test_build_footer_per_platform_off_suppresses():
assert out == ""



# ---------------------------------------------------------------------------
# latency — opt-in wall-clock turn duration
# ---------------------------------------------------------------------------
Expand Down
6 changes: 5 additions & 1 deletion website/docs/user-guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1920,7 +1920,7 @@ Focus view is **display-only**. It never edits conversation history, the system

### Runtime-metadata footer (gateway only)

When `display.runtime_footer.enabled: true`, Hermes appends a small runtime-context footer to the **final** message of each gateway turn. The current footer can show the model, context-window percentage, and current working directory. Off by default; opt in per-gateway if your team wants every reply to include this provenance.
When `display.runtime_footer.enabled: true`, Hermes appends a small runtime-context footer to the **final** message of each gateway turn. The footer can show the active provider, model, context-window percentage, and current working directory. Off by default; opt in per-gateway if your team wants every reply to include this provenance.

```yaml
display:
Expand All @@ -1933,6 +1933,7 @@ Supported fields:

| Field | Renders | Example |
| --- | --- | --- |
| `provider` | Active model provider/backend | `openrouter` |
| `model` | Bare model id, vendor prefix dropped | `gpt-5.4` |
| `context_pct` | Last-call context occupancy as a percent | `5%` |
| `latency` | Wall-clock duration of the turn | `22s`, `1m05s` |
Expand All @@ -1942,6 +1943,9 @@ The default field set is `["model", "context_pct", "cwd"]`. `latency` is opt-in

The `/footer` slash command toggles this at runtime in any session.

For provider provenance, add `provider` wherever you want it in the field order,
for example `["provider", "model", "context_pct"]`.

Example footer appended to a Telegram/Discord/Slack reply:

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1260,15 +1260,18 @@ display:

### 运行时元数据页脚(仅限 gateway)

当 `display.runtime_footer.enabled: true` 时,Hermes 在每个 gateway 轮次的**最终**消息中附加一个小型运行时上下文页脚。目前页脚可显示模型、上下文窗口百分比和当前工作目录。默认关闭;如果您的团队希望每个回复都包含这些来源信息,请按 gateway 选择加入。
当 `display.runtime_footer.enabled: true` 时,Hermes 在每个 gateway 轮次的**最终**消息中附加一个小型运行时上下文页脚。目前页脚可显示模型提供商、模型、上下文窗口百分比和当前工作目录。默认关闭;如果您的团队希望每个回复都包含这些来源信息,请按 gateway 选择加入。

```yaml
display:
runtime_footer:
enabled: true
fields: ["model", "context_pct", "cwd"] # 支持字段:model、context_pct、cwd
fields: ["model", "context_pct", "cwd"]
```

将 `provider` 加入 `fields` 即可显示当前模型提供商,例如
`["provider", "model", "context_pct"]`。

`/footer` 斜杠命令在任何会话中运行时切换此功能。

附加到 Telegram/Discord/Slack 回复的示例页脚:
Expand Down