Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
"""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}"
|
||||
)
|
||||
Reference in New Issue
Block a user