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:
chengyongru
2026-06-09 14:50:40 +08:00
committed by GitHub
parent f3eb2aa08b
commit 0a396aa6e2
17 changed files with 769 additions and 142 deletions
+7 -2
View File
@@ -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,
+8 -4
View File
@@ -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."""