Improve tool call validation strictness (#4190)
* Improve tool call validation strictness Reject near-miss tool names without executing suggested tools. Require object-shaped tool parameters while preserving only lossless JSON wire-shape normalization. * Tighten tool call argument validation * Simplify tool argument validation tests * Improve tool name suggestions * Simplify tool suggestion helpers * Limit tool suggestions to canonical matches * Allow repair only for tool history replay * Clarify non-object tool argument errors * Inline replay tool argument normalization * Track only successful tool executions * Reject JSON null tool arguments
This commit is contained in:
@@ -49,13 +49,18 @@ async def invoke_file_edit_progress(
|
||||
await on_progress("", file_edit_events=file_edit_events)
|
||||
|
||||
|
||||
def _tool_event_arguments(tool_call: Any) -> dict[str, Any]:
|
||||
arguments = getattr(tool_call, "arguments", {}) or {}
|
||||
return arguments if isinstance(arguments, dict) else {}
|
||||
|
||||
|
||||
def build_tool_event_start_payload(tool_call: Any) -> dict[str, Any]:
|
||||
return {
|
||||
"version": 1,
|
||||
"phase": "start",
|
||||
"call_id": str(getattr(tool_call, "id", "") or ""),
|
||||
"name": getattr(tool_call, "name", ""),
|
||||
"arguments": getattr(tool_call, "arguments", {}) or {},
|
||||
"arguments": _tool_event_arguments(tool_call),
|
||||
"result": None,
|
||||
"error": None,
|
||||
"files": [],
|
||||
@@ -86,7 +91,7 @@ def build_tool_event_finish_payloads(context: AgentHookContext) -> list[dict[str
|
||||
"phase": phase,
|
||||
"call_id": str(getattr(tool_call, "id", "") or ""),
|
||||
"name": getattr(tool_call, "name", ""),
|
||||
"arguments": getattr(tool_call, "arguments", {}) or {},
|
||||
"arguments": _tool_event_arguments(tool_call),
|
||||
"result": result if phase == "end" else None,
|
||||
"error": None,
|
||||
"files": files,
|
||||
|
||||
@@ -75,8 +75,10 @@ def build_goal_continue_message(custom: str | None = None) -> dict[str, str]:
|
||||
return {"role": "user", "content": custom or SUSTAINED_GOAL_CONTINUE_PROMPT}
|
||||
|
||||
|
||||
def external_lookup_signature(tool_name: str, arguments: dict[str, Any]) -> str | None:
|
||||
def external_lookup_signature(tool_name: str, arguments: Any) -> str | None:
|
||||
"""Stable signature for repeated external lookups we want to throttle."""
|
||||
if not isinstance(arguments, dict):
|
||||
return None
|
||||
if tool_name == "web_fetch":
|
||||
url = str(arguments.get("url") or "").strip()
|
||||
if url:
|
||||
@@ -90,7 +92,7 @@ def external_lookup_signature(tool_name: str, arguments: dict[str, Any]) -> str
|
||||
|
||||
def repeated_external_lookup_error(
|
||||
tool_name: str,
|
||||
arguments: dict[str, Any],
|
||||
arguments: Any,
|
||||
seen_counts: dict[str, int],
|
||||
) -> str | None:
|
||||
"""Block repeated external lookups after a small retry budget."""
|
||||
@@ -119,9 +121,11 @@ _OUTSIDE_PATH_PATTERN = re.compile(r"(?:^|[\s|>'\"])((?:/[^\s\"'>;|<]+)|(?:~[^\s
|
||||
|
||||
def workspace_violation_signature(
|
||||
tool_name: str,
|
||||
arguments: dict[str, Any],
|
||||
arguments: Any,
|
||||
) -> str | None:
|
||||
"""Return a stable cross-tool signature for the outside-workspace target."""
|
||||
if not isinstance(arguments, dict):
|
||||
return None
|
||||
for key in ("path", "file_path", "target", "source", "destination"):
|
||||
val = arguments.get(key)
|
||||
if isinstance(val, str) and val.strip():
|
||||
@@ -151,7 +155,7 @@ def _normalize_violation_target(raw: str) -> str:
|
||||
|
||||
def repeated_workspace_violation_error(
|
||||
tool_name: str,
|
||||
arguments: dict[str, Any],
|
||||
arguments: Any,
|
||||
seen_counts: dict[str, int],
|
||||
) -> str | None:
|
||||
"""Return an escalated error after repeated bypass attempts."""
|
||||
|
||||
Reference in New Issue
Block a user