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:
CharlesMcquade 2026-07-14 22:47:05 -05:00 committed by Teknium
parent 3dccc45032
commit 4fe2fecf54
20 changed files with 654 additions and 22 deletions

View file

@ -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