mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-30 11:52:04 +00:00
feat(gateway): show per-category context breakdown in /usage (#55204)
Channel users get the same context split the desktop popover shows (PR #54907) — system prompt, tools, rules, skills, MCP, subagents, memory, conversation — under the existing Context line in /usage. Reuses agent.context_breakdown.compute_session_context_breakdown, so there is no new tool and no new engine. The slices are estimates (chars/4) and the block is labelled _(estimated)_; the headline Context line keeps using the provider-measured last_prompt_tokens. Rendering is fail-open: any engine error returns no breakdown and the rest of /usage is unaffected. - gateway/slash_commands.py: _context_breakdown_lines() helper + wire into _handle_usage_command - locales/*.yaml: breakdown_header, breakdown_line, and 8 category labels across all 16 locales (parity gate) - tests/gateway/test_usage_command.py: render + fail-open coverage
This commit is contained in:
parent
53a75f147f
commit
6aefc9d925
18 changed files with 272 additions and 0 deletions
|
|
@ -3455,6 +3455,47 @@ class GatewaySlashCommandsMixin:
|
|||
lines.append("Complete your top-up in the browser — credits will appear in /credits shortly.")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _context_breakdown_lines(self, agent, source) -> list[str]:
|
||||
"""Render the per-category context breakdown for /usage.
|
||||
|
||||
Estimated (chars/4) — same engine the desktop popover uses. Returns an
|
||||
empty list and never raises on failure so /usage stays robust.
|
||||
"""
|
||||
try:
|
||||
from agent.context_breakdown import compute_session_context_breakdown
|
||||
|
||||
history: list[dict] = []
|
||||
try:
|
||||
entry = self.session_store.get_or_create_session(source)
|
||||
history = self.session_store.load_transcript(entry.session_id) or []
|
||||
except Exception:
|
||||
history = []
|
||||
|
||||
payload = compute_session_context_breakdown(agent, history)
|
||||
categories = payload.get("categories") or []
|
||||
if not categories:
|
||||
return []
|
||||
|
||||
total = payload.get("estimated_total") or 0
|
||||
out = [t("gateway.usage.breakdown_header")]
|
||||
for cat in categories:
|
||||
tokens = int(cat.get("tokens") or 0)
|
||||
if tokens <= 0:
|
||||
continue
|
||||
cat_id = str(cat.get("id") or "")
|
||||
label = t(f"gateway.usage.breakdown_cat_{cat_id}")
|
||||
# Missing key → t() echoes the key back; fall back to the
|
||||
# English label the engine already provides.
|
||||
if label.endswith(f"breakdown_cat_{cat_id}"):
|
||||
label = str(cat.get("label") or cat_id)
|
||||
pct = round(tokens / total * 100) if total else 0
|
||||
out.append(
|
||||
t("gateway.usage.breakdown_line", label=label, count=f"{tokens:,}", pct=pct)
|
||||
)
|
||||
return out if len(out) > 1 else []
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
async def _handle_usage_command(self, event: MessageEvent) -> str:
|
||||
"""Handle /usage command -- show token usage for the current session.
|
||||
|
||||
|
|
@ -3554,6 +3595,15 @@ class GatewaySlashCommandsMixin:
|
|||
if ctx.compression_count:
|
||||
lines.append(t("gateway.usage.label_compressions", count=ctx.compression_count))
|
||||
|
||||
# Per-category context breakdown (estimated — chars/4 heuristic).
|
||||
# Same engine the desktop popover uses (PR #54907). The system
|
||||
# prompt / tools / skills / memory slices read off the live agent;
|
||||
# the conversation slice is estimated from the session transcript.
|
||||
breakdown_lines = self._context_breakdown_lines(agent, source)
|
||||
if breakdown_lines:
|
||||
lines.append("")
|
||||
lines.extend(breakdown_lines)
|
||||
|
||||
if account_lines:
|
||||
lines.append("")
|
||||
lines.extend(account_lines)
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "Koste: ingesluit"
|
||||
label_context: "Konteks: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Saamperserings: {count}"
|
||||
breakdown_header: "🧩 **Konteksverdeling** _(geskat)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "Stelselopdrag"
|
||||
breakdown_cat_tool_definitions: "Nutsmiddeldefinisies"
|
||||
breakdown_cat_rules: "Reëls"
|
||||
breakdown_cat_skills: "Vaardighede"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Subagent-definisies"
|
||||
breakdown_cat_memory: "Geheue"
|
||||
breakdown_cat_conversation: "Gesprek"
|
||||
header_session_info: "📊 **Sessie-inligting**"
|
||||
label_messages: "Boodskappe: {count}"
|
||||
label_estimated_context: "Geskatte konteks: ~{count} tokens"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "Kosten: inbegriffen"
|
||||
label_context: "Kontext: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Kompressionen: {count}"
|
||||
breakdown_header: "🧩 **Kontextaufschlüsselung** _(geschätzt)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "System-Prompt"
|
||||
breakdown_cat_tool_definitions: "Tool-Definitionen"
|
||||
breakdown_cat_rules: "Regeln"
|
||||
breakdown_cat_skills: "Fähigkeiten"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Subagent-Definitionen"
|
||||
breakdown_cat_memory: "Speicher"
|
||||
breakdown_cat_conversation: "Konversation"
|
||||
header_session_info: "📊 **Sitzungsinfo**"
|
||||
label_messages: "Nachrichten: {count}"
|
||||
label_estimated_context: "Geschätzter Kontext: ~{count} Tokens"
|
||||
|
|
|
|||
|
|
@ -344,6 +344,16 @@ gateway:
|
|||
label_cost_included: "Cost: included"
|
||||
label_context: "Context: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Compressions: {count}"
|
||||
breakdown_header: "🧩 **Context breakdown** _(estimated)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "System prompt"
|
||||
breakdown_cat_tool_definitions: "Tool definitions"
|
||||
breakdown_cat_rules: "Rules"
|
||||
breakdown_cat_skills: "Skills"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Subagent definitions"
|
||||
breakdown_cat_memory: "Memory"
|
||||
breakdown_cat_conversation: "Conversation"
|
||||
header_session_info: "📊 **Session Info**"
|
||||
label_messages: "Messages: {count}"
|
||||
label_estimated_context: "Estimated context: ~{count} tokens"
|
||||
|
|
|
|||
|
|
@ -329,6 +329,16 @@ gateway:
|
|||
label_cost_included: "Costo: incluido"
|
||||
label_context: "Contexto: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Compresiones: {count}"
|
||||
breakdown_header: "🧩 **Desglose del contexto** _(estimado)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "Prompt del sistema"
|
||||
breakdown_cat_tool_definitions: "Definiciones de herramientas"
|
||||
breakdown_cat_rules: "Reglas"
|
||||
breakdown_cat_skills: "Habilidades"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Definiciones de subagentes"
|
||||
breakdown_cat_memory: "Memoria"
|
||||
breakdown_cat_conversation: "Conversación"
|
||||
header_session_info: "📊 **Información de la sesión**"
|
||||
label_messages: "Mensajes: {count}"
|
||||
label_estimated_context: "Contexto estimado: ~{count} tokens"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "Coût : inclus"
|
||||
label_context: "Contexte : {used} / {total} ({pct}%)"
|
||||
label_compressions: "Compressions : {count}"
|
||||
breakdown_header: "🧩 **Répartition du contexte** _(estimée)_"
|
||||
breakdown_line: "• {label} : ~{count} ({pct} %)"
|
||||
breakdown_cat_system_prompt: "Invite système"
|
||||
breakdown_cat_tool_definitions: "Définitions des outils"
|
||||
breakdown_cat_rules: "Règles"
|
||||
breakdown_cat_skills: "Compétences"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Définitions des sous-agents"
|
||||
breakdown_cat_memory: "Mémoire"
|
||||
breakdown_cat_conversation: "Conversation"
|
||||
header_session_info: "📊 **Infos de session**"
|
||||
label_messages: "Messages : {count}"
|
||||
label_estimated_context: "Contexte estimé : ~{count} jetons"
|
||||
|
|
|
|||
|
|
@ -336,6 +336,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "Costas: san áireamh"
|
||||
label_context: "Comhthéacs: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Dlúthuithe: {count}"
|
||||
breakdown_header: "🧩 **Miondealú comhthéacs** _(measta)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "Leid an chórais"
|
||||
breakdown_cat_tool_definitions: "Sainmhínithe uirlisí"
|
||||
breakdown_cat_rules: "Rialacha"
|
||||
breakdown_cat_skills: "Scileanna"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Sainmhínithe fo-ghníomhairí"
|
||||
breakdown_cat_memory: "Cuimhne"
|
||||
breakdown_cat_conversation: "Comhrá"
|
||||
header_session_info: "📊 **Eolas Seisiúin**"
|
||||
label_messages: "Teachtaireachtaí: {count}"
|
||||
label_estimated_context: "Comhthéacs measta: ~{count} comhartha"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "Költség: belefoglalva"
|
||||
label_context: "Kontextus: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Tömörítések: {count}"
|
||||
breakdown_header: "🧩 **Kontextus-bontás** _(becsült)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "Rendszerüzenet"
|
||||
breakdown_cat_tool_definitions: "Eszközdefiníciók"
|
||||
breakdown_cat_rules: "Szabályok"
|
||||
breakdown_cat_skills: "Készségek"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Alügynök-definíciók"
|
||||
breakdown_cat_memory: "Memória"
|
||||
breakdown_cat_conversation: "Beszélgetés"
|
||||
header_session_info: "📊 **Munkamenet-információ**"
|
||||
label_messages: "Üzenetek: {count}"
|
||||
label_estimated_context: "Becsült kontextus: ~{count} token"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "Costo: incluso"
|
||||
label_context: "Contesto: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Compressioni: {count}"
|
||||
breakdown_header: "🧩 **Suddivisione del contesto** _(stimata)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "Prompt di sistema"
|
||||
breakdown_cat_tool_definitions: "Definizioni degli strumenti"
|
||||
breakdown_cat_rules: "Regole"
|
||||
breakdown_cat_skills: "Competenze"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Definizioni dei subagenti"
|
||||
breakdown_cat_memory: "Memoria"
|
||||
breakdown_cat_conversation: "Conversazione"
|
||||
header_session_info: "📊 **Info sessione**"
|
||||
label_messages: "Messaggi: {count}"
|
||||
label_estimated_context: "Contesto stimato: ~{count} token"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "コスト: 含まれています"
|
||||
label_context: "コンテキスト: {used} / {total} ({pct}%)"
|
||||
label_compressions: "圧縮回数: {count}"
|
||||
breakdown_header: "🧩 **コンテキスト内訳** _(推定)_"
|
||||
breakdown_line: "• {label}: 約{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "システムプロンプト"
|
||||
breakdown_cat_tool_definitions: "ツール定義"
|
||||
breakdown_cat_rules: "ルール"
|
||||
breakdown_cat_skills: "スキル"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "サブエージェント定義"
|
||||
breakdown_cat_memory: "メモリ"
|
||||
breakdown_cat_conversation: "会話"
|
||||
header_session_info: "📊 **セッション情報**"
|
||||
label_messages: "メッセージ数: {count}"
|
||||
label_estimated_context: "推定コンテキスト: ~{count} トークン"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "비용: 포함됨"
|
||||
label_context: "컨텍스트: {used} / {total} ({pct}%)"
|
||||
label_compressions: "압축: {count}"
|
||||
breakdown_header: "🧩 **컨텍스트 분석** _(추정치)_"
|
||||
breakdown_line: "• {label}: 약 {count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "시스템 프롬프트"
|
||||
breakdown_cat_tool_definitions: "도구 정의"
|
||||
breakdown_cat_rules: "규칙"
|
||||
breakdown_cat_skills: "스킬"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "서브에이전트 정의"
|
||||
breakdown_cat_memory: "메모리"
|
||||
breakdown_cat_conversation: "대화"
|
||||
header_session_info: "📊 **세션 정보**"
|
||||
label_messages: "메시지: {count}"
|
||||
label_estimated_context: "예상 컨텍스트: 약 {count} 토큰"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "Custo: incluído"
|
||||
label_context: "Contexto: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Compressões: {count}"
|
||||
breakdown_header: "🧩 **Detalhamento do contexto** _(estimado)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "Prompt do sistema"
|
||||
breakdown_cat_tool_definitions: "Definições de ferramentas"
|
||||
breakdown_cat_rules: "Regras"
|
||||
breakdown_cat_skills: "Habilidades"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Definições de subagentes"
|
||||
breakdown_cat_memory: "Memória"
|
||||
breakdown_cat_conversation: "Conversa"
|
||||
header_session_info: "📊 **Informações da sessão**"
|
||||
label_messages: "Mensagens: {count}"
|
||||
label_estimated_context: "Contexto estimado: ~{count} tokens"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "Стоимость: включено"
|
||||
label_context: "Контекст: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Сжатий: {count}"
|
||||
breakdown_header: "🧩 **Разбивка контекста** _(оценка)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "Системный промпт"
|
||||
breakdown_cat_tool_definitions: "Определения инструментов"
|
||||
breakdown_cat_rules: "Правила"
|
||||
breakdown_cat_skills: "Навыки"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Определения субагентов"
|
||||
breakdown_cat_memory: "Память"
|
||||
breakdown_cat_conversation: "Разговор"
|
||||
header_session_info: "📊 **Информация о сеансе**"
|
||||
label_messages: "Сообщений: {count}"
|
||||
label_estimated_context: "Ориентировочный контекст: ~{count} токенов"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "Maliyet: dahil"
|
||||
label_context: "Bağlam: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Sıkıştırmalar: {count}"
|
||||
breakdown_header: "🧩 **Bağlam dökümü** _(tahmini)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "Sistem istemi"
|
||||
breakdown_cat_tool_definitions: "Araç tanımları"
|
||||
breakdown_cat_rules: "Kurallar"
|
||||
breakdown_cat_skills: "Beceriler"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Alt aracı tanımları"
|
||||
breakdown_cat_memory: "Bellek"
|
||||
breakdown_cat_conversation: "Konuşma"
|
||||
header_session_info: "📊 **Oturum Bilgisi**"
|
||||
label_messages: "Mesajlar: {count}"
|
||||
label_estimated_context: "Tahmini bağlam: ~{count} token"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "Вартість: включено"
|
||||
label_context: "Контекст: {used} / {total} ({pct}%)"
|
||||
label_compressions: "Стиснень: {count}"
|
||||
breakdown_header: "🧩 **Розбивка контексту** _(оцінка)_"
|
||||
breakdown_line: "• {label}: ~{count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "Системний промпт"
|
||||
breakdown_cat_tool_definitions: "Визначення інструментів"
|
||||
breakdown_cat_rules: "Правила"
|
||||
breakdown_cat_skills: "Навички"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "Визначення субагентів"
|
||||
breakdown_cat_memory: "Пам'ять"
|
||||
breakdown_cat_conversation: "Розмова"
|
||||
header_session_info: "📊 **Інформація про сеанс**"
|
||||
label_messages: "Повідомлень: {count}"
|
||||
label_estimated_context: "Орієнтовний контекст: ~{count} токенів"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "費用:已包含"
|
||||
label_context: "上下文:{used} / {total}({pct}%)"
|
||||
label_compressions: "壓縮次數:{count}"
|
||||
breakdown_header: "🧩 **上下文明細** _(估算)_"
|
||||
breakdown_line: "• {label}:約 {count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "系統提示詞"
|
||||
breakdown_cat_tool_definitions: "工具定義"
|
||||
breakdown_cat_rules: "規則"
|
||||
breakdown_cat_skills: "技能"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "子代理定義"
|
||||
breakdown_cat_memory: "記憶"
|
||||
breakdown_cat_conversation: "對話"
|
||||
header_session_info: "📊 **工作階段資訊**"
|
||||
label_messages: "訊息數:{count}"
|
||||
label_estimated_context: "預估上下文:~{count} 個 token"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ Future messages in this room will use that transcript until `/reset` or another
|
|||
label_cost_included: "费用:已包含"
|
||||
label_context: "上下文:{used} / {total}({pct}%)"
|
||||
label_compressions: "压缩次数:{count}"
|
||||
breakdown_header: "🧩 **上下文明细** _(估算)_"
|
||||
breakdown_line: "• {label}:约 {count} ({pct}%)"
|
||||
breakdown_cat_system_prompt: "系统提示词"
|
||||
breakdown_cat_tool_definitions: "工具定义"
|
||||
breakdown_cat_rules: "规则"
|
||||
breakdown_cat_skills: "技能"
|
||||
breakdown_cat_mcp: "MCP"
|
||||
breakdown_cat_subagent_definitions: "子代理定义"
|
||||
breakdown_cat_memory: "记忆"
|
||||
breakdown_cat_conversation: "对话"
|
||||
header_session_info: "📊 **会话信息**"
|
||||
label_messages: "消息数:{count}"
|
||||
label_estimated_context: "估计上下文:~{count} 个令牌"
|
||||
|
|
|
|||
|
|
@ -243,3 +243,65 @@ class TestUsageAccountSection:
|
|||
assert account_call["kwargs"]["base_url"] == "https://chatgpt.com/backend-api/codex"
|
||||
assert "📊 **Session Info**" in result
|
||||
assert "📈 **Account limits**" in result
|
||||
|
||||
|
||||
class TestUsageContextBreakdown:
|
||||
"""The /usage output includes the per-category context breakdown."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_breakdown_lines_rendered_for_live_agent(self):
|
||||
agent = _make_mock_agent()
|
||||
runner = _make_runner(SK, cached_agent=agent)
|
||||
session_entry = MagicMock()
|
||||
session_entry.session_id = "sess-bd"
|
||||
runner.session_store.get_or_create_session.return_value = session_entry
|
||||
runner.session_store.load_transcript.return_value = [
|
||||
{"role": "user", "content": "hi"},
|
||||
]
|
||||
event = MagicMock()
|
||||
|
||||
fake_payload = {
|
||||
"categories": [
|
||||
{"id": "system_prompt", "label": "System prompt", "tokens": 4000, "color": "x"},
|
||||
{"id": "tool_definitions", "label": "Tool definitions", "tokens": 6000, "color": "x"},
|
||||
{"id": "conversation", "label": "Conversation", "tokens": 0, "color": "x"},
|
||||
],
|
||||
"estimated_total": 10000,
|
||||
"context_max": 200000,
|
||||
"context_percent": 5,
|
||||
"context_used": 30000,
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
}
|
||||
|
||||
with patch("agent.rate_limit_tracker.format_rate_limit_compact", return_value="RPM: 50/60"), \
|
||||
patch("agent.context_breakdown.compute_session_context_breakdown", return_value=fake_payload):
|
||||
result = await runner._handle_usage_command(event)
|
||||
|
||||
# Localized header + at least the two non-zero category labels appear,
|
||||
# each labelled as a percentage of the estimated total.
|
||||
assert "Context breakdown" in result
|
||||
assert "System prompt" in result
|
||||
assert "Tool definitions" in result
|
||||
assert "4,000" in result # system prompt tokens, comma-formatted
|
||||
assert "40%" in result # 4000 / 10000
|
||||
assert "60%" in result # 6000 / 10000
|
||||
# Zero-token category is dropped, not rendered.
|
||||
assert "Conversation" not in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_breakdown_failure_is_non_fatal(self):
|
||||
"""A breakdown engine error must not break the rest of /usage."""
|
||||
agent = _make_mock_agent()
|
||||
runner = _make_runner(SK, cached_agent=agent)
|
||||
runner.session_store.get_or_create_session.side_effect = RuntimeError("boom")
|
||||
event = MagicMock()
|
||||
|
||||
with patch("agent.rate_limit_tracker.format_rate_limit_compact", return_value="RPM: 50/60"), \
|
||||
patch("agent.context_breakdown.compute_session_context_breakdown",
|
||||
side_effect=RuntimeError("engine down")):
|
||||
result = await runner._handle_usage_command(event)
|
||||
|
||||
# Core usage lines still render; no breakdown header.
|
||||
assert "📊 **Session Token Usage**" in result
|
||||
assert "50,000" in result # total tokens
|
||||
assert "Context breakdown" not in result
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue