mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
feat(gateway): add /context command for a detailed context-window view
A dedicated /context (alias /ctx) gateway slash command that gives a full
context-window view with:
- Usage gauge: visual bar + fraction + percentage + headroom
- Auto-compression threshold and how far away it is
- Compression count and how much the last one freed
- Cumulative session throughput (explicitly labelled as throughput,
NOT context size — each call re-sends the window)
- Cascading fallback: running agent → cached agent → SessionStore metadata
→ rough transcript estimate
Not included (per current-main design):
- Cache reporting removed: commit 446b8e239 intentionally removed cache
reporting from user-facing surfaces because providers that omit cached-token
details produce misleading values
- Sync DB calls replaced with async_session_store (current main requires
AsyncSessionStore with await)
Also rewords the /status tokens line from 'Cumulative API tokens (re-sent
each call)' to 'Lifetime tokens billed: ... (not your current context size;
use /context)' to reduce the recurring confusion that the cumulative figure
is the current context window.
Fixes salvation of PR #52184 (salvage commit replaces a 12K-commit-behind
fork branch with a fresh implementation against current main, incorporating
reviewer feedback from @whoislikemiha and the hermes-sweeper).
This commit is contained in:
parent
3dccc45032
commit
4fe2fecf54
20 changed files with 654 additions and 22 deletions
|
|
@ -11168,6 +11168,9 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
if event.get_command() == "status":
|
||||
return await self._handle_status_command(event)
|
||||
|
||||
if event.get_command() in {"context", "ctx"}:
|
||||
return await self._handle_context_command(event)
|
||||
|
||||
# Resolve the command once for all early-intercept checks below.
|
||||
from hermes_cli.commands import (
|
||||
ACTIVE_SESSION_BYPASS_COMMANDS as _DEDICATED_HANDLERS,
|
||||
|
|
@ -11743,6 +11746,9 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
|
||||
return format_status_text()
|
||||
|
||||
if canonical == "context":
|
||||
return await self._handle_context_command(event)
|
||||
|
||||
if canonical == "agents":
|
||||
return await self._handle_agents_command(event)
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,19 @@ logger = logging.getLogger("gateway.run")
|
|||
_RESET_CLEANUP_TIMEOUT_S = 30.0
|
||||
|
||||
|
||||
def _clean_str(value: Any) -> str:
|
||||
"""Strip and return a non-empty string value, or empty string."""
|
||||
return value.strip() if isinstance(value, str) and value.strip() else ""
|
||||
|
||||
|
||||
def _int_value(value: Any) -> int:
|
||||
"""Safely coerce to int."""
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
|
||||
|
||||
def _model_switch_skew_guard() -> Optional[str]:
|
||||
"""Refuse a model switch when the gateway is running stale code.
|
||||
|
||||
|
|
@ -701,6 +714,180 @@ class GatewaySlashCommandsMixin:
|
|||
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()[:12]
|
||||
return f"sha256:{digest}"
|
||||
|
||||
async def _handle_context_command(self, event: MessageEvent) -> str:
|
||||
"""Handle /context — the dedicated context-window view.
|
||||
|
||||
/status shows a one-line ``used / total`` summary; this command is the
|
||||
deep view: a usage gauge, auto-compression threshold and headroom,
|
||||
compression count and last savings, and cumulative throughput — the last
|
||||
clearly labelled as throughput, NOT context size.
|
||||
|
||||
Resolves from the running agent (mid-turn), then the cached agent
|
||||
(between turns), then the SessionStore/SessionDB metadata for a gauge
|
||||
even when no agent is resident. Falls back to a transcript estimate only
|
||||
as a last resort.
|
||||
"""
|
||||
from gateway.run import _AGENT_PENDING_SENTINEL
|
||||
|
||||
source = event.source
|
||||
session_key = self._session_key_for_source(source)
|
||||
session_entry = await self.async_session_store.get_or_create_session(source)
|
||||
|
||||
# Try running agent first (mid-turn), then cached agent (between turns).
|
||||
agent = self._running_agents.get(session_key)
|
||||
if not agent or agent is _AGENT_PENDING_SENTINEL:
|
||||
cache_lock = getattr(self, "_agent_cache_lock", None)
|
||||
cache = getattr(self, "_agent_cache", None)
|
||||
if cache_lock is not None and cache is not None:
|
||||
try:
|
||||
with cache_lock:
|
||||
cached = cache.get(session_key)
|
||||
if cached:
|
||||
agent = cached[0]
|
||||
except Exception:
|
||||
agent = None
|
||||
has_agent = bool(agent) and agent is not _AGENT_PENDING_SENTINEL
|
||||
|
||||
ctx = getattr(agent, "context_compressor", None) if has_agent else None
|
||||
|
||||
# Resolve current-context size + window with cascading fallbacks.
|
||||
# used : compressor.last_prompt_tokens → SessionStore.last_prompt_tokens
|
||||
# model : agent.model → SessionDB row model
|
||||
# window: compressor.context_length → get_model_context_length(model)
|
||||
used = 0
|
||||
context_length = 0
|
||||
if ctx is not None:
|
||||
used = getattr(ctx, "last_prompt_tokens", 0) or 0
|
||||
context_length = getattr(ctx, "context_length", 0) or 0
|
||||
|
||||
model_name = _clean_str(getattr(agent, "model", "")) if has_agent else ""
|
||||
|
||||
if not used:
|
||||
used = _int_value(getattr(session_entry, "last_prompt_tokens", 0))
|
||||
|
||||
if not model_name and self._session_db:
|
||||
try:
|
||||
row = await self._session_db.get_session(session_entry.session_id) or {}
|
||||
if isinstance(row, dict):
|
||||
model_name = _clean_str(row.get("model", ""))
|
||||
except Exception:
|
||||
model_name = ""
|
||||
|
||||
if not context_length and model_name:
|
||||
try:
|
||||
from agent.model_metadata import get_model_context_length
|
||||
|
||||
context_length = _int_value(
|
||||
await asyncio.to_thread(get_model_context_length, model_name)
|
||||
)
|
||||
except Exception:
|
||||
context_length = 0
|
||||
|
||||
# Gauge path: real current-context figure
|
||||
if used > 0 and context_length > 0:
|
||||
pct = min(100.0, used / context_length * 100)
|
||||
headroom = max(0, context_length - used)
|
||||
BAR_WIDTH = 24
|
||||
filled = int(round(pct / 100 * BAR_WIDTH))
|
||||
bar = "█" * max(0, filled) + "░" * max(0, BAR_WIDTH - filled)
|
||||
|
||||
lines = [
|
||||
t("gateway.context.header"),
|
||||
"",
|
||||
t("gateway.context.model", model=model_name or "?"),
|
||||
t("gateway.context.window", total=f"{context_length:,}"),
|
||||
t(
|
||||
"gateway.context.in_use",
|
||||
used=f"{used:,}",
|
||||
total=f"{context_length:,}",
|
||||
pct=f"{pct:.0f}",
|
||||
),
|
||||
t("gateway.context.bar", bar=bar),
|
||||
t("gateway.context.headroom", headroom=f"{headroom:,}"),
|
||||
]
|
||||
|
||||
# Full view — compression / throughput need the live agent.
|
||||
if ctx is not None:
|
||||
threshold = getattr(ctx, "threshold_tokens", 0) or 0
|
||||
threshold_pct = (getattr(ctx, "threshold_percent", 0) or 0) * 100
|
||||
lines.append("")
|
||||
if threshold > 0:
|
||||
if used >= threshold:
|
||||
lines.append(
|
||||
t(
|
||||
"gateway.context.over_threshold",
|
||||
threshold=f"{threshold:,}",
|
||||
threshold_pct=f"{threshold_pct:.0f}",
|
||||
)
|
||||
)
|
||||
else:
|
||||
lines.append(
|
||||
t(
|
||||
"gateway.context.threshold",
|
||||
threshold=f"{threshold:,}",
|
||||
threshold_pct=f"{threshold_pct:.0f}",
|
||||
to_go=f"{threshold - used:,}",
|
||||
)
|
||||
)
|
||||
compressions = getattr(ctx, "compression_count", 0) or 0
|
||||
lines.append(t("gateway.context.compressions", count=compressions))
|
||||
if compressions:
|
||||
savings = getattr(ctx, "_last_compression_savings_pct", None)
|
||||
if savings is not None:
|
||||
lines.append(
|
||||
t("gateway.context.last_savings", savings=f"{savings:.0f}")
|
||||
)
|
||||
|
||||
api_calls = getattr(agent, "session_api_calls", 0) or 0
|
||||
input_tokens = getattr(agent, "session_input_tokens", 0) or 0
|
||||
output_tokens = getattr(agent, "session_output_tokens", 0) or 0
|
||||
reasoning_tokens = getattr(agent, "session_reasoning_tokens", 0) or 0
|
||||
total_tokens = getattr(agent, "session_total_tokens", 0) or 0
|
||||
lines.append("")
|
||||
lines.append(
|
||||
t("gateway.context.totals_header", calls=api_calls)
|
||||
)
|
||||
lines.append(
|
||||
t(
|
||||
"gateway.context.totals_line",
|
||||
input=f"{input_tokens:,}",
|
||||
output=f"{output_tokens:,}",
|
||||
reasoning=f"{reasoning_tokens:,}",
|
||||
)
|
||||
)
|
||||
lines.append(t("gateway.context.total_billed", total=f"{total_tokens:,}"))
|
||||
lines.append(t("gateway.context.throughput_note"))
|
||||
else:
|
||||
lines.append("")
|
||||
lines.append(t("gateway.context.detail_after_first"))
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
# Last resort: rough estimate from transcript
|
||||
history = await self.async_session_store.load_transcript(session_entry.session_id)
|
||||
if history:
|
||||
from agent.model_metadata import estimate_messages_tokens_rough
|
||||
|
||||
msgs = [
|
||||
m
|
||||
for m in history
|
||||
if m.get("role") in {"user", "assistant"} and m.get("content")
|
||||
]
|
||||
approx = estimate_messages_tokens_rough(msgs)
|
||||
return "\n".join(
|
||||
[
|
||||
t("gateway.context.header"),
|
||||
"",
|
||||
t(
|
||||
"gateway.context.estimated",
|
||||
count=f"{approx:,}",
|
||||
messages=len(msgs),
|
||||
),
|
||||
t("gateway.context.detail_after_first"),
|
||||
]
|
||||
)
|
||||
return t("gateway.context.no_data")
|
||||
|
||||
def _gateway_session_origin_for_id(self, session_id: str) -> Optional[SessionSource]:
|
||||
"""Best-effort origin lookup for gateway session IDs."""
|
||||
lookup = getattr(type(self.session_store), "lookup_by_session_id", None)
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ COMMAND_REGISTRY: list[CommandDef] = [
|
|||
CommandDef("status", "Show session, model, token, and context info", "Session"),
|
||||
CommandDef("egress", "Show Docker egress proxy status", "Session",
|
||||
args_hint="[status]", subcommands=("status",)),
|
||||
CommandDef("context", "Show detailed context window view with usage gauge, compression stats, and throughput", "Session",
|
||||
aliases=("ctx",)),
|
||||
CommandDef("whoami", "Show your slash command access (admin / user)", "Info"),
|
||||
CommandDef("profile", "Show active profile name and home directory", "Info"),
|
||||
CommandDef("sethome", "Set this chat as the home channel", "Session",
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "Kon nie tuiste-kanaal stoor nie: {error}"
|
||||
success: "✅ Tuiste-kanaal gestel op **{name}** (ID: {chat_id}).\nKron-take en kruisplatform-boodskappe sal hier afgelewer word."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Hermes Gateway Status**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Kumulatiewe API-tokens (elke oproep weer gestuur):** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Agent loop:** {state}"
|
||||
state_yes: "Ja ⚡"
|
||||
state_no: "Nee"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "Home-Kanal konnte nicht gespeichert werden: {error}"
|
||||
success: "✅ Home-Kanal auf **{name}** (ID: {chat_id}) gesetzt.\nCron-Jobs und plattformübergreifende Nachrichten werden hierher geliefert."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Hermes-Gateway-Status**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Modell:** `{model}` ({provider})"
|
||||
context: "**Kontext:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Kontext:** ~{used} Tokens"
|
||||
tokens: "**Kumulierte API-Tokens (bei jedem Aufruf erneut gesendet):** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Agent läuft:** {state}"
|
||||
state_yes: "Ja ⚡"
|
||||
state_no: "Nein"
|
||||
|
|
|
|||
|
|
@ -295,6 +295,25 @@ gateway:
|
|||
save_failed: "Failed to save home channel: {error}"
|
||||
success: "✅ Home channel set to **{name}** (ID: {chat_id}).\nCron jobs and cross-platform messages will be delivered here."
|
||||
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Hermes Gateway Status**"
|
||||
matrix_scope_header: "**Matrix scope:**"
|
||||
|
|
@ -311,7 +330,7 @@ gateway:
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Cumulative API tokens (re-sent each call):** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Agent Running:** {state}"
|
||||
state_yes: "Yes ⚡"
|
||||
state_no: "No"
|
||||
|
|
|
|||
|
|
@ -279,6 +279,24 @@ gateway:
|
|||
set_home:
|
||||
save_failed: "No se pudo guardar el canal principal: {error}"
|
||||
success: "✅ Canal principal establecido en **{name}** (ID: {chat_id}).\nLas tareas cron y los mensajes entre plataformas se entregarán aquí."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Estado de Hermes Gateway**"
|
||||
|
|
@ -296,7 +314,7 @@ gateway:
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Tokens de API acumulados (reenviados en cada llamada):** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Agente activo:** {state}"
|
||||
state_yes: "Sí ⚡"
|
||||
state_no: "No"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "Impossible d'enregistrer le canal principal : {error}"
|
||||
success: "✅ Canal principal défini sur **{name}** (ID : {chat_id}).\nLes tâches cron et les messages multi-plateformes seront livrés ici."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **État de Hermes Gateway**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Jetons :** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Agent en cours :** {state}"
|
||||
state_yes: "Oui ⚡"
|
||||
state_no: "Non"
|
||||
|
|
|
|||
|
|
@ -286,6 +286,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "Theip ar shábháil chainéil bhaile: {error}"
|
||||
success: "✅ Cainéal baile socraithe go **{name}** (ID: {chat_id}).\nSeachadfar tascanna cron agus teachtaireachtaí trasardáin anseo."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Stádas Hermes Gateway**"
|
||||
|
|
@ -303,7 +321,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Comharthaí:** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Gníomhaire ag rith:** {state}"
|
||||
state_yes: "Tá ⚡"
|
||||
state_no: "Níl"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "Nem sikerült menteni a kezdőcsatornát: {error}"
|
||||
success: "✅ Kezdőcsatorna beállítva: **{name}** (ID: {chat_id}).\nA cron-feladatok és a platformok közötti üzenetek ide érkeznek."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Hermes Gateway állapot**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Tokenek:** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Ügynök fut:** {state}"
|
||||
state_yes: "Igen ⚡"
|
||||
state_no: "Nem"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "Salvataggio del canale home non riuscito: {error}"
|
||||
success: "✅ Canale home impostato su **{name}** (ID: {chat_id}).\nI cron job e i messaggi cross-platform verranno consegnati qui."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Stato del Gateway Hermes**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Token:** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Agente in esecuzione:** {state}"
|
||||
state_yes: "Sì ⚡"
|
||||
state_no: "No"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "ホームチャンネルを保存できませんでした: {error}"
|
||||
success: "✅ ホームチャンネルを **{name}** (ID: {chat_id}) に設定しました。\nCron ジョブとプラットフォーム間メッセージはここに配信されます。"
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Hermes ゲートウェイ状態**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**トークン:** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**エージェント実行中:** {state}"
|
||||
state_yes: "はい ⚡"
|
||||
state_no: "いいえ"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "홈 채널 저장에 실패했습니다: {error}"
|
||||
success: "✅ 홈 채널이 **{name}**(ID: {chat_id})(으)로 설정되었습니다.\n크론 작업과 플랫폼 간 메시지가 여기로 전달됩니다."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Hermes 게이트웨이 상태**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**토큰:** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**에이전트 실행 중:** {state}"
|
||||
state_yes: "예 ⚡"
|
||||
state_no: "아니오"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "Falha ao guardar o canal principal: {error}"
|
||||
success: "✅ Canal principal definido como **{name}** (ID: {chat_id}).\nAs tarefas cron e mensagens entre plataformas serão entregues aqui."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Estado do Hermes Gateway**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Tokens de API cumulativos (reenviados a cada chamada):** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Agente em execução:** {state}"
|
||||
state_yes: "Sim ⚡"
|
||||
state_no: "Não"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "Не удалось сохранить главный канал: {error}"
|
||||
success: "✅ Главный канал установлен на **{name}** (ID: {chat_id}).\nCron-задачи и межплатформенные сообщения будут доставляться сюда."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Состояние Hermes Gateway**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Токены:** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Агент активен:** {state}"
|
||||
state_yes: "Да ⚡"
|
||||
state_no: "Нет"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "Ana kanal kaydedilemedi: {error}"
|
||||
success: "✅ Ana kanal **{name}** (ID: {chat_id}) olarak ayarlandı.\nCron işleri ve platformlar arası mesajlar buraya iletilecek."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Hermes Gateway Durumu**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Token:** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Aracı çalışıyor:** {state}"
|
||||
state_yes: "Evet ⚡"
|
||||
state_no: "Hayır"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "Не вдалося зберегти головний канал: {error}"
|
||||
success: "✅ Головний канал встановлено на **{name}** (ID: {chat_id}).\nCron-завдання та міжплатформні повідомлення доставлятимуться сюди."
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Стан Hermes Gateway**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Токени:** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**Агент активний:** {state}"
|
||||
state_yes: "Так ⚡"
|
||||
state_no: "Ні"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "無法儲存主頻道:{error}"
|
||||
success: "✅ 主頻道已設定為 **{name}**(ID:{chat_id})。\n排程任務和跨平台訊息將傳送至此處。"
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Hermes 閘道狀態**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Token 數:** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**代理執行中:** {state}"
|
||||
state_yes: "是 ⚡"
|
||||
state_no: "否"
|
||||
|
|
|
|||
|
|
@ -282,6 +282,24 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
set_home:
|
||||
save_failed: "无法保存主频道:{error}"
|
||||
success: "✅ 主频道已设置为 **{name}**(ID:{chat_id})。\n定时任务和跨平台消息将发送到此处。"
|
||||
context:
|
||||
header: "🧠 **Context Window**"
|
||||
model: "Model: `{model}`"
|
||||
window: "Window: {total} tokens"
|
||||
in_use: "In use: {used} / {total} ({pct}%)"
|
||||
bar: "{bar}"
|
||||
headroom: "Headroom to limit: {headroom} tokens"
|
||||
threshold: "Auto-compresses at: {threshold} ({threshold_pct}%) — {to_go} to go"
|
||||
over_threshold: "⚠️ **Over auto-compression threshold ({threshold}, {threshold_pct}%)**"
|
||||
compressions: "Compressions this session: {count}"
|
||||
last_savings: "Last compression freed: {savings}% of context"
|
||||
totals_header: "Session totals (cumulative across {calls} API calls)"
|
||||
totals_line: "Input {input} · Output {output} · Reasoning {reasoning}"
|
||||
total_billed: "Total billed: {total}"
|
||||
throughput_note: "_Throughput, not context size — each call re-sends the window above._"
|
||||
estimated: "Estimated context: ~{count} tokens across {messages} messages"
|
||||
detail_after_first: "_(Full compression and throughput stats available after the first agent response)_"
|
||||
no_data: "No context data available yet. Send a message to start a session."
|
||||
|
||||
status:
|
||||
header: "📊 **Hermes 网关状态**"
|
||||
|
|
@ -299,7 +317,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
model_provider: "**Model:** `{model}` ({provider})"
|
||||
context: "**Context:** {used} / {total} ({pct}%)"
|
||||
context_used: "**Context:** ~{used} tokens"
|
||||
tokens: "**Token 数:** {tokens}"
|
||||
tokens: "**Lifetime tokens billed:** {tokens} _(not your current context size; use `/context`)_"
|
||||
agent_running: "**代理运行中:** {state}"
|
||||
state_yes: "是 ⚡"
|
||||
state_no: "否"
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ async def test_status_command_reports_running_agent_without_interrupt(monkeypatc
|
|||
result = await runner._handle_message(_make_event("/status"))
|
||||
|
||||
assert "**Session ID:** `sess-1`" in result
|
||||
assert "**Cumulative API tokens (re-sent each call):** 321" in result
|
||||
assert "**Lifetime tokens billed:** 321" in result
|
||||
assert "**Agent Running:** Yes ⚡" in result
|
||||
assert "**Title:**" not in result
|
||||
running_agent.interrupt.assert_not_called()
|
||||
|
|
@ -153,7 +153,7 @@ async def test_status_command_reads_token_totals_from_session_db():
|
|||
result = await runner._handle_message(_make_event("/status"))
|
||||
|
||||
# 1000 + 250 + 500 + 100 + 50 = 1,900
|
||||
assert "**Cumulative API tokens (re-sent each call):** 1,900" in result
|
||||
assert "**Lifetime tokens billed:** 1,900" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -174,7 +174,7 @@ async def test_status_command_tokens_zero_when_session_db_row_missing():
|
|||
|
||||
result = await runner._handle_message(_make_event("/status"))
|
||||
|
||||
assert "**Cumulative API tokens (re-sent each call):** 0" in result
|
||||
assert "**Lifetime tokens billed:** 0" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -212,8 +212,7 @@ async def test_status_command_includes_live_agent_model_and_context():
|
|||
|
||||
assert "**Model:** `openai/gpt-test` (openai)" in result
|
||||
assert "**Context:** 12,345 / 100,000 (12%)" in result
|
||||
assert "**Cumulative API tokens (re-sent each call):** 1,250" in result
|
||||
assert "1,250 (cumulative)" not in result
|
||||
assert "**Lifetime tokens billed:** 1,250" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -245,7 +244,7 @@ async def test_status_command_includes_persisted_model_and_context_when_agent_no
|
|||
|
||||
assert "**Model:** `openai/gpt-persisted` (openai-codex)" in result
|
||||
assert "**Context:** 24,000 / 272,000 (9%)" in result
|
||||
assert "**Cumulative API tokens (re-sent each call):** 2,500" in result
|
||||
assert "**Lifetime tokens billed:** 2,500" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -821,3 +820,152 @@ async def test_post_delivery_callback_generation_snapshot_happens_after_bind():
|
|||
assert fired == []
|
||||
assert session_key in adapter._post_delivery_callbacks
|
||||
assert adapter._post_delivery_callbacks[session_key][0] == 2
|
||||
|
||||
|
||||
# ── /context command tests ────────────────────────────────────────────────
|
||||
|
||||
def _stub_agent(**overrides) -> SimpleNamespace:
|
||||
"""Build a stub agent with the attributes _handle_context_command reads."""
|
||||
props = dict(
|
||||
model="openai/gpt-test",
|
||||
context_compressor=SimpleNamespace(
|
||||
last_prompt_tokens=47_231,
|
||||
context_length=200_000,
|
||||
threshold_tokens=100_000,
|
||||
threshold_percent=0.5,
|
||||
compression_count=2,
|
||||
_last_compression_savings_pct=63.0,
|
||||
),
|
||||
session_api_calls=47,
|
||||
session_input_tokens=410_000,
|
||||
session_output_tokens=38_000,
|
||||
session_reasoning_tokens=12_000,
|
||||
session_total_tokens=3_158_641,
|
||||
session_cache_read_tokens=2_900_000,
|
||||
session_cache_write_tokens=48_000,
|
||||
)
|
||||
props.update(overrides)
|
||||
return SimpleNamespace(**props)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_context_command_live_agent():
|
||||
"""/context with a live running agent shows the full view: gauge,
|
||||
compression, and throughput — but NOT cache stats."""
|
||||
session_entry = SessionEntry(
|
||||
session_key=build_session_key(_make_source()),
|
||||
session_id="sess-1",
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now(),
|
||||
platform=Platform.TELEGRAM,
|
||||
chat_type="dm",
|
||||
)
|
||||
runner = _make_runner(session_entry)
|
||||
session_key = session_entry.session_key
|
||||
agent = _stub_agent()
|
||||
runner._running_agents[session_key] = agent
|
||||
|
||||
result = await runner._handle_context_command(_make_event("/context"))
|
||||
|
||||
assert "🧠 **Context Window**" in result
|
||||
assert "Model: `openai/gpt-test`" in result
|
||||
assert "Window: 200,000 tokens" in result
|
||||
assert "In use: 47,231 / 200,000 (24%)" in result
|
||||
assert "Headroom to limit: 152,769 tokens" in result
|
||||
# Compression section
|
||||
assert "Auto-compresses at: 100,000 (50%)" in result
|
||||
assert "Compressions this session: 2" in result
|
||||
assert "Last compression freed: 63% of context" in result
|
||||
# Throughput — NOT cache
|
||||
assert "Session totals (cumulative across 47 API calls)" in result
|
||||
assert "Input 410,000" in result
|
||||
assert "Output 38,000" in result
|
||||
assert "Reasoning 12,000" in result
|
||||
assert "Total billed: 3,158,641" in result
|
||||
assert "each call re-sends the window above" in result
|
||||
# Cache stats must NOT appear (removed per design)
|
||||
assert "Cache read" not in result
|
||||
assert "Cache write" not in result
|
||||
assert "Cache hit" not in result
|
||||
assert "Hit rate" not in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_context_command_over_threshold():
|
||||
"""When used >= threshold, the over-threshold warning is shown."""
|
||||
session_entry = SessionEntry(
|
||||
session_key=build_session_key(_make_source()),
|
||||
session_id="sess-2",
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now(),
|
||||
platform=Platform.TELEGRAM,
|
||||
chat_type="dm",
|
||||
)
|
||||
runner = _make_runner(session_entry)
|
||||
session_key = session_entry.session_key
|
||||
agent = _stub_agent(
|
||||
context_compressor=SimpleNamespace(
|
||||
last_prompt_tokens=150_000,
|
||||
context_length=200_000,
|
||||
threshold_tokens=100_000,
|
||||
threshold_percent=0.5,
|
||||
compression_count=5,
|
||||
_last_compression_savings_pct=40.0,
|
||||
)
|
||||
)
|
||||
runner._running_agents[session_key] = agent
|
||||
|
||||
result = await runner._handle_context_command(_make_event("/context"))
|
||||
|
||||
assert "⚠️" in result
|
||||
assert "Over auto-compression threshold" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_context_command_no_agent_transcript_fallback():
|
||||
"""When no agent is resident and session_entry has no last_prompt_tokens,
|
||||
/context falls back to a transcript estimate."""
|
||||
session_entry = SessionEntry(
|
||||
session_key=build_session_key(_make_source()),
|
||||
session_id="sess-3",
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now(),
|
||||
platform=Platform.TELEGRAM,
|
||||
chat_type="dm",
|
||||
last_prompt_tokens=0, # No live context data
|
||||
)
|
||||
runner = _make_runner(session_entry)
|
||||
# Stub the transcript so estimate_messages_tokens_rough has something to work with
|
||||
runner.session_store.load_transcript.return_value = [
|
||||
{"role": "user", "content": "Hello"},
|
||||
{"role": "assistant", "content": "Hi there!"},
|
||||
{"role": "user", "content": "What's my balance?"},
|
||||
{"role": "assistant", "content": "Your balance is $1,000."},
|
||||
]
|
||||
|
||||
result = await runner._handle_context_command(_make_event("/context"))
|
||||
|
||||
assert "🧠 **Context Window**" in result
|
||||
assert "Estimated context:" in result
|
||||
assert "4 messages" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_context_command_no_data():
|
||||
"""When there's no agent, no session data, and no transcript,
|
||||
/context returns the no-data message."""
|
||||
session_entry = SessionEntry(
|
||||
session_key=build_session_key(_make_source()),
|
||||
session_id="sess-4",
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now(),
|
||||
platform=Platform.TELEGRAM,
|
||||
chat_type="dm",
|
||||
last_prompt_tokens=0,
|
||||
)
|
||||
runner = _make_runner(session_entry)
|
||||
runner.session_store.load_transcript.return_value = []
|
||||
|
||||
result = await runner._handle_context_command(_make_event("/context"))
|
||||
|
||||
assert "No context data available yet" in result
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue