Files
hermes-agent/tests/agent/test_anthropic_kimi_signed_thinking_replay.py
T
Teknium 6b81590c55 test: prune low-value tests suite-wide (wave 1) — 46,820 → 28,106 test functions
Systematic prune per AGENTS.md test policy, one pass over every major
test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli,
cron, tui_gateway, honcho/openviking, root-level):

- DELETE: source-reading tests (read_text/getsource on prod files),
  change-detector tests (exact catalog counts, model-name snapshots,
  config version literals), mock-echo tests (assert a mock returns what
  it was told), assertion-free/trivial tests, near-duplicate
  parametrizations (boundaries + one representative kept), async/sync
  twin duplicates, cosmetic within-file variations.
- KEEP (mandatory): security/redaction/approval guards, message-role
  alternation invariants, prompt-caching/deterministic-call-id
  invariants, issue-number regression tests (deduped), E2E tests.
- 6 test files deleted outright (script-style/no-assert or fully
  redundant); conftest.py, fakes/, fixtures/ untouched.
- tests/acp/conftest.py added: autouse fixture stubs the live
  models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server
  tests performed on every session create — test_server.py 147s → 3.4s,
  and the tests are now genuinely hermetic.
- Sleep-based slowness shrunk where safe (codex_ttfb_watchdog,
  compression_concurrent_fork, etc.); no wall-clock assertion tightened.

Verification: full hermetic suite via scripts/run_tests.sh —
2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall
(baseline: 583s wall, 13,564s subprocess CPU).
2026-07-29 13:10:23 -07:00

108 lines
4.2 KiB
Python

"""Kimi-family endpoints don't need thinking blocks stripped on replay; DeepSeek does."""
from types import SimpleNamespace
from agent.transports import get_transport
from agent.anthropic_adapter import convert_messages_to_anthropic
SIG = "sig-k3"
KIMI = "https://api.kimi.com/coding"
MOONSHOT = "https://api.moonshot.cn/anthropic"
DEEPSEEK = "https://api.deepseek.com/anthropic"
def _thinking_on_replay(base_url, signature=SIG, model="k3"):
"""Normalize a thinking+text turn, store it, convert to the next-turn request,
and return its thinking blocks."""
response = SimpleNamespace(
content=[
SimpleNamespace(type="thinking", thinking="five: 5 11 27 63 88", signature=signature),
SimpleNamespace(type="text", text="5 27 88"),
],
stop_reason="end_turn",
usage=None,
)
normalized = get_transport("anthropic_messages").normalize_response(response)
stored = {
"role": "assistant",
"content": normalized.content or "",
"reasoning_details": (normalized.provider_data or {}).get("reasoning_details"),
}
messages = [
{"role": "user", "content": "q1"},
stored,
{"role": "user", "content": "q2"},
]
_sys, out = convert_messages_to_anthropic(messages, base_url=base_url, model=model)
assistant = [m for m in out if m.get("role") == "assistant"][0]
return [b for b in assistant["content"] if isinstance(b, dict) and b.get("type") == "thinking"]
def test_moonshot_keeps_signed_thinking():
thinking = _thinking_on_replay(MOONSHOT)
assert thinking and thinking[0].get("signature") == SIG
def test_kimi_model_name_on_foreign_gateway_keeps_thinking():
"""A Kimi-family model slug replayed through a non-Kimi gateway hostname
keeps its thinking blocks — upstream Kimi still enforces its replay
semantics no matter what host fronts it (hermes-agent#13848, #17057).
Covers both the named and bare Coding Plan slugs."""
for model in ("kimi-k2.5", "k3"):
assert _thinking_on_replay(DEEPSEEK, model=model), model
def test_orphan_tool_turn_demotes_and_leaks_no_internal_marker():
"""Signed thinking + parallel tool batch interrupted mid-flight (one orphan):
the internal _thinking_signature_invalidated marker must be popped —
never leak into the Kimi payload — while the thinking block itself
replays as-is (Kimi does not enforce signatures)."""
response = SimpleNamespace(
content=[
SimpleNamespace(type="thinking", thinking="plan both reads", signature=SIG),
SimpleNamespace(type="tool_use", id="toolu_1", name="read_file", input={"path": "a.py"}),
SimpleNamespace(type="tool_use", id="toolu_2", name="read_file", input={"path": "b.py"}),
],
stop_reason="tool_use",
usage=None,
)
normalized = get_transport("anthropic_messages").normalize_response(response)
provider_data = normalized.provider_data or {}
stored = {
"role": "assistant",
"content": normalized.content or "",
"reasoning_details": provider_data.get("reasoning_details"),
"tool_calls": [
{"id": tc.id, "type": "function", "function": {"name": tc.name, "arguments": tc.arguments}}
for tc in (normalized.tool_calls or [])
],
}
if provider_data.get("anthropic_content_blocks"):
stored["anthropic_content_blocks"] = provider_data["anthropic_content_blocks"]
messages = [
{"role": "user", "content": "inspect both"},
stored,
{"role": "tool", "tool_call_id": "toolu_1", "content": "a.py: ok"},
# toolu_2 interrupted: no tool result follows (orphan)
{"role": "user", "content": "continue"},
]
_sys, out = convert_messages_to_anthropic(messages, base_url=KIMI, model="k3")
assistant = [m for m in out if m.get("role") == "assistant"][0]
assert "_thinking_signature_invalidated" not in assistant, (
f"internal marker leaked into Kimi payload: {assistant.keys()}"
)
types = [b.get("type") for b in assistant["content"] if isinstance(b, dict)]
assert "thinking" in types, (
"Kimi does not enforce signatures — even orphan-invalidated blocks "
f"must replay as-is: {types}"
)