150 lines
4.8 KiB
Python
150 lines
4.8 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import httpx
|
|
|
|
from opensquilla.tools.envelope import build_tool_failure_envelope
|
|
from opensquilla.tools.types import RetryableToolInputError, SafeToolError
|
|
|
|
|
|
def test_type_error_tool_failure_is_model_retriable_without_raw_traceback() -> None:
|
|
envelope = build_tool_failure_envelope(
|
|
TypeError("secret raw argument detail"),
|
|
"write_file",
|
|
)
|
|
|
|
assert envelope["status"] == "error"
|
|
assert envelope["tool"] == "write_file"
|
|
assert envelope["error_class"] == "TypeError"
|
|
assert envelope["retry_allowed"] is True
|
|
assert "tool schema" in envelope["user_message"]
|
|
assert "secret raw argument detail" not in envelope["user_message"]
|
|
|
|
|
|
def test_httpx_timeout_exception_is_retriable_with_specific_message() -> None:
|
|
envelope = build_tool_failure_envelope(
|
|
httpx.TimeoutException("secret endpoint"),
|
|
"web_fetch",
|
|
)
|
|
|
|
assert envelope["error_class"] == "TimeoutException"
|
|
assert envelope["retry_allowed"] is True
|
|
assert "timed out" in envelope["user_message"]
|
|
assert "secret endpoint" not in envelope["user_message"]
|
|
|
|
|
|
def test_httpx_connect_error_is_retriable_with_specific_message() -> None:
|
|
envelope = build_tool_failure_envelope(
|
|
httpx.ConnectError("secret host"),
|
|
"web_fetch",
|
|
)
|
|
|
|
assert envelope["error_class"] == "ConnectError"
|
|
assert envelope["retry_allowed"] is True
|
|
assert "could not connect" in envelope["user_message"]
|
|
assert "secret host" not in envelope["user_message"]
|
|
|
|
|
|
def test_json_decode_error_has_specific_message() -> None:
|
|
envelope = build_tool_failure_envelope(
|
|
json.JSONDecodeError("secret payload", "not-json", 0),
|
|
"web_fetch",
|
|
)
|
|
|
|
assert envelope["error_class"] == "JSONDecodeError"
|
|
assert envelope["retry_allowed"] is False
|
|
assert "invalid response payload" in envelope["user_message"]
|
|
assert "secret payload" not in envelope["user_message"]
|
|
|
|
|
|
def test_policy_denial_envelope_has_exactly_five_keys() -> None:
|
|
envelope = build_tool_failure_envelope(
|
|
PermissionError("raw denied detail"),
|
|
"exec_command",
|
|
policy_denial=True,
|
|
error_class_override="PolicyDenied",
|
|
user_message_override="Blocked by policy.",
|
|
)
|
|
|
|
assert set(envelope) == {
|
|
"status",
|
|
"tool",
|
|
"error_class",
|
|
"user_message",
|
|
"retry_allowed",
|
|
}
|
|
assert envelope["status"] == "error"
|
|
assert envelope["tool"] == "exec_command"
|
|
assert envelope["error_class"] == "PolicyDenied"
|
|
assert envelope["user_message"] == "Blocked by policy."
|
|
assert envelope["retry_allowed"] is False
|
|
|
|
|
|
def test_safe_tool_error_instance_message_preserves_five_key_shape() -> None:
|
|
envelope = build_tool_failure_envelope(
|
|
SafeToolError("PDF file not found: input.pdf (resolved=/workspace/input.pdf)", "secret"),
|
|
"pdf",
|
|
)
|
|
|
|
assert set(envelope) == {
|
|
"status",
|
|
"tool",
|
|
"error_class",
|
|
"user_message",
|
|
"retry_allowed",
|
|
}
|
|
assert envelope["status"] == "error"
|
|
assert envelope["tool"] == "pdf"
|
|
assert envelope["error_class"] == "SafeToolError"
|
|
assert "PDF file not found" in envelope["user_message"]
|
|
assert "secret" not in envelope["user_message"]
|
|
|
|
|
|
def test_retryable_tool_input_error_marks_model_correctable_failures() -> None:
|
|
envelope = build_tool_failure_envelope(
|
|
RetryableToolInputError(
|
|
"edit_file could not find old_text. Read the file and retry with exact text."
|
|
),
|
|
"edit_file",
|
|
)
|
|
|
|
assert set(envelope) == {
|
|
"status",
|
|
"tool",
|
|
"error_class",
|
|
"user_message",
|
|
"retry_allowed",
|
|
}
|
|
assert envelope["tool"] == "edit_file"
|
|
assert envelope["error_class"] == "RetryableToolInputError"
|
|
assert envelope["retry_allowed"] is True
|
|
assert "retry with exact text" in envelope["user_message"]
|
|
|
|
|
|
def test_image_attachment_path_safe_tool_error_is_not_generic_internal_error() -> None:
|
|
envelope = build_tool_failure_envelope(
|
|
SafeToolError(
|
|
"Image path is not accessible by the image tool: ab367.png. "
|
|
"Pass a real local file path or HTTP(S) URL. If this is a chat attachment, "
|
|
"answer from the attached image directly instead of calling the image tool.",
|
|
"resolved=/secret/workspace/ab367.png",
|
|
),
|
|
"image",
|
|
)
|
|
|
|
assert set(envelope) == {
|
|
"status",
|
|
"tool",
|
|
"error_class",
|
|
"user_message",
|
|
"retry_allowed",
|
|
}
|
|
assert envelope["tool"] == "image"
|
|
assert envelope["error_class"] == "SafeToolError"
|
|
assert envelope["retry_allowed"] is False
|
|
assert "not accessible by the image tool" in envelope["user_message"]
|
|
assert "chat attachment" in envelope["user_message"]
|
|
assert "internal error" not in envelope["user_message"]
|
|
assert "secret" not in envelope["user_message"]
|