Add deterministic context control layer that intercepts prompt construction without modifying existing architecture: - context_engine.py: single choke point (build_context) that assembles structured prompts from ledger + sigil + live window, with token budget enforcement and automatic window shrinking - ledger.py: bounded per-stream JSON state (orientation, blockers, open questions, delta) with hard field/list limits - sigil.py: FIFO shorthand memory (max 15 entries) with deterministic rule-based generation from message patterns - token_gate.py: fast token estimation (~4 chars/token) and hard cap enforcement with configurable MAX_TOKENS/LIVE_WINDOW - redact.py: secret pattern detection (Discord, OpenAI, Anthropic, AWS, Slack, GitHub, Telegram, Bearer, generic key=value) replaced with [REDACTED_SECRET] before any output path All 64 tests passing. No modifications to existing agent spawning, model routing, tool system, or Discord relay architecture. https://claude.ai/code/session_01K7BWJY2gUoJi6dq91Yc7nx
70 lines
1.8 KiB
Python
70 lines
1.8 KiB
Python
"""Tests for ra2.token_gate"""
|
|
|
|
import pytest
|
|
from ra2.token_gate import (
|
|
estimate_tokens,
|
|
check_budget,
|
|
shrink_window,
|
|
TokenBudgetExceeded,
|
|
LIVE_WINDOW_MIN,
|
|
)
|
|
|
|
|
|
class TestEstimateTokens:
|
|
def test_empty_string(self):
|
|
assert estimate_tokens("") == 0
|
|
|
|
def test_short_string(self):
|
|
# "ab" = 2 chars, 2//4 = 0 → clamped to 1
|
|
assert estimate_tokens("ab") == 1
|
|
|
|
def test_known_length(self):
|
|
text = "a" * 400
|
|
# 400 / 4 = 100
|
|
assert estimate_tokens(text) == 100
|
|
|
|
def test_proportional(self):
|
|
short = estimate_tokens("hello world")
|
|
long = estimate_tokens("hello world " * 100)
|
|
assert long > short
|
|
|
|
|
|
class TestCheckBudget:
|
|
def test_within_budget(self):
|
|
assert check_budget(100, limit=200) is True
|
|
|
|
def test_at_budget(self):
|
|
assert check_budget(200, limit=200) is True
|
|
|
|
def test_over_budget(self):
|
|
assert check_budget(201, limit=200) is False
|
|
|
|
|
|
class TestShrinkWindow:
|
|
def test_halves(self):
|
|
assert shrink_window(16) == 8
|
|
|
|
def test_halves_again(self):
|
|
assert shrink_window(8) == 4
|
|
|
|
def test_at_minimum_raises(self):
|
|
with pytest.raises(TokenBudgetExceeded):
|
|
shrink_window(LIVE_WINDOW_MIN)
|
|
|
|
def test_below_minimum_raises(self):
|
|
with pytest.raises(TokenBudgetExceeded):
|
|
shrink_window(2)
|
|
|
|
def test_odd_number(self):
|
|
# 5 // 2 = 2, but clamped to LIVE_WINDOW_MIN (4)
|
|
assert shrink_window(5) == LIVE_WINDOW_MIN
|
|
|
|
|
|
class TestTokenBudgetExceeded:
|
|
def test_attributes(self):
|
|
exc = TokenBudgetExceeded(estimated=7000, limit=6000)
|
|
assert exc.estimated == 7000
|
|
assert exc.limit == 6000
|
|
assert "7000" in str(exc)
|
|
assert "6000" in str(exc)
|