fix(middleware): preserve pre-Relay tool request snapshot

When Relay rewrites a tool call before Hermes' own pipeline runs,
tool_request middleware received the Relay-rewritten args as
original_args, while tool_execution middleware already got the model's
call (function_args). Pass function_args through as original_args so both
middleware kinds see the same pre-Relay snapshot.

Sequential chaining of request rewrites itself landed in f67d490f71
(#133159); this keeps only the remaining pre-Relay delta of #82092.
This commit is contained in:
Teknium committed 2026-10-31 22:07:49 -07:00
1 parent b47fe6ecc6
commit 78f7db6349
3 files changed
+30 -4

No files matched your search

+1
View File
@@ -811,6 +811,7 @@ def _run_agent_tool_execution_middleware(
request_result = apply_tool_request_middleware(
function_name,
relay_args,
original_args=function_args,
skip_relay=True,
**tool_hook_ids(agent, effective_task_id, tool_call_id),
)
+5 -4
View File
@@ -100,12 +100,13 @@ def apply_llm_request_middleware(request: dict[str, Any], **context: Any) -> Req
def apply_tool_request_middleware(
tool_name: str, args: dict[str, Any], **context: Any
tool_name: str, args: dict[str, Any], *, original_args: dict[str, Any] | None = None, **context: Any
) -> RequestMiddlewareResult:
"""Apply registered tool request middleware; ``{"args": {...}}`` replaces the effective tool
arguments before hooks, guardrails, approvals, and execution see them."""
original_args = _safe_copy(args)
current_args = _safe_copy(original_args)
arguments before hooks, guardrails, approvals, and execution see them. ``original_args`` is the
model's pre-Relay call when ``args`` already carries Relay rewrites (defaults to ``args``)."""
original_args = _safe_copy(args if original_args is None else original_args)
current_args = _safe_copy(args)
trace: list[dict[str, Any]] = []
session_id = str(context.get("session_id") or "")
@@ -65,3 +65,27 @@ def test_request_middleware_in_place_mutation_does_not_leak(seen, kind, apply):
# Each callback gets its own copy of the payload and of the pre-middleware snapshot.
assert "meddler" not in result.payload["steps"]
assert seen[kind][1] == result.original_payload == original == {"input": ["original"]}
def test_tool_request_original_args_is_the_pre_relay_call(seen, monkeypatch):
from types import SimpleNamespace
from agent import relay_tools, tool_executor
def relay_rewrites(_name, args, callback, **_kwargs):
relay_args = {**args, "relay": True}
return callback(relay_args), relay_args
dispatched = []
monkeypatch.setattr(relay_tools, "execute", relay_rewrites)
monkeypatch.setattr(tool_executor, "_dispatch_authorized_once", lambda _agent, _state, ref, **_k: dispatched.append(ref.args))
tool_executor._run_agent_tool_execution_middleware(
SimpleNamespace(session_id="s1"), function_name="fixture", function_args={"input": ["original"]},
effective_task_id="t1", tool_call_id="c1", execute=None,
)
# Middleware sees Relay's rewrite as ``args`` but the model's own call as ``original_args``.
assert seen["tool_request"] == ({"input": ["original"], "relay": True, "steps": ["first"]},
{"input": ["original"]})
assert dispatched == [{"input": ["original"], "relay": True, "steps": ["first"], "second": True}]