from __future__ import annotations import asyncio import json from dataclasses import replace from typing import Any from opensquilla.engine import Agent, AgentConfig from opensquilla.sandbox.config import SandboxSettings from opensquilla.sandbox.integration import configure_runtime, reset_runtime from opensquilla.tool_boundary import ToolCall from opensquilla.tools.dispatch import build_tool_handler from opensquilla.tools.registry import ToolRegistry, get_default_registry from opensquilla.tools.types import ( InteractionMode, ToolContext, ToolSpec, current_tool_context, ) class _Provider: provider_name = "fake" def chat(self, messages, tools=None, config=None): raise AssertionError("provider should not be used") async def list_models(self) -> list[Any]: return [] def test_agent_wires_tool_context_runtime_events(tmp_path) -> None: runtime_events_path = tmp_path / "runtime_events.jsonl" agent = Agent( provider=_Provider(), config=AgentConfig( runtime_events_path=str(runtime_events_path), metadata={"agent_id": "configured-agent"}, ), session_key="agent-session", tool_context=ToolContext(), ) assert agent._tool_context is not None assert agent._tool_context.on_runtime_event is not None agent._tool_context.on_runtime_event( { "feature": "fresh_read_guard", "name": "fresh_read_guard.blocked", "tool": "edit_file", "outcome": "blocked", } ) events = [ json.loads(line) for line in runtime_events_path.read_text(encoding="utf-8").splitlines() ] assert len(events) == 1 assert events[0]["feature"] == "fresh_read_guard" assert events[0]["name"] == "fresh_read_guard.blocked" assert events[0]["tool"] == "edit_file" assert events[0]["outcome"] == "blocked" assert events[0]["session_key"] == "agent-session" assert events[0]["agent_id"] == "configured-agent" assert "created_at" in events[0] assert "timestamp" in events[0] def test_agent_preserves_tool_context_runtime_event_identity(tmp_path) -> None: runtime_events_path = tmp_path / "runtime_events.jsonl" agent = Agent( provider=_Provider(), config=AgentConfig( runtime_events_path=str(runtime_events_path), metadata={"agent_id": "configured-agent"}, ), session_key="agent-session", tool_context=ToolContext(), ) assert agent._tool_context is not None assert agent._tool_context.on_runtime_event is not None agent._tool_context.on_runtime_event( { "feature": "fresh_read_guard", "name": "fresh_read_guard.blocked", "agent_id": "tool-agent", "session_key": "tool-session", "outcome": "blocked", } ) event = json.loads(runtime_events_path.read_text(encoding="utf-8")) assert event["agent_id"] == "tool-agent" assert event["session_key"] == "tool-session" def test_agent_backfills_none_tool_context_runtime_event_identity(tmp_path) -> None: runtime_events_path = tmp_path / "runtime_events.jsonl" agent = Agent( provider=_Provider(), config=AgentConfig( runtime_events_path=str(runtime_events_path), metadata={"agent_id": "configured-agent"}, ), session_key="agent-session", tool_context=ToolContext(), ) assert agent._tool_context is not None assert agent._tool_context.on_runtime_event is not None agent._tool_context.on_runtime_event( { "feature": "fresh_read_guard", "name": "fresh_read_guard.blocked", "agent_id": None, "session_key": None, "outcome": "blocked", } ) event = json.loads(runtime_events_path.read_text(encoding="utf-8")) assert event["agent_id"] == "configured-agent" assert event["session_key"] == "agent-session" def test_agent_runtime_event_callback_reaches_prebuilt_tool_handler(tmp_path) -> None: runtime_events_path = tmp_path / "runtime_events.jsonl" ctx = ToolContext() registry = ToolRegistry() async def emit_event() -> str: active = current_tool_context.get() assert active is ctx assert active.on_runtime_event is not None active.on_runtime_event( { "feature": "tool_runtime", "name": "tool_runtime.probe", "outcome": "used", } ) return "ok" registry.register( ToolSpec( name="emit_event", description="Emit a runtime event.", parameters={}, required=[], ), emit_event, ) handler = build_tool_handler(registry, ctx) agent = Agent( provider=_Provider(), config=AgentConfig( runtime_events_path=str(runtime_events_path), metadata={"agent_id": "configured-agent"}, ), tool_handler=handler, session_key="agent-session", tool_context=ctx, ) assert agent._tool_context is ctx assert ctx.on_runtime_event is not None import asyncio result = asyncio.run(handler(ToolCall("call-1", "emit_event", {}))) assert result.content == "ok" event = json.loads(runtime_events_path.read_text(encoding="utf-8")) assert event["feature"] == "tool_runtime" assert event["name"] == "tool_runtime.probe" assert event["agent_id"] == "configured-agent" assert event["session_key"] == "agent-session" def test_agent_runtime_event_context_reaches_replaced_builtin_tool_handler( tmp_path, ) -> None: runtime_events_path = tmp_path / "runtime_events.jsonl" target = tmp_path / "src" / "app.py" target.parent.mkdir() target.write_text( "def run():\n" " if enabled:\n" " return 1\n", encoding="utf-8", ) configure_runtime( SandboxSettings( sandbox=False, security_grading=False, allow_legacy_mode=True, ), workspace=tmp_path, ) try: handler_ctx = ToolContext( is_owner=True, interaction_mode=InteractionMode.UNATTENDED, workspace_dir=str(tmp_path), elevated="bypass", ) handler = build_tool_handler(get_default_registry(), handler_ctx) agent_ctx = replace(handler_ctx, session_key="agent-session") agent = Agent( provider=_Provider(), config=AgentConfig( runtime_events_path=str(runtime_events_path), metadata={"agent_id": "configured-agent"}, ), tool_handler=handler, session_key="agent-session", tool_context=agent_ctx, ) assert agent.tool_handler is not None result = asyncio.run( agent.tool_handler( ToolCall( "call-1", "edit_file", { "path": str(target), "old_text": "if enabled:\n return 1\n", "new_text": "if enabled:\n return 2\n", }, ) ) ) finally: reset_runtime() assert result.is_error is False events = [ json.loads(line) for line in runtime_events_path.read_text(encoding="utf-8").splitlines() ] assert any(event["name"] == "edit_file.flexible_match_used" for event in events) assert events[0]["agent_id"] == "main" assert events[0]["session_key"] == "agent-session"