preserve native web tools after network guard rejection

This commit is contained in:
pewdiepie-archdaemon
2026-09-18 21:41:24 +00:00
parent 75b2420239
commit f086f66a4f
2 changed files with 158 additions and 1 deletions
+59 -1
View File
@@ -6788,6 +6788,49 @@ _COMPACT_EMAIL_UNSUBSCRIBE_TOOLS = {
}
def _blocked_network_recovery_tools(events: Sequence[Dict[str, Any]]) -> Set[str]:
"""Preserve native recovery tools named by our own network guard.
Compact routing is recomputed after every tool result. When the native
shell/Python guard rejects ad-hoc HTTP it deliberately tells the model to
use bounded web/PDF tools instead. Dropping those schemas on the next
round makes that recovery impossible. Match only the harness-authored
guard prefix on tool-role results so user or fetched content cannot widen
the tool surface.
"""
for event in reversed(list(events or [])[-6:]):
if not isinstance(event, dict):
continue
candidates: list[str] = []
if (
(event.get("type") == "tool_output" or isinstance(event.get("tool"), str))
and isinstance(event.get("output"), str)
):
candidates.append(event["output"])
elif event.get("role") == "tool" and isinstance(event.get("content"), str):
candidates.append(event["content"])
elif event.get("role") == "user" and isinstance(event.get("content"), list):
for part in event["content"]:
if not isinstance(part, dict) or part.get("type") != "tool_result":
continue
result = part.get("content")
if isinstance(result, str):
candidates.append(result)
elif isinstance(result, list):
candidates.extend(
item.get("text", "") for item in result
if isinstance(item, dict) and item.get("type") == "text"
)
for content in candidates:
if re.match(
r"^(?:bash|python): ad-hoc HTTP (?:downloads are|access is) disabled "
r"when native web tools are available\.",
content.strip(),
):
return {"pdf_extract", "web_fetch", "web_search"}
return set()
def _compact_native_route_tools(
tool_names: Optional[Set[str]],
text: str,
@@ -23292,6 +23335,11 @@ async def stream_agent_loop(
route_tools.difference_update(_irrelevant_local_media_web_tools)
if turn_contract is not None and tool_surface != "full":
route_tools = set(turn_contract.offered)
if prompt_compact:
route_tools.update(
_blocked_network_recovery_tools(tool_events)
- set(disabled_tools)
)
# Native OpenAI-compatible endpoints use the compact system prompt by
# default even when no explicit per-model surface preference is stored.
# Keep the schema bundle consistent with that prompt: artifact routes
@@ -23625,6 +23673,7 @@ async def stream_agent_loop(
# demonstrably shorter than session history. Keep route-local system and
# injected context plus the source's newest request (which may be
# multimodal), and restore only missing antecedent conversation.
tool_events = [] # Available to initial and follow-up route construction.
_initial_route_source_messages = messages
if history_session is not None:
try:
@@ -23713,7 +23762,6 @@ async def stream_agent_loop(
total_start = time.time()
time_to_first_token = None
first_token_received = False
tool_events = [] # Persist tool executions for history reload
round_texts = [] # Cleaned text per round for history reload
round_models = [] # Actual model for each corresponding round
round_endpoint_ids = []
@@ -25050,6 +25098,16 @@ async def stream_agent_loop(
} - _source_companion_tools)
_relevant_tools.update(_source_companion_tools)
_network_recovery_floor = (
_blocked_network_recovery_tools(tool_events)
- set(disabled_tools)
- set(_hard_blocked_tools)
)
if _network_recovery_floor:
if _relevant_tools is None:
_relevant_tools = set()
_relevant_tools.update(_network_recovery_floor)
_active_route_state = {
"messages": messages,
"mcp_schemas": mcp_schemas,
@@ -0,0 +1,99 @@
from src.agent_loop import _blocked_network_recovery_tools
def test_network_guard_preserves_native_recovery_tools():
events = [
{
"type": "tool_output",
"tool": "python",
"output": (
"python: ad-hoc HTTP access is disabled when native web tools "
"are available. Use pdf_extract for online PDFs, web_fetch for "
"a concrete page, or web_search for discovery."
),
},
]
assert _blocked_network_recovery_tools(events) == {
"pdf_extract",
"web_fetch",
"web_search",
}
def test_structured_tool_result_preserves_native_recovery_tools():
events = [{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "call-1",
"content": [{
"type": "text",
"text": (
"bash: ad-hoc HTTP downloads are disabled when native web "
"tools are available. Use pdf_extract for online PDFs."
),
}],
}],
}]
assert _blocked_network_recovery_tools(events) == {
"pdf_extract",
"web_fetch",
"web_search",
}
def test_persisted_internal_tool_event_preserves_recovery_tools():
events = [{
"tool": "bash",
"output": (
"bash: ad-hoc HTTP downloads are disabled when native web tools "
"are available. Use pdf_extract for online PDFs."
),
"exit_code": 1,
}]
assert _blocked_network_recovery_tools(events) == {
"pdf_extract",
"web_fetch",
"web_search",
}
def test_tool_event_state_exists_before_initial_route_build():
from pathlib import Path
source = (Path(__file__).parents[1] / "src" / "agent_loop.py").read_text()
init = source.index("tool_events = [] # Available to initial")
first_build = source.index("_route_state = await _build_route_request_state(", init)
assert init < first_build
def test_network_recovery_floor_precedes_round_schema_materialization():
from pathlib import Path
source = (Path(__file__).parents[1] / "src" / "agent_loop.py").read_text()
floor = source.index("_network_recovery_floor = (")
active_state = source.index("_active_route_state = {", floor)
schemas = source.index("all_tool_schemas = _tool_schemas_for_route", active_state)
assert floor < active_state < schemas
def test_non_tool_event_cannot_widen_route():
events = [{
"type": "message",
"role": "user",
"output": (
"python: ad-hoc HTTP access is disabled when native web tools are "
"available. Use pdf_extract for online PDFs."
),
}]
assert _blocked_network_recovery_tools(events) == set()
def test_unrelated_tool_failure_does_not_widen_route():
events = [{"type": "tool_output", "tool": "bash", "output": "Permission denied"}]
assert _blocked_network_recovery_tools(events) == set()