Files
2026-07-13 13:12:33 +08:00

196 lines
6.1 KiB
Python

from __future__ import annotations
from types import SimpleNamespace
from typing import Any
import pytest
from opensquilla.session.models import AgentTaskStatus
from opensquilla.session.terminal_reply import build_terminal_reply, sanitize_agent_error
RAW_INTERNAL_STRINGS = (
"Gateway task timeout",
"Stream idle for more than",
"Context overflow is in the current turn",
"current_turn_context_exhausted",
"Provider output limit reached before completion",
)
@pytest.mark.parametrize(
("payload", "expected_fragment"),
[
(
{
"status": "timeout",
"terminal_reason": "timeout",
"error_class": "TimeoutError",
"error_message": "Gateway task timeout: Stream idle for more than 60s",
},
"timed out",
),
(
{
"status": "failed",
"terminal_reason": "error",
"error_class": "RuntimeError",
"error_message": "boom",
},
"failed",
),
(
{
"status": "abandoned",
"terminal_reason": "shutdown_timeout",
},
"stopped",
),
(
{
"status": "cancelled",
"terminal_reason": "cancelled",
},
"cancelled",
),
(
{
"status": "failed",
"terminal_reason": "provider_request_budget_exhausted",
"error_class": "provider_request_budget_exhausted",
"error_message": '{"fallback_reason":"provider_request_budget_exhausted"}',
},
"automatic context compaction",
),
(
{
"status": "failed",
"terminal_reason": "error",
"error_class": "current_turn_context_exhausted",
"error_message": (
"Context overflow is in the current turn's recent tool calls "
"or reasoning tail; history compaction cannot reduce it."
),
},
"too large",
),
(
{
"status": "failed",
"terminal_reason": "provider_request_too_large",
"error_class": "provider_request_too_large",
"error_message": "provider request too large",
},
"too large",
),
(
{
"status": "failed",
"terminal_message": (
"Context overflow is in the current turn's recent tool calls "
"or reasoning tail; history compaction cannot reduce it."
),
},
"too large",
),
(
{
"status": "failed",
"terminal_reason": "output_truncated",
"error_class": "provider_output_truncated",
"error_message": "Provider output limit reached before completion",
},
"output limit",
),
(
{
"status": "failed",
"terminal_reason": "sandbox_threshold_exceeded",
"error_class": "sandbox_threshold_exceeded",
"error_message": "Autonomous execution paused after repeated sandbox denials.",
},
"paused",
),
(
{
"status": "failed",
"terminal_reason": "max_iterations",
"error_class": "max_iterations",
},
"maximum number of steps",
),
(
{
"status": "failed",
"terminal_reason": "turn_output_token_budget_exceeded",
"error_class": "turn_output_token_budget_exceeded",
},
"budget limit",
),
],
)
def test_build_terminal_reply_returns_user_readable_messages(
payload: dict[str, Any],
expected_fragment: str,
) -> None:
message = build_terminal_reply(payload)
assert message
assert expected_fragment in message.lower()
assert not message.startswith("terminal_reason=")
for raw in RAW_INTERNAL_STRINGS:
assert raw not in message
def test_sandbox_pause_reply_is_distinct_from_generic_failure() -> None:
# The denial-pause code must not collapse into the generic "failed" sentence:
# it needs its own actionable, resume-oriented phrasing.
paused = build_terminal_reply(
{
"status": "failed",
"terminal_reason": "sandbox_threshold_exceeded",
"error_class": "sandbox_threshold_exceeded",
}
)
generic = build_terminal_reply(
{"status": "failed", "terminal_reason": "error", "error_class": "RuntimeError"}
)
assert paused != generic
assert "resume" in paused.lower()
def test_build_terminal_reply_accepts_agent_task_record_like_objects() -> None:
record = SimpleNamespace(
status=AgentTaskStatus.TIMEOUT,
terminal_reason="timeout",
error_class="TimeoutError",
error_message="Gateway task timeout: Stream idle for more than 120s",
)
message = build_terminal_reply(record, surface="terminal", locale="en")
assert "timed out" in message.lower()
for raw in RAW_INTERNAL_STRINGS:
assert raw not in message
def test_sanitize_agent_error_rewrites_raw_provider_output_limit_message() -> None:
error_class, message = sanitize_agent_error(
"Provider output limit reached before completion",
fallback_error_class="provider_output_truncated",
)
assert error_class == "provider_output_truncated"
assert "output limit" in message.lower()
assert "Provider output limit reached before completion" not in message
def test_sanitize_agent_error_rewrites_raw_iteration_timeout_message() -> None:
error_class, message = sanitize_agent_error(
"Iteration 1 exceeded iteration_timeout",
fallback_error_class="iteration_timeout",
)
assert error_class == "iteration_timeout"
assert "timed out" in message.lower()
assert "Iteration 1 exceeded iteration_timeout" not in message